drizzle-multitenant 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,277 @@
1
+ import {existsSync}from'fs';import {mkdir,writeFile,readdir}from'fs/promises';import {join,resolve,dirname}from'path';function h(t){let{tableName:e,tableNamePascal:n,tableNameCamel:r,type:i}=t,a=w(t),o=F(t),d=k(t),s=z(t),m=M(t);return `/**
2
+ * ${n} schema
3
+ * Type: ${i}
4
+ *
5
+ * @module schema/${i}/${r}
6
+ */
7
+
8
+ ${a}
9
+
10
+ /**
11
+ * ${n} table definition
12
+ */
13
+ export const ${r} = pgTable('${e}', {
14
+ ${o}
15
+ });
16
+
17
+ ${d}
18
+ ${s}
19
+ ${m}
20
+ `}function w(t){let{includeTimestamps:e,includeSoftDelete:n,useUuid:r,includeExample:i}=t,a=["pgTable"];return r?a.push("uuid"):a.push("serial"),a.push("text"),i&&a.push("varchar","boolean"),(e||n)&&a.push("timestamp"),a.push("index"),`import { ${[...new Set(a)].sort().join(", ")} } from 'drizzle-orm/pg-core';
21
+ import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
22
+ import { z } from 'zod';`}function F(t){let{includeTimestamps:e,includeSoftDelete:n,useUuid:r,includeExample:i}=t,a=[];return r?a.push(" id: uuid('id').primaryKey().defaultRandom(),"):a.push(" id: serial('id').primaryKey(),"),i&&(a.push(" name: varchar('name', { length: 255 }).notNull(),"),a.push(" description: text('description'),"),a.push(" isActive: boolean('is_active').notNull().default(true),")),e&&(a.push(" createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),"),a.push(" updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),")),n&&a.push(" deletedAt: timestamp('deleted_at', { withTimezone: true }),"),a.join(`
23
+ `)}function k(t){let{tableNameCamel:e,tableName:n,includeExample:r,includeTimestamps:i}=t,a=[];return r&&(a.push(` nameIdx: index('${n}_name_idx').on(${e}.name),`),a.push(` isActiveIdx: index('${n}_is_active_idx').on(${e}.isActive),`)),i&&a.push(` createdAtIdx: index('${n}_created_at_idx').on(${e}.createdAt),`),a.length===0?"":`/**
24
+ * ${t.tableNamePascal} table indexes
25
+ */
26
+ export const ${e}Indexes = {
27
+ ${a.join(`
28
+ `)}
29
+ };
30
+ `}function z(t){return `// Uncomment and modify to add relations
31
+ // import { relations } from 'drizzle-orm';
32
+ //
33
+ // export const ${t.tableNameCamel}Relations = relations(${t.tableNameCamel}, ({ one, many }) => ({
34
+ // // Add your relations here
35
+ // // user: one(users, {
36
+ // // fields: [${t.tableNameCamel}.userId],
37
+ // // references: [users.id],
38
+ // // }),
39
+ // }));
40
+ `}function M(t){let{tableNameCamel:e,tableNamePascal:n}=t;return `/**
41
+ * Zod schemas for validation
42
+ */
43
+ export const insert${n}Schema = createInsertSchema(${e});
44
+ export const select${n}Schema = createSelectSchema(${e});
45
+
46
+ /**
47
+ * TypeScript types inferred from schema
48
+ */
49
+ export type ${n} = typeof ${e}.$inferSelect;
50
+ export type New${n} = typeof ${e}.$inferInsert;
51
+
52
+ /**
53
+ * Zod types
54
+ */
55
+ export type Insert${n} = z.infer<typeof insert${n}Schema>;
56
+ export type Select${n} = z.infer<typeof select${n}Schema>;
57
+ `}function S(t){let{seedName:e,type:n,tableName:r}=t;return n==="shared"?P(e,r):v(e,r)}function v(t,e){let n=e?`import { ${e} } from '../../src/db/schema/tenant/${e}.js';`:"// import { yourTable } from '../../src/db/schema/tenant/yourTable.js';",r=e?G(e):X();return `/**
58
+ * Tenant seed: ${t}
59
+ *
60
+ * This seed runs in the context of each tenant's schema.
61
+ * Use this to populate initial data for each tenant.
62
+ *
63
+ * @module seeds/tenant/${t}
64
+ */
65
+
66
+ import type { SeedFunction } from 'drizzle-multitenant';
67
+ ${n}
68
+
69
+ /**
70
+ * Seed function that populates tenant data
71
+ *
72
+ * @param db - Drizzle database instance scoped to the tenant
73
+ * @param tenantId - The tenant ID being seeded
74
+ *
75
+ * @example
76
+ * \`\`\`bash
77
+ * npx drizzle-multitenant seed --file=./drizzle/seeds/tenant/${t}.ts --tenant=my-tenant
78
+ * \`\`\`
79
+ */
80
+ export const seed: SeedFunction = async (db, tenantId) => {
81
+ console.log(\`Seeding tenant: \${tenantId}\`);
82
+
83
+ ${r}
84
+ };
85
+
86
+ export default seed;
87
+ `}function P(t,e){let n=e?`import { ${e} } from '../../src/db/schema/shared/${e}.js';`:"// import { yourTable } from '../../src/db/schema/shared/yourTable.js';",r=e?B(e):j();return `/**
88
+ * Shared seed: ${t}
89
+ *
90
+ * This seed runs against the shared/public schema.
91
+ * Use this to populate global data like plans, roles, permissions, etc.
92
+ *
93
+ * @module seeds/shared/${t}
94
+ */
95
+
96
+ import type { SharedSeedFunction } from 'drizzle-multitenant';
97
+ ${n}
98
+
99
+ /**
100
+ * Seed function that populates shared data
101
+ *
102
+ * @param db - Drizzle database instance for the shared schema
103
+ *
104
+ * @example
105
+ * \`\`\`bash
106
+ * npx drizzle-multitenant seed:shared --file=./drizzle/seeds/shared/${t}.ts
107
+ * \`\`\`
108
+ */
109
+ export const seed: SharedSeedFunction = async (db) => {
110
+ console.log('Seeding shared schema...');
111
+
112
+ ${r}
113
+ };
114
+
115
+ export default seed;
116
+ `}function G(t){return ` // Insert data with conflict handling (idempotent)
117
+ await db.insert(${t}).values([
118
+ {
119
+ name: 'Example Item 1',
120
+ // Add your columns here
121
+ },
122
+ {
123
+ name: 'Example Item 2',
124
+ // Add your columns here
125
+ },
126
+ ]).onConflictDoNothing();
127
+
128
+ console.log(\`Seeded ${t} for tenant: \${tenantId}\`);`}function B(t){return ` // Insert data with conflict handling (idempotent)
129
+ await db.insert(${t}).values([
130
+ {
131
+ name: 'Example Item 1',
132
+ // Add your columns here
133
+ },
134
+ {
135
+ name: 'Example Item 2',
136
+ // Add your columns here
137
+ },
138
+ ]).onConflictDoNothing();
139
+
140
+ console.log('Seeded ${t} in shared schema');`}function X(){return ` // Example: Insert initial data
141
+ // await db.insert(yourTable).values([
142
+ // { name: 'Item 1', description: 'Description 1' },
143
+ // { name: 'Item 2', description: 'Description 2' },
144
+ // ]).onConflictDoNothing();
145
+
146
+ // You can use tenantId to customize data per tenant
147
+ // if (tenantId === 'demo-tenant') {
148
+ // await db.insert(yourTable).values([
149
+ // { name: 'Demo Item', description: 'Only for demo tenant' },
150
+ // ]);
151
+ // }
152
+
153
+ console.log(\`Seed completed for tenant: \${tenantId}\`);`}function j(){return ` // Example: Insert shared data like plans, roles, permissions
154
+ // await db.insert(plans).values([
155
+ // { id: 'free', name: 'Free', price: 0 },
156
+ // { id: 'pro', name: 'Pro', price: 29 },
157
+ // { id: 'enterprise', name: 'Enterprise', price: 99 },
158
+ // ]).onConflictDoNothing();
159
+
160
+ // await db.insert(roles).values([
161
+ // { id: 'admin', name: 'Administrator', permissions: ['*'] },
162
+ // { id: 'user', name: 'User', permissions: ['read'] },
163
+ // ]).onConflictDoNothing();
164
+
165
+ console.log('Shared seed completed');`}function N(t){let{migrationName:e,type:n,template:r,tableName:i}=t,a=new Date().toISOString(),o=`-- Migration: ${e}
166
+ -- Type: ${n}
167
+ -- Created at: ${a}
168
+ -- Template: ${r}
169
+
170
+ `;switch(r){case "create-table":return o+K(i);case "add-column":return o+W(i);case "add-index":return o+q(i);case "add-foreign-key":return o+Y(i);default:return o+H()}}function K(t){let e=t||"table_name";return `-- Create table: ${e}
171
+ CREATE TABLE IF NOT EXISTS "${e}" (
172
+ "id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
173
+ "name" VARCHAR(255) NOT NULL,
174
+ "description" TEXT,
175
+ "is_active" BOOLEAN NOT NULL DEFAULT true,
176
+ "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
177
+ "updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
178
+ );
179
+
180
+ -- Create indexes
181
+ CREATE INDEX IF NOT EXISTS "${e}_name_idx" ON "${e}" ("name");
182
+ CREATE INDEX IF NOT EXISTS "${e}_created_at_idx" ON "${e}" ("created_at");
183
+
184
+ -- Add updated_at trigger (optional but recommended)
185
+ CREATE OR REPLACE FUNCTION update_updated_at_column()
186
+ RETURNS TRIGGER AS $$
187
+ BEGIN
188
+ NEW.updated_at = NOW();
189
+ RETURN NEW;
190
+ END;
191
+ $$ LANGUAGE plpgsql;
192
+
193
+ DROP TRIGGER IF EXISTS update_${e}_updated_at ON "${e}";
194
+ CREATE TRIGGER update_${e}_updated_at
195
+ BEFORE UPDATE ON "${e}"
196
+ FOR EACH ROW
197
+ EXECUTE FUNCTION update_updated_at_column();
198
+ `}function W(t){let e=t||"table_name";return `-- Add column to: ${e}
199
+ -- ALTER TABLE "${e}" ADD COLUMN "column_name" data_type [constraints];
200
+
201
+ -- Examples:
202
+
203
+ -- Add a nullable text column
204
+ -- ALTER TABLE "${e}" ADD COLUMN "notes" TEXT;
205
+
206
+ -- Add a non-null column with default
207
+ -- ALTER TABLE "${e}" ADD COLUMN "status" VARCHAR(50) NOT NULL DEFAULT 'pending';
208
+
209
+ -- Add a column with foreign key reference
210
+ -- ALTER TABLE "${e}" ADD COLUMN "user_id" UUID REFERENCES "users" ("id");
211
+
212
+ -- Add a column with check constraint
213
+ -- ALTER TABLE "${e}" ADD COLUMN "priority" INTEGER CHECK (priority >= 1 AND priority <= 5);
214
+
215
+ -- Write your column additions below:
216
+
217
+ `}function q(t){let e=t||"table_name";return `-- Add index to: ${e}
218
+ -- CREATE INDEX [CONCURRENTLY] "index_name" ON "${e}" ("column_name");
219
+
220
+ -- Examples:
221
+
222
+ -- Simple index on single column
223
+ -- CREATE INDEX "${e}_column_idx" ON "${e}" ("column_name");
224
+
225
+ -- Composite index on multiple columns
226
+ -- CREATE INDEX "${e}_col1_col2_idx" ON "${e}" ("column1", "column2");
227
+
228
+ -- Partial index (filtered)
229
+ -- CREATE INDEX "${e}_active_idx" ON "${e}" ("created_at") WHERE "is_active" = true;
230
+
231
+ -- Unique index
232
+ -- CREATE UNIQUE INDEX "${e}_email_unique_idx" ON "${e}" ("email");
233
+
234
+ -- GIN index for full-text search or JSONB
235
+ -- CREATE INDEX "${e}_data_gin_idx" ON "${e}" USING GIN ("data");
236
+
237
+ -- CONCURRENT index (doesn't lock table, recommended for production)
238
+ -- CREATE INDEX CONCURRENTLY "${e}_column_idx" ON "${e}" ("column_name");
239
+
240
+ -- Write your indexes below:
241
+
242
+ `}function Y(t){let e=t||"table_name";return `-- Add foreign key to: ${e}
243
+ -- ALTER TABLE "${e}" ADD CONSTRAINT "fk_name" FOREIGN KEY ("column") REFERENCES "other_table" ("id");
244
+
245
+ -- Examples:
246
+
247
+ -- Basic foreign key
248
+ -- ALTER TABLE "${e}" ADD CONSTRAINT "${e}_user_id_fk"
249
+ -- FOREIGN KEY ("user_id") REFERENCES "users" ("id");
250
+
251
+ -- Foreign key with ON DELETE action
252
+ -- ALTER TABLE "${e}" ADD CONSTRAINT "${e}_user_id_fk"
253
+ -- FOREIGN KEY ("user_id") REFERENCES "users" ("id")
254
+ -- ON DELETE CASCADE;
255
+
256
+ -- Foreign key with ON UPDATE action
257
+ -- ALTER TABLE "${e}" ADD CONSTRAINT "${e}_category_id_fk"
258
+ -- FOREIGN KEY ("category_id") REFERENCES "categories" ("id")
259
+ -- ON DELETE SET NULL
260
+ -- ON UPDATE CASCADE;
261
+
262
+ -- Cross-schema foreign key (to shared/public schema)
263
+ -- ALTER TABLE "${e}" ADD CONSTRAINT "${e}_plan_id_fk"
264
+ -- FOREIGN KEY ("plan_id") REFERENCES "public"."plans" ("id");
265
+
266
+ -- Write your foreign keys below:
267
+
268
+ `}function H(){return `-- Write your SQL migration here
269
+
270
+ -- Up migration (apply changes)
271
+
272
+
273
+ -- Remember to consider:
274
+ -- 1. Idempotency: Use IF NOT EXISTS / IF EXISTS where possible
275
+ -- 2. Transactions: This will run in a transaction
276
+ -- 3. Rollback: Plan how to undo these changes if needed
277
+ `}function x(t){let e=[/^create[-_](.+)$/i,/^add[-_](.+?)[-_]?(?:table)?$/i,/^(?:add|create)[-_](?:index|fk|constraint)[-_](?:to|on)[-_](.+)$/i,/^(.+?)[-_](?:table|schema)$/i];for(let n of e){let r=t.match(n);if(r?.[1])return r[1].toLowerCase().replace(/[-\s]+/g,"_").replace(/s$/,"")}}function $(t){let e=t.toLowerCase();return e.includes("create")||e.includes("table")?"create-table":e.includes("index")||e.includes("idx")?"add-index":e.includes("fk")||e.includes("foreign")||e.includes("reference")?"add-foreign-key":e.includes("add")||e.includes("column")?"add-column":"blank"}function y(t){let e=t.replace(/([a-z])([A-Z])/g,"$1_$2").replace(/[-\s]+/g,"_").toLowerCase(),n=e.split("_").map(i=>i.charAt(0).toUpperCase()+i.slice(1)).join(""),r=n.charAt(0).toLowerCase()+n.slice(1);return {snake:e,pascal:n,camel:r}}var f={schemaDir:"src/db/schema",seedDir:"drizzle/seeds",tenantMigrationsDir:"drizzle/tenant-migrations",sharedMigrationsDir:"drizzle/shared-migrations"};async function Q(t){let{name:e,type:n,outputDir:r,includeExample:i=true,includeTimestamps:a=true,includeSoftDelete:o=false,useUuid:d=true}=t;try{let s=y(e),m={tableName:s.snake,tableNamePascal:s.pascal,tableNameCamel:s.camel,type:n,includeTimestamps:a,includeSoftDelete:o,useUuid:d,includeExample:i},l=h(m),E=r||join(f.schemaDir,n),p=`${s.camel}.ts`,c=resolve(process.cwd(),E,p);return await mkdir(dirname(c),{recursive:!0}),existsSync(c)?{success:!1,filePath:c,fileName:p,kind:"schema",type:n,error:`File already exists: ${c}`}:(await writeFile(c,l,"utf-8"),{success:!0,filePath:c,fileName:p,kind:"schema",type:n})}catch(s){return {success:false,filePath:"",fileName:"",kind:"schema",type:n,error:s instanceof Error?s.message:String(s)}}}async function V(t){let{name:e,type:n,outputDir:r,tableName:i}=t;try{let a=y(e),o={seedName:a.camel,type:n,...i!==void 0&&{tableName:i}},d=S(o),s=r||join(f.seedDir,n),m=`${a.camel}.ts`,l=resolve(process.cwd(),s,m);return await mkdir(dirname(l),{recursive:!0}),existsSync(l)?{success:!1,filePath:l,fileName:m,kind:"seed",type:n,error:`File already exists: ${l}`}:(await writeFile(l,d,"utf-8"),{success:!0,filePath:l,fileName:m,kind:"seed",type:n})}catch(a){return {success:false,filePath:"",fileName:"",kind:"seed",type:n,error:a instanceof Error?a.message:String(a)}}}async function J(t){let{name:e,type:n,outputDir:r,template:i}=t;try{let a=n==="shared"?f.sharedMigrationsDir:f.tenantMigrationsDir,o=r||a,d=resolve(process.cwd(),o);await mkdir(d,{recursive:!0});let m=(existsSync(d)?await readdir(d):[]).filter(g=>g.endsWith(".sql")),l=0;for(let g of m){let R=g.match(/^(\d+)_/);R?.[1]&&(l=Math.max(l,parseInt(R[1],10)));}let E=(l+1).toString().padStart(4,"0"),p=e.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,""),c=`${E}_${p}.sql`,u=join(d,c),O=i||$(e),I=x(e),L={migrationName:e,type:n,template:O,...I!==void 0&&{tableName:I}},U=N(L);return existsSync(u)?{success:!1,filePath:u,fileName:c,kind:"migration",type:n,error:`File already exists: ${u}`}:(await writeFile(u,U,"utf-8"),{success:!0,filePath:u,fileName:c,kind:"migration",type:n})}catch(a){return {success:false,filePath:"",fileName:"",kind:"migration",type:n,error:a instanceof Error?a.message:String(a)}}}function ee(){return [{value:"create-table",label:"Create Table",description:"Template for creating a new table with common columns"},{value:"add-column",label:"Add Column",description:"Template for adding columns to an existing table"},{value:"add-index",label:"Add Index",description:"Template for creating indexes"},{value:"add-foreign-key",label:"Add Foreign Key",description:"Template for adding foreign key constraints"},{value:"blank",label:"Blank",description:"Empty migration with basic comments"}]}export{f as DEFAULT_DIRS,N as generateMigrationTemplate,h as generateSchemaTemplate,S as generateSeedTemplate,ee as getMigrationTemplates,$ as inferMigrationTemplate,x as inferTableName,J as scaffoldMigration,Q as scaffoldSchema,V as scaffoldSeed,y as toCase};
@@ -111,6 +111,52 @@ interface DebugContext {
111
111
  /** Additional metadata */
112
112
  metadata?: Record<string, unknown>;
113
113
  }
