@donotdev/cli 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -18
- package/dependencies-matrix.json +54 -45
- package/dist/bin/commands/build.js +19 -5
- package/dist/bin/commands/bump.js +30 -5
- package/dist/bin/commands/cacheout.js +36 -19
- package/dist/bin/commands/create-app.js +73 -7
- package/dist/bin/commands/create-project.js +90 -8
- package/dist/bin/commands/deploy.js +130 -27
- package/dist/bin/commands/dev.js +23 -7
- package/dist/bin/commands/emu.js +28 -12
- package/dist/bin/commands/format.js +39 -22
- package/dist/bin/commands/lint.js +36 -19
- package/dist/bin/commands/preview.js +24 -8
- package/dist/bin/commands/sync-secrets.js +19 -5
- package/dist/bin/dndev.js +7 -20
- package/dist/bin/donotdev.js +7 -20
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +209 -100
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/templates/app-next/src/config/app.ts.example +1 -1
- package/templates/app-vite/index.html.example +24 -2
- package/templates/app-vite/src/config/app.ts.example +1 -1
- package/templates/app-vite/src/pages/FormPageExample.tsx.example +8 -5
- package/templates/app-vite/src/pages/ListPageExample.tsx.example +4 -7
- package/templates/root-consumer/.firebaserc.example +5 -0
- package/templates/root-consumer/entities/ExampleEntity.ts.example +2 -1
- package/templates/root-consumer/entities/demo.ts.example +15 -1
- package/templates/root-consumer/eslint.config.js.example +2 -80
- package/templates/root-consumer/firestore.indexes.json.example +4 -0
- package/templates/root-consumer/firestore.rules.example +11 -0
- package/templates/root-consumer/guides/dndev/COMPONENTS_CRUD.md.example +9 -6
- package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +376 -38
- package/templates/root-consumer/guides/dndev/SETUP_I18N.md.example +46 -0
- package/templates/root-consumer/guides/wai-way/entity_patterns.md.example +1 -1
- package/templates/root-consumer/storage.rules.example +8 -0
|
@@ -16,8 +16,13 @@
|
|
|
16
16
|
<!-- ✅ PWA: Manifest link (if exists) -->
|
|
17
17
|
<link rel="manifest" href="/manifest.json" />
|
|
18
18
|
|
|
19
|
-
<!-- ✅ PERFORMANCE:
|
|
20
|
-
<link rel="
|
|
19
|
+
<!-- ✅ PERFORMANCE: Preload critical fonts (non-blocking) -->
|
|
20
|
+
<link rel="preload" href="/fonts/Inter-latin.woff2" as="font" type="font/woff2" crossorigin="anonymous">
|
|
21
|
+
<link rel="preload" href="/fonts/Roboto-400-latin.woff2" as="font" type="font/woff2" crossorigin="anonymous">
|
|
22
|
+
|
|
23
|
+
<!-- ✅ PERFORMANCE: Load extended font subsets async (non-blocking) -->
|
|
24
|
+
<link rel="stylesheet" href="/fonts/fonts.css" media="print" onload="this.media='all'">
|
|
25
|
+
<noscript><link rel="stylesheet" href="/fonts/fonts.css"></noscript>
|
|
21
26
|
|
|
22
27
|
<!-- ✅ PERFORMANCE: Preconnect to external domains (OAuth providers) -->
|
|
23
28
|
<!-- GitHub OAuth -->
|
|
@@ -36,6 +41,23 @@
|
|
|
36
41
|
|
|
37
42
|
<!-- ✅ PERFORMANCE: Critical CSS inlined here by build -->
|
|
38
43
|
<style>
|
|
44
|
+
/* Critical @font-face declarations - must be inline for preloaded fonts to work */
|
|
45
|
+
@font-face {
|
|
46
|
+
font-family: Inter;
|
|
47
|
+
font-style: normal;
|
|
48
|
+
font-weight: 400 700;
|
|
49
|
+
font-display: swap;
|
|
50
|
+
src: url('/fonts/Inter-latin.woff2') format('woff2');
|
|
51
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
52
|
+
}
|
|
53
|
+
@font-face {
|
|
54
|
+
font-family: Roboto;
|
|
55
|
+
font-style: normal;
|
|
56
|
+
font-weight: 400;
|
|
57
|
+
font-display: swap;
|
|
58
|
+
src: url('/fonts/Roboto-400-latin.woff2') format('woff2');
|
|
59
|
+
unicode-range: U+0000-00FF;
|
|
60
|
+
}
|
|
39
61
|
/* Critical above-the-fold styles */
|
|
40
62
|
html, body {
|
|
41
63
|
margin: 0;
|
|
@@ -29,7 +29,7 @@ export const appConfig: AppConfig = {
|
|
|
29
29
|
name: APP_NAME,
|
|
30
30
|
shortName: APP_SHORT_NAME,
|
|
31
31
|
description: APP_DESCRIPTION,
|
|
32
|
-
//
|
|
32
|
+
// Note: URL comes from VITE_APP_URL in .env, not here
|
|
33
33
|
|
|
34
34
|
// Footer legal links - remove any you don't need
|
|
35
35
|
footer: {
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
import { useEffect, useState } from 'react';
|
|
14
14
|
|
|
15
15
|
import { Section, Button, Alert } from '@donotdev/components';
|
|
16
|
-
import {
|
|
16
|
+
import { useCrud } from '@donotdev/crud';
|
|
17
17
|
import { useTranslation } from '@donotdev/core';
|
|
18
18
|
import type { PageMeta } from '@donotdev/core';
|
|
19
|
-
import { PageContainer, Link,
|
|
19
|
+
import { PageContainer, Link, EntityFormRenderer } from '@donotdev/ui';
|
|
20
20
|
|
|
21
21
|
// Import your entity from root-level entities folder
|
|
22
22
|
// import { productEntity } from 'entities/Product';
|
|
@@ -51,7 +51,6 @@ export const meta: PageMeta = {
|
|
|
51
51
|
export default function ProductPage() {
|
|
52
52
|
const { t } = useTranslation(NAMESPACE);
|
|
53
53
|
const id = useParam('id');
|
|
54
|
-
const navigate = useNavigate();
|
|
55
54
|
const isNew = id === 'new';
|
|
56
55
|
|
|
57
56
|
// useCrud provides CRUD operations with optimistic updates
|
|
@@ -84,8 +83,8 @@ export default function ProductPage() {
|
|
|
84
83
|
update(id, data); // No await - fires in background
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
//
|
|
88
|
-
navigate
|
|
86
|
+
// Navigation happens automatically via cancelPath (defaults to /products)
|
|
87
|
+
// Or you can navigate manually if needed
|
|
89
88
|
};
|
|
90
89
|
|
|
91
90
|
// ==========================================================================
|
|
@@ -121,6 +120,8 @@ export default function ProductPage() {
|
|
|
121
120
|
onSubmit={handleSubmit}
|
|
122
121
|
defaultValues={{ status: 'draft' }} // Initial values for new items
|
|
123
122
|
submitText={t('create')}
|
|
123
|
+
// Cancel automatically navigates to /products (or cancelPath if provided)
|
|
124
|
+
cancelPath="/products"
|
|
124
125
|
/>
|
|
125
126
|
) : (
|
|
126
127
|
// EDIT MODE
|
|
@@ -130,6 +131,8 @@ export default function ProductPage() {
|
|
|
130
131
|
onSubmit={handleSubmit}
|
|
131
132
|
defaultValues={formData} // Loaded data
|
|
132
133
|
submitText={t('update')}
|
|
134
|
+
// Cancel automatically navigates to /products (or cancelPath if provided)
|
|
135
|
+
cancelPath="/products"
|
|
133
136
|
/>
|
|
134
137
|
)}
|
|
135
138
|
</Section>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { Package } from 'lucide-react';
|
|
16
16
|
|
|
17
|
-
import { EntityList } from '@donotdev/
|
|
17
|
+
import { EntityList } from '@donotdev/ui';
|
|
18
18
|
import { useAuth } from '@donotdev/auth';
|
|
19
19
|
import type { PageMeta } from '@donotdev/core';
|
|
20
20
|
import { PageContainer } from '@donotdev/ui';
|
|
@@ -51,17 +51,14 @@ export default function ProductsListPage() {
|
|
|
51
51
|
// - Applies visibility rules based on user role
|
|
52
52
|
// - Includes search, sort, pagination
|
|
53
53
|
// - Add/Edit/Delete buttons with proper permissions
|
|
54
|
+
// - Automatic routing: edit/view -> /products/:id, create -> /products/new
|
|
54
55
|
|
|
55
56
|
return (
|
|
56
57
|
<PageContainer>
|
|
57
58
|
<EntityList
|
|
58
59
|
entity={productEntity}
|
|
59
60
|
userRole={user?.role}
|
|
60
|
-
// Optional
|
|
61
|
-
// onRowClick={(item) => navigate(`/products/${item.id}`)}
|
|
62
|
-
// createPath="/products/new"
|
|
63
|
-
// hideActions={false}
|
|
64
|
-
// pageSize={25}
|
|
61
|
+
// Optional: basePath="/admin/products" or onClick={(id) => openSheet(id)}
|
|
65
62
|
/>
|
|
66
63
|
</PageContainer>
|
|
67
64
|
);
|
|
@@ -73,7 +70,7 @@ export default function ProductsListPage() {
|
|
|
73
70
|
//
|
|
74
71
|
// For a card-based grid instead of table:
|
|
75
72
|
//
|
|
76
|
-
// import { EntityCardList } from '@donotdev/
|
|
73
|
+
// import { EntityCardList } from '@donotdev/ui';
|
|
77
74
|
//
|
|
78
75
|
// export default function ProductsGridPage() {
|
|
79
76
|
// return (
|
|
@@ -63,6 +63,7 @@ export const productEntity = defineEntity({
|
|
|
63
63
|
// 'checkbox' → Multiple selections
|
|
64
64
|
// 'date' → Date picker
|
|
65
65
|
// 'timestamp' → Date + time
|
|
66
|
+
// 'price' → Structured price (amount, currency, VAT, discount %)
|
|
66
67
|
// 'images' → Image upload (multiple)
|
|
67
68
|
// 'reference' → Link to another entity
|
|
68
69
|
//
|
|
@@ -120,7 +121,7 @@ export const productEntity = defineEntity({
|
|
|
120
121
|
price: {
|
|
121
122
|
name: 'price',
|
|
122
123
|
label: 'price',
|
|
123
|
-
type: '
|
|
124
|
+
type: 'price',
|
|
124
125
|
visibility: 'guest',
|
|
125
126
|
editable: 'admin',
|
|
126
127
|
validation: { required: true },
|
|
@@ -16,6 +16,7 @@ const demoEntity = defineEntity({
|
|
|
16
16
|
name: 'Demo',
|
|
17
17
|
collection: 'demos',
|
|
18
18
|
// collection: 'users/{userId}/demos', // Subcollection syntax
|
|
19
|
+
// namespace: 'entity-demo', // Optional: defaults to entity-${name.toLowerCase()}
|
|
19
20
|
|
|
20
21
|
listFields: ['title', 'status', 'category', 'price'],
|
|
21
22
|
listCardFields: ['images', 'title', 'price'],
|
|
@@ -98,7 +99,7 @@ const demoEntity = defineEntity({
|
|
|
98
99
|
price: {
|
|
99
100
|
name: 'price',
|
|
100
101
|
label: 'price',
|
|
101
|
-
type: '
|
|
102
|
+
type: 'price', // Value: { amount, currency?, vatIncluded?, discountPercent? }
|
|
102
103
|
visibility: 'guest',
|
|
103
104
|
editable: 'admin',
|
|
104
105
|
validation: { required: true },
|
|
@@ -113,6 +114,19 @@ const demoEntity = defineEntity({
|
|
|
113
114
|
validation: { required: false },
|
|
114
115
|
},
|
|
115
116
|
|
|
117
|
+
year: {
|
|
118
|
+
name: 'year',
|
|
119
|
+
label: 'year',
|
|
120
|
+
type: 'year', // Year combobox (type or select)
|
|
121
|
+
visibility: 'guest',
|
|
122
|
+
editable: 'owner',
|
|
123
|
+
validation: {
|
|
124
|
+
required: true,
|
|
125
|
+
min: 1900, // Optional: defaults to 1900
|
|
126
|
+
max: 2100, // Optional: defaults to current year + 10
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
|
|
116
130
|
// =========================================================================
|
|
117
131
|
// CONTACT
|
|
118
132
|
// =========================================================================
|
|
@@ -19,7 +19,8 @@ import react from 'eslint-plugin-react';
|
|
|
19
19
|
import reactRefresh from 'eslint-plugin-react-refresh';
|
|
20
20
|
import globals from 'globals';
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
// ESLint is not provided by the framework - set up your own configuration
|
|
23
|
+
// This is just a basic example template
|
|
23
24
|
|
|
24
25
|
const __filename = fileURLToPath(import.meta.url);
|
|
25
26
|
const __dirname = dirname(__filename);
|
|
@@ -106,7 +107,6 @@ export default [
|
|
|
106
107
|
'jsx-a11y': jsxA11y,
|
|
107
108
|
prettier: prettierPlugin,
|
|
108
109
|
'@typescript-eslint': tsPlugin,
|
|
109
|
-
'@donotdev/config': customPlugin,
|
|
110
110
|
},
|
|
111
111
|
settings: {
|
|
112
112
|
react: {
|
|
@@ -120,9 +120,6 @@ export default [
|
|
|
120
120
|
},
|
|
121
121
|
},
|
|
122
122
|
rules: {
|
|
123
|
-
'@donotdev/config/no-singleton-lifecycle': 'error',
|
|
124
|
-
'@donotdev/config/react-19-ssr-safe': 'error',
|
|
125
|
-
'@donotdev/config/prefer-framework-utils': 'warn',
|
|
126
123
|
'react/no-unstable-nested-components': 'error',
|
|
127
124
|
'no-restricted-globals': [
|
|
128
125
|
'error',
|
|
@@ -203,81 +200,6 @@ export default [
|
|
|
203
200
|
'warn',
|
|
204
201
|
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
|
|
205
202
|
],
|
|
206
|
-
'@donotdev/config/require-use-client': [
|
|
207
|
-
'warn',
|
|
208
|
-
{
|
|
209
|
-
clientPatterns: [
|
|
210
|
-
'useState',
|
|
211
|
-
'useEffect',
|
|
212
|
-
'useCallback',
|
|
213
|
-
'useMemo',
|
|
214
|
-
'useRef',
|
|
215
|
-
'useContext',
|
|
216
|
-
'useReducer',
|
|
217
|
-
'useLayoutEffect',
|
|
218
|
-
'useImperativeHandle',
|
|
219
|
-
'useDebugValue',
|
|
220
|
-
'useId',
|
|
221
|
-
'useSyncExternalStore',
|
|
222
|
-
'useTransition',
|
|
223
|
-
'useDeferredValue',
|
|
224
|
-
'useInsertionEffect',
|
|
225
|
-
'onClick',
|
|
226
|
-
'onChange',
|
|
227
|
-
'onSubmit',
|
|
228
|
-
'onFocus',
|
|
229
|
-
'onBlur',
|
|
230
|
-
'onMouseEnter',
|
|
231
|
-
'onMouseLeave',
|
|
232
|
-
'onKeyDown',
|
|
233
|
-
'onKeyUp',
|
|
234
|
-
'onKeyPress',
|
|
235
|
-
'onScroll',
|
|
236
|
-
'onResize',
|
|
237
|
-
'onLoad',
|
|
238
|
-
'onError',
|
|
239
|
-
'window',
|
|
240
|
-
'document',
|
|
241
|
-
'localStorage',
|
|
242
|
-
'sessionStorage',
|
|
243
|
-
'navigator',
|
|
244
|
-
'location',
|
|
245
|
-
'history',
|
|
246
|
-
'addEventListener',
|
|
247
|
-
'removeEventListener',
|
|
248
|
-
'useAuth',
|
|
249
|
-
'useTranslation',
|
|
250
|
-
'useLocalStorage',
|
|
251
|
-
'useTheme',
|
|
252
|
-
'useRouter',
|
|
253
|
-
'usePathname',
|
|
254
|
-
'useSearchParams',
|
|
255
|
-
'framer-motion',
|
|
256
|
-
'lottie',
|
|
257
|
-
'gsap',
|
|
258
|
-
'shiki',
|
|
259
|
-
],
|
|
260
|
-
filePatterns: ['**/*.{ts,tsx}'],
|
|
261
|
-
excludePatterns: [
|
|
262
|
-
'**/*.config.{js,ts}',
|
|
263
|
-
'**/*.d.ts',
|
|
264
|
-
'**/types/**',
|
|
265
|
-
'**/constants/**',
|
|
266
|
-
'**/utils/**',
|
|
267
|
-
'**/helpers/**',
|
|
268
|
-
'**/stores/**',
|
|
269
|
-
'**/api/**',
|
|
270
|
-
'**/functions/**',
|
|
271
|
-
'**/lib/**',
|
|
272
|
-
'**/server/**',
|
|
273
|
-
'**/middleware/**',
|
|
274
|
-
'**/edge/**',
|
|
275
|
-
'**/node_modules/**',
|
|
276
|
-
'**/dist/**',
|
|
277
|
-
'**/build/**',
|
|
278
|
-
],
|
|
279
|
-
},
|
|
280
|
-
],
|
|
281
203
|
'import/no-named-export': 'off',
|
|
282
204
|
},
|
|
283
205
|
},
|
|
@@ -19,14 +19,14 @@ await remove('doc-id');
|
|
|
19
19
|
Paginated list with automatic loading. For data tables.
|
|
20
20
|
|
|
21
21
|
```tsx
|
|
22
|
-
const { items, loading,
|
|
22
|
+
const { items, loading, refresh } = useCrudList(productEntity);
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
### useCrudCardList
|
|
26
26
|
Card-based list with infinite scroll.
|
|
27
27
|
|
|
28
28
|
```tsx
|
|
29
|
-
const { items, loading,
|
|
29
|
+
const { items, loading, refresh } = useCrudCardList(articleEntity);
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
---
|
|
@@ -106,6 +106,9 @@ Field components are auto-rendered by `FormFieldRenderer`. You don't import them
|
|
|
106
106
|
### Numbers
|
|
107
107
|
- `number` - Numeric input
|
|
108
108
|
- `range` - Slider input
|
|
109
|
+
- `rating` - Star rating input (1–5, configurable max)
|
|
110
|
+
|
|
111
|
+
**Rating + comment (e.g. reviews):** Use two fields on the same entity — `rating` (type `rating`) for stars and `comment` (type `textarea`) for the text. The form renders them as separate rows; no composite field type needed.
|
|
109
112
|
|
|
110
113
|
### Boolean
|
|
111
114
|
- `checkbox` - Checkbox input
|
|
@@ -153,7 +156,7 @@ import { useController, registerFieldType } from '@donotdev/crud';
|
|
|
153
156
|
import type { ControlledFieldProps } from '@donotdev/crud';
|
|
154
157
|
|
|
155
158
|
// Custom controlled component MUST use framework's useController (not react-hook-form's)
|
|
156
|
-
function
|
|
159
|
+
function ScoreField({
|
|
157
160
|
fieldConfig,
|
|
158
161
|
control,
|
|
159
162
|
errors,
|
|
@@ -179,7 +182,7 @@ function RatingField({
|
|
|
179
182
|
onChange?.(value);
|
|
180
183
|
}}
|
|
181
184
|
min={0}
|
|
182
|
-
max={
|
|
185
|
+
max={10}
|
|
183
186
|
/>
|
|
184
187
|
{fieldState?.error && (
|
|
185
188
|
<span className="error">{fieldState.error.message}</span>
|
|
@@ -189,8 +192,8 @@ function RatingField({
|
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
registerFieldType({
|
|
192
|
-
type: '
|
|
193
|
-
controlledComponent:
|
|
195
|
+
type: 'score',
|
|
196
|
+
controlledComponent: ScoreField,
|
|
194
197
|
});
|
|
195
198
|
```
|
|
196
199
|
|