@ciderjs/gasnuki 0.3.8 → 0.4.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,7 +1,7 @@
1
1
  # @ciderjs/gasnuki
2
2
 
3
3
  [![README-en](https://img.shields.io/badge/English-blue?logo=ReadMe)](./README.md)
4
- [![Test Coverage](https://img.shields.io/badge/test%20coverage-93.63%25-brightgreen)](https://github.com/luthpg/gasnuki)
4
+ [![Test Coverage](https://img.shields.io/badge/test%20coverage-93.02%25-brightgreen)](https://github.com/luthpg/gasnuki)
5
5
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
6
  [![npm version](https://img.shields.io/npm/v/@ciderjs/gasnuki.svg)](https://www.npmjs.com/package/@ciderjs/gasnuki)
7
7
  [![GitHub issues](https://img.shields.io/github/issues/luthpg/gasnuki.svg)](https://github.com/luthpg/gasnuki/issues)
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @ciderjs/gasnuki
2
2
 
3
3
  [![README-ja](https://img.shields.io/badge/日本語-blue?logo=ReadMe)](./README.ja.md)
4
- [![Test Coverage](https://img.shields.io/badge/test%20coverage-93.63%25-brightgreen)](https://github.com/luthpg/gasnuki)
4
+ [![Test Coverage](https://img.shields.io/badge/test%20coverage-93.02%25-brightgreen)](https://github.com/luthpg/gasnuki)
5
5
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
6
  [![npm version](https://img.shields.io/npm/v/@ciderjs/gasnuki.svg)](https://www.npmjs.com/package/@ciderjs/gasnuki)
7
7
  [![GitHub issues](https://img.shields.io/github/issues/luthpg/gasnuki.svg)](https://github.com/luthpg/gasnuki/issues)
package/dist/cli.cjs CHANGED
@@ -4,9 +4,10 @@
4
4
  const path = require('node:path');
5
5
  const commander = require('commander');
6
6
  const index = require('./index.cjs');
7
- const config = require('./shared/gasnuki.BVdkJsiP.cjs');
7
+ const config = require('./shared/gasnuki.Co0-wxJi.cjs');
8
8
  require('chokidar');
9
9
  require('consola');
10
+ require('node:crypto');
10
11
  require('node:fs');
11
12
  require('ts-morph');
12
13
  require('jiti');
@@ -25,7 +26,7 @@ function _interopNamespaceCompat(e) {
25
26
 
26
27
  const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
27
28
 
28
- const version = "0.3.8";
29
+ const version = "0.4.1";
29
30
 
30
31
  const parseArgs = async (command) => {
31
32
  const cliOpts = command.opts();
@@ -69,7 +70,7 @@ const runCli = async () => {
69
70
  "-o, --outDir <dir>",
70
71
  "Output directory name (relative to project root)",
71
72
  "types"
72
- ).option("-f, --outputFile <file>", "Output file name", "appsscript.ts").option("-w, --watch", "Watch for changes and re-generate types", false);
73
+ ).option("-f, --outputFile <file>", "Output file name", "appsscript.ts").option("-w, --watch", "Watch for changes and re-generate types", false).option("--cache", "Enable checking generation cache", true).option("--no-cache", "Disable checking generation cache", true);
73
74
  await program.parseAsync(process.argv);
74
75
  };
75
76
  const isMainModule = typeof require !== "undefined" && require.main === module;
package/dist/cli.mjs CHANGED
@@ -2,14 +2,15 @@
2
2
  import * as path from 'node:path';
3
3
  import { Command } from 'commander';
4
4
  import { generateTypes } from './index.mjs';
5
- import { l as loadConfig } from './shared/gasnuki.Cd8JOTQ5.mjs';
5
+ import { l as loadConfig } from './shared/gasnuki.DePJgSg9.mjs';
6
6
  import 'chokidar';
7
7
  import 'consola';
8
+ import 'node:crypto';
8
9
  import 'node:fs';
9
10
  import 'ts-morph';
10
11
  import 'jiti';
11
12
 
12
- const version = "0.3.8";
13
+ const version = "0.4.1";
13
14
 
14
15
  const parseArgs = async (command) => {
15
16
  const cliOpts = command.opts();
@@ -53,7 +54,7 @@ const runCli = async () => {
53
54
  "-o, --outDir <dir>",
54
55
  "Output directory name (relative to project root)",
55
56
  "types"
56
- ).option("-f, --outputFile <file>", "Output file name", "appsscript.ts").option("-w, --watch", "Watch for changes and re-generate types", false);
57
+ ).option("-f, --outputFile <file>", "Output file name", "appsscript.ts").option("-w, --watch", "Watch for changes and re-generate types", false).option("--cache", "Enable checking generation cache", true).option("--no-cache", "Disable checking generation cache", true);
57
58
  await program.parseAsync(process.argv);
58
59
  };
59
60
  const isMainModule = typeof require !== "undefined" && require.main === module;
package/dist/index.cjs CHANGED
@@ -3,7 +3,8 @@
3
3
  const path = require('node:path');
4
4
  const chokidar = require('chokidar');
5
5
  const consola = require('consola');
6
- const config = require('./shared/gasnuki.BVdkJsiP.cjs');
6
+ const config = require('./shared/gasnuki.Co0-wxJi.cjs');
7
+ require('node:crypto');
7
8
  require('node:fs');
8
9
  require('ts-morph');
9
10
  require('jiti');
@@ -28,13 +29,23 @@ const generateTypes = async ({
28
29
  srcDir,
29
30
  outDir,
30
31
  outputFile,
31
- watch
32
+ watch,
33
+ cache
32
34
  }) => {
35
+ if (cache === false) {
36
+ consola.consola.info("Generation cache is disabled");
37
+ }
33
38
  const runGeneration = async (triggeredBy) => {
34
39
  const reason = triggeredBy ? ` (${triggeredBy})` : "";
35
40
  consola.consola.info(`Generating AppsScript types${reason}...`);
36
41
  try {
37
- await config.generateAppsScriptTypes({ project, srcDir, outDir, outputFile });
42
+ await config.generateAppsScriptTypes({
43
+ project,
44
+ srcDir,
45
+ outDir,
46
+ outputFile,
47
+ cache
48
+ });
38
49
  consola.consola.info("Type generation complete.");
39
50
  } catch (e) {
40
51
  consola.consola.error(`Type generation failed: ${e.message}`, e);
package/dist/index.d.cts CHANGED
@@ -16,8 +16,9 @@ interface GenerateOptions {
16
16
  outDir: string;
17
17
  outputFile: string;
18
18
  watch: boolean;
19
+ cache?: boolean;
19
20
  }
20
- declare const generateTypes: ({ project, srcDir, outDir, outputFile, watch, }: GenerateOptions) => Promise<void>;
21
+ declare const generateTypes: ({ project, srcDir, outDir, outputFile, watch, cache, }: GenerateOptions) => Promise<void>;
21
22
 
22
23
  export { defineConfig, generateTypes };
23
24
  export type { GenerateOptions, UserConfig };
package/dist/index.d.mts CHANGED
@@ -16,8 +16,9 @@ interface GenerateOptions {
16
16
  outDir: string;
17
17
  outputFile: string;
18
18
  watch: boolean;
19
+ cache?: boolean;
19
20
  }
20
- declare const generateTypes: ({ project, srcDir, outDir, outputFile, watch, }: GenerateOptions) => Promise<void>;
21
+ declare const generateTypes: ({ project, srcDir, outDir, outputFile, watch, cache, }: GenerateOptions) => Promise<void>;
21
22
 
22
23
  export { defineConfig, generateTypes };
23
24
  export type { GenerateOptions, UserConfig };
package/dist/index.d.ts CHANGED
@@ -16,8 +16,9 @@ interface GenerateOptions {
16
16
  outDir: string;
17
17
  outputFile: string;
18
18
  watch: boolean;
19
+ cache?: boolean;
19
20
  }
20
- declare const generateTypes: ({ project, srcDir, outDir, outputFile, watch, }: GenerateOptions) => Promise<void>;
21
+ declare const generateTypes: ({ project, srcDir, outDir, outputFile, watch, cache, }: GenerateOptions) => Promise<void>;
21
22
 
22
23
  export { defineConfig, generateTypes };
23
24
  export type { GenerateOptions, UserConfig };
package/dist/index.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  import * as path from 'node:path';
2
2
  import * as chokidar from 'chokidar';
3
3
  import { consola } from 'consola';
4
- import { g as generateAppsScriptTypes } from './shared/gasnuki.Cd8JOTQ5.mjs';
5
- export { d as defineConfig } from './shared/gasnuki.Cd8JOTQ5.mjs';
4
+ import { g as generateAppsScriptTypes } from './shared/gasnuki.DePJgSg9.mjs';
5
+ export { d as defineConfig } from './shared/gasnuki.DePJgSg9.mjs';
6
+ import 'node:crypto';
6
7
  import 'node:fs';
7
8
  import 'ts-morph';
8
9
  import 'jiti';
@@ -12,13 +13,23 @@ const generateTypes = async ({
12
13
  srcDir,
13
14
  outDir,
14
15
  outputFile,
15
- watch
16
+ watch,
17
+ cache
16
18
  }) => {
19
+ if (cache === false) {
20
+ consola.info("Generation cache is disabled");
21
+ }
17
22
  const runGeneration = async (triggeredBy) => {
18
23
  const reason = triggeredBy ? ` (${triggeredBy})` : "";
19
24
  consola.info(`Generating AppsScript types${reason}...`);
20
25
  try {
21
- await generateAppsScriptTypes({ project, srcDir, outDir, outputFile });
26
+ await generateAppsScriptTypes({
27
+ project,
28
+ srcDir,
29
+ outDir,
30
+ outputFile,
31
+ cache
32
+ });
22
33
  consola.info("Type generation complete.");
23
34
  } catch (e) {
24
35
  consola.error(`Type generation failed: ${e.message}`, e);
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const crypto = require('node:crypto');
3
4
  const fs = require('node:fs');
4
5
  const path = require('node:path');
5
6
  const consola = require('consola');
@@ -18,11 +19,24 @@ function _interopNamespaceCompat(e) {
18
19
  return n;
19
20
  }
20
21
 
22
+ const crypto__namespace = /*#__PURE__*/_interopNamespaceCompat(crypto);
21
23
  const fs__namespace = /*#__PURE__*/_interopNamespaceCompat(fs);
22
24
  const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
23
25
 
24
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 _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";
25
27
 
28
+ const generationCache = /* @__PURE__ */ new Map();
29
+ const computeSourceHash = (sourceFiles) => {
30
+ const hash = crypto__namespace.createHash("md5");
31
+ const sortedFiles = [...sourceFiles].sort(
32
+ (a, b) => a.getFilePath().localeCompare(b.getFilePath())
33
+ );
34
+ for (const file of sortedFiles) {
35
+ hash.update(file.getFilePath());
36
+ hash.update(file.getFullText());
37
+ }
38
+ return hash.digest("hex");
39
+ };
26
40
  const getInterfaceMethodDefinition_ = (name, node) => {
27
41
  const typeParameters = node.getTypeParameters?.() ?? [];
28
42
  const typeParamsString = typeParameters.length > 0 ? `<${typeParameters.map((tp) => tp.getText()).join(", ")}>` : "";
@@ -58,7 +72,7 @@ const getInterfaceMethodDefinition_ = (name, node) => {
58
72
  `;
59
73
  }
60
74
  }
61
- const cleanType = (t) => t.replace(/import\(.*?\)\./g, "");
75
+ const cleanType = (t) => t.replace(/import\(".*?"\)\.default\./g, "").replace(/import\(".*?"\)\./g, "");
62
76
  const cleanedReturnType = cleanType(returnType);
63
77
  const cleanedParameters = parameters.replace(
64
78
  /:\s*([^,;)]+)/g,
@@ -103,7 +117,9 @@ const generateAppsScriptTypes = async ({
103
117
  srcDir,
104
118
  outDir,
105
119
  outputFile,
106
- projectInstance
120
+ projectInstance,
121
+ cache = true
122
+ // Default to true
107
123
  }) => {
108
124
  const absoluteSrcDir = path__namespace.resolve(projectPath, srcDir);
109
125
  const absoluteOutDir = path__namespace.resolve(projectPath, outDir);
@@ -120,6 +136,16 @@ const generateAppsScriptTypes = async ({
120
136
  project.addSourceFilesAtPaths([sourceFilesPattern, testFilesPattern]);
121
137
  const sourceFiles = project.getSourceFiles();
122
138
  consola.consola.info(`Found ${sourceFiles.length} source file(s).`);
139
+ if (cache) {
140
+ const currentHash = computeSourceHash(sourceFiles);
141
+ const cacheKey = absoluteOutputFile;
142
+ if (generationCache.get(cacheKey) === currentHash && fs__namespace.existsSync(absoluteOutputFile)) {
143
+ consola.consola.success(
144
+ `Skipping generation: Source files have not changed (Hash: ${currentHash.slice(0, 8)}).`
145
+ );
146
+ return;
147
+ }
148
+ }
123
149
  const methodDefinitions = [];
124
150
  const exportedDeclarations = [];
125
151
  const exportedDeclarationNames = /* @__PURE__ */ new Set();
@@ -163,8 +189,85 @@ const generateAppsScriptTypes = async ({
163
189
  }
164
190
  }
165
191
  }
166
- const collectSymbolsFromType = (type, foundSymbols) => {
192
+ const collectSymbolsFromType = (type, foundSymbols, contextNode, depth = 0, isComponent = false) => {
193
+ if (type.isArray()) {
194
+ const elementType = type.getArrayElementType();
195
+ if (elementType) {
196
+ collectSymbolsFromType(
197
+ elementType,
198
+ foundSymbols,
199
+ contextNode,
200
+ depth + 1
201
+ );
202
+ }
203
+ }
204
+ if (type.isTuple()) {
205
+ for (const element of type.getTupleElements()) {
206
+ collectSymbolsFromType(element, foundSymbols, contextNode, depth + 1);
207
+ }
208
+ }
209
+ for (const typeArg of type.getTypeArguments()) {
210
+ collectSymbolsFromType(typeArg, foundSymbols, contextNode, depth + 1);
211
+ }
212
+ for (const typeArg of type.getAliasTypeArguments()) {
213
+ collectSymbolsFromType(typeArg, foundSymbols, contextNode, depth + 1);
214
+ }
215
+ if (type.isUnion()) {
216
+ for (const unionType of type.getUnionTypes()) {
217
+ collectSymbolsFromType(
218
+ unionType,
219
+ foundSymbols,
220
+ contextNode,
221
+ depth + 1,
222
+ true
223
+ );
224
+ }
225
+ }
226
+ if (type.isIntersection()) {
227
+ for (const intersectionType of type.getIntersectionTypes()) {
228
+ collectSymbolsFromType(
229
+ intersectionType,
230
+ foundSymbols,
231
+ contextNode,
232
+ depth + 1,
233
+ true
234
+ );
235
+ }
236
+ }
237
+ const typeText = type.getText(contextNode);
238
+ const importMatches = typeText.matchAll(
239
+ /import\("(.*?)"\)\.([^<>[\]{}() ,;]+)/g
240
+ );
241
+ for (const match of importMatches) {
242
+ const absolutePath = match[1];
243
+ const typeName = match[2];
244
+ if (typeName === "default" || typeName === "__type") continue;
245
+ let sourceFile = project.getSourceFile(absolutePath) || project.addSourceFileAtPathIfExists(absolutePath);
246
+ if (!sourceFile) {
247
+ const extensions = [".ts", ".d.ts", ".tsx"];
248
+ for (const ext of extensions) {
249
+ sourceFile = project.getSourceFile(absolutePath + ext) || project.addSourceFileAtPathIfExists(absolutePath + ext);
250
+ if (sourceFile) break;
251
+ }
252
+ }
253
+ if (sourceFile) {
254
+ const exported = sourceFile.getExportedDeclarations().get(typeName);
255
+ let s;
256
+ if (exported && exported.length > 0) {
257
+ s = exported[0].getSymbol();
258
+ } else {
259
+ s = sourceFile.getInterface(typeName)?.getSymbol() || sourceFile.getTypeAlias(typeName)?.getSymbol() || sourceFile.getEnum(typeName)?.getSymbol() || sourceFile.getClass(typeName)?.getSymbol();
260
+ }
261
+ if (s && !foundSymbols.has(s)) {
262
+ foundSymbols.add(s);
263
+ }
264
+ }
265
+ }
167
266
  const aliasSymbol = type.getAliasSymbol();
267
+ let symbol = type.getSymbol();
268
+ if (!symbol && !aliasSymbol) {
269
+ symbol = type.getApparentType().getSymbol();
270
+ }
168
271
  if (aliasSymbol) {
169
272
  if (foundSymbols.has(aliasSymbol)) {
170
273
  return;
@@ -175,14 +278,10 @@ const generateAppsScriptTypes = async ({
175
278
  const isExternal = sourceFilePath.includes("node_modules") || !sourceFilePath.replace(/\\/g, "/").startsWith(absoluteSrcDir.replace(/\\/g, "/")) && !sourceFilePath.replace(/\\/g, "/").startsWith(path__namespace.join(projectPath, srcDir).replace(/\\/g, "/"));
176
279
  if (isExternal) {
177
280
  foundSymbols.add(aliasSymbol);
178
- for (const typeArg of type.getAliasTypeArguments()) {
179
- collectSymbolsFromType(typeArg, foundSymbols);
180
- }
181
281
  return;
182
282
  }
183
283
  }
184
284
  }
185
- const symbol = type.getSymbol();
186
285
  let shouldTraverseProperties = true;
187
286
  if (symbol) {
188
287
  if (foundSymbols.has(symbol)) {
@@ -196,42 +295,68 @@ const generateAppsScriptTypes = async ({
196
295
  path__namespace.join(projectPath, srcDir).replace(/\\/g, "/")
197
296
  );
198
297
  if (isExternal) {
199
- shouldTraverseProperties = false;
298
+ const isAnonymous = declarations.every(
299
+ (d) => d.getKind() === tsMorph.SyntaxKind.TypeLiteral || d.getKind() === tsMorph.SyntaxKind.ObjectLiteralExpression
300
+ );
301
+ if (!isComponent && !isAnonymous) {
302
+ shouldTraverseProperties = false;
303
+ }
200
304
  }
201
305
  }
202
306
  }
203
307
  }
204
- if (shouldTraverseProperties && type.isObject()) {
205
- for (const prop of type.getProperties()) {
206
- const propDecl = prop.getDeclarations()[0];
207
- if (propDecl) {
208
- const compilerNode = propDecl.compilerNode;
209
- if (
210
- // @ts-expect-error - avoiding full type checks for perf, checking kind directly
211
- compilerNode.name && // @ts-expect-error - avoiding full type checks for perf, checking kind directly
212
- compilerNode.name.kind === tsMorph.SyntaxKind.ComputedPropertyName
213
- ) {
214
- const expression = propDecl.getNameNode().getExpression();
215
- const symbol2 = expression.getSymbol();
216
- if (symbol2 && !foundSymbols.has(symbol2)) {
217
- foundSymbols.add(symbol2);
308
+ if (shouldTraverseProperties) {
309
+ const props = /* @__PURE__ */ new Set([
310
+ ...type.getProperties(),
311
+ ...type.getApparentProperties()
312
+ ]);
313
+ for (const prop of props) {
314
+ let propType;
315
+ if (contextNode) {
316
+ try {
317
+ propType = prop.getTypeAtLocation(contextNode);
318
+ } catch {
319
+ }
320
+ }
321
+ if (!propType || propType.getText() === "any") {
322
+ const propDecls = prop.getDeclarations();
323
+ if (propDecls.length > 0) {
324
+ propType = propDecls[0].getType();
325
+ }
326
+ }
327
+ if ((!propType || propType.getText() === "any") && prop.getAliasedSymbol()) {
328
+ const aliased = prop.getAliasedSymbol();
329
+ if (aliased) {
330
+ const decl = aliased.getValueDeclaration() || aliased.getDeclarations()[0];
331
+ if (decl) {
332
+ propType = decl.getType();
218
333
  }
219
334
  }
220
- collectSymbolsFromType(propDecl.getType(), foundSymbols);
221
335
  }
222
- }
223
- }
224
- for (const typeArg of type.getTypeArguments()) {
225
- collectSymbolsFromType(typeArg, foundSymbols);
226
- }
227
- if (type.isUnion()) {
228
- for (const unionType of type.getUnionTypes()) {
229
- collectSymbolsFromType(unionType, foundSymbols);
230
- }
231
- }
232
- if (type.isIntersection()) {
233
- for (const intersectionType of type.getIntersectionTypes()) {
234
- collectSymbolsFromType(intersectionType, foundSymbols);
336
+ if (propType) {
337
+ const propDecls = prop.getDeclarations();
338
+ if (propDecls.length > 0) {
339
+ const propDecl = propDecls[0];
340
+ const compilerNode = propDecl.compilerNode;
341
+ if (
342
+ // @ts-expect-error - checking kind directly for perf
343
+ compilerNode.name && // @ts-expect-error - checking kind directly for perf
344
+ compilerNode.name.kind === tsMorph.SyntaxKind.ComputedPropertyName
345
+ ) {
346
+ const expression = propDecl.getNameNode().getExpression();
347
+ const s = expression.getSymbol();
348
+ if (s && !foundSymbols.has(s)) {
349
+ foundSymbols.add(s);
350
+ }
351
+ }
352
+ }
353
+ collectSymbolsFromType(
354
+ propType,
355
+ foundSymbols,
356
+ contextNode,
357
+ depth + 1
358
+ );
359
+ }
235
360
  }
236
361
  }
237
362
  };
@@ -242,14 +367,14 @@ const generateAppsScriptTypes = async ({
242
367
  const func = decl.getKind() === tsMorph.SyntaxKind.FunctionDeclaration ? decl : decl.getInitializer();
243
368
  const parameters = func.getParameters();
244
369
  for (const param of parameters) {
245
- collectSymbolsFromType(param.getType(), functionSignatureSymbols);
246
- collectSymbolsFromType(param.getType(), symbolsToProcess);
370
+ collectSymbolsFromType(param.getType(), functionSignatureSymbols, func);
371
+ collectSymbolsFromType(param.getType(), symbolsToProcess, func);
247
372
  }
248
373
  const returnType = func.getReturnType();
249
- collectSymbolsFromType(returnType, functionSignatureSymbols);
250
- collectSymbolsFromType(returnType, symbolsToProcess);
374
+ collectSymbolsFromType(returnType, functionSignatureSymbols, func);
375
+ collectSymbolsFromType(returnType, symbolsToProcess, func);
251
376
  } else if (decl.getKind() === tsMorph.SyntaxKind.InterfaceDeclaration || decl.getKind() === tsMorph.SyntaxKind.TypeAliasDeclaration) {
252
- collectSymbolsFromType(decl.getType(), symbolsToProcess);
377
+ collectSymbolsFromType(decl.getType(), symbolsToProcess, decl);
253
378
  }
254
379
  }
255
380
  const importsMap = /* @__PURE__ */ new Map();
@@ -398,7 +523,9 @@ ${text}`;
398
523
  if (sortedModulePaths.length > 0) {
399
524
  const importStatements = sortedModulePaths.map((modulePath) => {
400
525
  const imports = [...importsMap.get(modulePath) ?? []].sort().filter((importName) => {
401
- const regex = new RegExp(`\\b${importName}\\b`);
526
+ const regex = new RegExp(
527
+ `(?:import\\(".*?"\\)\\.|\\b)${importName}\\b`
528
+ );
402
529
  return regex.test(bodyContent);
403
530
  });
404
531
  if (imports.length === 0) {
@@ -415,6 +542,10 @@ ${text}`;
415
542
  }
416
543
  outputContent += bodyContent;
417
544
  fs__namespace.writeFileSync(absoluteOutputFile, outputContent);
545
+ if (cache) {
546
+ const currentHash = computeSourceHash(sourceFiles);
547
+ generationCache.set(absoluteOutputFile, currentHash);
548
+ }
418
549
  };
419
550
 
420
551
  function defineConfig(config) {
@@ -1,3 +1,4 @@
1
+ import * as crypto from 'node:crypto';
1
2
  import * as fs from 'node:fs';
2
3
  import * as path from 'node:path';
3
4
  import { consola } from 'consola';
@@ -6,6 +7,18 @@ import { createJiti } from 'jiti';
6
7
 
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 _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";
8
9
 
10
+ const generationCache = /* @__PURE__ */ new Map();
11
+ const computeSourceHash = (sourceFiles) => {
12
+ const hash = crypto.createHash("md5");
13
+ const sortedFiles = [...sourceFiles].sort(
14
+ (a, b) => a.getFilePath().localeCompare(b.getFilePath())
15
+ );
16
+ for (const file of sortedFiles) {
17
+ hash.update(file.getFilePath());
18
+ hash.update(file.getFullText());
19
+ }
20
+ return hash.digest("hex");
21
+ };
9
22
  const getInterfaceMethodDefinition_ = (name, node) => {
10
23
  const typeParameters = node.getTypeParameters?.() ?? [];
11
24
  const typeParamsString = typeParameters.length > 0 ? `<${typeParameters.map((tp) => tp.getText()).join(", ")}>` : "";
@@ -41,7 +54,7 @@ const getInterfaceMethodDefinition_ = (name, node) => {
41
54
  `;
42
55
  }
43
56
  }
44
- const cleanType = (t) => t.replace(/import\(.*?\)\./g, "");
57
+ const cleanType = (t) => t.replace(/import\(".*?"\)\.default\./g, "").replace(/import\(".*?"\)\./g, "");
45
58
  const cleanedReturnType = cleanType(returnType);
46
59
  const cleanedParameters = parameters.replace(
47
60
  /:\s*([^,;)]+)/g,
@@ -86,7 +99,9 @@ const generateAppsScriptTypes = async ({
86
99
  srcDir,
87
100
  outDir,
88
101
  outputFile,
89
- projectInstance
102
+ projectInstance,
103
+ cache = true
104
+ // Default to true
90
105
  }) => {
91
106
  const absoluteSrcDir = path.resolve(projectPath, srcDir);
92
107
  const absoluteOutDir = path.resolve(projectPath, outDir);
@@ -103,6 +118,16 @@ const generateAppsScriptTypes = async ({
103
118
  project.addSourceFilesAtPaths([sourceFilesPattern, testFilesPattern]);
104
119
  const sourceFiles = project.getSourceFiles();
105
120
  consola.info(`Found ${sourceFiles.length} source file(s).`);
121
+ if (cache) {
122
+ const currentHash = computeSourceHash(sourceFiles);
123
+ const cacheKey = absoluteOutputFile;
124
+ if (generationCache.get(cacheKey) === currentHash && fs.existsSync(absoluteOutputFile)) {
125
+ consola.success(
126
+ `Skipping generation: Source files have not changed (Hash: ${currentHash.slice(0, 8)}).`
127
+ );
128
+ return;
129
+ }
130
+ }
106
131
  const methodDefinitions = [];
107
132
  const exportedDeclarations = [];
108
133
  const exportedDeclarationNames = /* @__PURE__ */ new Set();
@@ -146,8 +171,85 @@ const generateAppsScriptTypes = async ({
146
171
  }
147
172
  }
148
173
  }
149
- const collectSymbolsFromType = (type, foundSymbols) => {
174
+ const collectSymbolsFromType = (type, foundSymbols, contextNode, depth = 0, isComponent = false) => {
175
+ if (type.isArray()) {
176
+ const elementType = type.getArrayElementType();
177
+ if (elementType) {
178
+ collectSymbolsFromType(
179
+ elementType,
180
+ foundSymbols,
181
+ contextNode,
182
+ depth + 1
183
+ );
184
+ }
185
+ }
186
+ if (type.isTuple()) {
187
+ for (const element of type.getTupleElements()) {
188
+ collectSymbolsFromType(element, foundSymbols, contextNode, depth + 1);
189
+ }
190
+ }
191
+ for (const typeArg of type.getTypeArguments()) {
192
+ collectSymbolsFromType(typeArg, foundSymbols, contextNode, depth + 1);
193
+ }
194
+ for (const typeArg of type.getAliasTypeArguments()) {
195
+ collectSymbolsFromType(typeArg, foundSymbols, contextNode, depth + 1);
196
+ }
197
+ if (type.isUnion()) {
198
+ for (const unionType of type.getUnionTypes()) {
199
+ collectSymbolsFromType(
200
+ unionType,
201
+ foundSymbols,
202
+ contextNode,
203
+ depth + 1,
204
+ true
205
+ );
206
+ }
207
+ }
208
+ if (type.isIntersection()) {
209
+ for (const intersectionType of type.getIntersectionTypes()) {
210
+ collectSymbolsFromType(
211
+ intersectionType,
212
+ foundSymbols,
213
+ contextNode,
214
+ depth + 1,
215
+ true
216
+ );
217
+ }
218
+ }
219
+ const typeText = type.getText(contextNode);
220
+ const importMatches = typeText.matchAll(
221
+ /import\("(.*?)"\)\.([^<>[\]{}() ,;]+)/g
222
+ );
223
+ for (const match of importMatches) {
224
+ const absolutePath = match[1];
225
+ const typeName = match[2];
226
+ if (typeName === "default" || typeName === "__type") continue;
227
+ let sourceFile = project.getSourceFile(absolutePath) || project.addSourceFileAtPathIfExists(absolutePath);
228
+ if (!sourceFile) {
229
+ const extensions = [".ts", ".d.ts", ".tsx"];
230
+ for (const ext of extensions) {
231
+ sourceFile = project.getSourceFile(absolutePath + ext) || project.addSourceFileAtPathIfExists(absolutePath + ext);
232
+ if (sourceFile) break;
233
+ }
234
+ }
235
+ if (sourceFile) {
236
+ const exported = sourceFile.getExportedDeclarations().get(typeName);
237
+ let s;
238
+ if (exported && exported.length > 0) {
239
+ s = exported[0].getSymbol();
240
+ } else {
241
+ s = sourceFile.getInterface(typeName)?.getSymbol() || sourceFile.getTypeAlias(typeName)?.getSymbol() || sourceFile.getEnum(typeName)?.getSymbol() || sourceFile.getClass(typeName)?.getSymbol();
242
+ }
243
+ if (s && !foundSymbols.has(s)) {
244
+ foundSymbols.add(s);
245
+ }
246
+ }
247
+ }
150
248
  const aliasSymbol = type.getAliasSymbol();
249
+ let symbol = type.getSymbol();
250
+ if (!symbol && !aliasSymbol) {
251
+ symbol = type.getApparentType().getSymbol();
252
+ }
151
253
  if (aliasSymbol) {
152
254
  if (foundSymbols.has(aliasSymbol)) {
153
255
  return;
@@ -158,14 +260,10 @@ const generateAppsScriptTypes = async ({
158
260
  const isExternal = sourceFilePath.includes("node_modules") || !sourceFilePath.replace(/\\/g, "/").startsWith(absoluteSrcDir.replace(/\\/g, "/")) && !sourceFilePath.replace(/\\/g, "/").startsWith(path.join(projectPath, srcDir).replace(/\\/g, "/"));
159
261
  if (isExternal) {
160
262
  foundSymbols.add(aliasSymbol);
161
- for (const typeArg of type.getAliasTypeArguments()) {
162
- collectSymbolsFromType(typeArg, foundSymbols);
163
- }
164
263
  return;
165
264
  }
166
265
  }
167
266
  }
168
- const symbol = type.getSymbol();
169
267
  let shouldTraverseProperties = true;
170
268
  if (symbol) {
171
269
  if (foundSymbols.has(symbol)) {
@@ -179,42 +277,68 @@ const generateAppsScriptTypes = async ({
179
277
  path.join(projectPath, srcDir).replace(/\\/g, "/")
180
278
  );
181
279
  if (isExternal) {
182
- shouldTraverseProperties = false;
280
+ const isAnonymous = declarations.every(
281
+ (d) => d.getKind() === SyntaxKind.TypeLiteral || d.getKind() === SyntaxKind.ObjectLiteralExpression
282
+ );
283
+ if (!isComponent && !isAnonymous) {
284
+ shouldTraverseProperties = false;
285
+ }
183
286
  }
184
287
  }
185
288
  }
186
289
  }
187
- if (shouldTraverseProperties && type.isObject()) {
188
- for (const prop of type.getProperties()) {
189
- const propDecl = prop.getDeclarations()[0];
190
- if (propDecl) {
191
- const compilerNode = propDecl.compilerNode;
192
- if (
193
- // @ts-expect-error - avoiding full type checks for perf, checking kind directly
194
- compilerNode.name && // @ts-expect-error - avoiding full type checks for perf, checking kind directly
195
- compilerNode.name.kind === SyntaxKind.ComputedPropertyName
196
- ) {
197
- const expression = propDecl.getNameNode().getExpression();
198
- const symbol2 = expression.getSymbol();
199
- if (symbol2 && !foundSymbols.has(symbol2)) {
200
- foundSymbols.add(symbol2);
290
+ if (shouldTraverseProperties) {
291
+ const props = /* @__PURE__ */ new Set([
292
+ ...type.getProperties(),
293
+ ...type.getApparentProperties()
294
+ ]);
295
+ for (const prop of props) {
296
+ let propType;
297
+ if (contextNode) {
298
+ try {
299
+ propType = prop.getTypeAtLocation(contextNode);
300
+ } catch {
301
+ }
302
+ }
303
+ if (!propType || propType.getText() === "any") {
304
+ const propDecls = prop.getDeclarations();
305
+ if (propDecls.length > 0) {
306
+ propType = propDecls[0].getType();
307
+ }
308
+ }
309
+ if ((!propType || propType.getText() === "any") && prop.getAliasedSymbol()) {
310
+ const aliased = prop.getAliasedSymbol();
311
+ if (aliased) {
312
+ const decl = aliased.getValueDeclaration() || aliased.getDeclarations()[0];
313
+ if (decl) {
314
+ propType = decl.getType();
201
315
  }
202
316
  }
203
- collectSymbolsFromType(propDecl.getType(), foundSymbols);
204
317
  }
205
- }
206
- }
207
- for (const typeArg of type.getTypeArguments()) {
208
- collectSymbolsFromType(typeArg, foundSymbols);
209
- }
210
- if (type.isUnion()) {
211
- for (const unionType of type.getUnionTypes()) {
212
- collectSymbolsFromType(unionType, foundSymbols);
213
- }
214
- }
215
- if (type.isIntersection()) {
216
- for (const intersectionType of type.getIntersectionTypes()) {
217
- collectSymbolsFromType(intersectionType, foundSymbols);
318
+ if (propType) {
319
+ const propDecls = prop.getDeclarations();
320
+ if (propDecls.length > 0) {
321
+ const propDecl = propDecls[0];
322
+ const compilerNode = propDecl.compilerNode;
323
+ if (
324
+ // @ts-expect-error - checking kind directly for perf
325
+ compilerNode.name && // @ts-expect-error - checking kind directly for perf
326
+ compilerNode.name.kind === SyntaxKind.ComputedPropertyName
327
+ ) {
328
+ const expression = propDecl.getNameNode().getExpression();
329
+ const s = expression.getSymbol();
330
+ if (s && !foundSymbols.has(s)) {
331
+ foundSymbols.add(s);
332
+ }
333
+ }
334
+ }
335
+ collectSymbolsFromType(
336
+ propType,
337
+ foundSymbols,
338
+ contextNode,
339
+ depth + 1
340
+ );
341
+ }
218
342
  }
219
343
  }
220
344
  };
@@ -225,14 +349,14 @@ const generateAppsScriptTypes = async ({
225
349
  const func = decl.getKind() === SyntaxKind.FunctionDeclaration ? decl : decl.getInitializer();
226
350
  const parameters = func.getParameters();
227
351
  for (const param of parameters) {
228
- collectSymbolsFromType(param.getType(), functionSignatureSymbols);
229
- collectSymbolsFromType(param.getType(), symbolsToProcess);
352
+ collectSymbolsFromType(param.getType(), functionSignatureSymbols, func);
353
+ collectSymbolsFromType(param.getType(), symbolsToProcess, func);
230
354
  }
231
355
  const returnType = func.getReturnType();
232
- collectSymbolsFromType(returnType, functionSignatureSymbols);
233
- collectSymbolsFromType(returnType, symbolsToProcess);
356
+ collectSymbolsFromType(returnType, functionSignatureSymbols, func);
357
+ collectSymbolsFromType(returnType, symbolsToProcess, func);
234
358
  } else if (decl.getKind() === SyntaxKind.InterfaceDeclaration || decl.getKind() === SyntaxKind.TypeAliasDeclaration) {
235
- collectSymbolsFromType(decl.getType(), symbolsToProcess);
359
+ collectSymbolsFromType(decl.getType(), symbolsToProcess, decl);
236
360
  }
237
361
  }
238
362
  const importsMap = /* @__PURE__ */ new Map();
@@ -381,7 +505,9 @@ ${text}`;
381
505
  if (sortedModulePaths.length > 0) {
382
506
  const importStatements = sortedModulePaths.map((modulePath) => {
383
507
  const imports = [...importsMap.get(modulePath) ?? []].sort().filter((importName) => {
384
- const regex = new RegExp(`\\b${importName}\\b`);
508
+ const regex = new RegExp(
509
+ `(?:import\\(".*?"\\)\\.|\\b)${importName}\\b`
510
+ );
385
511
  return regex.test(bodyContent);
386
512
  });
387
513
  if (imports.length === 0) {
@@ -398,6 +524,10 @@ ${text}`;
398
524
  }
399
525
  outputContent += bodyContent;
400
526
  fs.writeFileSync(absoluteOutputFile, outputContent);
527
+ if (cache) {
528
+ const currentHash = computeSourceHash(sourceFiles);
529
+ generationCache.set(absoluteOutputFile, currentHash);
530
+ }
401
531
  };
402
532
 
403
533
  function defineConfig(config) {
package/dist/vite.cjs CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  const path = require('node:path');
4
4
  const consola = require('consola');
5
- const config = require('./shared/gasnuki.BVdkJsiP.cjs');
5
+ const config = require('./shared/gasnuki.Co0-wxJi.cjs');
6
+ require('node:crypto');
6
7
  require('node:fs');
7
8
  require('ts-morph');
8
9
  require('jiti');
package/dist/vite.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as path from 'node:path';
2
2
  import { consola } from 'consola';
3
- import { l as loadConfig, g as generateAppsScriptTypes } from './shared/gasnuki.Cd8JOTQ5.mjs';
3
+ import { l as loadConfig, g as generateAppsScriptTypes } from './shared/gasnuki.DePJgSg9.mjs';
4
+ import 'node:crypto';
4
5
  import 'node:fs';
5
6
  import 'ts-morph';
6
7
  import 'jiti';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ciderjs/gasnuki",
3
- "version": "0.3.8",
3
+ "version": "0.4.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",