@lenne.tech/cli 1.13.0 → 1.14.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.
package/README.md CHANGED
@@ -94,6 +94,37 @@ $ lt fullstack init --name myapp --framework-mode vendor --dry-run --noConfirm
94
94
  $ lt server create --name myapp --framework-mode vendor
95
95
  ```
96
96
 
97
+ ### Experimental: `--next` (nest-base)
98
+
99
+ Both `lt fullstack init` and `lt server create` support an experimental
100
+ `--next` flag that swaps the API template from
101
+ [`nest-server-starter`](https://github.com/lenneTech/nest-server-starter)
102
+ (MongoDB) to [`nest-base`](https://github.com/lenneTech/nest-base) — a new
103
+ NestJS stack on **Bun + Prisma 7 + Postgres + Better-Auth** with a built-in
104
+ `/dev` cockpit.
105
+
106
+ ```bash
107
+ # experimental standalone api
108
+ $ lt server create my-next-api --next --noConfirm
109
+
110
+ # experimental fullstack (nuxt + nest-base)
111
+ $ lt fullstack init --name my-next-app --frontend nuxt --next --noConfirm
112
+ ```
113
+
114
+ When `--next` is set the CLI:
115
+
116
+ - clones `nest-base` instead of `nest-server-starter`,
117
+ - forces `--api-mode Rest` and `--framework-mode npm` (other modes are not
118
+ applicable to nest-base),
119
+ - skips `nest-server-starter`-specific patching (`config.env.ts`,
120
+ `main.ts` Swagger setup, `meta.json`, `lt.config.json`),
121
+ - skips the workspace install in fullstack mode — run `pnpm install` for
122
+ the frontend and `bun install` for the API yourself.
123
+
124
+ This option is **experimental** and may change. The downstream `lt server
125
+ module/object/addProp/test/permissions` commands target the classic
126
+ `nest-server` layout and are not yet compatible with `nest-base`.
127
+
97
128
  ### Working on an existing project
98
129
 
99
130
  All `lt server …` commands (module, object, addProp, test, permissions)
@@ -27,7 +27,7 @@ const NewCommand = {
27
27
  // Info
28
28
  info('Create a new fullstack workspace');
29
29
  // Hint for non-interactive callers (e.g. Claude Code)
30
- toolbox.tools.nonInteractiveHint('lt fullstack init --name <name> --frontend <nuxt|angular> --api-mode <Rest|GraphQL|Both> --framework-mode <npm|vendor> [--framework-upstream-branch <ref>] [--dry-run] --noConfirm');
30
+ toolbox.tools.nonInteractiveHint('lt fullstack init --name <name> --frontend <nuxt|angular> --api-mode <Rest|GraphQL|Both> --framework-mode <npm|vendor> [--framework-upstream-branch <ref>] [--next] [--dry-run] --noConfirm');
31
31
  // Check git
32
32
  if (!(yield git.gitInstalled())) {
33
33
  return;
@@ -46,8 +46,9 @@ const NewCommand = {
46
46
  const configFrontendLink = (_v = (_u = ltConfig === null || ltConfig === void 0 ? void 0 : ltConfig.commands) === null || _u === void 0 ? void 0 : _u.fullstack) === null || _v === void 0 ? void 0 : _v.frontendLink;
47
47
  const configFrameworkMode = (_x = (_w = ltConfig === null || ltConfig === void 0 ? void 0 : ltConfig.commands) === null || _w === void 0 ? void 0 : _w.fullstack) === null || _x === void 0 ? void 0 : _x.frameworkMode;
48
48
  // Parse CLI arguments
49
- const { 'api-branch': cliApiBranch, 'api-copy': cliApiCopy, 'api-link': cliApiLink, 'api-mode': cliApiMode, 'dry-run': cliDryRun, 'framework-mode': cliFrameworkMode, 'framework-upstream-branch': cliFrameworkUpstreamBranch, frontend: cliFrontend, 'frontend-branch': cliFrontendBranch, 'frontend-copy': cliFrontendCopy, 'frontend-framework-mode': cliFrontendFrameworkMode, 'frontend-link': cliFrontendLink, git: cliGit, 'git-link': cliGitLink, name: cliName, } = parameters.options;
49
+ const { 'api-branch': cliApiBranch, 'api-copy': cliApiCopy, 'api-link': cliApiLink, 'api-mode': cliApiMode, 'dry-run': cliDryRun, 'framework-mode': cliFrameworkMode, 'framework-upstream-branch': cliFrameworkUpstreamBranch, frontend: cliFrontend, 'frontend-branch': cliFrontendBranch, 'frontend-copy': cliFrontendCopy, 'frontend-framework-mode': cliFrontendFrameworkMode, 'frontend-link': cliFrontendLink, git: cliGit, 'git-link': cliGitLink, name: cliName, next: cliNext, } = parameters.options;
50
50
  const dryRun = cliDryRun === true || cliDryRun === 'true';
51
+ const experimental = cliNext === true || cliNext === 'true';
51
52
  const frameworkUpstreamBranch = typeof cliFrameworkUpstreamBranch === 'string' && cliFrameworkUpstreamBranch.length > 0
52
53
  ? cliFrameworkUpstreamBranch
53
54
  : undefined;
@@ -109,7 +110,11 @@ const NewCommand = {
109
110
  // Determine API mode with priority: CLI > config > global > interactive (default: Rest)
110
111
  const globalApiMode = config.getGlobalDefault(ltConfig, 'apiMode');
111
112
  let apiMode;
112
- if (cliApiMode) {
113
+ if (experimental) {
114
+ apiMode = 'Rest';
115
+ info('Using experimental nest-base template (Bun + Prisma + Postgres + Better-Auth)');
116
+ }
117
+ else if (cliApiMode) {
113
118
  apiMode = cliApiMode;
114
119
  }
115
120
  else if (configApiMode) {
@@ -154,7 +159,10 @@ const NewCommand = {
154
159
  //
155
160
  // Default is still 'npm' until the vendoring pilot is fully evaluated.
156
161
  let frameworkMode;
157
- if (cliFrameworkMode === 'npm' || cliFrameworkMode === 'vendor') {
162
+ if (experimental) {
163
+ frameworkMode = 'npm';
164
+ }
165
+ else if (cliFrameworkMode === 'npm' || cliFrameworkMode === 'vendor') {
158
166
  frameworkMode = cliFrameworkMode;
159
167
  }
160
168
  else if (cliFrameworkMode) {
@@ -291,7 +299,10 @@ const NewCommand = {
291
299
  info('Would execute:');
292
300
  info(` 1. git clone lt-monorepo → ${projectDir}/`);
293
301
  info(` 2. setup frontend (${frontend}) → ${projectDir}/projects/app`);
294
- if (frameworkMode === 'vendor') {
302
+ if (experimental) {
303
+ info(` 3. clone nest-base (experimental) → ${projectDir}/projects/api`);
304
+ }
305
+ else if (frameworkMode === 'vendor') {
295
306
  info(` 3. clone nest-server-starter → ${projectDir}/projects/api`);
296
307
  info(` 4. clone @lenne.tech/nest-server${frameworkUpstreamBranch ? ` (branch/tag: ${frameworkUpstreamBranch})` : ''} → /tmp`);
297
308
  info(` 5. vendor core/ + flatten-fix + codemod consumer imports`);
@@ -420,6 +431,7 @@ const NewCommand = {
420
431
  apiMode,
421
432
  branch: apiBranch,
422
433
  copyPath: apiCopy,
434
+ experimental,
423
435
  frameworkMode,
424
436
  frameworkUpstreamBranch,
425
437
  linkPath: apiLink,
@@ -466,15 +478,20 @@ const NewCommand = {
466
478
  // instead.` and silently disables CVE overrides.
