@djangocfg/ext-base 1.0.1 → 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.
package/README.md CHANGED
@@ -1,3 +1,9 @@
1
+ <div align="center">
2
+
3
+ ![DjangoCFG Extension Preview](https://unpkg.com/@djangocfg/ext-base@latest/preview.png)
4
+
5
+ </div>
6
+
1
7
  # @djangocfg/ext-base
2
8
 
3
9
  Base utilities and common code for building DjangoCFG extensions.
@@ -25,50 +31,61 @@ pnpm add @djangocfg/ext-base
25
31
 
26
32
  ## CLI Usage
27
33
 
28
- The package includes a CLI tool for managing DjangoCFG extensions:
34
+ The package includes a CLI tool for creating new DjangoCFG extensions:
29
35
 
30
36
  ```bash
31
- # List available extensions
32
- djangocfg-ext list
33
-
34
- # Show extension info
35
- djangocfg-ext info ext-leads
36
-
37
- # Interactive installation wizard
38
- djangocfg-ext install
39
-
40
- # Initialize extension in your project
41
- djangocfg-ext init
37
+ # Create a new extension with interactive wizard
38
+ djangocfg-ext create
42
39
 
43
40
  # Show help
44
41
  djangocfg-ext help
45
42
  ```
46
43
 
47
- ### Available Extensions
48
-
49
- - **@djangocfg/ext-leads** - Lead management and contact forms
50
- - **@djangocfg/ext-payments** - Payment processing with multiple providers
51
- - **@djangocfg/ext-newsletter** - Newsletter and email campaigns
52
- - **@djangocfg/ext-support** - Customer support and ticketing
53
- - **@djangocfg/ext-knowbase** - Knowledge base and documentation
44
+ The CLI will guide you through creating a new extension with proper structure and configuration using the `createExtensionConfig` helper.
54
45
 
55
46
  ## Quick Start
56
47
 
57
48
  ### 1. Create extension metadata
58
49
 
50
+ Use the `createExtensionConfig` helper to automatically pull data from package.json:
51
+
59
52
  ```typescript
60
53
  // src/config.ts
61
- import type { ExtensionMetadata } from '@djangocfg/ext-base';
54
+ import { createExtensionConfig } from '@djangocfg/ext-base';
55
+ import packageJson from '../package.json';
62
56
 
63
- export const extensionConfig: ExtensionMetadata = {
57
+ export const extensionConfig = createExtensionConfig(packageJson, {
64
58
  name: 'my-extension',
65
- version: '1.0.0',
66
59
  displayName: 'My Extension',
67
- description: 'Amazing extension functionality',
68
- icon: '🚀',
69
- };
60
+ icon: 'Rocket', // Lucide icon name
61
+ category: 'utilities',
62
+ features: [
63
+ 'Feature 1',
64
+ 'Feature 2',
65
+ 'Feature 3',
66
+ ],
67
+ minVersion: '2.0.0',
68
+ examples: [
69
+ {
70
+ title: 'Basic Usage',
71
+ description: 'How to use this extension',
72
+ code: `import { MyComponent } from '@your-org/my-extension';`,
73
+ language: 'tsx',
74
+ },
75
+ ],
76
+ });
70
77
  ```
71
78
 
79
+ This automatically imports from package.json:
80
+ - version
81
+ - author
82
+ - description
83
+ - license
84
+ - homepage
85
+ - githubUrl (from repository)
86
+ - keywords
87
+ - peerDependencies
88
+
72
89
  ### 2. Create extension provider
73
90
 
74
91
  ```typescript
@@ -103,6 +120,52 @@ export default function RootLayout({ children }) {
103
120
 
104
121
  ## Core Features
105
122
 
123
+ ### Extension Config Helper
124
+
125
+ The `createExtensionConfig` helper creates a typed extension configuration by combining package.json data with manual metadata:
126
+
127
+ ```typescript
128
+ import { createExtensionConfig, type ExtensionConfigInput } from '@djangocfg/ext-base';
129
+ import packageJson from '../package.json';
130
+
131
+ export const extensionConfig = createExtensionConfig(packageJson, {
132
+ // Required fields
133
+ name: 'my-extension',
134
+ displayName: 'My Extension',
135
+ icon: 'Package', // Lucide icon name
136
+ category: 'utilities', // 'forms' | 'payments' | 'content' | 'support' | 'utilities' | 'analytics' | 'security' | 'integration' | 'other'
137
+ features: ['Feature list for marketplace'],
138
+
139
+ // Optional fields
140
+ minVersion: '2.0.0',
141
+ githubStars: 100,
142
+ relatedExtensions: ['other-extension'],
143
+ examples: [
144
+ {
145
+ title: 'Example title',
146
+ description: 'Example description',
147
+ code: 'import { Component } from "@your-org/my-extension";',
148
+ language: 'tsx',
149
+ },
150
+ ],
151
+ });
152
+ ```
153
+
154
+ **Automatically imported from package.json:**
155
+ - `version` - Package version
156
+ - `author` - Author name (from string or object)
157
+ - `description` - Package description
158
+ - `license` - License type
159
+ - `homepage` - Homepage URL
160
+ - `githubUrl` - Repository URL
161
+ - `keywords` - Keywords array
162
+ - `peerDependencies` - Peer dependencies
163
+
164
+ **Auto-generated:**
165
+ - `npmUrl` - npm package URL
166
+ - `installCommand` - pnpm install command
167
+ - `tags` - Same as keywords
168
+
106
169
  ### Environment Configuration
107
170
 
108
171
  ```typescript
@@ -205,12 +268,22 @@ function MyComponent() {
205
268
 
206
269
  ```typescript
207
270
  import type {
271
+ // Extension configuration
208
272
  ExtensionMetadata,
273
+ ExtensionConfigInput,
274
+ ExtensionCategory,
275
+ ExtensionExample,
276
+
277
+ // Provider
209
278
  ExtensionProviderProps,
279
+
280
+ // Pagination
210
281
  PaginatedResponse,
211
282
  PaginationParams,
212
283
  PaginationState,
213
284
  InfinitePaginationReturn,
285
+
286
+ // Utilities
214
287
  ExtensionLogger,
215
288
  ExtensionError,
216
289
  } from '@djangocfg/ext-base';
@@ -218,8 +291,11 @@ import type {
218
291
 
219
292
  ## Best Practices
220
293
 
294
+ - Use `createExtensionConfig` helper to maintain Single Source of Truth from package.json
221
295
  - Always wrap your extension with `ExtensionProvider` for proper registration
222
- - Define complete metadata in `config.ts`
296
+ - Use Lucide icon names (not emoji) for `icon` field
297
+ - Include comprehensive `features` list for marketplace visibility
298
+ - Provide code `examples` with proper syntax highlighting
223
299
  - Use provided pagination hooks for consistent data fetching
224
300
  - Use `createExtensionLogger` with consistent tags for structured logging
225
301
  - Separate client-only code using `/hooks` entry point
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ext-base",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Base utilities and common code for DjangoCFG extensions",
5
5
  "keywords": [
6
6
  "django",
@@ -27,33 +27,35 @@
27
27
  "license": "MIT",
28
28
  "type": "module",
29
29
  "main": "./dist/index.cjs",
30
- "module": "./dist/index.mjs",
30
+ "module": "./dist/index.js",
31
31
  "types": "./dist/index.d.ts",
32
32
  "exports": {
33
33
  ".": {
34
34
  "types": "./dist/index.d.ts",
35
- "import": "./dist/index.mjs",
35
+ "import": "./dist/index.js",
36
36
  "require": "./dist/index.cjs"
37
37
  },
38
38
  "./hooks": {
39
39
  "types": "./dist/hooks.d.ts",
40
- "import": "./dist/hooks.mjs",
40
+ "import": "./dist/hooks.js",
41
41
  "require": "./dist/hooks.cjs"
42
42
  },
43
43
  "./auth": {
44
44
  "types": "./dist/auth.d.ts",
45
- "import": "./dist/auth.mjs",
45
+ "import": "./dist/auth.js",
46
46
  "require": "./dist/auth.cjs"
47
47
  },
48
48
  "./api": {
49
49
  "types": "./dist/api.d.ts",
50
- "import": "./dist/api.mjs",
50
+ "import": "./dist/api.js",
51
51
  "require": "./dist/api.cjs"
52
52
  }
53
53
  },
54
54
  "files": [
55
55
  "dist",
56
- "src"
56
+ "src",
57
+ "preview.png",
58
+ "templates"
57
59
  ],
58
60
  "bin": {
59
61
  "djangocfg-ext": "./dist/cli.mjs"
package/preview.png ADDED
Binary file
package/src/cli/index.ts CHANGED
@@ -7,9 +7,10 @@
7
7
  import { consola } from 'consola';
8
8
  import chalk from 'chalk';
9
9
  import prompts from 'prompts';
10
- import { readFileSync } from 'fs';
10
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from 'fs';
11
11
  import { join, dirname } from 'path';
12
12
  import { fileURLToPath } from 'url';
13
+ import { EXTENSION_CATEGORIES } from '../types/context';
13
14
 
14
15
  const __dirname = dirname(fileURLToPath(import.meta.url));
15
16
 
@@ -26,43 +27,62 @@ function getVersion(): string {
26
27
 
27
28
  // CLI Commands
28
29
  const COMMANDS = {
29
- install: 'Install an extension',
30
- list: 'List available extensions',
31
- info: 'Show extension info',
32
- init: 'Initialize extension in your project',
30
+ create: 'Create a new DjangoCFG extension',
33
31
  help: 'Show help',
32
+ '--help': 'Show help',
33
+ '-h': 'Show help',
34
34
  } as const;
35
35
 
36
36
  type Command = keyof typeof COMMANDS;
37
37
 
38
- // Available extensions
39
- const EXTENSIONS = {
40
- 'ext-leads': {
41
- name: '@djangocfg/ext-leads',
42
- description: 'Lead management and contact forms',
43
- features: ['Contact forms', 'Lead tracking', 'Email integration'],
44
- },
45
- 'ext-payments': {
46
- name: '@djangocfg/ext-payments',
47
- description: 'Payment processing with multiple providers',
48
- features: ['NowPayments', 'Crypto payments', 'Payment tracking'],
49
- },
50
- 'ext-newsletter': {
51
- name: '@djangocfg/ext-newsletter',
52
- description: 'Newsletter and email campaigns',
53
- features: ['Subscription management', 'Email templates', 'Campaign tracking'],
54
- },
55
- 'ext-support': {
56
- name: '@djangocfg/ext-support',
57
- description: 'Customer support and ticketing',
58
- features: ['Ticket system', 'Chat support', 'Knowledge base'],
59
- },
60
- 'ext-knowbase': {
61
- name: '@djangocfg/ext-knowbase',
62
- description: 'Knowledge base and documentation',
63
- features: ['Articles', 'Categories', 'Search', 'Markdown support'],
64
- },
65
- } as const;
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
+ }
66
86
 
67
87
  // Print banner
68
88
  function printBanner() {
@@ -87,139 +107,134 @@ function printHelp() {
87
107
  console.log();
88
108
 
89
109
  console.log(chalk.yellow.bold('Examples:'));
90
- console.log(` ${chalk.gray('$')} ${chalk.cyan('djangocfg-ext install')}`);
91
- console.log(` ${chalk.gray('$')} ${chalk.cyan('djangocfg-ext list')}`);
92
- console.log(` ${chalk.gray('$')} ${chalk.cyan('djangocfg-ext info ext-leads')}`);
110
+ console.log(` ${chalk.gray('$')} ${chalk.cyan('djangocfg-ext create')}`);
93
111
  console.log();
94
112
  }
95
113
 
96
- // List extensions
97
- function listExtensions() {
114
+ // Create new extension
115
+ async function createExtension() {
98
116
  printBanner();
99
- console.log(chalk.yellow.bold('Available Extensions:'));
100
- console.log();
101
117
 
102
- Object.entries(EXTENSIONS).forEach(([key, ext]) => {
103
- console.log(chalk.cyan.bold(` 📦 ${ext.name}`));
104
- console.log(chalk.gray(` ${ext.description}`));
105
- console.log(chalk.gray(` Features: ${ext.features.join(', ')}`));
106
- console.log();
107
- });
108
- }
118
+ console.log(chalk.yellow('Create a new DjangoCFG extension'));
119
+ console.log();
109
120
 
110
- // Show extension info
111
- function showInfo(extensionKey: string) {
112
- const ext = EXTENSIONS[extensionKey as keyof typeof EXTENSIONS];
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
+ ]);
113
159
 
114
- if (!ext) {
115
- consola.error(`Extension not found: ${extensionKey}`);
116
- consola.info('Run `djangocfg-ext list` to see available extensions');
160
+ if (!response.name) {
161
+ consola.info('Extension creation cancelled');
117
162
  return;
118
163
  }
119
164
 
120
- printBanner();
121
- console.log(chalk.cyan.bold(`📦 ${ext.name}`));
122
- console.log();
123
- console.log(chalk.white(ext.description));
124
- console.log();
125
- console.log(chalk.yellow.bold('Features:'));
126
- ext.features.forEach(feature => {
127
- console.log(chalk.gray(` • ${feature}`));
128
- });
129
- console.log();
130
- console.log(chalk.yellow.bold('Installation:'));
131
- console.log(chalk.gray(` pnpm add ${ext.name}`));
132
- console.log();
133
- }
134
-
135
- // Install extension
136
- async function installExtension() {
137
- printBanner();
138
-
139
- const response = await prompts({
140
- type: 'select',
141
- name: 'extension',
142
- message: 'Which extension would you like to install?',
143
- choices: Object.entries(EXTENSIONS).map(([key, ext]) => ({
144
- title: ext.name,
145
- description: ext.description,
146
- value: key,
147
- })),
148
- });
165
+ const extName = `ext-${response.name}`;
166
+ const extDir = join(process.cwd(), 'extensions', extName);
149
167
 
150
- if (!response.extension) {
151
- consola.info('Installation cancelled');
168
+ // Check if extension already exists
169
+ if (existsSync(extDir)) {
170
+ consola.error(`Extension already exists: ${extDir}`);
152
171
  return;
153
172
  }
154
173
 
155
- const ext = EXTENSIONS[response.extension as keyof typeof EXTENSIONS];
156
-
157
- console.log();
158
- consola.info(`Installing ${chalk.cyan(ext.name)}...`);
159
- console.log();
160
-
161
- // Show installation command
162
- console.log(chalk.yellow.bold('Run this command:'));
163
- console.log(chalk.cyan(` pnpm add ${ext.name}`));
164
174
  console.log();
175
+ consola.start(`Creating extension: ${chalk.cyan(`@djangocfg/${extName}`)}`);
165
176
 
166
- console.log(chalk.yellow.bold('Then import in your app:'));
167
- console.log(chalk.gray(` import { ExtensionProvider } from '${ext.name}';`));
168
- console.log();
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
+ }
169
205
 
170
- consola.success('See documentation at https://djangocfg.com/docs');
171
- }
206
+ if (!templateDir) {
207
+ throw new Error('Extension template not found');
208
+ }
172
209
 
173
- // Init extension in project
174
- async function initExtension() {
175
- printBanner();
210
+ // Copy template with replacements
211
+ consola.start('Copying template files...');
212
+ copyTemplateRecursive(templateDir, extDir, replacements);
213
+ consola.success('Extension files created');
176
214
 
177
- console.log(chalk.yellow('This will create extension configuration in your project'));
178
- console.log();
215
+ console.log();
216
+ consola.success(`Extension created successfully: ${chalk.cyan(extDir)}`);
217
+ console.log();
179
218
 
180
- const response = await prompts([
181
- {
182
- type: 'select',
183
- name: 'extension',
184
- message: 'Which extension do you want to configure?',
185
- choices: Object.entries(EXTENSIONS).map(([key, ext]) => ({
186
- title: ext.name,
187
- description: ext.description,
188
- value: key,
189
- })),
190
- },
191
- {
192
- type: 'confirm',
193
- name: 'confirm',
194
- message: 'Generate extension configuration?',
195
- initial: true,
196
- },
197
- ]);
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();
198
232
 
199
- if (!response.confirm) {
200
- consola.info('Initialization cancelled');
201
- return;
233
+ consola.info('Documentation: https://djangocfg.com/docs');
234
+ } catch (error) {
235
+ consola.error('Failed to create extension:', error);
236
+ process.exit(1);
202
237
  }
203
-
204
- const ext = EXTENSIONS[response.extension as keyof typeof EXTENSIONS];
205
-
206
- console.log();
207
- consola.info(`Initializing ${chalk.cyan(ext.name)}...`);
208
- console.log();
209
-
210
- // Show what to do next
211
- console.log(chalk.yellow.bold('Next steps:'));
212
- console.log(chalk.gray(' 1. Install the extension: ') + chalk.cyan(`pnpm add ${ext.name}`));
213
- console.log(chalk.gray(' 2. Add provider to your layout:'));
214
- console.log();
215
- console.log(chalk.gray(' ') + chalk.white('import { ExtensionProvider } from \'' + ext.name + '\';'));
216
- console.log();
217
- console.log(chalk.gray(' ') + chalk.white('<ExtensionProvider>'));
218
- console.log(chalk.gray(' ') + chalk.white('{children}'));
219
- console.log(chalk.gray(' ') + chalk.white('</ExtensionProvider>'));
220
- console.log();
221
-
222
- consola.success('Initialization complete!');
223
238
  }
224
239
 
225
240
  // Main CLI
@@ -235,26 +250,8 @@ async function main() {
235
250
 
236
251
  // Handle commands
237
252
  switch (command) {
238
- case 'list':
239
- listExtensions();
240
- break;
241
-
242
- case 'info':
243
- const extKey = args[1];
244
- if (!extKey) {
245
- consola.error('Please specify an extension');
246
- consola.info('Example: djangocfg-ext info ext-leads');
247
- return;
248
- }
249
- showInfo(extKey);
250
- break;
251
-
252
- case 'install':
253
- await installExtension();
254
- break;
255
-
256
- case 'init':
257
- await initExtension();
253
+ case 'create':
254
+ await createExtension();
258
255
  break;
259
256
 
260
257
  case 'help':
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';