algolia-codegen 0.1.1 → 0.1.3

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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 nightlightmare
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md CHANGED
@@ -1,12 +1,20 @@
1
1
  # Algolia Type Generator
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/algolia-codegen.svg?style=flat-square)](https://www.npmjs.com/package/algolia-codegen)
4
+ [![npm downloads](https://img.shields.io/npm/dm/algolia-codegen.svg?style=flat-square)](https://www.npmjs.com/package/algolia-codegen)
5
+ [![npm bundle size](https://img.shields.io/bundlephobia/minzip/algolia-codegen?style=flat-square)](https://bundlephobia.com/package/algolia-codegen)
6
+ [![GitHub stars](https://img.shields.io/github/stars/nightlightmare/algolia-codegen.svg?style=flat-square&logo=github)](https://github.com/nightlightmare/algolia-codegen)
7
+ [![GitHub issues](https://img.shields.io/github/issues/nightlightmare/algolia-codegen.svg?style=flat-square&logo=github)](https://github.com/nightlightmare/algolia-codegen/issues)
8
+ [![GitHub license](https://img.shields.io/github/license/nightlightmare/algolia-codegen.svg?style=flat-square)](https://github.com/nightlightmare/algolia-codegen/blob/main/LICENSE)
9
+ [![Node.js version](https://img.shields.io/node/v/algolia-codegen.svg?style=flat-square)](https://nodejs.org/)
10
+
3
11
  This script automatically generates TypeScript types from your Algolia indices by fetching sample records and analyzing their structure. It supports multiple indices and flexible configuration through a config file.
4
12
 
5
13
  **Repository**: [https://github.com/nightlightmare/algolia-codegen](https://github.com/nightlightmare/algolia-codegen)
6
14
 
7
15
  ## Prerequisites
8
16
 
9
- - Node.js >= 18
17
+ - Node.js >= 20
10
18
  - Algolia account with at least one index
11
19
 
12
20
  ## Installation
@@ -30,17 +38,17 @@ yarn add algolia-codegen
30
38
  1. Create a configuration file named `algolia-codegen.ts` (or `.js`) in your project root:
31
39
 
32
40
  ```typescript
33
- import type { AlgoliaCodegenConfig } from "algolia-codegen";
41
+ import type { AlgoliaCodegenConfig } from 'algolia-codegen';
34
42
 
35
43
  const config: AlgoliaCodegenConfig = {
36
44
  overwrite: true,
37
45
  generates: {
38
- "src/algolia/types.ts": {
39
- appId: "YOUR_APP_ID",
40
- searchKey: "YOUR_SEARCH_API_KEY",
41
- indexName: "products",
42
- prefix: "Algolia", // Optional
43
- postfix: "Type", // Optional
46
+ 'src/algolia/types.ts': {
47
+ appId: 'YOUR_APP_ID',
48
+ searchKey: 'YOUR_SEARCH_API_KEY',
49
+ indexName: 'products',
50
+ prefix: 'Algolia', // Optional
51
+ postfix: 'Type', // Optional
44
52
  },
45
53
  },
46
54
  };
@@ -65,17 +73,17 @@ npx algolia-codegen
65
73
  Create a configuration file named `algolia-codegen.ts` (or `.js`) in your project root. The config file should export a default object with the following structure:
66
74
 
67
75
  ```typescript
68
- import type { AlgoliaCodegenConfig } from "algolia-codegen";
76
+ import type { AlgoliaCodegenConfig } from 'algolia-codegen';
69
77
 
70
78
  const config: AlgoliaCodegenConfig = {
71
79
  overwrite: true,
72
80
  generates: {
73
- "src/algolia/types.ts": {
74
- appId: "YOUR_APP_ID",
75
- searchKey: "YOUR_SEARCH_API_KEY",
76
- indexName: "products",
77
- prefix: "Algolia", // Optional
78
- postfix: "Type", // Optional
81
+ 'src/algolia/types.ts': {
82
+ appId: 'YOUR_APP_ID',
83
+ searchKey: 'YOUR_SEARCH_API_KEY',
84
+ indexName: 'products',
85
+ prefix: 'Algolia', // Optional
86
+ postfix: 'Type', // Optional
79
87
  },
80
88
  },
81
89
  };
@@ -91,15 +99,15 @@ You can generate types for multiple indices:
91
99
  const config: AlgoliaCodegenConfig = {
92
100
  overwrite: true,
93
101
  generates: {
94
- "src/algolia/products.ts": {
95
- appId: "YOUR_APP_ID",
96
- searchKey: "YOUR_SEARCH_API_KEY",
97
- indexName: "products",
102
+ 'src/algolia/products.ts': {
103
+ appId: 'YOUR_APP_ID',
104
+ searchKey: 'YOUR_SEARCH_API_KEY',
105
+ indexName: 'products',
98
106
  },
99
- "src/algolia/users.ts": {
100
- appId: "YOUR_APP_ID",
101
- searchKey: "YOUR_SEARCH_API_KEY",
102
- indexName: "users",
107
+ 'src/algolia/users.ts': {
108
+ appId: 'YOUR_APP_ID',
109
+ searchKey: 'YOUR_SEARCH_API_KEY',
110
+ indexName: 'users',
103
111
  },
104
112
  },
105
113
  };
@@ -114,17 +122,17 @@ const config: AlgoliaCodegenConfig = {
114
122
  overwrite: true,
115
123
  generates: [
116
124
  {
117
- "src/algolia/products.ts": {
118
- appId: "YOUR_APP_ID",
119
- searchKey: "YOUR_SEARCH_API_KEY",
120
- indexName: "products",
125
+ 'src/algolia/products.ts': {
126
+ appId: 'YOUR_APP_ID',
127
+ searchKey: 'YOUR_SEARCH_API_KEY',
128
+ indexName: 'products',
121
129
  },
122
130
  },
123
131
  {
124
- "src/algolia/users.ts": {
125
- appId: "YOUR_APP_ID",
126
- searchKey: "YOUR_SEARCH_API_KEY",
127
- indexName: "users",
132
+ 'src/algolia/users.ts': {
133
+ appId: 'YOUR_APP_ID',
134
+ searchKey: 'YOUR_SEARCH_API_KEY',
135
+ indexName: 'users',
128
136
  },
129
137
  },
130
138
  ],
@@ -180,19 +188,20 @@ type AlgoliaCodegenGeneratorConfig = {
180
188
 
181
189
  ## How It Works
182
190
 
183
- 1. **Loads Configuration**: Reads the `algolia-codegen.ts` config file (or custom path)
184
- 2. **Processes Each Path**: For each file path specified in the config:
191
+ 1. **Loads Environment Variables**: Automatically loads `.env` file if present in the current directory
192
+ 2. **Loads Configuration**: Reads the `algolia-codegen.ts` config file (or custom path), supporting both TypeScript and JavaScript
193
+ 3. **Processes Each Path**: For each file path specified in the config:
185
194
  - Connects to Algolia using the provided credentials
186
195
  - Fetches a sample record from the specified index
187
196
  - Analyzes the record structure and generates TypeScript types
188
197
  - Creates a single TypeScript file containing all types found in the index
189
- 3. **Type Generation**: The generator automatically:
198
+ 4. **Type Generation**: The generator automatically:
190
199
  - Infers types from the sample record structure
191
200
  - Handles nested objects, arrays, and complex types
192
201
  - Detects and generates generic `IdValue<T>` types for Algolia's id-value pattern arrays
193
202
  - Generates proper TypeScript interfaces with JSDoc comments
194
203
  - Sorts types by dependencies for correct ordering
195
- 4. **Error Handling**: Continues processing other files even if one fails, with detailed error messages
204
+ 5. **Error Handling**: Continues processing other files even if one fails, with detailed error messages
196
205
 
197
206
  Each generated file contains all types found in the index, including nested types, properly organized and sorted by dependencies.
198
207
 
@@ -201,7 +210,8 @@ Each generated file contains all types found in the index, including nested type
201
210
  - Each index must have at least one record for the script to work
202
211
  - The script processes files sequentially and continues even if one fails
203
212
  - Make sure your config file exports a default object
204
- - For TypeScript config files, you may need to use `tsx` or compile them first: `tsx algolia-codegen`
213
+ - TypeScript config files (`.ts`) are automatically supported - no compilation needed
214
+ - Environment variables from `.env` file are automatically loaded if present in the current directory
205
215
  - Each generated file contains all types found in the index (including nested types) in a single file
206
216
  - Types are automatically sorted by dependencies to ensure correct ordering
207
217
  - The generator handles arrays, nested objects, optional fields, and null values
@@ -211,44 +221,11 @@ Each generated file contains all types found in the index, including nested type
211
221
 
212
222
  ## Examples
213
223
 
214
- ### Generated Type Example
215
-
216
- Given an Algolia record like:
217
-
218
- ```json
219
- {
220
- "objectID": "123",
221
- "name": "Product Name",
222
- "price": 99.99,
223
- "tags": ["tag1", "tag2"],
224
- "metadata": {
225
- "category": "electronics",
226
- "rating": 4.5
227
- }
228
- }
229
- ```
230
-
231
- The generator will create TypeScript types:
232
-
233
- ```typescript
234
- /**
235
- * Generated TypeScript types for Algolia index: products
236
- * This file is auto-generated. Do not edit manually.
237
- */
238
-
239
- export interface AlgoliaHitType {
240
- metadata: AlgoliaMetadataType;
241
- name: string;
242
- objectID: string;
243
- price: number;
244
- tags: string[];
245
- }
246
-
247
- export interface AlgoliaMetadataType {
248
- category: string;
249
- rating: number;
250
- }
251
- ```
224
+ See the [examples directory](./examples/) for comprehensive examples including:
225
+ - Framework integrations (Next.js, React, Vue)
226
+ - CI/CD integration (GitHub Actions)
227
+ - Custom prefixes/postfixes
228
+ - Multiple indices configuration
252
229
 
253
230
  ## Repository
254
231
 
@@ -1,15 +1,32 @@
1
- // src/utils/config-loader.ts
1
+ // src/utils/load-config.ts
2
2
  import { existsSync } from "fs";
3
3
  import { resolve } from "path";
4
4
  import { pathToFileURL } from "url";
5
5
 
6
+ // src/utils/load-typescript-config.ts
7
+ import { readFileSync } from "fs";
8
+ async function loadTypeScriptConfig(resolvedPath) {
9
+ const esbuild = await import("esbuild");
10
+ const source = readFileSync(resolvedPath, "utf-8");
11
+ const result = esbuild.transformSync(source, {
12
+ loader: "ts",
13
+ format: "esm",
14
+ target: "node20",
15
+ sourcefile: resolvedPath
16
+ });
17
+ if (!result.code) {
18
+ throw new Error("Failed to transform TypeScript config file");
19
+ }
20
+ const dataUrl = `data:text/javascript;charset=utf-8,${encodeURIComponent(result.code)}`;
21
+ const module = await import(dataUrl);
22
+ return module;
23
+ }
24
+
6
25
  // src/utils/validations/generator-config.ts
7
26
  function validateGeneratorConfig(config, path) {
8
27
  if (typeof config !== "object" || config === null || Array.isArray(config)) {
9
- throw new Error(
10
- `Invalid generator config: must be an object
11
- Path: ${path}`
12
- );
28
+ throw new Error(`Invalid generator config: must be an object
29
+ Path: ${path}`);
13
30
  }
14
31
  const cfg = config;
15
32
  const requiredFields = [
@@ -51,10 +68,8 @@ Received: ${typeof cfg.postfix}`
51
68
  // src/utils/validations/url-schema.ts
52
69
  function validateUrlSchema(urlSchema, path) {
53
70
  if (typeof urlSchema !== "object" || urlSchema === null || Array.isArray(urlSchema)) {
54
- throw new Error(
55
- `Invalid generates entry: must be an object
56
- Path: ${path}`
57
- );
71
+ throw new Error(`Invalid generates entry: must be an object
72
+ Path: ${path}`);
58
73
  }
59
74
  const schema = urlSchema;
60
75
  for (const [filePath, generatorConfig] of Object.entries(schema)) {
@@ -71,10 +86,8 @@ Path: ${path}[${filePath}]`
71
86
  // src/utils/validations/config.ts
72
87
  function validateConfig(config, configPath) {
73
88
  if (typeof config !== "object" || config === null || Array.isArray(config)) {
74
- throw new Error(
75
- `Invalid config: must be an object
76
- Config file: ${configPath}`
77
- );
89
+ throw new Error(`Invalid config: must be an object
90
+ Config file: ${configPath}`);
78
91
  }
79
92
  const cfg = config;
80
93
  if (!("overwrite" in cfg)) {
@@ -112,7 +125,7 @@ Received: ${typeof generates}`
112
125
  }
113
126
  }
114
127
 
115
- // src/utils/config-loader.ts
128
+ // src/utils/load-config.ts
116
129
  async function loadConfig(configPath) {
117
130
  const defaultConfigPath = "algolia-codegen.ts";
118
131
  const finalConfigPath = configPath || defaultConfigPath;
@@ -125,34 +138,38 @@ Please create a config file or specify a different path using --config option.`
125
138
  }
126
139
  const configUrl = pathToFileURL(resolvedPath).href;
127
140
  let configModule;
128
- try {
129
- configModule = await import(configUrl);
130
- } catch (importError) {
131
- const jsPath = resolvedPath.replace(/\.ts$/, ".js");
132
- if (existsSync(jsPath)) {
133
- const jsUrl = pathToFileURL(jsPath).href;
134
- try {
135
- configModule = await import(jsUrl);
136
- } catch (jsImportError) {
141
+ if (resolvedPath.endsWith(".ts")) {
142
+ try {
143
+ configModule = await loadTypeScriptConfig(resolvedPath);
144
+ } catch (tsError) {
145
+ const jsPath = resolvedPath.replace(/\.ts$/, ".js");
146
+ if (existsSync(jsPath)) {
147
+ const jsUrl = pathToFileURL(jsPath).href;
148
+ try {
149
+ configModule = await import(jsUrl);
150
+ } catch (jsImportError) {
151
+ throw new Error(
152
+ `Failed to import config file: ${resolvedPath}
153
+ Tried both .ts (via esbuild) and .js extensions.
154
+ TypeScript error: ${tsError instanceof Error ? tsError.message : String(tsError)}
155
+ JavaScript error: ${jsImportError instanceof Error ? jsImportError.message : String(jsImportError)}`
156
+ );
157
+ }
158
+ } else {
137
159
  throw new Error(
138
- `Failed to import config file: ${resolvedPath}
139
- Tried both .ts and .js extensions.
140
- Error: ${jsImportError instanceof Error ? jsImportError.message : String(jsImportError)}
141
- Note: If using TypeScript config, you may need to compile it first or use a tool like tsx.`
160
+ `Failed to load TypeScript config file: ${resolvedPath}
161
+ Error: ${tsError instanceof Error ? tsError.message : String(tsError)}
162
+ Make sure esbuild is installed as a dependency.`
142
163
  );
143
164
  }
144
- } else {
165
+ }
166
+ } else {
167
+ try {
168
+ configModule = await import(configUrl);
169
+ } catch (importError) {
145
170
  const errorMessage = importError instanceof Error ? importError.message : String(importError);
146
- const isTypeScriptError = resolvedPath.endsWith(".ts") && (errorMessage.includes("Cannot find module") || errorMessage.includes("Unknown file extension"));
147
- throw new Error(
148
- `Failed to import config file: ${resolvedPath}
149
- ` + (isTypeScriptError ? `Node.js cannot directly import TypeScript files.
150
- Please either:
151
- 1. Compile your config to JavaScript (.js)
152
- 2. Use a tool like tsx to run the CLI: tsx algolia-codegen
153
- 3. Or use a JavaScript config file instead
154
- ` : `Error: ${errorMessage}`)
155
- );
171
+ throw new Error(`Failed to import config file: ${resolvedPath}
172
+ Error: ${errorMessage}`);
156
173
  }
157
174
  }
158
175
  if (!configModule.default) {
@@ -173,7 +190,7 @@ Received: ${typeof config}`
173
190
  }
174
191
 
175
192
  // src/utils/fetch-algolia-data.ts
176
- import algoliasearch from "algoliasearch";
193
+ import { algoliasearch } from "algoliasearch";
177
194
  import { existsSync as existsSync2, writeFileSync, mkdirSync } from "fs";
178
195
  import { resolve as resolve2, dirname } from "path";
179
196
 
@@ -281,7 +298,7 @@ var TypeGenerator = class {
281
298
  return `${this.prefix}Hit${this.postfix}`;
282
299
  }
283
300
  const lastPart = path[path.length - 1];
284
- const parts = lastPart.replace(/[\[\]]/g, "").split(/[-_\s]+/).filter(Boolean);
301
+ const parts = lastPart.replace(/[[\]]/g, "").split(/[-_\s]+/).filter(Boolean);
285
302
  const pascalCase = parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
286
303
  if (path.length > 1) {
287
304
  const parent = path[path.length - 2];
@@ -447,10 +464,7 @@ Set overwrite: true in config to allow overwriting existing files.`
447
464
  console.log(`App ID: ${generatorConfig.appId}`);
448
465
  let client;
449
466
  try {
450
- client = algoliasearch(
451
- generatorConfig.appId,
452
- generatorConfig.searchKey
453
- );
467
+ client = algoliasearch(generatorConfig.appId, generatorConfig.searchKey);
454
468
  } catch (error) {
455
469
  throw new Error(
456
470
  `Failed to initialize Algolia client: ${error instanceof Error ? error.message : String(error)}`
@@ -462,8 +476,8 @@ Set overwrite: true in config to allow overwriting existing files.`
462
476
  results = await client.search([
463
477
  {
464
478
  indexName: generatorConfig.indexName,
465
- query: "",
466
479
  params: {
480
+ query: "",
467
481
  hitsPerPage: 1
468
482
  }
469
483
  }
package/dist/cli.cjs CHANGED
@@ -24,20 +24,40 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/cli.ts
27
+ var import_dotenv = require("dotenv");
28
+ var import_fs4 = require("fs");
29
+ var import_path3 = require("path");
27
30
  var import_commander = require("commander");
28
31
 
29
- // src/utils/config-loader.ts
30
- var import_fs = require("fs");
32
+ // src/utils/load-config.ts
33
+ var import_fs2 = require("fs");
31
34
  var import_path = require("path");
32
35
  var import_url = require("url");
33
36
 
37
+ // src/utils/load-typescript-config.ts
38
+ var import_fs = require("fs");
39
+ async function loadTypeScriptConfig(resolvedPath) {
40
+ const esbuild = await import("esbuild");
41
+ const source = (0, import_fs.readFileSync)(resolvedPath, "utf-8");
42
+ const result = esbuild.transformSync(source, {
43
+ loader: "ts",
44
+ format: "esm",
45
+ target: "node20",
46
+ sourcefile: resolvedPath
47
+ });
48
+ if (!result.code) {
49
+ throw new Error("Failed to transform TypeScript config file");
50
+ }
51
+ const dataUrl = `data:text/javascript;charset=utf-8,${encodeURIComponent(result.code)}`;
52
+ const module2 = await import(dataUrl);
53
+ return module2;
54
+ }
55
+
34
56
  // src/utils/validations/generator-config.ts
35
57
  function validateGeneratorConfig(config, path) {
36
58
  if (typeof config !== "object" || config === null || Array.isArray(config)) {
37
- throw new Error(
38
- `Invalid generator config: must be an object
39
- Path: ${path}`
40
- );
59
+ throw new Error(`Invalid generator config: must be an object
60
+ Path: ${path}`);
41
61
  }
42
62
  const cfg = config;
43
63
  const requiredFields = [
@@ -79,10 +99,8 @@ Received: ${typeof cfg.postfix}`
79
99
  // src/utils/validations/url-schema.ts
80
100
  function validateUrlSchema(urlSchema, path) {
81
101
  if (typeof urlSchema !== "object" || urlSchema === null || Array.isArray(urlSchema)) {
82
- throw new Error(
83
- `Invalid generates entry: must be an object
84
- Path: ${path}`
85
- );
102
+ throw new Error(`Invalid generates entry: must be an object
103
+ Path: ${path}`);
86
104
  }
87
105
  const schema = urlSchema;
88
106
  for (const [filePath, generatorConfig] of Object.entries(schema)) {
@@ -99,10 +117,8 @@ Path: ${path}[${filePath}]`
99
117
  // src/utils/validations/config.ts
100
118
  function validateConfig(config, configPath) {
101
119
  if (typeof config !== "object" || config === null || Array.isArray(config)) {
102
- throw new Error(
103
- `Invalid config: must be an object
104
- Config file: ${configPath}`
105
- );
120
+ throw new Error(`Invalid config: must be an object
121
+ Config file: ${configPath}`);
106
122
  }
107
123
  const cfg = config;
108
124
  if (!("overwrite" in cfg)) {
@@ -140,12 +156,12 @@ Received: ${typeof generates}`
140
156
  }
141
157
  }
142
158
 
143
- // src/utils/config-loader.ts
159
+ // src/utils/load-config.ts
144
160
  async function loadConfig(configPath) {
145
161
  const defaultConfigPath = "algolia-codegen.ts";
146
162
  const finalConfigPath = configPath || defaultConfigPath;
147
163
  const resolvedPath = (0, import_path.resolve)(process.cwd(), finalConfigPath);
148
- if (!(0, import_fs.existsSync)(resolvedPath)) {
164
+ if (!(0, import_fs2.existsSync)(resolvedPath)) {
149
165
  throw new Error(
150
166
  `Config file not found: ${resolvedPath}
151
167
  Please create a config file or specify a different path using --config option.`
@@ -153,34 +169,38 @@ Please create a config file or specify a different path using --config option.`
153
169
  }
154
170
  const configUrl = (0, import_url.pathToFileURL)(resolvedPath).href;
155
171
  let configModule;
156
- try {
157
- configModule = await import(configUrl);
158
- } catch (importError) {
159
- const jsPath = resolvedPath.replace(/\.ts$/, ".js");
160
- if ((0, import_fs.existsSync)(jsPath)) {
161
- const jsUrl = (0, import_url.pathToFileURL)(jsPath).href;
162
- try {
163
- configModule = await import(jsUrl);
164
- } catch (jsImportError) {
172
+ if (resolvedPath.endsWith(".ts")) {
173
+ try {
174
+ configModule = await loadTypeScriptConfig(resolvedPath);
175
+ } catch (tsError) {
176
+ const jsPath = resolvedPath.replace(/\.ts$/, ".js");
177
+ if ((0, import_fs2.existsSync)(jsPath)) {
178
+ const jsUrl = (0, import_url.pathToFileURL)(jsPath).href;
179
+ try {
180
+ configModule = await import(jsUrl);
181
+ } catch (jsImportError) {
182
+ throw new Error(
183
+ `Failed to import config file: ${resolvedPath}
184
+ Tried both .ts (via esbuild) and .js extensions.
185
+ TypeScript error: ${tsError instanceof Error ? tsError.message : String(tsError)}
186
+ JavaScript error: ${jsImportError instanceof Error ? jsImportError.message : String(jsImportError)}`
187
+ );
188
+ }
189
+ } else {
165
190
  throw new Error(
166
- `Failed to import config file: ${resolvedPath}
167
- Tried both .ts and .js extensions.
168
- Error: ${jsImportError instanceof Error ? jsImportError.message : String(jsImportError)}
169
- Note: If using TypeScript config, you may need to compile it first or use a tool like tsx.`
191
+ `Failed to load TypeScript config file: ${resolvedPath}
192
+ Error: ${tsError instanceof Error ? tsError.message : String(tsError)}
193
+ Make sure esbuild is installed as a dependency.`
170
194
  );
171
195
  }
172
- } else {
196
+ }
197
+ } else {
198
+ try {
199
+ configModule = await import(configUrl);
200
+ } catch (importError) {
173
201
  const errorMessage = importError instanceof Error ? importError.message : String(importError);
174
- const isTypeScriptError = resolvedPath.endsWith(".ts") && (errorMessage.includes("Cannot find module") || errorMessage.includes("Unknown file extension"));
175
- throw new Error(
176
- `Failed to import config file: ${resolvedPath}
177
- ` + (isTypeScriptError ? `Node.js cannot directly import TypeScript files.
178
- Please either:
179
- 1. Compile your config to JavaScript (.js)
180
- 2. Use a tool like tsx to run the CLI: tsx algolia-codegen
181
- 3. Or use a JavaScript config file instead
182
- ` : `Error: ${errorMessage}`)
183
- );
202
+ throw new Error(`Failed to import config file: ${resolvedPath}
203
+ Error: ${errorMessage}`);
184
204
  }
185
205
  }
186
206
  if (!configModule.default) {
@@ -201,8 +221,8 @@ Received: ${typeof config}`
201
221
  }
202
222
 
203
223
  // src/utils/fetch-algolia-data.ts
204
- var import_algoliasearch = __toESM(require("algoliasearch"), 1);
205
- var import_fs2 = require("fs");
224
+ var import_algoliasearch = require("algoliasearch");
225
+ var import_fs3 = require("fs");
206
226
  var import_path2 = require("path");
207
227
 
208
228
  // src/utils/generate-typescript-types.ts
@@ -309,7 +329,7 @@ var TypeGenerator = class {
309
329
  return `${this.prefix}Hit${this.postfix}`;
310
330
  }
311
331
  const lastPart = path[path.length - 1];
312
- const parts = lastPart.replace(/[\[\]]/g, "").split(/[-_\s]+/).filter(Boolean);
332
+ const parts = lastPart.replace(/[[\]]/g, "").split(/[-_\s]+/).filter(Boolean);
313
333
  const pascalCase = parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
314
334
  if (path.length > 1) {
315
335
  const parent = path[path.length - 2];
@@ -465,7 +485,7 @@ async function fetchAlgoliaData(filePath, generatorConfig, overwrite) {
465
485
  console.log(`
466
486
  Processing file: ${filePath}`);
467
487
  const resolvedPath = (0, import_path2.resolve)(process.cwd(), filePath);
468
- if ((0, import_fs2.existsSync)(resolvedPath) && !overwrite) {
488
+ if ((0, import_fs3.existsSync)(resolvedPath) && !overwrite) {
469
489
  throw new Error(
470
490
  `File already exists: ${resolvedPath}
471
491
  Set overwrite: true in config to allow overwriting existing files.`
@@ -475,10 +495,7 @@ Set overwrite: true in config to allow overwriting existing files.`
475
495
  console.log(`App ID: ${generatorConfig.appId}`);
476
496
  let client;
477
497
  try {
478
- client = (0, import_algoliasearch.default)(
479
- generatorConfig.appId,
480
- generatorConfig.searchKey
481
- );
498
+ client = (0, import_algoliasearch.algoliasearch)(generatorConfig.appId, generatorConfig.searchKey);
482
499
  } catch (error) {
483
500
  throw new Error(
484
501
  `Failed to initialize Algolia client: ${error instanceof Error ? error.message : String(error)}`
@@ -490,8 +507,8 @@ Set overwrite: true in config to allow overwriting existing files.`
490
507
  results = await client.search([
491
508
  {
492
509
  indexName: generatorConfig.indexName,
493
- query: "",
494
510
  params: {
511
+ query: "",
495
512
  hitsPerPage: 1
496
513
  }
497
514
  }
@@ -532,10 +549,10 @@ Set overwrite: true in config to allow overwriting existing files.`
532
549
  console.log(`ObjectID: ${sampleHit.objectID || "N/A"}`);
533
550
  const fileContent = generateTypeScriptTypes(sampleHit, generatorConfig);
534
551
  const dir = (0, import_path2.dirname)(resolvedPath);
535
- if (!(0, import_fs2.existsSync)(dir)) {
536
- (0, import_fs2.mkdirSync)(dir, { recursive: true });
552
+ if (!(0, import_fs3.existsSync)(dir)) {
553
+ (0, import_fs3.mkdirSync)(dir, { recursive: true });
537
554
  }
538
- (0, import_fs2.writeFileSync)(resolvedPath, fileContent, "utf-8");
555
+ (0, import_fs3.writeFileSync)(resolvedPath, fileContent, "utf-8");
539
556
  console.log(`Generated file: ${filePath}`);
540
557
  }
541
558
 
@@ -579,6 +596,10 @@ Error processing file: ${filePath}`);
579
596
  };
580
597
 
581
598
  // src/cli.ts
599
+ var envPath = (0, import_path3.resolve)(process.cwd(), ".env");
600
+ if ((0, import_fs4.existsSync)(envPath)) {
601
+ (0, import_dotenv.config)({ path: envPath });
602
+ }
582
603
  var program = new import_commander.Command();
583
604
  program.name("algolia-codegen").description("Generate TypeScript types from Algolia index").option("-c, --config <path>", "Config file path").action(async (options) => {
584
605
  await main(options.config);
package/dist/cli.js CHANGED
@@ -1,10 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  main
4
- } from "./chunk-4FWQJC54.js";
4
+ } from "./chunk-4T5DZK75.js";
5
5
 
6
6
  // src/cli.ts
7
+ import { config as loadDotenv } from "dotenv";
8
+ import { existsSync } from "fs";
9
+ import { resolve } from "path";
7
10
  import { Command } from "commander";
11
+ var envPath = resolve(process.cwd(), ".env");
12
+ if (existsSync(envPath)) {
13
+ loadDotenv({ path: envPath });
14
+ }
8
15
  var program = new Command();
9
16
  program.name("algolia-codegen").description("Generate TypeScript types from Algolia index").option("-c, --config <path>", "Config file path").action(async (options) => {
10
17
  await main(options.config);
package/dist/index.cjs CHANGED
@@ -37,18 +37,35 @@ __export(src_exports, {
37
37
  });
38
38
  module.exports = __toCommonJS(src_exports);
39
39
 
40
- // src/utils/config-loader.ts
41
- var import_fs = require("fs");
40
+ // src/utils/load-config.ts
41
+ var import_fs2 = require("fs");
42
42
  var import_path = require("path");
43
43
  var import_url = require("url");
44
44
 
45
+ // src/utils/load-typescript-config.ts
46
+ var import_fs = require("fs");
47
+ async function loadTypeScriptConfig(resolvedPath) {
48
+ const esbuild = await import("esbuild");
49
+ const source = (0, import_fs.readFileSync)(resolvedPath, "utf-8");
50
+ const result = esbuild.transformSync(source, {
51
+ loader: "ts",
52
+ format: "esm",
53
+ target: "node20",
54
+ sourcefile: resolvedPath
55
+ });
56
+ if (!result.code) {
57
+ throw new Error("Failed to transform TypeScript config file");
58
+ }
59
+ const dataUrl = `data:text/javascript;charset=utf-8,${encodeURIComponent(result.code)}`;
60
+ const module2 = await import(dataUrl);
61
+ return module2;
62
+ }
63
+
45
64
  // src/utils/validations/generator-config.ts
46
65
  function validateGeneratorConfig(config, path) {
47
66
  if (typeof config !== "object" || config === null || Array.isArray(config)) {
48
- throw new Error(
49
- `Invalid generator config: must be an object
50
- Path: ${path}`
51
- );
67
+ throw new Error(`Invalid generator config: must be an object
68
+ Path: ${path}`);
52
69
  }
53
70
  const cfg = config;
54
71
  const requiredFields = [
@@ -90,10 +107,8 @@ Received: ${typeof cfg.postfix}`
90
107
  // src/utils/validations/url-schema.ts
91
108
  function validateUrlSchema(urlSchema, path) {
92
109
  if (typeof urlSchema !== "object" || urlSchema === null || Array.isArray(urlSchema)) {
93
- throw new Error(
94
- `Invalid generates entry: must be an object
95
- Path: ${path}`
96
- );
110
+ throw new Error(`Invalid generates entry: must be an object
111
+ Path: ${path}`);
97
112
  }
98
113
  const schema = urlSchema;
99
114
  for (const [filePath, generatorConfig] of Object.entries(schema)) {
@@ -110,10 +125,8 @@ Path: ${path}[${filePath}]`
110
125
  // src/utils/validations/config.ts
111
126
  function validateConfig(config, configPath) {
112
127
  if (typeof config !== "object" || config === null || Array.isArray(config)) {
113
- throw new Error(
114
- `Invalid config: must be an object
115
- Config file: ${configPath}`
116
- );
128
+ throw new Error(`Invalid config: must be an object
129
+ Config file: ${configPath}`);
117
130
  }
118
131
  const cfg = config;
119
132
  if (!("overwrite" in cfg)) {
@@ -151,12 +164,12 @@ Received: ${typeof generates}`
151
164
  }
152
165
  }
153
166
 
154
- // src/utils/config-loader.ts
167
+ // src/utils/load-config.ts
155
168
  async function loadConfig(configPath) {
156
169
  const defaultConfigPath = "algolia-codegen.ts";
157
170
  const finalConfigPath = configPath || defaultConfigPath;
158
171
  const resolvedPath = (0, import_path.resolve)(process.cwd(), finalConfigPath);
159
- if (!(0, import_fs.existsSync)(resolvedPath)) {
172
+ if (!(0, import_fs2.existsSync)(resolvedPath)) {
160
173
  throw new Error(
161
174
  `Config file not found: ${resolvedPath}
162
175
  Please create a config file or specify a different path using --config option.`
@@ -164,34 +177,38 @@ Please create a config file or specify a different path using --config option.`
164
177
  }
165
178
  const configUrl = (0, import_url.pathToFileURL)(resolvedPath).href;
166
179
  let configModule;
167
- try {
168
- configModule = await import(configUrl);
169
- } catch (importError) {
170
- const jsPath = resolvedPath.replace(/\.ts$/, ".js");
171
- if ((0, import_fs.existsSync)(jsPath)) {
172
- const jsUrl = (0, import_url.pathToFileURL)(jsPath).href;
173
- try {
174
- configModule = await import(jsUrl);
175
- } catch (jsImportError) {
180
+ if (resolvedPath.endsWith(".ts")) {
181
+ try {
182
+ configModule = await loadTypeScriptConfig(resolvedPath);
183
+ } catch (tsError) {
184
+ const jsPath = resolvedPath.replace(/\.ts$/, ".js");
185
+ if ((0, import_fs2.existsSync)(jsPath)) {
186
+ const jsUrl = (0, import_url.pathToFileURL)(jsPath).href;
187
+ try {
188
+ configModule = await import(jsUrl);
189
+ } catch (jsImportError) {
190
+ throw new Error(
191
+ `Failed to import config file: ${resolvedPath}
192
+ Tried both .ts (via esbuild) and .js extensions.
193
+ TypeScript error: ${tsError instanceof Error ? tsError.message : String(tsError)}
194
+ JavaScript error: ${jsImportError instanceof Error ? jsImportError.message : String(jsImportError)}`
195
+ );
196
+ }
197
+ } else {
176
198
  throw new Error(
177
- `Failed to import config file: ${resolvedPath}
178
- Tried both .ts and .js extensions.
179
- Error: ${jsImportError instanceof Error ? jsImportError.message : String(jsImportError)}
180
- Note: If using TypeScript config, you may need to compile it first or use a tool like tsx.`
199
+ `Failed to load TypeScript config file: ${resolvedPath}
200
+ Error: ${tsError instanceof Error ? tsError.message : String(tsError)}
201
+ Make sure esbuild is installed as a dependency.`
181
202
  );
182
203
  }
183
- } else {
204
+ }
205
+ } else {
206
+ try {
207
+ configModule = await import(configUrl);
208
+ } catch (importError) {
184
209
  const errorMessage = importError instanceof Error ? importError.message : String(importError);
185
- const isTypeScriptError = resolvedPath.endsWith(".ts") && (errorMessage.includes("Cannot find module") || errorMessage.includes("Unknown file extension"));
186
- throw new Error(
187
- `Failed to import config file: ${resolvedPath}
188
- ` + (isTypeScriptError ? `Node.js cannot directly import TypeScript files.
189
- Please either:
190
- 1. Compile your config to JavaScript (.js)
191
- 2. Use a tool like tsx to run the CLI: tsx algolia-codegen
192
- 3. Or use a JavaScript config file instead
193
- ` : `Error: ${errorMessage}`)
194
- );
210
+ throw new Error(`Failed to import config file: ${resolvedPath}
211
+ Error: ${errorMessage}`);
195
212
  }
196
213
  }
197
214
  if (!configModule.default) {
@@ -212,8 +229,8 @@ Received: ${typeof config}`
212
229
  }
213
230
 
214
231
  // src/utils/fetch-algolia-data.ts
215
- var import_algoliasearch = __toESM(require("algoliasearch"), 1);
216
- var import_fs2 = require("fs");
232
+ var import_algoliasearch = require("algoliasearch");
233
+ var import_fs3 = require("fs");
217
234
  var import_path2 = require("path");
218
235
 
219
236
  // src/utils/generate-typescript-types.ts
@@ -320,7 +337,7 @@ var TypeGenerator = class {
320
337
  return `${this.prefix}Hit${this.postfix}`;
321
338
  }
322
339
  const lastPart = path[path.length - 1];
323
- const parts = lastPart.replace(/[\[\]]/g, "").split(/[-_\s]+/).filter(Boolean);
340
+ const parts = lastPart.replace(/[[\]]/g, "").split(/[-_\s]+/).filter(Boolean);
324
341
  const pascalCase = parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
325
342
  if (path.length > 1) {
326
343
  const parent = path[path.length - 2];
@@ -476,7 +493,7 @@ async function fetchAlgoliaData(filePath, generatorConfig, overwrite) {
476
493
  console.log(`
477
494
  Processing file: ${filePath}`);
478
495
  const resolvedPath = (0, import_path2.resolve)(process.cwd(), filePath);
479
- if ((0, import_fs2.existsSync)(resolvedPath) && !overwrite) {
496
+ if ((0, import_fs3.existsSync)(resolvedPath) && !overwrite) {
480
497
  throw new Error(
481
498
  `File already exists: ${resolvedPath}
482
499
  Set overwrite: true in config to allow overwriting existing files.`
@@ -486,10 +503,7 @@ Set overwrite: true in config to allow overwriting existing files.`
486
503
  console.log(`App ID: ${generatorConfig.appId}`);
487
504
  let client;
488
505
  try {
489
- client = (0, import_algoliasearch.default)(
490
- generatorConfig.appId,
491
- generatorConfig.searchKey
492
- );
506
+ client = (0, import_algoliasearch.algoliasearch)(generatorConfig.appId, generatorConfig.searchKey);
493
507
  } catch (error) {
494
508
  throw new Error(
495
509
  `Failed to initialize Algolia client: ${error instanceof Error ? error.message : String(error)}`
@@ -501,8 +515,8 @@ Set overwrite: true in config to allow overwriting existing files.`
501
515
  results = await client.search([
502
516
  {
503
517
  indexName: generatorConfig.indexName,
504
- query: "",
505
518
  params: {
519
+ query: "",
506
520
  hitsPerPage: 1
507
521
  }
508
522
  }
@@ -543,10 +557,10 @@ Set overwrite: true in config to allow overwriting existing files.`
543
557
  console.log(`ObjectID: ${sampleHit.objectID || "N/A"}`);
544
558
  const fileContent = generateTypeScriptTypes(sampleHit, generatorConfig);
545
559
  const dir = (0, import_path2.dirname)(resolvedPath);
546
- if (!(0, import_fs2.existsSync)(dir)) {
547
- (0, import_fs2.mkdirSync)(dir, { recursive: true });
560
+ if (!(0, import_fs3.existsSync)(dir)) {
561
+ (0, import_fs3.mkdirSync)(dir, { recursive: true });
548
562
  }
549
- (0, import_fs2.writeFileSync)(resolvedPath, fileContent, "utf-8");
563
+ (0, import_fs3.writeFileSync)(resolvedPath, fileContent, "utf-8");
550
564
  console.log(`Generated file: ${filePath}`);
551
565
  }
552
566
 
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  validateConfig,
4
4
  validateGeneratorConfig,
5
5
  validateUrlSchema
6
- } from "./chunk-4FWQJC54.js";
6
+ } from "./chunk-4T5DZK75.js";
7
7
  export {
8
8
  main,
9
9
  validateConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "algolia-codegen",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Generate TypeScript types from Algolia indices",
5
5
  "license": "MIT",
6
6
  "author": "nightlightmare",
@@ -37,17 +37,25 @@
37
37
  "dist"
38
38
  ],
39
39
  "dependencies": {
40
- "algoliasearch": "^4.24.0",
41
- "commander": "^12.0.0"
40
+ "algoliasearch": "^5.46.2",
41
+ "commander": "^14.0.2",
42
+ "dotenv": "^17.2.3",
43
+ "esbuild": "^0.27.0",
44
+ "tsx": "^4.21.0"
42
45
  },
43
46
  "devDependencies": {
44
47
  "@types/node": "^25.0.3",
48
+ "@vitest/coverage-v8": "^2.1.8",
49
+ "eslint": "^9.39.2",
50
+ "eslint-config-prettier": "^10.1.8",
51
+ "prettier": "^3.7.4",
45
52
  "tsup": "^8.0.0",
46
- "tsx": "^4.21.0",
47
- "typescript": "^5.4.0"
53
+ "typescript": "^5.4.0",
54
+ "typescript-eslint": "^8.50.1",
55
+ "vitest": "^2.1.8"
48
56
  },
49
57
  "engines": {
50
- "node": ">=18"
58
+ "node": ">=20"
51
59
  },
52
60
  "publishConfig": {
53
61
  "access": "public"
@@ -56,6 +64,13 @@
56
64
  "build": "tsup",
57
65
  "dev": "tsup --watch",
58
66
  "clean": "rm -rf dist",
59
- "type-check": "tsc --noEmit"
67
+ "type-check": "tsc --noEmit",
68
+ "lint": "eslint .",
69
+ "lint:fix": "eslint . --fix",
70
+ "format": "prettier --write .",
71
+ "format:check": "prettier --check .",
72
+ "test": "vitest run",
73
+ "test:watch": "vitest",
74
+ "test:coverage": "vitest run --coverage"
60
75
  }
61
76
  }