114
+ /**
115
+ * Lint rule configuration - can be severity only or [severity, options]
116
+ */
117
+ type LintRuleConfig<TOptions = Record<string, unknown>> = 'off' | 'warn' | 'error' | ['off' | 'warn' | 'error', TOptions];
118
+ /**
119
+ * All available lint rules for configuration
120
+ */
121
+ interface LintRulesConfig {
122
+ /** Enforce table naming convention (snake_case by default) */
123
+ 'table-naming'?: LintRuleConfig<{
124
+ style?: 'snake_case' | 'camelCase' | 'PascalCase' | 'kebab-case';
125
+ exceptions?: string[];
126
+ }>;
127
+ /** Enforce column naming convention (snake_case by default) */
128
+ 'column-naming'?: LintRuleConfig<{
129
+ style?: 'snake_case' | 'camelCase' | 'PascalCase' | 'kebab-case';
130
+ exceptions?: string[];
131
+ }>;
132
+ /** Require every table to have a primary key */
133
+ 'require-primary-key'?: LintRuleConfig;
134
+ /** Prefer UUID over serial/integer for primary keys */
135
+ 'prefer-uuid-pk'?: LintRuleConfig;
136
+ /** Require created_at/updated_at columns */
137
+ 'require-timestamps'?: LintRuleConfig<{
138
+ columns?: string[];
139
+ }>;
140
+ /** Require indexes on foreign key columns */
141
+ 'index-foreign-keys'?: LintRuleConfig;
142
+ /** Warn about CASCADE DELETE on foreign keys */
143
+ 'no-cascade-delete'?: LintRuleConfig;
144
+ /** Require soft delete column on tables */
145
+ 'require-soft-delete'?: LintRuleConfig<{
146
+ column?: string;
147
+ }>;
148
+ }
149
+ /**
150
+ * Lint configuration
151
+ */
152
+ interface LintConfig {
153
+ /** Lint rules configuration */
154
+ rules?: LintRulesConfig;
155
+ /** Glob patterns for schema files to include */
156
+ include?: string[];
157
+ /** Glob patterns for schema files to exclude */
158
+ exclude?: string[];
159
+ }
114
160
  /**
115
161
  * Main configuration interface
116
162
  */
