@gasboost/vite 1.0.1 → 1.1.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.
package/README.md CHANGED
@@ -643,6 +643,58 @@ export default defineConfig(({ mode }) => {
643
643
 
644
644
  `build` を通常の frontend build に含めると、server entry が Vite の build input になるため、frontend と backend の build は分離してください。
645
645
 
646
+ ---
647
+
648
+ ## GAS ランタイム
649
+
650
+ `@gasboost/vite` は、build 時と local RPC 実行時に server entry を実際に評価します。
651
+
652
+ 一般的な GAS API は、デフォルトのローカルランタイムから提供されます。
653
+
654
+ ```ts
655
+ import { gasboost } from "@gasboost/vite";
656
+
657
+ export default gasboost({
658
+ entry: "./src/server.ts",
659
+ });
660
+ ```
661
+
662
+ entry の初期化時に追加の GAS API が必要な場合は、`runtime` から差し込めます。
663
+
664
+ ```ts
665
+ import { SpreadsheetAppStub } from "@gasboost/sheetorm";
666
+ import { gasboost } from "@gasboost/vite";
667
+
668
+ export default gasboost({
669
+ entry: "./src/server.ts",
670
+ runtime: {
671
+ SpreadsheetApp: SpreadsheetAppStub,
672
+ },
673
+ });
674
+ ```
675
+
676
+ `runtime` に渡した値は、デフォルトのローカルランタイムを上書きします。
677
+
678
+ このランタイムは build 時の entry 評価と、local RPC 実行時の両方で利用されます。
679
+
680
+ 基本的には、module 初期化時に必要な API だけを差し込み、実際の GAS API 呼び出しは handler 実行時まで遅延させることを推奨します。
681
+
682
+ ## ランタイム解析
683
+
684
+ GET / POST / RPC の登録状態は、entry ファイルを静的解析するのではなく、実際に評価された `AppsScript` インスタンスから取得します。
685
+
686
+ そのため、import 先、ヘルパー関数、loop、`AppsScript.calls()` 経由の登録にも対応できます。
687
+
688
+ ```ts
689
+ const app = new AppsScript();
690
+
691
+ for (const [name, handler] of Object.entries(handlers)) {
692
+ app.call(name, handler);
693
+ }
694
+
695
+ export default app;
696
+ ```
697
+
646
698
  ## 関連パッケージ
647
699
 
648
700
  - `@gasboost/app` — GAS バックエンドランタイムと RPC 定義
package/dist/dev.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- import type { Plugin } from "vite";
2
- export declare function createDevPlugin(entry: string): Plugin;
1
+ import { type Plugin } from "vite";
2
+ import type { GasboostOptions } from "./gasboost";
3
+ export declare function createDevPlugin(options: GasboostOptions): Plugin;
@@ -1,7 +1,9 @@
1
1
  import type { Plugin } from "vite";
2
+ export type GasRuntime = Readonly<Record<string, unknown>>;
2
3
  export interface GasboostOptions {
3
4
  entry: string;
4
5
  envDir?: string;
6
+ runtime?: GasRuntime;
5
7
  }
6
8
  export declare function gasboost(options: GasboostOptions): {
7
9
  build: Plugin;
package/dist/globals.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import type { AppsScriptAnalysis } from "./analyzer";
2
- export declare function createGlobalCode(analysis: AppsScriptAnalysis): string;
1
+ import type { AppsScriptDescription } from "@gasboost/app";
2
+ export declare function createGlobalCode(analysis: AppsScriptDescription): string;
package/dist/index.js CHANGED
@@ -1,91 +1,12 @@
1
- import e from "node:fs";
2
- import t from "node:path";
3
- import { isCallExpression as n, isIdentifier as r, isNewExpression as i, isPropertyAccessExpression as a, isStringLiteral as o, isVariableDeclaration as s } from "typescript/unstable/ast/is";
4
- import { createVirtualFileSystem as c } from "typescript/unstable/fs";
5
- import { API as l } from "typescript/unstable/sync";
6
- //#region src/analyzer.ts
7
- function u(r) {
8
- let i = t.resolve(r);
9
- if (!e.existsSync(i)) throw Error(`Entry file not found: ${i}`);
10
- let s = d(i, e.readFileSync(i, "utf8")), c = f(s);
11
- if (c.length === 0) throw Error("AppsScript instance not found.");
12
- if (c.length > 1) throw Error("Multiple AppsScript instances found in entry.");
13
- let l = c[0];
14
- g(s, l);
15
- let u = {
16
- hasGet: !1,
17
- hasPost: !1,
18
- calls: []
19
- }, p = /* @__PURE__ */ new Set();
20
- function m(e) {
21
- if (!a(e.expression)) return;
22
- let t = e.expression, n = t.name.text;
23
- if (!h(t.expression, l)) return;
24
- if (n === "get") {
25
- if (u.hasGet) throw Error("Duplicate GET handler registration.");
26
- u.hasGet = !0;
27
- return;
28
- }
29
- if (n === "post") {
30
- if (u.hasPost) throw Error("Duplicate POST handler registration.");
31
- u.hasPost = !0;
32
- return;
33
- }
34
- if (n !== "call") return;
35
- let r = e.arguments[0];
36
- if (!r) throw Error(".call() requires a function name.");
37
- if (!o(r)) throw Error(".call() function name must be a string literal.");
38
- if (p.has(r.text)) throw Error(`Duplicate RPC registration: "${r.text}".`);
39
- p.add(r.text), u.calls.unshift(r.text);
40
- }
41
- function _(e) {
42
- n(e) && m(e), e.forEachChild(_);
43
- }
44
- return _(s), u;
45
- }
46
- function d(e, t) {
47
- let n = "/entry.ts", r = "/tsconfig.json", i = c({
48
- [r]: JSON.stringify({ files: [n] }),
49
- [n]: t
50
- }), a = new l({
51
- cwd: "/",
52
- fs: i
53
- }).updateSnapshot({ openProject: r }).getProject(r);
54
- if (!a) throw Error("Failed to create TypeScript project.");
55
- let o = a.program.getSourceFile(n);
56
- if (!o) throw Error(`Failed to parse entry: ${e}`);
57
- return o;
58
- }
59
- function f(e) {
60
- let t = [];
61
- function n(e) {
62
- s(e) && p(e) && t.push(e.name.text), e.forEachChild(n);
63
- }
64
- return n(e), t;
65
- }
66
- function p(e) {
67
- return !r(e.name) || !e.initializer ? !1 : m(e.initializer);
68
- }
69
- function m(e) {
70
- return i(e) && r(e.expression) && e.expression.text === "AppsScript" ? !0 : n(e) && a(e.expression) ? m(e.expression.expression) : !1;
71
- }
72
- function h(e, t) {
73
- return r(e) && e.text === t || i(e) && r(e.expression) && e.expression.text === "AppsScript" ? !0 : n(e) && a(e.expression) ? h(e.expression.expression, t) : !1;
74
- }
75
- function g(e, t) {
76
- let n = !1;
77
- for (let r of e.statements) if (r.getText() === `export default ${t};`) {
78
- n = !0;
79
- break;
80
- }
81
- if (!n) throw Error("AppsScript instance must be default exported.");
82
- }
83
- //#endregion
1
+ import e from "node:path";
2
+ import { createServer as t, isRunnableDevEnvironment as n } from "vite";
3
+ import { InMemoryCacheService as r, InMemoryContext as i, InMemoryPropertiesService as a, InMemorySession as o, SecurityPolicy as s } from "@gasboost/fake-core";
4
+ import { NodeUtilities as c } from "@gasboost/fake-node";
84
5
  //#region src/config.ts
85
- function _(e) {
86
- let n = t.resolve(e.entry);
6
+ function l(t) {
7
+ let n = e.resolve(t.entry);
87
8
  return {
88
- envDir: e.envDir,
9
+ envDir: t.envDir,
89
10
  build: {
90
11
  target: "es2019",
91
12
  outDir: "dist",
@@ -94,7 +15,7 @@ function _(e) {
94
15
  input: n,
95
16
  output: {
96
17
  format: "cjs",
97
- entryFileNames: t.basename(n, t.extname(n)) + ".js"
18
+ entryFileNames: e.basename(n, e.extname(n)) + ".js"
98
19
  }
99
20
  }
100
21
  }
@@ -102,75 +23,124 @@ function _(e) {
102
23
  }
103
24
  //#endregion
104
25
  //#region src/globals.ts
105
- var v = /* @__PURE__ */ new Set(["doGet", "doPost"]);
106
- function y(e) {
107
- if (v.has(e)) throw Error(`RPC name "${e}" is reserved by Google Apps Script.`);
26
+ var u = /* @__PURE__ */ new Set(["doGet", "doPost"]);
27
+ function d(e) {
28
+ if (u.has(e)) throw Error(`RPC name "${e}" is reserved by Google Apps Script.`);
108
29
  if (!/^[$A-Z_a-z][$\w]*$/u.test(e)) throw Error(`RPC name "${e}" is not a valid JavaScript identifier.`);
109
30
  }
110
- function b(e) {
31
+ function f(e) {
111
32
  let t = [];
112
33
  e.hasGet && t.push("function doGet() {}"), e.hasPost && t.push("function doPost() {}");
113
- for (let n of e.calls) y(n), t.push(`function ${n}() {}`);
34
+ for (let n of e.calls) d(n), t.push(`function ${n}() {}`);
114
35
  return t.join("\n");
115
36
  }
116
37
  //#endregion
38
+ //#region src/runtime.ts
39
+ var p = new i("", "", {
40
+ type: "WEB_APP",
41
+ executeAs: "USER"
42
+ }, new s([]), "en", "UTC"), m = {
43
+ CacheService: new r(),
44
+ PropertiesService: new a(),
45
+ Session: new o(p),
46
+ Utilities: new c()
47
+ };
48
+ function h(e = {}) {
49
+ Object.assign(globalThis, {
50
+ ...m,
51
+ ...e
52
+ });
53
+ }
54
+ //#endregion
55
+ //#region src/loadAppsScript.ts
56
+ async function g(e, r) {
57
+ h(e.runtime);
58
+ let i = await t({
59
+ configFile: !1,
60
+ root: r.root,
61
+ mode: r.mode,
62
+ envDir: e.envDir,
63
+ appType: "custom",
64
+ ssr: { external: ["@gasboost/app"] },
65
+ server: {
66
+ middlewareMode: !0,
67
+ hmr: !1
68
+ }
69
+ });
70
+ try {
71
+ let t = i.environments.ssr;
72
+ if (!n(t)) throw Error("Vite SSR environment is not runnable.");
73
+ let r = await t.runner.import(e.entry);
74
+ if (!r.default) throw Error("AppsScript entry must have a default export.");
75
+ return r.default.describe();
76
+ } finally {
77
+ await i.close();
78
+ }
79
+ }
80
+ //#endregion
117
81
  //#region src/build.ts
118
- function x(e) {
119
- let t;
82
+ function _(e) {
83
+ let t, n;
120
84
  return {
121
85
  name: "gasboost:build",
122
86
  apply: "build",
123
87
  config() {
124
- return _(e);
88
+ return l(e);
125
89
  },
126
- buildStart() {
127
- t = u(e.entry);
90
+ configResolved(e) {
91
+ t = e;
128
92
  },
129
- generateBundle(e, n) {
130
- let r = b(t);
131
- for (let e of Object.values(n)) e.type === "chunk" && e.isEntry && (e.code = `${r}\n\n${e.code}`);
93
+ async buildStart() {
94
+ n = await g(e, t);
95
+ },
96
+ generateBundle(e, t) {
97
+ let r = f(n);
98
+ for (let e of Object.values(t)) e.type === "chunk" && e.isEntry && (e.code = `${r}\n\n${e.code}`);
132
99
  }
133
100
  };
134
101
  }
135
102
  //#endregion
136
103
  //#region src/dev.ts
137
- var S = class extends Error {
104
+ var v = class extends Error {
138
105
  constructor(e) {
139
106
  super(e), this.name = "InvalidRpcRequestError";
140
107
  }
141
108
  };
142
- function C(e) {
109
+ function y(e) {
110
+ let { runtime: t } = e;
143
111
  return {
144
112
  name: "gasboost:dev",
145
113
  apply: "serve",
146
- configureServer(t) {
147
- t.middlewares.use(async (n, r, i) => {
148
- if (!n.url) {
149
- i();
114
+ configureServer(r) {
115
+ h(t), r.middlewares.use(async (t, i, a) => {
116
+ if (!t.url) {
117
+ a();
150
118
  return;
151
119
  }
152
- let a = n.url.split("?")[0];
153
- if (!a.startsWith("/__gasboost/")) {
154
- i();
120
+ let o = t.url.split("?")[0];
121
+ if (!o.startsWith("/__gasboost/")) {
122
+ a();
155
123
  return;
156
124
  }
157
- let o = a.slice(12);
158
- if (!o || o.includes("/")) {
159
- i();
125
+ let s = o.slice(12);
126
+ if (!s || s.includes("/")) {
127
+ a();
160
128
  return;
161
129
  }
162
- if (n.method !== "POST") {
163
- r.statusCode = 405, r.setHeader("Content-Type", "application/json; charset=utf-8"), r.end(JSON.stringify({ error: {
130
+ if (t.method !== "POST") {
131
+ i.statusCode = 405, i.setHeader("Content-Type", "application/json; charset=utf-8"), i.end(JSON.stringify({ error: {
164
132
  name: "MethodNotAllowedError",
165
133
  message: "Only POST is allowed."
166
134
  } }));
167
135
  return;
168
136
  }
169
137
  try {
170
- let { args: i } = await w(n), a = await (await t.ssrLoadModule(e)).default.dispatch(decodeURIComponent(o), ...i);
171
- r.statusCode = 200, r.setHeader("Content-Type", "application/json; charset=utf-8"), r.end(a.contents);
138
+ let { args: a } = await b(t), o = r.environments.ssr;
139
+ if (!n(o)) throw Error("Vite SSR environment is not runnable.");
140
+ let c = await (await o.runner.import(e.entry)).default.dispatch(decodeURIComponent(s), ...a);
141
+ i.statusCode = 200, i.setHeader("Content-Type", "application/json; charset=utf-8"), i.end(c.contents);
172
142
  } catch (e) {
173
- r.statusCode = e instanceof S ? 400 : 500, r.setHeader("Content-Type", "application/json; charset=utf-8"), r.end(JSON.stringify({ error: e instanceof Error ? {
143
+ i.statusCode = e instanceof v ? 400 : 500, i.setHeader("Content-Type", "application/json; charset=utf-8"), i.end(JSON.stringify({ error: e instanceof Error ? {
174
144
  name: e.name,
175
145
  message: e.message,
176
146
  stack: e.stack
@@ -183,7 +153,7 @@ function C(e) {
183
153
  }
184
154
  };
185
155
  }
186
- function w(e) {
156
+ function b(e) {
187
157
  return new Promise((t, n) => {
188
158
  let r = [];
189
159
  e.on("data", (e) => {
@@ -198,11 +168,11 @@ function w(e) {
198
168
  try {
199
169
  i = JSON.parse(e);
200
170
  } catch {
201
- n(new S("Invalid RPC request body. Expected valid JSON."));
171
+ n(new v("Invalid RPC request body. Expected valid JSON."));
202
172
  return;
203
173
  }
204
174
  if (typeof i != "object" || !i || !("args" in i) || !Array.isArray(i.args)) {
205
- n(new S("Invalid RPC request body. Expected { args: unknown[] }."));
175
+ n(new v("Invalid RPC request body. Expected { args: unknown[] }."));
206
176
  return;
207
177
  }
208
178
  t({ args: i.args });
@@ -211,11 +181,11 @@ function w(e) {
211
181
  }
212
182
  //#endregion
213
183
  //#region src/gasboost.ts
214
- function T(e) {
184
+ function x(e) {
215
185
  return {
216
- build: x(e),
217
- dev: C(e.entry)
186
+ build: _(e),
187
+ dev: y(e)
218
188
  };
219
189
  }
220
190
  //#endregion
221
- export { T as gasboost };
191
+ export { x as gasboost };
@@ -0,0 +1,4 @@
1
+ import type { AppsScriptDescription } from "@gasboost/app";
2
+ import { type ResolvedConfig } from "vite";
3
+ import type { GasboostOptions } from "./gasboost";
4
+ export declare function loadAppsScript(options: GasboostOptions, config: ResolvedConfig): Promise<AppsScriptDescription>;
@@ -0,0 +1,3 @@
1
+ import type { GasRuntime } from "./gasboost";
2
+ export declare const defaultGasRuntime: GasRuntime;
3
+ export declare function installGasRuntime(runtime?: GasRuntime): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gasboost/vite",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "A Vite plugin for building @gasboost/app applications for Google Apps Script.",
5
5
  "keywords": [
6
6
  "google-apps-script",
@@ -33,11 +33,14 @@
33
33
  },
34
34
  "type": "module",
35
35
  "devDependencies": {
36
+ "@types/google-apps-script": "^2.0.12",
36
37
  "vite": "^8.2.2"
37
38
  },
38
39
  "dependencies": {
40
+ "@gasboost/fake-core": "^0.1.2",
41
+ "@gasboost/fake-node": "^0.1.2",
39
42
  "typescript": "^7.0.2",
40
- "@gasboost/app": "1.0.1"
43
+ "@gasboost/app": "1.3.0"
41
44
  },
42
45
  "publishConfig": {
43
46
  "access": "public"
@@ -1,6 +0,0 @@
1
- export interface AppsScriptAnalysis {
2
- hasGet: boolean;
3
- hasPost: boolean;
4
- calls: string[];
5
- }
6
- export declare function analyzeAppsScript(entry: string): AppsScriptAnalysis;