@popina/cli 5.0.0-20260203082304Z → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,201 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import type { PlopTypes } from '@turbo/gen'
4
+
5
+ export const generatorIcon = (plop: PlopTypes.NodePlopAPI) => {
6
+ plop.setGenerator('icon', {
7
+ description: 'Create a new icon in @pragma/ui',
8
+ prompts: [
9
+ // Prompt the user to choose how to create the icon
10
+ {
11
+ type: 'list',
12
+ name: 'iconSource',
13
+ message: 'How do you want to create the icon?',
14
+ choices: [
15
+ '🔸 Import Lucide React from "https://lucide.dev/icons/"',
16
+ '🔹 Create Custom SVG',
17
+ ],
18
+ },
19
+ // Prompt to enter the name of the Lucide icon to import (if chosen)
20
+ {
21
+ type: 'input',
22
+ name: 'importedIconName',
23
+ message: 'Enter the name of the Lucide icon you want to import:',
24
+ when: (answers) =>
25
+ answers.iconSource ===
26
+ '🔸 Import Lucide React from "https://lucide.dev/icons/"',
27
+ validate: (input) => {
28
+ if (!input) {
29
+ return 'Icon name is required.'
30
+ }
31
+
32
+ // Trim and convert the icon name to PascalCase
33
+ const importedIconName = input.trim()
34
+ const pascalCaseImportedIconName =
35
+ importedIconName[0].toUpperCase() + importedIconName.slice(1)
36
+ const importedIconFileName = `${importedIconName}.js`
37
+
38
+ // Build file paths
39
+ const importedIconFilePath = path.join(
40
+ plop.getDestBasePath(),
41
+ '/node_modules/lucide-react/dist/esm/icons/' + importedIconFileName,
42
+ )
43
+ const iconIndexFilePath = path.join(
44
+ plop.getDestBasePath(),
45
+ 'packages/ui/src/icons/index.tsx',
46
+ )
47
+
48
+ // Check if the Lucide icon file exists
49
+ if (!fs.existsSync(importedIconFilePath)) {
50
+ return `The icon ${importedIconName} is not found in the Lucide React list. Please choose a different name or verify the icon exists. Refer to the website: 👉 https://lucide.dev/icons/ 👈`
51
+ }
52
+
53
+ // Check if the Lucide icon name already exists in the index.tsx file
54
+ if (fs.existsSync(iconIndexFilePath)) {
55
+ const iconIndexContent = fs.readFileSync(iconIndexFilePath, 'utf-8')
56
+ const iconIndexPattern = new RegExp(
57
+ `\\b${pascalCaseImportedIconName}\\b`,
58
+ )
59
+
60
+ if (iconIndexPattern.test(iconIndexContent)) {
61
+ return `The icon name "${pascalCaseImportedIconName}" already exists in the index.tsx file. Please choose a different name.`
62
+ }
63
+ }
64
+
65
+ return true
66
+ },
67
+ },
68
+ // Prompt to enter the name of the custom SVG icon (if chosen)
69
+ {
70
+ type: 'input',
71
+ name: 'customIconName',
72
+ message: 'What is the name of the icon you want to create?',
73
+ when: (answers) => answers.iconSource === '🔹 Create Custom SVG',
74
+ validate: (input) => {
75
+ if (!input) {
76
+ return 'Icon name is required.'
77
+ }
78
+
79
+ // Trim and convert the icon name to PascalCase
80
+ const pascalCaseCustomIconName = plop.getHelper('pascalCase')(input)
81
+
82
+ // Build the file path for the custom SVG icon
83
+ const customIconFilePath = path.join(
84
+ plop.getDestBasePath(),
85
+ 'packages/ui/src/icons/index.tsx',
86
+ )
87
+
88
+ // Check if the custom SVG icon file already exists
89
+ if (fs.existsSync(customIconFilePath)) {
90
+ const iconIndexContent = fs.readFileSync(
91
+ customIconFilePath,
92
+ 'utf-8',
93
+ )
94
+ const iconIndexPattern = new RegExp(
95
+ `\\b${pascalCaseCustomIconName}\\b`,
96
+ )
97
+
98
+ if (iconIndexPattern.test(iconIndexContent)) {
99
+ return `The icon name "${pascalCaseCustomIconName}" already exists in the index.tsx file. Please choose a different name.`
100
+ }
101
+ }
102
+
103
+ return true
104
+ },
105
+ },
106
+ ],
107
+ actions: function (answers) {
108
+ const actions: PlopTypes.Actions = []
109
+
110
+ if (answers && answers.iconSource === '🔹 Create Custom SVG') {
111
+ const iconName = plop.getHelper('pascalCase')(answers.customIconName)
112
+ const camelCaseCustomName = plop.getHelper('camelCase')(iconName)
113
+ const customIconFileName = `${iconName}.tsx`
114
+ const importStatement = `import { ${iconName} } from './${iconName}'`
115
+ const customIconFilePath = path.join(
116
+ plop.getDestBasePath(),
117
+ 'packages/ui/src/icons/' + customIconFileName,
118
+ )
119
+
120
+ // Add actions for '🔹 Create Custom SVG'
121
+ actions.push(
122
+ {
123
+ type: 'add',
124
+ path: customIconFilePath,
125
+ templateFile: 'templates/icon/icon-react.hbs',
126
+ },
127
+ {
128
+ type: 'modify',
129
+ path: '{{ turbo.paths.root }}/packages/ui/src/icons/index.tsx',
130
+ pattern: /(?=(import \{))(.+)(?=(\export type Icon = LucideIcon))/s,
131
+ template: `$2${importStatement}\n\n`,
132
+ },
133
+ {
134
+ type: 'modify',
135
+ path: '{{ turbo.paths.root }}/packages/ui/src/icons/index.tsx',
136
+ pattern: /(?<=(const \Icons \= \{))/s,
137
+ template: `\n ${camelCaseCustomName}: ${iconName},`,
138
+ },
139
+ )
140
+ }
141
+
142
+ if (
143
+ answers &&
144
+ answers.iconSource ===
145
+ '🔸 Import Lucide React from "https://lucide.dev/icons/"'
146
+ ) {
147
+ const importedIconName = answers.importedIconName
148
+
149
+ let pascalCaseImportedIconName: string
150
+
151
+ if (importedIconName.includes('-')) {
152
+ const splittedAnswer: string[] = answers.importedIconName.split('-') // ['air', 'vent']
153
+
154
+ pascalCaseImportedIconName = splittedAnswer.reduce<string>(
155
+ (acc, word) => {
156
+ return acc + word[0].toUpperCase() + word.slice(1)
157
+ },
158
+ '',
159
+ )
160
+ console.log(pascalCaseImportedIconName)
161
+ } else {
162
+ pascalCaseImportedIconName =
163
+ importedIconName[0].toUpperCase() + importedIconName.slice(1)
164
+ }
165
+
166
+ const importedIconFileName = `${importedIconName}.js`
167
+ const importedIconFilePath = path.join(
168
+ plop.getDestBasePath(),
169
+ '/node_modules/lucide-react/dist/esm/icons/' + importedIconFileName,
170
+ )
171
+
172
+ if (fs.existsSync(importedIconFilePath)) {
173
+ const importKey = `${pascalCaseImportedIconName[0].toLowerCase()}${pascalCaseImportedIconName.slice(
174
+ 1,
175
+ )}`
176
+ // Add actions for '🔸 Import Lucide React since "https://lucide.dev/icons/"'
177
+ actions.push(
178
+ {
179
+ type: 'modify',
180
+ path: '{{ turbo.paths.root }}/packages/ui/src/icons/index.tsx',
181
+ pattern: /(?=(import \{))(.*?)(?=(\} from 'lucide-react'))/s,
182
+ template: `$2 ${pascalCaseImportedIconName},\n`,
183
+ },
184
+ {
185
+ type: 'modify',
186
+ path: '{{ turbo.paths.root }}/packages/ui/src/icons/index.tsx',
187
+ pattern: /(?<=(const \Icons \= \{))/s,
188
+ template: `\n ${importKey}: ${pascalCaseImportedIconName},`,
189
+ },
190
+ )
191
+ } else {
192
+ console.error(
193
+ `The file "${importedIconFileName}" doesn't exist in the specified path.`,
194
+ )
195
+ }
196
+ }
197
+
198
+ return actions
199
+ },
200
+ })
201
+ }
@@ -0,0 +1,46 @@
1
+ import { RouteHandler } from 'fastify'
2
+
3
+ import type {
4
+ {{#each endpointActions}}
5
+ {{this.typeName}},
6
+ {{/each}}
7
+ } from './{{ getEntityFromPath modulePath}}.schema'
8
+
9
+ import {
10
+ {{#each endpointActions}}
11
+ {{this.actionName}},
12
+ {{/each}}
13
+ } from './{{ getEntityFromPath modulePath}}.service'
14
+
15
+ import {
16
+ {{!-- Counting on prettier to format your code correctly and remove this if empty 🫠 --}}
17
+ {{#each endpointActions}}
18
+ {{#if this.isAParamRoute}}
19
+ {{ pascalCase this.entityName }}IdType,
20
+ {{/if}}
21
+ {{/each}}
22
+ } from '@pragma/common/server'
23
+
24
+ {{#each endpointActions}}
25
+ export const {{this.actionName}}Handler: RouteHandler<{
26
+ Reply: {{this.typeName}}
27
+ {{#if this.isAParamRoute}}
28
+ Params: {{ pascalCase this.entityName }}IdType
29
+ }> = (req, reply) => {
30
+ const {{this.entity}}Id = req.params.id
31
+ const { id, createdAt, updatedAt } = {{this.actionName}}({{this.entity}}Id)
32
+
33
+ reply.code({{this.responseCode}}).send({
34
+ id: id,
35
+ createdAt: createdAt,
36
+ updatedAt: updatedAt,
37
+ })
38
+ {{else}}
39
+ }> = (_, reply) => {
40
+ const payload = {{this.actionName}}()
41
+
42
+ reply.code({{this.responseCode}}).send(payload)
43
+ {{/if}}
44
+ }
45
+
46
+ {{/each}}
@@ -0,0 +1,22 @@
1
+ import { buildJsonSchemas } from '@pragma/common/server'
2
+ import { z } from 'zod'
3
+
4
+ {{#each endpointActions}}
5
+ {{#if this.isAParamRoute}}
6
+ const {{this.entityName}}IdSchema = z.object({
7
+ id: z.string({ description: '{{ pascalCase this.entityName }} id path parameter' }).uuid(),
8
+ })
9
+
10
+ export type {{ pascalCase this.entityName }}IdType = z.infer<typeof {{this.entityName}}IdSchema>
11
+
12
+ export const { schemas: {{this.entityName}}IdParamSchema, $ref: {{this.entityName}}ParamRef } =
13
+ buildJsonSchemas(
14
+ {
15
+ {{this.entityName}}IdSchema,
16
+ },
17
+ { $id: '{{this.entityName}}_id' },
18
+ )
19
+
20
+ export const with{{ pascalCase this.entityName }}Param = {{this.entityName}}ParamRef('{{this.entityName}}IdSchema')
21
+ {{/if}}
22
+ {{/each}}
@@ -0,0 +1,28 @@
1
+ import { FastifyPluginAsync } from 'fastify'
2
+
3
+ import {
4
+ {{#each endpointActions}}
5
+ {{this.actionName}}Handler,
6
+ {{/each}}
7
+ } from './{{ getEntityFromPath modulePath }}.controller'
8
+
9
+ import {
10
+ {{#each endpointActions}}
11
+ {{this.fastifySchemaName}},
12
+ {{/each}}
13
+ } from './{{ getEntityFromPath modulePath}}.schema'
14
+
15
+ const {{ getEntityFromPath modulePath }}: FastifyPluginAsync = async (fastify): Promise<void> => {
16
+ {{#each endpointActions}}
17
+ fastify.{{this.verb}}(
18
+ '{{this.route}}',
19
+ {
20
+ schema: {{this.fastifySchemaName}},
21
+ config: { authorize: 'SUPER_ADMIN_ONLY' },
22
+ },
23
+ {{this.actionName}}Handler,
24
+ )
25
+ {{/each}}
26
+ }
27
+
28
+ export default {{ getEntityFromPath modulePath }}
@@ -0,0 +1,90 @@
1
+ import { FastifySchema } from 'fastify'
2
+ import { buildJsonSchemas } from '@pragma/common/server'
3
+ import z from 'zod'
4
+
5
+ import {
6
+ {{!-- Counting on prettier to format your code correctly and remove this if empty 🫠 --}}
7
+ {{#each endpointActions}}
8
+ {{#if this.isAParamRoute}}
9
+ with{{pascalCase this.entityName}}Param,
10
+ {{/if}}
11
+ {{/each}}
12
+ } from '@pragma/common/server'
13
+
14
+
15
+ // import { bindExamples } from '@pragma/common/shared'
16
+
17
+ const {{ upperCase ( getEntityFromPath modulePath ) }}_TAG = { tags: ['{{ pascalCase ( getEntityFromPath modulePath ) }}'] }
18
+
19
+ /**
20
+ * Zod elementary models definitions.
21
+ */
22
+ {{#each endpointActions}}
23
+ const {{this.zodElementarySchemaDefinition}} = {
24
+ id: z.string({ description: '{{ pascalCase this.entityName }} ID' }).uuid(),
25
+ createdAt: z.date({ description: '{{ pascalCase this.entityName }} creation date in the DB' }),
26
+ updatedAt: z.date({ description: '{{ pascalCase this.entityName }} last update date in the DB' }),
27
+ }
28
+
29
+ {{/each}}
30
+ /**
31
+ * Zod schema definitions.
32
+ */
33
+ {{#each endpointActions}}
34
+ export const {{ this.zodSchemaDefinitionName }} = {{#unless this.isAParamRoute}}z.array({{/unless}}z.object({{ this.zodElementarySchemaDefinition }}, {
35
+ description: '{{ pascalCase this.entityName }}',
36
+ }){{#unless this.isAParamRoute}}){{/unless}}
37
+
38
+ {{/each}}
39
+ /**
40
+ * Generate types from zod schemas.
41
+ */
42
+ {{#each endpointActions}}
43
+ export type {{this.typeName}} = z.infer<typeof {{this.zodSchemaDefinitionName}}>
44
+ {{/each}}
45
+
46
+ /**
47
+ * Examples of schemas from types definitions.
48
+ */
49
+ // const schemaExamples = {
50
+ {{#each endpointActions}}
51
+ // {{this.actionName}}SchemaExample,
52
+ {{/each}}
53
+ // }
54
+
55
+ /**
56
+ * Generate JSON schemas from zod schemas.
57
+ */
58
+ export const { schemas: {{ getEntityFromPath modulePath }}Schemas, $ref: {{ getEntityFromPath modulePath }}Ref } = buildJsonSchemas(
59
+ {
60
+ {{#each endpointActions}}
61
+ {{this.zodSchemaDefinitionName}},
62
+ {{/each}}
63
+ },
64
+ { $id: '{{ getEntityFromPath modulePath }}Schemas' },
65
+ )
66
+
67
+ /**
68
+ * Bind examples to JSON schemas.
69
+ */
70
+ // bindExamples({{ getEntityFromPath modulePath }}Schemas, schemaExamples)
71
+
72
+ /**
73
+ * Export Fastify schemas.
74
+ */
75
+ {{#each endpointActions}}
76
+ export const {{this.fastifySchemaName}}: FastifySchema = {
77
+ ...{{ upperCase ( getEntityFromPath ../this.modulePath ) }}_TAG,
78
+ description: '{{this.fastifyDescription}}',
79
+ summary: '{{this.fastifySummary}}',
80
+ operationId: '{{this.fastifyOperationId}}',
81
+ security: [{ apiKey: [] }],
82
+ hide: true,
83
+ response: {
84
+ {{this.responseCode}}: {{ getEntityFromPath ../this.modulePath }}Ref('{{this.zodSchemaDefinitionName}}'),
85
+ },
86
+ {{#if this.isAParamRoute}}
87
+ params: with{{pascalCase this.entityName}}Param,
88
+ {{/if}}
89
+ }
90
+ {{/each}}
@@ -0,0 +1,42 @@
1
+ import {
2
+ {{#each endpointActions}}
3
+ type {{this.typeName}},
4
+ {{/each}}
5
+ } from './{{ getEntityFromPath modulePath}}.schema'
6
+
7
+ import { faker } from '@faker-js/faker'
8
+
9
+ {{#each endpointActions}}
10
+ export function {{this.actionName}}(
11
+ {{#if this.isAParamRoute}}
12
+ id: string
13
+ {{/if}}
14
+ ): {{this.typeName}} {
15
+ {{#if this.isAParamRoute}}
16
+ return {
17
+ id: id,
18
+ createdAt: new Date(),
19
+ updatedAt: new Date(),
20
+ }
21
+ {{else}}
22
+ return [
23
+ {
24
+ id: faker.string.uuid(),
25
+ createdAt: new Date(),
26
+ updatedAt: new Date(),
27
+ },
28
+ {
29
+ id: faker.string.uuid(),
30
+ createdAt: new Date(),
31
+ updatedAt: new Date(),
32
+ },
33
+ {
34
+ id: faker.string.uuid(),
35
+ createdAt: new Date(),
36
+ updatedAt: new Date(),
37
+ },
38
+ ]
39
+ {{/if}}
40
+ }
41
+
42
+ {{/each}}
@@ -0,0 +1 @@
1
+ export * from './{{pascalCase componentName}}'
@@ -0,0 +1,14 @@
1
+ import React from 'react'
2
+
3
+ interface {{pascalCase componentName}}Props {
4
+ /**
5
+ * TODO: Change this
6
+ */
7
+ name?: 'change me'
8
+ }
9
+
10
+ const {{pascalCase componentName}} = ({ name }: {{pascalCase componentName}}Props) => {
11
+ return <div>{name}</div>
12
+ }
13
+
14
+ export { {{pascalCase componentName}} }
@@ -0,0 +1,21 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import React from 'react'
3
+
4
+ import { {{pascalCase componentName}} } from './{{pascalCase componentName}}'
5
+
6
+ const meta: Meta<typeof {{pascalCase componentName}}> = {
7
+ title: 'Components/{{pascalCase componentName}}', //Todo: Change the section name ex. 'Components/DataDisplay/componentName'
8
+ component: {{pascalCase componentName}},
9
+ decorators: [
10
+ (Story) => (
11
+ <div className="h-full w-full">
12
+ <Story />
13
+ </div>
14
+ ),
15
+ ],
16
+ }
17
+ export default meta
18
+ type Story = StoryObj<typeof {{pascalCase componentName}}>
19
+ export const Base: Story = {
20
+ args: {},
21
+ }
@@ -0,0 +1,14 @@
1
+ import '@testing-library/jest-dom'
2
+
3
+ import { render, screen } from '@testing-library/react'
4
+ import React from 'react'
5
+
6
+ import { {{pascalCase componentName}} } from './{{pascalCase componentName}}'
7
+
8
+ describe('{{pascalCase componentName}} Component', () => {
9
+ test('should render the {{pascalCase componentName}} Component', () => {
10
+ render(<{{pascalCase componentName}} name="change me" />)
11
+ const component = screen.getByText('change me')
12
+ expect(component).toBeInTheDocument()
13
+ })
14
+ })
@@ -0,0 +1,32 @@
1
+ import React from 'react'
2
+
3
+ interface {{pascalCase customIconName}}Props extends React.SVGProps<SVGSVGElement> {
4
+ ref?: React.Ref<SVGSVGElement>;
5
+ }
6
+
7
+ export const {{pascalCase customIconName}} =
8
+ ({ ref, className, ...props }: {{pascalCase customIconName}}Props) => {
9
+ return (
10
+ <svg
11
+ xmlns="http://www.w3.org/2000/svg"
12
+ width="24"
13
+ height="24"
14
+ viewBox="0 0 24 24"
15
+ fill="none"
16
+ stroke="#f62992"
17
+ strokeWidth="2"
18
+ strokeLinecap="round"
19
+ strokeLinejoin="round">
20
+ <path d="M18 6a4 4 0 0 0-4 4 7 7 0 0 0-7 7c0-5 4-5 4-10.5a4.5 4.5 0 1 0-9 0 2.5 2.5 0 0 0 5 0C7 10 3 11 3 17c0 2.8 2.2 5 5 5h10" />
21
+ <path d="M16 20c0-1.7 1.3-3 3-3h1a2 2 0 0 0 2-2v-2a4 4 0 0 0-4-4V4" />
22
+ <path d="M15.2 22a3 3 0 0 0-2.2-5" />
23
+ <path d="M18 13h.01" />
24
+ </svg>
25
+ )
26
+ }
27
+ /**
28
+ * TODO: Then add in svg
29
+ * ref={ref}
30
+ * className={props.className}
31
+ * fill="currentColor"
32
+ */
package/build/index.d.ts DELETED
@@ -1 +0,0 @@
1
- #!/usr/bin/env node