@ciderjs/gasnuki 0.2.10 → 0.3.1

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.
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-90.91%25-brightgreen)](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-90.91%25-brightgreen)](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.Cbwq1CES.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.1";
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.Bcijv2Ub.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.1";
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.Cbwq1CES.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.Bcijv2Ub.mjs';
5
+ export { d as defineConfig } from './shared/gasnuki.Bcijv2Ub.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
 
@@ -78,21 +77,27 @@ const generateAppsScriptTypes = async ({
78
77
  const exportedDeclarationNames = /* @__PURE__ */ new Set();
79
78
  const exportedFunctions = [];
80
79
  for (const sourceFile of sourceFiles) {
80
+ const filePath = sourceFile.getFilePath().replace(/\\/g, "/");
81
+ const srcPath = absoluteSrcDir.replace(/\\/g, "/");
82
+ const projectRelativeSrcPath = path.join(projectPath, srcDir).replace(/\\/g, "/");
83
+ if (!filePath.startsWith(srcPath) && !filePath.startsWith(projectRelativeSrcPath)) {
84
+ continue;
85
+ }
81
86
  for (const iface of sourceFile.getInterfaces()) {
82
- if (iface.isExported() && !iface.getName()?.endsWith("_")) {
87
+ if (!iface.getName()?.endsWith("_")) {
83
88
  exportedDeclarations.push(iface);
84
89
  exportedDeclarationNames.add(iface.getName());
85
90
  }
86
91
  }
87
92
  for (const typeAlias of sourceFile.getTypeAliases()) {
88
- if (typeAlias.isExported() && !typeAlias.getName().endsWith("_")) {
93
+ if (!typeAlias.getName().endsWith("_")) {
89
94
  exportedDeclarations.push(typeAlias);
90
95
  exportedDeclarationNames.add(typeAlias.getName());
91
96
  }
92
97
  }
93
98
  for (const func of sourceFile.getFunctions()) {
94
99
  const name = func.getName();
95
- if (func.isExported() && name && !name.endsWith("_") && !SIMPLE_TRIGGER_FUNCTION_NAMES.includes(name)) {
100
+ if (name && !name.endsWith("_") && !SIMPLE_TRIGGER_FUNCTION_NAMES.includes(name)) {
96
101
  exportedDeclarations.push(func);
97
102
  exportedDeclarationNames.add(name);
98
103
  methodDefinitions.push(getInterfaceMethodDefinition_(name, func));
@@ -100,7 +105,6 @@ const generateAppsScriptTypes = async ({
100
105
  }
101
106
  }
102
107
  for (const varStmt of sourceFile.getVariableStatements()) {
103
- if (!varStmt.isExported()) continue;
104
108
  for (const varDecl of varStmt.getDeclarations()) {
105
109
  const name = varDecl.getName();
106
110
  const initializer = varDecl.getInitializer();
@@ -195,31 +199,14 @@ const generateAppsScriptTypes = async ({
195
199
  continue;
196
200
  }
197
201
  const sourceFile = declaration.getSourceFile();
198
- if (sourceFile.getFilePath().includes("node_modules")) {
202
+ const sourceFilePath = sourceFile.getFilePath();
203
+ if (sourceFilePath.includes("node_modules")) {
199
204
  continue;
200
205
  }
201
206
  processedSymbols.add(symbolName);
202
- const isLocalDefinition = declaration.getParent()?.getKind() !== SyntaxKind.SourceFile;
203
- if (isLocalDefinition) {
204
- if (returnValueSymbols.has(symbol)) {
205
- const declText = declaration.getText();
206
- inlineDefinitions.set(symbolName, declText);
207
- const tempSourceFile = project.createSourceFile(
208
- `__temp_${symbolName}.ts`,
209
- declText
210
- );
211
- for (const descendant of tempSourceFile.getDescendantsOfKind(
212
- SyntaxKind.TypeReference
213
- )) {
214
- const newSymbol = descendant.getType().getAliasSymbol() ?? descendant.getType().getSymbol();
215
- if (newSymbol) {
216
- symbolsToProcess.add(newSymbol);
217
- }
218
- }
219
- tempSourceFile.delete();
220
- }
221
- } else {
222
- let modulePath = path.relative(absoluteOutDir, sourceFile.getFilePath()).replace(/\\/g, "/");
207
+ const isExternal = !sourceFilePath.replace(/\\/g, "/").startsWith(absoluteSrcDir.replace(/\\/g, "/")) && !sourceFilePath.replace(/\\/g, "/").startsWith(path.join(projectPath, srcDir).replace(/\\/g, "/"));
208
+ if (isExternal) {
209
+ let modulePath = path.relative(absoluteOutDir, sourceFilePath).replace(/\\/g, "/");
223
210
  modulePath = modulePath.replace(/\.(d\.)?ts$/, "");
224
211
  if (modulePath.endsWith("/index")) {
225
212
  modulePath = modulePath.slice(0, -6);
@@ -234,6 +221,17 @@ const generateAppsScriptTypes = async ({
234
221
  importsMap.set(modulePath, /* @__PURE__ */ new Set());
235
222
  }
236
223
  importsMap.get(modulePath)?.add(symbolName);
224
+ } else {
225
+ const declText = declaration.getText();
226
+ inlineDefinitions.set(symbolName, declText);
227
+ }
228
+ for (const descendant of declaration.getDescendantsOfKind(
229
+ SyntaxKind.TypeReference
230
+ )) {
231
+ const newSymbol = descendant.getType().getAliasSymbol() ?? descendant.getType().getSymbol();
232
+ if (newSymbol) {
233
+ symbolsToProcess.add(newSymbol);
234
+ }
237
235
  }
238
236
  }
239
237
  if (!fs.existsSync(absoluteOutDir)) {
@@ -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
 
@@ -96,21 +94,27 @@ const generateAppsScriptTypes = async ({
96
94
  const exportedDeclarationNames = /* @__PURE__ */ new Set();
97
95
  const exportedFunctions = [];
98
96
  for (const sourceFile of sourceFiles) {
97
+ const filePath = sourceFile.getFilePath().replace(/\\/g, "/");
98
+ const srcPath = absoluteSrcDir.replace(/\\/g, "/");
99
+ const projectRelativeSrcPath = path__namespace.join(projectPath, srcDir).replace(/\\/g, "/");
100
+ if (!filePath.startsWith(srcPath) && !filePath.startsWith(projectRelativeSrcPath)) {
101
+ continue;
102
+ }
99
103
  for (const iface of sourceFile.getInterfaces()) {
100
- if (iface.isExported() && !iface.getName()?.endsWith("_")) {
104
+ if (!iface.getName()?.endsWith("_")) {
101
105
  exportedDeclarations.push(iface);
102
106
  exportedDeclarationNames.add(iface.getName());
103
107
  }
104
108
  }
105
109
  for (const typeAlias of sourceFile.getTypeAliases()) {
106
- if (typeAlias.isExported() && !typeAlias.getName().endsWith("_")) {
110
+ if (!typeAlias.getName().endsWith("_")) {
107
111
  exportedDeclarations.push(typeAlias);
108
112
  exportedDeclarationNames.add(typeAlias.getName());
109
113
  }
110
114
  }
111
115
  for (const func of sourceFile.getFunctions()) {
112
116
  const name = func.getName();
113
- if (func.isExported() && name && !name.endsWith("_") && !SIMPLE_TRIGGER_FUNCTION_NAMES.includes(name)) {
117
+ if (name && !name.endsWith("_") && !SIMPLE_TRIGGER_FUNCTION_NAMES.includes(name)) {
114
118
  exportedDeclarations.push(func);
115
119
  exportedDeclarationNames.add(name);
116
120
  methodDefinitions.push(getInterfaceMethodDefinition_(name, func));
@@ -118,7 +122,6 @@ const generateAppsScriptTypes = async ({
118
122
  }
119
123
  }
120
124
  for (const varStmt of sourceFile.getVariableStatements()) {
121
- if (!varStmt.isExported()) continue;
122
125
  for (const varDecl of varStmt.getDeclarations()) {
123
126
  const name = varDecl.getName();
124
127
  const initializer = varDecl.getInitializer();
@@ -213,31 +216,14 @@ const generateAppsScriptTypes = async ({
213
216
  continue;
214
217
  }
215
218
  const sourceFile = declaration.getSourceFile();
216
- if (sourceFile.getFilePath().includes("node_modules")) {
219
+ const sourceFilePath = sourceFile.getFilePath();
220
+ if (sourceFilePath.includes("node_modules")) {
217
221
  continue;
218
222
  }
219
223
  processedSymbols.add(symbolName);
220
- const isLocalDefinition = declaration.getParent()?.getKind() !== tsMorph.SyntaxKind.SourceFile;
221
- if (isLocalDefinition) {
222
- if (returnValueSymbols.has(symbol)) {
223
- const declText = declaration.getText();
224
- inlineDefinitions.set(symbolName, declText);
225
- const tempSourceFile = project.createSourceFile(
226
- `__temp_${symbolName}.ts`,
227
- declText
228
- );
229
- for (const descendant of tempSourceFile.getDescendantsOfKind(
230
- tsMorph.SyntaxKind.TypeReference
231
- )) {
232
- const newSymbol = descendant.getType().getAliasSymbol() ?? descendant.getType().getSymbol();
233
- if (newSymbol) {
234
- symbolsToProcess.add(newSymbol);
235
- }
236
- }
237
- tempSourceFile.delete();
238
- }
239
- } else {
240
- let modulePath = path__namespace.relative(absoluteOutDir, sourceFile.getFilePath()).replace(/\\/g, "/");
224
+ const isExternal = !sourceFilePath.replace(/\\/g, "/").startsWith(absoluteSrcDir.replace(/\\/g, "/")) && !sourceFilePath.replace(/\\/g, "/").startsWith(path__namespace.join(projectPath, srcDir).replace(/\\/g, "/"));
225
+ if (isExternal) {
226
+ let modulePath = path__namespace.relative(absoluteOutDir, sourceFilePath).replace(/\\/g, "/");
241
227
  modulePath = modulePath.replace(/\.(d\.)?ts$/, "");
242
228
  if (modulePath.endsWith("/index")) {
243
229
  modulePath = modulePath.slice(0, -6);
@@ -252,6 +238,17 @@ const generateAppsScriptTypes = async ({
252
238
  importsMap.set(modulePath, /* @__PURE__ */ new Set());
253
239
  }
254
240
  importsMap.get(modulePath)?.add(symbolName);
241
+ } else {
242
+ const declText = declaration.getText();
243
+ inlineDefinitions.set(symbolName, declText);
244
+ }
245
+ for (const descendant of declaration.getDescendantsOfKind(
246
+ tsMorph.SyntaxKind.TypeReference
247
+ )) {
248
+ const newSymbol = descendant.getType().getAliasSymbol() ?? descendant.getType().getSymbol();
249
+ if (newSymbol) {
250
+ symbolsToProcess.add(newSymbol);
251
+ }
255
252
  }
256
253
  }
257
254
  if (!fs__namespace.existsSync(absoluteOutDir)) {
@@ -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.Cbwq1CES.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.Bcijv2Ub.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.1",
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": {
@@ -31,6 +36,7 @@
31
36
  "test:coverage:update": "pnpm test:coverage && pnpm update-coverage-badge",
32
37
  "prepublish": "pnpm run generate && pnpm run check && pnpm run test:coverage:update && pnpm run build",
33
38
  "pg:react": "pnpm -C playground/react",
39
+ "pg:react-vite": "pnpm -C playground/react-with-vite-plugin",
34
40
  "pg:vue": "pnpm -C playground/vue3"
35
41
  },
36
42
  "keywords": [
@@ -47,20 +53,24 @@
47
53
  "bugs": {
48
54
  "url": "https://github.com/luthpg/gasnuki/issues"
49
55
  },
50
- "packageManager": "pnpm@10.11.1",
56
+ "packageManager": "pnpm@10.18.3",
51
57
  "dependencies": {
52
58
  "chokidar": "^4.0.3",
53
59
  "commander": "^14.0.1",
54
60
  "consola": "^3.4.2",
55
- "jiti": "^2.5.1",
56
- "ts-morph": "^27.0.0"
61
+ "jiti": "^2.6.1",
62
+ "ts-morph": "^27.0.2"
57
63
  },
58
64
  "devDependencies": {
59
- "@biomejs/biome": "^2.2.4",
60
- "@types/node": "^24.5.2",
65
+ "@biomejs/biome": "^2.2.6",
66
+ "@types/node": "^24.8.0",
61
67
  "@vitest/coverage-v8": "3.2.4",
62
- "typescript": "^5.9.2",
68
+ "typescript": "^5.9.3",
63
69
  "unbuild": "^3.6.1",
70
+ "vite": "^7.1.10",
64
71
  "vitest": "3.2.4"
72
+ },
73
+ "peerDependencies": {
74
+ "vite": "^7"
65
75
  }
66
76
  }