467
479
  (0, hoist_workspace_pnpm_config_1.hoistWorkspacePnpmConfig)({ filesystem, projectDir, subProjects: ['projects/api', 'projects/app'] });
468
480
  // Install all packages
469
- const installSpinner = spin('Install all packages');
470
- try {
471
- const detectedPm = toolbox.pm.detect(projectDir);
472
- yield system.run(`cd ${projectDir} && ${toolbox.pm.install(detectedPm)} && ${toolbox.pm.run('init', detectedPm)}`);
473
- installSpinner.succeed('Successfully installed all packages');
481
+ if (!experimental) {
482
+ const installSpinner = spin('Install all packages');
483
+ try {
484
+ const detectedPm = toolbox.pm.detect(projectDir);
485
+ yield system.run(`cd ${projectDir} && ${toolbox.pm.install(detectedPm)} && ${toolbox.pm.run('init', detectedPm)}`);
486
+ installSpinner.succeed('Successfully installed all packages');
487
+ }
488
+ catch (err) {
489
+ installSpinner.fail(`Failed to install packages: ${err.message}`);
490
+ return;
491
+ }
474
492
  }
475
- catch (err) {
476
- installSpinner.fail(`Failed to install packages: ${err.message}`);
477
- return;
493
+ else {
494
+ info('Skipping workspace install — run `bun install` (api) and `pnpm install` (app) manually.');
478
495
  }
479
496
  // Post-install format pass. processApiMode (run earlier in
480
497
  // setupServerForFullstack) and convertAppCloneToVendored rewrite
@@ -482,10 +499,10 @@ const NewCommand = {
482
499
  // `pnpm run format:check` (multi-line arrays/imports after region
483
500
  // stripping, import-path rewrites that now fit single-line). The
484
501
  // formatter is only available after install, so we normalize here.
485
- if (apiMode && filesystem.isDirectory(`${projectDir}/projects/api`)) {
502
+ if (!experimental && apiMode && filesystem.isDirectory(`${projectDir}/projects/api`)) {
486
503
  yield toolbox.apiMode.formatProject(`${projectDir}/projects/api`);
487
504
  }
488
- if (isNuxt && filesystem.isDirectory(`${projectDir}/projects/app`)) {
505
+ if (!experimental && isNuxt && filesystem.isDirectory(`${projectDir}/projects/app`)) {
489
506
  yield toolbox.apiMode.formatProject(`${projectDir}/projects/app`);
490
507
  }
491
508
  // Create initial commit after everything is set up
@@ -511,9 +528,18 @@ const NewCommand = {
511
528
  success(`Generated fullstack workspace with ${frontend} in ${projectDir} with ${name} app in ${helper.msToMinutesAndSeconds(timer())}m.`);
512
529
  info('');
513
530
  info('Next:');
514
- info(` Run ${name}`);
515
- info(` $ cd ${projectDir}`);
516
- info(` $ ${toolbox.pm.run('start')}`);
531
+ if (experimental) {
532
+ info(` $ cd ${projectDir}`);
533
+ info(' Frontend: cd projects/app && pnpm install');
534
+ info(' API: cd projects/api && bun install');
535
+ info(' Configure projects/api/.env (see .env.example)');
536
+ info(' Start Postgres + run prisma generate / migrate');
537
+ }
538
+ else {
539
+ info(` Run ${name}`);
540
+ info(` $ cd ${projectDir}`);
541
+ info(` $ ${toolbox.pm.run('start')}`);
542
+ }
517
543
  info('');
518
544
  if (!toolbox.parameters.options.fromGluegunMenu) {
519
545
  process.exit();
@@ -42,6 +42,13 @@ const NewCommand = {
42
42
  { description: 'Git branch to clone from', flag: '--branch', required: false, type: 'string' },
43
43
  { description: 'Copy from local path instead of cloning', flag: '--copy', required: false, type: 'string' },
44
44
  { description: 'Symlink to local path instead of cloning', flag: '--link', required: false, type: 'string' },
45
+ {
46
+ default: false,
47
+ description: 'Use experimental nest-base template (Bun + Prisma + Postgres)',
48
+ flag: '--next',
49
+ required: false,
50
+ type: 'boolean',
51
+ },
45
52
  {
46
53
  default: false,
47
54
  description: 'Skip all interactive prompts',
@@ -77,6 +84,7 @@ const NewCommand = {
77
84
  const cliApiMode = parameters.options['api-mode'] || parameters.options.apiMode;
78
85
  const cliFrameworkMode = parameters.options['framework-mode'];
79
86
  const cliFrameworkUpstreamBranch = parameters.options['framework-upstream-branch'];
87
+ const experimental = parameters.options.next === true || parameters.options.next === 'true';
80
88
  // Determine noConfirm with priority: CLI > config > global > default (false)
81
89
  const noConfirm = config.getNoConfirm({
82
90
  cliValue: cliNoConfirm,
@@ -88,7 +96,7 @@ const NewCommand = {
88
96
  // Info
89
97
  info('Create a new server');
90
98
  // Hint for non-interactive callers (e.g. Claude Code)
91
- toolbox.tools.nonInteractiveHint('lt server create --name <name> --api-mode <Rest|GraphQL|Both> --noConfirm');
99
+ toolbox.tools.nonInteractiveHint('lt server create --name <name> --api-mode <Rest|GraphQL|Both> [--next] --noConfirm');
92
100
  // Check git
93
101
  if (!(yield git.gitInstalled())) {
94
102
  return;
@@ -150,7 +158,11 @@ const NewCommand = {
150
158
  }
151
159
  // Determine API mode with priority: CLI > config > global > interactive (default: Rest)
152
160
  let apiMode;
153
- if (cliApiMode) {
161
+ if (experimental) {
162
+ apiMode = 'Rest';
163
+ info('Using experimental nest-base template (Bun + Prisma + Postgres + Better-Auth)');
164
+ }
165
+ else if (cliApiMode) {
154
166
  apiMode = cliApiMode;
155
167
  }
156
168
  else if (configApiMode) {
@@ -184,7 +196,10 @@ const NewCommand = {
184
196
  // Determine framework consumption mode — same resolution cascade as
185
197
  // lt fullstack init: CLI flag > lt.config > interactive (default npm).
186
198
  let frameworkMode;
187
- if (cliFrameworkMode === 'npm' || cliFrameworkMode === 'vendor') {
199
+ if (experimental) {
200
+ frameworkMode = 'npm';
201
+ }
202
+ else if (cliFrameworkMode === 'npm' || cliFrameworkMode === 'vendor') {
188
203
  frameworkMode = cliFrameworkMode;
189
204
  }
190
205
  else if (cliFrameworkMode) {
@@ -222,6 +237,7 @@ const NewCommand = {
222
237
  branch,
223
238
  copyPath,
224
239
  description,
240
+ experimental,
225
241
  frameworkMode,
226
242
  frameworkUpstreamBranch,
227
243
  linkPath,
@@ -279,36 +295,47 @@ const NewCommand = {
279
295
  }
280
296
  // Derive controller type from API mode and save project config
281
297
  const controllerType = apiMode;
282
- // Create lt.config.json
283
- const projectConfig = {
284
- commands: {
285
- server: {
286
- module: {
287
- controller: controllerType,
298
+ if (!experimental) {
299
+ // Create lt.config.json
300
+ const projectConfig = {
301
+ commands: {
302
+ server: {
303
+ module: {
304
+ controller: controllerType,
305
+ },
288
306
  },
289
307
  },
290
- },
291
- meta: {
292
- apiMode,
293
- version: '1.0.0',
294
- },
295
- };
296
- const configPath = filesystem.path(projectDir, 'lt.config.json');
297
- filesystem.write(configPath, projectConfig, { jsonIndent: 2 });
298
- info('');
299
- success(`Configuration saved to ${projectDir}/lt.config.json`);
300
- info(` API mode: ${apiMode}`);
301
- info(` Default controller type: ${controllerType}`);
308
+ meta: {
309
+ apiMode,
310
+ version: '1.0.0',
311
+ },
312
+ };
313
+ const configPath = filesystem.path(projectDir, 'lt.config.json');
314
+ filesystem.write(configPath, projectConfig, { jsonIndent: 2 });
315
+ info('');
316
+ success(`Configuration saved to ${projectDir}/lt.config.json`);
317
+ info(` API mode: ${apiMode}`);
318
+ info(` Default controller type: ${controllerType}`);
319
+ }
302
320
  // We're done, so show what to do next
303
321
  info('');
304
322
  success(`Generated ${name} server with lenne.Tech CLI ${meta.version()} in ${helper.msToMinutesAndSeconds(timer())}m.`);
305
323
  info('');
306
324
  info('Next:');
307
- info(' Start database server (e.g. MongoDB)');
308
- info(` Check config: ${projectDir}/src/config.env.ts`);
309
- info(` Go to project directory: cd ${projectDir}`);
310
- info(` Run tests: ${toolbox.pm.run('test:e2e')}`);
311
- info(` Start server: ${toolbox.pm.run('start')}`);
325
+ if (experimental) {
326
+ info(` Go to project directory: cd ${projectDir}`);
327
+ info(' Install dependencies: bun install');
328
+ info(' Configure .env (see .env.example)');
329
+ info(' Start Postgres + run prisma generate / migrate');
330
+ info(' Start server: bun run dev');
331
+ }
332
+ else {
333
+ info(' Start database server (e.g. MongoDB)');
334
+ info(` Check config: ${projectDir}/src/config.env.ts`);
335
+ info(` Go to project directory: cd ${projectDir}`);
336
+ info(` Run tests: ${toolbox.pm.run('test:e2e')}`);
337
+ info(` Start server: ${toolbox.pm.run('start')}`);
338
+ }
312
339
  info('');
313
340
  if (!toolbox.parameters.options.fromGluegunMenu) {
314
341
  process.exit();
@@ -633,13 +633,16 @@ class Server {
633
633
  setupServer(dest, options) {
634
634
  return __awaiter(this, void 0, void 0, function* () {
635
635
  const { apiMode: apiModeHelper, patching, system, template, templateHelper } = this.toolbox;
636
- const { apiMode, author = '', branch, copyPath, description = '', frameworkMode = 'npm', frameworkUpstreamBranch, linkPath, name, projectDir, skipInstall = false, skipPatching = false, } = options;
636
+ const { apiMode, author = '', branch, copyPath, description = '', experimental = false, frameworkMode = 'npm', frameworkUpstreamBranch, linkPath, name, projectDir, skipInstall = false, skipPatching = false, } = options;
637
+ const repoUrl = experimental
638
+ ? 'https://github.com/lenneTech/nest-base.git'
639
+ : 'https://github.com/lenneTech/nest-server-starter.git';
637
640
  // Setup template
638
641
  const result = yield templateHelper.setup(dest, {
639
642
  branch,
640
643
  copyPath,
641
644
  linkPath,
642
- repoUrl: 'https://github.com/lenneTech/nest-server-starter.git',
645
+ repoUrl,
643
646
  });
644
647
  if (!result.success) {
645
648
  return { method: result.method, path: result.path, success: false };
@@ -651,18 +654,20 @@ class Server {
651
654
  // Apply patches (config.env.ts, package.json, main.ts, meta.json)
652
655
  if (!skipPatching) {
653
656
  try {
654
- // Generate README
655
- yield template.generate({
656
- props: { description, name },
657
- target: `${dest}/README.md`,
658
- template: 'nest-server-starter/README.md.ejs',
659
- });
660
- // Replace secret or private keys and update database names via AST
661
- this.patchConfigEnvTs(`${dest}/src/config.env.ts`, projectDir);
662
- // Update Swagger configuration in main.ts
663
- yield patching.update(`${dest}/src/main.ts`, (content) => content
664
- .replace(/\.setTitle\('.*?'\)/, `.setTitle('${name}')`)
665
- .replace(/\.setDescription\('.*?'\)/, `.setDescription('${description || name}')`));
657
+ if (!experimental) {
658
+ // Generate README
659
+ yield template.generate({
660
+ props: { description, name },
661
+ target: `${dest}/README.md`,
662
+ template: 'nest-server-starter/README.md.ejs',
663
+ });
664
+ // Replace secret or private keys and update database names via AST
665
+ this.patchConfigEnvTs(`${dest}/src/config.env.ts`, projectDir);
666
+ // Update Swagger configuration in main.ts
667
+ yield patching.update(`${dest}/src/main.ts`, (content) => content
668
+ .replace(/\.setTitle\('.*?'\)/, `.setTitle('${name}')`)
669
+ .replace(/\.setDescription\('.*?'\)/, `.setDescription('${description || name}')`));
670
+ }
666
671
  // Update package.json
667
672
  yield patching.update(`${dest}/package.json`, (config) => {
668
673
  config.author = author;
@@ -674,13 +679,15 @@ class Server {
674
679
  config.version = '0.0.1';
675
680
  return config;
676
681
  });
677
- // Update meta.json if exists
678
- if (this.filesystem.exists(`${dest}/src/meta`)) {
679
- yield patching.update(`${dest}/src/meta`, (config) => {
680
- config.name = name;
681
- config.description = description;
682
- return config;
683
- });
682
+ if (!experimental) {
683
+ // Update meta.json if exists
684
+ if (this.filesystem.exists(`${dest}/src/meta`)) {
685
+ yield patching.update(`${dest}/src/meta`, (config) => {
686
+ config.name = name;
687
+ config.description = description;
688
+ return config;
689
+ });
690
+ }
684
691
  }
685
692
  }
686
693
  catch (err) {
@@ -703,7 +710,7 @@ class Server {
703
710
  // manifest (same dance as in setupServerForFullstack).
704
711
  let standaloneVendorUpstreamDeps = {};
705
712
  let standaloneVendorCoreEssentials = [];
706
- if (frameworkMode === 'vendor') {
713
+ if (!experimental && frameworkMode === 'vendor') {
707
714
  try {
708
715
  const converted = yield this.convertCloneToVendored({
709
716
  dest,
@@ -718,7 +725,7 @@ class Server {
718
725
  standaloneVendorCoreEssentials = this.readApiModeGraphqlEssentials(dest);
719
726
  }
720
727
  // Process API mode (before install so package.json is correct)
721
- if (apiMode) {
728
+ if (!experimental && apiMode) {
722
729
  try {
723
730
  yield apiModeHelper.processApiMode(dest, apiMode);
724
731
  }
@@ -727,7 +734,7 @@ class Server {
727
734
  }
728
735
  }
729
736
  // Restore core essentials after processApiMode stripped them (vendor + REST only).
730
- if (frameworkMode === 'vendor' && apiMode === 'Rest') {
737
+ if (!experimental && frameworkMode === 'vendor' && apiMode === 'Rest') {
731
738
  try {
732
739
  this.restoreVendorCoreEssentials({
733
740
  dest,
@@ -740,9 +747,11 @@ class Server {
740
747
  }
741
748
  }
742
749
  // Patch CLAUDE.md with API mode info
743
- this.patchClaudeMdApiMode(dest, apiMode);
750
+ if (!experimental) {
751
+ this.patchClaudeMdApiMode(dest, apiMode);
752
+ }
744
753
  // Install packages
745
- if (!skipInstall) {
754
+ if (!skipInstall && !experimental) {
746
755
  try {
747
756
  const { pm } = this.toolbox;
748
757
  yield system.run(`cd "${dest}" && ${pm.install(pm.detect(dest))}`);
@@ -771,7 +780,10 @@ class Server {
771
780
  setupServerForFullstack(dest, options) {
772
781
  return __awaiter(this, void 0, void 0, function* () {
773
782
  const { apiMode: apiModeHelper, templateHelper } = this.toolbox;
774
- const { apiMode, branch, copyPath, frameworkMode = 'npm', frameworkUpstreamBranch, linkPath, name, projectDir, } = options;
783
+ const { apiMode, branch, copyPath, experimental = false, frameworkMode = 'npm', frameworkUpstreamBranch, linkPath, name, projectDir, } = options;
784
+ const repoUrl = experimental
785
+ ? 'https://github.com/lenneTech/nest-base.git'
786
+ : 'https://github.com/lenneTech/nest-server-starter';
775
787
  // Both npm and vendor mode clone nest-server-starter as the base. The
776
788
  // starter ships the minimal consumer conventions a project needs
777
789
  // (src/server/common/models/persistence.model.ts, src/server/modules/user/,
@@ -791,7 +803,7 @@ class Server {
791
803
  branch,
792
804
  copyPath,
793
805
  linkPath,
794
- repoUrl: 'https://github.com/lenneTech/nest-server-starter',
806
+ repoUrl,
795
807
  });
796
808
  if (!result.success) {
797
809
  return { method: result.method, path: result.path, success: false };
@@ -801,18 +813,33 @@ class Server {
801
813
  return { method: 'link', path: result.path, success: true };
802
814
  }
803
815
  // Apply minimal patches for fullstack
804
- try {
805
- // Write meta.json
806
- this.filesystem.write(`${dest}/src/meta.json`, {
807
- description: `API for ${name} app`,
808
- name: `${name}-api-server`,
809
- version: '0.0.0',
810
- });
811
- // Replace secret or private keys and update database names via AST
812
- this.patchConfigEnvTs(`${dest}/src/config.env.ts`, projectDir);
816
+ if (!experimental) {
817
+ try {
818
+ // Write meta.json
819
+ this.filesystem.write(`${dest}/src/meta.json`, {
820
+ description: `API for ${name} app`,
821
+ name: `${name}-api-server`,
822
+ version: '0.0.0',
823
+ });
824
+ // Replace secret or private keys and update database names via AST
825
+ this.patchConfigEnvTs(`${dest}/src/config.env.ts`, projectDir);
826
+ }
827
+ catch (err) {
828
+ return { method: result.method, path: dest, success: false };
829
+ }
813
830
  }
814
- catch (err) {
815
- return { method: result.method, path: dest, success: false };
831
+ else {
832
+ try {
833
+ yield this.toolbox.patching.update(`${dest}/package.json`, (config) => {
834
+ config.name = projectDir;
835
+ config.description = `API for ${name} app`;
836
+ config.version = '0.0.0';
837
+ return config;
838
+ });
839
+ }
840
+ catch (err) {
841
+ return { method: result.method, path: dest, success: false };
842
+ }
816
843
  }
817
844
  // Clean up copied template artifacts
818
845
  if (result.method === 'copy') {
@@ -833,7 +860,7 @@ class Server {
833
860
  // without hard-coding package lists.
834
861
  let vendorUpstreamDeps = {};
835
862
  let vendorCoreEssentials = [];
836
- if (frameworkMode === 'vendor') {
863
+ if (!experimental && frameworkMode === 'vendor') {
837
864
  try {
838
865
  const converted = yield this.convertCloneToVendored({
839
866
  dest,
@@ -856,7 +883,7 @@ class Server {
856
883
  vendorCoreEssentials = this.readApiModeGraphqlEssentials(dest);
857
884
  }
858
885
  // Process API mode (before install which happens at monorepo level)
859
- if (apiMode) {
886
+ if (!experimental && apiMode) {
860
887
  try {
861
888
  yield apiModeHelper.processApiMode(dest, apiMode);
862
889
  }
@@ -867,7 +894,7 @@ class Server {
867
894
  // In vendor mode + REST, re-add the graphql essentials that
868
895
  // processApiMode just stripped. Both and GraphQL keep all packages
869
896
  // by construction and don't need restoration.
870
- if (frameworkMode === 'vendor' && apiMode === 'Rest') {
897
+ if (!experimental && frameworkMode === 'vendor' && apiMode === 'Rest') {
871
898
  try {
872
899
  this.restoreVendorCoreEssentials({
873
900
  dest,
@@ -881,7 +908,9 @@ class Server {
881
908
  }
882
909
  }
883
910
  // Patch CLAUDE.md with API mode info
884
- this.patchClaudeMdApiMode(dest, apiMode);
911
+ if (!experimental) {
912
+ this.patchClaudeMdApiMode(dest, apiMode);
913
+ }
885
914
  return { method: result.method, path: dest, success: true };
886
915
  });
887
916
  }
@@ -117,6 +117,7 @@ Flags:
117
117
  - `--frontend-framework-mode npm|vendor` — Frontend mode
118
118
  - `--framework-upstream-branch <tag>` — Specific nest-server version for vendor
119
119
  - `--dry-run` — Show plan without making changes
120
+ - `--next` — **Experimental:** clone [`nest-base`](https://github.com/lenneTech/nest-base) (Bun + Prisma 7 + Postgres + Better-Auth) for the API instead of `nest-server-starter`. Forces `--api-mode Rest`, `--framework-mode npm`, and skips workspace install (run `pnpm install` for app and `bun install` for api manually). Downstream `lt server module/object/addProp/test/permissions` are NOT compatible with the resulting layout.
120
121
 
121
122
  ---
122
123
 
package/docs/commands.md CHANGED
@@ -79,13 +79,16 @@ lt server create [name] [options]
79
79
  |--------|-------------|
80
80
  | `--description <text>` | Project description |
81
81
  | `--author <name>` | Author name |
82
+ | `--api-mode <Rest\|GraphQL\|Both>` | API mode (ignored with `--next`) |
83
+ | `--framework-mode <npm\|vendor>` | Framework consumption mode (ignored with `--next`) |
82
84
  | `--branch <branch>` / `-b` | Branch of nest-server-starter to use as template |
83
85
  | `--copy <path>` / `-c` | Copy from local template directory instead of cloning |
84
86
  | `--link <path>` | Symlink to local template directory (fastest, changes affect original) |
85
87
  | `--git` | Initialize git repository |
88
+ | `--next` | **Experimental:** clone [`nest-base`](https://github.com/lenneTech/nest-base) (Bun + Prisma 7 + Postgres + Better-Auth) instead of `nest-server-starter`. Skips API-mode / vendor-mode / install / lt.config.json processing. |
86
89
  | `--noConfirm` | Skip confirmation prompts |
87
90
 
88
- **CLAUDE.md Patching:** If the project contains a `CLAUDE.md`, the generic API mode description is replaced with the selected mode. In single-mode projects (`Rest` or `GraphQL`), the "API Mode System" documentation section is condensed to a brief note.
91
+ **CLAUDE.md Patching:** If the project contains a `CLAUDE.md`, the generic API mode description is replaced with the selected mode. In single-mode projects (`Rest` or `GraphQL`), the "API Mode System" documentation section is condensed to a brief note. Skipped when `--next` is used.
89
92
 
90
93
  **Configuration:** `commands.server.create.*`, `defaults.author`, `defaults.noConfirm`
91
94
 
@@ -532,6 +535,7 @@ lt fullstack init [options]
532
535
  | `--framework-mode <mode>` | Backend framework consumption mode: `npm` (classic) or `vendor` (pilot, core copied to `src/core/`) |
533
536
  | `--framework-upstream-branch <ref>` | Upstream `nest-server` branch/tag to vendor from (only with `--framework-mode vendor`) |
534
537
  | `--frontend-framework-mode <mode>` | Frontend framework consumption mode: `npm` or `vendor` (nuxt-extensions copied to `app/core/`) |
538
+ | `--next` | **Experimental:** clone [`nest-base`](https://github.com/lenneTech/nest-base) (Bun + Prisma 7 + Postgres + Better-Auth) for the API instead of `nest-server-starter`. Forces `--api-mode Rest` and `--framework-mode npm`, skips workspace install (run `pnpm install` for app and `bun install` for api manually). |
535
539
  | `--dry-run` | Print the resolved plan without making any changes |
536
540
  | `--git` | Push initial commit to remote repository (git is always initialized) |
537
541
  | `--git-link <url>` | Git remote repository URL (required when `--git` is true) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/cli",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "lenne.Tech CLI: lt",
5
5
  "keywords": [
6
6
  "lenne.Tech",