@donotdev/cli 0.0.7 → 0.0.8
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 +45 -120
- 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 +30 -1
- package/dist/bin/commands/create-project.js +30 -1
- package/dist/bin/commands/deploy.js +19 -5
- 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 +0 -16
- package/dist/bin/donotdev.js +0 -16
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -70
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/root-consumer/entities/demo.ts.example +14 -0
- package/templates/root-consumer/eslint.config.js.example +2 -80
- package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +75 -9
- package/templates/root-consumer/guides/dndev/SETUP_I18N.md.example +46 -0
package/dist/index.js
CHANGED
|
@@ -897,6 +897,7 @@ __export(cli_output_exports, {
|
|
|
897
897
|
success: () => success,
|
|
898
898
|
warn: () => warn
|
|
899
899
|
});
|
|
900
|
+
import "node:os";
|
|
900
901
|
function supportsColor() {
|
|
901
902
|
if (process.env.NO_COLOR) return false;
|
|
902
903
|
if (process.platform === "win32") {
|
|
@@ -6730,7 +6731,16 @@ var init_constants = __esm({
|
|
|
6730
6731
|
},
|
|
6731
6732
|
i18n: {
|
|
6732
6733
|
eager: ["src/locales/*_*.json"],
|
|
6733
|
-
lazy: [
|
|
6734
|
+
lazy: [
|
|
6735
|
+
"src/**/locales/*_*.json",
|
|
6736
|
+
"!src/locales/*_*.json",
|
|
6737
|
+
// Auto-detect shared entity translations in monorepos (if exists, use it; if not, no problem)
|
|
6738
|
+
"../../entities/locales/*_*.json"
|
|
6739
|
+
],
|
|
6740
|
+
// Additional paths from workspace packages (e.g., shared entities)
|
|
6741
|
+
// Consumers can still configure via i18n.additionalPaths in dndev/vite config for custom paths
|
|
6742
|
+
// Example: ['../../packages/shared/locales/*_*.json']
|
|
6743
|
+
additional: [],
|
|
6734
6744
|
framework: {
|
|
6735
6745
|
eager: [`${I18N_PATHS.SOURCE_EAGER}/*_*.json`],
|
|
6736
6746
|
lazy: [`${I18N_PATHS.SOURCE_LAZY}/*_*.json`]
|
|
@@ -6823,6 +6833,7 @@ var init_constants = __esm({
|
|
|
6823
6833
|
|
|
6824
6834
|
// packages/core/config/utils/PathResolver.ts
|
|
6825
6835
|
import * as fs from "node:fs";
|
|
6836
|
+
import "node:fs";
|
|
6826
6837
|
import { createRequire } from "node:module";
|
|
6827
6838
|
import {
|
|
6828
6839
|
resolve,
|
|
@@ -8290,6 +8301,7 @@ var init_pathResolver = __esm({
|
|
|
8290
8301
|
|
|
8291
8302
|
// packages/tooling/src/bundler/utils.ts
|
|
8292
8303
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
8304
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "node:fs";
|
|
8293
8305
|
import { createRequire as createRequire3 } from "node:module";
|
|
8294
8306
|
import { dirname as dirname3, resolve as resolve3 } from "node:path";
|
|
8295
8307
|
import process from "node:process";
|
|
@@ -11250,6 +11262,23 @@ function generatePackageJson(templateName, mode, options = {}) {
|
|
|
11250
11262
|
}
|
|
11251
11263
|
}
|
|
11252
11264
|
}
|
|
11265
|
+
if (mode === "published") {
|
|
11266
|
+
const overrides = {};
|
|
11267
|
+
for (const [groupName, group] of Object.entries(matrix.groups || {})) {
|
|
11268
|
+
if (groupName.startsWith("@donotdev/")) {
|
|
11269
|
+
const version = group.packages?.[groupName];
|
|
11270
|
+
if (version) {
|
|
11271
|
+
overrides[groupName] = version.replace(/^\^/, "");
|
|
11272
|
+
}
|
|
11273
|
+
}
|
|
11274
|
+
}
|
|
11275
|
+
if (matrix.overrides) {
|
|
11276
|
+
Object.assign(overrides, matrix.overrides);
|
|
11277
|
+
}
|
|
11278
|
+
if (Object.keys(overrides).length > 0) {
|
|
11279
|
+
result.overrides = overrides;
|
|
11280
|
+
}
|
|
11281
|
+
}
|
|
11253
11282
|
return result;
|
|
11254
11283
|
}
|
|
11255
11284
|
|
|
@@ -12006,7 +12035,7 @@ init_cli_output();
|
|
|
12006
12035
|
init_errors();
|
|
12007
12036
|
init_pathResolver();
|
|
12008
12037
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
12009
|
-
import { EOL } from "node:os";
|
|
12038
|
+
import { EOL as EOL2 } from "node:os";
|
|
12010
12039
|
async function main9(options = {}) {
|
|
12011
12040
|
const dryRun = options.dryRun ?? false;
|
|
12012
12041
|
const verbose = options.verbose ?? false;
|
|
@@ -12124,7 +12153,7 @@ async function addPathComments(files, rootDir, dryRun, verbose) {
|
|
|
12124
12153
|
let modified = false;
|
|
12125
12154
|
if (contentLines.length === 0) {
|
|
12126
12155
|
if (!dryRun) {
|
|
12127
|
-
writeSync(file, `${expectedPathComment}${
|
|
12156
|
+
writeSync(file, `${expectedPathComment}${EOL2}`, {
|
|
12128
12157
|
overwrite: true
|
|
12129
12158
|
});
|
|
12130
12159
|
}
|
|
@@ -12249,7 +12278,7 @@ async function addPathComments(files, rootDir, dryRun, verbose) {
|
|
|
12249
12278
|
newLines.push("");
|
|
12250
12279
|
}
|
|
12251
12280
|
newLines.push(...contentLines);
|
|
12252
|
-
const newContent = newLines.join(
|
|
12281
|
+
const newContent = newLines.join(EOL2);
|
|
12253
12282
|
const contentChanged = fileContent !== newContent;
|
|
12254
12283
|
if (contentChanged) {
|
|
12255
12284
|
try {
|
|
@@ -12576,69 +12605,6 @@ ${stderr}` : "");
|
|
|
12576
12605
|
}
|
|
12577
12606
|
}
|
|
12578
12607
|
|
|
12579
|
-
// packages/tooling/src/quality/lint.ts
|
|
12580
|
-
init_utils();
|
|
12581
|
-
init_cli_output();
|
|
12582
|
-
init_errors();
|
|
12583
|
-
init_pathResolver();
|
|
12584
|
-
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
12585
|
-
async function main10(options = {}) {
|
|
12586
|
-
const fix = options.fix ?? false;
|
|
12587
|
-
const cwd = process.cwd();
|
|
12588
|
-
const eslintConfigs = [
|
|
12589
|
-
"eslint.config.js",
|
|
12590
|
-
"eslint.config.mjs",
|
|
12591
|
-
"eslint.config.cjs"
|
|
12592
|
-
];
|
|
12593
|
-
const hasEslintConfig = eslintConfigs.some(
|
|
12594
|
-
(config) => pathExists(joinPath(cwd, config))
|
|
12595
|
-
);
|
|
12596
|
-
if (!hasEslintConfig) {
|
|
12597
|
-
log.info("No eslint.config.js found in current directory. Skipping lint.");
|
|
12598
|
-
log.info("To enable linting, create an eslint.config.js file.");
|
|
12599
|
-
return 0;
|
|
12600
|
-
}
|
|
12601
|
-
const eslintArgs = fix ? ["--fix"] : [];
|
|
12602
|
-
if (options.debug) {
|
|
12603
|
-
eslintArgs.push("--debug");
|
|
12604
|
-
}
|
|
12605
|
-
if (options.quiet) {
|
|
12606
|
-
eslintArgs.push("--quiet");
|
|
12607
|
-
}
|
|
12608
|
-
if (options.debug || options.verbose) {
|
|
12609
|
-
log.debug(`Linting code${fix ? " and fixing issues" : ""}...`);
|
|
12610
|
-
log.debug(` Working directory: ${cwd}`);
|
|
12611
|
-
log.debug(` ESLint args: ${eslintArgs.join(" ")}`);
|
|
12612
|
-
} else {
|
|
12613
|
-
log.info(fix ? "Linting and fixing code..." : "Linting code...");
|
|
12614
|
-
}
|
|
12615
|
-
try {
|
|
12616
|
-
const result = spawnSync10("bunx", ["eslint", ".", ...eslintArgs], {
|
|
12617
|
-
cwd,
|
|
12618
|
-
stdio: "inherit",
|
|
12619
|
-
env: process.env,
|
|
12620
|
-
shell: true
|
|
12621
|
-
});
|
|
12622
|
-
const exitCode = result.status ?? 1;
|
|
12623
|
-
if (exitCode === 0) {
|
|
12624
|
-
if (!options.debug && !options.verbose) {
|
|
12625
|
-
log.success("Linting completed successfully");
|
|
12626
|
-
}
|
|
12627
|
-
} else {
|
|
12628
|
-
if (!options.debug && !options.verbose) {
|
|
12629
|
-
log.error("Linting found issues (see above for details)");
|
|
12630
|
-
}
|
|
12631
|
-
}
|
|
12632
|
-
return exitCode;
|
|
12633
|
-
} catch (err) {
|
|
12634
|
-
throw DoNotDevError.from(
|
|
12635
|
-
err,
|
|
12636
|
-
"Failed to run ESLint",
|
|
12637
|
-
"cli-execution-error"
|
|
12638
|
-
);
|
|
12639
|
-
}
|
|
12640
|
-
}
|
|
12641
|
-
|
|
12642
12608
|
// packages/tooling/src/maintenance/cacheout.ts
|
|
12643
12609
|
init_utils();
|
|
12644
12610
|
init_cli_output();
|
|
@@ -12765,7 +12731,7 @@ function removeItem(path, rootDir, dryRun, verbose) {
|
|
|
12765
12731
|
return false;
|
|
12766
12732
|
}
|
|
12767
12733
|
}
|
|
12768
|
-
async function
|
|
12734
|
+
async function main10(options) {
|
|
12769
12735
|
const opts = options;
|
|
12770
12736
|
const targetDir = opts.app ? joinPath(process.cwd(), "apps", opts.app) : process.cwd();
|
|
12771
12737
|
if (opts.app && !pathExists(targetDir)) {
|
|
@@ -12814,14 +12780,13 @@ async function main11(options) {
|
|
|
12814
12780
|
}
|
|
12815
12781
|
export {
|
|
12816
12782
|
main as build,
|
|
12817
|
-
|
|
12783
|
+
main10 as cacheout,
|
|
12818
12784
|
main7 as createApp,
|
|
12819
12785
|
main8 as createProject,
|
|
12820
12786
|
main5 as deploy,
|
|
12821
12787
|
main2 as dev,
|
|
12822
12788
|
main3 as emu,
|
|
12823
12789
|
main9 as format,
|
|
12824
|
-
main10 as lint,
|
|
12825
12790
|
main4 as preview,
|
|
12826
12791
|
main6 as syncSecrets
|
|
12827
12792
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAE5B;;;;;GAKG;AAEH,qFAAqF;AACrF,OAAO,EACL,SAAS,EACT,aAAa,EACb,MAAM,EACN,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAE5B;;;;;GAKG;AAEH,qFAAqF;AACrF,OAAO,EACL,SAAS,EACT,aAAa,EACb,MAAM,EACN,GAAG,EACH,KAAK,EACL,OAAO,EACP,GAAG,EACH,QAAQ,EACR,WAAW,EACX,MAAM,GACP,MAAM,mBAAmB,CAAC"}
|
package/package.json
CHANGED
|
@@ -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'],
|
|
@@ -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
|
},
|
|
@@ -13,6 +13,7 @@ import { defineEntity } from '@donotdev/core';
|
|
|
13
13
|
export const productEntity = defineEntity({
|
|
14
14
|
name: 'Product',
|
|
15
15
|
collection: 'products',
|
|
16
|
+
// namespace: 'entity-product', // Optional: defaults to entity-${name.toLowerCase()}
|
|
16
17
|
fields: {
|
|
17
18
|
name: {
|
|
18
19
|
name: 'name',
|
|
@@ -135,23 +136,70 @@ export default function ProductPage() {
|
|
|
135
136
|
|
|
136
137
|
```tsx
|
|
137
138
|
import { EntityList } from '@donotdev/crud';
|
|
139
|
+
import { useAuth } from '@donotdev/auth';
|
|
140
|
+
import { PageContainer, useNavigate } from '@donotdev/ui';
|
|
138
141
|
import { productEntity } from 'entities';
|
|
139
142
|
|
|
140
143
|
export default function ProductsPage() {
|
|
141
|
-
|
|
144
|
+
const user = useAuth('user');
|
|
145
|
+
const navigate = useNavigate();
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<PageContainer>
|
|
149
|
+
<EntityList
|
|
150
|
+
entity={productEntity}
|
|
151
|
+
userRole={user?.role}
|
|
152
|
+
// Required: Edit handler - navigates to edit page when edit button clicked
|
|
153
|
+
onEdit={(id) => navigate(`/products/${id}`)}
|
|
154
|
+
// Optional: Create handler - navigates to create page when "Add New" clicked
|
|
155
|
+
onCreate={() => navigate('/products/new')}
|
|
156
|
+
// Optional: View handler - called when row is clicked
|
|
157
|
+
onView={(id) => navigate(`/products/${id}`)}
|
|
158
|
+
/>
|
|
159
|
+
</PageContainer>
|
|
160
|
+
);
|
|
142
161
|
}
|
|
143
162
|
```
|
|
144
163
|
|
|
164
|
+
**Props:**
|
|
165
|
+
- `onEdit` **(required)** - Function `(id: string) => void` called when edit button is clicked. Typically navigates to `/${collection}/${id}` (e.g., `/products/${id}`).
|
|
166
|
+
- `onCreate` **(optional)** - Function `() => void` called when "Add New" button is clicked. Typically navigates to `/${collection}/new`.
|
|
167
|
+
- `onView` **(optional)** - Function `(id: string) => void` called when a table row is clicked. Useful for view-only pages or detail navigation.
|
|
168
|
+
- `userRole` **(optional)** - Current user role for field visibility filtering (backend still enforces security).
|
|
169
|
+
|
|
170
|
+
**Routing Convention:**
|
|
171
|
+
- Default edit route: `/${entity.collection}/${id}` (e.g., `/products/abc123`)
|
|
172
|
+
- You must create a page at `/${collection}/:id` route to handle the edit page
|
|
173
|
+
|
|
145
174
|
### Card Grid
|
|
146
175
|
|
|
147
176
|
```tsx
|
|
148
177
|
import { EntityCardList } from '@donotdev/crud';
|
|
178
|
+
import { PageContainer, useNavigate } from '@donotdev/ui';
|
|
179
|
+
import { productEntity } from 'entities';
|
|
149
180
|
|
|
150
181
|
export default function ShopPage() {
|
|
151
|
-
|
|
182
|
+
const navigate = useNavigate();
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<PageContainer>
|
|
186
|
+
<EntityCardList
|
|
187
|
+
entity={productEntity}
|
|
188
|
+
// Required: View handler - called when card is clicked
|
|
189
|
+
onView={(id) => navigate(`/products/${id}`)}
|
|
190
|
+
/>
|
|
191
|
+
</PageContainer>
|
|
192
|
+
);
|
|
152
193
|
}
|
|
153
194
|
```
|
|
154
195
|
|
|
196
|
+
**Props:**
|
|
197
|
+
- `onView` **(required)** - Function `(id: string) => void` called when a card is clicked. Typically navigates to `/${collection}/${id}` (e.g., `/products/${id}`).
|
|
198
|
+
- `cols` **(optional)** - Responsive column breakpoints `[mobile, tablet, desktop, wide]` (default: `[1, 2, 3, 4]`).
|
|
199
|
+
- `staleTime` **(optional)** - Cache stale time in milliseconds (default: 30 minutes).
|
|
200
|
+
- `filter` **(optional)** - Filter function `(item: any) => boolean` to filter items client-side.
|
|
201
|
+
- `hideFilters` **(optional)** - Hide filters section (default: `false`).
|
|
202
|
+
|
|
155
203
|
---
|
|
156
204
|
|
|
157
205
|
## 5. Field Types
|
|
@@ -169,6 +217,7 @@ export default function ShopPage() {
|
|
|
169
217
|
### Numbers
|
|
170
218
|
- `number` - Numeric input
|
|
171
219
|
- `range` - Slider input
|
|
220
|
+
- `year` - Year input (combobox: type or select from dropdown)
|
|
172
221
|
|
|
173
222
|
### Boolean
|
|
174
223
|
- `checkbox` - Checkbox input
|
|
@@ -226,14 +275,18 @@ category: {
|
|
|
226
275
|
}
|
|
227
276
|
```
|
|
228
277
|
|
|
229
|
-
### Year
|
|
278
|
+
### Year Field
|
|
230
279
|
|
|
231
|
-
|
|
232
|
-
import { yearOptions } from '@donotdev/crud';
|
|
280
|
+
Use `type: 'year'` for year inputs (combobox with type-or-select UX):
|
|
233
281
|
|
|
282
|
+
```typescript
|
|
234
283
|
year: {
|
|
235
|
-
type: '
|
|
236
|
-
validation: {
|
|
284
|
+
type: 'year',
|
|
285
|
+
validation: {
|
|
286
|
+
required: true,
|
|
287
|
+
min: 1900, // Optional: defaults to 1900
|
|
288
|
+
max: 2100 // Optional: defaults to current year + 10
|
|
289
|
+
}
|
|
237
290
|
}
|
|
238
291
|
```
|
|
239
292
|
|
|
@@ -447,9 +500,22 @@ export function CustomProductForm() {
|
|
|
447
500
|
|
|
448
501
|
## 9. i18n
|
|
449
502
|
|
|
450
|
-
**Namespace:** `entity-${entity.name.toLowerCase()}`
|
|
503
|
+
**Namespace:** Automatically set to `entity-${entity.name.toLowerCase()}` (e.g., `entity-product`)
|
|
504
|
+
|
|
505
|
+
You can customize it by setting `namespace` in your entity definition:
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
export const productEntity = defineEntity({
|
|
509
|
+
name: 'Product',
|
|
510
|
+
collection: 'products',
|
|
511
|
+
namespace: 'custom-namespace', // Optional: override default
|
|
512
|
+
fields: { ... }
|
|
513
|
+
});
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**Default behavior:** If not specified, the namespace is automatically set to `entity-${entity.name.toLowerCase()}`.
|
|
451
517
|
|
|
452
|
-
**
|
|
518
|
+
**Translation file:** `locales/entity-product_en.json` (or your custom namespace)
|
|
453
519
|
```json
|
|
454
520
|
{
|
|
455
521
|
"fields": {
|
|
@@ -184,4 +184,50 @@ const { languages, currentLanguage } = useLanguageSelector();
|
|
|
184
184
|
|
|
185
185
|
---
|
|
186
186
|
|
|
187
|
+
## Advanced: Shared Entity Translations (Monorepos)
|
|
188
|
+
|
|
189
|
+
**Auto-detected:** The framework automatically scans `../../entities/locales/*_*.json` for shared entity translations. If the path exists, translations are loaded; if not, nothing happens.
|
|
190
|
+
|
|
191
|
+
**File naming:** `entity-car_en.json`, `entity-car_fr.json`
|
|
192
|
+
|
|
193
|
+
**Merge behavior:** Base translations from shared package, app can override any key. Deep merge, app wins.
|
|
194
|
+
|
|
195
|
+
**Example structure:**
|
|
196
|
+
```
|
|
197
|
+
monorepo/
|
|
198
|
+
entities/
|
|
199
|
+
locales/
|
|
200
|
+
entity-car_en.json # ✅ Auto-detected - no config needed
|
|
201
|
+
entity-car_fr.json
|
|
202
|
+
apps/
|
|
203
|
+
admin/ # ✅ Just works - framework auto-detects
|
|
204
|
+
public/ # ✅ Just works - framework auto-detects
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Custom paths:** If your shared translations are in a different location, use `additionalPaths`:
|
|
208
|
+
|
|
209
|
+
**Vite:**
|
|
210
|
+
```ts
|
|
211
|
+
// vite.config.ts
|
|
212
|
+
export default defineViteConfig({
|
|
213
|
+
appConfig,
|
|
214
|
+
i18n: {
|
|
215
|
+
additionalPaths: ['../../packages/shared/locales'] // Custom path
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Next.js:**
|
|
221
|
+
```ts
|
|
222
|
+
// next.config.ts
|
|
223
|
+
export default defineNextConfig({
|
|
224
|
+
appConfig,
|
|
225
|
+
i18n: {
|
|
226
|
+
additionalPaths: ['../../packages/shared/locales'] // Custom path
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
187
233
|
**Drop files, get languages. Framework handles the rest.**
|