@lenne.tech/cli 1.12.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.
@@ -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
  }
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.createBrowserFetcher = createBrowserFetcher;
13
+ /**
14
+ * Headless-browser HTML fetcher for single-page applications.
15
+ *
16
+ * Mirrors the chrome-md content script's PageReadyDetector:
17
+ * waits for the network to settle, then returns the fully hydrated
18
+ * HTML so Defuddle can extract the real content instead of the
19
+ * pre-render shell.
20
+ *
21
+ * Uses `playwright-core` with a three-tier strategy:
22
+ * 1. System Chrome / Edge via `channel: 'chrome' | 'msedge'`.
23
+ * 2. Playwright's own bundled Chromium (if already installed).
24
+ * 3. Auto-install Playwright's Chromium (`npx playwright install
25
+ * chromium`) and retry — opt-in via `autoInstall`.
26
+ */
27
+ const child_process_1 = require("child_process");
28
+ const DEFAULT_USER_AGENT = 'Mozilla/5.0 (compatible; lenneTech-CLI-Crawler/1.0; +https://lenne.tech)';
29
+ /**
30
+ * Try to construct a browser fetcher. Prefers a system Chrome /
31
+ * Edge via Playwright channels, falls back to Playwright's bundled
32
+ * Chromium, and (optionally) auto-installs Chromium on demand.
33
+ */
34
+ function createBrowserFetcher() {
35
+ return __awaiter(this, arguments, void 0, function* (options = {}) {
36
+ const log = options.onLog || (() => undefined);
37
+ const reasons = [];
38
+ const { chromium } = require('playwright-core');
39
+ // 1. System Chrome.
40
+ const chromeFetcher = yield launch(chromium, { channel: 'chrome' }, options, 'system-chrome').catch((error) => {
41
+ reasons.push(`channel:chrome: ${error.message}`);
42
+ return null;
43
+ });
44
+ if (chromeFetcher) {
45
+ log(`Browser engine: ${chromeFetcher.engine}`);
46
+ return chromeFetcher;
47
+ }
48
+ // 2. System Edge (Windows fallback, also common on macOS).
49
+ const edgeFetcher = yield launch(chromium, { channel: 'msedge' }, options, 'system-edge').catch((error) => {
50
+ reasons.push(`channel:msedge: ${error.message}`);
51
+ return null;
52
+ });
53
+ if (edgeFetcher) {
54
+ log(`Browser engine: ${edgeFetcher.engine}`);
55
+ return edgeFetcher;
56
+ }
57
+ // 3. Playwright's bundled Chromium.
58
+ const bundledFetcher = yield launch(chromium, {}, options, 'playwright-chromium').catch((error) => {
59
+ reasons.push(`playwright-chromium: ${error.message}`);
60
+ return null;
61
+ });
62
+ if (bundledFetcher) {
63
+ log(`Browser engine: ${bundledFetcher.engine}`);
64
+ return bundledFetcher;
65
+ }
66
+ // 4. Optional auto-install, then retry Playwright's chromium.
67
+ if (options.autoInstall) {
68
+ log('No browser available — installing Playwright chromium (one-time download, ~170 MB)…');
69
+ try {
70
+ yield runNpx(['playwright', 'install', 'chromium']);
71
+ const retry = yield launch(chromium, {}, options, 'playwright-chromium');
72
+ if (retry) {
73
+ log(`Browser engine: ${retry.engine}`);
74
+ return retry;
75
+ }
76
+ }
77
+ catch (error) {
78
+ reasons.push(`auto-install: ${error instanceof Error ? error.message : String(error)}`);
79
+ }
80
+ }
81
+ throw new Error([
82
+ 'Could not start a headless browser for SPA rendering.',
83
+ ...reasons.map((r) => ` - ${r}`),
84
+ '',
85
+ 'Fix one of these:',
86
+ ' 1. Install Google Chrome or Microsoft Edge (Playwright picks them up automatically).',
87
+ ' 2. Install Playwright browsers manually: `npx playwright install chromium`.',
88
+ ' 3. Re-run with --install-browser to let the CLI install them.',
89
+ ].join('\n'));
90
+ });
91
+ }
92
+ function launch(chromium, launchOptions, options, engineLabel) {
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ const browser = yield chromium.launch(Object.assign(Object.assign({}, launchOptions), { headless: true }));
95
+ const context = yield browser.newContext({
96
+ userAgent: options.userAgent || DEFAULT_USER_AGENT,
97
+ });
98
+ return {
99
+ close: () => __awaiter(this, void 0, void 0, function* () {
100
+ yield context.close();
101
+ yield browser.close();
102
+ }),
103
+ engine: engineLabel,
104
+ fetch: (url) => __awaiter(this, void 0, void 0, function* () {
105
+ var _a;
106
+ const page = yield context.newPage();
107
+ try {
108
+ yield page.goto(url, {
109
+ timeout: (_a = options.maxWaitMs) !== null && _a !== void 0 ? _a : 20000,
110
+ waitUntil: 'networkidle',
111
+ });
112
+ if (options.extraWaitMs) {
113
+ yield page.waitForTimeout(options.extraWaitMs);
114
+ }
115
+ return yield page.content();
116
+ }
117
+ finally {
118
+ yield page.close();
119
+ }
120
+ }),
121
+ };
122
+ });
123
+ }
124
+ /**
125
+ * Run an `npx` command, streaming its output to the current stdio.
126
+ * Resolves on exit code 0, rejects otherwise.
127
+ */
128
+ function runNpx(args) {
129
+ return new Promise((resolve, reject) => {
130
+ const child = (0, child_process_1.spawn)('npx', args, { shell: false, stdio: 'inherit' });
131
+ child.on('error', reject);
132
+ child.on('exit', (code) => {
133
+ if (code === 0)
134
+ resolve();
135
+ else
136
+ reject(new Error(`npx ${args.join(' ')} exited with code ${code}`));
137
+ });
138
+ });
139
+ }