@djangocfg/ext-base 1.0.0 → 1.0.2

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.
Files changed (41) hide show
  1. package/README.md +140 -176
  2. package/package.json +28 -12
  3. package/preview.png +0 -0
  4. package/src/cli/index.ts +274 -0
  5. package/src/config.ts +17 -0
  6. package/src/index.ts +2 -1
  7. package/src/metadata.ts +73 -0
  8. package/src/types/context.ts +124 -3
  9. package/src/utils/createExtensionConfig.ts +139 -0
  10. package/src/utils/index.ts +5 -0
  11. package/templates/extension-template/README.md.template +62 -0
  12. package/templates/extension-template/package.json.template +73 -0
  13. package/templates/extension-template/preview.png +0 -0
  14. package/templates/extension-template/src/components/.gitkeep +0 -0
  15. package/templates/extension-template/src/config.ts +35 -0
  16. package/templates/extension-template/src/contexts/__PROVIDER_NAME__Context.tsx +41 -0
  17. package/templates/extension-template/src/contexts/__PROVIDER_NAME__ExtensionProvider.tsx +36 -0
  18. package/templates/extension-template/src/hooks/index.ts +27 -0
  19. package/templates/extension-template/src/index.ts +17 -0
  20. package/templates/extension-template/src/types.ts +7 -0
  21. package/templates/extension-template/tsconfig.json +8 -0
  22. package/templates/extension-template/tsup.config.ts +26 -0
  23. package/dist/api.cjs +0 -41
  24. package/dist/api.d.cts +0 -35
  25. package/dist/api.d.ts +0 -35
  26. package/dist/api.js +0 -2
  27. package/dist/auth.cjs +0 -10
  28. package/dist/auth.d.cts +0 -1
  29. package/dist/auth.d.ts +0 -1
  30. package/dist/auth.js +0 -2
  31. package/dist/chunk-3RG5ZIWI.js +0 -8
  32. package/dist/chunk-MECBWZG4.js +0 -44
  33. package/dist/chunk-YQGNYUBX.js +0 -67
  34. package/dist/hooks.cjs +0 -190
  35. package/dist/hooks.d.cts +0 -96
  36. package/dist/hooks.d.ts +0 -96
  37. package/dist/hooks.js +0 -65
  38. package/dist/index.cjs +0 -131
  39. package/dist/index.d.cts +0 -246
  40. package/dist/index.d.ts +0 -246
  41. package/dist/index.js +0 -3
