algolia-codegen 0.1.2 → 0.1.4
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 +22 -0
- package/README.md +75 -71
- package/dist/{chunk-R66ECGLW.js → chunk-USMYY2DN.js} +185 -56
- package/dist/cli.cjs +191 -58
- package/dist/cli.js +7 -3
- package/dist/index.cjs +185 -56
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +1 -1
- package/package.json +19 -4
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
|
+
[](https://www.npmjs.com/package/algolia-codegen)
|
|
4
|
+
[](https://www.npmjs.com/package/algolia-codegen)
|
|
5
|
+
[](https://bundlephobia.com/package/algolia-codegen)
|
|
6
|
+
[](https://github.com/nightlightmare/algolia-codegen)
|
|
7
|
+
[](https://github.com/nightlightmare/algolia-codegen/issues)
|
|
8
|
+
[](https://github.com/nightlightmare/algolia-codegen/blob/main/LICENSE)
|
|
9
|
+
[](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 >=
|
|
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
|
|
41
|
+
import type { AlgoliaCodegenConfig } from 'algolia-codegen';
|
|
34
42
|
|
|
35
43
|
const config: AlgoliaCodegenConfig = {
|
|
36
44
|
overwrite: true,
|
|
37
45
|
generates: {
|
|
38
|
-
|
|
39
|
-
appId:
|
|
40
|
-
searchKey:
|
|
41
|
-
indexName:
|
|
42
|
-
prefix:
|
|
43
|
-
postfix:
|
|
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
|
|
76
|
+
import type { AlgoliaCodegenConfig } from 'algolia-codegen';
|
|
69
77
|
|
|
70
78
|
const config: AlgoliaCodegenConfig = {
|
|
71
79
|
overwrite: true,
|
|
72
80
|
generates: {
|
|
73
|
-
|
|
74
|
-
appId:
|
|
75
|
-
searchKey:
|
|
76
|
-
indexName:
|
|
77
|
-
prefix:
|
|
78
|
-
postfix:
|
|
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
|
-
|
|
95
|
-
appId:
|
|
96
|
-
searchKey:
|
|
97
|
-
indexName:
|
|
102
|
+
'src/algolia/products.ts': {
|
|
103
|
+
appId: 'YOUR_APP_ID',
|
|
104
|
+
searchKey: 'YOUR_SEARCH_API_KEY',
|
|
105
|
+
indexName: 'products',
|
|
98
106
|
},
|
|
99
|
-
|
|
100
|
-
appId:
|
|
101
|
-
searchKey:
|
|
102
|
-
indexName:
|
|
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
|
-
|
|
118
|
-
appId:
|
|
119
|
-
searchKey:
|
|
120
|
-
indexName:
|
|
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
|
-
|
|
125
|
-
appId:
|
|
126
|
-
searchKey:
|
|
127
|
-
indexName:
|
|
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
|
],
|
|
@@ -141,7 +149,15 @@ After installation, you can use the CLI command:
|
|
|
141
149
|
algolia-codegen
|
|
142
150
|
```
|
|
143
151
|
|
|
144
|
-
|
|
152
|
+
#### Options
|
|
153
|
+
|
|
154
|
+
- `-c, --config <path>` - Specify a custom config file path
|
|
155
|
+
- `-v, --verbose` - Enable verbose output (shows detailed logging)
|
|
156
|
+
- `--dry-run` - Simulate execution without writing files (useful for testing)
|
|
157
|
+
|
|
158
|
+
#### Examples
|
|
159
|
+
|
|
160
|
+
Specify a custom config file:
|
|
145
161
|
|
|
146
162
|
```bash
|
|
147
163
|
algolia-codegen --config path/to/config.ts
|
|
@@ -149,6 +165,26 @@ algolia-codegen --config path/to/config.ts
|
|
|
149
165
|
algolia-codegen -c path/to/config.ts
|
|
150
166
|
```
|
|
151
167
|
|
|
168
|
+
Enable verbose output:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
algolia-codegen --verbose
|
|
172
|
+
# or
|
|
173
|
+
algolia-codegen -v
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Run in dry-run mode (no files will be written):
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
algolia-codegen --dry-run
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Combine options:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
algolia-codegen --config custom-config.ts --verbose --dry-run
|
|
186
|
+
```
|
|
187
|
+
|
|
152
188
|
Or if installed locally:
|
|
153
189
|
|
|
154
190
|
```bash
|
|
@@ -187,13 +223,13 @@ type AlgoliaCodegenGeneratorConfig = {
|
|
|
187
223
|
- Fetches a sample record from the specified index
|
|
188
224
|
- Analyzes the record structure and generates TypeScript types
|
|
189
225
|
- Creates a single TypeScript file containing all types found in the index
|
|
190
|
-
|
|
226
|
+
4. **Type Generation**: The generator automatically:
|
|
191
227
|
- Infers types from the sample record structure
|
|
192
228
|
- Handles nested objects, arrays, and complex types
|
|
193
229
|
- Detects and generates generic `IdValue<T>` types for Algolia's id-value pattern arrays
|
|
194
230
|
- Generates proper TypeScript interfaces with JSDoc comments
|
|
195
231
|
- Sorts types by dependencies for correct ordering
|
|
196
|
-
|
|
232
|
+
5. **Error Handling**: Continues processing other files even if one fails, with detailed error messages
|
|
197
233
|
|
|
198
234
|
Each generated file contains all types found in the index, including nested types, properly organized and sorted by dependencies.
|
|
199
235
|
|
|
@@ -213,44 +249,12 @@ Each generated file contains all types found in the index, including nested type
|
|
|
213
249
|
|
|
214
250
|
## Examples
|
|
215
251
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
Given an Algolia record like:
|
|
219
|
-
|
|
220
|
-
```json
|
|
221
|
-
{
|
|
222
|
-
"objectID": "123",
|
|
223
|
-
"name": "Product Name",
|
|
224
|
-
"price": 99.99,
|
|
225
|
-
"tags": ["tag1", "tag2"],
|
|
226
|
-
"metadata": {
|
|
227
|
-
"category": "electronics",
|
|
228
|
-
"rating": 4.5
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
The generator will create TypeScript types:
|
|
252
|
+
See the [examples directory](./examples/) for comprehensive examples including:
|
|
234
253
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
*/
|
|
240
|
-
|
|
241
|
-
export interface AlgoliaHitType {
|
|
242
|
-
metadata: AlgoliaMetadataType;
|
|
243
|
-
name: string;
|
|
244
|
-
objectID: string;
|
|
245
|
-
price: number;
|
|
246
|
-
tags: string[];
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export interface AlgoliaMetadataType {
|
|
250
|
-
category: string;
|
|
251
|
-
rating: number;
|
|
252
|
-
}
|
|
253
|
-
```
|
|
254
|
+
- Framework integrations (Next.js, React, Vue)
|
|
255
|
+
- CI/CD integration (GitHub Actions)
|
|
256
|
+
- Custom prefixes/postfixes
|
|
257
|
+
- Multiple indices configuration
|
|
254
258
|
|
|
255
259
|
## Repository
|
|
256
260
|
|
|
@@ -11,7 +11,7 @@ async function loadTypeScriptConfig(resolvedPath) {
|
|
|
11
11
|
const result = esbuild.transformSync(source, {
|
|
12
12
|
loader: "ts",
|
|
13
13
|
format: "esm",
|
|
14
|
-
target: "
|
|
14
|
+
target: "node20",
|
|
15
15
|
sourcefile: resolvedPath
|
|
16
16
|
});
|
|
17
17
|
if (!result.code) {
|
|
@@ -25,10 +25,8 @@ async function loadTypeScriptConfig(resolvedPath) {
|
|
|
25
25
|
// src/utils/validations/generator-config.ts
|
|
26
26
|
function validateGeneratorConfig(config, path) {
|
|
27
27
|
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
28
|
-
throw new Error(
|
|
29
|
-
|
|
30
|
-
Path: ${path}`
|
|
31
|
-
);
|
|
28
|
+
throw new Error(`Invalid generator config: must be an object
|
|
29
|
+
Path: ${path}`);
|
|
32
30
|
}
|
|
33
31
|
const cfg = config;
|
|
34
32
|
const requiredFields = [
|
|
@@ -70,10 +68,8 @@ Received: ${typeof cfg.postfix}`
|
|
|
70
68
|
// src/utils/validations/url-schema.ts
|
|
71
69
|
function validateUrlSchema(urlSchema, path) {
|
|
72
70
|
if (typeof urlSchema !== "object" || urlSchema === null || Array.isArray(urlSchema)) {
|
|
73
|
-
throw new Error(
|
|
74
|
-
|
|
75
|
-
Path: ${path}`
|
|
76
|
-
);
|
|
71
|
+
throw new Error(`Invalid generates entry: must be an object
|
|
72
|
+
Path: ${path}`);
|
|
77
73
|
}
|
|
78
74
|
const schema = urlSchema;
|
|
79
75
|
for (const [filePath, generatorConfig] of Object.entries(schema)) {
|
|
@@ -90,10 +86,8 @@ Path: ${path}[${filePath}]`
|
|
|
90
86
|
// src/utils/validations/config.ts
|
|
91
87
|
function validateConfig(config, configPath) {
|
|
92
88
|
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
93
|
-
throw new Error(
|
|
94
|
-
|
|
95
|
-
Config file: ${configPath}`
|
|
96
|
-
);
|
|
89
|
+
throw new Error(`Invalid config: must be an object
|
|
90
|
+
Config file: ${configPath}`);
|
|
97
91
|
}
|
|
98
92
|
const cfg = config;
|
|
99
93
|
if (!("overwrite" in cfg)) {
|
|
@@ -174,10 +168,8 @@ Make sure esbuild is installed as a dependency.`
|
|
|
174
168
|
configModule = await import(configUrl);
|
|
175
169
|
} catch (importError) {
|
|
176
170
|
const errorMessage = importError instanceof Error ? importError.message : String(importError);
|
|
177
|
-
throw new Error(
|
|
178
|
-
|
|
179
|
-
Error: ${errorMessage}`
|
|
180
|
-
);
|
|
171
|
+
throw new Error(`Failed to import config file: ${resolvedPath}
|
|
172
|
+
Error: ${errorMessage}`);
|
|
181
173
|
}
|
|
182
174
|
}
|
|
183
175
|
if (!configModule.default) {
|
|
@@ -306,7 +298,7 @@ var TypeGenerator = class {
|
|
|
306
298
|
return `${this.prefix}Hit${this.postfix}`;
|
|
307
299
|
}
|
|
308
300
|
const lastPart = path[path.length - 1];
|
|
309
|
-
const parts = lastPart.replace(/[
|
|
301
|
+
const parts = lastPart.replace(/[[\]]/g, "").split(/[-_\s]+/).filter(Boolean);
|
|
310
302
|
const pascalCase = parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
|
|
311
303
|
if (path.length > 1) {
|
|
312
304
|
const parent = path[path.length - 2];
|
|
@@ -458,30 +450,34 @@ function generateTypeScriptTypes(sampleHit, config) {
|
|
|
458
450
|
}
|
|
459
451
|
|
|
460
452
|
// src/utils/fetch-algolia-data.ts
|
|
461
|
-
async function fetchAlgoliaData(filePath, generatorConfig, overwrite) {
|
|
462
|
-
|
|
463
|
-
Processing file: ${filePath}`);
|
|
453
|
+
async function fetchAlgoliaData(filePath, generatorConfig, overwrite, logger) {
|
|
454
|
+
logger.info(`Processing file: ${filePath}`);
|
|
464
455
|
const resolvedPath = resolve2(process.cwd(), filePath);
|
|
456
|
+
logger.verbose(`Resolved path: ${resolvedPath}`);
|
|
465
457
|
if (existsSync2(resolvedPath) && !overwrite) {
|
|
466
458
|
throw new Error(
|
|
467
459
|
`File already exists: ${resolvedPath}
|
|
468
460
|
Set overwrite: true in config to allow overwriting existing files.`
|
|
469
461
|
);
|
|
470
462
|
}
|
|
471
|
-
|
|
472
|
-
|
|
463
|
+
const connectSpinner = logger.spinner("Connecting to Algolia...");
|
|
464
|
+
connectSpinner.start();
|
|
473
465
|
let client;
|
|
474
466
|
try {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
);
|
|
467
|
+
logger.verbose(`App ID: ${generatorConfig.appId}`);
|
|
468
|
+
logger.verbose(`Index: ${generatorConfig.indexName}`);
|
|
469
|
+
client = algoliasearch(generatorConfig.appId, generatorConfig.searchKey);
|
|
470
|
+
connectSpinner.succeed("Connected to Algolia");
|
|
479
471
|
} catch (error) {
|
|
472
|
+
connectSpinner.fail("Failed to connect to Algolia");
|
|
480
473
|
throw new Error(
|
|
481
474
|
`Failed to initialize Algolia client: ${error instanceof Error ? error.message : String(error)}`
|
|
482
475
|
);
|
|
483
476
|
}
|
|
484
|
-
|
|
477
|
+
const fetchSpinner = logger.spinner(
|
|
478
|
+
`Fetching sample record from index: ${generatorConfig.indexName}`
|
|
479
|
+
);
|
|
480
|
+
fetchSpinner.start();
|
|
485
481
|
let results;
|
|
486
482
|
try {
|
|
487
483
|
results = await client.search([
|
|
@@ -493,7 +489,9 @@ Set overwrite: true in config to allow overwriting existing files.`
|
|
|
493
489
|
}
|
|
494
490
|
}
|
|
495
491
|
]);
|
|
492
|
+
fetchSpinner.succeed("Sample record fetched successfully");
|
|
496
493
|
} catch (error) {
|
|
494
|
+
fetchSpinner.fail("Failed to fetch data from Algolia");
|
|
497
495
|
let errorMessage;
|
|
498
496
|
if (error instanceof Error) {
|
|
499
497
|
errorMessage = error.message;
|
|
@@ -525,52 +523,183 @@ Set overwrite: true in config to allow overwriting existing files.`
|
|
|
525
523
|
throw new Error(`No hits found in Algolia index: ${generatorConfig.indexName}`);
|
|
526
524
|
}
|
|
527
525
|
const sampleHit = result.hits[0];
|
|
528
|
-
|
|
529
|
-
|
|
526
|
+
logger.verbose(`ObjectID: ${sampleHit.objectID || "N/A"}`);
|
|
527
|
+
const generateSpinner = logger.spinner("Generating TypeScript types...");
|
|
528
|
+
generateSpinner.start();
|
|
530
529
|
const fileContent = generateTypeScriptTypes(sampleHit, generatorConfig);
|
|
530
|
+
generateSpinner.succeed("TypeScript types generated");
|
|
531
531
|
const dir = dirname(resolvedPath);
|
|
532
532
|
if (!existsSync2(dir)) {
|
|
533
|
-
|
|
533
|
+
if (!logger.isDryRun) {
|
|
534
|
+
mkdirSync(dir, { recursive: true });
|
|
535
|
+
logger.verbose(`Created directory: ${dir}`);
|
|
536
|
+
} else {
|
|
537
|
+
logger.dryRun(`Would create directory: ${dir}`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
if (logger.isDryRun) {
|
|
541
|
+
logger.dryRun(`Would write file: ${filePath}`);
|
|
542
|
+
logger.verbose(`File content length: ${fileContent.length} characters`);
|
|
543
|
+
} else {
|
|
544
|
+
writeFileSync(resolvedPath, fileContent, "utf-8");
|
|
545
|
+
logger.success(`Generated file: ${filePath}`);
|
|
534
546
|
}
|
|
535
|
-
writeFileSync(resolvedPath, fileContent, "utf-8");
|
|
536
|
-
console.log(`Generated file: ${filePath}`);
|
|
537
547
|
}
|
|
538
548
|
|
|
549
|
+
// src/utils/logger.ts
|
|
550
|
+
import chalk from "chalk";
|
|
551
|
+
import ora from "ora";
|
|
552
|
+
var Logger = class {
|
|
553
|
+
_verbose;
|
|
554
|
+
_dryRun;
|
|
555
|
+
constructor(options = {}) {
|
|
556
|
+
this._verbose = options.verbose ?? false;
|
|
557
|
+
this._dryRun = options.dryRun ?? false;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Check if verbose mode is enabled
|
|
561
|
+
*/
|
|
562
|
+
get isVerbose() {
|
|
563
|
+
return this._verbose;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Check if dry-run mode is enabled
|
|
567
|
+
*/
|
|
568
|
+
get isDryRun() {
|
|
569
|
+
return this._dryRun;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Log info message (always shown)
|
|
573
|
+
*/
|
|
574
|
+
info(message) {
|
|
575
|
+
console.log(chalk.blue("\u2139"), message);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Log success message (always shown)
|
|
579
|
+
*/
|
|
580
|
+
success(message) {
|
|
581
|
+
console.log(chalk.green("\u2713"), message);
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Log warning message (always shown)
|
|
585
|
+
*/
|
|
586
|
+
warn(message) {
|
|
587
|
+
console.log(chalk.yellow("\u26A0"), message);
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Log error message (always shown)
|
|
591
|
+
*/
|
|
592
|
+
error(message) {
|
|
593
|
+
console.error(chalk.red("\u2717"), message);
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Log verbose message (only shown in verbose mode)
|
|
597
|
+
*/
|
|
598
|
+
verbose(message) {
|
|
599
|
+
if (this._verbose) {
|
|
600
|
+
console.log(chalk.gray("\u2192"), message);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Log dry-run message (only shown in dry-run mode)
|
|
605
|
+
*/
|
|
606
|
+
dryRun(message) {
|
|
607
|
+
if (this._dryRun) {
|
|
608
|
+
console.log(chalk.cyan("[DRY-RUN]"), message);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Create a spinner for long-running operations
|
|
613
|
+
*/
|
|
614
|
+
spinner(text) {
|
|
615
|
+
return ora({
|
|
616
|
+
text,
|
|
617
|
+
color: "cyan",
|
|
618
|
+
spinner: "dots"
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Log file operation (respects dry-run)
|
|
623
|
+
*/
|
|
624
|
+
fileOperation(action, filePath) {
|
|
625
|
+
if (this._dryRun) {
|
|
626
|
+
this.dryRun(`Would ${action}: ${chalk.underline(filePath)}`);
|
|
627
|
+
} else {
|
|
628
|
+
this.verbose(`${action}: ${filePath}`);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Format error with context
|
|
633
|
+
*/
|
|
634
|
+
formatError(error, context) {
|
|
635
|
+
let errorMessage = "";
|
|
636
|
+
if (error instanceof Error) {
|
|
637
|
+
errorMessage = error.message;
|
|
638
|
+
if (this._verbose && error.stack) {
|
|
639
|
+
errorMessage += `
|
|
640
|
+
${chalk.gray(error.stack)}`;
|
|
641
|
+
}
|
|
642
|
+
} else if (error && typeof error === "object") {
|
|
643
|
+
const errorObj = error;
|
|
644
|
+
if (errorObj.message) {
|
|
645
|
+
errorMessage = String(errorObj.message);
|
|
646
|
+
} else if (errorObj.status) {
|
|
647
|
+
errorMessage = `HTTP ${errorObj.status}: ${errorObj.statusText || "Unknown error"}`;
|
|
648
|
+
} else {
|
|
649
|
+
try {
|
|
650
|
+
errorMessage = JSON.stringify(error, null, 2);
|
|
651
|
+
} catch {
|
|
652
|
+
errorMessage = String(error);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
} else {
|
|
656
|
+
errorMessage = String(error);
|
|
657
|
+
}
|
|
658
|
+
if (context) {
|
|
659
|
+
return `${chalk.red(context)}
|
|
660
|
+
${errorMessage}`;
|
|
661
|
+
}
|
|
662
|
+
return errorMessage;
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
var logger_default = Logger;
|
|
666
|
+
|
|
539
667
|
// src/index.ts
|
|
540
|
-
var main = async (
|
|
668
|
+
var main = async (options) => {
|
|
669
|
+
const opts = typeof options === "string" ? { configPath: options } : options ?? {};
|
|
670
|
+
const logger = new logger_default({
|
|
671
|
+
verbose: opts.verbose ?? false,
|
|
672
|
+
dryRun: opts.dryRun ?? false
|
|
673
|
+
});
|
|
541
674
|
try {
|
|
542
|
-
const
|
|
543
|
-
|
|
675
|
+
const configSpinner = logger.spinner("Loading configuration...");
|
|
676
|
+
configSpinner.start();
|
|
677
|
+
const config = await loadConfig(opts.configPath);
|
|
678
|
+
configSpinner.succeed("Configuration loaded successfully");
|
|
679
|
+
logger.verbose(`Config path: ${opts.configPath || "default"}`);
|
|
680
|
+
logger.verbose(`Overwrite mode: ${config.overwrite ? "enabled" : "disabled"}`);
|
|
544
681
|
const generatesArray = Array.isArray(config.generates) ? config.generates : [config.generates];
|
|
682
|
+
logger.verbose(`Found ${generatesArray.length} generate configuration(s)`);
|
|
545
683
|
for (const urlSchema of generatesArray) {
|
|
546
684
|
for (const [filePath, generatorConfig] of Object.entries(urlSchema)) {
|
|
547
685
|
try {
|
|
548
|
-
await fetchAlgoliaData(filePath, generatorConfig, config.overwrite);
|
|
686
|
+
await fetchAlgoliaData(filePath, generatorConfig, config.overwrite, logger);
|
|
549
687
|
} catch (error) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
console.error(error.message);
|
|
554
|
-
if (error.stack) {
|
|
555
|
-
console.error(error.stack);
|
|
556
|
-
}
|
|
557
|
-
} else {
|
|
558
|
-
try {
|
|
559
|
-
console.error(JSON.stringify(error, null, 2));
|
|
560
|
-
} catch {
|
|
561
|
-
console.error(String(error));
|
|
562
|
-
}
|
|
563
|
-
}
|
|
688
|
+
logger.error(`Error processing file: ${filePath}`);
|
|
689
|
+
const errorMessage = logger.formatError(error, "Processing failed");
|
|
690
|
+
logger.error(errorMessage);
|
|
564
691
|
}
|
|
565
692
|
}
|
|
566
693
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
if (error instanceof Error) {
|
|
570
|
-
console.error(error.message);
|
|
694
|
+
if (opts.dryRun) {
|
|
695
|
+
logger.info("Dry-run completed. No files were written.");
|
|
571
696
|
} else {
|
|
572
|
-
|
|
697
|
+
logger.success("All files generated successfully!");
|
|
573
698
|
}
|
|
699
|
+
} catch (error) {
|
|
700
|
+
logger.error("Failed to load configuration");
|
|
701
|
+
const errorMessage = logger.formatError(error, "Configuration error");
|
|
702
|
+
logger.error(errorMessage);
|
|
574
703
|
process.exit(1);
|
|
575
704
|
}
|
|
576
705
|
};
|