@ciderjs/gasnuki 0.2.6 → 0.2.8
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/.vscode/settings.json +3 -0
- package/README.ja.md +1 -1
- package/README.md +1 -1
- package/package.json +14 -11
- package/dist/cli.cjs +0 -77
- package/dist/cli.d.cts +0 -7
- package/dist/cli.d.mts +0 -7
- package/dist/cli.d.ts +0 -7
- package/dist/cli.mjs +0 -60
- package/dist/index.cjs +0 -14
- package/dist/index.d.cts +0 -23
- package/dist/index.d.mts +0 -23
- package/dist/index.d.ts +0 -23
- package/dist/index.mjs +0 -7
- package/dist/promise.cjs +0 -19
- package/dist/promise.d.cts +0 -8
- package/dist/promise.d.mts +0 -8
- package/dist/promise.d.ts +0 -8
- package/dist/promise.mjs +0 -17
- package/dist/shared/gasnuki.DH8w-v3n.cjs +0 -390
- package/dist/shared/gasnuki.DgQBiLxg.mjs +0 -370
package/README.ja.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# gasnuki
|
|
2
2
|
|
|
3
|
-
[](https://github.com/luthpg/gasnuki)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://www.npmjs.com/package/@ciderjs/gasnuki)
|
|
6
6
|
[](https://github.com/luthpg/gasnuki/issues)
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @ciderjs/gasnuki
|
|
2
2
|
|
|
3
|
-
[](https://github.com/luthpg/gasnuki)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://www.npmjs.com/package/@ciderjs/gasnuki)
|
|
6
6
|
[](https://github.com/luthpg/gasnuki/issues)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ciderjs/gasnuki",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
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",
|
|
@@ -29,11 +29,15 @@
|
|
|
29
29
|
"test:coverage": "vitest run --coverage",
|
|
30
30
|
"update-coverage-badge": "jiti scripts/update-coverage-badge.ts README.md README.ja.md",
|
|
31
31
|
"test:coverage:update": "pnpm test:coverage && pnpm update-coverage-badge",
|
|
32
|
-
"
|
|
32
|
+
"prepublish": "pnpm run generate && pnpm run check && pnpm run test:coverage:update && pnpm run build",
|
|
33
33
|
"pg:react": "pnpm -C playground/react",
|
|
34
34
|
"pg:vue": "pnpm -C playground/vue3"
|
|
35
35
|
},
|
|
36
|
-
"keywords": [
|
|
36
|
+
"keywords": [
|
|
37
|
+
"google-apps-script",
|
|
38
|
+
"typescript",
|
|
39
|
+
"@google/clasp"
|
|
40
|
+
],
|
|
37
41
|
"author": "ciderjs/luth",
|
|
38
42
|
"license": "ISC",
|
|
39
43
|
"repository": {
|
|
@@ -45,19 +49,18 @@
|
|
|
45
49
|
},
|
|
46
50
|
"packageManager": "pnpm@10.11.1",
|
|
47
51
|
"dependencies": {
|
|
48
|
-
"@inquirer/prompts": "^7.5.3",
|
|
49
52
|
"chokidar": "^4.0.3",
|
|
50
53
|
"commander": "^14.0.0",
|
|
51
54
|
"consola": "^3.4.2",
|
|
52
|
-
"jiti": "^2.
|
|
55
|
+
"jiti": "^2.5.1",
|
|
53
56
|
"ts-morph": "^26.0.0"
|
|
54
57
|
},
|
|
55
58
|
"devDependencies": {
|
|
56
|
-
"@biomejs/biome": "^1.
|
|
57
|
-
"@types/node": "^
|
|
58
|
-
"@vitest/coverage-v8": "3.2.
|
|
59
|
-
"typescript": "^5.
|
|
60
|
-
"unbuild": "^3.
|
|
61
|
-
"vitest": "
|
|
59
|
+
"@biomejs/biome": "^2.1.4",
|
|
60
|
+
"@types/node": "^24.2.1",
|
|
61
|
+
"@vitest/coverage-v8": "3.2.4",
|
|
62
|
+
"typescript": "^5.9.2",
|
|
63
|
+
"unbuild": "^3.6.0",
|
|
64
|
+
"vitest": "3.2.4"
|
|
62
65
|
}
|
|
63
66
|
}
|
package/dist/cli.cjs
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
#! /usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const commander = require('commander');
|
|
6
|
-
const index = require('./shared/gasnuki.DH8w-v3n.cjs');
|
|
7
|
-
require('chokidar');
|
|
8
|
-
require('consola');
|
|
9
|
-
require('node:fs');
|
|
10
|
-
require('ts-morph');
|
|
11
|
-
require('jiti');
|
|
12
|
-
|
|
13
|
-
function _interopNamespaceCompat(e) {
|
|
14
|
-
if (e && typeof e === 'object' && 'default' in e) return e;
|
|
15
|
-
const n = Object.create(null);
|
|
16
|
-
if (e) {
|
|
17
|
-
for (const k in e) {
|
|
18
|
-
n[k] = e[k];
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
n.default = e;
|
|
22
|
-
return n;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
|
|
26
|
-
|
|
27
|
-
const version = "0.2.6";
|
|
28
|
-
|
|
29
|
-
const parseArgs = async (command) => {
|
|
30
|
-
const cliOpts = command.opts();
|
|
31
|
-
const fileConfig = await index.loadConfig(path__namespace.resolve(cliOpts.project));
|
|
32
|
-
const defaultOpts = {};
|
|
33
|
-
for (const option of command.options) {
|
|
34
|
-
const key = option.attributeName();
|
|
35
|
-
defaultOpts[key] = option.defaultValue;
|
|
36
|
-
}
|
|
37
|
-
const explicitCliOpts = {};
|
|
38
|
-
for (const option of command.options) {
|
|
39
|
-
const key = option.attributeName();
|
|
40
|
-
if (command.getOptionValueSource(key) === "cli") {
|
|
41
|
-
explicitCliOpts[key] = cliOpts[key];
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
const finalOptions = {
|
|
45
|
-
...defaultOpts,
|
|
46
|
-
...fileConfig,
|
|
47
|
-
...explicitCliOpts,
|
|
48
|
-
project: cliOpts.project,
|
|
49
|
-
watch: cliOpts.watch
|
|
50
|
-
};
|
|
51
|
-
await index.generateTypes(finalOptions);
|
|
52
|
-
};
|
|
53
|
-
const cli = async () => {
|
|
54
|
-
const program = new commander.Command();
|
|
55
|
-
program.name("gasnuki").description(
|
|
56
|
-
"Generate type definitions and utilities for Google Apps Script client-side API"
|
|
57
|
-
);
|
|
58
|
-
program.version(version, "-v, --version");
|
|
59
|
-
program.action(async (_param, command) => await parseArgs(command)).option(
|
|
60
|
-
"-p, --project <project>",
|
|
61
|
-
"Project root directory path",
|
|
62
|
-
process.cwd().replace(/\\/g, "/")
|
|
63
|
-
).option(
|
|
64
|
-
"-s, --srcDir <dir>",
|
|
65
|
-
"Source directory name (relative to project root)",
|
|
66
|
-
"server"
|
|
67
|
-
).option(
|
|
68
|
-
"-o, --outDir <dir>",
|
|
69
|
-
"Output directory name (relative to project root)",
|
|
70
|
-
"types"
|
|
71
|
-
).option("-f, --outputFile <file>", "Output file name", "appsscript.ts").option("-w, --watch", "Watch for changes and re-generate types", false);
|
|
72
|
-
await program.parseAsync(process.argv);
|
|
73
|
-
};
|
|
74
|
-
cli();
|
|
75
|
-
|
|
76
|
-
exports.cli = cli;
|
|
77
|
-
exports.parseArgs = parseArgs;
|
package/dist/cli.d.cts
DELETED
package/dist/cli.d.mts
DELETED
package/dist/cli.d.ts
DELETED
package/dist/cli.mjs
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
#! /usr/bin/env node
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import { l as loadConfig, g as generateTypes } from './shared/gasnuki.DgQBiLxg.mjs';
|
|
5
|
-
import 'chokidar';
|
|
6
|
-
import 'consola';
|
|
7
|
-
import 'node:fs';
|
|
8
|
-
import 'ts-morph';
|
|
9
|
-
import 'jiti';
|
|
10
|
-
|
|
11
|
-
const version = "0.2.6";
|
|
12
|
-
|
|
13
|
-
const parseArgs = async (command) => {
|
|
14
|
-
const cliOpts = command.opts();
|
|
15
|
-
const fileConfig = await loadConfig(path.resolve(cliOpts.project));
|
|
16
|
-
const defaultOpts = {};
|
|
17
|
-
for (const option of command.options) {
|
|
18
|
-
const key = option.attributeName();
|
|
19
|
-
defaultOpts[key] = option.defaultValue;
|
|
20
|
-
}
|
|
21
|
-
const explicitCliOpts = {};
|
|
22
|
-
for (const option of command.options) {
|
|
23
|
-
const key = option.attributeName();
|
|
24
|
-
if (command.getOptionValueSource(key) === "cli") {
|
|
25
|
-
explicitCliOpts[key] = cliOpts[key];
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
const finalOptions = {
|
|
29
|
-
...defaultOpts,
|
|
30
|
-
...fileConfig,
|
|
31
|
-
...explicitCliOpts,
|
|
32
|
-
project: cliOpts.project,
|
|
33
|
-
watch: cliOpts.watch
|
|
34
|
-
};
|
|
35
|
-
await generateTypes(finalOptions);
|
|
36
|
-
};
|
|
37
|
-
const cli = async () => {
|
|
38
|
-
const program = new Command();
|
|
39
|
-
program.name("gasnuki").description(
|
|
40
|
-
"Generate type definitions and utilities for Google Apps Script client-side API"
|
|
41
|
-
);
|
|
42
|
-
program.version(version, "-v, --version");
|
|
43
|
-
program.action(async (_param, command) => await parseArgs(command)).option(
|
|
44
|
-
"-p, --project <project>",
|
|
45
|
-
"Project root directory path",
|
|
46
|
-
process.cwd().replace(/\\/g, "/")
|
|
47
|
-
).option(
|
|
48
|
-
"-s, --srcDir <dir>",
|
|
49
|
-
"Source directory name (relative to project root)",
|
|
50
|
-
"server"
|
|
51
|
-
).option(
|
|
52
|
-
"-o, --outDir <dir>",
|
|
53
|
-
"Output directory name (relative to project root)",
|
|
54
|
-
"types"
|
|
55
|
-
).option("-f, --outputFile <file>", "Output file name", "appsscript.ts").option("-w, --watch", "Watch for changes and re-generate types", false);
|
|
56
|
-
await program.parseAsync(process.argv);
|
|
57
|
-
};
|
|
58
|
-
cli();
|
|
59
|
-
|
|
60
|
-
export { cli, parseArgs };
|
package/dist/index.cjs
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
require('node:path');
|
|
4
|
-
require('chokidar');
|
|
5
|
-
require('consola');
|
|
6
|
-
const index = require('./shared/gasnuki.DH8w-v3n.cjs');
|
|
7
|
-
require('node:fs');
|
|
8
|
-
require('ts-morph');
|
|
9
|
-
require('jiti');
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
exports.defineConfig = index.defineConfig;
|
|
14
|
-
exports.generateTypes = index.generateTypes;
|
package/dist/index.d.cts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User-defined configuration for gasnuki.
|
|
3
|
-
* `project` and `watch` options are excluded as they are runtime flags.
|
|
4
|
-
*/
|
|
5
|
-
type UserConfig = Partial<Omit<GenerateOptions, 'watch' | 'project'>>;
|
|
6
|
-
/**
|
|
7
|
-
* A helper function to define the gasnuki configuration with type safety.
|
|
8
|
-
* @param config The configuration object.
|
|
9
|
-
* @returns The configuration object.
|
|
10
|
-
*/
|
|
11
|
-
declare function defineConfig(config: UserConfig): UserConfig;
|
|
12
|
-
|
|
13
|
-
interface GenerateOptions {
|
|
14
|
-
project: string;
|
|
15
|
-
srcDir: string;
|
|
16
|
-
outDir: string;
|
|
17
|
-
outputFile: string;
|
|
18
|
-
watch: boolean;
|
|
19
|
-
}
|
|
20
|
-
declare const generateTypes: ({ project, srcDir, outDir, outputFile, watch, }: GenerateOptions) => Promise<void>;
|
|
21
|
-
|
|
22
|
-
export { defineConfig, generateTypes };
|
|
23
|
-
export type { GenerateOptions, UserConfig };
|
package/dist/index.d.mts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User-defined configuration for gasnuki.
|
|
3
|
-
* `project` and `watch` options are excluded as they are runtime flags.
|
|
4
|
-
*/
|
|
5
|
-
type UserConfig = Partial<Omit<GenerateOptions, 'watch' | 'project'>>;
|
|
6
|
-
/**
|
|
7
|
-
* A helper function to define the gasnuki configuration with type safety.
|
|
8
|
-
* @param config The configuration object.
|
|
9
|
-
* @returns The configuration object.
|
|
10
|
-
*/
|
|
11
|
-
declare function defineConfig(config: UserConfig): UserConfig;
|
|
12
|
-
|
|
13
|
-
interface GenerateOptions {
|
|
14
|
-
project: string;
|
|
15
|
-
srcDir: string;
|
|
16
|
-
outDir: string;
|
|
17
|
-
outputFile: string;
|
|
18
|
-
watch: boolean;
|
|
19
|
-
}
|
|
20
|
-
declare const generateTypes: ({ project, srcDir, outDir, outputFile, watch, }: GenerateOptions) => Promise<void>;
|
|
21
|
-
|
|
22
|
-
export { defineConfig, generateTypes };
|
|
23
|
-
export type { GenerateOptions, UserConfig };
|
package/dist/index.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User-defined configuration for gasnuki.
|
|
3
|
-
* `project` and `watch` options are excluded as they are runtime flags.
|
|
4
|
-
*/
|
|
5
|
-
type UserConfig = Partial<Omit<GenerateOptions, 'watch' | 'project'>>;
|
|
6
|
-
/**
|
|
7
|
-
* A helper function to define the gasnuki configuration with type safety.
|
|
8
|
-
* @param config The configuration object.
|
|
9
|
-
* @returns The configuration object.
|
|
10
|
-
*/
|
|
11
|
-
declare function defineConfig(config: UserConfig): UserConfig;
|
|
12
|
-
|
|
13
|
-
interface GenerateOptions {
|
|
14
|
-
project: string;
|
|
15
|
-
srcDir: string;
|
|
16
|
-
outDir: string;
|
|
17
|
-
outputFile: string;
|
|
18
|
-
watch: boolean;
|
|
19
|
-
}
|
|
20
|
-
declare const generateTypes: ({ project, srcDir, outDir, outputFile, watch, }: GenerateOptions) => Promise<void>;
|
|
21
|
-
|
|
22
|
-
export { defineConfig, generateTypes };
|
|
23
|
-
export type { GenerateOptions, UserConfig };
|
package/dist/index.mjs
DELETED
package/dist/promise.cjs
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const getPromisedServerScripts = (mockupFunctions = {}) => {
|
|
4
|
-
return new Proxy(mockupFunctions, {
|
|
5
|
-
get(target, method) {
|
|
6
|
-
if (!("google" in globalThis) || !google?.script?.run) {
|
|
7
|
-
return target[method];
|
|
8
|
-
}
|
|
9
|
-
if (!(method in google.script.run)) {
|
|
10
|
-
throw Error(`Method ${method} not found in AppsScript.`);
|
|
11
|
-
}
|
|
12
|
-
return (...args) => new Promise((resolve, reject) => {
|
|
13
|
-
google.script.run.withSuccessHandler(resolve).withFailureHandler(reject)[method](...args);
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
exports.getPromisedServerScripts = getPromisedServerScripts;
|
package/dist/promise.d.cts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
type Promised<T> = {
|
|
2
|
-
[K in keyof T]: T[K] extends (...args: infer A) => infer R ? (...args: A) => Promise<R> : T[K];
|
|
3
|
-
};
|
|
4
|
-
type PartialScriptType<T> = Partial<Promised<T>>;
|
|
5
|
-
declare const getPromisedServerScripts: <T extends Record<string, (...args: any[]) => any> = Omit<typeof google.script.run, "withSuccessHandler" | "withFailureHandler" | "withUserObject">>(mockupFunctions?: PartialScriptType<T>) => Promised<T>;
|
|
6
|
-
|
|
7
|
-
export { getPromisedServerScripts };
|
|
8
|
-
export type { PartialScriptType, Promised };
|
package/dist/promise.d.mts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
type Promised<T> = {
|
|
2
|
-
[K in keyof T]: T[K] extends (...args: infer A) => infer R ? (...args: A) => Promise<R> : T[K];
|
|
3
|
-
};
|
|
4
|
-
type PartialScriptType<T> = Partial<Promised<T>>;
|
|
5
|
-
declare const getPromisedServerScripts: <T extends Record<string, (...args: any[]) => any> = Omit<typeof google.script.run, "withSuccessHandler" | "withFailureHandler" | "withUserObject">>(mockupFunctions?: PartialScriptType<T>) => Promised<T>;
|
|
6
|
-
|
|
7
|
-
export { getPromisedServerScripts };
|
|
8
|
-
export type { PartialScriptType, Promised };
|
package/dist/promise.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
type Promised<T> = {
|
|
2
|
-
[K in keyof T]: T[K] extends (...args: infer A) => infer R ? (...args: A) => Promise<R> : T[K];
|
|
3
|
-
};
|
|
4
|
-
type PartialScriptType<T> = Partial<Promised<T>>;
|
|
5
|
-
declare const getPromisedServerScripts: <T extends Record<string, (...args: any[]) => any> = Omit<typeof google.script.run, "withSuccessHandler" | "withFailureHandler" | "withUserObject">>(mockupFunctions?: PartialScriptType<T>) => Promised<T>;
|
|
6
|
-
|
|
7
|
-
export { getPromisedServerScripts };
|
|
8
|
-
export type { PartialScriptType, Promised };
|
package/dist/promise.mjs
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
const getPromisedServerScripts = (mockupFunctions = {}) => {
|
|
2
|
-
return new Proxy(mockupFunctions, {
|
|
3
|
-
get(target, method) {
|
|
4
|
-
if (!("google" in globalThis) || !google?.script?.run) {
|
|
5
|
-
return target[method];
|
|
6
|
-
}
|
|
7
|
-
if (!(method in google.script.run)) {
|
|
8
|
-
throw Error(`Method ${method} not found in AppsScript.`);
|
|
9
|
-
}
|
|
10
|
-
return (...args) => new Promise((resolve, reject) => {
|
|
11
|
-
google.script.run.withSuccessHandler(resolve).withFailureHandler(reject)[method](...args);
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export { getPromisedServerScripts };
|
|
@@ -1,390 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const path = require('node:path');
|
|
4
|
-
const chokidar = require('chokidar');
|
|
5
|
-
const consola = require('consola');
|
|
6
|
-
const fs = require('node:fs');
|
|
7
|
-
const tsMorph = require('ts-morph');
|
|
8
|
-
const jiti = 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
|
-
const chokidar__namespace = /*#__PURE__*/_interopNamespaceCompat(chokidar);
|
|
24
|
-
const fs__namespace = /*#__PURE__*/_interopNamespaceCompat(fs);
|
|
25
|
-
|
|
26
|
-
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 _WebAppLovacationType {\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: (\n callback: (location: _WebAppLovacationType) => void,\n ) => void;\n };\n history: {\n push: _AppsScriptHistoryFunction;\n replace: _AppsScriptHistoryFunction;\n setChangeHandler: (\n callback: (e: {\n state: object;\n location: _WebAppLovacationType;\n }) => void,\n ) => void;\n };\n };\n}\n\ndeclare global {\n const google: GoogleClientSideApi;\n}\n";
|
|
27
|
-
|
|
28
|
-
const getInterfaceMethodDefinition_ = (name, node) => {
|
|
29
|
-
const typeParameters = node.getTypeParameters?.() ?? [];
|
|
30
|
-
const typeParamsString = typeParameters.length > 0 ? `<${typeParameters.map((tp) => tp.getText()).join(", ")}>` : "";
|
|
31
|
-
const parameters = node.getParameters().map((param) => {
|
|
32
|
-
const paramName = param.getName();
|
|
33
|
-
const type = param.getTypeNode()?.getText() ?? param.getType().getText(node) ?? "any";
|
|
34
|
-
const questionToken = param.hasQuestionToken() ? "?" : "";
|
|
35
|
-
return `${paramName}${questionToken}: ${type}`;
|
|
36
|
-
}).join(", ");
|
|
37
|
-
const returnTypeNode = node.getReturnTypeNode();
|
|
38
|
-
let returnType;
|
|
39
|
-
if (returnTypeNode != null) {
|
|
40
|
-
returnType = returnTypeNode.getText();
|
|
41
|
-
} else {
|
|
42
|
-
const inferredReturnType = node.getReturnType();
|
|
43
|
-
if (inferredReturnType.isVoid()) {
|
|
44
|
-
returnType = "void";
|
|
45
|
-
} else {
|
|
46
|
-
returnType = inferredReturnType.getText(node);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
let jsDocString = "";
|
|
50
|
-
const jsDocOwner = "getJsDocs" in node ? node : "getParentOrThrow" in node && // @ts-expect-error variable declaration
|
|
51
|
-
node.getParentOrThrow().getKind() === tsMorph.SyntaxKind.VariableDeclaration ? (
|
|
52
|
-
// @ts-expect-error variable declaration
|
|
53
|
-
node.getParentOrThrow()
|
|
54
|
-
) : null;
|
|
55
|
-
if (jsDocOwner != null) {
|
|
56
|
-
const jsDocs = "getJsDocs" in jsDocOwner ? jsDocOwner.getJsDocs() : [];
|
|
57
|
-
if (jsDocs.length > 0) {
|
|
58
|
-
const firstDoc = jsDocs[0];
|
|
59
|
-
jsDocString = `${firstDoc.getFullText().trim()}
|
|
60
|
-
`;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return `${jsDocString}${name}${typeParamsString}(${parameters}): ${returnType};`;
|
|
64
|
-
};
|
|
65
|
-
const SIMPLE_TRIGGER_FUNCTION_NAMES = [
|
|
66
|
-
"onOpen",
|
|
67
|
-
"onEdit",
|
|
68
|
-
"onInstall",
|
|
69
|
-
"onSelectionChange",
|
|
70
|
-
"doGet",
|
|
71
|
-
"doPost"
|
|
72
|
-
];
|
|
73
|
-
const generateAppsScriptTypes = async ({
|
|
74
|
-
project: projectPath,
|
|
75
|
-
srcDir,
|
|
76
|
-
outDir,
|
|
77
|
-
outputFile
|
|
78
|
-
}) => {
|
|
79
|
-
const absoluteSrcDir = path__namespace.resolve(projectPath, srcDir);
|
|
80
|
-
const absoluteOutDir = path__namespace.resolve(projectPath, outDir);
|
|
81
|
-
const absoluteOutputFile = path__namespace.resolve(absoluteOutDir, outputFile);
|
|
82
|
-
consola.consola.info("Starting AppsScript type generation with gasnuki...");
|
|
83
|
-
consola.consola.info(` AppsScript Source Directory: ${absoluteSrcDir}`);
|
|
84
|
-
consola.consola.info(` Output File: ${absoluteOutputFile}`);
|
|
85
|
-
const project = new tsMorph.Project({
|
|
86
|
-
tsConfigFilePath: path__namespace.resolve(projectPath, "tsconfig.json"),
|
|
87
|
-
skipAddingFilesFromTsConfig: true
|
|
88
|
-
});
|
|
89
|
-
const sourceFilesPattern = path__namespace.join(absoluteSrcDir, "**/*.ts").replace(/\\/g, "/");
|
|
90
|
-
const testFilesPattern = `!${path__namespace.join(absoluteSrcDir, "**/*.{test,spec}.ts").replace(/\\/g, "/")}`;
|
|
91
|
-
project.addSourceFilesAtPaths([sourceFilesPattern, testFilesPattern]);
|
|
92
|
-
const sourceFiles = project.getSourceFiles();
|
|
93
|
-
consola.consola.info(`Found ${sourceFiles.length} source file(s).`);
|
|
94
|
-
const methodDefinitions = [];
|
|
95
|
-
const exportedDeclarations = [];
|
|
96
|
-
const exportedDeclarationNames = /* @__PURE__ */ new Set();
|
|
97
|
-
const exportedFunctions = [];
|
|
98
|
-
for (const sourceFile of sourceFiles) {
|
|
99
|
-
for (const iface of sourceFile.getInterfaces()) {
|
|
100
|
-
if (iface.isExported() && !iface.getName()?.endsWith("_")) {
|
|
101
|
-
exportedDeclarations.push(iface);
|
|
102
|
-
exportedDeclarationNames.add(iface.getName());
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
for (const typeAlias of sourceFile.getTypeAliases()) {
|
|
106
|
-
if (typeAlias.isExported() && !typeAlias.getName().endsWith("_")) {
|
|
107
|
-
exportedDeclarations.push(typeAlias);
|
|
108
|
-
exportedDeclarationNames.add(typeAlias.getName());
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
for (const func of sourceFile.getFunctions()) {
|
|
112
|
-
const name = func.getName();
|
|
113
|
-
if (func.isExported() && name && !name.endsWith("_") && !SIMPLE_TRIGGER_FUNCTION_NAMES.includes(name)) {
|
|
114
|
-
exportedDeclarations.push(func);
|
|
115
|
-
exportedDeclarationNames.add(name);
|
|
116
|
-
methodDefinitions.push(getInterfaceMethodDefinition_(name, func));
|
|
117
|
-
exportedFunctions.push(func);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
for (const varStmt of sourceFile.getVariableStatements()) {
|
|
121
|
-
if (!varStmt.isExported()) continue;
|
|
122
|
-
for (const varDecl of varStmt.getDeclarations()) {
|
|
123
|
-
const name = varDecl.getName();
|
|
124
|
-
const initializer = varDecl.getInitializer();
|
|
125
|
-
if (!name.endsWith("_") && !SIMPLE_TRIGGER_FUNCTION_NAMES.includes(name) && initializer && (initializer.getKind() === tsMorph.SyntaxKind.ArrowFunction || initializer.getKind() === tsMorph.SyntaxKind.FunctionExpression)) {
|
|
126
|
-
const funcExpr = initializer;
|
|
127
|
-
exportedDeclarations.push(varDecl);
|
|
128
|
-
exportedDeclarationNames.add(name);
|
|
129
|
-
methodDefinitions.push(getInterfaceMethodDefinition_(name, funcExpr));
|
|
130
|
-
exportedFunctions.push(funcExpr);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
const collectSymbolsFromType = (type, foundSymbols) => {
|
|
136
|
-
const symbol = type.getAliasSymbol() ?? type.getSymbol();
|
|
137
|
-
if (symbol && !foundSymbols.has(symbol)) {
|
|
138
|
-
foundSymbols.add(symbol);
|
|
139
|
-
if (type.isObject()) {
|
|
140
|
-
for (const prop of type.getProperties()) {
|
|
141
|
-
const propDecl = prop.getDeclarations()[0];
|
|
142
|
-
if (propDecl) {
|
|
143
|
-
collectSymbolsFromType(propDecl.getType(), foundSymbols);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
for (const typeArg of type.getTypeArguments()) {
|
|
149
|
-
collectSymbolsFromType(typeArg, foundSymbols);
|
|
150
|
-
}
|
|
151
|
-
if (type.isUnion()) {
|
|
152
|
-
for (const unionType of type.getUnionTypes()) {
|
|
153
|
-
collectSymbolsFromType(unionType, foundSymbols);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
if (type.isIntersection()) {
|
|
157
|
-
for (const intersectionType of type.getIntersectionTypes()) {
|
|
158
|
-
collectSymbolsFromType(intersectionType, foundSymbols);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
const returnValueSymbols = /* @__PURE__ */ new Set();
|
|
163
|
-
for (const func of exportedFunctions) {
|
|
164
|
-
collectSymbolsFromType(func.getReturnType(), returnValueSymbols);
|
|
165
|
-
}
|
|
166
|
-
const importsMap = /* @__PURE__ */ new Map();
|
|
167
|
-
const inlineDefinitions = /* @__PURE__ */ new Map();
|
|
168
|
-
const symbolsToProcess = /* @__PURE__ */ new Set();
|
|
169
|
-
const processedSymbols = /* @__PURE__ */ new Set();
|
|
170
|
-
for (const decl of exportedDeclarations) {
|
|
171
|
-
for (const descendant of decl.getDescendantsOfKind(
|
|
172
|
-
tsMorph.SyntaxKind.TypeReference
|
|
173
|
-
)) {
|
|
174
|
-
const symbol = descendant.getType().getAliasSymbol() ?? descendant.getType().getSymbol();
|
|
175
|
-
if (symbol) {
|
|
176
|
-
symbolsToProcess.add(symbol);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
while (symbolsToProcess.size > 0) {
|
|
181
|
-
const symbol = symbolsToProcess.values().next().value;
|
|
182
|
-
if (!symbol) continue;
|
|
183
|
-
symbolsToProcess.delete(symbol);
|
|
184
|
-
const symbolName = symbol.getName();
|
|
185
|
-
if (processedSymbols.has(symbolName) || exportedDeclarationNames.has(symbolName) || symbolName === "__type") {
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
const symbolFlags = symbol.getFlags();
|
|
189
|
-
if (symbolFlags & tsMorph.SymbolFlags.TypeParameter) {
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
const declaration = symbol.getDeclarations()[0];
|
|
193
|
-
if (!declaration) {
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
const sourceFile = declaration.getSourceFile();
|
|
197
|
-
if (sourceFile.getFilePath().includes("node_modules")) {
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
processedSymbols.add(symbolName);
|
|
201
|
-
const isLocalDefinition = declaration.getParent()?.getKind() !== tsMorph.SyntaxKind.SourceFile;
|
|
202
|
-
if (isLocalDefinition) {
|
|
203
|
-
if (returnValueSymbols.has(symbol)) {
|
|
204
|
-
const declText = declaration.getText();
|
|
205
|
-
inlineDefinitions.set(symbolName, declText);
|
|
206
|
-
const tempSourceFile = project.createSourceFile(
|
|
207
|
-
`__temp_${symbolName}.ts`,
|
|
208
|
-
declText
|
|
209
|
-
);
|
|
210
|
-
for (const descendant of tempSourceFile.getDescendantsOfKind(
|
|
211
|
-
tsMorph.SyntaxKind.TypeReference
|
|
212
|
-
)) {
|
|
213
|
-
const newSymbol = descendant.getType().getAliasSymbol() ?? descendant.getType().getSymbol();
|
|
214
|
-
if (newSymbol) {
|
|
215
|
-
symbolsToProcess.add(newSymbol);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
tempSourceFile.delete();
|
|
219
|
-
}
|
|
220
|
-
} else {
|
|
221
|
-
let modulePath = path__namespace.relative(absoluteOutDir, sourceFile.getFilePath()).replace(/\\/g, "/");
|
|
222
|
-
modulePath = modulePath.replace(/\.(d\.)?ts$/, "");
|
|
223
|
-
if (modulePath.endsWith("/index")) {
|
|
224
|
-
modulePath = modulePath.slice(0, -6);
|
|
225
|
-
}
|
|
226
|
-
if (modulePath === "index" || modulePath === "") {
|
|
227
|
-
modulePath = ".";
|
|
228
|
-
}
|
|
229
|
-
if (!modulePath.startsWith(".")) {
|
|
230
|
-
modulePath = `./${modulePath}`;
|
|
231
|
-
}
|
|
232
|
-
if (!importsMap.has(modulePath)) {
|
|
233
|
-
importsMap.set(modulePath, /* @__PURE__ */ new Set());
|
|
234
|
-
}
|
|
235
|
-
importsMap.get(modulePath)?.add(symbolName);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
if (!fs__namespace.existsSync(absoluteOutDir)) {
|
|
239
|
-
fs__namespace.mkdirSync(absoluteOutDir, { recursive: true });
|
|
240
|
-
consola.consola.info(`Created output directory: ${absoluteOutDir}`);
|
|
241
|
-
}
|
|
242
|
-
const generatorName = "gasnuki";
|
|
243
|
-
let outputContent = `// Auto-generated by ${generatorName}
|
|
244
|
-
// Do NOT edit this file manually.
|
|
245
|
-
|
|
246
|
-
`;
|
|
247
|
-
const sortedModulePaths = [...importsMap.keys()].sort((a, b) => {
|
|
248
|
-
const aIsRelative = a.startsWith(".");
|
|
249
|
-
const bIsRelative = b.startsWith(".");
|
|
250
|
-
if (aIsRelative !== bIsRelative) return aIsRelative ? 1 : -1;
|
|
251
|
-
return a.localeCompare(b);
|
|
252
|
-
});
|
|
253
|
-
if (sortedModulePaths.length > 0) {
|
|
254
|
-
const importStatements = sortedModulePaths.map((modulePath) => {
|
|
255
|
-
const imports = [...importsMap.get(modulePath) ?? []].sort();
|
|
256
|
-
const finalModulePath = modulePath === "." ? "./index" : modulePath;
|
|
257
|
-
return `import type { ${imports.join(", ")} } from '${finalModulePath}';`;
|
|
258
|
-
});
|
|
259
|
-
outputContent += `${importStatements.join("\n")}
|
|
260
|
-
|
|
261
|
-
`;
|
|
262
|
-
}
|
|
263
|
-
if (inlineDefinitions.size > 0) {
|
|
264
|
-
outputContent += `${[...inlineDefinitions.values()].join("\n\n")}
|
|
265
|
-
|
|
266
|
-
`;
|
|
267
|
-
}
|
|
268
|
-
const exportedTypeDefinitions = exportedDeclarations.filter(
|
|
269
|
-
(d) => d.getKind() === tsMorph.SyntaxKind.InterfaceDeclaration || d.getKind() === tsMorph.SyntaxKind.TypeAliasDeclaration
|
|
270
|
-
).map((decl) => decl.getText());
|
|
271
|
-
if (exportedTypeDefinitions.length > 0) {
|
|
272
|
-
outputContent += `${exportedTypeDefinitions.join("\n\n")}
|
|
273
|
-
|
|
274
|
-
`;
|
|
275
|
-
}
|
|
276
|
-
if (methodDefinitions.length > 0) {
|
|
277
|
-
const formattedMethods = methodDefinitions.map(
|
|
278
|
-
(method) => method.split("\n").map((line) => ` ${line}`).join("\n")
|
|
279
|
-
).join("\n\n");
|
|
280
|
-
outputContent += `export type ServerScripts = {
|
|
281
|
-
${formattedMethods}
|
|
282
|
-
}
|
|
283
|
-
`;
|
|
284
|
-
consola.consola.info(
|
|
285
|
-
`Interface 'ServerScript' type definitions written to ${absoluteOutputFile} (${methodDefinitions.length} function(s), ${exportedTypeDefinitions.length + inlineDefinitions.size} type(s)).`
|
|
286
|
-
);
|
|
287
|
-
} else {
|
|
288
|
-
outputContent += "export type ServerScripts = {}\n";
|
|
289
|
-
consola.consola.info(
|
|
290
|
-
`Interface 'ServerScript' type definitions written to ${absoluteOutputFile} (no functions found).`
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
outputContent += `
|
|
294
|
-
// Auto-generated Types for GoogleAppsScript in client-side code
|
|
295
|
-
|
|
296
|
-
${text}`;
|
|
297
|
-
fs__namespace.writeFileSync(absoluteOutputFile, outputContent);
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
function defineConfig(config) {
|
|
301
|
-
return config;
|
|
302
|
-
}
|
|
303
|
-
async function loadConfig(projectRoot) {
|
|
304
|
-
const configFileExtensions = [".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"];
|
|
305
|
-
let foundConfigPath;
|
|
306
|
-
let foundConfigFileName;
|
|
307
|
-
for (const configFileExtension of configFileExtensions) {
|
|
308
|
-
const configFile = `gasnuki.config${configFileExtension}`;
|
|
309
|
-
const fullPath = path__namespace.resolve(projectRoot, configFile);
|
|
310
|
-
if (fs__namespace.existsSync(fullPath)) {
|
|
311
|
-
foundConfigPath = fullPath;
|
|
312
|
-
foundConfigFileName = configFile;
|
|
313
|
-
break;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
if (!foundConfigPath || !foundConfigFileName) {
|
|
317
|
-
return {};
|
|
318
|
-
}
|
|
319
|
-
try {
|
|
320
|
-
const jiti$1 = jiti.createJiti(projectRoot, {
|
|
321
|
-
fsCache: false,
|
|
322
|
-
moduleCache: false,
|
|
323
|
-
interopDefault: true
|
|
324
|
-
});
|
|
325
|
-
const configModule = await jiti$1.import(foundConfigPath, {
|
|
326
|
-
default: true
|
|
327
|
-
});
|
|
328
|
-
consola.consola.success(`Loaded configuration from ${foundConfigFileName}`);
|
|
329
|
-
return configModule;
|
|
330
|
-
} catch (error) {
|
|
331
|
-
consola.consola.error(`Error loading ${foundConfigFileName}:`, error);
|
|
332
|
-
return {};
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const generateTypes = async ({
|
|
337
|
-
project,
|
|
338
|
-
srcDir,
|
|
339
|
-
outDir,
|
|
340
|
-
outputFile,
|
|
341
|
-
watch
|
|
342
|
-
}) => {
|
|
343
|
-
const runGeneration = async (triggeredBy) => {
|
|
344
|
-
const reason = triggeredBy ? ` (${triggeredBy})` : "";
|
|
345
|
-
consola.consola.info(`Generating AppsScript types${reason}...`);
|
|
346
|
-
try {
|
|
347
|
-
await generateAppsScriptTypes({ project, srcDir, outDir, outputFile });
|
|
348
|
-
consola.consola.info("Type generation complete.");
|
|
349
|
-
} catch (e) {
|
|
350
|
-
consola.consola.error(`Type generation failed: ${e.message}`, e);
|
|
351
|
-
}
|
|
352
|
-
};
|
|
353
|
-
await runGeneration();
|
|
354
|
-
if (watch) {
|
|
355
|
-
const sourcePathToWatch = path__namespace.resolve(project, srcDir).replace(/\\/g, "/");
|
|
356
|
-
consola.consola.info(
|
|
357
|
-
`Watching for changes in ${sourcePathToWatch}... (Press Ctrl+C to stop)`
|
|
358
|
-
);
|
|
359
|
-
const watcher = chokidar__namespace.watch(sourcePathToWatch, {
|
|
360
|
-
ignored: ["node_modules", "dist"],
|
|
361
|
-
persistent: true,
|
|
362
|
-
ignoreInitial: true
|
|
363
|
-
});
|
|
364
|
-
const eventHandler = async (filePath, eventName) => {
|
|
365
|
-
consola.consola.info(`Watcher is called triggered on ${eventName}`);
|
|
366
|
-
const relativePath = path__namespace.relative(project, filePath);
|
|
367
|
-
await runGeneration(relativePath);
|
|
368
|
-
};
|
|
369
|
-
watcher.on("ready", async () => {
|
|
370
|
-
console.log("...waiting...");
|
|
371
|
-
watcher.on("all", async (event, path2) => {
|
|
372
|
-
consola.consola.info(`Watcher is called triggered on ${event}: ${path2}`);
|
|
373
|
-
await eventHandler(path2, event);
|
|
374
|
-
});
|
|
375
|
-
});
|
|
376
|
-
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
377
|
-
process.on(signal, async () => {
|
|
378
|
-
await watcher.close();
|
|
379
|
-
consola.consola.info("Watcher is closed.");
|
|
380
|
-
process.exit(0);
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
} else {
|
|
384
|
-
process.exit(0);
|
|
385
|
-
}
|
|
386
|
-
};
|
|
387
|
-
|
|
388
|
-
exports.defineConfig = defineConfig;
|
|
389
|
-
exports.generateTypes = generateTypes;
|
|
390
|
-
exports.loadConfig = loadConfig;
|
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
import * as path from 'node:path';
|
|
2
|
-
import * as chokidar from 'chokidar';
|
|
3
|
-
import { consola } from 'consola';
|
|
4
|
-
import * as fs from 'node:fs';
|
|
5
|
-
import { Project, SyntaxKind, SymbolFlags } from 'ts-morph';
|
|
6
|
-
import { createJiti } from 'jiti';
|
|
7
|
-
|
|
8
|
-
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 _WebAppLovacationType {\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: (\n callback: (location: _WebAppLovacationType) => void,\n ) => void;\n };\n history: {\n push: _AppsScriptHistoryFunction;\n replace: _AppsScriptHistoryFunction;\n setChangeHandler: (\n callback: (e: {\n state: object;\n location: _WebAppLovacationType;\n }) => void,\n ) => void;\n };\n };\n}\n\ndeclare global {\n const google: GoogleClientSideApi;\n}\n";
|
|
9
|
-
|
|
10
|
-
const getInterfaceMethodDefinition_ = (name, node) => {
|
|
11
|
-
const typeParameters = node.getTypeParameters?.() ?? [];
|
|
12
|
-
const typeParamsString = typeParameters.length > 0 ? `<${typeParameters.map((tp) => tp.getText()).join(", ")}>` : "";
|
|
13
|
-
const parameters = node.getParameters().map((param) => {
|
|
14
|
-
const paramName = param.getName();
|
|
15
|
-
const type = param.getTypeNode()?.getText() ?? param.getType().getText(node) ?? "any";
|
|
16
|
-
const questionToken = param.hasQuestionToken() ? "?" : "";
|
|
17
|
-
return `${paramName}${questionToken}: ${type}`;
|
|
18
|
-
}).join(", ");
|
|
19
|
-
const returnTypeNode = node.getReturnTypeNode();
|
|
20
|
-
let returnType;
|
|
21
|
-
if (returnTypeNode != null) {
|
|
22
|
-
returnType = returnTypeNode.getText();
|
|
23
|
-
} else {
|
|
24
|
-
const inferredReturnType = node.getReturnType();
|
|
25
|
-
if (inferredReturnType.isVoid()) {
|
|
26
|
-
returnType = "void";
|
|
27
|
-
} else {
|
|
28
|
-
returnType = inferredReturnType.getText(node);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
let jsDocString = "";
|
|
32
|
-
const jsDocOwner = "getJsDocs" in node ? node : "getParentOrThrow" in node && // @ts-expect-error variable declaration
|
|
33
|
-
node.getParentOrThrow().getKind() === SyntaxKind.VariableDeclaration ? (
|
|
34
|
-
// @ts-expect-error variable declaration
|
|
35
|
-
node.getParentOrThrow()
|
|
36
|
-
) : null;
|
|
37
|
-
if (jsDocOwner != null) {
|
|
38
|
-
const jsDocs = "getJsDocs" in jsDocOwner ? jsDocOwner.getJsDocs() : [];
|
|
39
|
-
if (jsDocs.length > 0) {
|
|
40
|
-
const firstDoc = jsDocs[0];
|
|
41
|
-
jsDocString = `${firstDoc.getFullText().trim()}
|
|
42
|
-
`;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return `${jsDocString}${name}${typeParamsString}(${parameters}): ${returnType};`;
|
|
46
|
-
};
|
|
47
|
-
const SIMPLE_TRIGGER_FUNCTION_NAMES = [
|
|
48
|
-
"onOpen",
|
|
49
|
-
"onEdit",
|
|
50
|
-
"onInstall",
|
|
51
|
-
"onSelectionChange",
|
|
52
|
-
"doGet",
|
|
53
|
-
"doPost"
|
|
54
|
-
];
|
|
55
|
-
const generateAppsScriptTypes = async ({
|
|
56
|
-
project: projectPath,
|
|
57
|
-
srcDir,
|
|
58
|
-
outDir,
|
|
59
|
-
outputFile
|
|
60
|
-
}) => {
|
|
61
|
-
const absoluteSrcDir = path.resolve(projectPath, srcDir);
|
|
62
|
-
const absoluteOutDir = path.resolve(projectPath, outDir);
|
|
63
|
-
const absoluteOutputFile = path.resolve(absoluteOutDir, outputFile);
|
|
64
|
-
consola.info("Starting AppsScript type generation with gasnuki...");
|
|
65
|
-
consola.info(` AppsScript Source Directory: ${absoluteSrcDir}`);
|
|
66
|
-
consola.info(` Output File: ${absoluteOutputFile}`);
|
|
67
|
-
const project = new Project({
|
|
68
|
-
tsConfigFilePath: path.resolve(projectPath, "tsconfig.json"),
|
|
69
|
-
skipAddingFilesFromTsConfig: true
|
|
70
|
-
});
|
|
71
|
-
const sourceFilesPattern = path.join(absoluteSrcDir, "**/*.ts").replace(/\\/g, "/");
|
|
72
|
-
const testFilesPattern = `!${path.join(absoluteSrcDir, "**/*.{test,spec}.ts").replace(/\\/g, "/")}`;
|
|
73
|
-
project.addSourceFilesAtPaths([sourceFilesPattern, testFilesPattern]);
|
|
74
|
-
const sourceFiles = project.getSourceFiles();
|
|
75
|
-
consola.info(`Found ${sourceFiles.length} source file(s).`);
|
|
76
|
-
const methodDefinitions = [];
|
|
77
|
-
const exportedDeclarations = [];
|
|
78
|
-
const exportedDeclarationNames = /* @__PURE__ */ new Set();
|
|
79
|
-
const exportedFunctions = [];
|
|
80
|
-
for (const sourceFile of sourceFiles) {
|
|
81
|
-
for (const iface of sourceFile.getInterfaces()) {
|
|
82
|
-
if (iface.isExported() && !iface.getName()?.endsWith("_")) {
|
|
83
|
-
exportedDeclarations.push(iface);
|
|
84
|
-
exportedDeclarationNames.add(iface.getName());
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
for (const typeAlias of sourceFile.getTypeAliases()) {
|
|
88
|
-
if (typeAlias.isExported() && !typeAlias.getName().endsWith("_")) {
|
|
89
|
-
exportedDeclarations.push(typeAlias);
|
|
90
|
-
exportedDeclarationNames.add(typeAlias.getName());
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
for (const func of sourceFile.getFunctions()) {
|
|
94
|
-
const name = func.getName();
|
|
95
|
-
if (func.isExported() && name && !name.endsWith("_") && !SIMPLE_TRIGGER_FUNCTION_NAMES.includes(name)) {
|
|
96
|
-
exportedDeclarations.push(func);
|
|
97
|
-
exportedDeclarationNames.add(name);
|
|
98
|
-
methodDefinitions.push(getInterfaceMethodDefinition_(name, func));
|
|
99
|
-
exportedFunctions.push(func);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
for (const varStmt of sourceFile.getVariableStatements()) {
|
|
103
|
-
if (!varStmt.isExported()) continue;
|
|
104
|
-
for (const varDecl of varStmt.getDeclarations()) {
|
|
105
|
-
const name = varDecl.getName();
|
|
106
|
-
const initializer = varDecl.getInitializer();
|
|
107
|
-
if (!name.endsWith("_") && !SIMPLE_TRIGGER_FUNCTION_NAMES.includes(name) && initializer && (initializer.getKind() === SyntaxKind.ArrowFunction || initializer.getKind() === SyntaxKind.FunctionExpression)) {
|
|
108
|
-
const funcExpr = initializer;
|
|
109
|
-
exportedDeclarations.push(varDecl);
|
|
110
|
-
exportedDeclarationNames.add(name);
|
|
111
|
-
methodDefinitions.push(getInterfaceMethodDefinition_(name, funcExpr));
|
|
112
|
-
exportedFunctions.push(funcExpr);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
const collectSymbolsFromType = (type, foundSymbols) => {
|
|
118
|
-
const symbol = type.getAliasSymbol() ?? type.getSymbol();
|
|
119
|
-
if (symbol && !foundSymbols.has(symbol)) {
|
|
120
|
-
foundSymbols.add(symbol);
|
|
121
|
-
if (type.isObject()) {
|
|
122
|
-
for (const prop of type.getProperties()) {
|
|
123
|
-
const propDecl = prop.getDeclarations()[0];
|
|
124
|
-
if (propDecl) {
|
|
125
|
-
collectSymbolsFromType(propDecl.getType(), foundSymbols);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
for (const typeArg of type.getTypeArguments()) {
|
|
131
|
-
collectSymbolsFromType(typeArg, foundSymbols);
|
|
132
|
-
}
|
|
133
|
-
if (type.isUnion()) {
|
|
134
|
-
for (const unionType of type.getUnionTypes()) {
|
|
135
|
-
collectSymbolsFromType(unionType, foundSymbols);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (type.isIntersection()) {
|
|
139
|
-
for (const intersectionType of type.getIntersectionTypes()) {
|
|
140
|
-
collectSymbolsFromType(intersectionType, foundSymbols);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
const returnValueSymbols = /* @__PURE__ */ new Set();
|
|
145
|
-
for (const func of exportedFunctions) {
|
|
146
|
-
collectSymbolsFromType(func.getReturnType(), returnValueSymbols);
|
|
147
|
-
}
|
|
148
|
-
const importsMap = /* @__PURE__ */ new Map();
|
|
149
|
-
const inlineDefinitions = /* @__PURE__ */ new Map();
|
|
150
|
-
const symbolsToProcess = /* @__PURE__ */ new Set();
|
|
151
|
-
const processedSymbols = /* @__PURE__ */ new Set();
|
|
152
|
-
for (const decl of exportedDeclarations) {
|
|
153
|
-
for (const descendant of decl.getDescendantsOfKind(
|
|
154
|
-
SyntaxKind.TypeReference
|
|
155
|
-
)) {
|
|
156
|
-
const symbol = descendant.getType().getAliasSymbol() ?? descendant.getType().getSymbol();
|
|
157
|
-
if (symbol) {
|
|
158
|
-
symbolsToProcess.add(symbol);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
while (symbolsToProcess.size > 0) {
|
|
163
|
-
const symbol = symbolsToProcess.values().next().value;
|
|
164
|
-
if (!symbol) continue;
|
|
165
|
-
symbolsToProcess.delete(symbol);
|
|
166
|
-
const symbolName = symbol.getName();
|
|
167
|
-
if (processedSymbols.has(symbolName) || exportedDeclarationNames.has(symbolName) || symbolName === "__type") {
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
const symbolFlags = symbol.getFlags();
|
|
171
|
-
if (symbolFlags & SymbolFlags.TypeParameter) {
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
const declaration = symbol.getDeclarations()[0];
|
|
175
|
-
if (!declaration) {
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
const sourceFile = declaration.getSourceFile();
|
|
179
|
-
if (sourceFile.getFilePath().includes("node_modules")) {
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
processedSymbols.add(symbolName);
|
|
183
|
-
const isLocalDefinition = declaration.getParent()?.getKind() !== SyntaxKind.SourceFile;
|
|
184
|
-
if (isLocalDefinition) {
|
|
185
|
-
if (returnValueSymbols.has(symbol)) {
|
|
186
|
-
const declText = declaration.getText();
|
|
187
|
-
inlineDefinitions.set(symbolName, declText);
|
|
188
|
-
const tempSourceFile = project.createSourceFile(
|
|
189
|
-
`__temp_${symbolName}.ts`,
|
|
190
|
-
declText
|
|
191
|
-
);
|
|
192
|
-
for (const descendant of tempSourceFile.getDescendantsOfKind(
|
|
193
|
-
SyntaxKind.TypeReference
|
|
194
|
-
)) {
|
|
195
|
-
const newSymbol = descendant.getType().getAliasSymbol() ?? descendant.getType().getSymbol();
|
|
196
|
-
if (newSymbol) {
|
|
197
|
-
symbolsToProcess.add(newSymbol);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
tempSourceFile.delete();
|
|
201
|
-
}
|
|
202
|
-
} else {
|
|
203
|
-
let modulePath = path.relative(absoluteOutDir, sourceFile.getFilePath()).replace(/\\/g, "/");
|
|
204
|
-
modulePath = modulePath.replace(/\.(d\.)?ts$/, "");
|
|
205
|
-
if (modulePath.endsWith("/index")) {
|
|
206
|
-
modulePath = modulePath.slice(0, -6);
|
|
207
|
-
}
|
|
208
|
-
if (modulePath === "index" || modulePath === "") {
|
|
209
|
-
modulePath = ".";
|
|
210
|
-
}
|
|
211
|
-
if (!modulePath.startsWith(".")) {
|
|
212
|
-
modulePath = `./${modulePath}`;
|
|
213
|
-
}
|
|
214
|
-
if (!importsMap.has(modulePath)) {
|
|
215
|
-
importsMap.set(modulePath, /* @__PURE__ */ new Set());
|
|
216
|
-
}
|
|
217
|
-
importsMap.get(modulePath)?.add(symbolName);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
if (!fs.existsSync(absoluteOutDir)) {
|
|
221
|
-
fs.mkdirSync(absoluteOutDir, { recursive: true });
|
|
222
|
-
consola.info(`Created output directory: ${absoluteOutDir}`);
|
|
223
|
-
}
|
|
224
|
-
const generatorName = "gasnuki";
|
|
225
|
-
let outputContent = `// Auto-generated by ${generatorName}
|
|
226
|
-
// Do NOT edit this file manually.
|
|
227
|
-
|
|
228
|
-
`;
|
|
229
|
-
const sortedModulePaths = [...importsMap.keys()].sort((a, b) => {
|
|
230
|
-
const aIsRelative = a.startsWith(".");
|
|
231
|
-
const bIsRelative = b.startsWith(".");
|
|
232
|
-
if (aIsRelative !== bIsRelative) return aIsRelative ? 1 : -1;
|
|
233
|
-
return a.localeCompare(b);
|
|
234
|
-
});
|
|
235
|
-
if (sortedModulePaths.length > 0) {
|
|
236
|
-
const importStatements = sortedModulePaths.map((modulePath) => {
|
|
237
|
-
const imports = [...importsMap.get(modulePath) ?? []].sort();
|
|
238
|
-
const finalModulePath = modulePath === "." ? "./index" : modulePath;
|
|
239
|
-
return `import type { ${imports.join(", ")} } from '${finalModulePath}';`;
|
|
240
|
-
});
|
|
241
|
-
outputContent += `${importStatements.join("\n")}
|
|
242
|
-
|
|
243
|
-
`;
|
|
244
|
-
}
|
|
245
|
-
if (inlineDefinitions.size > 0) {
|
|
246
|
-
outputContent += `${[...inlineDefinitions.values()].join("\n\n")}
|
|
247
|
-
|
|
248
|
-
`;
|
|
249
|
-
}
|
|
250
|
-
const exportedTypeDefinitions = exportedDeclarations.filter(
|
|
251
|
-
(d) => d.getKind() === SyntaxKind.InterfaceDeclaration || d.getKind() === SyntaxKind.TypeAliasDeclaration
|
|
252
|
-
).map((decl) => decl.getText());
|
|
253
|
-
if (exportedTypeDefinitions.length > 0) {
|
|
254
|
-
outputContent += `${exportedTypeDefinitions.join("\n\n")}
|
|
255
|
-
|
|
256
|
-
`;
|
|
257
|
-
}
|
|
258
|
-
if (methodDefinitions.length > 0) {
|
|
259
|
-
const formattedMethods = methodDefinitions.map(
|
|
260
|
-
(method) => method.split("\n").map((line) => ` ${line}`).join("\n")
|
|
261
|
-
).join("\n\n");
|
|
262
|
-
outputContent += `export type ServerScripts = {
|
|
263
|
-
${formattedMethods}
|
|
264
|
-
}
|
|
265
|
-
`;
|
|
266
|
-
consola.info(
|
|
267
|
-
`Interface 'ServerScript' type definitions written to ${absoluteOutputFile} (${methodDefinitions.length} function(s), ${exportedTypeDefinitions.length + inlineDefinitions.size} type(s)).`
|
|
268
|
-
);
|
|
269
|
-
} else {
|
|
270
|
-
outputContent += "export type ServerScripts = {}\n";
|
|
271
|
-
consola.info(
|
|
272
|
-
`Interface 'ServerScript' type definitions written to ${absoluteOutputFile} (no functions found).`
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
outputContent += `
|
|
276
|
-
// Auto-generated Types for GoogleAppsScript in client-side code
|
|
277
|
-
|
|
278
|
-
${text}`;
|
|
279
|
-
fs.writeFileSync(absoluteOutputFile, outputContent);
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
function defineConfig(config) {
|
|
283
|
-
return config;
|
|
284
|
-
}
|
|
285
|
-
async function loadConfig(projectRoot) {
|
|
286
|
-
const configFileExtensions = [".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"];
|
|
287
|
-
let foundConfigPath;
|
|
288
|
-
let foundConfigFileName;
|
|
289
|
-
for (const configFileExtension of configFileExtensions) {
|
|
290
|
-
const configFile = `gasnuki.config${configFileExtension}`;
|
|
291
|
-
const fullPath = path.resolve(projectRoot, configFile);
|
|
292
|
-
if (fs.existsSync(fullPath)) {
|
|
293
|
-
foundConfigPath = fullPath;
|
|
294
|
-
foundConfigFileName = configFile;
|
|
295
|
-
break;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
if (!foundConfigPath || !foundConfigFileName) {
|
|
299
|
-
return {};
|
|
300
|
-
}
|
|
301
|
-
try {
|
|
302
|
-
const jiti = createJiti(projectRoot, {
|
|
303
|
-
fsCache: false,
|
|
304
|
-
moduleCache: false,
|
|
305
|
-
interopDefault: true
|
|
306
|
-
});
|
|
307
|
-
const configModule = await jiti.import(foundConfigPath, {
|
|
308
|
-
default: true
|
|
309
|
-
});
|
|
310
|
-
consola.success(`Loaded configuration from ${foundConfigFileName}`);
|
|
311
|
-
return configModule;
|
|
312
|
-
} catch (error) {
|
|
313
|
-
consola.error(`Error loading ${foundConfigFileName}:`, error);
|
|
314
|
-
return {};
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const generateTypes = async ({
|
|
319
|
-
project,
|
|
320
|
-
srcDir,
|
|
321
|
-
outDir,
|
|
322
|
-
outputFile,
|
|
323
|
-
watch
|
|
324
|
-
}) => {
|
|
325
|
-
const runGeneration = async (triggeredBy) => {
|
|
326
|
-
const reason = triggeredBy ? ` (${triggeredBy})` : "";
|
|
327
|
-
consola.info(`Generating AppsScript types${reason}...`);
|
|
328
|
-
try {
|
|
329
|
-
await generateAppsScriptTypes({ project, srcDir, outDir, outputFile });
|
|
330
|
-
consola.info("Type generation complete.");
|
|
331
|
-
} catch (e) {
|
|
332
|
-
consola.error(`Type generation failed: ${e.message}`, e);
|
|
333
|
-
}
|
|
334
|
-
};
|
|
335
|
-
await runGeneration();
|
|
336
|
-
if (watch) {
|
|
337
|
-
const sourcePathToWatch = path.resolve(project, srcDir).replace(/\\/g, "/");
|
|
338
|
-
consola.info(
|
|
339
|
-
`Watching for changes in ${sourcePathToWatch}... (Press Ctrl+C to stop)`
|
|
340
|
-
);
|
|
341
|
-
const watcher = chokidar.watch(sourcePathToWatch, {
|
|
342
|
-
ignored: ["node_modules", "dist"],
|
|
343
|
-
persistent: true,
|
|
344
|
-
ignoreInitial: true
|
|
345
|
-
});
|
|
346
|
-
const eventHandler = async (filePath, eventName) => {
|
|
347
|
-
consola.info(`Watcher is called triggered on ${eventName}`);
|
|
348
|
-
const relativePath = path.relative(project, filePath);
|
|
349
|
-
await runGeneration(relativePath);
|
|
350
|
-
};
|
|
351
|
-
watcher.on("ready", async () => {
|
|
352
|
-
console.log("...waiting...");
|
|
353
|
-
watcher.on("all", async (event, path2) => {
|
|
354
|
-
consola.info(`Watcher is called triggered on ${event}: ${path2}`);
|
|
355
|
-
await eventHandler(path2, event);
|
|
356
|
-
});
|
|
357
|
-
});
|
|
358
|
-
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
359
|
-
process.on(signal, async () => {
|
|
360
|
-
await watcher.close();
|
|
361
|
-
consola.info("Watcher is closed.");
|
|
362
|
-
process.exit(0);
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
} else {
|
|
366
|
-
process.exit(0);
|
|
367
|
-
}
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
export { defineConfig as d, generateTypes as g, loadConfig as l };
|