@@ -0,0 +1,274 @@
1
+ /**
2
+ * DjangoCFG Extension CLI
3
+ *
4
+ * Interactive CLI for managing DjangoCFG extensions
5
+ */
6
+
7
+ import { consola } from 'consola';
8
+ import chalk from 'chalk';
9
+ import prompts from 'prompts';
10
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from 'fs';
11
+ import { join, dirname } from 'path';
12
+ import { fileURLToPath } from 'url';
13
+ import { EXTENSION_CATEGORIES } from '../types/context';
14
+
15
+ const __dirname = dirname(fileURLToPath(import.meta.url));
16
+
17
+ // Read package version
18
+ function getVersion(): string {
19
+ try {
20
+ const pkgPath = join(__dirname, '../../package.json');
21
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
22
+ return pkg.version;
23
+ } catch {
24
+ return '1.0.0';
25
+ }
26
+ }
27
+
28
+ // CLI Commands
29
+ const COMMANDS = {
30
+ create: 'Create a new DjangoCFG extension',
31
+ help: 'Show help',
32
+ '--help': 'Show help',
33
+ '-h': 'Show help',
34
+ } as const;
35
+
36
+ type Command = keyof typeof COMMANDS;
37
+
38
+ // Template helpers
39
+ function replacePlaceholders(content: string, replacements: Record<string, string>): string {
40
+ let result = content;
41
+ for (const [key, value] of Object.entries(replacements)) {
42
+ result = result.replaceAll(key, value);
43
+ }
44
+ return result;
45
+ }
46
+
47
+ function copyTemplateRecursive(src: string, dest: string, replacements: Record<string, string>) {
48
+ if (!existsSync(src)) {
49
+ throw new Error(`Template not found: ${src}`);
50
+ }
51
+
52
+ const stats = statSync(src);
53
+
54
+ if (stats.isDirectory()) {
55
+ // Create destination directory
56
+ if (!existsSync(dest)) {
57
+ mkdirSync(dest, { recursive: true });
58
+ }
59
+
60
+ // Copy all files recursively
61
+ const files = readdirSync(src);
62
+ for (const file of files) {
63
+ const srcPath = join(src, file);
64
+ let destFile = file;
65
+
66
+ // Replace __PROVIDER_NAME__ in filenames
67
+ if (file.includes('__PROVIDER_NAME__')) {
68
+ destFile = file.replaceAll('__PROVIDER_NAME__', replacements['__PROVIDER_NAME__']);
69
+ }
70
+
71
+ // Remove .template extension
72
+ if (destFile.endsWith('.template')) {
73
+ destFile = destFile.slice(0, -9); // Remove '.template'
74
+ }
75
+
76
+ const destPath = join(dest, destFile);
77
+ copyTemplateRecursive(srcPath, destPath, replacements);
78
+ }
79
+ } else {
80
+ // Copy file with placeholder replacement
81
+ const content = readFileSync(src, 'utf-8');
82
+ const replaced = replacePlaceholders(content, replacements);
83
+ writeFileSync(dest, replaced, 'utf-8');
84
+ }
85
+ }
86
+
87
+ // Print banner
88
+ function printBanner() {
89
+ console.log();
90
+ console.log(chalk.cyan.bold(' 🚀 DjangoCFG Extension Manager'));
91
+ console.log(chalk.gray(` v${getVersion()}`));
92
+ console.log();
93
+ }
94
+
95
+ // Print help
96
+ function printHelp() {
97
+ printBanner();
98
+
99
+ console.log(chalk.yellow.bold('Usage:'));
100
+ console.log(` ${chalk.cyan('djangocfg-ext')} ${chalk.gray('[command]')}`);
101
+ console.log();
102
+
103
+ console.log(chalk.yellow.bold('Commands:'));
104
+ Object.entries(COMMANDS).forEach(([cmd, desc]) => {
105
+ console.log(` ${chalk.cyan(cmd.padEnd(12))} ${chalk.gray(desc)}`);
106
+ });
107
+ console.log();
108
+
109
+ console.log(chalk.yellow.bold('Examples:'));
110
+ console.log(` ${chalk.gray('$')} ${chalk.cyan('djangocfg-ext create')}`);
111
+ console.log();
112
+ }
113
+
114
+ // Create new extension
115
+ async function createExtension() {
116
+ printBanner();
117
+
118
+ console.log(chalk.yellow('Create a new DjangoCFG extension'));
119
+ console.log();
120
+
121
+ const response = await prompts([
122
+ {
123
+ type: 'text',
124
+ name: 'name',
125
+ message: 'Extension name (e.g., "leads", "payments"):',
126
+ validate: (value: string) => {
127
+ if (!value) return 'Extension name is required';
128
+ if (!/^[a-z][a-z0-9-]*$/.test(value)) {
129
+ return 'Extension name must start with a letter and contain only lowercase letters, numbers, and hyphens';
130
+ }
131
+ return true;
132
+ },
133
+ },
134
+ {
135
+ type: 'text',
136
+ name: 'displayName',
137
+ message: 'Display name (e.g., "Leads & Forms"):',
138
+ validate: (value: string) => value ? true : 'Display name is required',
139
+ },
140
+ {
141
+ type: 'text',
142
+ name: 'description',
143
+ message: 'Description:',
144
+ validate: (value: string) => value ? true : 'Description is required',
145
+ },
146
+ {
147
+ type: 'text',
148
+ name: 'icon',
149
+ message: 'Lucide icon name (e.g., "Mail", "CreditCard"):',
150
+ initial: 'Package',
151
+ },
152
+ {
153
+ type: 'select',
154
+ name: 'category',
155
+ message: 'Category:',
156
+ choices: EXTENSION_CATEGORIES,
157
+ },
158
+ ]);
159
+
160
+ if (!response.name) {
161
+ consola.info('Extension creation cancelled');
162
+ return;
163
+ }
164
+
165
+ const extName = `ext-${response.name}`;
166
+ const extDir = join(process.cwd(), 'extensions', extName);
167
+
168
+ // Check if extension already exists
169
+ if (existsSync(extDir)) {
170
+ consola.error(`Extension already exists: ${extDir}`);
171
+ return;
172
+ }
173
+
174
+ console.log();
175
+ consola.start(`Creating extension: ${chalk.cyan(`@djangocfg/${extName}`)}`);
176
+
177
+ try {
178
+ const providerName = response.displayName.replace(/[^a-zA-Z]/g, '');
179
+
180
+ // Prepare replacements
181
+ const replacements = {
182
+ '__NAME__': response.name,
183
+ '__DISPLAY_NAME__': response.displayName,
184
+ '__DESCRIPTION__': response.description,
185
+ '__ICON__': response.icon,
186
+ '__CATEGORY__': response.category,
187
+ '__PROVIDER_NAME__': providerName,
188
+ };
189
+
190
+ // Find template directory
191
+ const templatePaths = [
192
+ // When installed from npm
193
+ join(__dirname, '../templates/extension-template'),
194
+ // Workspace path (for development)
195
+ join(process.cwd(), 'extensions', 'ext-base', 'templates', 'extension-template'),
196
+ ];
197
+
198
+ let templateDir: string | null = null;
199
+ for (const path of templatePaths) {
200
+ if (existsSync(path)) {
201
+ templateDir = path;
202
+ break;
203
+ }
204
+ }
205
+
206
+ if (!templateDir) {
207
+ throw new Error('Extension template not found');
208
+ }
209
+
210
+ // Copy template with replacements
211
+ consola.start('Copying template files...');
212
+ copyTemplateRecursive(templateDir, extDir, replacements);
213
+ consola.success('Extension files created');
214
+
215
+ console.log();
216
+ consola.success(`Extension created successfully: ${chalk.cyan(extDir)}`);
217
+ console.log();
218
+
219
+ console.log(chalk.yellow.bold('Next steps:'));
220
+ console.log();
221
+ console.log(chalk.gray('1. Install dependencies:'));
222
+ console.log(chalk.cyan(` cd ${extDir} && pnpm install`));
223
+ console.log();
224
+ console.log(chalk.gray('2. Build the extension:'));
225
+ console.log(chalk.cyan(` pnpm build`));
226
+ console.log();
227
+ console.log(chalk.gray('3. Add your features and customize:'));
228
+ console.log(chalk.cyan(` - Edit src/config.ts to add features`));
229
+ console.log(chalk.cyan(` - Add components, hooks, and utilities`));
230
+ console.log(chalk.cyan(` - Update README.md with usage examples`));
231
+ console.log();
232
+
233
+ consola.info('Documentation: https://djangocfg.com/docs');
234
+ } catch (error) {
235
+ consola.error('Failed to create extension:', error);
236
+ process.exit(1);
237
+ }
238
+ }
239
+
240
+ // Main CLI
241
+ async function main() {
242
+ const args = process.argv.slice(2);
243
+ const command = args[0] as Command;
244
+
245
+ // No command - show help
246
+ if (!command) {
247
+ printHelp();
248
+ return;
249
+ }
250
+
251
+ // Handle commands
252
+ switch (command) {
253
+ case 'create':
254
+ await createExtension();
255
+ break;
256
+
257
+ case 'help':
258
+ case '--help':
259
+ case '-h':
260
+ printHelp();
261
+ break;
262
+
263
+ default:
264
+ consola.error(`Unknown command: ${command}`);
265
+ console.log();
266
+ printHelp();
267
+ process.exit(1);
268
+ }
269
+ }
270
+
271
+ main().catch((error) => {
272
+ consola.error('CLI error:', error);
273
+ process.exit(1);
274
+ });
package/src/config.ts ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Extension configuration and environment utilities
3
+ */
4
+
5
+ export const isDevelopment = process.env.NODE_ENV === 'development';
6
+ export const isProduction = process.env.NODE_ENV === 'production';
7
+ export const isStaticBuild = process.env.STATIC_BUILD === 'true';
8
+
9
+ /**
10
+ * Get API URL from environment or default
11
+ */
12
+ export function getApiUrl(): string {
13
+ return process.env.NEXT_PUBLIC_API_URL || '/api';
14
+ }
15
+
16
+ // Re-export metadata
17
+ export * from './metadata';
package/src/index.ts CHANGED
@@ -5,8 +5,9 @@
5
5
  * Server-safe entry point - can be used in both server and client components.
