@djangocfg/ext-base 1.0.2 → 1.0.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/README.md +186 -7
- package/dist/api.cjs +40 -0
- package/dist/api.d.cts +35 -0
- package/dist/api.d.ts +35 -0
- package/dist/api.js +2 -0
- package/dist/auth.cjs +10 -0
- package/dist/auth.d.cts +1 -0
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +2 -0
- package/dist/chunk-3RG5ZIWI.js +8 -0
- package/dist/chunk-UXTBBEO5.js +237 -0
- package/dist/chunk-VJEGYVBV.js +140 -0
- package/dist/cli.mjs +530 -0
- package/dist/hooks.cjs +437 -0
- package/dist/hooks.d.cts +97 -0
- package/dist/hooks.d.ts +97 -0
- package/dist/hooks.js +95 -0
- package/dist/index.cjs +345 -0
- package/dist/index.d.cts +363 -0
- package/dist/index.d.ts +363 -0
- package/dist/index.js +3 -0
- package/package.json +5 -2
- package/src/cli/index.ts +470 -35
- package/src/context/ExtensionProvider.tsx +67 -4
- package/src/extensionConfig.ts +114 -0
- package/src/index.ts +3 -0
- package/src/metadata.ts +1 -2
- package/src/types/context.ts +21 -15
- package/src/utils/createExtensionConfig.ts +34 -18
- package/templates/extension-template/README.md.template +37 -5
- package/templates/extension-template/package.json.template +13 -5
- package/templates/extension-template/playground/.gitignore.template +34 -0
- package/templates/extension-template/playground/CLAUDE.md +35 -0
- package/templates/extension-template/playground/README.md.template +76 -0
- package/templates/extension-template/playground/app/globals.css.template +19 -0
- package/templates/extension-template/playground/app/layout.tsx.template +30 -0
- package/templates/extension-template/playground/app/page.tsx.template +44 -0
- package/templates/extension-template/playground/next.config.ts.template +62 -0
- package/templates/extension-template/playground/package.json.template +33 -0
- package/templates/extension-template/playground/tsconfig.json.template +27 -0
- package/templates/extension-template/src/config.ts +1 -2
- package/templates/extension-template/src/contexts/__PROVIDER_NAME__Context.tsx +1 -1
- package/templates/extension-template/src/contexts/__PROVIDER_NAME__ExtensionProvider.tsx +1 -0
- package/templates/extension-template/src/hooks/index.ts +1 -1
- package/templates/extension-template/src/index.ts +12 -4
- package/templates/extension-template/src/utils/withSmartProvider.tsx +70 -0
- package/templates/extension-template/tsup.config.ts +36 -22
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { EXTENSION_CATEGORIES, createExtensionConfig, createExtensionError, createExtensionLogger, extensionConfig, formatErrorMessage, handleExtensionError, isExtensionError } from './chunk-UXTBBEO5.js';
|
|
2
|
+
export { createExtensionAPI, getApiUrl, getSharedAuthStorage, isDevelopment, isProduction, isStaticBuild } from './chunk-VJEGYVBV.js';
|
|
3
|
+
import './chunk-3RG5ZIWI.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ext-base",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Base utilities and common code for DjangoCFG extensions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"django",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"name": "DjangoCFG",
|
|
16
16
|
"url": "https://djangocfg.com"
|
|
17
17
|
},
|
|
18
|
-
"homepage": "https://djangocfg.com",
|
|
18
|
+
"homepage": "https://hub.djangocfg.com/extensions/djangocfg-ext-base",
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
21
21
|
"url": "https://github.com/markolofsen/django-cfg.git",
|
|
@@ -64,8 +64,11 @@
|
|
|
64
64
|
"build": "tsup",
|
|
65
65
|
"dev": "tsup --watch",
|
|
66
66
|
"check": "tsc --noEmit",
|
|
67
|
+
"test": "tsx src/cli/index.ts test",
|
|
67
68
|
"cli": "tsx src/cli/index.ts",
|
|
68
69
|
"cli:help": "tsx src/cli/index.ts --help",
|
|
70
|
+
"cli:create": "tsx src/cli/index.ts create",
|
|
71
|
+
"cli:test": "tsx src/cli/index.ts test",
|
|
69
72
|
"cli:list": "tsx src/cli/index.ts list",
|
|
70
73
|
"cli:info": "tsx src/cli/index.ts info"
|
|
71
74
|
},
|
package/src/cli/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import prompts from 'prompts';
|
|
|
10
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 { execSync } from 'child_process';
|
|
13
14
|
import { EXTENSION_CATEGORIES } from '../types/context';
|
|
14
15
|
|
|
15
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -28,6 +29,7 @@ function getVersion(): string {
|
|
|
28
29
|
// CLI Commands
|
|
29
30
|
const COMMANDS = {
|
|
30
31
|
create: 'Create a new DjangoCFG extension',
|
|
32
|
+
test: 'Quick create test extension (no prompts)',
|
|
31
33
|
help: 'Show help',
|
|
32
34
|
'--help': 'Show help',
|
|
33
35
|
'-h': 'Show help',
|
|
@@ -92,6 +94,80 @@ function printBanner() {
|
|
|
92
94
|
console.log();
|
|
93
95
|
}
|
|
94
96
|
|
|
97
|
+
// Check if package exists on npm
|
|
98
|
+
async function checkPackageExists(packageName: string): Promise<boolean> {
|
|
99
|
+
try {
|
|
100
|
+
const encodedName = encodeURIComponent(packageName);
|
|
101
|
+
const response = await fetch(`https://registry.npmjs.org/${encodedName}`);
|
|
102
|
+
return response.status === 200;
|
|
103
|
+
} catch {
|
|
104
|
+
// Network error or other issue - allow creation
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check if extension directory already exists (case-insensitive)
|
|
110
|
+
function checkDirectoryExists(packageName: string): { exists: boolean; path?: string } {
|
|
111
|
+
// Generate directory name from package name
|
|
112
|
+
const extDirName = packageName.replace('@', '').replace('/', '-');
|
|
113
|
+
|
|
114
|
+
// Determine base directory
|
|
115
|
+
const isInExtBase = process.cwd().endsWith('ext-base');
|
|
116
|
+
const baseDir = isInExtBase
|
|
117
|
+
? join(process.cwd(), '..')
|
|
118
|
+
: join(process.cwd(), 'extensions');
|
|
119
|
+
|
|
120
|
+
const extDir = join(baseDir, extDirName);
|
|
121
|
+
|
|
122
|
+
// Check exact match first
|
|
123
|
+
if (existsSync(extDir)) {
|
|
124
|
+
return { exists: true, path: extDir };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check case-insensitive match (important for macOS)
|
|
128
|
+
try {
|
|
129
|
+
const files = readdirSync(baseDir);
|
|
130
|
+
const lowerCaseName = extDirName.toLowerCase();
|
|
131
|
+
|
|
132
|
+
for (const file of files) {
|
|
133
|
+
if (file.toLowerCase() === lowerCaseName) {
|
|
134
|
+
const fullPath = join(baseDir, file);
|
|
135
|
+
const stats = statSync(fullPath);
|
|
136
|
+
if (stats.isDirectory()) {
|
|
137
|
+
return { exists: true, path: fullPath };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
// If we can't read directory, assume it doesn't exist
|
|
143
|
+
return { exists: false };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { exists: false };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Detect available package manager
|
|
150
|
+
function detectPackageManager(): string | null {
|
|
151
|
+
const managers = ['pnpm', 'yarn', 'npm'];
|
|
152
|
+
|
|
153
|
+
for (const manager of managers) {
|
|
154
|
+
try {
|
|
155
|
+
const result = execSync(`${manager} --version`, {
|
|
156
|
+
encoding: 'utf-8',
|
|
157
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
158
|
+
});
|
|
159
|
+
if (result) {
|
|
160
|
+
return manager;
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
// Manager not available, try next
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
95
171
|
// Print help
|
|
96
172
|
function printHelp() {
|
|
97
173
|
printBanner();
|
|
@@ -118,23 +194,65 @@ async function createExtension() {
|
|
|
118
194
|
console.log(chalk.yellow('Create a new DjangoCFG extension'));
|
|
119
195
|
console.log();
|
|
120
196
|
|
|
121
|
-
|
|
122
|
-
|
|
197
|
+
// Step 1: Get and validate package name with npm check
|
|
198
|
+
let packageName = '';
|
|
199
|
+
let packageNameValid = false;
|
|
200
|
+
|
|
201
|
+
while (!packageNameValid) {
|
|
202
|
+
const nameResponse = await prompts({
|
|
123
203
|
type: 'text',
|
|
124
|
-
name: '
|
|
125
|
-
message: '
|
|
204
|
+
name: 'packageName',
|
|
205
|
+
message: 'Package name (e.g., "@my-org/my-extension" or "my-extension"):',
|
|
126
206
|
validate: (value: string) => {
|
|
127
|
-
if (!value) return '
|
|
128
|
-
|
|
129
|
-
|
|
207
|
+
if (!value) return 'Package name is required';
|
|
208
|
+
const normalized = value.toLowerCase();
|
|
209
|
+
// Allow @scope/name or just name
|
|
210
|
+
if (!/^(@[a-zA-Z0-9-]+\/)?[a-zA-Z0-9][a-zA-Z0-9-]*$/.test(normalized)) {
|
|
211
|
+
return 'Invalid package name format. Use @scope/name or name';
|
|
130
212
|
}
|
|
131
213
|
return true;
|
|
132
214
|
},
|
|
133
|
-
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
if (!nameResponse.packageName) {
|
|
218
|
+
consola.info('Extension creation cancelled');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Always normalize to lowercase (npm requirement)
|
|
223
|
+
packageName = nameResponse.packageName.toLowerCase();
|
|
224
|
+
|
|
225
|
+
// Check if extension directory already exists (case-insensitive)
|
|
226
|
+
const dirCheck = checkDirectoryExists(packageName);
|
|
227
|
+
if (dirCheck.exists) {
|
|
228
|
+
consola.error(`Extension directory already exists: ${chalk.red(dirCheck.path)}`);
|
|
229
|
+
console.log(chalk.gray('Please try a different name.'));
|
|
230
|
+
console.log();
|
|
231
|
+
continue; // Ask again
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Check if package already exists on npm
|
|
235
|
+
consola.start(`Checking if ${chalk.cyan(packageName)} is available on npm...`);
|
|
236
|
+
const packageExists = await checkPackageExists(packageName);
|
|
237
|
+
|
|
238
|
+
if (packageExists) {
|
|
239
|
+
consola.error(`Package ${chalk.red(packageName)} already exists on npm!`);
|
|
240
|
+
console.log(chalk.gray('Please try a different name.'));
|
|
241
|
+
console.log();
|
|
242
|
+
continue; // Ask again
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
consola.success('Package name is available!');
|
|
246
|
+
console.log();
|
|
247
|
+
packageNameValid = true;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Step 2: Get remaining details
|
|
251
|
+
const response = await prompts([
|
|
134
252
|
{
|
|
135
253
|
type: 'text',
|
|
136
254
|
name: 'displayName',
|
|
137
|
-
message: 'Display name (e.g., "
|
|
255
|
+
message: 'Display name (e.g., "My Extension"):',
|
|
138
256
|
validate: (value: string) => value ? true : 'Display name is required',
|
|
139
257
|
},
|
|
140
258
|
{
|
|
@@ -143,12 +261,6 @@ async function createExtension() {
|
|
|
143
261
|
message: 'Description:',
|
|
144
262
|
validate: (value: string) => value ? true : 'Description is required',
|
|
145
263
|
},
|
|
146
|
-
{
|
|
147
|
-
type: 'text',
|
|
148
|
-
name: 'icon',
|
|
149
|
-
message: 'Lucide icon name (e.g., "Mail", "CreditCard"):',
|
|
150
|
-
initial: 'Package',
|
|
151
|
-
},
|
|
152
264
|
{
|
|
153
265
|
type: 'select',
|
|
154
266
|
name: 'category',
|
|
@@ -157,42 +269,81 @@ async function createExtension() {
|
|
|
157
269
|
},
|
|
158
270
|
]);
|
|
159
271
|
|
|
160
|
-
if (!response.
|
|
272
|
+
if (!response.displayName) {
|
|
161
273
|
consola.info('Extension creation cancelled');
|
|
162
274
|
return;
|
|
163
275
|
}
|
|
164
276
|
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
277
|
+
// Create response with all fields
|
|
278
|
+
const fullResponse = {
|
|
279
|
+
...response,
|
|
280
|
+
packageName,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// Check if we're in djangocfg monorepo (early detection for directory naming)
|
|
284
|
+
const isInDjangoCfgMonorepo = existsSync(join(process.cwd(), '../../pnpm-workspace.yaml')) &&
|
|
285
|
+
existsSync(join(process.cwd(), '../../packages/api/package.json'));
|
|
286
|
+
|
|
287
|
+
// Generate directory name from package name
|
|
288
|
+
// In djangocfg monorepo: @djangocfg/demo123a -> demo123a
|
|
289
|
+
// Outside monorepo: @my-org/my-extension -> my-org-my-extension
|
|
290
|
+
let extDirName: string;
|
|
291
|
+
if (isInDjangoCfgMonorepo && fullResponse.packageName.startsWith('@djangocfg/')) {
|
|
292
|
+
// In djangocfg monorepo, just use the part after the slash
|
|
293
|
+
extDirName = fullResponse.packageName.split('/')[1];
|
|
294
|
+
} else {
|
|
295
|
+
// Outside monorepo or different scope, replace @ and / with -
|
|
296
|
+
extDirName = fullResponse.packageName.replace('@', '').replace('/', '-');
|
|
172
297
|
}
|
|
173
298
|
|
|
299
|
+
// Create extension in parent directory if we're in ext-base
|
|
300
|
+
const isInExtBase = process.cwd().endsWith('ext-base');
|
|
301
|
+
const extDir = isInExtBase
|
|
302
|
+
? join(process.cwd(), '..', extDirName)
|
|
303
|
+
: join(process.cwd(), 'extensions', extDirName);
|
|
304
|
+
|
|
174
305
|
console.log();
|
|
175
|
-
consola.start(`Creating extension: ${chalk.cyan(
|
|
306
|
+
consola.start(`Creating extension: ${chalk.cyan(fullResponse.packageName)}`);
|
|
176
307
|
|
|
177
308
|
try {
|
|
178
|
-
|
|
309
|
+
// Convert displayName to PascalCase for provider/component naming
|
|
310
|
+
// "demo" -> "Demo"
|
|
311
|
+
// "My Extension" -> "MyExtension"
|
|
312
|
+
// "my-extension" -> "MyExtension"
|
|
313
|
+
const providerName = fullResponse.displayName
|
|
314
|
+
.split(/[\s-_]+/) // Split by spaces, hyphens, underscores
|
|
315
|
+
.map((word: string) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
316
|
+
.join('');
|
|
317
|
+
|
|
318
|
+
// Extract short name from package name (last part after /)
|
|
319
|
+
const shortName = fullResponse.packageName.split('/').pop() || fullResponse.packageName.replace('@', '');
|
|
320
|
+
|
|
321
|
+
// Generate marketplace ID and links for README
|
|
322
|
+
const marketplaceId = fullResponse.packageName.replace('@', '').replace('/', '-');
|
|
323
|
+
const marketplaceLink = `**[📦 View in Marketplace](https://hub.djangocfg.com/extensions/${marketplaceId})** • **[📖 Documentation](https://djangocfg.com)** • **[⭐ GitHub](https://github.com/markolofsen/django-cfg)**`;
|
|
179
324
|
|
|
180
325
|
// Prepare replacements
|
|
181
326
|
const replacements = {
|
|
182
|
-
'__NAME__':
|
|
183
|
-
'
|
|
184
|
-
'
|
|
185
|
-
'
|
|
186
|
-
'__CATEGORY__':
|
|
327
|
+
'__NAME__': shortName,
|
|
328
|
+
'__PACKAGE_NAME__': fullResponse.packageName,
|
|
329
|
+
'__DISPLAY_NAME__': fullResponse.displayName,
|
|
330
|
+
'__DESCRIPTION__': fullResponse.description,
|
|
331
|
+
'__CATEGORY__': fullResponse.category,
|
|
187
332
|
'__PROVIDER_NAME__': providerName,
|
|
333
|
+
'__MARKETPLACE_ID__': marketplaceId,
|
|
334
|
+
'__MARKETPLACE_LINK__': marketplaceLink,
|
|
188
335
|
};
|
|
189
336
|
|
|
190
337
|
// Find template directory
|
|
191
338
|
const templatePaths = [
|
|
192
|
-
// When installed from npm
|
|
339
|
+
// When running from built dist/ (installed from npm)
|
|
193
340
|
join(__dirname, '../templates/extension-template'),
|
|
194
|
-
//
|
|
341
|
+
// When running from src/ during development (tsx)
|
|
342
|
+
join(__dirname, '../../templates/extension-template'),
|
|
343
|
+
// Workspace path (for development from monorepo root)
|
|
195
344
|
join(process.cwd(), 'extensions', 'ext-base', 'templates', 'extension-template'),
|
|
345
|
+
// When running from ext-base directory
|
|
346
|
+
join(process.cwd(), 'templates', 'extension-template'),
|
|
196
347
|
];
|
|
197
348
|
|
|
198
349
|
let templateDir: string | null = null;
|
|
@@ -212,17 +363,90 @@ async function createExtension() {
|
|
|
212
363
|
copyTemplateRecursive(templateDir, extDir, replacements);
|
|
213
364
|
consola.success('Extension files created');
|
|
214
365
|
|
|
366
|
+
// Replace "latest" with "workspace:*" if in djangocfg monorepo
|
|
367
|
+
if (isInDjangoCfgMonorepo) {
|
|
368
|
+
consola.info('Detected djangocfg monorepo - using workspace:* for @djangocfg packages');
|
|
369
|
+
const pkgJsonPath = join(extDir, 'package.json');
|
|
370
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
371
|
+
|
|
372
|
+
// Replace "latest" with "workspace:*" for @djangocfg packages
|
|
373
|
+
const replaceDeps = (deps?: Record<string, string>) => {
|
|
374
|
+
if (!deps) return;
|
|
375
|
+
for (const [key, value] of Object.entries(deps)) {
|
|
376
|
+
if (key.startsWith('@djangocfg/') && value === 'latest') {
|
|
377
|
+
deps[key] = 'workspace:*';
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
replaceDeps(pkgJson.dependencies);
|
|
383
|
+
replaceDeps(pkgJson.peerDependencies);
|
|
384
|
+
replaceDeps(pkgJson.devDependencies);
|
|
385
|
+
|
|
386
|
+
writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + '\n', 'utf-8');
|
|
387
|
+
}
|
|
388
|
+
|
|
215
389
|
console.log();
|
|
390
|
+
|
|
391
|
+
// Auto-install dependencies
|
|
392
|
+
const packageManager = detectPackageManager();
|
|
393
|
+
if (packageManager) {
|
|
394
|
+
consola.start(`Installing dependencies with ${chalk.cyan(packageManager)}...`);
|
|
395
|
+
try {
|
|
396
|
+
execSync(`${packageManager} install`, {
|
|
397
|
+
cwd: extDir,
|
|
398
|
+
stdio: 'inherit',
|
|
399
|
+
});
|
|
400
|
+
consola.success('Dependencies installed successfully');
|
|
401
|
+
console.log();
|
|
402
|
+
|
|
403
|
+
// Install playground dependencies
|
|
404
|
+
const playgroundDir = join(extDir, 'playground');
|
|
405
|
+
if (existsSync(playgroundDir)) {
|
|
406
|
+
consola.start(`Installing playground dependencies with ${chalk.cyan(packageManager)}...`);
|
|
407
|
+
try {
|
|
408
|
+
execSync(`${packageManager} install`, {
|
|
409
|
+
cwd: playgroundDir,
|
|
410
|
+
stdio: 'inherit',
|
|
411
|
+
});
|
|
412
|
+
consola.success('Playground dependencies installed successfully');
|
|
413
|
+
console.log();
|
|
414
|
+
} catch (error) {
|
|
415
|
+
consola.warn(`Failed to install playground dependencies. Please run: ${chalk.cyan(`cd ${extDirName}/playground && ${packageManager} install`)}`);
|
|
416
|
+
console.log();
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Run type check
|
|
421
|
+
consola.start('Running type check...');
|
|
422
|
+
try {
|
|
423
|
+
execSync(`${packageManager} run check`, {
|
|
424
|
+
cwd: extDir,
|
|
425
|
+
stdio: 'inherit',
|
|
426
|
+
});
|
|
427
|
+
consola.success('Type check passed');
|
|
428
|
+
} catch (error) {
|
|
429
|
+
consola.warn('Type check failed. Please review and fix type errors.');
|
|
430
|
+
}
|
|
431
|
+
} catch (error) {
|
|
432
|
+
consola.warn(`Failed to install dependencies automatically. Please run: ${chalk.cyan(`cd ${extDirName} && ${packageManager} install`)}`);
|
|
433
|
+
}
|
|
434
|
+
console.log();
|
|
435
|
+
} else {
|
|
436
|
+
consola.warn('No package manager found (pnpm, yarn, or npm). Please install dependencies manually.');
|
|
437
|
+
console.log();
|
|
438
|
+
}
|
|
439
|
+
|
|
216
440
|
consola.success(`Extension created successfully: ${chalk.cyan(extDir)}`);
|
|
217
441
|
console.log();
|
|
218
442
|
|
|
219
443
|
console.log(chalk.yellow.bold('Next steps:'));
|
|
220
444
|
console.log();
|
|
221
|
-
console.log(chalk.gray('1.
|
|
222
|
-
console.log(chalk.cyan(` cd ${
|
|
445
|
+
console.log(chalk.gray('1. Navigate to extension directory:'));
|
|
446
|
+
console.log(chalk.cyan(` cd ${extDirName}`));
|
|
223
447
|
console.log();
|
|
224
448
|
console.log(chalk.gray('2. Build the extension:'));
|
|
225
|
-
console.log(chalk.cyan(` pnpm build`));
|
|
449
|
+
console.log(chalk.cyan(` ${packageManager || 'pnpm'} build`));
|
|
226
450
|
console.log();
|
|
227
451
|
console.log(chalk.gray('3. Add your features and customize:'));
|
|
228
452
|
console.log(chalk.cyan(` - Edit src/config.ts to add features`));
|
|
@@ -237,6 +461,213 @@ async function createExtension() {
|
|
|
237
461
|
}
|
|
238
462
|
}
|
|
239
463
|
|
|
464
|
+
// Create test extension (no prompts)
|
|
465
|
+
async function createTestExtension() {
|
|
466
|
+
printBanner();
|
|
467
|
+
|
|
468
|
+
// Clean up old test extensions first
|
|
469
|
+
const isInExtBase = process.cwd().endsWith('ext-base');
|
|
470
|
+
const baseDir = isInExtBase
|
|
471
|
+
? join(process.cwd(), '..')
|
|
472
|
+
: join(process.cwd(), 'extensions');
|
|
473
|
+
|
|
474
|
+
if (existsSync(baseDir)) {
|
|
475
|
+
const files = readdirSync(baseDir);
|
|
476
|
+
const testExtensions = files.filter(f => f.startsWith('test-'));
|
|
477
|
+
|
|
478
|
+
if (testExtensions.length > 0) {
|
|
479
|
+
consola.info(`Cleaning up ${testExtensions.length} old test extension(s)...`);
|
|
480
|
+
for (const testExt of testExtensions) {
|
|
481
|
+
const testPath = join(baseDir, testExt);
|
|
482
|
+
try {
|
|
483
|
+
execSync(`rm -rf "${testPath}"`, { stdio: 'ignore' });
|
|
484
|
+
consola.success(`Removed ${testExt}`);
|
|
485
|
+
} catch (error) {
|
|
486
|
+
consola.warn(`Failed to remove ${testExt}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
console.log();
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Generate unique test name
|
|
494
|
+
const timestamp = Date.now().toString(36);
|
|
495
|
+
const packageName = `@djangocfg/test-${timestamp}`;
|
|
496
|
+
const displayName = `Test ${timestamp}`;
|
|
497
|
+
const description = 'Test extension created for development';
|
|
498
|
+
const category = 'utilities';
|
|
499
|
+
|
|
500
|
+
console.log(chalk.yellow('Quick Test Extension Creation'));
|
|
501
|
+
console.log();
|
|
502
|
+
console.log(chalk.cyan(`Package: ${packageName}`));
|
|
503
|
+
console.log(chalk.cyan(`Name: ${displayName}`));
|
|
504
|
+
console.log();
|
|
505
|
+
|
|
506
|
+
// Check if we're in djangocfg monorepo
|
|
507
|
+
const isInDjangoCfgMonorepo = existsSync(join(process.cwd(), '../../pnpm-workspace.yaml')) &&
|
|
508
|
+
existsSync(join(process.cwd(), '../../packages/api/package.json'));
|
|
509
|
+
|
|
510
|
+
// Generate directory name from package name
|
|
511
|
+
const extDirName = packageName.split('/')[1]; // test-xyz
|
|
512
|
+
|
|
513
|
+
// Create extension in parent directory if we're in ext-base (reuse from above)
|
|
514
|
+
const extDir = isInExtBase
|
|
515
|
+
? join(process.cwd(), '..', extDirName)
|
|
516
|
+
: join(process.cwd(), 'extensions', extDirName);
|
|
517
|
+
|
|
518
|
+
// Check if already exists
|
|
519
|
+
if (existsSync(extDir)) {
|
|
520
|
+
consola.error(`Extension directory already exists: ${chalk.red(extDir)}`);
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
consola.start(`Creating test extension: ${chalk.cyan(packageName)}`);
|
|
525
|
+
|
|
526
|
+
try {
|
|
527
|
+
// Convert displayName to PascalCase for provider/component naming
|
|
528
|
+
const providerName = displayName
|
|
529
|
+
.split(/[\s-_]+/)
|
|
530
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
531
|
+
.join('');
|
|
532
|
+
|
|
533
|
+
// Extract short name from package name
|
|
534
|
+
const shortName = packageName.split('/').pop() || packageName.replace('@', '');
|
|
535
|
+
|
|
536
|
+
// Generate marketplace ID and links for README
|
|
537
|
+
const marketplaceId = packageName.replace('@', '').replace('/', '-');
|
|
538
|
+
const marketplaceLink = `**[📦 View in Marketplace](https://hub.djangocfg.com/extensions/${marketplaceId})** • **[📖 Documentation](https://djangocfg.com)** • **[⭐ GitHub](https://github.com/markolofsen/django-cfg)**`;
|
|
539
|
+
|
|
540
|
+
// Prepare replacements
|
|
541
|
+
const replacements = {
|
|
542
|
+
'__NAME__': shortName,
|
|
543
|
+
'__PACKAGE_NAME__': packageName,
|
|
544
|
+
'__DISPLAY_NAME__': displayName,
|
|
545
|
+
'__DESCRIPTION__': description,
|
|
546
|
+
'__CATEGORY__': category,
|
|
547
|
+
'__PROVIDER_NAME__': providerName,
|
|
548
|
+
'__MARKETPLACE_ID__': marketplaceId,
|
|
549
|
+
'__MARKETPLACE_LINK__': marketplaceLink,
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
// Find template directory
|
|
553
|
+
const templatePaths = [
|
|
554
|
+
join(__dirname, '../templates/extension-template'),
|
|
555
|
+
join(__dirname, '../../templates/extension-template'),
|
|
556
|
+
join(process.cwd(), 'extensions', 'ext-base', 'templates', 'extension-template'),
|
|
557
|
+
join(process.cwd(), 'templates', 'extension-template'),
|
|
558
|
+
];
|
|
559
|
+
|
|
560
|
+
let templateDir: string | null = null;
|
|
561
|
+
for (const path of templatePaths) {
|
|
562
|
+
if (existsSync(path)) {
|
|
563
|
+
templateDir = path;
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (!templateDir) {
|
|
569
|
+
throw new Error('Extension template not found');
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Copy template with replacements
|
|
573
|
+
consola.start('Copying template files...');
|
|
574
|
+
copyTemplateRecursive(templateDir, extDir, replacements);
|
|
575
|
+
consola.success('Extension files created');
|
|
576
|
+
|
|
577
|
+
// Replace "latest" with "workspace:*" if in djangocfg monorepo
|
|
578
|
+
if (isInDjangoCfgMonorepo) {
|
|
579
|
+
consola.info('Detected djangocfg monorepo - using workspace:* for @djangocfg packages');
|
|
580
|
+
const pkgJsonPath = join(extDir, 'package.json');
|
|
581
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
582
|
+
|
|
583
|
+
const replaceDeps = (deps?: Record<string, string>) => {
|
|
584
|
+
if (!deps) return;
|
|
585
|
+
for (const [key, value] of Object.entries(deps)) {
|
|
586
|
+
if (key.startsWith('@djangocfg/') && value === 'latest') {
|
|
587
|
+
deps[key] = 'workspace:*';
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
replaceDeps(pkgJson.dependencies);
|
|
593
|
+
replaceDeps(pkgJson.peerDependencies);
|
|
594
|
+
replaceDeps(pkgJson.devDependencies);
|
|
595
|
+
|
|
596
|
+
// Also update playground package.json
|
|
597
|
+
const playgroundPkgPath = join(extDir, 'playground', 'package.json');
|
|
598
|
+
if (existsSync(playgroundPkgPath)) {
|
|
599
|
+
const playgroundPkg = JSON.parse(readFileSync(playgroundPkgPath, 'utf-8'));
|
|
600
|
+
replaceDeps(playgroundPkg.dependencies);
|
|
601
|
+
replaceDeps(playgroundPkg.devDependencies);
|
|
602
|
+
writeFileSync(playgroundPkgPath, JSON.stringify(playgroundPkg, null, 2) + '\n', 'utf-8');
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + '\n', 'utf-8');
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
console.log();
|
|
609
|
+
|
|
610
|
+
// Auto-install dependencies
|
|
611
|
+
const packageManager = detectPackageManager();
|
|
612
|
+
if (packageManager) {
|
|
613
|
+
consola.start(`Installing dependencies with ${chalk.cyan(packageManager)}...`);
|
|
614
|
+
try {
|
|
615
|
+
execSync(`${packageManager} install`, {
|
|
616
|
+
cwd: extDir,
|
|
617
|
+
stdio: 'inherit',
|
|
618
|
+
});
|
|
619
|
+
consola.success('Dependencies installed successfully');
|
|
620
|
+
console.log();
|
|
621
|
+
|
|
622
|
+
// Install playground dependencies
|
|
623
|
+
const playgroundDir = join(extDir, 'playground');
|
|
624
|
+
if (existsSync(playgroundDir)) {
|
|
625
|
+
consola.start(`Installing playground dependencies with ${chalk.cyan(packageManager)}...`);
|
|
626
|
+
try {
|
|
627
|
+
execSync(`${packageManager} install`, {
|
|
628
|
+
cwd: playgroundDir,
|
|
629
|
+
stdio: 'inherit',
|
|
630
|
+
});
|
|
631
|
+
consola.success('Playground dependencies installed successfully');
|
|
632
|
+
console.log();
|
|
633
|
+
} catch (error) {
|
|
634
|
+
consola.warn(`Failed to install playground dependencies.`);
|
|
635
|
+
console.log();
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Run type check
|
|
640
|
+
consola.start('Running type check...');
|
|
641
|
+
try {
|
|
642
|
+
execSync(`${packageManager} run check`, {
|
|
643
|
+
cwd: extDir,
|
|
644
|
+
stdio: 'inherit',
|
|
645
|
+
});
|
|
646
|
+
consola.success('Type check passed');
|
|
647
|
+
} catch (error) {
|
|
648
|
+
consola.warn('Type check failed. Please review and fix type errors.');
|
|
649
|
+
}
|
|
650
|
+
} catch (error) {
|
|
651
|
+
consola.warn(`Failed to install dependencies automatically.`);
|
|
652
|
+
}
|
|
653
|
+
console.log();
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
consola.success(`Test extension created: ${chalk.cyan(extDir)}`);
|
|
657
|
+
console.log();
|
|
658
|
+
|
|
659
|
+
console.log(chalk.yellow.bold('Quick commands:'));
|
|
660
|
+
console.log();
|
|
661
|
+
console.log(chalk.cyan(` cd ${extDirName}`));
|
|
662
|
+
console.log(chalk.cyan(` pnpm build`));
|
|
663
|
+
console.log(chalk.cyan(` pnpm dev:playground`));
|
|
664
|
+
console.log();
|
|
665
|
+
} catch (error) {
|
|
666
|
+
consola.error('Failed to create test extension:', error);
|
|
667
|
+
process.exit(1);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
240
671
|
// Main CLI
|
|
241
672
|
async function main() {
|
|
242
673
|
const args = process.argv.slice(2);
|
|
@@ -254,6 +685,10 @@ async function main() {
|
|
|
254
685
|
await createExtension();
|
|
255
686
|
break;
|
|
256
687
|
|
|
688
|
+
case 'test':
|
|
689
|
+
await createTestExtension();
|
|
690
|
+
break;
|
|
691
|
+
|
|
257
692
|
case 'help':
|
|
258
693
|
case '--help':
|
|
259
694
|
case '-h':
|