@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/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: ["src/**/locales/*_*.json", "!src/locales/*_*.json"],
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}${EOL}`, {
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(EOL);
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 main11(options) {
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
- main11 as cacheout,
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,IAAI,EACJ,GAAG,EACH,KAAK,EACL,OAAO,EACP,GAAG,EACH,QAAQ,EACR,WAAW,EACX,MAAM,GACP,MAAM,mBAAmB,CAAC"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donotdev/cli",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Command-line interface for DoNotDev Framework",
5
5
  "type": "module",
6
6
  "private": false,
@@ -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
- import customPlugin from '@donotdev/core/config/eslint';
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
- return <EntityList entity={productEntity} onRowClick={(p) => navigate(`/products/${p.id}`)} />;
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
- return <EntityCardList entity={productEntity} />;
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 Dropdown
278
+ ### Year Field
230
279
 
231
- ```typescript
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: 'select',
236
- validation: { options: yearOptions(1990) } // 1990 to now, descending
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
- **File:** `locales/entity-product_en.json`
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.**