@ciderjs/gasnuki 0.2.10 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,220 @@
1
+ # `@ciderjs/gasnuki` Viteプラグイン 設計書
2
+
3
+ ## 1. はじめに
4
+
5
+ ### 1.1. 目的
6
+
7
+ 本ドキュメントは、先に作成された「`@ciderjs/gasnuki` Viteプラグイン機能追加 要件定義書」に基づき、Viteプラグイン機能の具体的なアーキテクチャ、実装詳細、データ構造などを定義することを目的とする。
8
+
9
+ ### 1.2. 対象読者
10
+
11
+ `@ciderjs/gasnuki` の開発者およびコントリビューターを対象とする。
12
+
13
+ ## 2. アーキテクチャ概要
14
+
15
+ `@ciderjs/gasnuki` のViteプラグインは、Viteの開発サーバーのライフサイクルにフックすることで動作する。既存の型生成コアロジック (`generateAppsScriptTypes`) を再利用し、Viteの開発環境とシームレスに連携する。
16
+
17
+ ### 2.1. 構成図
18
+
19
+ ```mermaid
20
+ graph TD
21
+ subgraph Vite開発環境
22
+ A[vite.config.ts] --> B(Vite Dev Server);
23
+ B -- 起動 --> C{gasnuki Plugin};
24
+ B -- ファイル変更通知 --> C;
25
+ end
26
+
27
+ subgraph gasnuki Plugin
28
+ C -- configResolvedフック --> D[設定解決ロジック];
29
+ C -- configureServerフック --> E[初回生成 & ウォッチャー設定];
30
+ E -- ファイル変更イベント --> F[型再生成トリガー];
31
+ end
32
+
33
+ subgraph gasnuki コアモジュール
34
+ G[./modules/config.ts];
35
+ H[./modules/generate.ts];
36
+ end
37
+
38
+ D -- 読み込み --> G;
39
+ E -- 実行 --> H;
40
+ F -- 実行 --> H;
41
+
42
+ style Vite開発環境 fill:#e6f2ff,stroke:#b3d9ff
43
+ style gasnukiコアモジュール fill:#fff2e6,stroke:#ffdab3
44
+ ```
45
+
46
+ ### 2.2. 処理フロー
47
+
48
+ 1. **設定解決**: Vite開発サーバー起動時、`configResolved` フックが発火。Viteの設定情報 (`config.root`) を元にプロジェクトルートを特定し、プラグインオプション、`gasnuki.config.ts`、デフォルト値をマージして最終的な設定を確定する。
49
+ 2. **初回生成**: `configureServer` フックで、開発サーバーの起動処理の一環として、型定義の初回生成 (`generateAppsScriptTypes`) を実行する。
50
+ 3. **ファイル監視と再生成**: 同じく `configureServer` フック内で、Viteのファイルウォッチャー (`server.watcher`) を利用して、設定されたソースディレクトリ (`srcDir`) を監視する。ファイルの追加・変更・削除を検知すると、再度 `generateAppsScriptTypes` を実行して型定義を更新する。
51
+
52
+ ## 3. ファイル構成
53
+
54
+ | ファイルパス | 状態 | 役割 |
55
+ | :------------------- | :--- | :-------------------------------------------------------------------------------------------------- |
56
+ | `src/vite.ts` | 新規 | Viteプラグインのロジックを実装する。プラグインのファクトリ関数 `gasnuki()` をエクスポートする。 |
57
+ | `package.json` | 変更 | `exports` フィールドに `./vite` エントリーポイントを追加し、Viteプラグインを外部から利用可能にする。 |
58
+ | `build.config.ts` | 変更 | `unbuild` のビルド対象に `src/vite.ts` を追加する。 |
59
+ | `src/modules/generate.ts` | 変更 | (必要であれば) Vite環境でのロギングやエラーハンドリングを改善するために軽微な修正を行う可能性がある。 |
60
+ | `src/modules/config.ts` | 変更なし | 既存のコンフィグローダーをそのまま利用する。 |
61
+
62
+ ## 4. 詳細設計
63
+
64
+ ### 4.1. Viteプラグインエントリーポイント (`src/vite.ts`)
65
+
66
+ プラグインは `gasnuki(options?: UserConfig): Plugin` というファクトリ関数として提供する。
67
+
68
+ ```typescript:gasnuki Vite Plugin:src/vite.ts
69
+ import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';
70
+ import * as path from 'node:path';
71
+ import { consola } from 'consola';
72
+ import { generateAppsScriptTypes } from './modules/generate';
73
+ import { loadConfig, type UserConfig } from './modules/config';
74
+ import type { GenerateOptions } from './index';
75
+
76
+ /**
77
+ * A factory function to create the gasnuki Vite plugin.
78
+ * @param options - The user-defined configuration for gasnuki.
79
+ * @returns The Vite plugin object.
80
+ */
81
+ export function gasnuki(options?: UserConfig): Plugin {
82
+ let projectRoot: string;
83
+ let gasnukiOptions: Omit<GenerateOptions, 'watch' | 'project'>;
84
+
85
+ // Debounce mechanism to prevent rapid-fire regeneration
86
+ let debounceTimer: NodeJS.Timeout | null = null;
87
+ const debounce = (func: () => void, timeout = 300) => {
88
+ if (debounceTimer) {
89
+ clearTimeout(debounceTimer);
90
+ }
91
+ debounceTimer = setTimeout(func, timeout);
92
+ };
93
+
94
+ const runGeneration = (triggeredBy?: string) => {
95
+ debounce(async () => {
96
+ const reason = triggeredBy ? ` (triggered by: ${triggeredBy})` : '';
97
+ consola.info(`[gasnuki] Generating AppsScript types${reason}...`);
98
+ try {
99
+ await generateAppsScriptTypes({
100
+ ...gasnukiOptions,
101
+ project: projectRoot,
102
+ });
103
+ consola.success('[gasnuki] Type generation complete.');
104
+ } catch (e) {
105
+ consola.error(`[gasnuki] Type generation failed: ${(e as Error).message}`);
106
+ }
107
+ });
108
+ };
109
+
110
+ return {
111
+ name: 'vite-plugin-gasnuki',
112
+ apply: 'serve', // Apply only in serve mode
113
+
114
+ async configResolved(config: ResolvedConfig) {
115
+ projectRoot = config.root;
116
+ const fileConfig = await loadConfig(projectRoot);
117
+ const defaultOptions: Partial<GenerateOptions> = {
118
+ srcDir: 'server',
119
+ outDir: 'types',
120
+ outputFile: 'appsscript.ts',
121
+ };
122
+ gasnukiOptions = {
123
+ ...defaultOptions,
124
+ ...fileConfig,
125
+ ...(options || {}),
126
+ };
127
+ },
128
+
129
+ async configureServer(server: ViteDevServer) {
130
+ const targetDir = path.resolve(projectRoot, gasnukiOptions.srcDir);
131
+
132
+ // Initial generation on server startup
133
+ runGeneration('server startup');
134
+
135
+ // Watch for file changes
136
+ server.watcher.on('all', (event, filePath) => {
137
+ // Normalize paths for reliable comparison
138
+ const normalizedFilePath = path.normalize(filePath);
139
+ const normalizedTargetDir = path.normalize(targetDir);
140
+
141
+ if (normalizedFilePath.startsWith(normalizedTargetDir)) {
142
+ runGeneration(path.relative(projectRoot, filePath));
143
+ }
144
+ });
145
+ },
146
+ };
147
+ }
148
+ ```
149
+
150
+ ### 4.2. 設定解決ロジック (`configResolved` フック内)
151
+
152
+ 1. `config.root` からプロジェクトのルートパスを取得する。
153
+ 2. 既存の `loadConfig(projectRoot)` を呼び出し、`gasnuki.config.ts` の内容を読み込む。
154
+ 3. 以下の優先順位で設定をマージし、`gasnukiOptions` として内部に保持する。
155
+ 1. **高**: `vite.config.ts` でプラグインに渡された `options`
156
+ 2. **中**: `gasnuki.config.ts` の内容
157
+ 3. **低**: プラグイン内部で定義されたデフォルト値 (`srcDir`, `outDir`, `outputFile`)
158
+
159
+ ### 4.3. ファイル監視ロジック (`configureServer` フック内)
160
+
161
+ - Viteの `server.watcher` を利用することで、`chokidar` を直接依存関係に追加する必要はない。
162
+ - `on('all', callback)` を使用し、ファイルの追加(`add`)、変更(`change`)、削除(`unlink`) すべてのイベントを捕捉する。
163
+ - イベントのコールバック内で、変更されたファイルパスが `gasnukiOptions.srcDir` 内に存在するかをチェックし、関係のないファイルの変更で再生成が走らないようにする。
164
+ - 頻繁なファイル変更による過剰な再生成を防ぐため、300ms程度のデバウンス処理を実装する。
165
+
166
+ ## 5. データ構造・インターフェース
167
+
168
+ ### 5.1. `UserConfig` (既存)
169
+
170
+ 既存の `UserConfig` 型をそのままプラグインオプションの型として利用する。
171
+
172
+ ```typescript
173
+ // src/modules/config.ts (既存)
174
+ export type UserConfig = Partial<Omit<GenerateOptions, 'watch' | 'project'>>;
175
+ ```
176
+
177
+ ## 6. シーケンス図
178
+
179
+ ### 6.1. 開発サーバー起動時
180
+
181
+ ```mermaid
182
+ sequenceDiagram
183
+ participant User
184
+ participant Vite
185
+ participant gasnukiPlugin as gasnuki Plugin
186
+ participant Core as gasnuki Core
187
+
188
+ User->>Vite: `vite dev` を実行
189
+ Vite->>gasnukiPlugin: configResolved フックを呼び出し
190
+ gasnukiPlugin->>Core: loadConfig()
191
+ Core-->>gasnukiPlugin: 設定ファイルを返す
192
+ gasnukiPlugin->>gasnukiPlugin: 設定をマージ
193
+ Vite->>gasnukiPlugin: configureServer フックを呼び出し
194
+ gasnukiPlugin->>gasnukiPlugin: runGeneration('server startup')
195
+ gasnukiPlugin->>Core: generateAppsScriptTypes()
196
+ Core-->>gasnukiPlugin: 生成完了
197
+ Note right of gasnukiPlugin: ファイルウォッチャーを設定
198
+ ```
199
+
200
+ ### 6.2. ファイル変更時
201
+
202
+ ```mermaid
203
+ sequenceDiagram
204
+ participant User
205
+ participant ViteWatcher
206
+ participant gasnukiPlugin as gasnuki Plugin
207
+ participant Core as gasnuki Core
208
+
209
+ User->>ViteWatcher: ソースファイルを変更・保存
210
+ ViteWatcher->>gasnukiPlugin: 'all' イベントを発火
211
+ gasnukiPlugin->>gasnukiPlugin: runGeneration('path/to/file.ts')
212
+ Note right of gasnukiPlugin: (デバウンス処理)
213
+ gasnukiPlugin->>Core: generateAppsScriptTypes()
214
+ Core-->>gasnukiPlugin: 再生成完了
215
+ ```
216
+
217
+ ## 7. その他考慮事項
218
+
219
+ - **エラー表示**: `generateAppsScriptTypes` が例外をスローした場合、`consola.error` を使用してViteのコンソールにスタックトレースを含む詳細なエラー情報を出力する。これにより、開発者はエラーの原因を特定しやすくなる。
220
+ - **テスト**: Viteプラグインの動作を検証するための単体テスト・結合テストを追加する。`vite` を `devDependencies` に追加し、テスト環境内でViteサーバーをプログラム的に起動してプラグインの各フックが正しく動作するかを確認する。
@@ -0,0 +1,72 @@
1
+ # `@ciderjs/gasnuki` Viteプラグイン機能追加 要件定義書
2
+
3
+ ## 1. 概要
4
+
5
+ 本ドキュメントは、npmパッケージ `@ciderjs/gasnuki` にViteプラグイン機能を追加するための要件を定義するものである。
6
+
7
+ ## 2. 背景・目的
8
+
9
+ `@ciderjs/gasnuki` は、Google Apps Script (GAS) のサーバーサイドコードからTypeScriptの型定義を生成するCLIツールである。現状では、開発者はCLIを単独で、または `--watch` モードで実行する必要がある。
10
+
11
+ Viteを利用したフロントエンド開発プロジェクトにおいて、`vite dev` コマンドによる開発サーバーのライフサイクルと `gasnuki` の型生成プロセスを連携させることで、以下の目的を達成する。
12
+
13
+ - **開発体験の向上**: 開発者は `vite dev` を実行するだけで、GASコードの変更が自動的にクライアントサイドの型定義に反映されるようになり、手動でのコマンド実行や別プロセスの管理が不要になる。
14
+ - **設定の統合**: `gasnuki` に関する設定を `vite.config.ts` 内に集約し、プロジェクト設定の一元管理を促進する。
15
+ - **シームレスな統合**: Viteの開発エコシステムに `gasnuki` をスムーズに統合し、導入のハードルを下げる。
16
+
17
+ ## 3. スコープ
18
+
19
+ ### 3.1. スコープ内
20
+
21
+ - Viteの開発サーバー (`vite dev` / `serve`) 実行時に動作するViteプラグインの開発。
22
+ - プラグイン経由での `gasnuki` の設定。
23
+ - 開発サーバー起動時の初回型定義生成。
24
+ - ソースファイルの変更を監視し、型定義を自動で再生成する機能。
25
+
26
+ ### 3.2. スコープ外
27
+
28
+ - Viteのビルドプロセス (`vite build`) へのフック。`gasnuki` は開発時の型安全性を目的とするため、本番ビルドには関与しない。
29
+ - CLI機能の変更・廃止。既存のCLI機能はそのまま維持する。
30
+
31
+ ## 4. 機能要件
32
+
33
+ | ID | 機能名 | 種別 | 概要 |
34
+ | :--- | :----------------------------- | :--- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
35
+ | F-01 | **プラグインインターフェース** | 新規 | `import { gasnuki } from '@ciderjs/gasnuki/vite'` のようにインポートできるエントリーポイントを提供する。<br>プラグインは `gasnuki(options?: UserConfig)` という形式のファクトリ関数として提供する。 |
36
+ | F-02 | **設定の解決** | 変更 | プラグインに `options` が渡された場合はその設定を優先する。<br>`options` がない場合は、プロジェクトルートの `gasnuki.config.ts` 等を自動で読み込む。<br>プロジェクトルートの特定にはViteのコンフィグを利用する。 |
37
+ | F-03 | **初回型定義生成** | 新規 | `vite dev` コマンドで開発サーバーが起動する際に、型定義の生成処理を一度実行する。 |
38
+ | F-04 | **ファイル変更時の自動再生成** | 新規 | 開発サーバーの起動中、設定された `srcDir` 内の `.ts` ファイルの変更(追加・更新・削除)を監視し、変更があった場合に型定義の再生成を自動で実行する。 |
39
+ | F-05 | **既存機能との連携** | 変更 | 型定義の生成ロジック (`generateAppsScriptTypes`) や設定ファイルの読み込みロジック (`loadConfig`) など、既存のコア機能を再利用して実装する。 |
40
+
41
+ ## 5. 非機能要件
42
+
43
+ | ID | 要件項目 | 概要 |
44
+ | :---- | :----------------- | :----------------------------------------------------------------------------------------------------- |
45
+ | NF-01 | **パフォーマンス** | ファイル変更時の再生成処理が、開発サーバーの応答性やパフォーマンスに大きな影響を与えないこと。 |
46
+ | NF-02 | **ユーザビリティ** | `vite.config.ts` に数行追加するだけで導入できる、シンプルで分かりやすいAPIを提供すること。 |
47
+ | NF-03 | **フィードバック** | 型定義の生成・再生成の開始、成功、エラー発生時には、Viteのコンソールに適切なメッセージを出力すること。 |
48
+ | NF-04 | **互換性** | 既存のCLI機能とViteプラグイン機能が共存でき、互いに影響を与えないこと。 |
49
+
50
+ ## 6. 実装方針
51
+
52
+ ### 6.1. パッケージ構成
53
+
54
+ - プラグインのロジックを実装するファイル (例: `src/vite.ts`) を新設する。
55
+ - `package.json` の `exports` フィールドに、Viteプラグイン用のエントリーポイント (例: `./vite`) を追加する。
56
+ - ビルド設定 (`build.config.ts`) を更新し、Viteプラグイン用のファイルを成果物として出力する。
57
+
58
+ ### 6.2. Viteプラグインフックの利用
59
+
60
+ - **`name`**: プラグインを識別するための一意な名前 (例: `vite-plugin-gasnuki`) を設定する。
61
+ - **`apply: 'serve'`**: 開発サーバーでのみプラグインが動作するように制限する。
62
+ - **`configResolved(config)`**: Viteの設定が解決された後に呼び出されるフック。`config.root` を基準にプロジェクトルートを特定し、`gasnuki` の最終的な設定オブジェクトをここで確定させる。
63
+ - **`configureServer(server)`**: 開発サーバーが構成された際に呼び出されるフック。
64
+ - サーバー起動処理の一環として、初回の型生成処理を非同期で実行する。
65
+ - `server.watcher` (Viteが内部で使用する `chokidar` インスタンス) を利用して、対象ソースファイルの変更イベントを購読する。
66
+ - ファイル変更イベント発生時に、型定義の再生成処理を呼び出す。
67
+
68
+ ## 7. ドキュメント
69
+
70
+ - `README.md` および `README.ja.md` を更新し、「Viteプラグインとしての利用」のセクションを追加する。
71
+ - `vite.config.ts` への基本的な設定例を記載する。
72
+ - プラグインに渡せるオプションについて説明する。
package/README.ja.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # gasnuki
2
2
 
