- 投稿日:2022-02-14T23:50:47+09:00
【実用編】【DB】discord.js v13 ユーザーごとにプロフィールを作成する
前書き 私がメインで開発している「ヒトリン」というボットにて、最近ユーザーごとにプロフィールを作成する機能を追加したため紹介します。 どのようなものか MongoDBを使用し、ユーザーごとに様々な情報を格納したデータを作成し、プロフィールを作成する。 今回は、MongoDBの使用方法からプロフィールを作成しコマンドで表示させるところまでのフルフルバージョンを紹介する予定でござんす。 MongoDB 上記のリンクからMongoDBのサイトへリンクしサインインまたは新規登録を完了してください。 ほとんど英語ですが頑張ってどんどん次に進みましょう。 一番右の「FREE」となっているものを選択し、「CREATE」をクリックしてください。 ここら辺は任意で選択してください。(なんもいじんなくても良いかもです) 右下にある「Create Cluster」をクリック ここら辺も適当でいいんですが、最後の「Where would you like to connect from?」は注意。(訳:あんちゃんはどっから接続するんや?) 特に理由がなければには右の「Add My Current IP Address」をクリックしてください。(意味:現在のIPアドレスを追加) 注意 IPアドレスを変更した場合、ここの設定から使用中のIPアドレスを追加してください。 完了したら「Finish and Close」をクリックしてください。 このような画面になれば設定は完了です。 npm MongoDBをNode.jsで使用するために「mongoose」をインストールします。 console:cmd npm install mongoose コーディング いよいよコーディングをしていきます。コーディングをしたくてしたくてたまらなく ☆禁断症状☆ が出ていた方もいらっしゃることかと存じます。ご安心ください。 ボットの核となるファイル「index.js」等に記述をしていきます。 index.js const mongoose = require('mongoose'); ~ ~ ~ mongoose //mongooseについて .connect(<接続するために必要>, { useNewUrlParser: true, //任意 }) .then(() => { console.log('データベースに接続したんだゾ'); }) .catch((error) => { console.log(error); //エラー出力 }); <接続するために必要> のところに入れるものを説明します。 先ほどのこの画面に来ます。 「Connect」をクリックし、「Connect your application」をクリック。 「Node.js」で、バージョンを任意のものに選択したら以下のようなよくわからない文字列が出てきます。 今回の例 mongodb+srv://test:<password>@cluster0.3afev.mongodb.net/<myFirstDatabase>?retryWrites=true&w=majority <password> には先ほど設定した「ユーザー」と「パスワード」の「パスワード」を入力 <myFirstDatabase> には任意のデータベースの名前を入力 そしたらその文字列を <接続するために必要> のところに入れます。 index.js mongoose //mongooseについて .connect(mongodb+srv://test:<password>@cluster0.3afev.mongodb.net/<myFirstDatabase>?retryWrites=true&w=majority, { useNewUrlParser: true, //任意 }) .then(() => { console.log('データベースに接続したんだゾ'); }) .catch((error) => { console.log(error); //エラー出力 }); そしたら次に任意のフォルダを作成し、そのフォルダの中に任意のJSファイルを作成します。今回は「models」というフォルダに「profileSchema.js」というJSファイルを作成します。 profileSchmea.js にはデータベースにどのようにどんな種類のデータを保存するのか、というものを書いていきます。 今回はユーザーのID、ユーザーネーム、アバターを保存していきます。 profileSchema.js const mongoose = require('mongoose'); //mongoDBを使用するためのおまじない const profileSchema = new mongoose.Schema({ _id: { type: String }, //ユーザーID name: { type: String }, //ユーザーネーム avatar: { type: String }, //アバター }); const model = mongoose.model('Profiles', profileSchema); module.exports = model; type: String 等の説明 type: の後には型が入ります。まぁプログラミング勉強してる方ならほぼ誰でもわかるかと思いますが一応紹介しておきます。 String: 文字列("あいうえお" "僕は天才、人生満喫中" 文字同士の計算はできない 例:"2" + "3" = "23" ) Number: 数字(1とか2とか。数字同士は計算できる 例:1+9=137193821738 ←嘘) Boolean: 真偽(「はい(True)」か「いいえ(False)」の二択) などがあります。 ちな「False」の読み方は「ファルス」ではなく「フォールス(フォルス)」です。 「NULL」は「ヌル」ではなく「ナル」です。だって「ヌル」だと「NULL NULL」の読み「ヌルヌル」じゃないですか...w 読み方を間違えると意外と恥ずかしいので一応勉強しておいて損はないかもです。そんなことよりコードの勉強した方がいいんですけどね。羞恥心にはかなわねぇや。 ところで「ユーザーID」って言ってんのになんで文字列の「String」を使ってんだ?おめぇーって思った方。 (???) 良い質問ですね IDってなんか番号っぽいから数字の「Number」使えよってお思いでしょう。 実は以下の理由があります。 Stringのほうが情報量多め どんなフォーマットで対応できるように 文字列にする!って決めたほうが楽 ( 引用:https://qiita.com/sato-shin/items/0ec486d9c1bf98d9bf4c ) このようにすることでユーザープロフィールをどのようにデータベースに保存するかを決められました。 次に、このデータベースを実際に保存・使用していきたいと思います。 基本的にいつ保存するかというのは自由なんですが、私はコマンドを実行する「index.js」のinteractionCreate内の上部に書いています。 ※ちなみにinteractionCreate内の上部に書いても、実際に動作するのはコマンドを実行するときのみです。 index.js const profileModel = require('./models/profileSchema'); //先ほど作成したスキーマを参照 ~ ~ ~ client.on('interactionCreate', async interaction => { //メッセージを受け取ったら const profileData = await profileModel.findOne({ _id: interaction.user.id }); if (!profileData) { const profile = await profileModel.create({ _id: interaction.user.id, //ユーザーID name: interaction.user.username, //ユーザーネーム avatar: interaction.user.displayAvatarURL({ format: 'png' }), //アバター }); profile.save(); console.log('データベースに保存したよ: ' + interaction.user.tag); //一応ログとしてコンソールに出力 } if (!interaction.isCommand()) return; const command = client.commands.get(interaction.commandName); if (!command) return; //コマンド以外無視 }); ガチ気を付けてマジンコマジマジ async / await の非同期処理で行わないとエラーが発生せず、正常に動作しない可能性があります。これで僕は何度も苦しめられたんです!!!皆さんは気をつけましょうね!!!!!!絶対にな!!!絶対にダゾ!!!! <任意モデル>.create({ ナンチャラー }) で新しく作成できます。(設定によっては重複エラー発生するときありますので注意) <任意モデル>.findOne({ 検索 }) でその検索に当てはまるものを一つ取り出します。 <任意モデル>.findOneAndUpdate({ 検索 }, { ナンチャラー }) で検索に当てはまるものを取り出し、それにナンチャラーします。はい。 思ったより割と簡単でしょ? ということでこれで作成とかは完了しました。そしたら今度はコマンドで表示してみましょう。 今回は「profile.js」というJSファイルを作成し表示させてみます。 profile.js const profileModel = require('../models/profileSchema'); const { SlashCommandBuilder } = require('@discordjs/builders'); require('dotenv').config(); module.exports = { data: new SlashCommandBuilder() .setName('profile') .setDescription('指定したユーザーのプロフィールを表示します。') .addUserOption(option => option.setName('対象').setDescription('ユーザーを選択')), async execute(interaction) { const user = interaction.options.getUser('対象'); //コマンドオプションのユーザー選択から const profileData = await profileModel.findOne({ _id: user.id }); //ユーザーのIDでデータベースから合うものを一つ取り出す。 await interaction.reply(`ID: ${profileData.id}\nユーザーネーム: ${profileData.name}\nアバター: ${profileData.avatar}`); }, }; 上記のようなコードを記述することでプロフィールを表示するコマンドが作成できます。 これだけではまだ「ユーザーの情報を表示する」みたいなコマンドだけで充分ですが先ほどのスキーマをいろいろといじれば標準機能よりもはるかに便利で交流的なプロフィールを作成できるはずです! ぜひみなさんもプロフィール機能を自分なりに作ってみてください! 宣伝 私がメインで開発しているBOT「ヒトリン」。 ...使用してみませんか? 開発中でまだまだ「くそ便利すぎマジワロタ」とまではいかないですが、ぜひみなさんと成長していけるようなBOTを作成したいと思っています。 ぜひご検討ください。
- 投稿日:2022-02-14T22:29:14+09:00
IFTTTのレシピの節約方法(1つのレシピでPCの音楽、Youtube音楽、youtube動画を操作)
#はじめに 「WSLのUbuntuでPC内の音楽データをGoogleHomeで操作する」 「WSLのUbuntuでyoutube音楽をGoogleHomeで操作する」 「WSLのUbuntuでyoutube動画をGoogleHomeで操作する」 を1つのIFTTTのレシピで操作する方法です。 IFTTTが有料になり、IFTTTのレシピ数を節約するのが目的です。 ●GoogleHomeで下記の音声入力を行う。 音楽 あいみょん:PC内の音楽ファイルを再生する 音楽 YT あいみょん:youtubeの音楽を再生する 音楽 動画 あいみょん:youtubeの動画を再生する 音楽がIFTTTのトリガーコマンドです。 その後の入力文字にYT、動画があった場合、プログラムで処理を分けています。 #プログラム(node.js) ●firebaseとyoutubeのKeyは自分のKeyに置き換えてください var firebase = require("firebase"); var iconv = require('iconv-lite'); //firebase config var config = { apiKey: "XXXXXXXXXXXXXXXXXXXXXXXX", authDomain: "XXXXXXXXXX.firebaseapp.com", databaseURL: "https://XXXXXXXX.firebaseio.com", projectId: "XXXXXXXX", storageBucket: "XXXXXXXX.appspot.com", messagingSenderId: "XXXXXXXXXXXX" }; firebase.initializeApp(config); const Youtube = require('youtube-node'); const youtube = new Youtube(); youtube.setKey('XXXXXXXXXXXXXXXXXXXXXXXXXX'); //jsonからvalueに一致する値取得 const getJsonData = (value, json) => { for (var word in json) if (value == word) return json[word] return json["default"] } //database更新時 const path = "/googlehome" const key = "word" const db = firebase.database() db.ref(path).on("value", function(changedSnapshot) { //値取得 let value = changedSnapshot.child(key).val() if (!value) return console.log(value) var keyword = value.replace(/ /g,""); const keyword2 = value.split(" "); flag=0; if(keyword2[0] === "動画") { flag=1; console.log("動画を再生します"); var keyword = keyword.replace("動画",""); } if(keyword2[0] === "YT") { flag=2; console.log("youtubeを再生します"); var keyword = keyword.replace("YT",""); } //コマンド const STOP = "killall mplayer mpv"; if(keyword === "停止"){ command = STOP } else if(keyword === "止め て"){ command = STOP } else { command = "未定義" } console.log(command) //コマンド実行 var exec = require('child_process').exec; exec(STOP); if(command === '未定義' && flag==1) { var exec = require('child_process').exec; exec(STOP); play_youtube(keyword); } if(command === '未定義' && flag==2) { var exec = require('child_process').exec; exec(STOP); command = "mpv --no-video --ytdl-format=bestaudio ytdl://ytsearch10:" var exec = require('child_process').exec; exec(command.concat(keyword),(err, stdout, stderr) => { if (err) { console.log(`stderr: ${stderr}`) return } console.log(`stdout: ${stdout}`) } ) console.log(command.concat(keyword)); } if(command === "未定義" && flag==0) { var exec = require('child_process').exec; exec(STOP); command = 'cat /mnt/j/music/htdocs/music/list.txt | grep "' + keyword + '"'; var exec = require('child_process').exec; exec(command, {maxBuffer: 40000*1024}, function(error, stdout, stderr) { if (error !== null) { console.log('指定された曲はありません'); command = '/home/XXXXXX/speak.sh 指定された曲はありません' exec(command); } else { console.log(stdout); } }); command = 'cat /mnt/j/music/htdocs/music/list.txt | grep "' + keyword + '">/home/XXXXXX/playlist'; console.log(command) var exec = require('child_process').exec; exec(command, {maxBuffer: 40000*1024}, function(error, stdout, stderr) { if (error !== null) { console.log('Exec error: ' + error); } }); function mplay(){ command = 'mplayer -shuffle -playlist /home/XXXXXX/playlist'; console.log(command) var exec = require('child_process').exec; exec(command, {maxBuffer: 40000*1024}, function(error, stdout, stderr) { if (error !== null) { console.log('Exec error: ' + error); } }); } setTimeout(mplay,3000); } //firebase clear db.ref(path).set({[key]: ""}); }) function play_youtube(keyword) { youtube.search(keyword, 1, {'type':'video'} , function(error, result) { if (error) { console.log(error); return ; } for (const item of result.items) { if (item.id.videoId) { var exec = require('child_process').exec; command = "'/mnt/c/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe' --new-window 'https://www.youtube.com/watch?v="+item.id.videoId+"'" console.log(command); exec(command); } } }); } #まとめ ●IFTTのレシピがいままで3つ必要だったのが1つでできました。
- 投稿日:2022-02-14T19:02:42+09:00
AWS Lambda (Node.js) 上で discord.js を使おうとしてつまずいた
何につまずいたのか ざっくり言うと、 Node.js で記載した AWS Lambda の関数上で最新版の discord.js を使おうとしたら使えなかった。 なぜ使えなかったのか AWS Lambda で使える Node.js のバージョンが現状 v14 系までなのに対し、最新版の discord.js は v16.6.0 以上でしか動かない。 現状の打開策 どうしても使いたいなら古いバージョンの discord.js ( 12.5.3 がギリギリ Node.js v14 系に対応している模様)を使うしかない。ただし、非推奨になっているのでオススメはできない。しっかり対策したいなら他のライブラリを探すか、AWS Lambda が Node.js v16 系に対応するのを待つしかなさそう・・・。
- 投稿日:2022-02-14T17:02:58+09:00
Webの勉強はじめてみた その31 〜クライアントのフレームワーク〜
N予備校「プログラミング入門Webアプリ」を受講しています。 今回は第4章7,8節です。 モジュールバンドラー 複数ファイルのJavaScriptを1つにまとめられる Node.jsのコアモジュールをブラウザでも利用できる webpack モジュールバンドラーとしてwebpack を使用。 babel-loaderも同時にインストール。 yarn add webpack@4.26.1 webpack-cli@3.1.2 @babel/core@7.1.6 @babel/preset-env@7.1.6 babel-loader@8.0.4 --dev babel-loader - 最新のJavaScriptで書かれたコードをブラウザが実行できるバージョンにコンパイルするモジュール webpackの設定ファイル webpack.config.js module.exports = { context: __dirname + '/app', entry: './entry', output: { path: __dirname + '/public/javascripts', filename: 'bundle.js' }, mode: 'none', module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } ] } }; __dirname - 現在の場所を示す既に用意された変数(定数?) mode - bundle.jsの圧縮の方法 test - 正規表現を使うときの決まり文句 exclude - 外部ファイル。今回はnode_modulesから引っ張ってくる entry.js 'use strict'; import dc from 'damage-calc'; import crypto from 'crypto'; const root = document.getElementById('root'); root.innerHTML = `<p> 攻撃力 100, 防御 50, 防御貫通 30 のダメージは ${dc.effectiveDamage(100, 50, 30)} </p> <p> ${crypto.randomBytes(8).toString('hex')} </p> `; importで書かれたモジュールがbundle.jsに書き込まれる。 npx webpack webpack.configの内容をもとに、bundle.jsに出力。 expressで作られたlayout.pugを変更 layout.pug script(defer, src='/javascripts/bundle.js') defer属性 - スクリプトを文書の解析完了後に JavaScript を実行することをブラウザに示す属性。ページ表示速度が向上する。 jQuery HTML の文章の横断や操作、イベントハンドリング、アニメーションなどを 行うための簡単な API を提供するライブラリ インストール。 yarn add jquery@3.4.1 entry.js import $ from 'jquery'; const block = $('#block'); const scalingButton = $('#scaling-button'); scalingButton.click(() => { block.animate({ width: '200pt', height: '200pt' }, 2000); block.animate({ width: '100pt', height: '100pt' }, 2000); }); clickは古いらしい。 entry.js scalingButton.on('click', () => { block.animate({ width: '200pt', height: '200pt' }, 2000); block.animate({ width: '100pt', height: '100pt' }, 2000); }); まとめ この辺り、ちょっとサーバーとクライアントの境が自分の中で曖昧になってきてる。整理しないと。
- 投稿日:2022-02-14T09:10:50+09:00
Renovateでライブラリの更新を自動化する
Renovateとは 時間の経過とともに古くなっていくライブラリの更新を自動化してくれるツール 具体的には以下のようなことをしてくれます。 リポジトリをスキャンして依存関係を検出する 依存関係の更新があるかどうかを確認する 依存関係を更新するためのコミットとマージ/プルリクエストの作成 様々なプラットフォーム、マネージャーに対応しています。 Renovate良いポイント renovateを適用したいリポジトリを選択できる プルリクを受けるスケジュールを作成できる issueに可視化できるダッシュボードが追加される リリースノートを表示してくれるので変更点が一目でわかる 似たようなパッケージをグループ化してプルリクしてくれる babel関連のパッケージなどはデフォルトでまとめてくれる まとめたいパッケージは自分でも指定できる Node.jsのバージョンにも対応 volta(package.jsonのvoltaフィールドに対応) LTSのリリースのアップグレードを提供してくれる インストール 公式を参照しながらやればすぐ終わる 共有プリセットを作る 全てのリポジトリで同じ設定が重複しないように共有プリセットを作ることができます。 renovateの設定を変更したいときに、個々のリポジトリにあるrenovate.jsonを変更することなく、一つのファイルで設定を管理することができます。 GitHubで共有プリセットを作成する 新しいリポジトリを作成する(例: renovate-config) リポジトリ名はなんでもOK。公式を参考にrenovate-config を作成する renovate-configリポジトリにdefault.jsonを作成する デフォルトではdefault.jsonを確認します。名前付きプリセットを使用することもできますが、今回は割愛します 共有可能なプリセットはJSON形式のみ使用可能です default.jsonにrenovateの設定を書いてデフォルトブランチ(例: main)に適用する renovateはデフォルトブランチにあるdefault.jsonファイルを探します これで共有プリセットの完成です(簡単!) 共有プリセットを使用する renovateを適用したリポジトリにあるrenovate.jsonを以下に書き換えます。 「github>ユーザー名/リポジトリ名」のようにextends配列で参照するだけ。 { "extends": ["github>yukiji/renovate-config"] } 今後renovateの設定を変更したいときはrenovate-config/default.jsonを変更するだけなので管理が楽になります。 色々設定してこうなった 一旦個人開発はこちらで運用してみようと思います。 下記の参考資料にある各記事も参考にしてみました。 renovateはデフォルトのconfig:baseに既に色々と設定されています。 基本的にconfig:baseをオーバーライドしていく形で設定していきます。 { "$schema": "https://docs.renovatebot.com/renovate-schema.json", //設定項目の補完 "extends": [ "config:base" ], "timezone": "Asia/Tokyo", // タイムゾーンを日本仕様に "schedule": [ "every weekend" // プルリクは週末(土日)だけ ], "prConcurrentLimit": 10, // プルリクの上限は10まで "packageRules": [ { "groupName": "webpack family", // webpack関連はまとめて1つのプルリクにする "matchPackagePatterns": [ "webpack", "-loader$" ] } ] } renovateは設定項目数がめっちゃあります。(一応全部見ました) 実際に運用してみて課題点などがあればその都度、公式ドキュメントを参考にしながら改善を加えていけばいいかなぁと個人的には思います。 まとめ まだ運用して間もないですが、週末にプルリクが作成され、平日にリリースノートを見ながら適用していくスタイルが個人的にはフィットしてます。 ライブラリに対しての情報感度が高くなるものいいですね。 テストを書いて自動マージなんかも今後はやっていきたい〜。 Tips renovate configをチェックする renovateのconfigが正しいかチェックするツールがあります。 renovateパッケージに含まれるrenovate-config-validatorを設定ファイルがある場所で実行します。タイポしているとエラーで怒ってくれます。 ファイル名がdefault.jsonだと反応してくれないところが気になる。 $ npm i -g renovate $ renovate-config-validator INFO: Validating renovate.json INFO: Config validated successfully 後から適用したいリポジトリを選択する GitHubのSettings -> Applications -> RenovateのConfigureを選択 初回インストールの手順と同じで、Renovateを適用したいリポジトリを全て選択するか個別に選択する 気になるconfigメモ enabledManagers prCreation Monorepo Presets 公式ドキュメント 参考資料
- 投稿日:2022-02-14T08:17:34+09:00
Node.jsのESmodule設定でcloudinary機能が使いたとき import cloudinary from 'cloudinary' 思った通りに機能しない。
結論 Node.jsでESmodule設定でもCloudinaryを使用するなら下記のようにCommonjsを使う。 v2がないと駄目っぽい? app.js import { createRequire } from 'module'; const require = createRequire(import.meta.url); ... const cloudinary = require('cloudinary').v2; // ok // import cloudinary from 'cloudinary' // No 使用方法をちゃんと読みましょう(教訓) import cloudinary from 'cloudinary' だと機能しない? 機能しないと思います。(私は機能しませんでした。) やりたいこと ReactからformDataを使って送ってきた画像をCloudinaryに保存して、画像のURL(path)のみMongoDBに保存。 multer-storage-cloudinaryを使用することで、Cloudinaryに保存することができる。 起きたエラー 下記のupload.array('file')の部分で、Cloudinaryに保存するまでは大丈夫でした。 しかし、upload.array('file')のミドルウェアの後、CreatePostの処理にいってくれませんでした... route.js router.post('/', auth, upload.array('file'), createPost); まとめ 解決策としては、最初に記述したようにV2をrequireするだけでした。 このエラーでかなりの時間を消費したので、誰かの助けになればと思います。
- 投稿日:2022-02-14T03:20:08+09:00
corepack prepare npm@latest--activateができないのでpython3でスクリプト書いて対処。
動機 Nodejs 16.9.0以上で標準搭載されたらしいcorepackでpnpm、yarn、npmを実行していたのですが、corepackのパッケージマネージャーを指定するコマンドで最新バージョンを簡単に指定できないようなので簡易的にPython3でスクリプトを書きました。 なお、この件につきましたはこのようなissueが上がっています。 --latestオプションの検討 近いうちにlatestオプションが追加されるかもしれません。それまでのつなぎとして… 「なんでjsじゃないんだ…?Nodejsなのだからjsで書こうよ…(´・ω・`)」という声が聞こえてきそうですが、遺憾ながらこのスクリプトは私のdotfilesを構築している時に書いた別目的の既存コードを改変した副産物なのでご了承ください… jsで書くには「引数処理を標準モジュールでできるか?」が不明のためちょっとやる気がでない現状です。 ちょっとした解説 やってることはただnpm search <マネージャー(npm|pnpm|yarn)>の結果をripgrepでversionだけ取得し、それをもとにactivateしているだけです。 これで最新バージョンのNodejsのパッケージマネージャーがactivate状態になります。 心配の方は先にdry-runをしてください。 python3 -u corepack-update.py -d 最新アプデ corepack-update.py """Corepack latest version updater Requirement: - python3 - ripgrep: https://github.com/BurntSushi/ripgrep - Nodejs >=16.9.0 or `npm i -g corepack` Usage: - activate latest version python3 corepack-update.py - dry-run mode python3 corepack-update.py --dry-run python3 corepack-update.py -d """ import argparse from os import system import subprocess from typing import Literal def system_call(command: str): """ Reference https://stackoverflow.com/questions/18739239/python-how-to-get-stdout-after-running-os-system """ return subprocess.getoutput(command) def corepack_enabled(manager_name: str): return system_call(f"corepack enabled {manager_name}") def activate_corepack(manager_name: str, version: str): system(f"corepack prepare {manager_name}@{version} --activate") def get_latest_version(manager_name: str, is_debug: bool): version_regexp = "[0-9]?[0-9]\\.[0-9]?[0-9]\\.[0-9]?[0-9]" cmd = f"npm search {manager_name} | rg \"^{manager_name} .* {version_regexp}\" \ | rg -o \"{version_regexp}\"" if is_debug: print(color("Execute command", "cyan") + f": {cmd}") return system_call(cmd) # `-o` is ripgrep only match option def get_args(): parser = argparse.ArgumentParser() parser.add_argument( "-d", "--dry-run", help="No activate corepack. just stdout", action="store_true") parser.add_argument( "-e", "--enabled", help="Enable management by corepack.", action="store_true") return (parser.parse_args()) def color(string: str, mode: Literal["green", "red", "yellow", "cyan"]): colors = { "red": "\033[31m", "green": "\033[32m", "yellow": "\033[33m", "cyan": "\033[36m"} return f'{colors[mode]}{string}\033[0m' def main(): args = get_args() managers = ["npm", "pnpm", "yarn"] if is_dry_run := args.dry_run: print(color("INFO: Dry run mode enabled.\n\ Please visually check that the version assigned by the code is correct.\n", "cyan")) for manager_name in managers: manager_latest_version = get_latest_version(manager_name, is_dry_run) if args.enabled: print(color("INFO: Enabling management by corepack...", "cyan")) corepack_enabled(manager_name) if is_dry_run: print( f"Probably... the latest {manager_name} version: {manager_latest_version}") search_cmd = f"npm search {manager_name}" print(color("Execute command", "cyan") + f": {search_cmd}") print( f"{search_cmd} Docs: https://docs.npmjs.com/cli/v6/commands/npm-search") system(search_cmd) print("\n") else: print(f"{manager_name} active: {manager_latest_version}") activate_corepack(manager_name, manager_latest_version) if __name__ == "__main__": main()