kalo-cli 0.1.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.
- package/README.md +47 -0
- package/bin/kalo.ts +17 -0
- package/generators/ai-enhancer/index.ts +281 -0
- package/generators/ai-enhancer/keywords.json +1158 -0
- package/generators/constants.ts +52 -0
- package/generators/django-app/index.ts +67 -0
- package/generators/django-app/templates/admin.py.hbs +6 -0
- package/generators/django-app/templates/apps.py.hbs +9 -0
- package/generators/django-app/templates/init.py.hbs +0 -0
- package/generators/django-app/templates/models_init.py.hbs +2 -0
- package/generators/django-app/templates/urls.py.hbs +8 -0
- package/generators/django-app/templates/views.py.hbs +5 -0
- package/generators/django-channel/index.ts +78 -0
- package/generators/django-channel/templates/consumer.py.hbs +47 -0
- package/generators/django-channel/templates/routing.py.hbs +8 -0
- package/generators/django-form/index.ts +62 -0
- package/generators/django-form/templates/form.py.hbs +12 -0
- package/generators/django-form/templates/forms_file.py.hbs +6 -0
- package/generators/django-form/templates/model_form.py.hbs +18 -0
- package/generators/django-view/index.ts +95 -0
- package/generators/django-view/templates/view_cbv.py.hbs +11 -0
- package/generators/django-view/templates/view_fbv.py.hbs +7 -0
- package/generators/django-view/templates/view_template.html.hbs +8 -0
- package/generators/docs/index.ts +36 -0
- package/generators/help/index.ts +84 -0
- package/generators/main/index.ts +429 -0
- package/generators/utils/ai/common.ts +141 -0
- package/generators/utils/ai/index.ts +2 -0
- package/generators/utils/analysis.ts +82 -0
- package/generators/utils/code-manipulation.ts +119 -0
- package/generators/utils/filesystem.ts +64 -0
- package/generators/utils/index.ts +47 -0
- package/generators/utils/plop-actions.ts +61 -0
- package/generators/utils/search.ts +24 -0
- package/generators/wagtail-admin/index.ts +122 -0
- package/generators/wagtail-admin/templates/admin_view.html.hbs +21 -0
- package/generators/wagtail-admin/templates/admin_view.py.hbs +15 -0
- package/generators/wagtail-admin/templates/component.html.hbs +6 -0
- package/generators/wagtail-admin/templates/component.py.hbs +11 -0
- package/generators/wagtail-admin/templates/wagtail_hooks.py.hbs +18 -0
- package/generators/wagtail-block/index.ts +55 -0
- package/generators/wagtail-block/templates/block_class.py.hbs +13 -0
- package/generators/wagtail-block/templates/block_template.html.hbs +5 -0
- package/generators/wagtail-page/actions/model.ts +18 -0
- package/generators/wagtail-page/actions/orderable.ts +21 -0
- package/generators/wagtail-page/actions/page.ts +40 -0
- package/generators/wagtail-page/actions/snippet.ts +19 -0
- package/generators/wagtail-page/index.ts +63 -0
- package/generators/wagtail-page/templates/django_model.py.hbs +18 -0
- package/generators/wagtail-page/templates/orderable_model.py.hbs +21 -0
- package/generators/wagtail-page/templates/page_pair_model.py.hbs +62 -0
- package/generators/wagtail-page/templates/page_template.html.hbs +14 -0
- package/generators/wagtail-page/templates/snippet_model.py.hbs +24 -0
- package/package.json +47 -0
- package/plopfile.ts +26 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ActionType } from 'plop';
|
|
2
|
+
import { createAppendActions, ensureSuffix } from '../../utils/plop-actions';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generates Plop actions for creating a Wagtail Snippet.
|
|
6
|
+
*
|
|
7
|
+
* @param {any} data - The prompt answers data.
|
|
8
|
+
* @returns {ActionType[]} An array of Plop actions to create snippet files and tests.
|
|
9
|
+
*/
|
|
10
|
+
export const getSnippetActions = (data: any): ActionType[] => {
|
|
11
|
+
const appPath = `app/${data.app}`;
|
|
12
|
+
|
|
13
|
+
data.name = ensureSuffix(data.name, 'Snippet');
|
|
14
|
+
|
|
15
|
+
return createAppendActions({
|
|
16
|
+
path: `${appPath}/models/snippets.py`,
|
|
17
|
+
templateFile: 'generators/wagtail-page/templates/snippet_model.py.hbs'
|
|
18
|
+
});
|
|
19
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { PlopGeneratorConfig } from 'plop';
|
|
2
|
+
import { getPageActions } from './actions/page';
|
|
3
|
+
import { getOrderableActions } from './actions/orderable';
|
|
4
|
+
import { getSnippetActions } from './actions/snippet';
|
|
5
|
+
import { getModelActions } from './actions/model';
|
|
6
|
+
import { WAGTAIL_PAGE_TYPES } from '../constants';
|
|
7
|
+
import { searchChoices } from '../utils/search';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generator configuration for Wagtail Page Models and related components.
|
|
11
|
+
* Supports creation of:
|
|
12
|
+
* - Page Pairs (Index + Detail)
|
|
13
|
+
* - Orderable Models
|
|
14
|
+
* - Snippets
|
|
15
|
+
* - Standard Django Models
|
|
16
|
+
* Delegates action generation to specialized action helpers.
|
|
17
|
+
*
|
|
18
|
+
* @type {PlopGeneratorConfig}
|
|
19
|
+
*/
|
|
20
|
+
export const wagtailPageGenerator: PlopGeneratorConfig = {
|
|
21
|
+
description: 'Créer un modèle Wagtail Page (Paire Index/Detail), Orderable, ou Snippet',
|
|
22
|
+
prompts: [
|
|
23
|
+
{
|
|
24
|
+
type: 'autocomplete',
|
|
25
|
+
name: 'type',
|
|
26
|
+
message: 'Quel type de modèle voulez-vous créer ?',
|
|
27
|
+
source: (answers, input) => searchChoices(input, WAGTAIL_PAGE_TYPES),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: 'input',
|
|
31
|
+
name: 'name',
|
|
32
|
+
message: (answers) => {
|
|
33
|
+
if (answers.type === 'page_pair') return 'Nom de base (ex: Blog -> BlogIndexPage, BlogPage) :';
|
|
34
|
+
if (answers.type === 'orderable') return 'Nom de l\'Orderable (ex: GalleryImage -> GalleryImageOrderable) :';
|
|
35
|
+
if (answers.type === 'snippet') return 'Nom du Snippet (ex: Category -> CategorySnippet) :';
|
|
36
|
+
return 'Nom du Modèle (ex: Product -> Product) :';
|
|
37
|
+
},
|
|
38
|
+
validate: (value, answers) => {
|
|
39
|
+
if (!value) return 'Le nom est requis';
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'input',
|
|
45
|
+
name: 'app',
|
|
46
|
+
message: 'Nom de l\'application cible',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
actions: (data) => {
|
|
50
|
+
switch (data.type) {
|
|
51
|
+
case 'page_pair':
|
|
52
|
+
return getPageActions(data);
|
|
53
|
+
case 'orderable':
|
|
54
|
+
return getOrderableActions(data);
|
|
55
|
+
case 'snippet':
|
|
56
|
+
return getSnippetActions(data);
|
|
57
|
+
case 'model':
|
|
58
|
+
return getModelActions(data);
|
|
59
|
+
default:
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
class {{pascalCase name}}(models.Model):
|
|
3
|
+
"""
|
|
4
|
+
{{pascalCase name}} standard Django model.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# <!-- CLASS = {{pascalCase name}} END AI_GENERATED_DJANGO_MODEL -->
|
|
8
|
+
# <!-- CLASS = {{pascalCase name}} START AI_GENERATED_DJANGO_MODEL -->
|
|
9
|
+
|
|
10
|
+
# Database fields
|
|
11
|
+
name = models.CharField(max_length=255)
|
|
12
|
+
|
|
13
|
+
class Meta:
|
|
14
|
+
verbose_name = "{{titleCase name}}"
|
|
15
|
+
verbose_name_plural = "{{titleCase name}}s"
|
|
16
|
+
|
|
17
|
+
def __str__(self):
|
|
18
|
+
return self.name
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
class {{pascalCase name}}(Orderable):
|
|
3
|
+
"""
|
|
4
|
+
{{pascalCase name}} orderable model.
|
|
5
|
+
"""
|
|
6
|
+
# page = ParentalKey('app_label.PageModel', related_name='related_items', on_delete=models.CASCADE)
|
|
7
|
+
|
|
8
|
+
# <!-- CLASS = {{pascalCase name}} START AI_GENERATED_ORDERABLE_MODEL -->
|
|
9
|
+
# <!-- CLASS = {{pascalCase name}} END AI_GENERATED_ORDERABLE_MODEL -->
|
|
10
|
+
|
|
11
|
+
# Database fields
|
|
12
|
+
sort_order = models.IntegerField(null=True, blank=True, editable=False)
|
|
13
|
+
# Add your fields here
|
|
14
|
+
|
|
15
|
+
panels = [
|
|
16
|
+
# FieldPanel('field_name'),
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
class Meta:
|
|
20
|
+
verbose_name = "{{titleCase name}}"
|
|
21
|
+
verbose_name_plural = "{{titleCase name}}s"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
|
|
2
|
+
class {{pascalCase baseName}}IndexPage(Page):
|
|
3
|
+
"""
|
|
4
|
+
Index page for {{pascalCase baseName}}.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Parent page / subpage type rules
|
|
8
|
+
subpage_types = ['{{pascalCase baseName}}Page']
|
|
9
|
+
|
|
10
|
+
# Database fields
|
|
11
|
+
intro = models.CharField(max_length=250, blank=True)
|
|
12
|
+
body = RichTextField(blank=True)
|
|
13
|
+
|
|
14
|
+
# Editor panels configuration
|
|
15
|
+
content_panels = Page.content_panels + [
|
|
16
|
+
FieldPanel('intro'),
|
|
17
|
+
FieldPanel('body'),
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
# <!-- CLASS = {{pascalCase baseName}}IndexPage START AI_GENERATED_PAGE_INDEX -->
|
|
21
|
+
# <!-- CLASS = {{pascalCase baseName}}IndexPage END AI_GENERATED_PAGE_INDEX -->
|
|
22
|
+
|
|
23
|
+
# Search index configuration
|
|
24
|
+
search_fields = Page.search_fields + [
|
|
25
|
+
index.SearchField('intro'),
|
|
26
|
+
index.SearchField('body'),
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
class Meta:
|
|
30
|
+
verbose_name = "{{titleCase baseName}} Index"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class {{pascalCase baseName}}Page(Page):
|
|
34
|
+
"""
|
|
35
|
+
Detail page for {{pascalCase baseName}}.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
# Parent page / subpage type rules
|
|
39
|
+
parent_page_types = ['{{pascalCase baseName}}IndexPage']
|
|
40
|
+
|
|
41
|
+
# Database fields
|
|
42
|
+
intro = models.CharField(max_length=250, blank=True)
|
|
43
|
+
body = RichTextField(blank=True)
|
|
44
|
+
|
|
45
|
+
# Editor panels configuration
|
|
46
|
+
content_panels = Page.content_panels + [
|
|
47
|
+
FieldPanel('intro'),
|
|
48
|
+
FieldPanel('body'),
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
# <!-- CLASS = {{pascalCase baseName}}Page START AI_GENERATED_PAGE -->
|
|
52
|
+
# <!-- CLASS = {{pascalCase baseName}}Page END AI_GENERATED_PAGE -->
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Search index configuration
|
|
56
|
+
search_fields = Page.search_fields + [
|
|
57
|
+
index.SearchField('intro'),
|
|
58
|
+
index.SearchField('body'),
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
class Meta:
|
|
62
|
+
verbose_name = "{{titleCase baseName}}"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
{% extends "base.html" %}
|
|
3
|
+
{% load wagtailcore_tags %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<h1>\{{ page.title }}</h1>
|
|
7
|
+
|
|
8
|
+
<div class="intro">\{{ page.intro|richtext }}</div>
|
|
9
|
+
|
|
10
|
+
\{{ page.body|richtext }}
|
|
11
|
+
{% endblock %}
|
|
12
|
+
|
|
13
|
+
<!-- CLASS = {{pascalCase name}} START AI_GENERATED_PAGE_TEMPLATE -->
|
|
14
|
+
<!-- CLASS = {{pascalCase name}} END AI_GENERATED_PAGE_TEMPLATE -->
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
@register_snippet
|
|
3
|
+
class {{pascalCase name}}(models.Model):
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
{{pascalCase name}} snippet.
|
|
7
|
+
"""
|
|
8
|
+
text = models.CharField(max_length=255)
|
|
9
|
+
|
|
10
|
+
panels = [
|
|
11
|
+
FieldPanel('text'),
|
|
12
|
+
]
|
|
13
|
+
# <!-- CLASS = {{pascalCase name}} START AI_GENERATED_SNIPPET -->
|
|
14
|
+
# <!-- CLASS = {{pascalCase name}} END AI_GENERATED_SNIPPET -->
|
|
15
|
+
|
|
16
|
+
def __str__(self):
|
|
17
|
+
return self.text
|
|
18
|
+
|
|
19
|
+
def save(self, *args, **kwargs):
|
|
20
|
+
super().save(*args, **kwargs)
|
|
21
|
+
|
|
22
|
+
class Meta:
|
|
23
|
+
verbose_name = "{{sentenceCase name}}"
|
|
24
|
+
verbose_name_plural = "{{sentenceCase name}}s"
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kalo-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Générateur de code structuré et uniforme pour Django et Wagtail",
|
|
5
|
+
"bin": {
|
|
6
|
+
"kalo": "./bin/kalo.ts"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"kalo": "bun bin/kalo.ts",
|
|
11
|
+
"doc": "bun run --port 3333 docs-site/index.html"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"django",
|
|
15
|
+
"wagtail",
|
|
16
|
+
"generator",
|
|
17
|
+
"plop",
|
|
18
|
+
"cli",
|
|
19
|
+
"bun"
|
|
20
|
+
],
|
|
21
|
+
"files": [
|
|
22
|
+
"bin",
|
|
23
|
+
"generators",
|
|
24
|
+
"utils",
|
|
25
|
+
"plopfile.ts",
|
|
26
|
+
"tsconfig.json"
|
|
27
|
+
],
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/bun": "latest"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"typescript": "^5"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@types/react": "^19.2.9",
|
|
36
|
+
"@types/react-dom": "^19.2.3",
|
|
37
|
+
"@types/react-syntax-highlighter": "^15.5.13",
|
|
38
|
+
"inquirer-autocomplete-prompt": "^3.0.1",
|
|
39
|
+
"minimist": "^1.2.8",
|
|
40
|
+
"plop": "^4.0.5",
|
|
41
|
+
"react": "^19.2.3",
|
|
42
|
+
"react-dom": "^19.2.3",
|
|
43
|
+
"react-syntax-highlighter": "^16.1.0",
|
|
44
|
+
"serve": "^14.2.5",
|
|
45
|
+
"tsx": "^4.21.0"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/plopfile.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { NodePlopAPI } from 'plop';
|
|
2
|
+
import autocompletePrompt from 'inquirer-autocomplete-prompt';
|
|
3
|
+
import registerGenerators from './generators/main';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Primary Plopfile entry point.
|
|
7
|
+
* Registers all generators and helpers.
|
|
8
|
+
*
|
|
9
|
+
* @param {NodePlopAPI} plop - The Plop API instance.
|
|
10
|
+
*/
|
|
11
|
+
export default function (plop: NodePlopAPI) {
|
|
12
|
+
// Register Prompts
|
|
13
|
+
plop.setPrompt('autocomplete', autocompletePrompt);
|
|
14
|
+
|
|
15
|
+
// Global Helpers
|
|
16
|
+
plop.setHelper('upperCase', (txt) => txt.toUpperCase());
|
|
17
|
+
plop.setHelper('snakeCase', (txt) => {
|
|
18
|
+
return txt && txt.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
|
|
19
|
+
?.map(x => x.toLowerCase())
|
|
20
|
+
.join('_');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// --- Register Generators ---
|
|
24
|
+
// The 'main' generator module now handles registering 'main' and 'help' generators.
|
|
25
|
+
registerGenerators(plop);
|
|
26
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": ["ESNext"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "Preserve",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
|
|
11
|
+
// Bundler mode
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
// Best practices
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedIndexedAccess": true,
|
|
22
|
+
"noImplicitOverride": true,
|
|
23
|
+
|
|
24
|
+
// Some stricter flags (disabled by default)
|
|
25
|
+
"noUnusedLocals": false,
|
|
26
|
+
"noUnusedParameters": false,
|
|
27
|
+
"noPropertyAccessFromIndexSignature": false
|
|
28
|
+
}
|
|
29
|
+
}
|