@@ -127,6 +173,8 @@ interface Config<TTenantSchema extends Record<string, unknown> = Record<string,
127
173
  metrics?: MetricsConfig;
128
174
  /** Debug configuration */
129
175
  debug?: DebugConfig;
176
+ /** Schema linting configuration */
177
+ lint?: LintConfig;
130
178
  }
131
179
  /**
132
180
  * Internal pool entry for LRU cache
@@ -346,4 +394,4 @@ declare const DEFAULT_CONFIG: {
346
394
  };
347
395
  };
348
396
 
349
- export { type Config as C, type DebugConfig as D, type Hooks as H, type IsolationConfig as I, type MetricsConfig as M, type PoolEntry as P, type RetryConfig as R, type SharedDb as S, type TenantManager as T, type WarmupOptions as W, type TenantDb as a, type ConnectionConfig as b, type IsolationStrategy as c, type SchemasConfig as d, type DebugContext as e, type WarmupResult as f, type TenantWarmupResult as g, type HealthCheckOptions as h, type HealthCheckResult as i, type PoolHealth as j, type PoolHealthStatus as k, type MetricsResult as l, type TenantPoolMetrics as m, type ConnectionMetrics as n, DEFAULT_CONFIG as o };
397
+ export { type Config as C, type DebugConfig as D, type Hooks as H, type IsolationConfig as I, type LintRuleConfig as L, type MetricsConfig as M, type PoolEntry as P, type RetryConfig as R, type SharedDb as S, type TenantManager as T, type WarmupOptions as W, type TenantDb as a, type ConnectionConfig as b, type IsolationStrategy as c, type SchemasConfig as d, type DebugContext as e, type WarmupResult as f, type TenantWarmupResult as g, type HealthCheckOptions as h, type HealthCheckResult as i, type PoolHealth as j, type PoolHealthStatus as k, type MetricsResult as l, type TenantPoolMetrics as m, type ConnectionMetrics as n, type LintRulesConfig as o, type LintConfig as p, DEFAULT_CONFIG as q };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drizzle-multitenant",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Multi-tenancy toolkit for Drizzle ORM with schema isolation, tenant context, and parallel migrations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,6 +34,22 @@
34
34
  "./cross-schema": {
35
35
  "import": "./dist/cross-schema/index.js",
36
36
  "types": "./dist/cross-schema/index.d.ts"
37
+ },
38
+ "./scaffold": {
39
+ "import": "./dist/scaffold/index.js",
40
+ "types": "./dist/scaffold/index.d.ts"
41
+ },
42
+ "./lint": {
43
+ "import": "./dist/lint/index.js",
44
+ "types": "./dist/lint/index.d.ts"
45
+ },
46
+ "./metrics": {
47
+ "import": "./dist/metrics/index.js",
48
+ "types": "./dist/metrics/index.d.ts"
49
+ },
50
+ "./export": {
51
+ "import": "./dist/export/index.js",
52
+ "types": "./dist/export/index.d.ts"
37
53
  }
38
54
  },
39
55
  "bin": {
@@ -73,6 +89,7 @@
73
89
  "cli-progress": "^3.12.0",
74
90
  "cli-table3": "^0.6.5",
75
91
  "commander": "^12.1.0",
92
+ "glob": "^13.0.0",
76
93
  "lru-cache": "^10.4.3",
77
94
  "ora": "^8.2.0"
78
95
  },