6
6
  */
7
7
 
8
- // Types
8
+ // Types & Constants
9
9
  export type * from './types';
10
+ export { EXTENSION_CATEGORIES } from './types/context';
10
11
 
11
12
  // Config
12
13
  export * from './config';
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Extension Base configuration
3
+ */
4
+
5
+ import type { ExtensionMetadata } from './types';
6
+ import packageJson from '../package.json';
7
+
8
+ export const extensionConfig: ExtensionMetadata = {
9
+ name: 'base',
10
+ version: packageJson.version,
11
+ author: 'DjangoCFG Team',
12
+ displayName: 'Extension Base',
13
+ description: 'Base utilities and common code for building DjangoCFG extensions. Includes CLI tool for managing extensions.',
14
+ icon: 'Wrench',
15
+ license: 'MIT',
16
+ githubUrl: 'https://github.com/markolofsen/django-cfg',
17
+ homepage: 'https://djangocfg.com',
18
+ keywords: ['base', 'utilities', 'cli', 'toolkit', 'helpers', 'hooks'],
19
+
20
+ // Marketplace metadata
21
+ category: 'utilities',
22
+ tags: ['base', 'utilities', 'cli', 'toolkit'],
23
+ features: [
24
+ 'Extension registration system',
25
+ 'React hooks for pagination and infinite scroll',
26
+ 'Environment utilities and helpers',
27
+ 'CLI tool for extension management',
28
+ 'Type-safe context helpers',
29
+ 'Logger utilities',
30
+ ],
31
+ npmUrl: `https://www.npmjs.com/package/${packageJson.name}`,
32
+ installCommand: `pnpm add ${packageJson.name}`,
33
+ peerDependencies: packageJson.peerDependencies,
34
+ examples: [
35
+ {
36
+ title: 'Basic Extension Setup',
37
+ description: 'Initialize a new DjangoCFG extension',
38
+ code: `import { createExtension } from '@djangocfg/ext-base';
39
+
40
+ const myExtension = createExtension({
41
+ name: 'my-extension',
42
+ version: '1.0.0',
43
+ register: (app) => {
44
+ console.log('Extension registered!');
45
+ },
46
+ });
47
+
48
+ export default myExtension;`,
49
+ language: 'typescript',
50
+ },
51
+ {
52
+ title: 'Using Pagination Hook',
53
+ description: 'Implement infinite scroll with usePagination',
54
+ code: `import { usePagination } from '@djangocfg/ext-base';
55
+
56
+ function DataList() {
57
+ const { data, loading, hasMore, loadMore } = usePagination({
58
+ fetchFn: (page) => fetch(\`/api/data?page=\${page}\`),
59
+ pageSize: 20,
60
+ });
61
+
62
+ return (
63
+ <div>
64
+ {data.map(item => <div key={item.id}>{item.name}</div>)}
65
+ {hasMore && <button onClick={loadMore}>Load More</button>}
66
+ </div>
67
+ );
68
+ }`,
69
+ language: 'tsx',
70
+ },
71
+ ],
72
+ githubStars: 245,
73
+ };
@@ -10,6 +10,60 @@ export interface ExtensionContextOptions {
10
10
  revalidateIfStale?: boolean;
11
11
  }
