目次
- 前置き
- 雑なプロンプトを手始めに実行する
- テスト可能な環境を作って、AIにフィードバックする
- 細かい指定をする
- 完成したもの
- 感じたこと
1. 前置き
- 今回は、ChatGPT-4に、app.config.tsにあるアプリのバージョン(appVersion)と、ビルドバージョン(buildVersion)を更新するスクリプトを書いてもらうことをゴールとする。
- 背景
- expoでは、app.config.ts内で、通常アプリのバージョンとビルドバージョンを管理している。
- EAS Updateを使う際に、app.config.ts外で全く同じアプリのバージョンを別で定義する必要がでてきた。
- 複数の箇所に散らばるアプリのバージョンの書き換え忘れを防止するため、スクリプトで自動でバージョンを更新できるようにしたい。
- その手始めに、今回app.config.ts内のバージョンを書き換えれるようにしていく。
2. 雑なプロンプトを手始めに実行する
- ポイント: 軽い要件を満たすプロンプトを渡して、AIが出力がどんなもんかをまずは試す。
- 30%ぐらいの要件を満たすようなプロンプトを考えて実行する。
- 一発で作らないことと、GPT-4を使うのがコツ。GPT-3だとしんどかった
命令
typescriptで、./app.config.tsの中のappVersionの値を更新するスクリプトを書いてください。
更新する値は環境変数のVERSIONで渡されます。
appVersionには、'2.0.0'の文字列形式で、ゼロ埋めはせずに更新してください。
# app.config.ts
const appVersion = '2.0.0'
const buildVersion = 11911
const runtimeVersion = '3251792'
出力
import fs from 'fs';
import path from 'path';
const filePath = path.resolve(__dirname, './app.config.ts');
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
console.error('Failed to read file', err);
return;
}
const newVersion = process.env.VERSION;
if (!newVersion) {
console.error('VERSION environment variable is not set');
return;
}
const result = data.replace(/(const appVersion = ')(.*?)(')/, `$1${newVersion}$3`);
fs.writeFile(filePath, result, 'utf8', err => {
if (err) {
console.error('Failed to write file', err);
return;
}
console.log(`Updated appVersion to ${newVersion}`);
});
});
3. テスト可能な環境を作って、AIにフィードバックする
- 現実のテスト可能な環境を作る
- 作ったスクリプトを動かしてみて、問題ある点を見つける
- 問題を踏まえて命令を修正。このとき、命令は前回のものを上書きするのがよい。
- 前回の命令も引き継いて、新しい出力をだすので、前回の会話を上書きするのが無難
- 一発のプロンプトを保存できるようになるのがすごく良い。出来上がったスクリプトとともにプロンプトを保存しておけば、要件が変わったときも、修正が簡単に済み、メンテナンス性が高い。(複数のやり取りを保存しておくと、要件が変わったときに変更が大変)
- 1~3を繰り返して、ほしいプロンプトを獲得する
1. 環境
- ポイント: スクリプトを動かす本番同様の環境を作る。
- ここの検証市やすさと本番との差異のなさによって、完成までの速度が変わる。
- 以下を準備。実際に存在するファイルで試した。
typescript: 4.9.4
ts-node: 10.9.1
実行ファイルの場所: .config/scripts/updateAppConfigAppAndBuildVersion.ts'
対象のファイル: app.config.ts
2. スクリプトを実行してみる
- ファイルが見つからない以下のエラーになる。
- 今回は、見た感じ問題はわかったが、AIに聞くのもあり。
- 例: 「実行すると、以下のエラーが発生しました。書きを踏まえて、スクリプトを修正してください」
Failed to read file [Error: ENOENT: no such file or directory, open '.config/scripts/app.config.ts'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '.config/scripts/app.config.ts'
}
3. 命令の修正
typescriptで、./app.config.tsの中のappVersionの値を更新するスクリプトを書いてください。
更新する値は環境変数のVERSIONで渡されます。
appVersionには、'2.0.0'の文字列形式で、ゼロ埋めはせずに更新してください。
+ スクリプトは、./.config/scripts/updateAppConfigAppAndBuildVersion.ts配置してください。
# app.config.ts
const appVersion = '2.0.0'
const buildVersion = 11911
const runtimeVersion = '3251792'
出力
import fs from 'fs';
import path from 'path';
const envVersion = process.env.VERSION;
if (!envVersion) {
console.error('No VERSION environment variable set');
process.exit(1);
}
const appConfigPath = path.join(__dirname, '../../app.config.ts');
const appConfigContent = fs.readFileSync(appConfigPath, 'utf8');
const newAppConfigContent = appConfigContent.replace(
/^(const appVersion = ')(.*?)(')$/m,
`$1${envVersion}$3`
);
fs.writeFileSync(appConfigPath, newAppConfigContent);
console.log(`appVersion updated to ${envVersion} in app.config.ts`);
4. 1~3を繰り返す
- 90%のものをAIで作って、手で編集するとかはしない。100%ほしいスクリプトを聞き続ける。
- 手を加えていない一発で再現できるプロンプトを作りあげるのがメンテナンス性のためには重要。
4. 細かい指定をする
ほしいプロンプトができたら、最後よりよいコードにする。
例:
- ライブラリを使わないように指定する -> glob ライブラリを使わずに行ってください。
- linterの規則などを教える -> クォーテーションは、シングルクォーテーションを使ってください
5. 完成したもの
- 以下のプロンプトとスクリプトが完成した。
- 完成したプロンプトはスクリプトと一緒にcommitしておく。
- そうすることによって、要件が変わったときも、プロンプトを変更するだけでいいので、よりよいメンテナンス性が得られる。
プロンプト
typescriptで、./app.config.tsの中のappVersionの値を更新するスクリプトを書いてください。
更新する値は環境変数のVERSIONで渡されます。渡される値の形式は2.0.0などのXX.YY.Zの形式です。
appVersionには、'2.0.0'の文字列形式で、ゼロ埋めはせずに更新してください。
また、./app.config.tsの中のbuildVersionも更新してください。
buildVersionは、合わされたピリオド区切りのバージョン(XX.YY.Z)を、YYをゼロ埋めした後、XXYYZにして数値化して、末尾に0をつけて更新してください。例えば、2.2.1であれば、20210になります。
コマンドは、package.jsonのscriptsに記述され、実行されます。
glob ライブラリを使わずに行ってください。
クォーテーションは、シングルクォーテーションを使ってください
実行するスクリプトは、./.config/scripts/配下に作成する前提で、お願いします。
console.logを利用する場合は、かわりにconsole.debugを使ってください
# app.config.ts
// アプリのバージョン
const appVersion = '2.0.0'
// ビルドが変わるごとに上がる。基本的には、appVersionを数値化して末尾に0をつけたもの。Storeに同じbuildVersionのものをSubmitできない。
const buildVersion = 11911
// eas updateで使うnative layerのバージョン。ネイティブのライブラリをinstall or update するたびに末尾をインクリメントする
const runtimeVersion = '3251792'
スクリプト
import fs from 'fs';
import path from 'path';
// Config file path
const configFile = path.join(__dirname, '..', '..', 'app.config.ts');
// Get new version from environment variable
const newVersion = process.env.VERSION;
if (!newVersion) {
console.debug('No VERSION environment variable provided');
process.exit(1);
}
console.debug(`New version: ${newVersion}`);
// Construct new build version
const [major, minor, patch] = newVersion.split('.');
const newBuildVersion = `${major}${minor.padStart(2, '0')}${patch}0`;
console.debug(`New build version: ${newBuildVersion}`);
// Read app.config.ts file
let configFileContent = fs.readFileSync(configFile, 'utf-8');
// Replace app version and build version
configFileContent = configFileContent.replace(/const appVersion = '([^']*)'/, `const appVersion = '${newVersion}'`);
configFileContent = configFileContent.replace(/const buildVersion = (\d+)/, `const buildVersion = ${newBuildVersion}`);
// Write the updated content back to app.config.ts
fs.writeFileSync(configFile, configFileContent);
console.debug(`Updated appVersion and buildVersion in app.config.ts`);
6. 感じたこと
細かいノウハウ
- 30%から始めて、どんどん要件を追加していくのが楽
- 実現したい機能要件、入力の要件、出力の要件を順番に書いていくと楽。
- 入力の要件については、フォーマットや入力の与え方も重要。
- 開発体験がここで変わってくる。
- Typescriptにおいては、
npm run
などで実行することを考えると、環境変数で与えれるのが楽- ファイル名やファイルの場所などもパス解決もtypescriptは貧弱なので、事前に指定しておきたい
- プロンプトの修正はチャット形式でやるのではなく、都度上書きして、一つのプロンプトを作るのが良い
- スクリプトとともにプロンプトも保存するとメンテナンス性が高まるので
生成AIとのエンジニアの共存
- AIからコードを作るのもありだが、AIは大きすぎるコンテキストを扱えない問題がある。
- コンテキストが小さい、コードを作るスクリプトをAIに作らせるという形の共存も良さそう。
今後
- フロントエンド周りでは、作成で必要とされるファイルの総量が多いので、そのあたりを作成してくれるコードをAIで作っていきたい。