├── .github
├── dependabot.yml
└── workflows
│ ├── ci.yml
│ └── dependabot-auto-merge.yml
├── .gitignore
├── Dockerfile
├── README.md
├── app.vue
├── assets
└── stubs
│ ├── Api
│ ├── Data
│ │ ├── ModelNameInterface.php.stub
│ │ └── ModelNameSearchResultsInterface.php.stub
│ └── ModelNameRepositoryInterface.php.stub
│ ├── Block
│ └── Adminhtml
│ │ └── ModelName
│ │ └── Edit
│ │ ├── BackButton.php.stub
│ │ ├── DeleteButton.php.stub
│ │ ├── GenericButton.php.stub
│ │ └── SaveButton.php.stub
│ ├── Controller
│ └── Adminhtml
│ │ └── ModelName
│ │ ├── Delete.php.stub
│ │ ├── Edit.php.stub
│ │ ├── Index.php.stub
│ │ ├── MassDelete.php.stub
│ │ ├── NewAction.php.stub
│ │ └── Save.php.stub
│ ├── Model
│ ├── Data
│ │ └── ModelName.php.stub
│ ├── ModelName
│ │ └── DataProvider.php.stub
│ ├── ModelNameRepositoryWithDataModel.php.stub
│ ├── ModelNameRepositoryWithoutDataModel.php.stub
│ ├── ModelNameSearchResults.php.stub
│ ├── ModelNameWithDataModel.php.stub
│ ├── ModelNameWithoutDataModel.php.stub
│ └── ResourceModel
│ │ ├── ModelName.php.stub
│ │ └── ModelName
│ │ └── Collection.php.stub
│ ├── Ui
│ └── Component
│ │ └── Listing
│ │ └── Column
│ │ └── Actions.php.stub
│ ├── composer.json.stub
│ ├── etc
│ ├── acl.xml.stub
│ ├── adminhtml
│ │ ├── menu.xml.stub
│ │ └── routes.xml.stub
│ ├── di.xml.stub
│ └── module.xml.stub
│ ├── registration.php.stub
│ └── view
│ └── adminhtml
│ ├── layout
│ ├── BaseName_edit.xml.stub
│ ├── BaseName_index.xml.stub
│ └── BaseName_new.xml.stub
│ └── ui_component
│ └── FormName.xml.stub
├── components
├── AdminGrid.vue
├── BottomSidebar.vue
├── DefineModelColumns.vue
├── ModelColumn.vue
├── ModelInformation.vue
├── ModuleName.vue
├── ScriptModal.vue
├── TopSidebar.vue
├── TreeItem.vue
└── TreeView.vue
├── cypress.config.ts
├── cypress
├── e2e
│ ├── data-models.cy.js
│ ├── fields.cy.js
│ └── file-output.cy.js
├── fixtures
│ └── example.json
└── support
│ ├── commands.ts
│ └── e2e.ts
├── eslint.config.mjs
├── functions
└── xml.ts
├── interfaces
├── AttributeInterface.ts
├── ColumnInterface.ts
├── GeneratesFileInterface.ts
├── GeneratesXmlInterface.ts
└── TreeItemInterface.ts
├── nuxt.config.ts
├── output
├── FileList.ts
├── GeneratedFile.ts
├── StateAware.ts
└── listing
│ ├── Column.ts
│ ├── Columns.ts
│ ├── DataArgument.ts
│ ├── DataSource.ts
│ ├── DbSchema.ts
│ ├── DbSchemaWhitelist.ts
│ ├── ListingTop.ts
│ ├── NewButton.ts
│ ├── Settings.ts
│ ├── UiComponent.ts
│ └── UiComponentForm.ts
├── package.json
├── pages
├── README.md
└── index.vue
├── public
└── favicon.ico
├── run-cypress.sh
├── run-docker.sh
├── server
└── tsconfig.json
├── static
├── favicon.png
├── icon.png
└── magento2-model-generator-logo.png
├── stores
├── admingridStore.ts
├── downloadStore.ts
├── fathomStore.ts
├── modelStore.ts
├── moduleStore.ts
└── tableStore.ts
├── tsconfig.json
└── yarn.lock
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | # Fetch and update latest `npm` packages
4 | - package-ecosystem: npm
5 | directory: '/'
6 | schedule:
7 | interval: daily
8 | time: '00:00'
9 | open-pull-requests-limit: 10
10 | reviewers:
11 | - michielgerritsen
12 | assignees:
13 | - michielgerritsen
14 | commit-message:
15 | prefix: fix
16 | prefix-development: chore
17 | include: scope
18 | # Fetch and update latest `github-actions` pkgs
19 | - package-ecosystem: github-actions
20 | directory: '/'
21 | schedule:
22 | interval: daily
23 | time: '00:00'
24 | open-pull-requests-limit: 10
25 | reviewers:
26 | - michielgerritsen
27 | assignees:
28 | - michielgerritsen
29 | commit-message:
30 | prefix: fix
31 | prefix-development: chore
32 | include: scope
33 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | ci:
7 | runs-on: ${{ matrix.os }}
8 |
9 | strategy:
10 | matrix:
11 | os: [ubuntu-latest]
12 | node: [18]
13 |
14 | steps:
15 | - name: Checkout 🛎
16 | uses: actions/checkout@v4.2.2
17 |
18 | - name: Setup node env 🏗
19 | uses: actions/setup-node@v4
20 | with:
21 | node-version: ${{ matrix.node }}
22 | check-latest: true
23 |
24 | - name: Get yarn cache directory path 🛠
25 | id: yarn-cache-dir-path
26 | run: echo "::set-output name=dir::$(yarn cache dir)"
27 |
28 | - name: Cache node_modules 📦
29 | uses: actions/cache@v4
30 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
31 | with:
32 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
33 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
34 | restore-keys: |
35 | ${{ runner.os }}-yarn-
36 |
37 | - name: Install dependencies 👨🏻💻
38 | run: yarn
39 |
40 | - name: Run linter 👀
41 | run: yarn lint
42 |
43 | - name: Run Cypress 🌎
44 | run: yarn cypress
45 |
46 | - name: Upload artifacts
47 | uses: actions/upload-artifact@v4
48 | if: always()
49 | with:
50 | name: Cypress logs
51 | path: |
52 | cypress/videos
53 |
--------------------------------------------------------------------------------
/.github/workflows/dependabot-auto-merge.yml:
--------------------------------------------------------------------------------
1 | name: dependabot-auto-merge
2 |
3 | on:
4 | pull_request:
5 |
6 | jobs:
7 | dependabot-auto-merge:
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v4.2.2
12 | - uses: ahmadnassri/action-dependabot-auto-merge@v2
13 | with:
14 | github-token: ${{ secrets.dependabot_auto_merge }}
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Nuxt dev/build outputs
2 | .output
3 | .data
4 | .nuxt
5 | .nitro
6 | .cache
7 | dist
8 |
9 | # Node dependencies
10 | node_modules
11 |
12 | # Logs
13 | logs
14 | *.log
15 |
16 | # Misc
17 | .DS_Store
18 | .fleet
19 | .idea
20 |
21 | # Local env files
22 | .env
23 | .env.*
24 | !.env.example
25 | cypress/videos
26 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine
2 |
3 | # create destination directory
4 | RUN mkdir -p /usr/src/nuxt-app
5 | WORKDIR /usr/src/nuxt-app
6 |
7 | ENV PYTHONUNBUFFERED=1
8 | RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python
9 |
10 | RUN apk --no-cache add g++ gcc libgcc libstdc++ linux-headers make
11 | RUN npm install --quiet node-gyp -g
12 |
13 | # copy the app, note .dockerignore
14 | COPY . /usr/src/nuxt-app/
15 | RUN yarn install
16 | RUN yarn run build
17 |
18 | EXPOSE 3000
19 |
20 | ENV NUXT_HOST=0.0.0.0
21 | ENV NUXT_PORT=3000
22 |
23 | CMD yarn install && yarn dev
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Magento 2 Model Generator
2 |
3 | With this app, you can create models for Magento 2 very fast. If you are not sure what a model in Magento 2 is, I can recommend you to read [this article](https://www.michiel-gerritsen.com/where-to-place-code-in-a-magento-2-module) (TLDR: It's everything that you need to connect to your database).
4 |
5 | Writing models can become cumbersome, as you need to write a lot of boilerplate code which is sort of the same for every model you need to make.
6 |
7 | This app tries to take the cumbersome out of your hands and generate the code for you.
8 |
9 | ## Build Setup
10 |
11 | ```bash
12 | # install dependencies
13 | $ yarn install
14 |
15 | # serve with hot reload at localhost:3000
16 | $ yarn dev
17 |
18 | # build for production and launch server
19 | $ yarn build
20 | $ yarn start
21 |
22 | # generate static project
23 | $ yarn generate
24 | ```
25 |
26 | For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org).
27 |
28 | ## Using docker
29 |
30 | Build the image:
31 |
32 | ```bash
33 | docker build -t magento2-model-generator .
34 | ```
35 |
36 | Run the container:
37 |
38 | ```bash
39 | docker run --rm -it -v $(pwd):/usr/src/nuxt-app --name m2mg -p 3000:3000 magento2-model-generator
40 | ```
41 |
42 | or simply use:
43 |
44 | ```bash
45 | sh run-docker.sh
46 | ```
47 |
--------------------------------------------------------------------------------
/app.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/assets/stubs/Api/Data/ModelNameInterface.php.stub:
--------------------------------------------------------------------------------
1 | __('Back'),
18 | 'on_click' => sprintf("location.href = '%s';", $this->getBackUrl()),
19 | 'class' => 'back',
20 | 'sort_order' => 10
21 | ];
22 | }
23 |
24 | /**
25 | * Get URL for back (reset) button
26 | *
27 | * @return string
28 | */
29 | public function getBackUrl()
30 | {
31 | return $this->getUrl('*/*/');
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/assets/stubs/Block/Adminhtml/ModelName/Edit/DeleteButton.php.stub:
--------------------------------------------------------------------------------
1 | getId()) {
18 | $data = [
19 | 'label' => __('Delete Item'),
20 | 'class' => 'delete',
21 | 'on_click' => 'deleteConfirm(\'' . __(
22 | 'Are you sure you want to do this?'
23 | ) . '\', \'' . $this->getDeleteUrl() . '\', {"data": {}})',
24 | 'sort_order' => 20,
25 | ];
26 | }
27 | return $data;
28 | }
29 |
30 | /**
31 | * URL to send delete requests to.
32 | *
33 | * @return string
34 | */
35 | public function getDeleteUrl()
36 | {
37 | return $this->getUrl('*/*/delete', ['{{ FieldIndex }}' => $this->getId()]);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/assets/stubs/Block/Adminhtml/ModelName/Edit/GenericButton.php.stub:
--------------------------------------------------------------------------------
1 | context = $context;
29 | $this->repository = $repository;
30 | }
31 |
32 | public function getId(): ?int
33 | {
34 | $id = $this->context->getRequest()->getParam('{{ IndexField }}');
35 | if (!$id) {
36 | return null;
37 | }
38 |
39 | try {
40 | return $this->repository->get($id)->getId();
41 | } catch (NoSuchEntityException $e) {
42 | }
43 | return null;
44 | }
45 |
46 | /**
47 | * Generate url by route and parameters
48 | *
49 | * @param string $route
50 | * @param array $params
51 | * @return string
52 | */
53 | public function getUrl($route = '', $params = [])
54 | {
55 | return $this->context->getUrlBuilder()->getUrl($route, $params);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/assets/stubs/Block/Adminhtml/ModelName/Edit/SaveButton.php.stub:
--------------------------------------------------------------------------------
1 | __('Save'),
18 | 'class' => 'save primary',
19 | 'data_attribute' => [
20 | 'mage-init' => [
21 | 'buttonAdapter' => [
22 | 'actions' => [
23 | [
24 | 'targetName' => '{{ FormName }}.{{ FormName }}',
25 | 'actionName' => 'save',
26 | 'params' => [
27 | true,
28 | [
29 | 'back' => 'continue'
30 | ]
31 | ]
32 | ]
33 | ]
34 | ]
35 | ]
36 | ],
37 | ];
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/assets/stubs/Controller/Adminhtml/ModelName/Delete.php.stub:
--------------------------------------------------------------------------------
1 | modelFactory = $modelFactory;
31 | $this->modelResource = $modelResource;
32 | }
33 |
34 | /** @thows \Exception */
35 | public function execute(): Redirect
36 | {
37 | try {
38 | $id = $this->getRequest()->getParam('{{ IndexField }}');
39 | /** @var {{ ModelName }} $model */
40 | $model = $this->modelFactory->create();
41 | $this->modelResource->load($model, $id);
42 | if ($model->getId()) {
43 | $this->modelResource->delete($model);
44 | $this->messageManager->addSuccessMessage(__('The record has been deleted.'));
45 | } else {
46 | $this->messageManager->addErrorMessage(__('The record does not exist.'));
47 | }
48 | } catch (\Exception $e) {
49 | $this->messageManager->addErrorMessage($e->getMessage());
50 | }
51 |
52 | /** @var Redirect $redirect */
53 | $redirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
54 |
55 | return $redirect->setPath('*/*');
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/assets/stubs/Controller/Adminhtml/ModelName/Edit.php.stub:
--------------------------------------------------------------------------------
1 | pageFactory->create();
27 | $page->setActiveMenu('{{ VendorName }}_{{ ModuleName }}::{{ ModelName }}');
28 | $page->getConfig()->getTitle()->prepend(__('Edit {{ ModelName }}'));
29 |
30 | return $page;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/assets/stubs/Controller/Adminhtml/ModelName/Index.php.stub:
--------------------------------------------------------------------------------
1 | pageFactory->create();
27 | $page->setActiveMenu('{{ VendorName }}_{{ ModuleName }}::{{ ModelName }}');
28 | $page->getConfig()->getTitle()->prepend(__('{{ ModelName }}s'));
29 |
30 | return $page;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/assets/stubs/Controller/Adminhtml/ModelName/MassDelete.php.stub:
--------------------------------------------------------------------------------
1 | collectionFactory->create();
30 | $items = $this->filter->getCollection($collection);
31 | $itemsSize = $items->getSize();
32 |
33 | foreach ($items as $item) {
34 | $item->delete();
35 | }
36 |
37 | $this->messageManager->addSuccessMessage(__('A total of %1 record(s) have been deleted.', $itemsSize));
38 |
39 | /** @var Redirect $redirect */
40 | $redirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
41 |
42 | return $redirect->setPath('*/*');
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/assets/stubs/Controller/Adminhtml/ModelName/NewAction.php.stub:
--------------------------------------------------------------------------------
1 | pageFactory->create();
27 | $page->setActiveMenu('{{ VendorName }}_{{ ModuleName }}::{{ ModelName }}');
28 | $page->getConfig()->getTitle()->prepend(__('New {{ ModelName }}'));
29 |
30 | return $page;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/assets/stubs/Controller/Adminhtml/ModelName/Save.php.stub:
--------------------------------------------------------------------------------
1 | request->getParam('{{ IndexField }}');
28 | $data = $this->request->getPostValue();
29 |
30 | $model = $this->modelFactory->create();
31 | if ($id) {
32 | $model = $this->repository->get((int)$id);
33 | }
34 |
35 | $model->setData($data);
36 |
37 | try {
38 | $model = $this->repository->save($model);
39 | $this->messageManager->addSuccessMessage(__('You saved the item.'));
40 | } catch (\Exception $e) {
41 | $this->messageManager->addErrorMessage($e->getMessage());
42 | return $this->resultRedirectFactory->create()->setPath('*');
43 | }
44 |
45 | return $this->resultRedirectFactory->create()
46 | ->setPath('*/*/edit', ['entity_id' => $model->getId()]);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/assets/stubs/Model/Data/ModelName.php.stub:
--------------------------------------------------------------------------------
1 | _get(self::{{ fieldNameUppercase }});
17 | }
18 |
19 | public function set{{ functionName }}({{ phpType }} ${{ fieldName }}): {{ ModelName }}Interface
20 | {
21 | return $this->setData(self::{{ fieldNameUppercase }}, ${{ fieldName }});
22 | }
23 |
24 | {{/Columns}}
25 | public function getExtensionAttributes(): ?{{ ModelName }}ExtensionInterface
26 | {
27 | return $this->_getExtensionAttributes();
28 | }
29 |
30 | public function setExtensionAttributes(
31 | {{ ModelName }}ExtensionInterface $extensionAttributes
32 | ): static {
33 | return $this->_setExtensionAttributes($extensionAttributes);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/assets/stubs/Model/ModelName/DataProvider.php.stub:
--------------------------------------------------------------------------------
1 | collection = $collectionFactory->create();
19 | }
20 |
21 | public function getData(): array
22 | {
23 | $data = parent::getData();
24 |
25 | $items = [];
26 | foreach ($data['items'] as $item) {
27 | $items[$item['entity_id']] = $item;
28 | }
29 |
30 | return $items;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/assets/stubs/Model/ModelNameRepositoryWithDataModel.php.stub:
--------------------------------------------------------------------------------
1 | extensibleDataObjectConverter->toNestedArray(
41 | $entity,
42 | [],
43 | {{ ModelName }}Interface::class
44 | );
45 |
46 | ${{ VariableName }}Model = $this->{{ VariableName }}Factory->create()->setData(${{ VariableName }}Data);
47 |
48 | try {
49 | $this->resource->save(${{ VariableName }}Model);
50 | } catch (\Exception $exception) {
51 | throw new CouldNotSaveException(__(
52 | 'Could not save the {{ VariableName }}: %1',
53 | $exception->getMessage()
54 | ));
55 | }
56 | return ${{ VariableName }}Model->getDataModel();
57 | }
58 |
59 | /**
60 | * @throws NoSuchEntityException
61 | */
62 | public function get(int $id): {{ ModelName }}Interface
63 | {
64 | ${{ VariableName }} = $this->{{ VariableName }}Factory->create();
65 | $this->resource->load(${{ VariableName }}, $id);
66 | if (!${{ VariableName }}->getId()) {
67 | throw new NoSuchEntityException(__('{{ ModelName }} with id "%1" does not exist.', $id));
68 | }
69 | return ${{ VariableName }}->getDataModel();
70 | }
71 |
72 | public function getList(SearchCriteriaInterface $criteria): {{ ModelName }}SearchResultsInterface
73 | {
74 | $collection = $this->{{ VariableName }}CollectionFactory->create();
75 |
76 | $this->extensionAttributesJoinProcessor->process(
77 | $collection,
78 | {{ ModelName }}Interface::class
79 | );
80 |
81 | $this->collectionProcessor->process($criteria, $collection);
82 |
83 | $searchResults = $this->searchResultsFactory->create();
84 | $searchResults->setSearchCriteria($criteria);
85 |
86 | $items = [];
87 | foreach ($collection as $model) {
88 | $items[] = $model->getDataModel();
89 | }
90 |
91 | $searchResults->setItems($items);
92 | $searchResults->setTotalCount($collection->getSize());
93 | return $searchResults;
94 | }
95 |
96 | /**
97 | * @throws CouldNotDeleteException
98 | */
99 | public function delete({{ ModelName }}Interface ${{ VariableName }}): bool
100 | {
101 | try {
102 | ${{ VariableName }}Model = $this->{{ VariableName }}Factory->create();
103 | $this->resource->load(${{ VariableName }}Model, ${{ VariableName }}->getEntityId());
104 | $this->resource->delete(${{ VariableName }}Model);
105 | } catch (\Exception $exception) {
106 | throw new CouldNotDeleteException(__(
107 | 'Could not delete the {{ ModelName }}: %1',
108 | $exception->getMessage()
109 | ));
110 | }
111 | return true;
112 | }
113 |
114 | public function deleteById(int $id): bool
115 | {
116 | return $this->delete($this->get($id));
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/assets/stubs/Model/ModelNameRepositoryWithoutDataModel.php.stub:
--------------------------------------------------------------------------------
1 | resource->save($entity);
38 | } catch (\Exception $exception) {
39 | throw new CouldNotSaveException(__(
40 | 'Could not save the {{ VariableName }}: %1',
41 | $exception->getMessage()
42 | ));
43 | }
44 | return $entity;
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | public function get(int $id): {{ ModelName }}Interface
51 | {
52 | ${{ VariableName }} = $this->{{ VariableName }}Factory->create();
53 | $this->resource->load(${{ VariableName }}, $id);
54 | if (!${{ VariableName }}->getId()) {
55 | throw new NoSuchEntityException(__('{{ ModelName }} with id "%1" does not exist.', $id));
56 | }
57 | return ${{ VariableName }};
58 | }
59 |
60 | /**
61 | * {@inheritdoc}
62 | */
63 | public function getList(SearchCriteriaInterface $criteria): {{ ModelName }}SearchResultsInterface
64 | {
65 | $collection = $this->{{ VariableName }}CollectionFactory->create();
66 |
67 | $this->extensionAttributesJoinProcessor->process(
68 | $collection,
69 | {{ ModelName }}Interface::class
70 | );
71 |
72 | $this->collectionProcessor->process($criteria, $collection);
73 |
74 | $searchResults = $this->searchResultsFactory->create();
75 | $searchResults->setSearchCriteria($criteria);
76 |
77 | $items = [];
78 | foreach ($collection as $model) {
79 | $items[] = $model;
80 | }
81 |
82 | $searchResults->setItems($items);
83 | $searchResults->setTotalCount($collection->getSize());
84 | return $searchResults;
85 | }
86 |
87 | /**
88 | * {@inheritdoc}
89 | */
90 | public function delete({{ ModelName }}Interface $entity): bool
91 | {
92 | try {
93 | ${{ VariableName }}Model = $this->{{ VariableName }}Factory->create();
94 | $this->resource->load(${{ VariableName }}Model, $entity->getEntityId());
95 | $this->resource->delete(${{ VariableName }}Model);
96 | } catch (\Exception $exception) {
97 | throw new CouldNotDeleteException(__(
98 | 'Could not delete the {{ ModelName }}: %1',
99 | $exception->getMessage()
100 | ));
101 | }
102 | return true;
103 | }
104 |
105 | /**
106 | * {@inheritdoc}
107 | */
108 | public function deleteById(int $id): bool
109 | {
110 | return $this->delete($this->get($id));
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/assets/stubs/Model/ModelNameSearchResults.php.stub:
--------------------------------------------------------------------------------
1 | getData();
32 |
33 | $dataObject = $this->{{ VariableName }}DataFactory->create();
34 | $this->dataObjectHelper->populateWithArray(
35 | $dataObject,
36 | $data,
37 | {{ ModelName }}InterfaceFactory::class
38 | );
39 |
40 | return $dataObject;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/assets/stubs/Model/ModelNameWithoutDataModel.php.stub:
--------------------------------------------------------------------------------
1 | _init(ResourceModel\{{ ModelName }}::class);
17 | }
18 |
19 | {{#Columns}}
20 | /**
21 | * Get {{ fieldName }}
22 | * @return {{ phpType }}|null
23 | */
24 | public function get{{ functionName }}(): ?{{ phpType }}
25 | {
26 | return $this->getData(self::{{ fieldNameUppercase }});
27 | }
28 |
29 | /**
30 | * Set {{ fieldName }}
31 | * @param {{ phpType }} ${{ fieldName }}
32 | * @return {{ ModelName }}Interface
33 | */
34 | public function set{{ functionName }}({{ phpType }} ${{ fieldName }}): {{ ModelName }}Interface
35 | {
36 | return $this->setData(self::{{ fieldNameUppercase }}, ${{ fieldName }});
37 | }
38 |
39 | {{/Columns}}
40 | /**
41 | * Retrieve existing extension attributes object or create a new one.
42 | * @return {{ ModelName }}ExtensionInterface|null
43 | */
44 | public function getExtensionAttributes(): ?{{ ModelName }}ExtensionInterface
45 | {
46 | return $this->_getExtensionAttributes();
47 | }
48 |
49 | /**
50 | * Set an extension attributes object.
51 | * @param {{ ModelName }}ExtensionInterface $extensionAttributes
52 | * @return $this
53 | */
54 | public function setExtensionAttributes(
55 | {{ ModelName }}ExtensionInterface $extensionAttributes
56 | ): static {
57 | return $this->_setExtensionAttributes($extensionAttributes);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/assets/stubs/Model/ResourceModel/ModelName.php.stub:
--------------------------------------------------------------------------------
1 | _init(self::MAIN_TABLE, self::ID_FIELD_NAME);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/assets/stubs/Model/ResourceModel/ModelName/Collection.php.stub:
--------------------------------------------------------------------------------
1 | _init({{ ModelName }}::class, \{{ VendorName }}\{{ ModuleName }}\Model\ResourceModel\{{ ModelName }}::class);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/assets/stubs/Ui/Component/Listing/Column/Actions.php.stub:
--------------------------------------------------------------------------------
1 | urlBuilder = $urlBuilder;
41 | parent::__construct($context, $uiComponentFactory, $components, $data);
42 | }
43 |
44 | /**
45 | * @inheritDoc
46 | */
47 | public function prepareDataSource(array $dataSource)
48 | {
49 | if (isset($dataSource['data']['items'])) {
50 | foreach ($dataSource['data']['items'] as & $item) {
51 | if (isset($item['{{ IndexField }}'])) {
52 | $item[$this->getData('name')] = [
53 | 'edit' => [
54 | 'href' => $this->urlBuilder->getUrl(
55 | static::URL_PATH_EDIT,
56 | [
57 | '{{ IndexField }}' => $item['{{ IndexField }}']
58 | ]
59 | ),
60 | 'label' => __('Edit')
61 | ],
62 | 'delete' => [
63 | 'href' => $this->urlBuilder->getUrl(
64 | static::URL_PATH_DELETE,
65 | [
66 | '{{ IndexField }}' => $item['{{ IndexField }}']
67 | ]
68 | ),
69 | 'label' => __('Delete'),
70 | 'confirm' => [
71 | 'title' => __('Delete item'),
72 | 'message' => __('Are you sure you want to delete this record?')
73 | ],
74 | 'post' => true
75 | ],
76 | ];
77 | }
78 | }
79 | }
80 |
81 | return $dataSource;
82 | }
83 |
84 | /**
85 | * Get instance of escaper
86 | *
87 | * @return Escaper
88 | * @deprecated 101.0.7
89 | */
90 | private function getEscaper()
91 | {
92 | if (!$this->escaper) {
93 | // phpcs:ignore Magento2.PHP.AutogeneratedClassNotInConstructor
94 | $this->escaper = ObjectManager::getInstance()->get(Escaper::class);
95 | }
96 | return $this->escaper;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/assets/stubs/composer.json.stub:
--------------------------------------------------------------------------------
1 | {
2 | "name": "{{ VendorName }}/{{ ModuleName}}",
3 | "authors": [
4 | {
5 | "name": "{{ VendorName }}",
6 | "email": "user@example.com"
7 | },
8 | {
9 | "name": "model-generator.com",
10 | "email": "modelgenerator@controlaltdelete.nl"
11 | }
12 | ],
13 | "require": {
14 | "php": "~8.1"
15 | },
16 | "autoload": {
17 | "files": [
18 | "src/registration.php"
19 | ],
20 | "psr-4": {
21 | "{{ VendorName }}\\{{ ModuleName }}\\": "src/"
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/assets/stubs/etc/acl.xml.stub:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/assets/stubs/etc/adminhtml/menu.xml.stub:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/assets/stubs/etc/adminhtml/routes.xml.stub:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/assets/stubs/etc/di.xml.stub:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{#IncludeAdminGrid}}
7 |
8 |
9 |
10 |
11 | - {{ VirtualCollectionName }}
12 |
13 |
14 |
15 |
16 |
17 |
18 | {{ TableName }}
19 | {{ VendorName }}\{{ ModuleName }}\Model\ResourceModel\{{ ModelName }}
20 |
21 |
22 | {{/IncludeAdminGrid}}
23 |
24 |
--------------------------------------------------------------------------------
/assets/stubs/etc/module.xml.stub:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/assets/stubs/registration.php.stub:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/assets/stubs/view/adminhtml/layout/BaseName_index.xml.stub:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/assets/stubs/view/adminhtml/layout/BaseName_new.xml.stub:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/assets/stubs/view/adminhtml/ui_component/FormName.xml.stub:
--------------------------------------------------------------------------------
1 |
2 |
45 |
--------------------------------------------------------------------------------
/components/AdminGrid.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Admingrid
4 |
5 |
6 |
7 |
8 |
9 |
10 |
17 |
18 |
19 | Enabled
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
38 |
39 |
40 |
Bookmarks
43 |
44 | Allows users to sort columns and create specific views.
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
62 |
63 |
64 |
Paging
67 |
68 | Users get to navigate trough big data sets.
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
86 |
87 |
88 |
Sticky
91 |
Make the top bar sticky.
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
108 |
109 |
110 |
Search
113 |
Allow fulltext search.
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
130 |
131 |
132 |
Massactions
135 |
136 | Users can execute massactions (update, delete)
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
154 |
155 |
156 |
Filters
159 |
160 | Allow users to filter on specific fields
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
178 |
179 |
180 |
New button
183 |
184 | Add a new button to let user add a new entity
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
202 |
203 |
204 |
Add to admin menu
207 |
208 | Add a menu item to the admin menu for this model
209 |
210 |
211 |
214 | Select a parent item
215 | Sales
216 | Catalog
217 | Customers
218 | Marketing
219 | Content
220 | Reports
221 | Stores
222 | System
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
288 |
--------------------------------------------------------------------------------
/components/BottomSidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | What's this?
5 |
6 |
7 |
8 | With this app, you can create models for Magento 2 very fast. If you are
9 | not sure what a model in Magento 2 is, I can recommend you to read
10 | this article
16 | (TLDR: It's everything that you need to connect to your database).
17 |
18 |
19 |
20 | Writing models can become cumbersome, as you need to write a whole bunch
21 | of boilerplate code which is sort of the same for every model you need to
22 | make.
23 |
24 |
25 |
26 | This app tries to take the cumbersome out of your hands and generate the
27 | code for you.
28 |
29 |
30 |
31 | Is this the same as
32 | Mage2gen ?
33 |
34 |
35 |
36 | I've used Mage2gen many times myself and it does a great job, but when it
37 | comes to creating models I still had to do too much manual work to get
38 | everything right.
39 |
40 |
41 |
42 | Mage2gen offers a lot of automatic generated code and is great in doing
43 | that, but i wanted a more specialized tool on 2 parts that are key for me.
44 |
45 |
46 |
47 | This app only focuses on creating the models (and everything required for
48 | that) and the admingrids and does that thing well.
49 |
50 |
51 |
52 |
54 |
--------------------------------------------------------------------------------
/components/DefineModelColumns.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Model Columns
4 |
5 |
6 |
7 |
8 | Fieldname
9 |
10 |
11 |
12 |
13 |
14 | Input type
15 |
16 |
17 |
18 |
19 |
29 |
30 |
31 |
32 |
61 |
--------------------------------------------------------------------------------
/components/ModelColumn.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
20 |
21 |
22 |
33 |
38 |
39 |
40 |
41 |
42 |
46 | Fieldname
47 |
48 |
49 |
55 |
56 |
57 |
58 |
62 | Input type
63 |
64 |
65 |
71 | Please select
72 |
73 | Integer
74 | Smallint
75 | Bigint
76 |
77 |
78 | Float
79 | Decimal
80 |
81 |
82 | Text
83 | Varchar
84 |
85 |
86 | Datetime
87 | Timestamp
88 |
89 |
90 |
91 |
92 |
93 |
101 |
106 |
107 |
108 |
116 |
121 |
122 |
123 |
124 |
125 |
126 |
173 |
--------------------------------------------------------------------------------
/components/ModelInformation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Model Naming
4 |
5 |
6 |
7 |
8 | What's the classname for your model?
9 |
10 |
11 |
20 |
21 |
22 |
23 |
24 |
25 | What's the tablename for your model?
26 |
27 |
28 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
88 |
--------------------------------------------------------------------------------
/components/ModuleName.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | Reset
9 |
10 |
11 |
12 | Module Definition
13 |
14 |
15 |
16 |
17 |
18 | Vendor name
19 |
20 |
21 |
29 |
30 |
31 |
32 |
33 |
34 | Module name
35 |
36 |
37 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
58 |
59 |
60 |
64 | Include module registration (module.xml and registration.php)
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
80 |
81 |
82 |
83 | Use data models
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
137 |
--------------------------------------------------------------------------------
/components/ScriptModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
12 |
17 |
18 |
23 |
24 |
27 |
28 |
33 | Close
34 |
35 |
43 |
49 |
50 |
51 |
52 |
53 |
68 |
69 |
74 | Close
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
115 |
--------------------------------------------------------------------------------
/components/TopSidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
23 |
24 |
25 | Magento 2 Model Generator
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
39 |
40 | Include non-mergable files
41 |
44 |
53 |
58 |
59 |
60 |
61 |
62 |
63 |
68 | Download
69 |
70 |
71 |
72 |
73 | Before you can download your module make sure you enter your:
74 |
75 | Vendor Name (eg Acme)
76 | Module Name (eg Blog, Faq, StockReport)
77 | Model Name (eg Post, Item, StockItem)
78 |
79 |
80 |
81 |
82 |
83 |
173 |
--------------------------------------------------------------------------------
/components/TreeItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
16 | {{ item.CurrentPath.replace(/\/$/, '') }}
17 |
18 | {{ item.Contents }}
19 |
20 |
21 |
22 | {{ item.Name }}
23 |
24 |
25 |
26 | {{ item.Name }}
27 |
28 |
29 |
36 |
37 |
38 |
39 |
72 |
--------------------------------------------------------------------------------
/components/TreeView.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
19 |
--------------------------------------------------------------------------------
/cypress.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "cypress";
2 | import fs from 'fs';
3 |
4 | export default defineConfig({
5 | e2e: {
6 | setupNodeEvents(on) {
7 | // Disable videos for successful tests
8 | on('after:spec', (spec, results) => {
9 | // If a retry failed, save the video, otherwise delete it to save time by not compressing it.
10 | if (results && results.video) {
11 | // Do we have failures for any retry attempts?
12 | const failures = results.tests.find(test => {
13 | return test.attempts.find(attempt => {
14 | return attempt.state === 'failed'
15 | })
16 | });
17 |
18 | // Delete the video if the spec passed on all attempts
19 | if (!failures) {
20 | fs.existsSync(results.video) && fs.unlinkSync(results.video)
21 | }
22 | }
23 | })
24 | },
25 | },
26 | });
27 |
--------------------------------------------------------------------------------
/cypress/e2e/data-models.cy.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | describe('Test that we get the right output when using the data models options', () => {
3 | beforeEach(() => {
4 | cy.visit('/')
5 | })
6 |
7 | it('should give an option to use data models or not', () => {
8 | cy.get('[aria-labelledby=module-details]').contains('Use data models')
9 | })
10 |
11 | it('selecting the option should persist between refreshes', () => {
12 | cy.get(
13 | '[aria-labelledby=module-details] [name="includeDataModels"]'
14 | ).check()
15 |
16 | cy.reload()
17 |
18 | cy.get(
19 | '[aria-labelledby=module-details] [name="includeDataModels"]'
20 | ).should('be.checked')
21 | })
22 |
23 | it('should not include the data model', () => {
24 | cy.setValidData()
25 | cy.disableDataModels()
26 |
27 | cy.get('.is-dir-model .is-dir-data .is-file-blog-php').should('not.exist')
28 | })
29 |
30 | it('generates a class with the right contets when data models are disabled', () => {
31 | cy.setValidData()
32 | cy.disableDataModels()
33 |
34 | cy.get('.is-file-blog-php a').click()
35 | cy.get('[aria-labelledby="modal-title"]').contains(
36 | 'extends AbstractExtensibleObject'
37 | )
38 | })
39 |
40 | it.only('generates a class with the right contents when data models are enabled', () => {
41 | cy.setValidData()
42 | cy.enableDataModels()
43 |
44 | cy.get('.is-file-blog-php:first a').click()
45 | cy.get('[aria-labelledby="modal-title"]').contains(
46 | 'public function getDataModel()'
47 | )
48 | })
49 | })
50 |
--------------------------------------------------------------------------------
/cypress/e2e/fields.cy.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | describe('Test the input fields', () => {
4 | beforeEach(() => {
5 | cy.visit('/')
6 | })
7 |
8 | it('should show instructions when no input is given', () => {
9 | cy.get('[aria-labelledby=top-sidebar]').contains(
10 | 'Before you can download your module make sure you enter your'
11 | )
12 | })
13 |
14 | it('should show the generated files when entered the correct data', () => {
15 | cy.setValidData()
16 |
17 | cy.get('[aria-labelledby=top-sidebar]').contains(
18 | 'BlogRepositoryInterface.php'
19 | )
20 | })
21 |
22 | it('Should include the registration.php and module.xml when selected', () => {
23 | cy.setValidData()
24 |
25 | cy.get('[name="includeModuleRegistration"]').check()
26 |
27 | cy.get('[aria-labelledby=top-sidebar]').contains('registration.php')
28 |
29 | cy.get('[aria-labelledby=top-sidebar]').contains('module.xml')
30 | })
31 |
32 | it('Should show the controllers when admingrid is enabled', () => {
33 | cy.setValidData()
34 | cy.enableAdminGrid()
35 |
36 | cy.get('[aria-labelledby=top-sidebar]').contains('Controller')
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/cypress/e2e/file-output.cy.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | describe('Check that the generated output files are correct', () => {
4 | beforeEach(() => {
5 | cy.visit('/')
6 |
7 | cy.setValidData()
8 | })
9 |
10 | it('Has a SearchResultsInterface implementation', () => {
11 | cy.openFileByPath('Model/BlogSearchResults.php')
12 |
13 | cy.get('.code-contents').contains('implements BlogSearchResultsInterface')
14 | cy.get('.code-contents').contains('extends SearchResults')
15 | })
16 |
17 | it('Does not include fulltext when no text fields are added', () => {
18 | cy.enableAdminGrid()
19 | cy.enableSearch()
20 |
21 | cy.addField('title', 'Integer')
22 |
23 | cy.openFileByPath('etc/db_schema.xml')
24 |
25 | cy.get('.code-contents').should('not.contain', 'indexType="fulltext"')
26 | })
27 |
28 | it('Does include fulltext when a text field is added', () => {
29 | cy.enableAdminGrid()
30 | cy.enableSearch()
31 |
32 | cy.addField('title', 'Text')
33 |
34 | cy.openFileByPath('etc/db_schema.xml')
35 |
36 | cy.get('.code-contents').should('contain', 'indexType="fulltext"')
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/cypress/support/commands.ts:
--------------------------------------------------------------------------------
1 | ///
2 | // ***********************************************
3 | // This example commands.ts shows you how to
4 | // create various custom commands and overwrite
5 | // existing commands.
6 | //
7 | // For more comprehensive examples of custom
8 | // commands please read more here:
9 | // https://on.cypress.io/custom-commands
10 | // ***********************************************
11 | //
12 | //
13 | // -- This is a parent command --
14 | // Cypress.Commands.add('login', (email, password) => { ... })
15 | //
16 | //
17 | // -- This is a child command --
18 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
19 | //
20 | //
21 | // -- This is a dual command --
22 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
23 | //
24 | //
25 | // -- This will overwrite an existing command --
26 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
27 | //
28 | // declare global {
29 | // namespace Cypress {
30 | // interface Chainable {
31 | // login(email: string, password: string): Chainable
32 | // drag(subject: string, options?: Partial): Chainable
33 | // dismiss(subject: string, options?: Partial): Chainable
34 | // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable
35 | // }
36 | // }
37 | // }
38 |
39 | Cypress.Commands.add('setValidData', () => {
40 | cy.get('[name="vendor"]').type('Acme')
41 | cy.get('[name="module"]').type('FooBar')
42 | cy.get('[name="modelName"]').type('Blog')
43 | })
44 |
45 | Cypress.Commands.add('enableDataModels', () => {
46 | cy.get('[aria-labelledby=module-details] [name="includeDataModels"]').check()
47 | })
48 |
49 | Cypress.Commands.add('disableDataModels', () => {
50 | cy.get(
51 | '[aria-labelledby=module-details] [name="includeDataModels"]'
52 | ).uncheck()
53 | })
54 |
55 | Cypress.Commands.add('enableAdminGrid', () => {
56 | cy.get('[aria-labelledby=module-details] [name="enabled"]').check()
57 | })
58 |
59 | Cypress.Commands.add('disableAdminGrid', () => {
60 | cy.get('[aria-labelledby=module-details] [name="enabled"]').uncheck()
61 | })
62 |
63 | Cypress.Commands.add('enableSearch', () => {
64 | cy.get('[aria-labelledby=module-details] [name="search"]').check()
65 | })
66 |
67 | Cypress.Commands.add('addField', (fieldName: string, inputType: string) => {
68 | cy.get('[data-action="addRow"]').last().click()
69 | cy.get('[name="fieldname"]').last().type(fieldName)
70 | cy.get('[name="input_type"]').last().select(inputType)
71 | })
72 |
73 | Cypress.Commands.add('openFileByPath', (fileName: string) => {
74 | let selector = ''
75 | const parts = fileName.split('/')
76 | parts.forEach((part) => {
77 | const filename = part.replace('.', '-')
78 |
79 | selector +=
80 | ' ' +
81 | (part.includes('.') ? `> .is-file-${filename} a` : `.is-dir-${part} > ul`)
82 | })
83 |
84 | selector = selector.toLowerCase()
85 |
86 | cy.log('Path', fileName)
87 | cy.log('Selector', selector)
88 |
89 | cy.get(selector).should('be.visible').click()
90 | cy.get('#modal-title').should('contain', fileName).should('be.visible')
91 | })
92 |
--------------------------------------------------------------------------------
/cypress/support/e2e.ts:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/e2e.ts is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | import withNuxt from './.nuxt/eslint.config.mjs'
3 |
4 | export default withNuxt(
5 | // Your custom configs here
6 | )
7 |
--------------------------------------------------------------------------------
/functions/xml.ts:
--------------------------------------------------------------------------------
1 | import type AttributeInterface from '~/interfaces/AttributeInterface'
2 |
3 | export function createItem(
4 | xml: XMLDocument,
5 | name: string,
6 | type: string,
7 | content?: string,
8 | options?: Array
9 | ): HTMLElement {
10 | const item = xml.createElement('item')
11 | item.setAttribute('name', name)
12 | item.setAttribute('xsi:type', type)
13 |
14 | if (content) {
15 | item.innerHTML = content
16 | }
17 |
18 | if (options) {
19 | options.forEach((option) => {
20 | item.setAttribute(option.name, option.value)
21 | })
22 | }
23 |
24 | return item
25 | }
26 |
27 | const stringTimesN = (n: number, char: string) => Array(n + 1).join(char)
28 |
29 | // Adapted from https://gist.github.com/sente/1083506
30 | export function prettifyXml(xmlInput: string): string {
31 | const EOL = '\n'
32 | const indentString = stringTimesN(2, ' ')
33 |
34 | let formatted = ''
35 | const regex = /(>)(<)(\/*)/g
36 | const xml = xmlInput.replace(regex, `$1${EOL}$2$3`)
37 | let pad = 0
38 | xml.split(/\r?\n/).forEach((l) => {
39 | const line = l.trim()
40 |
41 | let indent = 0
42 | if (line.match(/.+<\/\w[^>]*>$/)) {
43 | indent = 0
44 | } else if (line.match(/^<\/\w/)) {
45 | // Somehow istanbul doesn't see the else case as covered, although it is. Skip it.
46 | /* istanbul ignore else */
47 | if (pad !== 0) {
48 | pad -= 1
49 | }
50 | } else if (line.match(/^<\w([^>]*[^/])?>.*$/)) {
51 | indent = 1
52 | } else {
53 | indent = 0
54 | }
55 |
56 | const padding = stringTimesN(pad, indentString)
57 | formatted += padding + line + EOL
58 | pad += indent
59 | })
60 |
61 | return formatted.trim()
62 | }
63 |
--------------------------------------------------------------------------------
/interfaces/AttributeInterface.ts:
--------------------------------------------------------------------------------
1 | export default interface AttributeInterface {
2 | name: string
3 | value: string
4 | }
5 |
--------------------------------------------------------------------------------
/interfaces/ColumnInterface.ts:
--------------------------------------------------------------------------------
1 | export default interface ColumnInterface {
2 | fieldName: string
3 | inputType: string
4 | }
5 |
--------------------------------------------------------------------------------
/interfaces/GeneratesFileInterface.ts:
--------------------------------------------------------------------------------
1 | export default interface GeneratesFileInterface {
2 | getPath(): string
3 |
4 | getContents(): string
5 |
6 | isMergeable(): boolean
7 | }
8 |
--------------------------------------------------------------------------------
/interfaces/GeneratesXmlInterface.ts:
--------------------------------------------------------------------------------
1 | export default interface GeneratesXmlInterface {
2 | getXml(xml: XMLDocument): HTMLElement
3 | }
4 |
--------------------------------------------------------------------------------
/interfaces/TreeItemInterface.ts:
--------------------------------------------------------------------------------
1 | export default interface TreeItemInterface {
2 | Name: string
3 | Contents: string | null
4 | Children: TreeItemInterface | null
5 | CurrentPath: string
6 | }
7 |
--------------------------------------------------------------------------------
/nuxt.config.ts:
--------------------------------------------------------------------------------
1 | // https://nuxt.com/docs/api/configuration/nuxt-config
2 |
3 | export default defineNuxtConfig({
4 | devtools: { enabled: true },
5 | ssr: false,
6 | modules: [
7 | '@nuxtjs/tailwindcss',
8 | '@pinia/nuxt',
9 | '@pinia-plugin-persistedstate/nuxt',
10 | "@nuxt/eslint",
11 | ...(process.env.NODE_ENV === 'production' ? ['nuxt-fathom'] : [])
12 | ],
13 |
14 | fathom: {
15 | siteId: "BKQYZZYA",
16 | },
17 | })
--------------------------------------------------------------------------------
/output/FileList.ts:
--------------------------------------------------------------------------------
1 | import StateAware from '~/output/StateAware'
2 | import GeneratedFile from '~/output/GeneratedFile'
3 | import UiComponent from '~/output/listing/UiComponent'
4 | import type GeneratesFileInterface from '~/interfaces/GeneratesFileInterface'
5 | import DbSchemaWhitelist from '~/output/listing/DbSchemaWhitelist'
6 | import DbSchema from '~/output/listing/DbSchema'
7 | import UiComponentForm from "~/output/listing/UiComponentForm";
8 |
9 | export default class FileList extends StateAware {
10 | generate() {
11 | const files = [
12 | new GeneratedFile('Api/ModelNameRepositoryInterface.php.stub'),
13 | new GeneratedFile('Api/Data/ModelNameSearchResultsInterface.php.stub'),
14 | new GeneratedFile('Api/Data/ModelNameInterface.php.stub'),
15 | new GeneratedFile('etc/di.xml.stub'),
16 | new GeneratedFile('Model/ModelNameSearchResults.php.stub'),
17 | new GeneratedFile('Model/ResourceModel/ModelName.php.stub'),
18 | new GeneratedFile('Model/ResourceModel/ModelName/Collection.php.stub'),
19 | new DbSchemaWhitelist(),
20 | new DbSchema(),
21 | ]
22 |
23 | if (this.isAdmingrid()) {
24 | this.addAdmingridFiles(files)
25 | }
26 |
27 | if (this.includeModuleRegistration()) {
28 | files.push(new GeneratedFile('etc/module.xml.stub'))
29 | files.push(new GeneratedFile('registration.php.stub'))
30 | files.push(new GeneratedFile('composer.json.stub'))
31 | }
32 |
33 | if (this.includeDataModels()) {
34 | files.push(new GeneratedFile('Model/ModelNameRepository.php.stub', 'Model/ModelNameRepositoryWithDataModel.php.stub'))
35 | files.push(new GeneratedFile('Model/ModelName.php.stub', 'Model/ModelNameWithDataModel.php.stub'))
36 | files.push(new GeneratedFile('Model/Data/ModelName.php.stub'))
37 | } else {
38 | files.push(new GeneratedFile('Model/ModelNameRepository.php.stub', 'Model/ModelNameRepositoryWithoutDataModel.php.stub'))
39 | files.push(new GeneratedFile('Model/ModelName.php.stub', 'Model/ModelNameWithoutDataModel.php.stub'))
40 | }
41 |
42 | return files
43 | }
44 |
45 | private addAdmingridFiles(files: GeneratesFileInterface[]) {
46 | files.push(new GeneratedFile('etc/acl.xml.stub'));
47 | files.push(new GeneratedFile('etc/adminhtml/routes.xml.stub'));
48 | files.push(new GeneratedFile('Controller/Adminhtml/ModelName/Edit.php.stub'));
49 | files.push(new GeneratedFile('Controller/Adminhtml/ModelName/Delete.php.stub'));
50 | files.push(new GeneratedFile('Controller/Adminhtml/ModelName/Index.php.stub'));
51 |
52 | files.push(new GeneratedFile('view/adminhtml/layout/BaseName_edit.xml.stub'))
53 | files.push(new GeneratedFile('view/adminhtml/layout/BaseName_new.xml.stub'))
54 | files.push(new GeneratedFile('Ui/Component/Listing/Column/Actions.php.stub'))
55 | files.push(new UiComponent())
56 |
57 | if (this.isMassActions()) {
58 | files.push(new GeneratedFile('Controller/Adminhtml/ModelName/MassDelete.php.stub'))
59 | }
60 |
61 | if (this.isNewButtonsEnabled()) {
62 | files.push(new GeneratedFile('Controller/Adminhtml/ModelName/Save.php.stub'))
63 | files.push(new GeneratedFile('view/adminhtml/layout/BaseName_index.xml.stub'))
64 | files.push(new GeneratedFile('Controller/Adminhtml/ModelName/NewAction.php.stub'))
65 | files.push(new GeneratedFile('Model/ModelName/DataProvider.php.stub'))
66 | files.push(new GeneratedFile('Block/Adminhtml/ModelName/Edit/SaveButton.php.stub'))
67 | files.push(new GeneratedFile('Block/Adminhtml/ModelName/Edit/DeleteButton.php.stub'))
68 | files.push(new GeneratedFile('Block/Adminhtml/ModelName/Edit/BackButton.php.stub'))
69 | files.push(new GeneratedFile('Block/Adminhtml/ModelName/Edit/GenericButton.php.stub'))
70 | files.push(new UiComponentForm('view/adminhtml/ui_component/FormName.xml.stub'))
71 | }
72 |
73 | if (this.addToMenu()) {
74 | files.push(new GeneratedFile('etc/adminhtml/menu.xml.stub'))
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/output/GeneratedFile.ts:
--------------------------------------------------------------------------------
1 | import Mustache from 'mustache'
2 | import StateAware from '~/output/StateAware'
3 | import type GeneratesFileInterface from '~/interfaces/GeneratesFileInterface'
4 | import ApiModelNameRepositoryInterfacePhpStub from '@/assets/stubs/Api/ModelNameRepositoryInterface.php.stub?raw'
5 | import ApiDataModelNameSearchResultsInterfacePhpStub from '@/assets/stubs/Api/Data/ModelNameSearchResultsInterface.php.stub?raw'
6 | import ApiDataModelNameInterfacePhpStub from '@/assets/stubs/Api/Data/ModelNameInterface.php.stub?raw'
7 | import BlockAdminhtmlModelNameEditSaveButtonphpstub from '@/assets/stubs/Block/Adminhtml/ModelName/Edit/SaveButton.php.stub?raw'
8 | import BlockAdminhtmlModelNameEditDeleteButtonphpstub from '@/assets/stubs/Block/Adminhtml/ModelName/Edit/DeleteButton.php.stub?raw'
9 | import BlockAdminhtmlModelNameEditBackButtonphpstub from '@/assets/stubs/Block/Adminhtml/ModelName/Edit/BackButton.php.stub?raw'
10 | import BlockAdminhtmlModelNameEditGenericButtonphpstub from '@/assets/stubs/Block/Adminhtml/ModelName/Edit/GenericButton.php.stub?raw'
11 | import etcdiXmlStub from '@/assets/stubs/etc/di.xml.stub?raw'
12 | import etcadminhtmlmenuXmlStub from '@/assets/stubs/etc/adminhtml/menu.xml.stub?raw'
13 | import etcadminhtmlroutesXmlStub from '@/assets/stubs/etc/adminhtml/routes.xml.stub?raw'
14 | import ModelModelNameSearchResultsPhpStub from '@/assets/stubs/Model/ModelNameSearchResults.php.stub?raw'
15 | import ModelResourceModelModelNamePhpStub from '@/assets/stubs/Model/ResourceModel/ModelName.php.stub?raw'
16 | import ModelResourceModelModelNameCollectionPhpStub from '@/assets/stubs/Model/ResourceModel/ModelName/Collection.php.stub?raw'
17 | import etcmoduleXmlStub from '@/assets/stubs/etc/module.xml.stub?raw'
18 | import registrationPhpStub from '@/assets/stubs/registration.php.stub?raw'
19 | import composerjsonstub from '@/assets/stubs/composer.json.stub?raw'
20 | import ModelModelNameRepositoryWithDataModelPhpStub from '@/assets/stubs/Model/ModelNameRepositoryWithDataModel.php.stub?raw'
21 | import ModelModelNameWithDataModelPhpStub from '@/assets/stubs/Model/ModelNameWithDataModel.php.stub?raw'
22 | import ModelModelNameRepositoryWithoutDataModelPhpStub from '@/assets/stubs/Model/ModelNameRepositoryWithoutDataModel.php.stub?raw'
23 | import ModelModelNameWithoutDataModelPhpStub from '@/assets/stubs/Model/ModelNameWithoutDataModel.php.stub?raw'
24 | import ModelDataModelNamePhpStub from '@/assets/stubs/Model/Data/ModelName.php.stub?raw'
25 | import ModelModelNameDataProviderphpstub from '@/assets/stubs/Model/ModelName/DataProvider.php.stub?raw'
26 | import etcaclXmlStub from '@/assets/stubs/etc/acl.xml.stub?raw'
27 | import ControllerAdminhtmlModelNameEditPhpStub from '@/assets/stubs/Controller/Adminhtml/ModelName/Edit.php.stub?raw'
28 | import ControllerAdminhtmlModelNameDeletePhpStub from '@/assets/stubs/Controller/Adminhtml/ModelName/Delete.php.stub?raw'
29 | import ControllerAdminhtmlModelNameIndexPhpStub from '@/assets/stubs/Controller/Adminhtml/ModelName/Index.php.stub?raw'
30 | import ControllerAdminhtmlModelNameSavephpstub from '@/assets/stubs/Controller/Adminhtml/ModelName/Save.php.stub?raw'
31 | import viewadminhtmllayoutBaseName_indexXmlStub from '@/assets/stubs/view/adminhtml/layout/BaseName_index.xml.stub?raw'
32 | import viewadminhtmllayoutBaseName_editXmlStub from '@/assets/stubs/view/adminhtml/layout/BaseName_edit.xml.stub?raw'
33 | import viewadminhtmllayoutBaseName_newXmlStub from '@/assets/stubs/view/adminhtml/layout/BaseName_new.xml.stub?raw'
34 | import viewadminhtmlui_componentFormNamexmlstub from '@/assets/stubs/view/adminhtml/ui_component/FormName.xml.stub?raw'
35 | import ControllerAdminhtmlModelNameMassDeletePhpStub from '@/assets/stubs/Controller/Adminhtml/ModelName/MassDelete.php.stub?raw'
36 | import ControllerAdminhtmlModelNameNewActionPhpStub from '@/assets/stubs/Controller/Adminhtml/ModelName/NewAction.php.stub?raw'
37 | import UiComponentListingColumnActionsphpstub from '@/assets/stubs/Ui/Component/Listing/Column/Actions.php.stub?raw'
38 |
39 | export default class GeneratedFile extends StateAware implements GeneratesFileInterface {
40 | private path: string
41 |
42 | private source: string | null
43 |
44 | constructor(path: string, source: string | null = null) {
45 | super()
46 |
47 | this.path = path
48 | this.source = source
49 |
50 | if (!source) {
51 | this.source = path
52 | }
53 | }
54 |
55 | getPath(): string {
56 | return this.path
57 | .replace(
58 | 'ModelName',
59 | this.capitalizeFirstLetter(this.snakeToCamel(this.modelName()))
60 | )
61 | .replace(
62 | 'FormName',
63 | this.formName()
64 | )
65 | .replace('BaseName', this.baseName())
66 | .replace('.stub', '')
67 | }
68 |
69 | getContents(): string {
70 | const mapping = {
71 | 'Api/ModelNameRepositoryInterface.php.stub': ApiModelNameRepositoryInterfacePhpStub,
72 | 'Api/Data/ModelNameSearchResultsInterface.php.stub': ApiDataModelNameSearchResultsInterfacePhpStub,
73 | 'Api/Data/ModelNameInterface.php.stub': ApiDataModelNameInterfacePhpStub,
74 | 'Block/Adminhtml/ModelName/Edit/SaveButton.php.stub': BlockAdminhtmlModelNameEditSaveButtonphpstub,
75 | 'Block/Adminhtml/ModelName/Edit/DeleteButton.php.stub': BlockAdminhtmlModelNameEditDeleteButtonphpstub,
76 | 'Block/Adminhtml/ModelName/Edit/BackButton.php.stub': BlockAdminhtmlModelNameEditBackButtonphpstub,
77 | 'Block/Adminhtml/ModelName/Edit/GenericButton.php.stub': BlockAdminhtmlModelNameEditGenericButtonphpstub,
78 | 'etc/di.xml.stub': etcdiXmlStub,
79 | 'etc/adminhtml/menu.xml.stub': etcadminhtmlmenuXmlStub,
80 | 'etc/adminhtml/routes.xml.stub': etcadminhtmlroutesXmlStub,
81 | 'Model/ModelNameSearchResults.php.stub': ModelModelNameSearchResultsPhpStub,
82 | 'Model/ResourceModel/ModelName.php.stub': ModelResourceModelModelNamePhpStub,
83 | 'Model/ResourceModel/ModelName/Collection.php.stub': ModelResourceModelModelNameCollectionPhpStub,
84 | 'etc/module.xml.stub': etcmoduleXmlStub,
85 | 'registration.php.stub': registrationPhpStub,
86 | 'composer.json.stub': composerjsonstub,
87 | 'Model/ModelNameRepositoryWithDataModel.php.stub': ModelModelNameRepositoryWithDataModelPhpStub,
88 | 'Model/ModelNameWithDataModel.php.stub': ModelModelNameWithDataModelPhpStub,
89 | 'Model/ModelNameRepositoryWithoutDataModel.php.stub': ModelModelNameRepositoryWithoutDataModelPhpStub,
90 | 'Model/ModelNameWithoutDataModel.php.stub': ModelModelNameWithoutDataModelPhpStub,
91 | 'Model/Data/ModelName.php.stub': ModelDataModelNamePhpStub,
92 | 'Model/ModelName/DataProvider.php.stub': ModelModelNameDataProviderphpstub,
93 | 'etc/acl.xml.stub': etcaclXmlStub,
94 | 'Controller/Adminhtml/ModelName/Edit.php.stub': ControllerAdminhtmlModelNameEditPhpStub,
95 | 'Controller/Adminhtml/ModelName/Delete.php.stub': ControllerAdminhtmlModelNameDeletePhpStub,
96 | 'Controller/Adminhtml/ModelName/Index.php.stub': ControllerAdminhtmlModelNameIndexPhpStub,
97 | 'view/adminhtml/layout/BaseName_index.xml.stub': viewadminhtmllayoutBaseName_indexXmlStub,
98 | 'view/adminhtml/layout/BaseName_edit.xml.stub': viewadminhtmllayoutBaseName_editXmlStub,
99 | 'view/adminhtml/layout/BaseName_new.xml.stub': viewadminhtmllayoutBaseName_newXmlStub,
100 | 'view/adminhtml/ui_component/FormName.xml.stub': viewadminhtmlui_componentFormNamexmlstub,
101 | 'Controller/Adminhtml/ModelName/MassDelete.php.stub': ControllerAdminhtmlModelNameMassDeletePhpStub,
102 | 'Controller/Adminhtml/ModelName/NewAction.php.stub': ControllerAdminhtmlModelNameNewActionPhpStub,
103 | 'Controller/Adminhtml/ModelName/Save.php.stub': ControllerAdminhtmlModelNameSavephpstub,
104 | 'Ui/Component/Listing/Column/Actions.php.stub': UiComponentListingColumnActionsphpstub,
105 | }
106 |
107 | if (this.source === null) {
108 | throw new Error(`No source found for ${this.source}`)
109 | }
110 |
111 | if (!mapping[this.source]) {
112 | throw new Error(`No stub found for ${this.source}`)
113 | }
114 |
115 | return Mustache.render(mapping[this.source], this.fileContext())
116 | }
117 |
118 | snakeToCamel(input: string) {
119 | return input.replace(/([-_][a-z])/g, (group) =>
120 | group.toUpperCase().replace('-', '').replace('_', '')
121 | )
122 | }
123 |
124 | capitalizeFirstLetter(input: string) {
125 | return input.charAt(0).toUpperCase() + input.slice(1)
126 | }
127 |
128 | isMergeable(): boolean {
129 | return ![
130 | 'composer.json.stub',
131 | 'registration.php',
132 | 'etc/acl.xml',
133 | 'etc/di.xml',
134 | 'etc/module.xml',
135 | 'etc/adminhtml/menu.xml',
136 | 'etc/adminhtml/routes.xml',
137 | ].includes(this.getPath())
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/output/StateAware.ts:
--------------------------------------------------------------------------------
1 | import type ColumnInterface from "~/interfaces/ColumnInterface";
2 | import { useModuleStore } from '@/stores/moduleStore'
3 | import type { ModuleStore } from '@/stores/moduleStore'
4 | import { useModelStore } from '@/stores/modelStore'
5 | import type { ModelStore } from '@/stores/modelStore'
6 | import { useAdminGridStore} from "@/stores/admingridStore";
7 | import type { AdminGridStore} from "@/stores/admingridStore";
8 | import { useTableStore} from "@/stores/tableStore";
9 | import type { TableStore} from "@/stores/tableStore";
10 |
11 | export default class StateAware {
12 | protected moduleStore: ModuleStore
13 | protected adminGridStore: AdminGridStore
14 | protected modelStore: ModelStore
15 | protected tableStore: TableStore
16 |
17 | constructor() {
18 | this.moduleStore = useModuleStore();
19 | this.adminGridStore = useAdminGridStore();
20 | this.modelStore = useModelStore();
21 | this.tableStore = useTableStore();
22 | }
23 |
24 | moduleName() {
25 | return this.moduleStore.moduleName
26 | }
27 |
28 | vendorName() {
29 | return this.moduleStore.vendorName
30 | }
31 |
32 | modelName(): string {
33 | return this.modelStore.name
34 | }
35 |
36 | baseName() {
37 | return (
38 | this.vendorName().toLowerCase() +
39 | '_' +
40 | this.moduleName().toLowerCase() +
41 | '_' +
42 | this.modelName().toLowerCase()
43 | )
44 | }
45 |
46 | moduleRegistration() {
47 | return `${this.moduleName()}_${this.vendorName()}`
48 | }
49 |
50 | listingName() {
51 | return `${this.baseName()}_listing`
52 | }
53 |
54 | formName() {
55 | return `${this.baseName()}_form`
56 | }
57 |
58 | columnsName() {
59 | return `${this.baseName()}_columns`
60 | }
61 |
62 | listingDataSource() {
63 | return `${this.listingName()}_data_source`
64 | }
65 |
66 | formDataSource() {
67 | return `${this.formName()}_data_source`
68 | }
69 |
70 | isNewButtonsEnabled() {
71 | return this.adminGridStore.newButton
72 | }
73 |
74 | routePath() {
75 | const moduleName = this.moduleStore.moduleName.toLowerCase()
76 | const vendorName = this.moduleStore.vendorName.toLowerCase()
77 |
78 | return `${vendorName}_${moduleName}`
79 | }
80 |
81 | routeFrontName() {
82 | const moduleName = this.moduleStore.moduleName.toLowerCase()
83 | const vendorName = this.moduleStore.vendorName.toLowerCase()
84 |
85 | return `${vendorName}-${moduleName}`
86 | }
87 |
88 | baseRoute() {
89 | const moduleName = this.moduleStore.moduleName.toLowerCase()
90 | const vendorName = this.moduleStore.vendorName.toLowerCase()
91 | const modelName = this.modelStore.name
92 |
93 | return `${vendorName}_${moduleName}/${modelName.toLowerCase()}/`
94 | }
95 |
96 | columns(): Array {
97 | return this.tableStore.columns.filter((column: ColumnInterface) => column.fieldName !== '')
98 | }
99 |
100 | formattedColumns(): Array {
101 | const columns = this.columns().map((column) => {
102 | return {
103 | fieldName: column.fieldName,
104 | inputType: column.inputType,
105 | fieldNameUppercase: column.fieldName.toUpperCase(),
106 | functionName: this.toPascalCase(column.fieldName),
107 | phpType: this.inputTypeToPhpType(column.inputType),
108 | }
109 | })
110 |
111 | if (!this.includeDataModels()) {
112 | return columns.filter((column) => column.fieldName !== 'entity_id')
113 | }
114 |
115 | return columns
116 | }
117 |
118 | inputTypeToPhpType(inputType: string): string {
119 | switch (inputType) {
120 | case 'text':
121 | case 'textarea':
122 | case 'select':
123 | case 'multiselect':
124 | case 'boolean':
125 | case 'date':
126 | case 'datetime':
127 | case 'time':
128 | case 'file':
129 | return 'string'
130 | case 'int':
131 | return 'int'
132 | case 'decimal':
133 | return 'float'
134 | default:
135 | return 'string'
136 | }
137 | }
138 |
139 | toPascalCase(input: string): string {
140 | input = input.replace(/([-_][a-z])/g, (group) =>
141 | group.toUpperCase().replace('-', '').replace('_', '')
142 | )
143 |
144 | return input.charAt(0).toUpperCase() + input.slice(1)
145 | }
146 |
147 | tableName(): string {
148 | return this.modelStore.tableName
149 | }
150 |
151 | indexField(): string {
152 | return this.columns()[0].fieldName
153 | }
154 |
155 | isAdmingrid(): boolean {
156 | return this.adminGridStore.enabled;
157 | }
158 |
159 | isSticky(): boolean {
160 | return this.adminGridStore.sticky
161 | }
162 |
163 | isPaging(): boolean {
164 | return this.adminGridStore.paging
165 | }
166 |
167 | isBookmarks(): boolean {
168 | return this.adminGridStore.bookmarks
169 | }
170 |
171 | isSearch(): boolean {
172 | return this.adminGridStore.search
173 | }
174 |
175 | isMassActions(): boolean {
176 | return this.adminGridStore.massactions
177 | }
178 |
179 | getIndexField(): string {
180 | return `${this.tableName()}_${this.indexField()}`.toUpperCase()
181 | }
182 |
183 | variableName(): string {
184 | const modelName = this.modelName()
185 | const firstLetter = modelName.charAt(0).toLowerCase()
186 |
187 | return firstLetter + modelName.slice(1)
188 | }
189 |
190 | includeModuleRegistration() {
191 | return this.moduleStore.includeModuleRegistration
192 | }
193 |
194 | includeDataModels() {
195 | return this.moduleStore.includeDataModels
196 | }
197 |
198 | addToMenu() {
199 | return this.adminGridStore.addToMenu
200 | }
201 |
202 | menuParent() {
203 | return this.adminGridStore.menuParent
204 | }
205 |
206 | virtualCollectionName() {
207 | return `${this.vendorName()}${this.moduleName()}${this.modelName()}Collection`
208 | }
209 |
210 | friendlyName(column: ColumnInterface) {
211 | return column.fieldName.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
212 | }
213 |
214 | fileContext() {
215 | return {
216 | ModuleName: this.moduleName(),
217 | VendorName: this.vendorName(),
218 | ModelName: this.modelName(),
219 | TableName: this.tableName(),
220 | IndexField: this.indexField(),
221 | ListingName: this.listingName(),
222 | FormName: this.formName(),
223 | VariableName: this.variableName(),
224 | Columns: this.formattedColumns(),
225 | BaseRoute: this.baseRoute(),
226 | RoutePath: this.routePath(),
227 | RouteFrontName: this.routeFrontName(),
228 | MenuParent: this.menuParent(),
229 | ModelPath: this.includeDataModels() ? 'Model\\Data' : 'Model',
230 | IncludeAdminGrid: this.isAdmingrid(),
231 | DataSource: this.listingDataSource(),
232 | FormDataSource: this.formDataSource(),
233 | VirtualCollectionName: this.virtualCollectionName(),
234 | }
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/output/listing/Column.ts:
--------------------------------------------------------------------------------
1 | import StateAware from '~/output/StateAware'
2 | import type ColumnInterface from '~/interfaces/ColumnInterface'
3 |
4 | export default class Column extends StateAware {
5 | getXml(xml: XMLDocument, data: ColumnInterface): HTMLElement {
6 | const column = xml.createElement('column')
7 | column.setAttribute('name', data.fieldName)
8 |
9 | const settings = xml.createElement('settings')
10 | column.appendChild(settings)
11 |
12 | const label = xml.createElement('label')
13 | label.setAttribute('translate', 'true')
14 | label.innerHTML = this.friendlyName(data)
15 | settings.appendChild(label)
16 |
17 | return column
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/output/listing/Columns.ts:
--------------------------------------------------------------------------------
1 | import StateAware from '~/output/StateAware'
2 | import type GeneratesXmlInterface from '~/interfaces/GeneratesXmlInterface'
3 | import type ColumnInterface from '~/interfaces/ColumnInterface'
4 | import Column from '~/output/listing/Column'
5 |
6 | export default class Columns extends StateAware implements GeneratesXmlInterface {
7 | getXml(xml: XMLDocument): HTMLElement {
8 | const columns = xml.createElement('columns')
9 | columns.setAttribute('name', this.columnsName())
10 |
11 | if (this.isMassActions()) {
12 | this.addSelectionsColumn(xml, columns)
13 | }
14 |
15 | this.columns().forEach((data: ColumnInterface) => {
16 | if (!data.fieldName || !data.inputType) {
17 | return
18 | }
19 |
20 | columns.appendChild(new Column().getXml(xml, data))
21 | })
22 |
23 | this.addActions(xml, columns)
24 |
25 | return columns
26 | }
27 |
28 | private addSelectionsColumn(xml: XMLDocument, columns: HTMLElement) {
29 | const selectionsColumn = xml.createElement('selectionsColumn')
30 | selectionsColumn.setAttribute('name', 'ids')
31 |
32 | const settings = xml.createElement('settings')
33 | selectionsColumn.appendChild(settings)
34 |
35 | const indexField = xml.createElement('indexField')
36 | indexField.innerHTML = 'id'
37 | settings.appendChild(indexField)
38 |
39 | columns.appendChild(selectionsColumn)
40 | }
41 |
42 | private addActions(xml: XMLDocument, columns: HTMLElement) {
43 | /**
44 | *
45 | *
46 | * block_id
47 | *
48 | *
49 | */
50 |
51 | const actionsColumn = xml.createElement('actionsColumn')
52 | actionsColumn.setAttribute('name', 'actions')
53 | actionsColumn.setAttribute(
54 | 'class',
55 | `${this.vendorName()}\\${this.moduleName()}\\Ui\\Component\\Listing\\Column\\Actions`
56 | )
57 |
58 | const settings = xml.createElement('settings')
59 | actionsColumn.appendChild(settings)
60 |
61 | const indexField = xml.createElement('indexField')
62 | indexField.innerHTML = this.indexField()
63 | settings.appendChild(indexField)
64 |
65 | columns.appendChild(actionsColumn)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/output/listing/DataArgument.ts:
--------------------------------------------------------------------------------
1 | import { createItem } from '~/functions/xml'
2 | import StateAware from '~/output/StateAware'
3 | import NewButton from '~/output/listing/NewButton'
4 | import type GeneratesXmlInterface from '~/interfaces/GeneratesXmlInterface'
5 |
6 | export default class DataArgument extends StateAware implements GeneratesXmlInterface {
7 | getXml(xml: XMLDocument): HTMLElement {
8 | const argument = xml.createElement('argument')
9 | argument.setAttribute('name', 'data')
10 | argument.setAttribute('xsi:type', 'array')
11 |
12 | const jsConfig = createItem(xml, 'js_config', 'array')
13 |
14 | // const provider = createItem(xml, 'provider', 'string', this.dataSource)
15 | const provider = createItem(
16 | xml,
17 | 'provider',
18 | 'string',
19 | `${this.listingName()}.${this.listingDataSource()}`
20 | )
21 | jsConfig.appendChild(provider)
22 | argument.appendChild(jsConfig)
23 |
24 | if (this.isNewButtonsEnabled()) {
25 | argument.appendChild(new NewButton().getXml(xml))
26 | }
27 |
28 | return argument
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/output/listing/DataSource.ts:
--------------------------------------------------------------------------------
1 | import StateAware from '~/output/StateAware'
2 | import type GeneratesXmlInterface from '~/interfaces/GeneratesXmlInterface'
3 |
4 | export default class DataSource extends StateAware implements GeneratesXmlInterface {
5 | getXml(xml: XMLDocument): HTMLElement {
6 | const dataSource = xml.createElement('dataSource')
7 | dataSource.setAttribute('name', this.listingDataSource())
8 | dataSource.setAttribute('component', 'Magento_Ui/js/grid/provider')
9 |
10 | this.addSettings(xml, dataSource)
11 | this.addAclResource(xml, dataSource)
12 | this.addDataProvider(xml, dataSource)
13 |
14 | return dataSource
15 | }
16 |
17 | /**
18 | * Example:
19 | * ```
20 | *
21 | *
22 | *
23 | * entity_id
24 | *
25 | *
26 | * ```
27 | *
28 | * @param xml
29 | * @param dataSource
30 | * @private
31 | */
32 | private addDataProvider(xml: XMLDocument, dataSource: HTMLElement) {
33 | const dataProvider = xml.createElement('dataProvider')
34 | dataProvider.setAttribute('name', this.listingDataSource())
35 | dataProvider.setAttribute('class', 'Magento\\Framework\\View\\Element\\UiComponent\\DataProvider\\DataProvider')
36 | dataSource.appendChild(dataProvider)
37 |
38 | const settings = xml.createElement('settings')
39 | dataProvider.appendChild(settings)
40 |
41 | const requestFieldName = xml.createElement('requestFieldName')
42 | requestFieldName.innerHTML = this.indexField()
43 | settings.appendChild(requestFieldName)
44 |
45 | const primaryFieldName = xml.createElement('primaryFieldName')
46 | primaryFieldName.innerHTML = this.indexField()
47 | settings.appendChild(primaryFieldName)
48 | }
49 |
50 | /**
51 | * Example:
52 | * ```
53 | * VendorName_ModuleName::ModelName
54 | * ```
55 | *
56 | * @param xml
57 | * @param dataSource
58 | * @private
59 | */
60 | private addAclResource(xml: XMLDocument, dataSource: HTMLElement) {
61 | const aclResource = xml.createElement('aclResource')
62 | aclResource.innerHTML = `${this.moduleRegistration()}::${this.modelName()}`
63 | dataSource.appendChild(aclResource)
64 | }
65 |
66 | /**
67 | * Example:
68 | * ```
69 | *
70 | *
71 | * entity_id
72 | * entity_id
73 | *
74 | *
75 | * ```
76 | *
77 | * @param xml
78 | * @param dataSource
79 | * @private
80 | */
81 | private addSettings(xml: XMLDocument, dataSource: HTMLElement) {
82 | const settings = xml.createElement('settings')
83 | dataSource.appendChild(settings)
84 |
85 | const updateUrl = xml.createElement('updateUrl')
86 | updateUrl.setAttribute('path', 'mui/index/render')
87 | settings.appendChild(updateUrl)
88 |
89 | const storageConfig = xml.createElement('storageConfig')
90 | settings.appendChild(storageConfig)
91 |
92 | const param = xml.createElement('param')
93 | param.setAttribute('name', 'indexField')
94 | param.setAttribute('xsi:type', 'string')
95 | param.innerHTML = this.indexField()
96 | storageConfig.appendChild(param)
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/output/listing/DbSchema.ts:
--------------------------------------------------------------------------------
1 | import StateAware from '~/output/StateAware'
2 | import type GeneratesFileInterface from '~/interfaces/GeneratesFileInterface'
3 | import { prettifyXml } from '~/functions/xml'
4 | import type ColumnInterface from '~/interfaces/ColumnInterface'
5 |
6 | export default class DbSchema extends StateAware implements GeneratesFileInterface {
7 | getContents(): string {
8 | const xml = document.implementation.createDocument(null, null, null)
9 | const schema = xml.createElement('schema')
10 | schema.setAttributeNS(
11 | 'http://www.w3.org/2001/XMLSchema-instance',
12 | 'xsi:noNamespaceSchemaLocation',
13 | 'urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd'
14 | )
15 |
16 | xml.appendChild(schema)
17 |
18 | const table = xml.createElement('table')
19 | table.setAttribute('name', this.tableName())
20 | table.setAttribute('engine', 'innodb')
21 | table.setAttribute('comment', this.modelName().toUpperCase() + 's')
22 | schema.appendChild(table)
23 |
24 | this.addColumns(xml, table)
25 |
26 | if (this.isSearch()) {
27 | this.addSearchField(xml, table)
28 | }
29 |
30 | const output = new XMLSerializer().serializeToString(xml)
31 | return '' + '\n' + prettifyXml(output)
32 | }
33 |
34 | getPath(): string {
35 | return 'etc/db_schema.xml'
36 | }
37 |
38 | isMergeable(): boolean {
39 | return false
40 | }
41 |
42 | private addColumns(xml: XMLDocument, table: HTMLTableElement) {
43 | let indexField: ColumnInterface | boolean = false
44 |
45 | this.columns().forEach((data: ColumnInterface) => {
46 | if (!data.fieldName || !data.inputType) {
47 | return
48 | }
49 |
50 | if (data.fieldName === this.indexField()) {
51 | indexField = data
52 | this.addIndexField(xml, table, data)
53 | return
54 | }
55 |
56 | const column = xml.createElement('column')
57 | column.setAttribute('name', data.fieldName)
58 | column.setAttribute('xsi:type', data.inputType)
59 | column.setAttribute('comment', data.fieldName)
60 | column.setAttribute('nullable', 'true')
61 |
62 | table.appendChild(column)
63 | })
64 |
65 | if (indexField) {
66 | this.addConstraint(xml, table, indexField)
67 | }
68 | }
69 |
70 | private addConstraint(xml: XMLDocument, table: HTMLTableElement, data: ColumnInterface) {
71 | const constraint = xml.createElement('constraint')
72 | constraint.setAttribute('xsi:type', 'primary')
73 | constraint.setAttribute('referenceId', 'PRIMARY')
74 |
75 | table.appendChild(constraint)
76 |
77 | const constraintColumn = xml.createElement('column')
78 | constraintColumn.setAttribute('name', data.fieldName)
79 |
80 | constraint.appendChild(constraintColumn)
81 | }
82 |
83 | private addIndexField(
84 | xml: XMLDocument,
85 | table: HTMLTableElement,
86 | data: ColumnInterface
87 | ) {
88 | const column = xml.createElement('column')
89 | column.setAttribute('name', data.fieldName)
90 | column.setAttribute('xsi:type', data.inputType)
91 | column.setAttribute('comment', data.fieldName)
92 | column.setAttribute('nullable', 'true')
93 | column.setAttribute('identity', 'true')
94 | column.setAttribute('unsigned', 'true')
95 | column.setAttribute('padding', '10')
96 |
97 | table.appendChild(column)
98 | }
99 |
100 | private addSearchField(xml: XMLDocument, table: HTMLTableElement) {
101 | const textColumns = this.columns().filter((data: ColumnInterface) => {
102 | return data.inputType === 'varchar' || data.inputType === 'text';
103 | });
104 |
105 | if (textColumns.length === 0) {
106 | return;
107 | }
108 |
109 | const index = xml.createElement('index')
110 | index.setAttribute('referenceId', this.getIndexField())
111 | index.setAttribute('indexType', 'fulltext')
112 | table.appendChild(index)
113 |
114 | textColumns.forEach((data: ColumnInterface) => {
115 | const column = xml.createElement('column')
116 | column.setAttribute('name', data.fieldName)
117 | index.appendChild(column)
118 | })
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/output/listing/DbSchemaWhitelist.ts:
--------------------------------------------------------------------------------
1 | import StateAware from '~/output/StateAware'
2 | import type GeneratesFileInterface from '~/interfaces/GeneratesFileInterface'
3 | import type ColumnInterface from '~/interfaces/ColumnInterface'
4 |
5 | export default class DbSchemaWhitelist extends StateAware implements GeneratesFileInterface {
6 | getContents(): string {
7 | interface Output {
8 | column: Record;
9 | constraint: {
10 | PRIMARY: boolean;
11 | };
12 | search?: Record;
13 | }
14 |
15 | const output: Output = {
16 | column: {},
17 | constraint: {
18 | PRIMARY: true,
19 | },
20 | }
21 |
22 | this.columns().forEach((data: ColumnInterface) => {
23 | if (!data.fieldName || !data.inputType) {
24 | return
25 | }
26 |
27 | output.column[data.fieldName] = true
28 | })
29 |
30 | if (this.isSearch()) {
31 | const searchKey = this.getIndexField()
32 | output.search = {}
33 | output.search[searchKey] = true
34 | }
35 |
36 | const tableName = this.tableName()
37 | const result: Record = {};
38 | result[tableName] = output
39 |
40 | return JSON.stringify(result, null, 4)
41 | }
42 |
43 | getPath(): string {
44 | return 'etc/db_schema_whitelist.json'
45 | }
46 |
47 | isMergeable(): boolean {
48 | return false
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/output/listing/ListingTop.ts:
--------------------------------------------------------------------------------
1 | import StateAware from '~/output/StateAware'
2 | import type GeneratesXmlInterface from '~/interfaces/GeneratesXmlInterface'
3 |
4 | export default class ListingTop extends StateAware implements GeneratesXmlInterface {
5 | getXml(xml: XMLDocument): HTMLElement {
6 | const listingToolbar = xml.createElement('listingToolbar')
7 | listingToolbar.setAttribute('name', 'listing_top')
8 |
9 | if (this.isSticky()) {
10 | this.addSticky(xml, listingToolbar)
11 | }
12 |
13 | if (this.isPaging()) {
14 | this.addPaging(xml, listingToolbar)
15 | }
16 |
17 | if (this.isBookmarks()) {
18 | this.addBookmarks(xml, listingToolbar)
19 | }
20 |
21 | if (this.isSearch()) {
22 | this.addFilterSearch(xml, listingToolbar)
23 | }
24 |
25 | if (this.isMassActions()) {
26 | this.addMassActions(xml, listingToolbar);
27 | }
28 |
29 | return listingToolbar
30 | }
31 |
32 | private addMassActions(xml: XMLDocument, listingToolbar: HTMLElement) {
33 | const massaction = xml.createElement('massaction')
34 | massaction.setAttribute('name', 'listing_massaction')
35 | massaction.setAttribute('component', 'Magento_Ui/js/grid/tree-massactions')
36 |
37 | this.addSettings(xml, massaction)
38 | this.addDeleteAction(xml, massaction)
39 |
40 | listingToolbar.appendChild(massaction)
41 | }
42 |
43 | /**
44 | * Example:
45 | * ```
46 | *
47 | *
48 | * delete
49 | * Delete
50 | *
51 | *
52 | * Delete items
53 | * Are you sure you want to delete the selected items?
54 | *
55 | *
56 | *
57 | * ```
58 | *
59 | * @param xml
60 | * @param massaction
61 | * @private
62 | */
63 | private addDeleteAction(xml: XMLDocument, massaction: HTMLElement) {
64 | const action = xml.createElement('action')
65 | action.setAttribute('name', 'delete')
66 | const settings = xml.createElement('settings')
67 | action.appendChild(settings)
68 | massaction.appendChild(action)
69 |
70 | const type = xml.createElement('type')
71 | type.innerHTML = 'delete'
72 | settings.appendChild(type)
73 |
74 | const label = xml.createElement('label')
75 | label.setAttribute('translate', 'true')
76 | label.innerHTML = 'Delete'
77 | settings.appendChild(label)
78 |
79 | const url = xml.createElement('url')
80 | url.setAttribute('path', '*/*/massDelete')
81 | settings.appendChild(url)
82 |
83 | const confirm = xml.createElement('confirm')
84 | settings.appendChild(confirm)
85 |
86 | const title = xml.createElement('title')
87 | title.setAttribute('translate', 'true')
88 | title.innerHTML = 'Delete items'
89 | confirm.appendChild(title)
90 |
91 | const message = xml.createElement('message')
92 | message.setAttribute('translate', 'true')
93 | message.innerHTML = 'Are you sure you want to delete the selected items?'
94 | confirm.appendChild(message)
95 | }
96 |
97 | /**
98 | * Example:
99 | * ```
100 | *
101 | * vendorname_modulename_modelname_listing.vendorname_modulename_modelname_listing.vendorname_modulename_modelname_columns.ids
102 | * id
103 | *
104 | * ```
105 | *
106 | * @param xml
107 | * @param massaction
108 | * @private
109 | */
110 | private addSettings(xml: XMLDocument, massaction: HTMLElement) {
111 | const settings = xml.createElement('settings')
112 | massaction.appendChild(settings)
113 |
114 | const selectProvider = xml.createElement('selectProvider')
115 | selectProvider.innerHTML = `${this.listingName()}.${this.listingName()}.${this.columnsName()}.ids`
116 | settings.appendChild(selectProvider)
117 |
118 | const indexField = xml.createElement('indexField')
119 | indexField.innerHTML = this.indexField()
120 | settings.appendChild(indexField)
121 | }
122 |
123 | /**
124 | * Example:
125 | * ```
126 | *
127 | * ```
128 | *
129 | * @param xml
130 | * @param listingToolbar
131 | * @private
132 | */
133 | private addFilterSearch(xml: XMLDocument, listingToolbar: HTMLElement) {
134 | const filterSearch = xml.createElement('filterSearch')
135 | filterSearch.setAttribute('name', 'fulltext')
136 | listingToolbar.appendChild(filterSearch)
137 | }
138 |
139 | /**
140 | * Example:
141 | * ```
142 | *
143 | * ```
144 | *
145 | * @param xml
146 | * @param listingToolbar
147 | * @private
148 | */
149 | private addBookmarks(xml: XMLDocument, listingToolbar: HTMLElement) {
150 | const bookmark = xml.createElement('bookmark')
151 | bookmark.setAttribute('name', 'bookmarks')
152 | listingToolbar.appendChild(bookmark)
153 | }
154 |
155 | /**
156 | * Example:
157 | * ```
158 | *
159 | * ```
160 | *
161 | * @param xml
162 | * @param listingToolbar
163 | * @private
164 | */
165 | private addPaging(xml: XMLDocument, listingToolbar: HTMLElement) {
166 | const paging = xml.createElement('paging')
167 | paging.setAttribute('name', 'listing_paging')
168 | listingToolbar.appendChild(paging)
169 | }
170 |
171 | /**
172 | * Example:
173 | * ```
174 | *
175 | * true
176 | *
177 | * ```
178 | *
179 | * @param xml
180 | * @param listingToolbar
181 | * @private
182 | */
183 | private addSticky(xml: XMLDocument, listingToolbar: HTMLElement) {
184 | const settings = xml.createElement('settings')
185 | const sticky = xml.createElement('sticky')
186 | sticky.innerHTML = 'true'
187 |
188 | settings.appendChild(sticky)
189 | listingToolbar.appendChild(settings)
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/output/listing/NewButton.ts:
--------------------------------------------------------------------------------
1 | import StateAware from '~/output/StateAware'
2 | import { createItem } from '~/functions/xml'
3 | import type GeneratesXmlInterface from '~/interfaces/GeneratesXmlInterface'
4 |
5 | export default class NewButton extends StateAware implements GeneratesXmlInterface {
6 | getXml(xml: XMLDocument): HTMLElement {
7 | const buttons = createItem(xml, 'buttons', 'array')
8 | const add = createItem(xml, 'add', 'array')
9 | buttons.appendChild(add)
10 |
11 | add.appendChild(createItem(xml, 'name', 'string', 'new'))
12 | add.appendChild(
13 | createItem(xml, 'label', 'string', `Add New ${this.modelName()} Item`, [
14 | { name: 'translate', value: 'true' },
15 | ])
16 | )
17 | add.appendChild(createItem(xml, 'url', 'string', this.baseRoute() + 'new'))
18 | const classNode = createItem(xml, 'class', 'string');
19 | classNode.innerHTML = 'primary';
20 | add.appendChild(classNode)
21 |
22 | return buttons
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/output/listing/Settings.ts:
--------------------------------------------------------------------------------
1 | import StateAware from '~/output/StateAware'
2 | import type GeneratesXmlInterface from '~/interfaces/GeneratesXmlInterface'
3 |
4 | export default class Settings extends StateAware implements GeneratesXmlInterface {
5 | getXml(xml: XMLDocument): HTMLElement {
6 | const settings = xml.createElement('settings')
7 | const deps = xml.createElement('deps')
8 | const dep = xml.createElement('dep')
9 | dep.innerHTML = `${this.listingName()}.${this.listingDataSource()}`
10 | deps.appendChild(dep)
11 | settings.appendChild(deps)
12 |
13 | const spinner = xml.createElement('spinner')
14 | spinner.innerHTML = `${this.baseName()}_columns`
15 | settings.appendChild(spinner)
16 |
17 | return settings
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/output/listing/UiComponent.ts:
--------------------------------------------------------------------------------
1 | import DataArgument from '~/output/listing/DataArgument'
2 | import Settings from '~/output/listing/Settings'
3 | import DataSource from '~/output/listing/DataSource'
4 | import ListingTop from '~/output/listing/ListingTop'
5 | import Columns from '~/output/listing/Columns'
6 | import StateAware from '~/output/StateAware'
7 | import { prettifyXml } from '~/functions/xml'
8 | import type GeneratesFileInterface from '~/interfaces/GeneratesFileInterface'
9 |
10 | export default class UiComponent extends StateAware implements GeneratesFileInterface{
11 | getContents(): string {
12 | const xml = document.implementation.createDocument(null, null, null)
13 | const listing = xml.createElement('listing')
14 | listing.setAttributeNS(
15 | 'http://www.w3.org/2001/XMLSchema-instance',
16 | 'xsi:noNamespaceSchemaLocation',
17 | 'urn:magento:module:Magento_Ui:etc/ui_configuration.xsd'
18 | )
19 |
20 | xml.appendChild(listing)
21 |
22 | listing.appendChild(new DataArgument().getXml(xml))
23 | listing.appendChild(new Settings().getXml(xml))
24 | listing.appendChild(new DataSource().getXml(xml))
25 | listing.appendChild(new ListingTop().getXml(xml))
26 | listing.appendChild(new Columns().getXml(xml))
27 |
28 | const output = new XMLSerializer().serializeToString(xml)
29 | return '' + '\n' + prettifyXml(output)
30 | }
31 |
32 | getPath(): string {
33 | return 'view/adminhtml/ui_component/' + this.listingName() + '.xml'
34 | }
35 |
36 | isMergeable(): boolean {
37 | return true
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/output/listing/UiComponentForm.ts:
--------------------------------------------------------------------------------
1 | import GeneratedFile from "~/output/GeneratedFile";
2 | import type ColumnInterface from "~/interfaces/ColumnInterface";
3 |
4 | export default class UiComponentForm extends GeneratedFile {
5 | override fileContext() {
6 | const fileContext = super.fileContext();
7 |
8 | fileContext.UiColumns = this.columns().map(column => this.generateUiColumn(column)).join('');
9 |
10 | return fileContext;
11 | }
12 |
13 | generateUiColumn(column: ColumnInterface): string {
14 | if (column.fieldName == this.indexField()) {
15 | return '';
16 | }
17 |
18 | if (column.inputType == 'text') {
19 | return this.columnType(column, 'textarea');
20 | }
21 |
22 | if (column.inputType == 'date') {
23 | return this.columnType(column, 'date');
24 | }
25 |
26 | return this.columnType(column, 'input');
27 | }
28 |
29 | private columnType(column: ColumnInterface, type: string) {
30 | return `
31 |
32 |
33 | -
34 |
- ${this.modelName()}
35 |
36 |
37 |
38 | text
39 | true
40 | ${this.friendlyName(column)}
41 |
42 |
43 | `;
44 | }
45 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nuxt-app",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "build": "nuxt build",
7 | "dev": "nuxt dev",
8 | "generate": "nuxt generate",
9 | "preview": "nuxt preview",
10 | "postinstall": "nuxt prepare",
11 | "lint": "eslint .",
12 | "lint:fix": "eslint . --fix",
13 | "cypress": "/bin/sh run-cypress.sh",
14 | "cypress-dev": "cypress open --config baseUrl=http://localhost:3000"
15 | },
16 | "dependencies": {
17 | "@nuxt/eslint": "^0.4.0",
18 | "@nuxtjs/tailwindcss": "^6.14.0",
19 | "@types/prismjs": "^1.26.5",
20 | "cypress": "^14.4.1",
21 | "eslint": "^9.27.0",
22 | "file-saver": "^2.0.5",
23 | "jszip": "^3.10.1",
24 | "mustache": "^4.2.0",
25 | "nuxt": "^3.12.2",
26 | "prism-es6": "^1.2.0",
27 | "prismjs": "^1.30.0",
28 | "serve": "^14.2.4",
29 | "typescript": "^5.8.3",
30 | "vue": "^3.5.16",
31 | "vue-router": "^4.5.1"
32 | },
33 | "devDependencies": {
34 | "@pinia-plugin-persistedstate/nuxt": "^1.2.1",
35 | "@pinia/nuxt": "^0.5.5",
36 | "nuxt-fathom": "^0.0.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/pages/README.md:
--------------------------------------------------------------------------------
1 | # PAGES
2 |
3 | This directory contains your Application Views and Routes.
4 | The framework reads all the `*.vue` files inside this directory and creates the router of your application.
5 |
6 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing).
7 |
--------------------------------------------------------------------------------
/pages/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 | Magento 2 Model Generator
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Magento 2 model generator
20 |
21 |
22 |
23 |
24 | Enter model details
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
61 |
62 |
63 |
64 |
96 |
97 |
98 |
99 |
106 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michielgerritsen/magento2-model-generator/59180118990e22a8ddce3c49126954fdf8c59c86/public/favicon.ico
--------------------------------------------------------------------------------
/run-cypress.sh:
--------------------------------------------------------------------------------
1 | echo "Running nuxt generate"
2 | nuxt generate
3 |
4 | echo "Running yarn preview"
5 | nohup yarn preview &
6 |
7 | echo "Running cypress install"
8 | cypress install
9 |
10 | echo "Running cypress run"
11 | cypress run --config baseUrl=http://localhost:3000
12 |
--------------------------------------------------------------------------------
/run-docker.sh:
--------------------------------------------------------------------------------
1 | docker build -t magento2-model-generator . && \
2 | docker run --rm -it -v $(pwd):/usr/src/nuxt-app --name m2mg -p 3344:3000 magento2-model-generator
3 |
--------------------------------------------------------------------------------
/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../.nuxt/tsconfig.server.json"
3 | }
4 |
--------------------------------------------------------------------------------
/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michielgerritsen/magento2-model-generator/59180118990e22a8ddce3c49126954fdf8c59c86/static/favicon.png
--------------------------------------------------------------------------------
/static/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michielgerritsen/magento2-model-generator/59180118990e22a8ddce3c49126954fdf8c59c86/static/icon.png
--------------------------------------------------------------------------------
/static/magento2-model-generator-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michielgerritsen/magento2-model-generator/59180118990e22a8ddce3c49126954fdf8c59c86/static/magento2-model-generator-logo.png
--------------------------------------------------------------------------------
/stores/admingridStore.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia'
2 |
3 | export const useAdminGridStore = defineStore('admingrid', {
4 | state: () => ({
5 | enabled: true,
6 | bookmarks: false,
7 | paging: true,
8 | sticky: true,
9 | search: false,
10 | massactions: false,
11 | filters: false,
12 | newButton: true,
13 | addToMenu: false,
14 | menuParent: '',
15 | }),
16 |
17 | actions: {
18 | setEnabled(value: boolean) {
19 | this.enabled = value
20 | },
21 | setBookmarks(value: boolean) {
22 | this.bookmarks = value
23 | },
24 | setPaging(value: boolean) {
25 | this.paging = value
26 | },
27 | setSticky(value: boolean) {
28 | this.sticky = value
29 | },
30 | setSearch(value: boolean) {
31 | this.search = value
32 | },
33 | setMassactions(value: boolean) {
34 | this.massactions = value
35 | },
36 | setFilters(value: boolean) {
37 | this.filters = value
38 | },
39 | setNewButton(value: boolean) {
40 | this.newButton = value
41 | },
42 | setAddToMenu(value: boolean) {
43 | this.addToMenu = value
44 | },
45 | setMenuParent(value: string) {
46 | this.menuParent = value
47 | },
48 | reset() {
49 | this.enabled = true
50 | this.bookmarks = false
51 | this.paging = true
52 | this.sticky = true
53 | this.search = false
54 | this.massactions = false
55 | this.filters = false
56 | this.newButton = true
57 | this.addToMenu = false
58 | this.menuParent = ''
59 | },
60 | },
61 |
62 | persist: true,
63 | })
64 |
65 | export type AdminGridStore = ReturnType;
66 |
--------------------------------------------------------------------------------
/stores/downloadStore.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia'
2 |
3 | export const useDownloadStore = defineStore('download', {
4 | state: () => ({
5 | includeNonMergable: true,
6 | }),
7 |
8 | actions: {
9 | setIncludeNonMergable(value: boolean) {
10 | this.includeNonMergable = value
11 | },
12 | reset() {
13 | this.includeNonMergable = true
14 | },
15 | },
16 |
17 | persist: true,
18 | });
19 |
20 | export type DownloadStore = ReturnType;
21 |
--------------------------------------------------------------------------------
/stores/fathomStore.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia'
2 |
3 | export const useFathomStore = defineStore('fathom', {
4 | state: () => ({
5 | triggered: false,
6 | }),
7 |
8 | actions: {
9 | setTriggered() {
10 | this.triggered = true
11 | },
12 | reset() {
13 | this.triggered = false
14 | },
15 | },
16 |
17 | persist: true,
18 | })
19 |
20 | export type FathomStore = ReturnType;
21 |
--------------------------------------------------------------------------------
/stores/modelStore.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia'
2 |
3 | export const useModelStore = defineStore('model', {
4 | state: () => ({
5 | name: '',
6 | tableName: '',
7 | }),
8 |
9 | actions: {
10 | setName(text: string) {
11 | this.name = text
12 | },
13 | setTableName(text: string) {
14 | this.tableName = text
15 | },
16 | reset() {
17 | this.name = ''
18 | this.tableName = ''
19 | },
20 | },
21 |
22 | persist: true,
23 | })
24 |
25 | export type ModelStore = ReturnType;
26 |
--------------------------------------------------------------------------------
/stores/moduleStore.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia'
2 |
3 | export const useModuleStore = defineStore('module', {
4 | state: () => ({
5 | vendorName: '',
6 | moduleName: '',
7 | includeModuleRegistration: false,
8 | includeDataModels: false,
9 | }),
10 |
11 | actions: {
12 | setVendorName(text: string) {
13 | this.vendorName = text
14 | },
15 | setModuleName(text: string) {
16 | this.moduleName = text
17 | },
18 | setIncludeModuleRegistration(value: boolean) {
19 | this.includeModuleRegistration = value
20 | },
21 | setIncludeDataModels(value: boolean) {
22 | this.includeDataModels = value
23 | },
24 | reset() {
25 | this.vendorName = ''
26 | this.moduleName = ''
27 | this.includeModuleRegistration = false
28 | this.includeDataModels = false
29 | },
30 | },
31 |
32 | persist: true,
33 | })
34 |
35 | export type ModuleStore = ReturnType;
36 |
--------------------------------------------------------------------------------
/stores/tableStore.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia'
2 | import type ColumnInterface from '~/interfaces/ColumnInterface'
3 |
4 | export const useTableStore = defineStore('table', {
5 | state: () => ({
6 | columns: [
7 | {
8 | fieldName: 'entity_id',
9 | inputType: 'int',
10 | },
11 | {
12 | fieldName: '',
13 | inputType: '',
14 | },
15 | ],
16 | }),
17 |
18 | actions: {
19 | moveColumnUp(column: ColumnInterface) {
20 | const index = this.columns.indexOf(column)
21 | if (index > 0) {
22 | const [removedColumn] = this.columns.splice(index, 1)
23 | this.columns.splice(index - 1, 0, removedColumn)
24 | }
25 | },
26 |
27 | moveColumnDown(column: ColumnInterface) {
28 | const index = this.columns.indexOf(column)
29 | if (index < this.columns.length - 1) {
30 | const [removedColumn] = this.columns.splice(index, 1)
31 | this.columns.splice(index + 1, 0, removedColumn)
32 | }
33 | },
34 |
35 | addColumn(column: ColumnInterface) {
36 | this.columns.push(column)
37 | },
38 |
39 | updateColumn(data: ColumnInterface) {
40 | this.columns.splice(data.index, 1, data.column)
41 | },
42 |
43 | removeColumn(column: ColumnInterface) {
44 | this.columns.splice(this.columns.indexOf(column), 1)
45 | },
46 |
47 | reset() {
48 | this.columns = [
49 | {
50 | fieldName: 'entity_id',
51 | inputType: 'int',
52 | },
53 | {
54 | fieldName: '',
55 | inputType: '',
56 | },
57 | ]
58 | },
59 | },
60 |
61 | persist: true,
62 | })
63 |
64 | export type TableStore = ReturnType;
65 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | // https://nuxt.com/docs/guide/concepts/typescript
3 | "extends": "./.nuxt/tsconfig.json"
4 | }
5 |
--------------------------------------------------------------------------------