12
12
 
13
+ /**
14
+ * Extension category types
15
+ */
16
+ export type ExtensionCategory =
17
+ | 'forms' // Forms, CRM, Lead Management
18
+ | 'payments' // Payment Processing, Billing
19
+ | 'content' // Content Management, Marketing
20
+ | 'support' // Support, Helpdesk, Tickets
21
+ | 'utilities' // Tools, Base, Infrastructure
22
+ | 'analytics' // Analytics, Tracking, Monitoring
23
+ | 'security' // Security, Authentication, Authorization
24
+ | 'integration' // Third-party Integrations
25
+ | 'other'; // Other/Uncategorized
26
+
27
+ /**
28
+ * Extension categories with display names for CLI and UI
29
+ */
30
+ export const EXTENSION_CATEGORIES: Array<{ title: string; value: ExtensionCategory }> = [
31
+ { title: 'Forms', value: 'forms' },
32
+ { title: 'Payments', value: 'payments' },
33
+ { title: 'Content', value: 'content' },
34
+ { title: 'Support', value: 'support' },
35
+ { title: 'Utilities', value: 'utilities' },
36
+ { title: 'Analytics', value: 'analytics' },
37
+ { title: 'Security', value: 'security' },
38
+ { title: 'Integration', value: 'integration' },
39
+ { title: 'Other', value: 'other' },
40
+ ];
41
+
42
+ /**
43
+ * Code example for extension documentation
44
+ */
45
+ export interface ExtensionExample {
46
+ /**
47
+ * Example title
48
+ */
49
+ title: string;
50
+
51
+ /**
52
+ * Example description
53
+ */
54
+ description?: string;
55
+
56
+ /**
57
+ * Code snippet
58
+ */
59
+ code: string;
60
+
61
+ /**
62
+ * Programming language for syntax highlighting
63
+ */
64
+ language: string;
65
+ }
66
+
13
67
  export interface ExtensionMetadata {
14
68
  /**
15
69
  * Unique extension name (e.g., 'newsletter', 'payments')
@@ -58,13 +112,13 @@ export interface ExtensionMetadata {
58
112
  keywords?: string[];
59
113
 
60
114
  /**
61
- * Extension icon URL or emoji
62
- * @example '📧' or 'https://example.com/icon.png'
115
+ * Extension icon - Lucide icon name or emoji
116
+ * @example 'Mail' or '📧'
63
117
  */
64
118
  icon?: string;
65
119
 
66
120
  /**
67
- * List of extension dependencies
121
+ * List of extension dependencies (other extension names)
68
122
  * @example ['payments', 'auth']
69
123
  */
70
124
  dependencies?: string[];
@@ -73,6 +127,73 @@ export interface ExtensionMetadata {
73
127
  * Minimum required DjangoCFG version
74
128
  */
75
129
  minVersion?: string;
130
+
131
+ // ─────────────────────────────────────────────────────────────────────────
132
+ // Marketplace-specific fields
133
+ // ─────────────────────────────────────────────────────────────────────────
134
+
135
+ /**
136
+ * Extension category for marketplace organization
137
+ */
138
+ category?: ExtensionCategory;
139
+
140
+ /**
141
+ * Tags for search and filtering
142
+ */
143
+ tags?: string[];
144
+
145
+ /**
146
+ * List of key features
147
+ */
148
+ features?: string[];
149
+
150
+ /**
151
+ * npm package URL
152
+ * @example 'https://www.npmjs.com/package/@djangocfg/ext-newsletter'
153
+ */
154
+ npmUrl?: string;
155
+
156
+ /**
157
+ * Installation command
158
+ * @example 'pnpm add @djangocfg/ext-newsletter'
159
+ */
160
+ installCommand?: string;
161
+
162
+ /**
163
+ * Code examples for documentation
164
+ */
165
+ examples?: ExtensionExample[];
166
+
167
+ /**
168
+ * npm package dependencies
169
+ */
170
+ packageDependencies?: Record<string, string>;
171
+
172
+ /**
173
+ * npm peer dependencies
174
+ */
175
+ peerDependencies?: Record<string, string>;
176
+
177
+ /**
178
+ * Related extension IDs
179
+ */
180
+ relatedExtensions?: string[];
181
+
182
+ /**
183
+ * README content in markdown
184
+ */
185
+ readme?: string;
186
+
187
+ /**
188
+ * Preview image URL (1200x630 recommended)
189
+ * @example 'https://unpkg.com/@djangocfg/ext-leads@latest/preview.svg'
190
+ */
191
+ preview?: string;
192
+
193
+ /**
194
+ * GitHub stars count
195
+ */
196
+ githubStars?: number;
76
197
  }
77
198
 
78
199
  export interface ExtensionProviderProps {
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Helper to create extension configuration from package.json
3
+ */
4
+
5
+ import type { ExtensionMetadata } from '../types';
6
+
7
+ /**
8
+ * Package.json structure
9
+ */
10
+ interface PackageJson {
11
+ name: string;
12
+ version: string;
13
+ description?: string;
14
+ keywords?: string[];
15
+ author?: string | { name: string; email?: string; url?: string };
16
+ license?: string;
17
+ homepage?: string;
18
+ repository?: string | { type: string; url: string; directory?: string };
19
+ peerDependencies?: Record<string, string>;
20
+ dependencies?: Record<string, string>;
21
+ }
22
+
23
+ /**
24
+ * Manual metadata fields that must be provided
25
+ */
26
+ export interface ExtensionConfigInput {
27
+ /**
28
+ * Extension short name (e.g., 'leads', 'payments')
29
+ */
30
+ name: string;
31
+
32
+ /**
33
+ * Display name for marketplace
34
+ */
35
+ displayName: string;
36
+
37
+ /**
38
+ * Lucide icon name
39
+ * @example 'FileText', 'CreditCard', 'Mail'
40
+ */
41
+ icon: string;
42
+
43
+ /**
44
+ * Extension category
45
+ */
46
+ category: ExtensionMetadata['category'];
47
+
48
+ /**
49
+ * Key features list
50
+ */
51
+ features: string[];
52
+
53
+ /**
54
+ * Code examples (optional)
55
+ */
56
+ examples?: ExtensionMetadata['examples'];
57
+
58
+ /**
59
+ * Minimum required DjangoCFG version (optional)
60
+ */
61
+ minVersion?: string;
62
+
63
+ /**
64
+ * Related extension IDs (optional)
65
+ */
66
+ relatedExtensions?: string[];
67
+
68
+ /**
69
+ * GitHub stars count (optional)
70
+ */
71
+ githubStars?: number;
72
+ }
73
+
74
+ /**
75
+ * Create extension configuration from package.json + manual metadata
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * import packageJson from '../package.json';
80
+ * import { createExtensionConfig } from '@djangocfg/ext-base';
81
+ *
82
+ * export const extensionConfig = createExtensionConfig(packageJson, {
83
+ * name: 'leads',
84
+ * displayName: 'Leads & Forms',
85
+ * icon: 'FileText',
86
+ * category: 'forms',
87
+ * features: [
88
+ * 'Contact form components',
89
+ * 'Lead tracking',
90
+ * ],
91
+ * });
92
+ * ```
93
+ */
94
+ export function createExtensionConfig(
95
+ packageJson: PackageJson,
96
+ config: ExtensionConfigInput
97
+ ): ExtensionMetadata {
98
+ // Extract author name
99
+ const author = typeof packageJson.author === 'string'
100
+ ? packageJson.author
101
+ : packageJson.author?.name || 'Unknown';
102
+
103
+ // Extract repository URL
104
+ const githubUrl = typeof packageJson.repository === 'string'
105
+ ? packageJson.repository
106
+ : packageJson.repository?.url;
107
+
108
+ return {
109
+ // From package.json
110
+ name: config.name,
111
+ version: packageJson.version,
112
+ author,
113
+ description: packageJson.description,
114
+ keywords: packageJson.keywords,
115
+ license: packageJson.license,
116
+ homepage: packageJson.homepage,
117
+ githubUrl,
118
+ peerDependencies: packageJson.peerDependencies,
119
+
120
+ // From manual config
121
+ displayName: config.displayName,
122
+ icon: config.icon,
123
+ category: config.category,
124
+ features: config.features,
125
+ examples: config.examples,
126
+ minVersion: config.minVersion,
127
+ relatedExtensions: config.relatedExtensions,
128
+ githubStars: config.githubStars,
129
+
130
+ // Auto-generated
131
+ npmUrl: `https://www.npmjs.com/package/${packageJson.name}`,
132
+ installCommand: `pnpm add ${packageJson.name}`,
133
+ tags: packageJson.keywords,
134
+ preview: `https://unpkg.com/${packageJson.name}@latest/preview.png`,
135
+
136
+ // Dependencies (empty by default, can be set via keywords)
137
+ dependencies: [],
138
+ };
139
+ }
@@ -8,3 +8,8 @@ export {
8
8
  formatErrorMessage,
9
9
  handleExtensionError,
10
10
  } from './errors';
11
+
12
+ export {
13
+ createExtensionConfig,
14
+ type ExtensionConfigInput,
15
+ } from './createExtensionConfig';