@lark-apaas/fullstack-cli 1.1.16-beta.12 → 1.1.16-beta.14

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.
@@ -1,6 +1,6 @@
1
1
  // src/config/drizzle.config.ts
2
2
  import { defineConfig } from "drizzle-kit";
3
- var outputDir = process.env.__DRIZZLE_OUT_DIR__ || "./server/database/.introspect";
3
+ var outputDir = process.env.__DRIZZLE_OUT_DIR__ || "./tmp/.introspect";
4
4
  var schemaPath = process.env.__DRIZZLE_SCHEMA_PATH__ || "./server/database/schema.ts";
5
5
  var parsedUrl = new URL(process.env.SUDA_DATABASE_URL || "");
6
6
  var envSchemaFilter = process.env.DRIZZLE_SCHEMA_FILTER;
package/dist/index.js CHANGED
@@ -2167,7 +2167,9 @@ async function run(options = {}) {
2167
2167
  process.exit(1);
2168
2168
  }
2169
2169
  const outputPath = options.output || process.env.DB_SCHEMA_OUTPUT || "server/database/schema.ts";
2170
- const OUT_DIR = path2.resolve(process.cwd(), "server/database/.introspect");
2170
+ const INTROSPECT_ROOT = path2.resolve(process.cwd(), "tmp/.introspect");
2171
+ fs4.mkdirSync(INTROSPECT_ROOT, { recursive: true });
2172
+ const OUT_DIR = fs4.mkdtempSync(path2.join(INTROSPECT_ROOT, "run-"));
2171
2173
  const SCHEMA_FILE = path2.resolve(process.cwd(), outputPath);
2172
2174
  console.log("[gen-db-schema] Starting...");
2173
2175
  const __filename = fileURLToPath2(import.meta.url);
@@ -2363,8 +2365,8 @@ var genDbSchemaCommand = {
2363
2365
  };
2364
2366
 
2365
2367
  // src/commands/sync/run.handler.ts
2366
- import path4 from "path";
2367
- import fs6 from "fs";
2368
+ import path5 from "path";
2369
+ import fs7 from "fs";
2368
2370
  import { fileURLToPath as fileURLToPath3 } from "url";
2369
2371
 
2370
2372
  // src/config/sync.ts
@@ -2525,18 +2527,93 @@ function deepMergeJson(user, template, arrayMerge = {}, currentPath = "") {
2525
2527
  return result;
2526
2528
  }
2527
2529
 
2530
+ // src/utils/package-json.ts
2531
+ import fs6 from "fs";
2532
+ import path4 from "path";
2533
+ var LEGACY_LINT_SCRIPT = 'concurrently "npm run eslint" "npm run type:check" "npm run stylelint"';
2534
+ var PATCHED_LINT_SCRIPT = "node ./scripts/lint.js";
2535
+ function readPackageJson(cwd = process.cwd()) {
2536
+ const pkgPath = path4.join(cwd, "package.json");
2537
+ if (!fs6.existsSync(pkgPath)) {
2538
+ throw new Error(`package.json not found at ${pkgPath}`);
2539
+ }
2540
+ const content = fs6.readFileSync(pkgPath, "utf-8");
2541
+ return JSON.parse(content);
2542
+ }
2543
+ function writePackageJson(pkg2, cwd = process.cwd()) {
2544
+ const pkgPath = path4.join(cwd, "package.json");
2545
+ const content = JSON.stringify(pkg2, null, 2) + "\n";
2546
+ fs6.writeFileSync(pkgPath, content, "utf-8");
2547
+ }
2548
+ function patchLintScriptForFilesSupport(pkg2) {
2549
+ const currentLint = pkg2.scripts?.lint;
2550
+ if (!currentLint) {
2551
+ return "skipped-missing";
2552
+ }
2553
+ if (currentLint === PATCHED_LINT_SCRIPT || currentLint === "node scripts/lint.js") {
2554
+ return "already-patched";
2555
+ }
2556
+ if (currentLint !== LEGACY_LINT_SCRIPT) {
2557
+ return "skipped-custom";
2558
+ }
2559
+ pkg2.scripts = pkg2.scripts || {};
2560
+ pkg2.scripts.lint = PATCHED_LINT_SCRIPT;
2561
+ return "patched";
2562
+ }
2563
+ function removeUpgradeScript(pkg2) {
2564
+ if (!pkg2.scripts?.upgrade) {
2565
+ return false;
2566
+ }
2567
+ delete pkg2.scripts.upgrade;
2568
+ return true;
2569
+ }
2570
+ function cleanDevScript(pkg2) {
2571
+ if (!pkg2.scripts?.dev) {
2572
+ return false;
2573
+ }
2574
+ const originalDev = pkg2.scripts.dev;
2575
+ const cleanedDev = originalDev.replace(/npm\s+run\s+upgrade\s*&&\s*/g, "").replace(/npm\s+run\s+upgrade\s*$/g, "").trim();
2576
+ if (cleanedDev !== originalDev) {
2577
+ pkg2.scripts.dev = cleanedDev;
2578
+ return true;
2579
+ }
2580
+ return false;
2581
+ }
2582
+ function cleanupPackageJson(cwd = process.cwd()) {
2583
+ try {
2584
+ const pkg2 = readPackageJson(cwd);
2585
+ let changed = false;
2586
+ if (removeUpgradeScript(pkg2)) {
2587
+ console.log("[fullstack-cli] \u2713 Removed scripts.upgrade");
2588
+ changed = true;
2589
+ }
2590
+ if (cleanDevScript(pkg2)) {
2591
+ console.log("[fullstack-cli] \u2713 Cleaned scripts.dev (removed npm run upgrade)");
2592
+ changed = true;
2593
+ }
2594
+ if (changed) {
2595
+ writePackageJson(pkg2, cwd);
2596
+ }
2597
+ return changed;
2598
+ } catch (error) {
2599
+ const message = error instanceof Error ? error.message : String(error);
2600
+ console.log(`[fullstack-cli] \u26A0 Could not cleanup package.json: ${message}`);
2601
+ return false;
2602
+ }
2603
+ }
2604
+
2528
2605
  // src/commands/sync/run.handler.ts