3
- [![Test Coverage](https://img.shields.io/badge/test%20coverage-81.52%25-green)](https://github.com/luthpg/gasnuki)
3
+ [![Test Coverage](https://img.shields.io/badge/test%20coverage-84.76%25-green)](https://github.com/luthpg/gasnuki)
4
4
  [![License](https://img.shields.io/badge/license-ISC-blue.svg)](LICENSE)
5
5
  [![npm version](https://img.shields.io/npm/v/@ciderjs/gasnuki.svg)](https://www.npmjs.com/package/@ciderjs/gasnuki)
6
6
  [![GitHub issues](https://img.shields.io/github/issues/luthpg/gasnuki.svg)](https://github.com/luthpg/gasnuki/issues)
@@ -45,6 +45,35 @@ npx @ciderjs/gasnuki
45
45
 
46
46
  デフォルトでは `types` ディレクトリに型定義ファイルが生成されます。
47
47
 
48
+ ### Vite プラグインとしての使い方
49
+
50
+ Vite を使用している場合、`gasnuki` をプラグインとして統合し、サーバーサイドのファイルが変更されたときに自動で型定義を生成できます。
51
+
52
+ 1. `vite` と `@ciderjs/gasnuki` をインストールします:
53
+
54
+ ```bash
55
+ pnpm add -D vite @ciderjs/gasnuki
56
+ ```
57
+
58
+ 2. `vite.config.ts` にプラグインを追加します:
59
+
60
+ ```ts
61
+ import { defineConfig } from 'vite';
62
+ import { gasnuki } from '@ciderjs/gasnuki/vite';
63
+
64
+ export default defineConfig({
65
+ plugins: [
66
+ gasnuki({
67
+ /* オプション */
68
+ }),
69
+ ],
70
+ });
71
+ ```
72
+
73
+ これで `vite dev` を実行すると、`gasnuki` がApps Scriptソースファイルの変更を自動的に監視し、型を再生成します。
74
+
75
+ ---
76
+
48
77
  2. 生成されたディレクトリ(デフォルト: `types`)を `tsconfig.json` の `include` に追加してください:
49
78
 
50
79
  ```jsonc
@@ -77,6 +106,60 @@ google.script.run
77
106
  - Google Apps Script クライアントAPIの型定義
78
107
  - サーバーサイド関数の戻り値型をvoidに変換するユーティリティ型
79
108
 
109
+ ### Promiseベースのラッパー
110
+
111
+ `@ciderjs/gasnuki/promise` を利用すると、`google.script.run` をPromiseを返す型安全なラッパーとして使用できます。これにより、`async/await` を使ったモダンな非同期処理を記述できます。
112
+
113
+ 1. まず、`gasnuki` で生成した型定義 (`ServerScripts`) と、`getPromisedServerScripts` 関数をインポートします。
114
+
115
+ ```ts:lib/gas.ts
116
+ import { getPromisedServerScripts } from '@ciderjs/gasnuki/promise';
117
+ // gasnukiが生成した型定義のパスを指定します
118
+ import type { ServerScripts } from '../types/appsscript';
119
+
120
+ export const gas = getPromisedServerScripts<ServerScripts>();
121
+ ```
122
+
123
+ 2. 作成した `gas` オブジェクトを使って、サーバーサイド関数を `async/await` で呼び出します。
124
+
125
+ ```ts:components/MyComponent.tsx
126
+ import { gas } from '../lib/gas';
127
+
128
+ async function fetchData() {
129
+ try {
130
+ // 'getContent' の引数と戻り値が型安全になります
131
+ const result = await gas.getContent('Sheet1');
132
+ console.log(result);
133
+ } catch (error) {
134
+ console.error(error);
135
+ }
136
+ }
137
+ ```
138
+
139
+ #### モックアップによる開発
140
+
141
+ `getPromisedServerScripts` にモック関数を渡すことで、`clasp push` をせずともフロントエンド開発を進めることができます。
142
+
143
+ ```ts:lib/gas.ts
144
+ import {
145
+ getPromisedServerScripts,
146
+ type PartialScriptType,
147
+ } from '@ciderjs/gasnuki/promise';
148
+ import type { ServerScripts } from '../types/appsscript';
149
+
150
+ // 開発用のモック関数を定義します
151
+ const mockup: PartialScriptType<ServerScripts> = {
152
+ // sayHello関数の動作をシミュレート
153
+ sayHello: async (name) => {
154
+ await new Promise(resolve => setTimeout(resolve, 500)); // ネットワーク遅延を模倣
155
+ return `Hello from mockup, ${name}!`;
156
+ },
157
+ // 他の関数も同様にモックできます
158
+ };
159
+
160
+ export const gas = getPromisedServerScripts<ServerScripts>(mockup);
161
+ ```
162
+
80
163
  ## コントリビュート
81
164
 
82
165
  バグ報告やプルリクエストは歓迎します。
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @ciderjs/gasnuki
2
2
 
3
- [![Test Coverage](https://img.shields.io/badge/test%20coverage-81.52%25-green)](https://github.com/luthpg/gasnuki)
3
+ [![Test Coverage](https://img.shields.io/badge/test%20coverage-84.76%25-green)](https://github.com/luthpg/gasnuki)
4
4
  [![License](https://img.shields.io/badge/license-ISC-blue.svg)](LICENSE)
5
5
  [![npm version](https://img.shields.io/npm/v/@ciderjs/gasnuki.svg)](https://www.npmjs.com/package/@ciderjs/gasnuki)
6
6
  [![GitHub issues](https://img.shields.io/github/issues/luthpg/gasnuki.svg)](https://github.com/luthpg/gasnuki/issues)
@@ -44,6 +44,35 @@ npx @ciderjs/gasnuki
44
44
 
45
45
  This will generate type definition files in the `types` directory by default.
46
46
 
47
+ ### Vite Plugin Usage
48
+
49
+ If you are using Vite, you can integrate `gasnuki` as a plugin to automatically generate types when your server-side files change.
50
+
51
+ 1. Install `vite` and `@ciderjs/gasnuki`:
52
+
53
+ ```bash
54
+ pnpm add -D vite @ciderjs/gasnuki
55
+ ```
56
+
57
+ 2. Add the plugin to your `vite.config.ts`:
58
+
59
+ ```ts
60
+ import { defineConfig } from 'vite';
61
+ import { gasnuki } from '@ciderjs/gasnuki/vite';
62
+
63
+ export default defineConfig({
64
+ plugins: [
65
+ gasnuki({
66
+ /* options */
67
+ }),
68
+ ],
69
+ });
70
+ ```
71
+
72
+ Now, when you run `vite dev`, `gasnuki` will automatically watch for changes in your Apps Script source files and regenerate the types.
73
+
74
+ ---
75
+
47
76
  2. Make sure the generated directory (default: `types`) is included in your `tsconfig.json`:
48
77
 
49
78
  ```jsonc
@@ -76,6 +105,62 @@ google.script.run
76
105
  - Type definitions for Google Apps Script client-side API
77
106
  - Utility type to convert server-side function return types to void
78
107
 
108
+ ### Promise-based Wrapper
109
+
110
+ For a more modern asynchronous approach, you can use the type-safe Promise-based wrapper for `google.script.run`. This allows you to write clean code with `async/await`.
111
+
112
+ 1. First, import the type definitions (`ServerScripts`) generated by `gasnuki` and the `getPromisedServerScripts` function.
113
+
114
+ ```ts:lib/gas.ts
115
+ import { getPromisedServerScripts } from '@ciderjs/gasnuki/promise';
116
+ // Specify the path to the type definitions generated by gasnuki
117
+ import type { ServerScripts } from '../types/appsscript';
118
+
119
+ export const gas = getPromisedServerScripts<ServerScripts>();
120
+ ```
121
+
122
+ 2. Use the created `gas` object to call your server-side functions with `async/await`.
123
+
124
+ ```ts:components/MyComponent.tsx
125
+ import { gas } from '../lib/gas';
126
+
127
+ async function fetchData() {
128
+ try {
129
+ // The arguments and return value of 'getContent' are now type-safe!
130
+ const result = await gas.getContent('Sheet1');
131
+ console.log(result);
132
+ } catch (error) {
133
+ console.error(error);
134
+ }
135
+ }
136
+ ```
137
+
138
+ #### Development with Mockups
139
+
140
+ By passing mockup functions to `getPromisedServerScripts`, you can proceed with frontend development without needing to `clasp push` every time.
141
+
142
+ ```ts:lib/gas.ts
143
+ import {
144
+ getPromisedServerScripts,
145
+ type PartialScriptType,
146
+ } from '@ciderjs/gasnuki/promise';
147
+ import type { ServerScripts } from '../types/appsscript';
148
+
149
+ // Define mockup functions for development
150
+ const mockup: PartialScriptType<ServerScripts> = {
151
+ // Simulate the behavior of the sayHello function
152
+ sayHello: async (name) => {
153
+ await new Promise(resolve => setTimeout(resolve, 500)); // Simulate network delay
154
+ return `Hello from mockup, ${name}!`;
155
+ },
156
+ // Other functions can be mocked similarly
157
+ };
158
+
159
+ export const gas = getPromisedServerScripts<ServerScripts>(mockup);
160
+ ```
161
+
162
+ ---
163
+
79
164
  ## Contributing
80
165
 
81
166
  Bug reports and pull requests are welcome. Please use the `issues` or `pull requests` section.
package/dist/cli.cjs CHANGED
@@ -3,7 +3,8 @@
3
3
 
4
4
  const path = require('node:path');
5
5
  const commander = require('commander');
6
- const index = require('./shared/gasnuki.93G9yyCy.cjs');
6
+ const index = require('./index.cjs');
7
+ const config = require('./shared/gasnuki.CxoM0Mts.cjs');
7
8
  require('chokidar');
8
9
  require('consola');
9
10
  require('node:fs');
@@ -24,11 +25,11 @@ function _interopNamespaceCompat(e) {
24
25
 
25
26
  const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
26
27
 
27
- const version = "0.2.10";
28
+ const version = "0.3.0";
28
29
 
29
30
  const parseArgs = async (command) => {
30
31
  const cliOpts = command.opts();
31
- const fileConfig = await index.loadConfig(path__namespace.resolve(cliOpts.project));
32
+ const fileConfig = await config.loadConfig(path__namespace.resolve(cliOpts.project));
32
33
  const defaultOpts = {};
33
34
  for (const option of command.options) {
34
35
  const key = option.attributeName();
package/dist/cli.mjs CHANGED
@@ -1,14 +1,15 @@
1
1
  #! /usr/bin/env node
2
2
  import * as path from 'node:path';
3
3
  import { Command } from 'commander';
4
- import { l as loadConfig, g as generateTypes } from './shared/gasnuki.CS974pL-.mjs';
4
+ import { generateTypes } from './index.mjs';
5
+ import { l as loadConfig } from './shared/gasnuki.BYl_yopE.mjs';
5
6
  import 'chokidar';
6
7
  import 'consola';
7
8
  import 'node:fs';
8
9
  import 'ts-morph';
9
10
  import 'jiti';
10
11
 
11
- const version = "0.2.10";
12
+ const version = "0.3.0";
12
13
 
13
14
  const parseArgs = async (command) => {
14
15
  const cliOpts = command.opts();
package/dist/index.cjs CHANGED
@@ -1,14 +1,79 @@
1
1
  'use strict';
2
2
 
3
- require('node:path');
4
- require('chokidar');
5
- require('consola');
6
- const index = require('./shared/gasnuki.93G9yyCy.cjs');
3
+ const path = require('node:path');
4
+ const chokidar = require('chokidar');
5
+ const consola = require('consola');
6
+ const config = require('./shared/gasnuki.CxoM0Mts.cjs');
7
7
  require('node:fs');
8
8
  require('ts-morph');
9
9
  require('jiti');
10
10
 
11
+ function _interopNamespaceCompat(e) {
12
+ if (e && typeof e === 'object' && 'default' in e) return e;
13
+ const n = Object.create(null);
14
+ if (e) {
15
+ for (const k in e) {
16
+ n[k] = e[k];
17
+ }
18
+ }
19
+ n.default = e;
20
+ return n;
21
+ }
11
22
 
23
+ const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
24
+ const chokidar__namespace = /*#__PURE__*/_interopNamespaceCompat(chokidar);
12
25
 
13
- exports.defineConfig = index.defineConfig;
14
- exports.generateTypes = index.generateTypes;
26
+ const generateTypes = async ({
27
+ project,
28
+ srcDir,
29
+ outDir,
30
+ outputFile,
31
+ watch
32
+ }) => {
33
+ const runGeneration = async (triggeredBy) => {
34
+ const reason = triggeredBy ? ` (${triggeredBy})` : "";
35
+ consola.consola.info(`Generating AppsScript types${reason}...`);
36
+ try {
37
+ await config.generateAppsScriptTypes({ project, srcDir, outDir, outputFile });
38
+ consola.consola.info("Type generation complete.");
39
+ } catch (e) {
40
+ consola.consola.error(`Type generation failed: ${e.message}`, e);
41
+ }
42
+ };
43
+ await runGeneration();
44
+ if (watch) {
45
+ const sourcePathToWatch = path__namespace.resolve(project, srcDir).replace(/\\/g, "/");
46
+ consola.consola.info(
47
+ `Watching for changes in ${sourcePathToWatch}... (Press Ctrl+C to stop)`
48
+ );
49
+ const watcher = chokidar__namespace.watch(sourcePathToWatch, {
50
+ ignored: ["node_modules", "dist"],
51
+ persistent: true,
52
+ ignoreInitial: true
53
+ });
54
+ const eventHandler = async (filePath, eventName) => {
55
+ consola.consola.info(`Watcher is called triggered on ${eventName}`);
56
+ const relativePath = path__namespace.relative(project, filePath);
57
+ await runGeneration(relativePath);
58
+ };
59
+ watcher.on("ready", async () => {
60
+ console.log("...waiting...");
61
+ watcher.on("all", async (event, path2) => {
62
+ consola.consola.info(`Watcher is called triggered on ${event}: ${path2}`);
63
+ await eventHandler(path2, event);
64
+ });
65
+ });
66
+ for (const signal of ["SIGINT", "SIGTERM"]) {
67
+ process.on(signal, async () => {
68
+ await watcher.close();
69
+ consola.consola.info("Watcher is closed.");
70
+ process.exit(0);
71
+ });
72
+ }
73
+ } else {
74
+ process.exit(0);
75
+ }
76
+ };
77
+
78
+ exports.defineConfig = config.defineConfig;
79
+ exports.generateTypes = generateTypes;
package/dist/index.mjs CHANGED
@@ -1,7 +1,62 @@
1
- import 'node:path';
2
- import 'chokidar';
3
- import 'consola';
4
- export { d as defineConfig, g as generateTypes } from './shared/gasnuki.CS974pL-.mjs';
1
+ import * as path from 'node:path';
2
+ import * as chokidar from 'chokidar';
3
+ import { consola } from 'consola';
4
+ import { g as generateAppsScriptTypes } from './shared/gasnuki.BYl_yopE.mjs';
5
+ export { d as defineConfig } from './shared/gasnuki.BYl_yopE.mjs';
5
6
  import 'node:fs';
6
7
  import 'ts-morph';
7
8
  import 'jiti';
9
+
10
+ const generateTypes = async ({
11
+ project,
12
+ srcDir,
13
+ outDir,
14
+ outputFile,
15
+ watch
16
+ }) => {
17
+ const runGeneration = async (triggeredBy) => {
18
+ const reason = triggeredBy ? ` (${triggeredBy})` : "";
19
+ consola.info(`Generating AppsScript types${reason}...`);
20
+ try {
21
+ await generateAppsScriptTypes({ project, srcDir, outDir, outputFile });
22
+ consola.info("Type generation complete.");
23
+ } catch (e) {
24
+ consola.error(`Type generation failed: ${e.message}`, e);
25
+ }
26
+ };
27
+ await runGeneration();
28
+ if (watch) {
29
+ const sourcePathToWatch = path.resolve(project, srcDir).replace(/\\/g, "/");
30
+ consola.info(
31
+ `Watching for changes in ${sourcePathToWatch}... (Press Ctrl+C to stop)`
32
+ );
33
+ const watcher = chokidar.watch(sourcePathToWatch, {
34
+ ignored: ["node_modules", "dist"],
35
+ persistent: true,
36
+ ignoreInitial: true
37
+ });
38
+ const eventHandler = async (filePath, eventName) => {
39
+ consola.info(`Watcher is called triggered on ${eventName}`);
40
+ const relativePath = path.relative(project, filePath);
41
+ await runGeneration(relativePath);
42
+ };
43
+ watcher.on("ready", async () => {
44
+ console.log("...waiting...");
45
+ watcher.on("all", async (event, path2) => {
46
+ consola.info(`Watcher is called triggered on ${event}: ${path2}`);
47
+ await eventHandler(path2, event);
48
+ });
49
+ });
50
+ for (const signal of ["SIGINT", "SIGTERM"]) {
51
+ process.on(signal, async () => {
52
+ await watcher.close();
53
+ consola.info("Watcher is closed.");
54
+ process.exit(0);
55
+ });
56
+ }
57
+ } else {
58
+ process.exit(0);
59
+ }
60
+ };
61
+
62
+ export { generateTypes };
@@ -1,7 +1,6 @@
1
+ import * as fs from 'node:fs';
1
2
  import * as path from 'node:path';
2
- import * as chokidar from 'chokidar';
3
3
  import { consola } from 'consola';
4
- import * as fs from 'node:fs';
5
4
  import { Project, SyntaxKind, SymbolFlags } from 'ts-morph';
6
5
  import { createJiti } from 'jiti';
7
6
 
@@ -79,20 +78,20 @@ const generateAppsScriptTypes = async ({
79
78
  const exportedFunctions = [];
80
79
  for (const sourceFile of sourceFiles) {
81
80
  for (const iface of sourceFile.getInterfaces()) {
82
- if (iface.isExported() && !iface.getName()?.endsWith("_")) {
81
+ if (!iface.getName()?.endsWith("_")) {
83
82
  exportedDeclarations.push(iface);
84
83
  exportedDeclarationNames.add(iface.getName());
85
84
  }
86
85
  }
87
86
  for (const typeAlias of sourceFile.getTypeAliases()) {
88
- if (typeAlias.isExported() && !typeAlias.getName().endsWith("_")) {
87
+ if (!typeAlias.getName().endsWith("_")) {
89
88
  exportedDeclarations.push(typeAlias);
90
89
  exportedDeclarationNames.add(typeAlias.getName());
91
90
  }
92
91
  }
93
92
  for (const func of sourceFile.getFunctions()) {
94
93
  const name = func.getName();
95
- if (func.isExported() && name && !name.endsWith("_") && !SIMPLE_TRIGGER_FUNCTION_NAMES.includes(name)) {
94
+ if (name && !name.endsWith("_") && !SIMPLE_TRIGGER_FUNCTION_NAMES.includes(name)) {
96
95
  exportedDeclarations.push(func);
97
96
  exportedDeclarationNames.add(name);
98
97
  methodDefinitions.push(getInterfaceMethodDefinition_(name, func));
@@ -100,7 +99,6 @@ const generateAppsScriptTypes = async ({
100
99
  }
101
100
  }
102
101
  for (const varStmt of sourceFile.getVariableStatements()) {
103
- if (!varStmt.isExported()) continue;
104
102
  for (const varDecl of varStmt.getDeclarations()) {
105
103
  const name = varDecl.getName();
106
104
  const initializer = varDecl.getInitializer();
@@ -334,56 +332,4 @@ async function loadConfig(projectRoot) {
334
332
  }
335
333
  }
336
334
 
337
- const generateTypes = async ({
338
- project,
339
- srcDir,
340
- outDir,
341
- outputFile,
342
- watch
343
- }) => {
344
- const runGeneration = async (triggeredBy) => {
345
- const reason = triggeredBy ? ` (${triggeredBy})` : "";
346
- consola.info(`Generating AppsScript types${reason}...`);
347
- try {
348
- await generateAppsScriptTypes({ project, srcDir, outDir, outputFile });
349
- consola.info("Type generation complete.");
350
- } catch (e) {
351
- consola.error(`Type generation failed: ${e.message}`, e);
352
- }
353
- };
354
- await runGeneration();
355
- if (watch) {
356
- const sourcePathToWatch = path.resolve(project, srcDir).replace(/\\/g, "/");
357
- consola.info(
358
- `Watching for changes in ${sourcePathToWatch}... (Press Ctrl+C to stop)`
359
- );
360
- const watcher = chokidar.watch(sourcePathToWatch, {
361
- ignored: ["node_modules", "dist"],
362
- persistent: true,
363
- ignoreInitial: true
364
- });
365
- const eventHandler = async (filePath, eventName) => {
366
- consola.info(`Watcher is called triggered on ${eventName}`);
367
- const relativePath = path.relative(project, filePath);
368
- await runGeneration(relativePath);
369
- };
370
- watcher.on("ready", async () => {
371
- console.log("...waiting...");
372
- watcher.on("all", async (event, path2) => {
373
- consola.info(`Watcher is called triggered on ${event}: ${path2}`);
374
- await eventHandler(path2, event);
375
- });
376
- });
377
- for (const signal of ["SIGINT", "SIGTERM"]) {
378
- process.on(signal, async () => {
379
- await watcher.close();
380
- consola.info("Watcher is closed.");
381
- process.exit(0);
382
- });
383
- }
384
- } else {
385
- process.exit(0);
386
- }
387
- };
388
-
389
- export { defineConfig as d, generateTypes as g, loadConfig as l };
335
+ export { defineConfig as d, generateAppsScriptTypes as g, loadConfig as l };
@@ -1,9 +1,8 @@
1
1
  'use strict';
2
2
 
3
+ const fs = require('node:fs');
3
4
  const path = require('node:path');
4
- const chokidar = require('chokidar');
5
5
  const consola = require('consola');
6
- const fs = require('node:fs');
7
6
  const tsMorph = require('ts-morph');
8
7
  const jiti = require('jiti');
9
8
 
@@ -19,9 +18,8 @@ function _interopNamespaceCompat(e) {
19
18
  return n;
20
19
  }
21
20
 
22
- const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
23
- const chokidar__namespace = /*#__PURE__*/_interopNamespaceCompat(chokidar);
24
21
  const fs__namespace = /*#__PURE__*/_interopNamespaceCompat(fs);
22
+ const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
25
23
 
26
24
  const text = "export type RemoveReturnType<T> = {\n [P in keyof T]: T[P] extends (...args: infer A) => any\n ? (...args: A) => void\n : T[P];\n};\n\ntype _AppsScriptRun = RemoveReturnType<ServerScripts> & {\n [key: string]: (...args: any[]) => any;\n withSuccessHandler: <T = string | number | boolean | undefined, U = any>(\n callback: (returnValues: T, userObject?: U) => void,\n ) => _AppsScriptRun;\n withFailureHandler: <U = any>(\n callback: (error: Error, userObject?: U) => void,\n ) => _AppsScriptRun;\n withUserObject: <U = any>(userObject: U) => _AppsScriptRun;\n};\n\ntype _AppsScriptHistoryFunction = (\n stateObject: object,\n params: object,\n hash: string,\n) => void;\n\ninterface _WebAppLocationType {\n hash: string;\n parameter: Record<string, string>;\n parameters: Record<string, string[]>;\n}\n\nexport declare interface GoogleClientSideApi {\n script: {\n run: _AppsScriptRun;\n url: {\n getLocation: (callback: (location: _WebAppLocationType) => void) => void;\n };\n history: {\n push: _AppsScriptHistoryFunction;\n replace: _AppsScriptHistoryFunction;\n setChangeHandler: (\n callback: (e: { state: object; location: _WebAppLocationType }) => void,\n ) => void;\n };\n };\n}\n\ndeclare global {\n const google: GoogleClientSideApi;\n}\n";
27
25
 
@@ -97,20 +95,20 @@ const generateAppsScriptTypes = async ({
97
95
  const exportedFunctions = [];
98
96
  for (const sourceFile of sourceFiles) {
99
97
  for (const iface of sourceFile.getInterfaces()) {
100
- if (iface.isExported() && !iface.getName()?.endsWith("_")) {
98
+ if (!iface.getName()?.endsWith("_")) {
101
99
  exportedDeclarations.push(iface);
102
100
  exportedDeclarationNames.add(iface.getName());
103
101
  }
104
102
  }
105
103
  for (const typeAlias of sourceFile.getTypeAliases()) {
106
- if (typeAlias.isExported() && !typeAlias.getName().endsWith("_")) {
104
+ if (!typeAlias.getName().endsWith("_")) {
107
105
  exportedDeclarations.push(typeAlias);
108
106
  exportedDeclarationNames.add(typeAlias.getName());
109
107
  }
110
108
  }
111
109
  for (const func of sourceFile.getFunctions()) {
112
110
  const name = func.getName();
113
- if (func.isExported() && name && !name.endsWith("_") && !SIMPLE_TRIGGER_FUNCTION_NAMES.includes(name)) {
111
+ if (name && !name.endsWith("_") && !SIMPLE_TRIGGER_FUNCTION_NAMES.includes(name)) {
114
112
  exportedDeclarations.push(func);
115
113
  exportedDeclarationNames.add(name);
116
114
  methodDefinitions.push(getInterfaceMethodDefinition_(name, func));
@@ -118,7 +116,6 @@ const generateAppsScriptTypes = async ({
118
116
  }
119
117
  }
120
118
  for (const varStmt of sourceFile.getVariableStatements()) {
121
- if (!varStmt.isExported()) continue;
122
119
  for (const varDecl of varStmt.getDeclarations()) {
123
120
  const name = varDecl.getName();
124
121
  const initializer = varDecl.getInitializer();
@@ -352,58 +349,6 @@ async function loadConfig(projectRoot) {
352
349
  }
353
350
  }
354
351
 
355
- const generateTypes = async ({
356
- project,
357
- srcDir,
358
- outDir,
359
- outputFile,
360
- watch
361
- }) => {
362
- const runGeneration = async (triggeredBy) => {
363
- const reason = triggeredBy ? ` (${triggeredBy})` : "";
364
- consola.consola.info(`Generating AppsScript types${reason}...`);
365
- try {
366
- await generateAppsScriptTypes({ project, srcDir, outDir, outputFile });
367
- consola.consola.info("Type generation complete.");
368
- } catch (e) {
369
- consola.consola.error(`Type generation failed: ${e.message}`, e);
370
- }
371
- };
372
- await runGeneration();
373
- if (watch) {
374
- const sourcePathToWatch = path__namespace.resolve(project, srcDir).replace(/\\/g, "/");
375
- consola.consola.info(
376
- `Watching for changes in ${sourcePathToWatch}... (Press Ctrl+C to stop)`
377
- );
378
- const watcher = chokidar__namespace.watch(sourcePathToWatch, {
379
- ignored: ["node_modules", "dist"],
380
- persistent: true,
381
- ignoreInitial: true
382
- });
383
- const eventHandler = async (filePath, eventName) => {
384
- consola.consola.info(`Watcher is called triggered on ${eventName}`);
385
- const relativePath = path__namespace.relative(project, filePath);
386
- await runGeneration(relativePath);
387
- };
388
- watcher.on("ready", async () => {
389
- console.log("...waiting...");
390
- watcher.on("all", async (event, path2) => {
391
- consola.consola.info(`Watcher is called triggered on ${event}: ${path2}`);
392
- await eventHandler(path2, event);
393
- });
394
- });
395
- for (const signal of ["SIGINT", "SIGTERM"]) {
396
- process.on(signal, async () => {
397
- await watcher.close();
398
- consola.consola.info("Watcher is closed.");
399
- process.exit(0);
400
- });
401
- }
402
- } else {
403
- process.exit(0);
404
- }
405
- };
406
-
407
352
  exports.defineConfig = defineConfig;
408
- exports.generateTypes = generateTypes;
353
+ exports.generateAppsScriptTypes = generateAppsScriptTypes;
409
354
  exports.loadConfig = loadConfig;
package/dist/vite.cjs ADDED
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const consola = require('consola');
5
+ const config = require('./shared/gasnuki.CxoM0Mts.cjs');
6
+ require('node:fs');
7
+ require('ts-morph');
8
+ require('jiti');
9
+
10
+ function _interopNamespaceCompat(e) {
11
+ if (e && typeof e === 'object' && 'default' in e) return e;
12
+ const n = Object.create(null);
13
+ if (e) {
14
+ for (const k in e) {
15
+ n[k] = e[k];
16
+ }
17
+ }
18
+ n.default = e;
19
+ return n;
20
+ }
21
+
22
+ const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
23
+
24
+ function gasnuki(options) {
25
+ let projectRoot;
26
+ let gasnukiOptions;
27
+ let debounceTimer = null;
28
+ const debounce = (func, timeout = 300) => {
29
+ if (debounceTimer) {
30
+ clearTimeout(debounceTimer);
31
+ }
32
+ debounceTimer = setTimeout(func, timeout);
33
+ };
34
+ const runGeneration = (triggeredBy) => {
35
+ debounce(async () => {
36
+ const reason = triggeredBy ? ` (triggered by: ${triggeredBy})` : "";
37
+ console.log("");
38
+ consola.consola.info(`[gasnuki] Generating AppsScript types${reason}...`);
39
+ try {
40
+ await config.generateAppsScriptTypes({
41
+ ...gasnukiOptions,
42
+ project: projectRoot
43
+ });
44
+ consola.consola.success("[gasnuki] Type generation complete.");
45
+ } catch (e) {
46
+ consola.consola.error(
47
+ `[gasnuki] Type generation failed: ${e.message}`
48
+ );
49
+ }
50
+ });
51
+ };
52
+ return {
53
+ name: "vite-plugin-gasnuki",
54
+ apply: "serve",
55
+ // Apply only in serve mode
56
+ async configResolved(config$1) {
57
+ projectRoot = config$1.root;
58
+ const fileConfig = await config.loadConfig(projectRoot);
59
+ const defaultOptions = {
60
+ srcDir: "server",
61
+ outDir: "types",
62
+ outputFile: "appsscript.ts"
63
+ };
64
+ gasnukiOptions = {
65
+ ...defaultOptions,
66
+ ...fileConfig,
67
+ ...options || {}
68
+ };
69
+ },
70
+ async configureServer(server) {
71
+ const targetDir = path__namespace.resolve(projectRoot, gasnukiOptions.srcDir);
72
+ runGeneration("server startup");
73
+ server.watcher.on("all", (_event, filePath) => {
74
+ const relativePath = path__namespace.relative(targetDir, filePath);
75
+ if (!relativePath.startsWith("..") && relativePath !== "..") {
76
+ runGeneration(path__namespace.relative(projectRoot, filePath));
77
+ }
78
+ });
79
+ }
80
+ };
81
+ }
82
+
83
+ exports.gasnuki = gasnuki;
@@ -0,0 +1,11 @@
1
+ import { Plugin } from 'vite';
2
+ import { UserConfig } from './index.cjs';
3
+
4
+ /**
5
+ * A factory function to create the gasnuki Vite plugin.
6
+ * @param options - The user-defined configuration for gasnuki.
7
+ * @returns The Vite plugin object.
8
+ */
9
+ declare function gasnuki(options?: UserConfig): Plugin;
10
+
11
+ export { gasnuki };
@@ -0,0 +1,11 @@
1
+ import { Plugin } from 'vite';
2
+ import { UserConfig } from './index.mjs';
3
+
4
+ /**
5
+ * A factory function to create the gasnuki Vite plugin.
6
+ * @param options - The user-defined configuration for gasnuki.
7
+ * @returns The Vite plugin object.
8
+ */
9
+ declare function gasnuki(options?: UserConfig): Plugin;
10
+
11
+ export { gasnuki };
package/dist/vite.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { Plugin } from 'vite';
2
+ import { UserConfig } from './index.js';
3
+
4
+ /**
5
+ * A factory function to create the gasnuki Vite plugin.
6
+ * @param options - The user-defined configuration for gasnuki.
7
+ * @returns The Vite plugin object.
8
+ */
9
+ declare function gasnuki(options?: UserConfig): Plugin;
10
+
11
+ export { gasnuki };
package/dist/vite.mjs ADDED
@@ -0,0 +1,67 @@
1
+ import * as path from 'node:path';
2
+ import { consola } from 'consola';
3
+ import { l as loadConfig, g as generateAppsScriptTypes } from './shared/gasnuki.BYl_yopE.mjs';
4
+ import 'node:fs';
5
+ import 'ts-morph';
6
+ import 'jiti';
7
+
8
+ function gasnuki(options) {
9
+ let projectRoot;
10
+ let gasnukiOptions;
11
+ let debounceTimer = null;
12
+ const debounce = (func, timeout = 300) => {
13
+ if (debounceTimer) {
14
+ clearTimeout(debounceTimer);
15
+ }
16
+ debounceTimer = setTimeout(func, timeout);
17
+ };
18
+ const runGeneration = (triggeredBy) => {
19
+ debounce(async () => {
20
+ const reason = triggeredBy ? ` (triggered by: ${triggeredBy})` : "";
21
+ console.log("");
22
+ consola.info(`[gasnuki] Generating AppsScript types${reason}...`);
23
+ try {
24
+ await generateAppsScriptTypes({
25
+ ...gasnukiOptions,
26
+ project: projectRoot
27
+ });
28
+ consola.success("[gasnuki] Type generation complete.");
29
+ } catch (e) {
30
+ consola.error(
31
+ `[gasnuki] Type generation failed: ${e.message}`
32
+ );
33
+ }
34
+ });
35
+ };
36
+ return {
37
+ name: "vite-plugin-gasnuki",
38
+ apply: "serve",
39
+ // Apply only in serve mode
40
+ async configResolved(config) {
41
+ projectRoot = config.root;
42
+ const fileConfig = await loadConfig(projectRoot);
43
+ const defaultOptions = {
44
+ srcDir: "server",
45
+ outDir: "types",
46
+ outputFile: "appsscript.ts"
47
+ };
48
+ gasnukiOptions = {
49
+ ...defaultOptions,
50
+ ...fileConfig,
51
+ ...options || {}
52
+ };
53
+ },
54
+ async configureServer(server) {
55
+ const targetDir = path.resolve(projectRoot, gasnukiOptions.srcDir);
56
+ runGeneration("server startup");
57
+ server.watcher.on("all", (_event, filePath) => {
58
+ const relativePath = path.relative(targetDir, filePath);
59
+ if (!relativePath.startsWith("..") && relativePath !== "..") {
60
+ runGeneration(path.relative(projectRoot, filePath));
61
+ }
62
+ });
63
+ }
64
+ };
65
+ }
66
+
67
+ export { gasnuki };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ciderjs/gasnuki",
3
- "version": "0.2.10",
3
+ "version": "0.3.0",
4
4
  "description": "Type definitions and utilities for Google Apps Script client-side API",
5
5
  "main": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -17,6 +17,11 @@
17
17
  "types": "./dist/promise.d.ts",
18
18
  "import": "./dist/promise.mjs",
19
19
  "require": "./dist/promise.cjs"
20
+ },
21
+ "./vite": {
22
+ "types": "./dist/vite.d.ts",
23
+ "import": "./dist/vite.mjs",
24
+ "require": "./dist/vite.cjs"
20
25
  }
21
26
  },
22
27
  "scripts": {
@@ -47,20 +52,24 @@
47
52
  "bugs": {
48
53
  "url": "https://github.com/luthpg/gasnuki/issues"
49
54
  },
50
- "packageManager": "pnpm@10.11.1",
55
+ "packageManager": "pnpm@10.18.3",
51
56
  "dependencies": {
52
57
  "chokidar": "^4.0.3",
53
58
  "commander": "^14.0.1",
54
59
  "consola": "^3.4.2",
55
- "jiti": "^2.5.1",
56
- "ts-morph": "^27.0.0"
60
+ "jiti": "^2.6.1",
61
+ "ts-morph": "^27.0.2"
57
62
  },
58
63
  "devDependencies": {
59
- "@biomejs/biome": "^2.2.4",
60
- "@types/node": "^24.5.2",
64
+ "@biomejs/biome": "^2.2.6",
65
+ "@types/node": "^24.7.2",
61
66
  "@vitest/coverage-v8": "3.2.4",
62
- "typescript": "^5.9.2",
67
+ "typescript": "^5.9.3",
63
68
  "unbuild": "^3.6.1",
69
+ "vite": "^7.1.10",
64
70
  "vitest": "3.2.4"
71
+ },
72
+ "peerDependencies": {
73
+ "vite": "^7"
65
74
  }
66
75
  }