@veloxts/cli 0.7.4 → 0.7.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @veloxts/cli
2
2
 
3
+ ## 0.7.6
4
+
5
+ ### Patch Changes
6
+
7
+ - feat(router): custom access levels for the Resource API + advanced Architectural Patterns
8
+ - Updated dependencies
9
+ - @veloxts/auth@0.7.6
10
+ - @veloxts/core@0.7.6
11
+ - @veloxts/orm@0.7.6
12
+ - @veloxts/router@0.7.6
13
+ - @veloxts/validation@0.7.6
14
+
15
+ ## 0.7.5
16
+
17
+ ### Patch Changes
18
+
19
+ - fix(cli): address sync command review findings
20
+ - Updated dependencies
21
+ - @veloxts/auth@0.7.5
22
+ - @veloxts/core@0.7.5
23
+ - @veloxts/orm@0.7.5
24
+ - @veloxts/router@0.7.5
25
+ - @veloxts/validation@0.7.5
26
+
3
27
  ## 0.7.4
4
28
 
5
29
  ### Patch Changes
@@ -48,7 +48,7 @@ export async function detectProjectContext(cwd) {
48
48
  name: 'unknown',
49
49
  hasAuth: false,
50
50
  database: 'sqlite',
51
- projectType: 'api',
51
+ projectType: 'vite',
52
52
  isVinxiProject: false,
53
53
  hasWeb: false,
54
54
  };