2529
2606
  async function run2(options) {
2530
2607
  const userProjectRoot = process.env.INIT_CWD || process.cwd();
2531
2608
  const __filename = fileURLToPath3(import.meta.url);
2532
- const __dirname2 = path4.dirname(__filename);
2533
- const pluginRoot = path4.resolve(__dirname2, "..");
2609
+ const __dirname2 = path5.dirname(__filename);
2610
+ const pluginRoot = path5.resolve(__dirname2, "..");
2534
2611
  if (userProjectRoot === pluginRoot) {
2535
2612
  console.log("[fullstack-cli] Skip syncing (installing plugin itself)");
2536
2613
  process.exit(0);
2537
2614
  }
2538
- const userPackageJson = path4.join(userProjectRoot, "package.json");
2539
- if (!fs6.existsSync(userPackageJson)) {
2615
+ const userPackageJson = path5.join(userProjectRoot, "package.json");
2616
+ if (!fs7.existsSync(userPackageJson)) {
2540
2617
  console.log("[fullstack-cli] Skip syncing (not a valid npm project)");
2541
2618
  process.exit(0);
2542
2619
  }
@@ -2552,6 +2629,7 @@ async function run2(options) {
2552
2629
  for (const rule of config.sync) {
2553
2630
  await syncRule(rule, pluginRoot, userProjectRoot);
2554
2631
  }
2632
+ patchUserPackageJson(userProjectRoot);
2555
2633
  if (config.permissions) {
2556
2634
  setPermissions(config.permissions, userProjectRoot);
2557
2635
  }
@@ -2562,9 +2640,32 @@ async function run2(options) {
2562
2640
  process.exit(1);
2563
2641
  }
2564
2642
  }
2643
+ function patchUserPackageJson(userProjectRoot) {
2644
+ try {
2645
+ const pkg2 = readPackageJson(userProjectRoot);
2646
+ const lintPatchResult = patchLintScriptForFilesSupport(pkg2);
2647
+ if (lintPatchResult === "patched") {
2648
+ writePackageJson(pkg2, userProjectRoot);
2649
+ console.log("[fullstack-cli] \u2713 Patched scripts.lint to support --files");
2650
+ return;
2651
+ }
2652
+ if (lintPatchResult === "already-patched") {
2653
+ console.log("[fullstack-cli] \u25CB scripts.lint already supports --files");
2654
+ return;
2655
+ }
2656
+ if (lintPatchResult === "skipped-custom") {
2657
+ console.warn(
2658
+ "[fullstack-cli] \u26A0 Skipped patching scripts.lint because it has been customized"
2659
+ );
2660
+ }
2661
+ } catch (error) {
2662
+ const message = error instanceof Error ? error.message : String(error);
2663
+ console.warn(`[fullstack-cli] \u26A0 Could not patch package.json: ${message}`);
2664
+ }
2665
+ }
2565
2666
  async function syncRule(rule, pluginRoot, userProjectRoot) {
2566
2667
  if (rule.type === "delete-file" || rule.type === "delete-directory") {
2567
- const destPath2 = path4.join(userProjectRoot, rule.to);
2668
+ const destPath2 = path5.join(userProjectRoot, rule.to);
2568
2669
  if (rule.type === "delete-file") {
2569
2670
  deleteFile(destPath2);
2570
2671
  } else {
@@ -2573,32 +2674,32 @@ async function syncRule(rule, pluginRoot, userProjectRoot) {
2573
2674
  return;
2574
2675
  }
2575
2676
  if (rule.type === "remove-line") {
2576
- const destPath2 = path4.join(userProjectRoot, rule.to);
2677
+ const destPath2 = path5.join(userProjectRoot, rule.to);
2577
2678
  removeLineFromFile(destPath2, rule.pattern);
2578
2679
  return;
2579
2680
  }
2580
2681
  if (rule.type === "add-script") {
2581
- const packageJsonPath = path4.join(userProjectRoot, "package.json");
2682
+ const packageJsonPath = path5.join(userProjectRoot, "package.json");
2582
2683
  addScript(packageJsonPath, rule.name, rule.command, rule.overwrite ?? false);
2583
2684
  return;
2584
2685
  }
2585
2686
  if (rule.type === "add-line") {
2586
- const destPath2 = path4.join(userProjectRoot, rule.to);
2687
+ const destPath2 = path5.join(userProjectRoot, rule.to);
2587
2688
  addLineToFile(destPath2, rule.line);
2588
2689
  return;
2589
2690
  }
2590
2691
  if (rule.type === "merge-json") {
2591
- const srcPath2 = path4.join(pluginRoot, rule.from);
2592
- const destPath2 = path4.join(userProjectRoot, rule.to);
2692
+ const srcPath2 = path5.join(pluginRoot, rule.from);
2693
+ const destPath2 = path5.join(userProjectRoot, rule.to);
2593
2694
  mergeJsonFile(srcPath2, destPath2, rule.arrayMerge);
2594
2695
  return;
2595
2696
  }
2596
2697
  if (!("from" in rule)) {
2597
2698
  return;
2598
2699
  }
2599
- const srcPath = path4.join(pluginRoot, rule.from);
2600
- const destPath = path4.join(userProjectRoot, rule.to);
2601
- if (!fs6.existsSync(srcPath)) {
2700
+ const srcPath = path5.join(pluginRoot, rule.from);
2701
+ const destPath = path5.join(userProjectRoot, rule.to);
2702
+ if (!fs7.existsSync(srcPath)) {
2602
2703
  console.warn(`[fullstack-cli] Source not found: ${rule.from}`);
2603
2704
  return;
2604
2705
  }
@@ -2615,68 +2716,68 @@ async function syncRule(rule, pluginRoot, userProjectRoot) {
2615
2716
  }
2616
2717
  }
2617
2718
  function syncFile(src, dest, overwrite = true, onlyIfExists = false) {
2618
- if (onlyIfExists && !fs6.existsSync(dest)) {
2619
- console.log(`[fullstack-cli] \u25CB ${path4.basename(dest)} (skipped, target not exists)`);
2719
+ if (onlyIfExists && !fs7.existsSync(dest)) {
2720
+ console.log(`[fullstack-cli] \u25CB ${path5.basename(dest)} (skipped, target not exists)`);
2620
2721
  return;
2621
2722
  }
2622
- const destDir = path4.dirname(dest);
2623
- if (!fs6.existsSync(destDir)) {
2624
- fs6.mkdirSync(destDir, { recursive: true });
2723
+ const destDir = path5.dirname(dest);
2724
+ if (!fs7.existsSync(destDir)) {
2725
+ fs7.mkdirSync(destDir, { recursive: true });
2625
2726
  }
2626
- if (fs6.existsSync(dest) && !overwrite) {
2627
- console.log(`[fullstack-cli] \u25CB ${path4.basename(dest)} (skipped, already exists)`);
2727
+ if (fs7.existsSync(dest) && !overwrite) {
2728
+ console.log(`[fullstack-cli] \u25CB ${path5.basename(dest)} (skipped, already exists)`);
2628
2729
  return;
2629
2730
  }
2630
- fs6.copyFileSync(src, dest);
2631
- console.log(`[fullstack-cli] \u2713 ${path4.basename(dest)}`);
2731
+ fs7.copyFileSync(src, dest);
2732
+ console.log(`[fullstack-cli] \u2713 ${path5.basename(dest)}`);
2632
2733
  }
2633
2734
  function syncDirectory(src, dest, overwrite = true) {
2634
- if (!fs6.existsSync(dest)) {
2635
- fs6.mkdirSync(dest, { recursive: true });
2735
+ if (!fs7.existsSync(dest)) {
2736
+ fs7.mkdirSync(dest, { recursive: true });
2636
2737
  }
2637
- const files = fs6.readdirSync(src);
2738
+ const files = fs7.readdirSync(src);
2638
2739
  let count = 0;
2639
2740
  files.forEach((file) => {
2640
- const srcFile = path4.join(src, file);
2641
- const destFile = path4.join(dest, file);
2642
- const stats = fs6.statSync(srcFile);
2741
+ const srcFile = path5.join(src, file);
2742
+ const destFile = path5.join(dest, file);
2743
+ const stats = fs7.statSync(srcFile);
2643
2744
  if (stats.isDirectory()) {
2644
2745
  syncDirectory(srcFile, destFile, overwrite);
2645
2746
  } else {
2646
- if (overwrite || !fs6.existsSync(destFile)) {
2647
- fs6.copyFileSync(srcFile, destFile);
2648
- console.log(`[fullstack-cli] \u2713 ${path4.relative(dest, destFile)}`);
2747
+ if (overwrite || !fs7.existsSync(destFile)) {
2748
+ fs7.copyFileSync(srcFile, destFile);
2749
+ console.log(`[fullstack-cli] \u2713 ${path5.relative(dest, destFile)}`);
2649
2750
  count++;
2650
2751
  }
2651
2752
  }
2652
2753
  });
2653
2754
  if (count > 0) {
2654
- console.log(`[fullstack-cli] Synced ${count} files to ${path4.basename(dest)}/`);
2755
+ console.log(`[fullstack-cli] Synced ${count} files to ${path5.basename(dest)}/`);
2655
2756
  }
2656
2757
  }
2657
2758
  function appendToFile(src, dest) {
2658
- const content = fs6.readFileSync(src, "utf-8");
2759
+ const content = fs7.readFileSync(src, "utf-8");
2659
2760
  let existingContent = "";
2660
- if (fs6.existsSync(dest)) {
2661
- existingContent = fs6.readFileSync(dest, "utf-8");
2761
+ if (fs7.existsSync(dest)) {
2762
+ existingContent = fs7.readFileSync(dest, "utf-8");
2662
2763
  }
2663
2764
  if (existingContent.includes(content.trim())) {
2664
- console.log(`[fullstack-cli] \u25CB ${path4.basename(dest)} (already contains content)`);
2765
+ console.log(`[fullstack-cli] \u25CB ${path5.basename(dest)} (already contains content)`);
2665
2766
  return;
2666
2767
  }
2667
- fs6.appendFileSync(dest, content);
2668
- console.log(`[fullstack-cli] \u2713 ${path4.basename(dest)} (appended)`);
2768
+ fs7.appendFileSync(dest, content);
2769
+ console.log(`[fullstack-cli] \u2713 ${path5.basename(dest)} (appended)`);
2669
2770
  }
2670
2771
  function setPermissions(permissions, projectRoot) {
2671
2772
  for (const [pattern, mode] of Object.entries(permissions)) {
2672
2773
  if (pattern === "**/*.sh") {
2673
- const scriptsDir = path4.join(projectRoot, "scripts");
2674
- if (fs6.existsSync(scriptsDir)) {
2675
- const files = fs6.readdirSync(scriptsDir);
2774
+ const scriptsDir = path5.join(projectRoot, "scripts");
2775
+ if (fs7.existsSync(scriptsDir)) {
2776
+ const files = fs7.readdirSync(scriptsDir);
2676
2777
  files.forEach((file) => {
2677
2778
  if (file.endsWith(".sh")) {
2678
- const filePath = path4.join(scriptsDir, file);
2679
- fs6.chmodSync(filePath, mode);
2779
+ const filePath = path5.join(scriptsDir, file);
2780
+ fs7.chmodSync(filePath, mode);
2680
2781
  }
2681
2782
  });
2682
2783
  }
@@ -2684,27 +2785,27 @@ function setPermissions(permissions, projectRoot) {
2684
2785
  }
2685
2786
  }
2686
2787
  function deleteFile(filePath) {
2687
- if (fs6.existsSync(filePath)) {
2688
- fs6.unlinkSync(filePath);
2689
- console.log(`[fullstack-cli] \u2713 ${path4.basename(filePath)} (deleted)`);
2788
+ if (fs7.existsSync(filePath)) {
2789
+ fs7.unlinkSync(filePath);
2790
+ console.log(`[fullstack-cli] \u2713 ${path5.basename(filePath)} (deleted)`);
2690
2791
  } else {
2691
- console.log(`[fullstack-cli] \u25CB ${path4.basename(filePath)} (not found)`);
2792
+ console.log(`[fullstack-cli] \u25CB ${path5.basename(filePath)} (not found)`);
2692
2793
  }
2693
2794
  }
2694
2795
  function deleteDirectory(dirPath) {
2695
- if (fs6.existsSync(dirPath)) {
2696
- fs6.rmSync(dirPath, { recursive: true });
2697
- console.log(`[fullstack-cli] \u2713 ${path4.basename(dirPath)} (deleted)`);
2796
+ if (fs7.existsSync(dirPath)) {
2797
+ fs7.rmSync(dirPath, { recursive: true });
2798
+ console.log(`[fullstack-cli] \u2713 ${path5.basename(dirPath)} (deleted)`);
2698
2799
  } else {
2699
- console.log(`[fullstack-cli] \u25CB ${path4.basename(dirPath)} (not found)`);
2800
+ console.log(`[fullstack-cli] \u25CB ${path5.basename(dirPath)} (not found)`);
2700
2801
  }
2701
2802
  }
2702
2803
  function addScript(packageJsonPath, name, command, overwrite) {
2703
- if (!fs6.existsSync(packageJsonPath)) {
2804
+ if (!fs7.existsSync(packageJsonPath)) {
2704
2805
  console.log(`[fullstack-cli] \u25CB package.json (not found)`);
2705
2806
  return;
2706
2807
  }
2707
- const content = fs6.readFileSync(packageJsonPath, "utf-8");
2808
+ const content = fs7.readFileSync(packageJsonPath, "utf-8");
2708
2809
  const pkg2 = JSON.parse(content);
2709
2810
  if (!pkg2.scripts) {
2710
2811
  pkg2.scripts = {};
@@ -2716,42 +2817,42 @@ function addScript(packageJsonPath, name, command, overwrite) {
2716
2817
  }
2717
2818
  }
2718
2819
  pkg2.scripts[name] = command;
2719
- fs6.writeFileSync(packageJsonPath, JSON.stringify(pkg2, null, 2) + "\n");
2820
+ fs7.writeFileSync(packageJsonPath, JSON.stringify(pkg2, null, 2) + "\n");
2720
2821
  console.log(`[fullstack-cli] \u2713 scripts.${name}`);
2721
2822
  }
2722
2823
  function addLineToFile(filePath, line) {
2723
- const fileName = path4.basename(filePath);
2724
- if (!fs6.existsSync(filePath)) {
2824
+ const fileName = path5.basename(filePath);
2825
+ if (!fs7.existsSync(filePath)) {
2725
2826
  console.log(`[fullstack-cli] \u25CB ${fileName} (not found, skipped)`);
2726
2827
  return;
2727
2828
  }
2728
- const content = fs6.readFileSync(filePath, "utf-8");
2829
+ const content = fs7.readFileSync(filePath, "utf-8");
2729
2830
  const lines = content.split("\n").map((l) => l.trim());
2730
2831
  if (lines.includes(line)) {
2731
2832
  console.log(`[fullstack-cli] \u25CB ${fileName} (line already exists: ${line})`);
2732
2833
  return;
2733
2834
  }
2734
2835
  const appendContent = (content.endsWith("\n") ? "" : "\n") + line + "\n";
2735
- fs6.appendFileSync(filePath, appendContent);
2836
+ fs7.appendFileSync(filePath, appendContent);
2736
2837
  console.log(`[fullstack-cli] \u2713 ${fileName} (added: ${line})`);
2737
2838
  }
2738
2839
  function mergeJsonFile(src, dest, arrayMerge) {
2739
- const fileName = path4.basename(dest);
2740
- if (!fs6.existsSync(src)) {
2840
+ const fileName = path5.basename(dest);
2841
+ if (!fs7.existsSync(src)) {
2741
2842
  console.warn(`[fullstack-cli] Source not found: ${src}`);
2742
2843
  return;
2743
2844
  }
2744
- const templateContent = JSON.parse(fs6.readFileSync(src, "utf-8"));
2745
- if (!fs6.existsSync(dest)) {
2746
- const destDir = path4.dirname(dest);
2747
- if (!fs6.existsSync(destDir)) {
2748
- fs6.mkdirSync(destDir, { recursive: true });
2845
+ const templateContent = JSON.parse(fs7.readFileSync(src, "utf-8"));
2846
+ if (!fs7.existsSync(dest)) {
2847
+ const destDir = path5.dirname(dest);
2848
+ if (!fs7.existsSync(destDir)) {
2849
+ fs7.mkdirSync(destDir, { recursive: true });
2749
2850
  }
2750
- fs6.writeFileSync(dest, JSON.stringify(templateContent, null, 2) + "\n");
2851
+ fs7.writeFileSync(dest, JSON.stringify(templateContent, null, 2) + "\n");
2751
2852
  console.log(`[fullstack-cli] \u2713 ${fileName} (created)`);
2752
2853
  return;
2753
2854
  }
2754
- const userContent = JSON.parse(fs6.readFileSync(dest, "utf-8"));
2855
+ const userContent = JSON.parse(fs7.readFileSync(dest, "utf-8"));
2755
2856
  const merged = deepMergeJson(userContent, templateContent, arrayMerge ?? {});
2756
2857
  const userStr = JSON.stringify(userContent, null, 2);
2757
2858
  const mergedStr = JSON.stringify(merged, null, 2);
@@ -2759,7 +2860,7 @@ function mergeJsonFile(src, dest, arrayMerge) {
2759
2860
  console.log(`[fullstack-cli] \u25CB ${fileName} (already up to date)`);
2760
2861
  return;
2761
2862
  }
2762
- fs6.writeFileSync(dest, mergedStr + "\n");
2863
+ fs7.writeFileSync(dest, mergedStr + "\n");
2763
2864
  console.log(`[fullstack-cli] \u2713 ${fileName} (merged)`);
2764
2865
  }
2765
2866
 
@@ -2820,12 +2921,12 @@ async function reportCreateInstanceEvent(pluginKey, version) {
2820
2921
 
2821
2922
  // src/utils/git.ts
2822
2923
  import { execSync, spawnSync as spawnSync2 } from "child_process";
2823
- import fs7 from "fs";
2824
- import path5 from "path";
2924
+ import fs8 from "fs";
2925
+ import path6 from "path";
2825
2926
  function isGitRepository(cwd = process.cwd()) {
2826
2927
  try {
2827
- const gitDir = path5.join(cwd, ".git");
2828
- if (fs7.existsSync(gitDir)) {
2928
+ const gitDir = path6.join(cwd, ".git");
2929
+ if (fs8.existsSync(gitDir)) {
2829
2930
  return true;
2830
2931
  }
2831
2932
  const result = spawnSync2("git", ["rev-parse", "--git-dir"], {
@@ -2854,7 +2955,7 @@ function getChangedFiles(cwd = process.cwd()) {
2854
2955
  function gitAddUpgradeFiles(cwd = process.cwd(), filesToStage) {
2855
2956
  const filteredFiles = [];
2856
2957
  for (const filePath of filesToStage) {
2857
- if (fs7.existsSync(path5.join(cwd, filePath))) {
2958
+ if (fs8.existsSync(path6.join(cwd, filePath))) {
2858
2959
  filteredFiles.push(filePath);
2859
2960
  continue;
2860
2961
  }
@@ -2938,64 +3039,6 @@ Auto-committed by fullstack-cli`;
2938
3039
  }
2939
3040
  }
2940
3041
 
2941
- // src/utils/package-json.ts
2942
- import fs8 from "fs";
2943
- import path6 from "path";
2944
- function readPackageJson(cwd = process.cwd()) {
2945
- const pkgPath = path6.join(cwd, "package.json");
2946
- if (!fs8.existsSync(pkgPath)) {
2947
- throw new Error(`package.json not found at ${pkgPath}`);
2948
- }
2949
- const content = fs8.readFileSync(pkgPath, "utf-8");
2950
- return JSON.parse(content);
2951
- }
2952
- function writePackageJson(pkg2, cwd = process.cwd()) {
2953
- const pkgPath = path6.join(cwd, "package.json");
2954
- const content = JSON.stringify(pkg2, null, 2) + "\n";
2955
- fs8.writeFileSync(pkgPath, content, "utf-8");
2956
- }
2957
- function removeUpgradeScript(pkg2) {
2958
- if (!pkg2.scripts?.upgrade) {
2959
- return false;
2960
- }
2961
- delete pkg2.scripts.upgrade;
2962
- return true;
2963
- }
2964
- function cleanDevScript(pkg2) {
2965
- if (!pkg2.scripts?.dev) {
2966
- return false;
2967
- }
2968
- const originalDev = pkg2.scripts.dev;
2969
- const cleanedDev = originalDev.replace(/npm\s+run\s+upgrade\s*&&\s*/g, "").replace(/npm\s+run\s+upgrade\s*$/g, "").trim();
2970
- if (cleanedDev !== originalDev) {
2971
- pkg2.scripts.dev = cleanedDev;
2972
- return true;
2973
- }
2974
- return false;
2975
- }
2976
- function cleanupPackageJson(cwd = process.cwd()) {
2977
- try {
2978
- const pkg2 = readPackageJson(cwd);
2979
- let changed = false;
2980
- if (removeUpgradeScript(pkg2)) {
2981
- console.log("[fullstack-cli] \u2713 Removed scripts.upgrade");
2982
- changed = true;
2983
- }
2984
- if (cleanDevScript(pkg2)) {
2985
- console.log("[fullstack-cli] \u2713 Cleaned scripts.dev (removed npm run upgrade)");
2986
- changed = true;
2987
- }
2988
- if (changed) {
2989
- writePackageJson(pkg2, cwd);
2990
- }
2991
- return changed;
2992
- } catch (error) {
2993
- const message = error instanceof Error ? error.message : String(error);
2994
- console.log(`[fullstack-cli] \u26A0 Could not cleanup package.json: ${message}`);
2995
- return false;
2996
- }
2997
- }
2998
-
2999
3042
  // src/commands/upgrade/shared/utils.ts
3000
3043
  import path7 from "path";
3001
3044
  import fs9 from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-cli",
3
- "version": "1.1.16-beta.12",
3
+ "version": "1.1.16-beta.14",
4
4
  "description": "CLI tool for fullstack template management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -56,6 +56,5 @@
56
56
  },
57
57
  "peerDependencies": {
58
58
  "typescript": "^5.9.2"
59
- },
60
- "migrationVersion": 1
59
+ }
61
60
  }
@@ -1,7 +1,7 @@
1
1
  import { defineConfig, Config } from 'drizzle-kit';
2
2
  require('dotenv').config();
3
3
 
4
- const outputDir = process.env.__DRIZZLE_OUT_DIR__ || './server/database/.introspect';
4
+ const outputDir = process.env.__DRIZZLE_OUT_DIR__ || './tmp/.introspect';
5
5
  const schemaPath = process.env.__DRIZZLE_SCHEMA_PATH__ || './server/database/schema.ts';
6
6
 
7
7
  const parsedUrl = new URL(process.env.SUDA_DATABASE_URL || '');
@@ -19,82 +19,149 @@ print_time() {
19
19
  }
20
20
 
21
21
  # ==================== 步骤 0 ====================
22
- echo "🗑️ [0/5] 安装插件"
22
+ echo "🗑️ [0/6] 安装插件"
23
23
  STEP_START=$(node -e "console.log(Date.now())")
24
24
  npx fullstack-cli action-plugin init
25
25
  print_time $STEP_START
26
26
  echo ""
27
27
 
28
28
  # ==================== 步骤 1 ====================
29
- echo "📝 [1/5] 更新 openapi 代码"
29
+ echo "📝 [1/6] 更新 openapi 代码"
30
30
  STEP_START=$(node -e "console.log(Date.now())")
31
31
  npm run gen:openapi
32
32
  print_time $STEP_START
33
33
  echo ""
34
34
 
35
35
  # ==================== 步骤 2 ====================
36
- echo "🗑️ [2/5] 清理 dist 目录"
36
+ echo "🗑️ [2/6] 清理 dist 目录"
37
37
  STEP_START=$(node -e "console.log(Date.now())")
38
38
  rm -rf "$ROOT_DIR/dist"
39
39
  print_time $STEP_START
40
40
  echo ""
41
41
 
42
42
  # ==================== 步骤 3 ====================
43
- echo "🔨 [3/5] 并行构建 server 和 client"
43
+ echo "🗺️ [3/6] 生成路由定义"
44
44
  STEP_START=$(node -e "console.log(Date.now())")
45
45
 
46
- # 并行构建
47
- echo " ├─ 启动 server 构建..."
48
- npm run build:server > /tmp/build-server.log 2>&1 &
49
- SERVER_PID=$!
46
+ # 在 client/server 构建之前生成到 dist/,供 DefinePlugin 注入前端 bundle
47
+ # 注意:nest-cli.json deleteOutDir 必须为 false(模板默认值),否则 nest build 会清掉 dist/
48
+ echo " ├─ 生成 API 路由定义..."
49
+ npx generate-api-routes --server-dir ./server --out-dir ./dist > /tmp/gen-api-routes.log 2>&1 &
50
+ API_ROUTES_PID=$!
50
51
 
51
- echo " ├─ 启动 client 构建..."
52
- npm run build:client > /tmp/build-client.log 2>&1 &
53
- CLIENT_PID=$!
52
+ echo " ├─ 生成页面路由定义..."
53
+ npx generate-page-routes --app-path ./client/src/app.tsx --out-dir ./dist > /tmp/gen-page-routes.log 2>&1 &
54
+ PAGE_ROUTES_PID=$!
54
55
 
55
- # 等待两个构建完成
56
- SERVER_EXIT=0
57
- CLIENT_EXIT=0
56
+ API_ROUTES_EXIT=0
57
+ PAGE_ROUTES_EXIT=0
58
58
 
59
- wait $SERVER_PID || SERVER_EXIT=$?
60
- wait $CLIENT_PID || CLIENT_EXIT=$?
59
+ wait $API_ROUTES_PID || API_ROUTES_EXIT=$?
60
+ wait $PAGE_ROUTES_PID || PAGE_ROUTES_EXIT=$?
61
61
 
62
- # 检查构建结果
63
- if [ $SERVER_EXIT -ne 0 ]; then
64
- echo " ❌ Server 构建失败"
65
- cat /tmp/build-server.log
66
- exit 1
62
+ if [ $API_ROUTES_EXIT -ne 0 ]; then
63
+ echo " ⚠️ API 路由生成失败(不影响构建)"
64
+ cat /tmp/gen-api-routes.log
65
+ else
66
+ echo " ✅ API 路由生成完成"
67
67
  fi
68
68
 
69
- if [ $CLIENT_EXIT -ne 0 ]; then
70
- echo " ❌ Client 构建失败"
71
- cat /tmp/build-client.log
72
- exit 1
69
+ if [ $PAGE_ROUTES_EXIT -ne 0 ]; then
70
+ echo " ⚠️ 页面路由生成失败(不影响构建)"
71
+ cat /tmp/gen-page-routes.log
72
+ else
73
+ echo " ✅ 页面路由生成完成"
73
74
  fi
74
-
75
- echo " ✅ Server 构建完成"
76
- echo " ✅ Client 构建完成"
77
75
  print_time $STEP_START
78
76
  echo ""
79
77
 
80
78
  # ==================== 步骤 4 ====================
81
- echo "📦 [4/5] 准备产物"
79
+ echo "🔨 [4/6] 并行构建 server 和 client"
82
80
  STEP_START=$(node -e "console.log(Date.now())")
83
81
 
84
- # 拷贝 run.sh dist/(prod dist/ 启动,确保 cwd 一致性)
85
- cp "$ROOT_DIR/scripts/run.sh" "$DIST_DIR/"
86
-
87
- # 拷贝 .env 文件(如果存在)
88
- if [ -f "$ROOT_DIR/.env" ]; then
89
- cp "$ROOT_DIR/.env" "$DIST_DIR/"
82
+ # server/client 构建子进程预留 8GB heap,缓解 vite build transform 阶段 OOM
83
+ # (典型错误:Reached heap limit Allocation failed)。
84
+ # 仅在外部未设置 NODE_OPTIONS 时注入,允许 CI / 用户通过外部环境变量完全覆盖
85
+ BUILD_NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=8192}"
86
+
87
+ # 根据 only_frontend_change 决定是否构建 server
88
+ if [[ "${only_frontend_change:-false}" == "true" ]]; then
89
+ echo "🔨 [4/6] 仅构建 client (only_frontend_change=true)"
90
+
91
+ echo " ├─ 启动 client 构建..."
92
+ NODE_OPTIONS="$BUILD_NODE_OPTIONS" npm run build:client > /tmp/build-client.log 2>&1
93
+ CLIENT_EXIT=$?
94
+
95
+ if [ $CLIENT_EXIT -ne 0 ]; then
96
+ echo " ❌ Client 构建失败"
97
+ cat /tmp/build-client.log
98
+ exit 1
99
+ fi
100
+
101
+ echo " ✅ Client 构建完成"
102
+ else
103
+ echo "🔨 [4/6] 并行构建 server 和 client"
104
+
105
+ # 并行构建
106
+ echo " ├─ 启动 server 构建..."
107
+ NODE_OPTIONS="$BUILD_NODE_OPTIONS" npm run build:server > /tmp/build-server.log 2>&1 &
108
+ SERVER_PID=$!
109
+
110
+ echo " ├─ 启动 client 构建..."
111
+ NODE_OPTIONS="$BUILD_NODE_OPTIONS" npm run build:client > /tmp/build-client.log 2>&1 &
112
+ CLIENT_PID=$!
113
+
114
+ # 等待两个构建完成
115
+ SERVER_EXIT=0
116
+ CLIENT_EXIT=0
117
+
118
+ wait $SERVER_PID || SERVER_EXIT=$?
119
+ wait $CLIENT_PID || CLIENT_EXIT=$?
120
+
121
+ # 检查构建结果
122
+ if [ $SERVER_EXIT -ne 0 ]; then
123
+ echo " ❌ Server 构建失败"
124
+ cat /tmp/build-server.log
125
+ exit 1
126
+ fi
127
+
128
+ if [ $CLIENT_EXIT -ne 0 ]; then
129
+ echo " ❌ Client 构建失败"
130
+ cat /tmp/build-client.log
131
+ exit 1
132
+ fi
133
+
134
+ echo " ✅ Server 构建完成"
135
+ echo " ✅ Client 构建完成"
90
136
  fi
91
137
 
138
+ print_time $STEP_START
139
+ echo ""
140
+
141
+ # ==================== 步骤 5 ====================
142
+ echo "📦 [5/6] 准备产物"
143
+ STEP_START=$(node -e "console.log(Date.now())")
144
+
92
145
  # 移动 client 下的 HTML 文件到 dist/dist/client,保证 views 路径在 dev/prod 下一致
146
+ # 使用 mv 而非 cp:HTML 不能上传到公网 CDN,移走后 dist/client 中不再包含 HTML
93
147
  if [ -d "$DIST_DIR/client" ]; then
94
148
  mkdir -p "$DIST_DIR/dist/client"
95
149
  find "$DIST_DIR/client" -maxdepth 1 -name "*.html" -exec mv {} "$DIST_DIR/dist/client/" \;
96
150
  fi
97
151
 
152
+ # server 相关产物准备(only_frontend_change=true 时跳过)
153
+ if [[ "${only_frontend_change:-false}" == "true" ]]; then
154
+ echo " [skip] 跳过 run.sh/.env 复制 (only_frontend_change=true)"
155
+ else
156
+ # 拷贝 run.sh 到 dist/(prod 从 dist/ 启动,确保 cwd 一致性)
157
+ cp "$ROOT_DIR/scripts/run.sh" "$DIST_DIR/"
158
+
159
+ # 拷贝 .env 文件(如果存在)
160
+ if [ -f "$ROOT_DIR/.env" ]; then
161
+ cp "$ROOT_DIR/.env" "$DIST_DIR/"
162
+ fi
163
+ fi
164
+
98
165
  # 清理无用文件
99
166
  rm -rf "$DIST_DIR/scripts"
100
167
  rm -rf "$DIST_DIR/tsconfig.node.tsbuildinfo"
@@ -102,12 +169,19 @@ rm -rf "$DIST_DIR/tsconfig.node.tsbuildinfo"
102
169
  print_time $STEP_START
103
170
  echo ""
104
171
 
105
- # ==================== 步骤 5 ====================
106
- echo "✂️ [5/5] 智能依赖裁剪"
172
+ # ==================== 步骤 6 ====================
173
+ echo "✂️ [6/6] 智能依赖裁剪"
107
174
  STEP_START=$(node -e "console.log(Date.now())")
108
175
 
109
- # 分析实际依赖、复制并裁剪 node_modules、生成精简的 package.json
110
- node "$ROOT_DIR/scripts/prune-smart.js"
176
+ # 智能依赖裁剪(仅全量构建时执行,纯前端场景不打包 server,无需裁剪)
177
+ if [[ "${only_frontend_change:-false}" == "true" ]]; then
178
+ echo "✂️ [6/6] 跳过智能依赖裁剪 (only_frontend_change=true)"
179
+ else
180
+ echo "✂️ [6/6] 智能依赖裁剪"
181
+
182
+ # 分析实际依赖、复制并裁剪 node_modules、生成精简的 package.json
183
+ node "$ROOT_DIR/scripts/prune-smart.js"
184
+ fi
111
185
 
112
186
  print_time $STEP_START
113
187
  echo ""
@@ -118,9 +192,16 @@ print_time $TOTAL_START
118
192
 
119
193
  # 输出产物信息
120
194
  DIST_SIZE=$(du -sh "$DIST_DIR" | cut -f1)
121
- NODE_MODULES_SIZE=$(du -sh "$DIST_DIR/node_modules" | cut -f1)
122
- echo ""
123
- echo "📊 构建产物统计:"
124
- echo " 产物大小: $DIST_SIZE"
125
- echo " node_modules: $NODE_MODULES_SIZE"
126
- echo ""
195
+ if [[ "${only_frontend_change:-false}" == "true" ]]; then
196
+ echo ""
197
+ echo "📊 构建产物统计:"
198
+ echo " 产物大小: $DIST_SIZE"
199
+ echo ""
200
+ else
201
+ NODE_MODULES_SIZE=$(du -sh "$DIST_DIR/node_modules" | cut -f1)
202
+ echo ""
203
+ echo "📊 构建产物统计:"
204
+ echo " 产物大小: $DIST_SIZE"
205
+ echo " node_modules: $NODE_MODULES_SIZE"
206
+ echo ""
207
+ fi
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+
3
+ const path = require('node:path');
4
+ const { spawn } = require('node:child_process');
5
+ const fs = require('node:fs');
6
+
7
+ const cwd = process.cwd();
8
+
9
+ function getBinName(name) {
10
+ return process.platform === 'win32' ? `${name}.cmd` : name;
11
+ }
12
+
13
+ function runCommand(command, args) {
14
+ return new Promise((resolve) => {
15
+ const child = spawn(command, args, {
16
+ cwd,
17
+ stdio: 'inherit',
18
+ shell: false,
19
+ });
20
+
21
+ child.on('close', (code) => resolve(code || 0));
22
+ child.on('error', () => resolve(1));
23
+ });
24
+ }
25
+
26
+ function normalizeProjectFile(filePath) {
27
+ const absolutePath = path.isAbsolute(filePath)
28
+ ? filePath
29
+ : path.resolve(cwd, filePath);
30
+
31
+ if (!fs.existsSync(absolutePath)) {
32
+ console.warn(`[lint] Skip missing file: ${filePath}`);
33
+ return null;
34
+ }
35
+
36
+ const relativePath = path.relative(cwd, absolutePath);
37
+ if (relativePath.startsWith('..')) {
38
+ console.warn(`[lint] Skip file outside project: ${filePath}`);
39
+ return null;
40
+ }
41
+
42
+ return relativePath.split(path.sep).join('/');
43
+ }
44
+
45
+ function parseFilesArg(argv) {
46
+ const filesIndex = argv.indexOf('--files');
47
+ if (filesIndex === -1) {
48
+ return null;
49
+ }
50
+
51
+ return argv.slice(filesIndex + 1).filter(Boolean);
52
+ }
53
+
54
+ function isEslintTarget(filePath) {
55
+ return /\.(c|m)?(j|t)sx?$/.test(filePath);
56
+ }
57
+
58
+ function isTypeCheckTarget(filePath) {
59
+ return /\.(ts|tsx|mts|cts)$/.test(filePath);
60
+ }
61
+
62
+ function isStylelintTarget(filePath) {
63
+ return filePath.endsWith('.css');
64
+ }
65
+
66
+ async function runDefaultLint() {
67
+ const code = await runCommand(getBinName('npx'), [
68
+ 'concurrently',
69
+ 'npm run eslint',
70
+ 'npm run type:check',
71
+ 'npm run stylelint',
72
+ ]);
73
+ process.exit(code);
74
+ }
75
+
76
+ async function runSelectiveLint(inputFiles) {
77
+ const normalizedFiles = Array.from(
78
+ new Set(inputFiles.map(normalizeProjectFile).filter(Boolean)),
79
+ );
80
+
81
+ if (normalizedFiles.length === 0) {
82
+ console.log('[lint] No supported project files found');
83
+ process.exit(0);
84
+ }
85
+
86
+ const eslintFiles = normalizedFiles.filter(isEslintTarget);
87
+ const stylelintFiles = normalizedFiles.filter(isStylelintTarget);
88
+ const typeCheckFiles = normalizedFiles.filter(isTypeCheckTarget);
89
+
90
+ const clientTypeFiles = [];
91
+ const serverTypeFiles = [];
92
+
93
+ for (const filePath of typeCheckFiles) {
94
+ if (filePath.startsWith('client/')) {
95
+ clientTypeFiles.push(filePath);
96
+ } else if (filePath.startsWith('server/')) {
97
+ serverTypeFiles.push(filePath);
98
+ } else if (filePath.startsWith('shared/')) {
99
+ clientTypeFiles.push(filePath);
100
+ serverTypeFiles.push(filePath);
101
+ }
102
+ }
103
+
104
+ const tasks = [];
105
+
106
+ if (eslintFiles.length > 0) {
107
+ tasks.push(runCommand(getBinName('npx'), ['eslint', '--quiet', ...eslintFiles]));
108
+ }
109
+
110
+ if (stylelintFiles.length > 0) {
111
+ tasks.push(runCommand(getBinName('npx'), ['stylelint', '--quiet', ...stylelintFiles]));
112
+ }
113
+
114
+ if (clientTypeFiles.length > 0) {
115
+ tasks.push(runCommand(getBinName('npm'), ['run', 'type:check:client']));
116
+ }
117
+
118
+ if (serverTypeFiles.length > 0) {
119
+ tasks.push(runCommand(getBinName('npm'), ['run', 'type:check:server']));
120
+ }
121
+
122
+ if (tasks.length === 0) {
123
+ console.log('[lint] No supported files matched for lint');
124
+ process.exit(0);
125
+ }
126
+
127
+ const results = await Promise.all(tasks);
128
+ process.exit(results.some(code => code !== 0) ? 1 : 0);
129
+ }
130
+
131
+ async function main() {
132
+ const files = parseFilesArg(process.argv.slice(2));
133
+ if (files === null) {
134
+ await runDefaultLint();
135
+ return;
136
+ }
137
+
138
+ if (files.length === 0) {
139
+ console.error('[lint] --files requires at least one file path');
140
+ process.exit(1);
141
+ }
142
+
143
+ await runSelectiveLint(files);
144
+ }
145
+
146
+ main().catch((error) => {
147
+ const message = error instanceof Error ? error.message : String(error);
148
+ console.error(`[lint] Failed to run lint: ${message}`);
149
+ process.exit(1);
150
+ });