diff --git a/.agents/skills/add-script/SKILL.md b/.agents/skills/add-script/SKILL.md index a8a5df5..9ed5dbb 100644 --- a/.agents/skills/add-script/SKILL.md +++ b/.agents/skills/add-script/SKILL.md @@ -108,14 +108,17 @@ pnpm new -- --app= --name= --license ``` `--license` はユーザーが不要と明示した場合のみ省略する。 +ScriptUI を選んだ場合は `--ui=scriptui` を追加する: + +```bash +pnpm new -- --app= --name= --license --ui=scriptui +``` 実行後に生成されるファイル: - `es.config.mjs` に `` エントリが追加される - `src///index.ts` にテンプレートが作成される -**ScriptUI を選んだ場合**: 生成後、`index.ts` の `entry` を `entryUI` に書き換える(後述)。 - ### 5. 用途(purpose)をファイルに記録する `src///index.ts` を開き、ファイル先頭に以下のコメントブロックを追加する: @@ -135,14 +138,19 @@ pnpm new -- --app= --name= --license `@workflow` はユーザーの `purpose` から推測して記述する。不明な場合は `TODO` として残す。 -**ScriptUI の場合**: `entry` を `entryUI` に変更し、以下のテンプレートに書き換える: +**ScriptUI の場合**: 生成済みテンプレートが `entryUI` と `__ES_THIS__` を使っていることを確認する: ```typescript import "../../init"; -import { entryUI } from "../lib/lib"; +import { entry, entryUI } from "../../lib/lib"; entryUI("", __ES_THIS__, (win) => { - // TODO: UI を構築する + const runButton = win.add("button", undefined, "実行"); + runButton.onClick = () => { + entry("", () => { + // TODO: 実行処理を書く + }); + }; }); ``` diff --git a/.codex/README.md b/.codex/README.md new file mode 100644 index 0000000..a32ff75 --- /dev/null +++ b/.codex/README.md @@ -0,0 +1,7 @@ +# Codex Configuration + +このディレクトリは Codex 向けのリポジトリローカル設定を置く場所です。 + +- 共通ルールの正本はルートの `AGENTS.md` です。 +- `.codex/config.toml` はローカルサブエージェントの並列度だけを定義します。 +- コマンドの自動承認や sandbox / network 権限は、この repo の設定では広げません。 diff --git a/.codex/config.toml b/.codex/config.toml new file mode 100644 index 0000000..a46aa15 --- /dev/null +++ b/.codex/config.toml @@ -0,0 +1,6 @@ +# Keep repo-local Codex config permission-neutral. +# Command approvals remain controlled by the active Codex session policy. + +[agents] +max_threads = 4 +max_depth = 2 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index a55a185..0000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,6 +0,0 @@ -# GitHub Copilot Instructions - -このリポジトリの作業指示の正本は `AGENTS.md` です。 -まず `AGENTS.md` を確認し、対象ファイルに応じて `.agents/instructions/` と `docs/` を参照してください。 - -Copilot 固有の補助情報が必要な場合のみ、`.github/instructions/` と `.github/skills/` を参照してください。 diff --git a/.github/instructions/create-script.instructions.md b/.github/instructions/create-script.instructions.md deleted file mode 100644 index d85ef53..0000000 --- a/.github/instructions/create-script.instructions.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -applyTo: "scripts/newScript.mjs,scripts/addApp.mjs,es.config.mjs" -description: "スクリプト追加・アプリ追加の手順と規約" ---- - -# スクリプト作成・管理ルール - -## 新規スクリプト追加 - -### CLI モード(推奨) - -```bash -pnpm new -- --app= --name= --license -``` - -- `--app`: 対象アプリ ID(`aeft`, `ilst`, `phxs` 等)。必須 -- `--name`: スクリプト名。必須。PascalCase 推奨 -- `--license`: ライセンスバナーを含める場合に指定 - -### 予約名 - -以下はスクリプト名として使用不可: - -- `lib` — アプリ固有ユーティリティディレクトリと衝突 -- `types` — 型定義ディレクトリと衝突 -- `tests` — テストディレクトリと衝突 - -## 新規アプリ追加 - -```bash -pnpm add-app -- --app= -``` - -以下が自動生成される: - -- `src/{appId}/tsconfig.json` -- `src/{appId}/types/index.d.ts` -- `src/{appId}/lib/.gitkeep` -- `src/{appId}/example/index.ts` -- `es.config.mjs` に `scripts.{appId}` キーを追加 - -## es.config.mjs の構造 - -```mjs -export default { - scripts: { - aeft: [{ name: "ScriptName", version: "0.0.1", build: true, license: true }], - ilst: [...], - phxs: [...], - }, - common: [{ name: "tests", version: "0.0.1", build: true, license: true }], -}; -``` - -- `scripts.{appId}`: アプリ別のスクリプト配列。ビルド時に対応する tsconfig が使用される -- `common`: アプリに依存しないスクリプト(テスト等)。ルート tsconfig が使用される diff --git a/.github/instructions/extendscript.instructions.md b/.github/instructions/extendscript.instructions.md deleted file mode 100644 index f717841..0000000 --- a/.github/instructions/extendscript.instructions.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -applyTo: "src/**/*.ts" -description: "ExtendScript (ES3) 向け TypeScript コーディングルール。src/ 以下のファイル編集時に適用。" ---- - -# ExtendScript コーディングルール - -## ES3 制約 - -- ExtendScript は ES3 ベース。`src/` 以下の `.ts` コードは ES3 にトランスパイルされる -- `tsconfig.json` の `target: "ES3"` は変更しないこと -- ES3 トランスパイル時のプロンプト警告は無視してよい - -## 使用禁止機能 - -- `Symbol` / `Promise` は `src/` 以下で使用しないこと(ポリフィル対応不可) -- `alert()` / `confirm()` は使用しないこと(`new Window()` を使う) - -## ディレクトリ構成 - -- スクリプトはアプリ別に `src/{appId}/{ScriptName}/index.ts` に配置される -- アプリ ID: `aeft`(After Effects)、`ilst`(Illustrator)、`phxs`(Photoshop)等 -- 共通ライブラリ: `src/lib/` -- アプリ固有ライブラリ: `src/{appId}/lib/` -- 共通型定義: `src/types/` -- アプリ固有型定義: `src/{appId}/types/` - -## init の import - -すべてのスクリプトの `index.ts` で、先頭に `import "../../init"` を記述すること。 -これによりポリフィルが読み込まれる。 - -## import パス - -スクリプトからの相対 import パスは以下の通り: - -```ts -import "../../init"; // ポリフィル -import { entry, foo } from "../lib/lib"; // 共通 & アプリ固有ユーティリティ(1行でまとめて import 可能) -``` - -`src/{appId}/lib/lib.ts` は `src/lib/lib.ts` を re-export しているため、 -共通ユーティリティとアプリ固有ユーティリティを `"../lib/lib"` 1本でインポートできる。 - -## ポリフィル - -ポリフィルの詳細は `docs/polyfills.md` を参照。 - -- ES6 以降の機能追加時は `src/lib/polyfills/esnext-shim.js` への追加を検討する -- ポリフィル追加時は `src/tests/index.ts` にテストを追加する - -## ダイアログ作成 - -ユーザーへの入力要求・確認には `new Window()` を使用する: - -```javascript -const dialog = new Window("dialog", "サンプルダイアログ"); -dialog.add("statictext", undefined, "これはサンプルダイアログです。"); -dialog.add("button", undefined, "OK"); -dialog.add("button", undefined, "Cancel"); - -// ユーザーがキャンセルした場合は処理を中断 -if (dialog.show() !== 1) return; -``` - -## 入れ子の三項演算子の使用禁止 - -ExtendScript には三項演算子の評価順序に既知のバグがある。 -**入れ子(ネスト)になった三項演算子は使用しないこと。** -1層のみで副作用を伴わない三項演算子は許容される。 - -```ts -// ✅ OK: 1層の三項演算子 -const value = condition ? "a" : "b"; - -// ❌ NG: 入れ子の三項演算子 -const value = a ? (b ? "x" : "y") : "z"; - -// ✅ OK(入れ子を避ける場合): 即時呼び出し無名関数 + if -const value = (() => { - if (a) return b ? "x" : "y"; - return "z"; -})(); - -// ✅ OK(let でよい場合): let + if で代入 -let value = "z"; -if (a) value = b ? "x" : "y"; -``` - -参考: https://uske-s.hatenablog.com/entry/2021/10/26/184709 diff --git a/.github/skills/add-script/SKILL.md b/.github/skills/add-script/SKILL.md deleted file mode 100644 index 3970301..0000000 --- a/.github/skills/add-script/SKILL.md +++ /dev/null @@ -1,180 +0,0 @@ ---- -name: add-script -description: "ExtendScript プロジェクトに新しいスクリプトを追加する。Use when: 新しいスクリプトを作成する、After Effects / Illustrator / Photoshop 向けのスクリプトを追加する、スクリプトの実装を開始する、aeft / ilst / phxs スクリプトを作りたい。" -argument-hint: "対象アプリ(After Effects / Illustrator / Photoshop)、何をするスクリプトか、を教えてください" ---- - -# ExtendScript 新規スクリプト追加 - -プロジェクトのスクリプト生成ツールを使って新規スクリプトを作成し、用途をコメントとして記録する。 -ユーザーの選択に応じてスケルトンで止めるか、実装まで進める。 - -## When to Use - -- 「スクリプトを作りたい」「新しいスクリプトを追加して」と言ったとき -- 対象の Adobe アプリと、何をするスクリプトかが示されたとき -- 新しい自動化処理・作業ツールを始めるとき - ---- - -## Procedure - -### 1. 基本情報をまとめて収集する - -ユーザーが指定していない項目を `vscode_askQuestions` で **一度にまとめて** 質問する。 -すでに提供されている項目はスキップする(重複して聞かない)。 - -収集する情報: - -| 項目 | 内容 | 必須 | -| --------- | ---------------------------------------------- | -------------------------- | -| `app` | 対象アプリ | ✅ 必須 | -| `purpose` | 何をするスクリプトか(詳しいほど精度が上がる) | ✅ 必須 | -| `uiType` | スクリプトの種類(通常 or ScriptUI) | ✅ 必須 | -| `license` | ライセンス表記を含めるか | 任意(デフォルト: 含める) | - -**`app` の選択肢:** - -```yaml -header: "app" -question: "どの Adobe アプリ向けのスクリプトですか?" -options: - - label: "After Effects(アフターエフェクト)" - description: "アプリID: aeft" - - label: "Illustrator(イラストレーター)" - description: "アプリID: ilst" - - label: "Photoshop(フォトショップ)" - description: "アプリID: phxs" -``` - -**`uiType` の選択肢:** - -```yaml -header: "uiType" -question: "スクリプトの種類を選んでください" -options: - - label: "通常スクリプト(シンプル)" - description: "実行すると処理が走るシンプルなスクリプト。ほとんどの自動化処理はこちら。" - recommended: true - - label: "ScriptUI(ウィンドウ型)" - description: "ボタン・テキストボックスなどがあるウィンドウを表示するスクリプト。" -``` - -**`purpose` の記載例(ユーザーに提示して入力を促す):** - -> 例: 「選択しているレイヤーの名前を一括でリネームする。プレフィックス文字列を指定できるようにしたい」 -> 例: 「アクティブなコンポの全レイヤーのアニメーションをフレーム1にリセットする」 - -purpose が一言しか書かれていない・処理の流れが読み取れない場合は、追加情報/仕様提案をまとめてを **1度だけ** まとめて質問する。 - -### 2. スクリプト名を提案・確認する - -`purpose` から英語のスクリプト名を推測し、ユーザーに確認する。 - -命名規則は既存のスクリプトから推測する(一般的には PascalCase)。 -`purpose` が日本語の場合は英語に意訳してから命名する。 - -**予約名(使用不可)**: `lib`, `types`, `tests`, `example` - -`vscode_askQuestions` で確認する: - -```yaml -header: "scriptName" -question: "スクリプト名は「<提案名>」でよいですか?英数字のみ使用できます(ファイル名になります)。" -``` - -ユーザーが別の名前を希望する場合はそれを使用する。 - -### 3. 実装方針をユーザーに確認する - -`vscode_askQuestions` で以下を尋ねる: - -```yaml -header: "implementation" -question: "スクリプトの実装をどこまで進めますか?" -options: - - label: "スケルトンのみ作成する(自分で実装する)" - description: "ファイルを作成し、仕様をコメントとして残します。コードは書きません。" - - label: "AIに実装まで依頼する" - description: "スクリプト作成から実装まで行います。" -``` - -### 4. スクリプトを生成する - -ターミナルで以下を実行する: - -```bash -pnpm new -- --app= --name= --license -``` - -`--license` はユーザーが不要と明示した場合のみ省略する。 - -実行後に生成されるファイル: - -- `es.config.mjs` に `` エントリが追加される -- `src///index.ts` にテンプレートが作成される - -**ScriptUI を選んだ場合**: 生成後、`index.ts` の `entry` を `entryUI` に書き換える(後述)。 - -### 5. 用途(purpose)をファイルに記録する - -`src///index.ts` を開き、ファイル先頭に以下のコメントブロックを追加する: - -```typescript -/** - * @script - * @app (After Effects / Illustrator / Photoshop) - * @description - * <ユーザーが述べた用途を具体的に記述する> - * - * @workflow - * 1. <期待する処理フローのステップ 1> - * 2. <ステップ 2 ...> - */ -``` - -`@workflow` はユーザーの `purpose` から推測して記述する。不明な場合は `TODO` として残す。 - -**ScriptUI の場合**: `entry` を `entryUI` に変更し、以下のテンプレートに書き換える: - -```typescript -import "../../init"; -import { entryUI } from "../lib/lib"; - -entryUI("", __ES_THIS__, (win) => { - // TODO: UI を構築する -}); -``` - -### 6. 作成したファイルをエディタで開く - -`run_vscode_command` ツールで `vscode.open` を呼び出し、作成した `src///index.ts` をエディタで開く。 - -### 7a. スケルトンモード — ここで完了 - -ユーザーが「スケルトンのみ」を選んだ場合: - -- ファイルパス `src///index.ts` をリンク付きでユーザーに報告する -- 実装する際のヒント(どの関数を使うか)を簡単に案内する -- **実装には着手しない** - -### 7b. 実装モード — 実装を進める - -ユーザーが「AIに実装まで依頼する」を選んだ場合: - -1. `.github/instructions/extendscript.instructions.md` を `read_file` で読み込み、コーディングルールを確認する -2. `purpose` と `@workflow` コメントを元に実装する -3. 実装完了後に `pnpm lint && pnpm format` を実行する -4. エラーがなければ `pnpm build -- /` を実行してビルドする -5. エラーがあればステップ 3 に戻って修正する -6. ビルド成功後、実装した内容を簡潔に日本語で報告する - ---- - -## Notes - -- `pnpm new` が失敗した場合は、エラーメッセージを確認し原因を調査してから再試行する -- 質問は **まとめて一度** に行い、分割して何度も聞かない -- アプリ ID の対応表: `aeft` = After Effects / `ilst` = Illustrator / `phxs` = Photoshop -- 予約名(`lib` / `types` / `tests` / `example`)はスクリプト名として使用できない -- `__ES_THIS__` は ScriptUI 用のグローバル変数で、パネルとして起動された場合に正しく動作させるために必要 diff --git a/.github/skills/debug-script/SKILL.md b/.github/skills/debug-script/SKILL.md deleted file mode 100644 index b9e773e..0000000 --- a/.github/skills/debug-script/SKILL.md +++ /dev/null @@ -1,95 +0,0 @@ ---- -name: debug-script -description: "TypeScript / Rollup のビルドエラーを修正する。Use when: pnpm build でエラーが出た、pnpm lint でエラーが出た、TypeScript の型エラーが出た、ビルドが失敗した、コンパイルエラー、型エラーの修正。" -argument-hint: "エラーメッセージを貼り付けるか、対象ファイルを指定してください" ---- - -# ビルドエラーの修正 - -`pnpm build` や `pnpm lint` で発生したエラーを調査して修正する。 - -> **注意**: ExtendScript のランタイムエラーは minify 後のコードで発生するため、 -> このスキルはビルド時(TypeScript / Rollup)のエラーのみを対象とする。 - -## When to Use - -- `pnpm build` を実行してエラーが出た -- `pnpm lint` で TypeScript の型エラーが報告された -- エラーメッセージが出ているが原因がわからない - ---- - -## Procedure - -### 1. エラーの種類を特定する - -エラーメッセージを確認し、以下のどれかに分類する: - -| 種類 | 特徴 | -| ----------------------- | ------------------------------------------------------------ | -| **TypeScript 型エラー** | `TS2xxx` のエラーコード、または `error TS` から始まる | -| **Rollup エラー** | `[!] Error:` から始まる、またはバンドル失敗 | -| **ESLint エラー** | `error` / `warning` + ルール名(例: `no-restricted-syntax`) | -| **import エラー** | `Cannot find module` / `Module not found` | - -エラーメッセージが提供されていない場合は、ユーザーに貼り付けてもらうか、 -ターミナルで以下を実行してエラーを取得する: - -```bash -pnpm build 2>&1 | head -60 -``` - -または - -```bash -pnpm lint 2>&1 | head -60 -``` - -### 2. エラーの原因を調査する - -#### TypeScript 型エラーの場合 - -- エラーファイルと行番号を確認する(例: `src/aeft/MyScript/index.ts:12:5`) -- `read_file` でファイルを開き、該当行周辺のコードを確認する -- 型定義不足であれば `src/{appId}/types/index.d.ts` を確認する -- ES3 制約違反(`Symbol`, `Promise` など)であれば使用禁止機能一覧を確認する - -#### Rollup エラーの場合 - -- エラーに含まれるファイルパスと行番号を確認する -- 循環 import や存在しない import パスが原因であることが多い - -#### ESLint エラーの場合 - -- ルール名を確認する(例: `no-restricted-syntax` は三項演算子禁止ルール) -- `.github/instructions/extendscript.instructions.md` のコーディングルールを `read_file` で確認する - -#### import エラーの場合 - -- import パスが正しいか確認する(相対パスの `../../` 等) -- 対象ファイルが存在するか `file_search` で確認する - -### 3. 修正する - -エラーの種類に応じて修正する。 -修正後は必ず以下を実行して確認する: - -```bash -pnpm lint && pnpm build -``` - -エラーが残っている場合はステップ 1 に戻る。 - -### 4. 完了を報告する - -- 修正した内容を日本語で簡潔に報告する -- 修正したファイルのパスをリンク付きで示す - ---- - -## Notes - -- **三項演算子** (`? :`) は ExtendScript の既知バグのため `no-restricted-syntax` ESLint エラーになる(`ConditionalExpression` ルール)。 - `const` が必要なら即時呼び出し無名関数 + `if` に、`let` でよければ `let` + `if` に書き直す -- **`Symbol` / `Promise`** は使用禁止。代替手段を提案する -- **型定義の不足** は `src/types/index.d.ts`(共通)または `src/{appId}/types/index.d.ts`(アプリ固有)に追加する diff --git a/.github/skills/es-to-ts/SKILL.md b/.github/skills/es-to-ts/SKILL.md deleted file mode 100644 index 0dee58d..0000000 --- a/.github/skills/es-to-ts/SKILL.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -name: es-to-ts -description: "既存の ExtendScript (.jsx / .js) を TypeScript に書き直す。Use when: 既存のスクリプトを TypeScript 化したい、.jsx ファイルをプロジェクトに追加したい、古い ExtendScript コードをモダンな TypeScript に移植したい、JSX を TS に変換したい。" -argument-hint: "変換したい ExtendScript コードを貼り付けるか、ファイルパスを指定してください" ---- - -# ExtendScript → TypeScript 変換 - -既存の ExtendScript (.jsx) を、このプロジェクトの TypeScript 形式に書き直す。 - -## When to Use - -- 既存の `.jsx` スクリプトをプロジェクトに取り込みたい -- 古い ExtendScript コードを TypeScript で管理したい - ---- - -## Procedure - -### 1. 対象コードを取得する - -ユーザーがコードを貼り付けていない場合は `read_file` でファイルを読み込む。 - -### 2. 事前確認: 三項演算子の検出 - -コード内の三項演算子 (`? :`) を探す。 -**三項演算子は ExtendScript の既知バグのため、変換後は必ず代替構文に書き直す。** - -三項演算子が見つかった場合は `vscode_askQuestions` でユーザーに確認する: - -```yaml -header: "ternary" -question: "以下の三項演算子の仕様を教えてください。変換に必要です。" -# 見つかった三項演算子を具体的に列挙して質問する -``` - -`condition ? valueIfTrue : valueIfFalse` の `valueIfTrue` / `valueIfFalse` それぞれが -何を返すべきかを確認し、`if` 文または即時呼び出し無名関数で書き直す。 - -### 3. 対象アプリ・スクリプト名を確認する - -ユーザーが指定していない場合は `vscode_askQuestions` で確認する: - -```yaml -header: "target" -question: "このスクリプトはどの Adobe アプリ向けですか?" -options: - - label: "After Effects" - - label: "Illustrator" - - label: "Photoshop" -``` - -### 4. 新しいスクリプトを作成する - -`add-script` スキルを呼び出して、新しいスクリプトのファイルを生成する。 -(スキルが利用できない場合は `pnpm new -- --app= --name= --license` を実行する) - -### 5. TypeScript に変換する - -`.github/instructions/extendscript.instructions.md` を `read_file` で読み込み、コーディングルールを確認してから変換する。 - -主な変換ポイント: - -| Before (ExtendScript) | After (TypeScript) | -| --------------------------------- | ------------------------------------------------------- | -| `var x = ...` | `const x = ...` / `let x = ...` | -| `function foo() {}` | `const foo = () => {}` | -| `x ? a : b` | `if` 文または即時呼び出し無名関数 | -| `try/catch` の `e.message` | `(e as Error).message` | -| 関数の先頭に処理を書く | `entry("name", () => { ... })` で囲む | -| ScriptUI ウィンドウをそのまま開く | `entryUI("name", __ES_THIS__, (win) => { ... })` で囲む | -| `alert(...)` / `confirm(...)` | `new Window(...)` を使う | -| `$.writeln(...)` | デバッグ用途のみ可。本番では削除する | - -型注釈を適切に追加する(`app.project.activeItem as CompItem` など)。 - -### 6. lint・ビルドで確認する - -```bash -pnpm lint && pnpm format && pnpm build -- / -``` - -エラーがあれば修正する。 - -### 7. 完了を報告する - -- 変換したファイルのパスをリンク付きで示す -- 三項演算子など手動確認が必要な点があれば明記する - ---- - -## Notes - -- **三項演算子の既知バグ**: ネストした三項演算子が JS 仕様通りに評価されない。 - 参考: https://uske-s.hatenablog.com/entry/2021/10/26/184709 -- **`Symbol` / `Promise`** は利用不可(ポリフィル対象外) -- **ES3 制約**: トランスパイル後は ES3 になるが、TypeScript の文法は最新のものが使える -- `$.writeln()` はデバッグ用。本番コードには残さないこと diff --git a/.github/skills/update-from-upstream/SKILL.md b/.github/skills/update-from-upstream/SKILL.md deleted file mode 100644 index 5e3e66b..0000000 --- a/.github/skills/update-from-upstream/SKILL.md +++ /dev/null @@ -1,174 +0,0 @@ ---- -name: update-from-upstream -description: "テンプレートリポジトリ(YukiWorks432/extendscript-ts-template)に更新があった場合に、自分のスクリプトを消さずに安全に取り込む。Use when: テンプレートの更新を取り込みたい、アップデートを確認したい、ポリフィルやビルドツールを最新化したい、upstream の変更を反映したい" -argument-hint: "確認のみ / 自動更新 / 特定ファイルのみ など、用途を指定できます" ---- - -# テンプレートのアップデートを取り込む - -テンプレートリポジトリ (`YukiWorks432/extendscript-ts-template`) の最新の変更を、 -**ユーザー自身のスクリプトを上書きせずに**安全に取り込む。 - -## When to Use - -- 「テンプレートのアップデートを取り込みたい」 -- 「ポリフィルやビルドスクリプトが古くなっていそう」 -- 「元のリポジトリで何が変わったか確認したい」 - -## 更新対象ファイルの分類 - -### ✅ 自動的に更新するファイル(テンプレート管理のインフラ) - -| パス | 説明 | -| ---------------------------------- | ---------------------------------- | -| `scripts/` | ビルドツール・スクリプト生成ツール | -| `src/lib/` | 共通ポリフィル | -| `src/init.ts` | 初期化コード | -| `src/tests/` | テストインフラ | -| `src/types/` | グローバル型定義 | -| `src/aeft/lib/`, `src/aeft/types/` | After Effects 共通ライブラリ・型 | -| `src/ilst/lib/`, `src/ilst/types/` | Illustrator 共通ライブラリ・型 | -| `src/phxs/lib/`, `src/phxs/types/` | Photoshop 共通ライブラリ・型 | -| `rollup.config.mjs` | バンドル設定 | -| `tsconfig.json` | TypeScript 設定 | -| `eslint.config.mjs` | Lint 設定 | -| `package.json` | パッケージ情報・依存関係 | -| `.prettierrc`, `.prettierignore` | フォーマット設定 | -| `pnpm-workspace.yaml` | ワークスペース設定 | -| `.github/instructions/` | Copilot 指示ファイル | -| `.github/skills/add-script/` | add-script スキル | -| `.github/copilot-instructions.md` | Copilot 基本設定 | -| `docs/` | ドキュメント | - -### ❌ 自動更新しないファイル(ユーザーが管理するもの) - -| パス | 説明 | -| -------------------------- | ---------------------------------------------------------- | -| `src/aeft/<スクリプト名>/` | ユーザーの After Effects スクリプト | -| `src/ilst/<スクリプト名>/` | ユーザーの Illustrator スクリプト | -| `src/phxs/<スクリプト名>/` | ユーザーの Photoshop スクリプト | -| `es.config.mjs` | スクリプトのビルド設定(ユーザーが追加したエントリを保持) | -| `README.md` | ユーザーが書き換えた可能性がある | -| `LICENSE` | ユーザーが書き換えた可能性がある | - -## Procedure - -### 1. upstream リモートを確認・設定する - -```bash -git remote -v -``` - -出力に `upstream` が含まれない場合は追加する: - -```bash -git remote add upstream https://github.com/YukiWorks432/extendscript-ts-template.git -``` - -> **注意**: すでに `upstream` が設定されている場合はスキップする。 - -### 2. 最新の情報を取得する - -```bash -git fetch upstream -``` - -### 3. 差分を確認・ユーザーに報告する - -コミット差分を確認する: - -```bash -git log HEAD..upstream/main --oneline -``` - -変更ファイル一覧を確認する: - -```bash -git diff --name-status HEAD..upstream/main -``` - -取得した差分情報をもとに、以下を日本語でユーザーに報告する: - -- 新しい機能・変更点の要約(コミットメッセージを元に) -- 自動更新対象のファイル一覧 -- **`es.config.mjs` が変更されている場合**は、手動マージが必要な旨を明記する -- **差分がない場合**は「最新の状態です」と報告してスキルを終了する - -### 4. 更新の方針をユーザーに確認する - -`vscode_askQuestions` で以下を確認する: - -``` -header: "update-policy" -question: "アップデートを取り込みますか?" -options: - - label: "インフラファイルのみ自動更新する(推奨)" - description: "ユーザーのスクリプトは変更しません。" - recommended: true - - label: "差分の詳細を確認してから判断する" - description: "変更ファイルごとの差分を確認します。" - - label: "キャンセル" -``` - -「キャンセル」の場合はここで停止する。 - -「差分の詳細を確認」の場合は、自動更新対象ファイルそれぞれの差分 (`git diff HEAD..upstream/main -- `) を表示し、再度確認を取る。 - -### 5. インフラファイルを upstream から取り込む - -以下のコマンドで対象ファイルを取り込む(実際に upstream に存在するファイルのみ実行する): - -```bash -git checkout upstream/main -- scripts/ -git checkout upstream/main -- src/lib/ -git checkout upstream/main -- src/init.ts -git checkout upstream/main -- src/tests/ -git checkout upstream/main -- src/types/ -git checkout upstream/main -- src/aeft/lib/ src/aeft/types/ -git checkout upstream/main -- src/ilst/lib/ src/ilst/types/ -git checkout upstream/main -- src/phxs/lib/ src/phxs/types/ -git checkout upstream/main -- rollup.config.mjs tsconfig.json eslint.config.mjs -git checkout upstream/main -- package.json .prettierrc .prettierignore pnpm-workspace.yaml -git checkout upstream/main -- .github/instructions/ .github/skills/add-script/ .github/copilot-instructions.md -git checkout upstream/main -- docs/ -``` - -ファイルが upstream に存在しない場合は `error: pathspec` が出るのでスキップする。 - -### 6. `es.config.mjs` の構造が変わっていた場合 - -`git diff HEAD..upstream/main -- es.config.mjs` を確認する。 - -構造や新しいフィールドが追加されていれば、変更点をユーザーに提示し、手動で `es.config.mjs` を更新するよう案内する。 -自動マージは行わない(ユーザーのスクリプト設定を破壊するリスクがあるため)。 - -### 7. 依存関係を更新する - -```bash -pnpm install -``` - -`pnpm-lock.yaml` は自動生成されるため、checkout 対象には含めない。 - -### 8. ビルドを確認する - -```bash -pnpm build --all -``` - -エラーがある場合は原因を調査・修正してから次のステップへ進む。 - -### 9. 完了を報告する - -ユーザーに以下を伝える: - -- 取り込んだ変更点の要約(日本語) -- ユーザーのスクリプトに影響する可能性のある変更がある場合は警告 -- 変更をコミットするよう案内(例: `git add . && git commit -m "chore: upstream からアップデートを取り込む"`) - -## Notes - -- `pnpm-lock.yaml` は `pnpm install` で自動更新されるため、`git checkout` 対象から**除外**する -- ユーザーの `src/aeft/tsconfig.json` など、アプリフォルダ直下の tsconfig は更新対象外 -- 大きな破壊的変更(API 変更など)がある場合は、自動更新を中断してユーザーに確認する -- `.github/skills/` 配下のユーザー独自スキル(`update-from-upstream` 自身を含む)は上書きしない diff --git a/AGENTS.md b/AGENTS.md index 4efb6d1..1bfc65d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,8 @@ ## 優先順位 - 上位の system / developer / user 指示を最優先し、次にこの `AGENTS.md`、次に参照先 docs / skills に従ってください。 -- GitHub Copilot 向けの `.github/copilot-instructions.md`、`.github/instructions/`、`.github/skills/` は正本にしません。Codex 向けの入口はこの `AGENTS.md` です。 +- Codex 向けの入口はこの `AGENTS.md` です。詳細手順は `.agents/`、ローカル Codex 設定は `.codex/` に置きます。 +- GitHub Copilot 専用の `.github/copilot-instructions.md`、`.github/instructions/`、`.github/skills/` は正本にせず、この配布用リポジトリには置きません。 - 作業前にこの `AGENTS.md` を確認してください。対象ファイルに対応する追加指示がある場合は、その指示も確認してください。 - `src/**/*.ts` を編集する場合は、`.agents/instructions/extendscript.md` を確認してください。 - 参照先 docs / skills とこのファイルが矛盾する場合は、このファイルを優先し、必要なら矛盾を報告してください。 @@ -51,6 +52,7 @@ - テンプレート利用後の初期整理: `docs/template-customization.md` - 配布用リポジトリの運営方針: `docs/repository-operations.md` - リリース手順: `docs/release-process.md` +- Codex ローカル設定: `.codex/README.md` - スクリプト追加: `scripts/newScript.mjs` と `docs/project-overview.md` - アプリ追加: `scripts/addApp.mjs` と `docs/project-overview.md` - ExtendScript 追加指示: `.agents/instructions/extendscript.md` diff --git a/README.md b/README.md index 2107462..0e3f205 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,12 @@ pnpm build pnpm new -- --app=aeft --name=MyScript --license ``` +ScriptUI パネル対応スクリプトを作る場合は `--ui=scriptui` を指定します: + +```shell +pnpm new -- --app=aeft --name=MyPanel --license --ui=scriptui +``` + 対話式でも作成できます: ```shell diff --git a/docs/guides/getting-started.md b/docs/guides/getting-started.md index ed21a6a..06dcbcf 100644 --- a/docs/guides/getting-started.md +++ b/docs/guides/getting-started.md @@ -26,6 +26,12 @@ pnpm i pnpm new -- --app=aeft --name=MyFirstScript --license ``` +ScriptUI パネル対応スクリプトを作る場合は、`--ui=scriptui` を追加します。 + +```bash +pnpm new -- --app=aeft --name=MyPanel --license --ui=scriptui +``` + これにより以下が生成されます: - `src/aeft/MyFirstScript/index.ts`(テンプレートコード) @@ -140,7 +146,7 @@ entryUI("MyScript", __ES_THIS__, (win) => { `onClick` などのイベントハンドラ内で処理を行う場合は、処理本体を `entry()` で囲んでください。 ```ts -import { entry, alertError } from "../../lib/lib"; +import { entry, entryUI } from "../../lib/lib"; ``` ### アプリ固有のユーティリティを作る @@ -177,9 +183,9 @@ declare class SomeUndefinedClass { このリポジトリは `YukiWorks432/extendscript-ts-template` を元に作られたテンプレートリポジトリです。 元テンプレートにポリフィルやビルドツールの更新があった場合、以下の手順で自分のスクリプトを消さずに取り込めます。 -### 0. GitHub Copilot を使う場合 +### 0. AI エージェントを使う場合 -Copilot が使える環境なら、`update-from-upstream` スキルに任せると自動で差分確認・取り込みができます。 +Codex など、このリポジトリの `.agents/skills` を読める環境なら、`update-from-upstream` スキルに任せると自動で差分確認・取り込みができます。 ### 1. 元テンプレートを「upstream」として登録する diff --git a/docs/project-overview.md b/docs/project-overview.md index 1424059..78098fb 100644 --- a/docs/project-overview.md +++ b/docs/project-overview.md @@ -95,6 +95,12 @@ export default { pnpm new -- --app=aeft --name=MyScript --license ``` +ScriptUI パネル対応スクリプトを生成する場合: + +```bash +pnpm new -- --app=aeft --name=MyPanel --license --ui=scriptui +``` + ### 対話モード ```bash @@ -120,15 +126,19 @@ entry("MyScript", () => { }); ``` -**ScriptUI スクリプト(パネル対応)を作る場合は `entryUI` を使う:** +`--ui=scriptui` を指定した場合は、以下のように `entryUI` と `__ES_THIS__` を使う: ```ts import "../../init"; -import { entryUI } from "../../lib/lib"; +import { entry, entryUI } from "../../lib/lib"; entryUI("MyScript", __ES_THIS__, (win) => { - win.add("statictext", undefined, "Hello!"); - // ここで UI を構築する + const runButton = win.add("button", undefined, "実行"); + runButton.onClick = () => { + entry("MyScript", () => { + // TODO: Implement MyScript + }); + }; }); ``` @@ -168,7 +178,7 @@ entry("MyScript", () => { ### `entryUI` — ScriptUI ウィンドウ用 ```ts -import { entryUI } from "../../lib/lib"; +import { entry, entryUI } from "../../lib/lib"; entryUI("MyScript", __ES_THIS__, (win) => { const btn = win.add("button", undefined, "実行"); @@ -224,6 +234,20 @@ pnpm add-app -- --app=idsn | `pnpm add-app` | 新規アプリスキャフォールディング | | `pnpm clean` | ビルドハッシュをクリーンアップ | +## 差分ビルドの判定 + +`pnpm build` は、各スクリプトの `index.ts` から相対 `import` / `export ... from` +で到達するファイルと、ビルド設定ファイルのハッシュを使って再ビルド対象を判定する。 +同じ `src/{appId}` 配下にある別スクリプトを変更しても、そのスクリプトを import していない +他のスクリプトは再ビルド対象にならない。 + +`rollup.config.mjs`、`es.config.mjs`、`package.json`、`pnpm-lock.yaml`、`tsconfig.json`、 +対象アプリの `tsconfig.json` を変更した場合は、該当するビルド対象のハッシュが変わる。 +`src/init.ts`、`src/lib/`、`src/{appId}/lib/` は、スクリプトから import で到達している場合だけ、 +そのスクリプトのハッシュに含まれる。 +`src/types/` は全スクリプト、`src/{appId}/types/` は該当アプリのスクリプトで使える ambient 型定義 +として扱うため、該当するビルド対象のハッシュに含まれる。 + ## 型情報について [Types-for-Adobe](https://github.com/docsforadobe/Types-for-Adobe) を使用。 diff --git a/docs/template-customization.md b/docs/template-customization.md index 162298f..6330708 100644 --- a/docs/template-customization.md +++ b/docs/template-customization.md @@ -23,10 +23,11 @@ ### AI エージェント設定 -`AGENTS.md` と `.agents/` は Codex 向けの作業ルールです。 -`.github/copilot-instructions.md`、`.github/instructions/`、`.github/skills/` は GitHub Copilot 向けの設定です。 +`AGENTS.md`、`.agents/`、`.codex/` は Codex 向けの作業ルールとローカル設定です。 +このテンプレートでは `AGENTS.md` をエージェント指示の入口にし、具体的な手順は `.agents/` に置きます。 +`.codex/config.toml` はローカルサブエージェントの並列度だけを定義し、承認・sandbox・network 権限は広げません。 -どちらも、このテンプレートの開発方針に合わせた初期値です。 +この設定は、このテンプレートの開発方針に合わせた初期値です。 自分のプロジェクト名、ブランチ運用、説明言語、品質ゲートに合わせて編集してください。 不要な場合は削除して構いません。 diff --git a/rollup.config.mjs b/rollup.config.mjs index 6c9373e..aa58932 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -42,6 +42,10 @@ const hashText = (text) => { const normalizePath = (filePath) => filePath.replace(/\\/g, "/"); +const IMPORT_RESOLVE_EXTENSIONS = [".ts", ".js", ".d.ts"]; +const IMPORT_SPECIFIER_PATTERN = + /\b(?:import|export)\s+(?:type\s+)?(?:[^'"]*?\s+from\s+)?["']([^"']+)["']|import\s*\(\s*["']([^"']+)["']\s*\)/g; + const collectFiles = (targetPath) => { if (!fs.existsSync(targetPath)) { return []; @@ -112,21 +116,119 @@ const SHARED_BUILD_INPUTS = [ "package.json", "pnpm-lock.yaml", "tsconfig.json", - "src/init.ts", - "src/lib", - "src/types", ]; const getLicenseFile = (srcDir) => fs.existsSync(`${srcDir}/LICENSE`) ? `${srcDir}/LICENSE` : "LICENSE"; -const getScriptHashInputs = ({ appId, script, srcDir, tsconfig }) => { - const inputs = [...SHARED_BUILD_INPUTS, srcDir, tsconfig]; +const getAmbientTypeInputs = (appId) => { + const inputs = ["src/types"]; if (appId) { - inputs.push(`src/${appId}`); + inputs.push(`src/${appId}/types`); + } + + return inputs; +}; + +const stripComments = (source) => + source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1"); + +const getRelativeImportSpecifiers = (filePath) => { + const source = stripComments(fs.readFileSync(filePath, "utf8")); + const specifiers = []; + + IMPORT_SPECIFIER_PATTERN.lastIndex = 0; + + let match = IMPORT_SPECIFIER_PATTERN.exec(source); + while (match) { + const specifier = match[1] || match[2]; + + if (specifier && specifier.startsWith(".")) { + specifiers.push(specifier); + } + + match = IMPORT_SPECIFIER_PATTERN.exec(source); } + return specifiers; +}; + +const getImportCandidates = (importBasePath) => { + const candidates = []; + + if (path.extname(importBasePath)) { + candidates.push(importBasePath); + } else { + IMPORT_RESOLVE_EXTENSIONS.forEach((extension) => { + candidates.push(`${importBasePath}${extension}`); + }); + } + + IMPORT_RESOLVE_EXTENSIONS.forEach((extension) => { + candidates.push(path.join(importBasePath, `index${extension}`)); + }); + + return candidates; +}; + +const resolveRelativeImport = (fromFilePath, specifier) => { + const importBasePath = path.resolve(path.dirname(fromFilePath), specifier); + + return ( + getImportCandidates(importBasePath).find((candidate) => { + if (!fs.existsSync(candidate)) { + return false; + } + + return fs.statSync(candidate).isFile(); + }) || null + ); +}; + +const canReadImports = (filePath) => + IMPORT_RESOLVE_EXTENSIONS.includes(path.extname(filePath)); + +const collectImportDependencyFiles = (entryFile) => { + const files = new Map(); + + const visit = (filePath) => { + const resolvedFile = path.resolve(filePath); + + if (files.has(resolvedFile) || !fs.existsSync(resolvedFile)) { + return; + } + + files.set(resolvedFile, resolvedFile); + + if (!canReadImports(resolvedFile)) { + return; + } + + getRelativeImportSpecifiers(resolvedFile).forEach((specifier) => { + const importedFile = resolveRelativeImport(resolvedFile, specifier); + + if (importedFile) { + visit(importedFile); + } + }); + }; + + visit(entryFile); + + return Array.from(files.values()).sort((left, right) => + normalizePath(left).localeCompare(normalizePath(right)) + ); +}; + +const getScriptHashInputs = ({ appId, script, srcDir, tsconfig }) => { + const inputs = [ + ...SHARED_BUILD_INPUTS, + tsconfig, + ...getAmbientTypeInputs(appId), + ...collectImportDependencyFiles(`${srcDir}/index.ts`), + ]; + if (script.license) { inputs.push(getLicenseFile(srcDir)); } @@ -288,6 +390,8 @@ export default (commandLineArgs) => { for (const [appId, scripts] of Object.entries(config.scripts)) { if (appFilter && appId !== appFilter) continue; for (const script of scripts) { + if (script.build === false) continue; + allScripts.push({ appId, script, @@ -303,6 +407,8 @@ export default (commandLineArgs) => { // common スクリプト(アプリ非依存) if (config.common && !appFilter) { for (const script of config.common) { + if (script.build === false) continue; + allScripts.push({ appId: null, script, diff --git a/scripts/newScript.mjs b/scripts/newScript.mjs index c4d2185..5b705e6 100644 --- a/scripts/newScript.mjs +++ b/scripts/newScript.mjs @@ -2,6 +2,7 @@ // Usage: // 対話モード: pnpm new // CLI モード: pnpm new -- --app=aeft --name=MyScript --license +// ScriptUI: pnpm new -- --app=aeft --name=MyPanel --license --ui=scriptui // es.config.mjsに新しいスクリプトを追加し、src/{app}/{name}配下にテンプレートを作成します。 // 新しいスクリプトはes.config.mjsのscripts.{app}配列の先頭に追加されます。 // es.config.mjsはprettierで整形されます。 @@ -22,6 +23,7 @@ const ES_CONFIG_PATH = path.resolve(projectRoot, "es.config.mjs"); const SRC_DIR = path.resolve(projectRoot, "src"); const RESERVED_NAMES = ["lib", "types", "tests"]; +const UI_TYPES = ["script", "scriptui"]; class CliError extends Error { constructor(message) { @@ -50,6 +52,8 @@ const I18N = { selectApp: (apps) => `対象アプリを選択してください (${apps.join(", ")}): `, enterName: "スクリプト名を入力してください: ", + selectUi: + "スクリプトの種類を選択してください (script/scriptui, default: script): ", includeLicense: "ライセンス表記を含めますか? (y/N): ", }, error: { @@ -57,6 +61,8 @@ const I18N = { emptyApp: "エラー: アプリが指定されていません。", invalidApp: (app, apps) => `エラー: 無効なアプリ名です: ${app} (有効: ${apps.join(", ")})`, + invalidUiType: (uiType) => + `エラー: 無効な UI 種別です: ${uiType} (有効: ${UI_TYPES.join(", ")})`, reservedName: (name) => `エラー: "${name}" は予約名のため使用できません。`, scriptsNotFound: @@ -76,6 +82,7 @@ const I18N = { prompt: { selectApp: (apps) => `Select target app (${apps.join(", ")}): `, enterName: "Please enter the script name: ", + selectUi: "Select script type (script/scriptui, default: script): ", includeLicense: "Include a license field? (y/N): ", }, error: { @@ -83,6 +90,8 @@ const I18N = { emptyApp: "Error: No app specified.", invalidApp: (app, apps) => `Error: Invalid app name: ${app} (valid: ${apps.join(", ")})`, + invalidUiType: (uiType) => + `Error: Invalid UI type: ${uiType} (valid: ${UI_TYPES.join(", ")})`, reservedName: (name) => `Error: "${name}" is a reserved name and cannot be used.`, scriptsNotFound: "Error: Could not locate scripts in es.config.mjs.", @@ -113,6 +122,18 @@ function toBoolean(input) { return v === "y" || v === "yes" || v === "true" || v === "1"; } +function normalizeUiType(input) { + const v = String(input || "") + .trim() + .toLowerCase(); + if (!v) return "script"; + if (v === "script" || v === "normal" || v === "entry") return "script"; + if (v === "scriptui" || v === "ui" || v === "panel" || v === "palette") { + return "scriptui"; + } + throw new CliError(L.error.invalidUiType(input)); +} + async function loadConfig() { try { const fileUrl = pathToFileURL(ES_CONFIG_PATH).href; @@ -164,10 +185,12 @@ async function getScriptDescriptor(rl, config) { const name = String(await ask(rl, L.prompt.enterName)).trim(); validateName(name, appId, config); + const uiType = normalizeUiType(await ask(rl, L.prompt.selectUi)); const withLicense = toBoolean(await ask(rl, L.prompt.includeLicense)); return { appId, + uiType, script: { name, version: "0.0.1", @@ -206,11 +229,39 @@ entry("${name}", () => { }); `; -async function createScriptTemplate(appId, name) { +const createScriptUiIndexTsTemplate = (name) => + `/** @description Build ScriptUI panel */ + +import "../../init"; +import { entry, entryUI } from "../../lib/lib"; + +entryUI("${name}", __ES_THIS__, (win) => { + win.orientation = "column"; + win.alignChildren = ["fill", "top"]; + win.spacing = 8; + win.margins = 12; + + win.add("statictext", undefined, "${name}"); + + const runButton = win.add("button", undefined, "実行"); + runButton.onClick = () => { + entry("${name}", () => { + // TODO: Implement ${name} + }); + }; + + win.layout.layout(true); +}); +`; + +async function createScriptTemplate(appId, name, uiType) { const scriptDir = path.resolve(SRC_DIR, appId, name); const indexPath = path.resolve(scriptDir, "index.ts"); await mkdir(scriptDir, { recursive: true }); - const indexContent = createIndexTsTemplate(name); + const indexContent = + uiType === "scriptui" + ? createScriptUiIndexTsTemplate(name) + : createIndexTsTemplate(name); try { await writeFile(indexPath, indexContent, { encoding: "utf8", @@ -228,6 +279,7 @@ function parseCliArgs() { options: { app: { type: "string" }, name: { type: "string" }, + ui: { type: "string", default: "script" }, license: { type: "boolean", default: false }, }, strict: true, @@ -235,6 +287,7 @@ function parseCliArgs() { if (values.name && values.app) { return { appId: values.app.trim(), + uiType: normalizeUiType(values.ui), script: { name: values.name.trim(), version: "0.0.1", @@ -243,18 +296,30 @@ function parseCliArgs() { }, }; } - } catch { + } catch (error) { + if (error instanceof CliError) throw error; // 不正な引数の場合は対話モードにフォールバック } return null; } async function main() { - const cliDescriptor = parseCliArgs(); + let cliDescriptor = null; + try { + cliDescriptor = parseCliArgs(); + } catch (err) { + process.exitCode = 1; + if (err instanceof CliError) { + console.error(err.message); + } else { + console.error(L.error.unexpected, err); + } + return; + } if (cliDescriptor) { // CLI モード - const { appId, script } = cliDescriptor; + const { appId, script, uiType } = cliDescriptor; try { const config = await loadConfig(); validateApp(appId, config); @@ -263,7 +328,7 @@ async function main() { await updateScriptConfig(appId, script); console.log(L.doneAdd(script.name, appId, script.license)); - await createScriptTemplate(appId, script.name); + await createScriptTemplate(appId, script.name, uiType); console.log(L.doneMake(script.name, appId)); execSync(`prettier --write "${ES_CONFIG_PATH}"`, { stdio: "inherit" }); } catch (err) { @@ -281,12 +346,12 @@ async function main() { const rl = createInterface({ input, output }); try { const config = await loadConfig(); - const { appId, script } = await getScriptDescriptor(rl, config); + const { appId, script, uiType } = await getScriptDescriptor(rl, config); await updateScriptConfig(appId, script); console.log(L.doneAdd(script.name, appId, script.license)); - await createScriptTemplate(appId, script.name); + await createScriptTemplate(appId, script.name, uiType); console.log(L.doneMake(script.name, appId)); execSync(`prettier --write "${ES_CONFIG_PATH}"`, { stdio: "inherit" }); } catch (err) { diff --git a/src/aeft/example/index.ts b/src/aeft/example/index.ts index 6688066..a862ef2 100644 --- a/src/aeft/example/index.ts +++ b/src/aeft/example/index.ts @@ -4,6 +4,13 @@ import "../../init"; // libから必要な関数をimportします。 import { entry } from "../lib/lib"; +const showMessage = (title: string, message: string) => { + const win = new Window("dialog", title); + win.add("statictext", undefined, message, { multiline: true }); + win.add("button", undefined, "OK", { name: "ok" }); + win.show(); +}; + // スクリプトはすべてentryの中に書いてください // アロー関数が使えるようになった entry("example", () => { @@ -17,32 +24,32 @@ entry("example", () => { if (selected.length === 0) return; // Array.mapとArray.joinが使えるようになった - alert(selected.map((l) => l.name).join("\n")); + const selectedNames = selected.map((l) => l.name).join("\n"); const names: string[] = []; // for...ofが使えるようになった for (const layer of selected) { names.push(layer.name); } - alert(names.join("\n")); - - // テンプレートリテラルが使えるようになった - alert(`選択中のレイヤー数: ${selected.length}`); // Array.reduceが使えるようになった const totalOpacity = selected.reduce( (sum, layer) => sum + layer.opacity.value, 0 ); - alert(`選択中のレイヤーの不透明度の合計: ${totalOpacity}`); - const hoge = () => { + const getOpacityStats = () => { return [totalOpacity, totalOpacity / selected.length]; }; // 分割代入が使えるようになった - const [_, averageOpacity] = hoge(); + const [_, averageOpacity] = getOpacityStats(); - alert( - `選択中のレイヤーの不透明度の合計: ${totalOpacity}\n平均: ${averageOpacity}` + // テンプレートリテラルが使えるようになった + showMessage( + "example", + `選択中のレイヤー数: ${selected.length}\n\n` + + `Array.map の結果:\n${selectedNames}\n\n` + + `for...of の結果:\n${names.join("\n")}\n\n` + + `不透明度の合計: ${totalOpacity}\n平均: ${averageOpacity}` ); }); diff --git a/src/ilst/example/index.ts b/src/ilst/example/index.ts index f4b3539..4af8004 100644 --- a/src/ilst/example/index.ts +++ b/src/ilst/example/index.ts @@ -1,6 +1,13 @@ import "../../init"; import { entry } from "../lib/lib"; +const showMessage = (title: string, message: string) => { + const win = new Window("dialog", title); + win.add("statictext", undefined, message, { multiline: true }); + win.add("button", undefined, "OK", { name: "ok" }); + win.show(); +}; + entry("example", () => { const doc = app.activeDocument; if (!doc) return; @@ -9,5 +16,5 @@ entry("example", () => { if (!selected || selected.length === 0) return; const names = selected.map((item) => item.name || item.typename); - alert(`選択中のアイテム:\n${names.join("\n")}`); + showMessage("example", `選択中のアイテム:\n${names.join("\n")}`); }); diff --git a/src/lib/polyfills/es6-shim.js b/src/lib/polyfills/es6-shim.js index f185536..ed23373 100644 --- a/src/lib/polyfills/es6-shim.js +++ b/src/lib/polyfills/es6-shim.js @@ -2512,17 +2512,25 @@ ].every(function (num) { return Math.round(num) === num; }); - defineProperty( - Math, - "round", - function round(x) { - var floor = _floor(x); - var ceil = floor === -1 ? -0 : floor + 1; - return x - floor < 0.5 ? floor : ceil; - }, - !roundHandlesBoundaryConditions || !roundDoesNotIncreaseIntegers - ); - Value.preserveToString(Math.round, origMathRound); + // extendscript-ts-template local modification: + // keep ExtendScript's native Math.round instead of applying es6-shim's polyfill. + var enableMathRoundPolyfill = false; + if ( + enableMathRoundPolyfill && + (!roundHandlesBoundaryConditions || !roundDoesNotIncreaseIntegers) + ) { + defineProperty( + Math, + "round", + function round(x) { + var floor = _floor(x); + var ceil = floor === -1 ? -0 : floor + 1; + return x - floor < 0.5 ? floor : ceil; + }, + !roundHandlesBoundaryConditions || !roundDoesNotIncreaseIntegers + ); + Value.preserveToString(Math.round, origMathRound); + } var origImul = Math.imul; if (Math.imul(0xffffffff, 5) !== -5) { diff --git a/src/phxs/example/index.ts b/src/phxs/example/index.ts index d6b3d95..3d9a433 100644 --- a/src/phxs/example/index.ts +++ b/src/phxs/example/index.ts @@ -1,10 +1,17 @@ import "../../init"; import { entry } from "../lib/lib"; +const showMessage = (title: string, message: string) => { + const win = new Window("dialog", title); + win.add("statictext", undefined, message, { multiline: true }); + win.add("button", undefined, "OK", { name: "ok" }); + win.show(); +}; + entry("example", () => { const doc = app.activeDocument; if (!doc) return; const layer = doc.activeLayer; - alert(`アクティブレイヤー: ${layer.name}`); + showMessage("example", `アクティブレイヤー: ${layer.name}`); }); diff --git a/src/tests/index.ts b/src/tests/index.ts index c633d10..c7c4331 100644 --- a/src/tests/index.ts +++ b/src/tests/index.ts @@ -248,6 +248,13 @@ import "../init"; ); }); + run("Math.round", () => { + assertEq(Math.round(0), 0, "Math.round(0) is 0"); + assertEq(Math.round(0.5), 1, "Math.round(0.5) is 1"); + assert(Object.is(Math.round(-0.5), -0), "Math.round(-0.5) is -0"); + assert(Object.is(Math.round(-0), -0), "Math.round(-0) is -0"); + }); + run("Object.assign", () => { const target: any = { a: 1 }; const assigned = Object.assign(target, { b: 2 }, { c: 3 });