@@ -70,11 +70,11 @@ export async function detectProjectContext(cwd) {
70
70
  const isVinxiProject = hasVinxiMarker || hasAppConfig;
71
71
  // Detect project type
72
72
  const hasAppPagesDir = existsSync(join(cwd, 'app', 'pages'));
73
- const projectType = isVinxiProject || hasAppPagesDir ? 'fullstack' : 'api';
73
+ const projectType = isVinxiProject || hasAppPagesDir ? 'vinxi' : 'vite';
74
74
  // Detect directory structure
75
- const pagesDir = projectType === 'fullstack' ? 'app/pages' : undefined;
76
- const layoutsDir = projectType === 'fullstack' ? 'app/layouts' : undefined;
77
- const actionsDir = projectType === 'fullstack' ? 'app/actions' : 'src/actions';
75
+ const pagesDir = projectType === 'vinxi' ? 'app/pages' : undefined;
76
+ const layoutsDir = projectType === 'vinxi' ? 'app/layouts' : undefined;
77
+ const actionsDir = projectType === 'vinxi' ? 'app/actions' : 'src/actions';
78
78
  return {
79
79
  name: packageJson.name ?? 'velox-app',
80
80
  hasAuth,
@@ -85,8 +85,8 @@ Examples:
85
85
  */
86
86
  async generate(config) {
87
87
  // Check if this is a full-stack project
88
- if (config.project.projectType !== 'fullstack') {
89
- throw new GeneratorError(GeneratorErrorCode.PROJECT_STRUCTURE, 'Layout generator requires a full-stack VeloxTS project.', 'Create a full-stack project with: npx create-velox-app my-app --full-stack');
88
+ if (config.project.projectType !== 'vinxi') {
89
+ throw new GeneratorError(GeneratorErrorCode.PROJECT_STRUCTURE, 'Layout generator requires a full-stack VeloxTS project.', 'Create a full-stack project with: npx create-velox-app my-app --rsc');
90
90
  }
91
91
  const context = this.createContext(config);
92
92
  const files = [];
@@ -77,8 +77,8 @@ Examples:
77
77
  */
78
78
  async generate(config) {
79
79
  // Check if this is a full-stack project
80
- if (config.project.projectType !== 'fullstack') {
81
- throw new GeneratorError(GeneratorErrorCode.PROJECT_STRUCTURE, 'Page generator requires a full-stack VeloxTS project.', 'Create a full-stack project with: npx create-velox-app my-app --full-stack');
80
+ if (config.project.projectType !== 'vinxi') {
81
+ throw new GeneratorError(GeneratorErrorCode.PROJECT_STRUCTURE, 'Page generator requires a full-stack VeloxTS project.', 'Create a full-stack project with: npx create-velox-app my-app --rsc');
82
82
  }
83
83
  const context = this.createContext(config);
84
84
  const files = [];
@@ -33,9 +33,9 @@ export interface EntityNames {
33
33
  readonly humanReadablePlural: string;
34
34
  }
35
35
  /**
36
- * Project type based on architecture
36
+ * Rendering strategy: 'vite' for client-side SPA, 'vinxi' for server-side RSC
37
37
  */
38
- export type ProjectType = 'api' | 'fullstack';
38
+ export type ProjectType = 'vite' | 'vinxi';
39
39
  /**
40
40
  * Project-level context for template generation
41
41
  */
@@ -46,17 +46,17 @@ export interface ProjectContext {
46
46
  readonly hasAuth: boolean;
47
47
  /** Database type from configuration */
48
48
  readonly database: 'sqlite' | 'postgresql' | 'mysql';
49
- /** Project type: 'api' for API-only, 'fullstack' for RSC/Vinxi */
49
+ /** Rendering strategy: 'vite' for client-side SPA, 'vinxi' for server-side RSC */
50
50
  readonly projectType: ProjectType;
51
51
  /** Whether this is a Vinxi/RSC project */
52
52
  readonly isVinxiProject: boolean;
53
53
  /** Whether @veloxts/web is installed */
54
54
  readonly hasWeb: boolean;
55
- /** Root directory for pages (app/pages for fullstack, undefined for api) */
55
+ /** Root directory for pages (app/pages for vinxi, undefined for vite) */
56
56
  readonly pagesDir?: string;
57
- /** Root directory for layouts (app/layouts for fullstack, undefined for api) */
57
+ /** Root directory for layouts (app/layouts for vinxi, undefined for vite) */
58
58
  readonly layoutsDir?: string;
59
- /** Root directory for actions (app/actions for fullstack, src/actions for api) */
59
+ /** Root directory for actions (app/actions for vinxi, src/actions for vite) */
60
60
  readonly actionsDir?: string;
61
61
  }
62
62
  /**
@@ -117,7 +117,7 @@ function findClosingBrace(content, openIndex) {
117
117
  return i;
118
118
  }
119
119
  }
120
- return content.length;
120
+ throw new Error('Prisma schema has unbalanced braces — model block not properly closed');
121
121
  }
122
122
  /**
123
123
  * Parse every field-like line from a model body into `RawFieldLine` objects.
@@ -52,7 +52,7 @@ export async function executeSync(projectRoot, options) {
52
52
  // ── Stage 3: Prompt ─────────────────────────────────────────
53
53
  let choices;
54
54
  if (options.force) {
55
- choices = models.map((model) => buildDefaultChoices(model));
55
+ choices = models.map((model) => buildDefaultChoices(model, existing));
56
56
  }
57
57
  else {
58
58
  const prompted = await promptAllModels(models, existing);
@@ -152,6 +152,7 @@ async function generateFiles(snapshot, plan, projectRoot, options) {
152
152
  }
153
153
  // ── Register procedures in router ───────────────────────────
154
154
  if (!options.skipRegistration) {
155
+ let registrationFailures = 0;
155
156
  for (const reg of plan.registrations) {
156
157
  try {
157
158
  const result = registerProcedures(projectRoot, reg.entityName, reg.procedureVarName, false);
@@ -159,14 +160,19 @@ async function generateFiles(snapshot, plan, projectRoot, options) {
159
160
  registered.push(reg.procedureVarName);
160
161
  }
161
162
  else if (result.error) {
163
+ registrationFailures++;
162
164
  errors.push(`Registration ${reg.procedureVarName}: ${result.error}`);
163
165
  }
164
166
  }
165
167
  catch (err) {
168
+ registrationFailures++;
166
169
  const msg = err instanceof Error ? err.message : String(err);
167
170
  errors.push(`Registration ${reg.procedureVarName}: ${msg}`);
168
171
  }
169
172
  }
173
+ if (registrationFailures > 0 && registrationFailures === plan.registrations.length) {
174
+ p.log.warn('All procedure registrations failed. You may need to manually register in src/index.ts.');
175
+ }
170
176
  }
171
177
  // ── Display results ─────────────────────────────────────────
172
178
  displayResults(created, overwritten, registered, errors, projectRoot);
@@ -237,11 +243,13 @@ function displayResults(created, overwritten, registered, errors, projectRoot) {
237
243
  /**
238
244
  * Build default ModelChoices for a model (used when --force is set).
239
245
  * Generates all CRUD, uses output strategy, includes no relations.
246
+ * Sets 'regenerate' if existing files are detected, so rollback works correctly.
240
247
  */
241
- function buildDefaultChoices(model) {
248
+ function buildDefaultChoices(model, existing) {
249
+ const hasExisting = existing.procedures.has(model.name) || existing.schemas.has(model.name);
242
250
  return {
243
251
  model: model.name,
244
- action: 'generate',
252
+ action: hasExisting ? 'regenerate' : 'generate',
245
253
  outputStrategy: 'output',
246
254
  crud: {
247
255
  get: true,
@@ -152,18 +152,15 @@ function generateListProcedure(plan, names) {
152
152
  const lines = [];
153
153
  lines.push(` ${procName}: procedure()`);
154
154
  lines.push(` .input(List${names.pascalPlural}Schema)`);
155
- if (plan.outputStrategy === 'output') {
156
- lines.push(` .query(async ({ input, ctx }) => {`);
157
- }
158
- else {
159
- lines.push(` .query(async ({ input, ctx }) => {`);
160
- }
155
+ lines.push(` .query(async ({ input, ctx }) => {`);
161
156
  lines.push(` const { page, perPage } = input;`);
162
157
  lines.push(` const [items, total] = await Promise.all([`);
163
158
  lines.push(` ctx.db.${names.camel}.findMany({`);
164
159
  lines.push(` skip: (page - 1) * perPage,`);
165
160
  lines.push(` take: perPage,`);
166
- lines.push(` orderBy: { createdAt: 'desc' },`);
161
+ if (plan.model.hasTimestamps) {
162
+ lines.push(` orderBy: { createdAt: 'desc' },`);
163
+ }
167
164
  if (hasIncludes) {
168
165
  lines.push(generateIncludeBlock(plan.includeRelations, 10));
169
166
  }
@@ -204,7 +201,7 @@ function generateUpdateProcedure(_plan, names) {
204
201
  const procName = `update${names.pascal}`;
205
202
  const lines = [];
206
203
  lines.push(` ${procName}: procedure()`);
207
- lines.push(` .input(z.object({ id: z.string().uuid() }).merge(Update${names.pascal}Schema))`);
204
+ lines.push(` .input(Update${names.pascal}Schema.extend({ id: z.string().uuid() }))`);
208
205
  lines.push(` .mutation(async ({ input, ctx }) => {`);
209
206
  lines.push(` const { id, ...data } = input;`);
210
207
  lines.push(` return ctx.db.${names.camel}.update({ where: { id }, data });`);
@@ -163,7 +163,10 @@ async function promptOutputStrategy(model) {
163
163
  message: 'Output strategy?',
164
164
  options: [
165
165
  { value: 'output', label: '.output() \u2014 Same fields for all users' },
166
- { value: 'resource', label: '.resource() \u2014 Different fields per access level' },
166
+ {
167
+ value: 'resource',
168
+ label: '.expose() \u2014 Different fields per access level (resource schema)',
169
+ },
167
170
  ],
168
171
  initialValue: defaultToResource ? 'resource' : 'output',
169
172
  });
@@ -73,7 +73,7 @@ export declare function isVeloxProject(cwd?: string): Promise<boolean>;
73
73
  /**
74
74
  * Project type detection result
75
75
  */
76
- export interface ProjectType {
76
+ export interface ProjectDetectionResult {
77
77
  /** Whether this is a Vinxi-based RSC project */
78
78
  isVinxi: boolean;
79
79
  /** Whether @veloxts/web is installed */
@@ -88,7 +88,7 @@ export interface ProjectType {
88
88
  * 1. Vinxi markers in dependencies (vinxi, @vinxi/server-functions, @veloxts/web)
89
89
  * 2. app.config.ts or app.config.js (Vinxi configuration)
90
90
  */
91
- export declare function detectProjectType(cwd?: string): Promise<ProjectType>;
91
+ export declare function detectProjectType(cwd?: string): Promise<ProjectDetectionResult>;
92
92
  /**
93
93
  * Read and parse a JSON file
94
94
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veloxts/cli",
3
- "version": "0.7.4",
3
+ "version": "0.7.6",
4
4
  "description": "Developer tooling and CLI commands for VeloxTS framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -41,11 +41,11 @@
41
41
  "pluralize": "8.0.0",
42
42
  "tsx": "4.21.0",
43
43
  "yaml": "2.8.2",
44
- "@veloxts/core": "0.7.4",
45
- "@veloxts/auth": "0.7.4",
46
- "@veloxts/router": "0.7.4",
47
- "@veloxts/orm": "0.7.4",
48
- "@veloxts/validation": "0.7.4"
44
+ "@veloxts/core": "0.7.6",
45
+ "@veloxts/router": "0.7.6",
46
+ "@veloxts/orm": "0.7.6",
47
+ "@veloxts/validation": "0.7.6",
48
+ "@veloxts/auth": "0.7.6"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "@prisma/client": ">=7.0.0"