@lark-apaas/fullstack-cli 1.1.35 → 1.1.36
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 +199 -192
- package/package.json +1 -1
- package/templates/scripts/lint.js +150 -0
package/dist/index.js
CHANGED
|
@@ -2363,8 +2363,8 @@ var genDbSchemaCommand = {
|
|
|
2363
2363
|
};
|
|
2364
2364
|
|
|
2365
2365
|
// src/commands/sync/run.handler.ts
|
|
2366
|
-
import
|
|
2367
|
-
import
|
|
2366
|
+
import path5 from "path";
|
|
2367
|
+
import fs7 from "fs";
|
|
2368
2368
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2369
2369
|
|
|
2370
2370
|
// src/config/sync.ts
|
|
@@ -2525,18 +2525,93 @@ function deepMergeJson(user, template, arrayMerge = {}, currentPath = "") {
|
|
|
2525
2525
|
return result;
|
|
2526
2526
|
}
|
|
2527
2527
|
|
|
2528
|
+
// src/utils/package-json.ts
|
|
2529
|
+
import fs6 from "fs";
|
|
2530
|
+
import path4 from "path";
|
|
2531
|
+
var LEGACY_LINT_SCRIPT = 'concurrently "npm run eslint" "npm run type:check" "npm run stylelint"';
|
|
2532
|
+
var PATCHED_LINT_SCRIPT = "node ./scripts/lint.js";
|
|
2533
|
+
function readPackageJson(cwd = process.cwd()) {
|
|
2534
|
+
const pkgPath = path4.join(cwd, "package.json");
|
|
2535
|
+
if (!fs6.existsSync(pkgPath)) {
|
|
2536
|
+
throw new Error(`package.json not found at ${pkgPath}`);
|
|
2537
|
+
}
|
|
2538
|
+
const content = fs6.readFileSync(pkgPath, "utf-8");
|
|
2539
|
+
return JSON.parse(content);
|
|
2540
|
+
}
|
|
2541
|
+
function writePackageJson(pkg2, cwd = process.cwd()) {
|
|
2542
|
+
const pkgPath = path4.join(cwd, "package.json");
|
|
2543
|
+
const content = JSON.stringify(pkg2, null, 2) + "\n";
|
|
2544
|
+
fs6.writeFileSync(pkgPath, content, "utf-8");
|
|
2545
|
+
}
|
|
2546
|
+
function patchLintScriptForFilesSupport(pkg2) {
|
|
2547
|
+
const currentLint = pkg2.scripts?.lint;
|
|
2548
|
+
if (!currentLint) {
|
|
2549
|
+
return "skipped-missing";
|
|
2550
|
+
}
|
|
2551
|
+
if (currentLint === PATCHED_LINT_SCRIPT || currentLint === "node scripts/lint.js") {
|
|
2552
|
+
return "already-patched";
|
|
2553
|
+
}
|
|
2554
|
+
if (currentLint !== LEGACY_LINT_SCRIPT) {
|
|
2555
|
+
return "skipped-custom";
|
|
2556
|
+
}
|
|
2557
|
+
pkg2.scripts = pkg2.scripts || {};
|
|
2558
|
+
pkg2.scripts.lint = PATCHED_LINT_SCRIPT;
|
|
2559
|
+
return "patched";
|
|
2560
|
+
}
|
|
2561
|
+
function removeUpgradeScript(pkg2) {
|
|
2562
|
+
if (!pkg2.scripts?.upgrade) {
|
|
2563
|
+
return false;
|
|
2564
|
+
}
|
|
2565
|
+
delete pkg2.scripts.upgrade;
|
|
2566
|
+
return true;
|
|
2567
|
+
}
|
|
2568
|
+
function cleanDevScript(pkg2) {
|
|
2569
|
+
if (!pkg2.scripts?.dev) {
|
|
2570
|
+
return false;
|
|
2571
|
+
}
|
|
2572
|
+
const originalDev = pkg2.scripts.dev;
|
|
2573
|
+
const cleanedDev = originalDev.replace(/npm\s+run\s+upgrade\s*&&\s*/g, "").replace(/npm\s+run\s+upgrade\s*$/g, "").trim();
|
|
2574
|
+
if (cleanedDev !== originalDev) {
|
|
2575
|
+
pkg2.scripts.dev = cleanedDev;
|
|
2576
|
+
return true;
|
|
2577
|
+
}
|
|
2578
|
+
return false;
|
|
2579
|
+
}
|
|
2580
|
+
function cleanupPackageJson(cwd = process.cwd()) {
|
|
2581
|
+
try {
|
|
2582
|
+
const pkg2 = readPackageJson(cwd);
|
|
2583
|
+
let changed = false;
|
|
2584
|
+
if (removeUpgradeScript(pkg2)) {
|
|
2585
|
+
console.log("[fullstack-cli] \u2713 Removed scripts.upgrade");
|
|
2586
|
+
changed = true;
|
|
2587
|
+
}
|
|
2588
|
+
if (cleanDevScript(pkg2)) {
|
|
2589
|
+
console.log("[fullstack-cli] \u2713 Cleaned scripts.dev (removed npm run upgrade)");
|
|
2590
|
+
changed = true;
|
|
2591
|
+
}
|
|
2592
|
+
if (changed) {
|
|
2593
|
+
writePackageJson(pkg2, cwd);
|
|
2594
|
+
}
|
|
2595
|
+
return changed;
|
|
2596
|
+
} catch (error) {
|
|
2597
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2598
|
+
console.log(`[fullstack-cli] \u26A0 Could not cleanup package.json: ${message}`);
|
|
2599
|
+
return false;
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2528
2603
|
// src/commands/sync/run.handler.ts
|
|
2529
2604
|
async function run2(options) {
|
|
2530
2605
|
const userProjectRoot = process.env.INIT_CWD || process.cwd();
|
|
2531
2606
|
const __filename = fileURLToPath3(import.meta.url);
|
|
2532
|
-
const __dirname2 =
|
|
2533
|
-
const pluginRoot =
|
|
2607
|
+
const __dirname2 = path5.dirname(__filename);
|
|
2608
|
+
const pluginRoot = path5.resolve(__dirname2, "..");
|
|
2534
2609
|
if (userProjectRoot === pluginRoot) {
|
|
2535
2610
|
console.log("[fullstack-cli] Skip syncing (installing plugin itself)");
|
|
2536
2611
|
process.exit(0);
|
|
2537
2612
|
}
|
|
2538
|
-
const userPackageJson =
|
|
2539
|
-
if (!
|
|
2613
|
+
const userPackageJson = path5.join(userProjectRoot, "package.json");
|
|
2614
|
+
if (!fs7.existsSync(userPackageJson)) {
|
|
2540
2615
|
console.log("[fullstack-cli] Skip syncing (not a valid npm project)");
|
|
2541
2616
|
process.exit(0);
|
|
2542
2617
|
}
|
|
@@ -2552,6 +2627,7 @@ async function run2(options) {
|
|
|
2552
2627
|
for (const rule of config.sync) {
|
|
2553
2628
|
await syncRule(rule, pluginRoot, userProjectRoot);
|
|
2554
2629
|
}
|
|
2630
|
+
patchUserPackageJson(userProjectRoot);
|
|
2555
2631
|
if (config.permissions) {
|
|
2556
2632
|
setPermissions(config.permissions, userProjectRoot);
|
|
2557
2633
|
}
|
|
@@ -2562,9 +2638,32 @@ async function run2(options) {
|
|
|
2562
2638
|
process.exit(1);
|
|
2563
2639
|
}
|
|
2564
2640
|
}
|
|
2641
|
+
function patchUserPackageJson(userProjectRoot) {
|
|
2642
|
+
try {
|
|
2643
|
+
const pkg2 = readPackageJson(userProjectRoot);
|
|
2644
|
+
const lintPatchResult = patchLintScriptForFilesSupport(pkg2);
|
|
2645
|
+
if (lintPatchResult === "patched") {
|
|
2646
|
+
writePackageJson(pkg2, userProjectRoot);
|
|
2647
|
+
console.log("[fullstack-cli] \u2713 Patched scripts.lint to support --files");
|
|
2648
|
+
return;
|
|
2649
|
+
}
|
|
2650
|
+
if (lintPatchResult === "already-patched") {
|
|
2651
|
+
console.log("[fullstack-cli] \u25CB scripts.lint already supports --files");
|
|
2652
|
+
return;
|
|
2653
|
+
}
|
|
2654
|
+
if (lintPatchResult === "skipped-custom") {
|
|
2655
|
+
console.warn(
|
|
2656
|
+
"[fullstack-cli] \u26A0 Skipped patching scripts.lint because it has been customized"
|
|
2657
|
+
);
|
|
2658
|
+
}
|
|
2659
|
+
} catch (error) {
|
|
2660
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2661
|
+
console.warn(`[fullstack-cli] \u26A0 Could not patch package.json: ${message}`);
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2565
2664
|
async function syncRule(rule, pluginRoot, userProjectRoot) {
|
|
2566
2665
|
if (rule.type === "delete-file" || rule.type === "delete-directory") {
|
|
2567
|
-
const destPath2 =
|
|
2666
|
+
const destPath2 = path5.join(userProjectRoot, rule.to);
|
|
2568
2667
|
if (rule.type === "delete-file") {
|
|
2569
2668
|
deleteFile(destPath2);
|
|
2570
2669
|
} else {
|
|
@@ -2573,32 +2672,32 @@ async function syncRule(rule, pluginRoot, userProjectRoot) {
|
|
|
2573
2672
|
return;
|
|
2574
2673
|
}
|
|
2575
2674
|
if (rule.type === "remove-line") {
|
|
2576
|
-
const destPath2 =
|
|
2675
|
+
const destPath2 = path5.join(userProjectRoot, rule.to);
|
|
2577
2676
|
removeLineFromFile(destPath2, rule.pattern);
|
|
2578
2677
|
return;
|
|
2579
2678
|
}
|
|
2580
2679
|
if (rule.type === "add-script") {
|
|
2581
|
-
const packageJsonPath =
|
|
2680
|
+
const packageJsonPath = path5.join(userProjectRoot, "package.json");
|
|
2582
2681
|
addScript(packageJsonPath, rule.name, rule.command, rule.overwrite ?? false);
|
|
2583
2682
|
return;
|
|
2584
2683
|
}
|
|
2585
2684
|
if (rule.type === "add-line") {
|
|
2586
|
-
const destPath2 =
|
|
2685
|
+
const destPath2 = path5.join(userProjectRoot, rule.to);
|
|
2587
2686
|
addLineToFile(destPath2, rule.line);
|
|
2588
2687
|
return;
|
|
2589
2688
|
}
|
|
2590
2689
|
if (rule.type === "merge-json") {
|
|
2591
|
-
const srcPath2 =
|
|
2592
|
-
const destPath2 =
|
|
2690
|
+
const srcPath2 = path5.join(pluginRoot, rule.from);
|
|
2691
|
+
const destPath2 = path5.join(userProjectRoot, rule.to);
|
|
2593
2692
|
mergeJsonFile(srcPath2, destPath2, rule.arrayMerge);
|
|
2594
2693
|
return;
|
|
2595
2694
|
}
|
|
2596
2695
|
if (!("from" in rule)) {
|
|
2597
2696
|
return;
|
|
2598
2697
|
}
|
|
2599
|
-
const srcPath =
|
|
2600
|
-
const destPath =
|
|
2601
|
-
if (!
|
|
2698
|
+
const srcPath = path5.join(pluginRoot, rule.from);
|
|
2699
|
+
const destPath = path5.join(userProjectRoot, rule.to);
|
|
2700
|
+
if (!fs7.existsSync(srcPath)) {
|
|
2602
2701
|
console.warn(`[fullstack-cli] Source not found: ${rule.from}`);
|
|
2603
2702
|
return;
|
|
2604
2703
|
}
|
|
@@ -2615,68 +2714,68 @@ async function syncRule(rule, pluginRoot, userProjectRoot) {
|
|
|
2615
2714
|
}
|
|
2616
2715
|
}
|
|
2617
2716
|
function syncFile(src, dest, overwrite = true, onlyIfExists = false) {
|
|
2618
|
-
if (onlyIfExists && !
|
|
2619
|
-
console.log(`[fullstack-cli] \u25CB ${
|
|
2717
|
+
if (onlyIfExists && !fs7.existsSync(dest)) {
|
|
2718
|
+
console.log(`[fullstack-cli] \u25CB ${path5.basename(dest)} (skipped, target not exists)`);
|
|
2620
2719
|
return;
|
|
2621
2720
|
}
|
|
2622
|
-
const destDir =
|
|
2623
|
-
if (!
|
|
2624
|
-
|
|
2721
|
+
const destDir = path5.dirname(dest);
|
|
2722
|
+
if (!fs7.existsSync(destDir)) {
|
|
2723
|
+
fs7.mkdirSync(destDir, { recursive: true });
|
|
2625
2724
|
}
|
|
2626
|
-
if (
|
|
2627
|
-
console.log(`[fullstack-cli] \u25CB ${
|
|
2725
|
+
if (fs7.existsSync(dest) && !overwrite) {
|
|
2726
|
+
console.log(`[fullstack-cli] \u25CB ${path5.basename(dest)} (skipped, already exists)`);
|
|
2628
2727
|
return;
|
|
2629
2728
|
}
|
|
2630
|
-
|
|
2631
|
-
console.log(`[fullstack-cli] \u2713 ${
|
|
2729
|
+
fs7.copyFileSync(src, dest);
|
|
2730
|
+
console.log(`[fullstack-cli] \u2713 ${path5.basename(dest)}`);
|
|
2632
2731
|
}
|
|
2633
2732
|
function syncDirectory(src, dest, overwrite = true) {
|
|
2634
|
-
if (!
|
|
2635
|
-
|
|
2733
|
+
if (!fs7.existsSync(dest)) {
|
|
2734
|
+
fs7.mkdirSync(dest, { recursive: true });
|
|
2636
2735
|
}
|
|
2637
|
-
const files =
|
|
2736
|
+
const files = fs7.readdirSync(src);
|
|
2638
2737
|
let count = 0;
|
|
2639
2738
|
files.forEach((file) => {
|
|
2640
|
-
const srcFile =
|
|
2641
|
-
const destFile =
|
|
2642
|
-
const stats =
|
|
2739
|
+
const srcFile = path5.join(src, file);
|
|
2740
|
+
const destFile = path5.join(dest, file);
|
|
2741
|
+
const stats = fs7.statSync(srcFile);
|
|
2643
2742
|
if (stats.isDirectory()) {
|
|
2644
2743
|
syncDirectory(srcFile, destFile, overwrite);
|
|
2645
2744
|
} else {
|
|
2646
|
-
if (overwrite || !
|
|
2647
|
-
|
|
2648
|
-
console.log(`[fullstack-cli] \u2713 ${
|
|
2745
|
+
if (overwrite || !fs7.existsSync(destFile)) {
|
|
2746
|
+
fs7.copyFileSync(srcFile, destFile);
|
|
2747
|
+
console.log(`[fullstack-cli] \u2713 ${path5.relative(dest, destFile)}`);
|
|
2649
2748
|
count++;
|
|
2650
2749
|
}
|
|
2651
2750
|
}
|
|
2652
2751
|
});
|
|
2653
2752
|
if (count > 0) {
|
|
2654
|
-
console.log(`[fullstack-cli] Synced ${count} files to ${
|
|
2753
|
+
console.log(`[fullstack-cli] Synced ${count} files to ${path5.basename(dest)}/`);
|
|
2655
2754
|
}
|
|
2656
2755
|
}
|
|
2657
2756
|
function appendToFile(src, dest) {
|
|
2658
|
-
const content =
|
|
2757
|
+
const content = fs7.readFileSync(src, "utf-8");
|
|
2659
2758
|
let existingContent = "";
|
|
2660
|
-
if (
|
|
2661
|
-
existingContent =
|
|
2759
|
+
if (fs7.existsSync(dest)) {
|
|
2760
|
+
existingContent = fs7.readFileSync(dest, "utf-8");
|
|
2662
2761
|
}
|
|
2663
2762
|
if (existingContent.includes(content.trim())) {
|
|
2664
|
-
console.log(`[fullstack-cli] \u25CB ${
|
|
2763
|
+
console.log(`[fullstack-cli] \u25CB ${path5.basename(dest)} (already contains content)`);
|
|
2665
2764
|
return;
|
|
2666
2765
|
}
|
|
2667
|
-
|
|
2668
|
-
console.log(`[fullstack-cli] \u2713 ${
|
|
2766
|
+
fs7.appendFileSync(dest, content);
|
|
2767
|
+
console.log(`[fullstack-cli] \u2713 ${path5.basename(dest)} (appended)`);
|
|
2669
2768
|
}
|
|
2670
2769
|
function setPermissions(permissions, projectRoot) {
|
|
2671
2770
|
for (const [pattern, mode] of Object.entries(permissions)) {
|
|
2672
2771
|
if (pattern === "**/*.sh") {
|
|
2673
|
-
const scriptsDir =
|
|
2674
|
-
if (
|
|
2675
|
-
const files =
|
|
2772
|
+
const scriptsDir = path5.join(projectRoot, "scripts");
|
|
2773
|
+
if (fs7.existsSync(scriptsDir)) {
|
|
2774
|
+
const files = fs7.readdirSync(scriptsDir);
|
|
2676
2775
|
files.forEach((file) => {
|
|
2677
2776
|
if (file.endsWith(".sh")) {
|
|
2678
|
-
const filePath =
|
|
2679
|
-
|
|
2777
|
+
const filePath = path5.join(scriptsDir, file);
|
|
2778
|
+
fs7.chmodSync(filePath, mode);
|
|
2680
2779
|
}
|
|
2681
2780
|
});
|
|
2682
2781
|
}
|
|
@@ -2684,27 +2783,27 @@ function setPermissions(permissions, projectRoot) {
|
|
|
2684
2783
|
}
|
|
2685
2784
|
}
|
|
2686
2785
|
function deleteFile(filePath) {
|
|
2687
|
-
if (
|
|
2688
|
-
|
|
2689
|
-
console.log(`[fullstack-cli] \u2713 ${
|
|
2786
|
+
if (fs7.existsSync(filePath)) {
|
|
2787
|
+
fs7.unlinkSync(filePath);
|
|
2788
|
+
console.log(`[fullstack-cli] \u2713 ${path5.basename(filePath)} (deleted)`);
|
|
2690
2789
|
} else {
|
|
2691
|
-
console.log(`[fullstack-cli] \u25CB ${
|
|
2790
|
+
console.log(`[fullstack-cli] \u25CB ${path5.basename(filePath)} (not found)`);
|
|
2692
2791
|
}
|
|
2693
2792
|
}
|
|
2694
2793
|
function deleteDirectory(dirPath) {
|
|
2695
|
-
if (
|
|
2696
|
-
|
|
2697
|
-
console.log(`[fullstack-cli] \u2713 ${
|
|
2794
|
+
if (fs7.existsSync(dirPath)) {
|
|
2795
|
+
fs7.rmSync(dirPath, { recursive: true });
|
|
2796
|
+
console.log(`[fullstack-cli] \u2713 ${path5.basename(dirPath)} (deleted)`);
|
|
2698
2797
|
} else {
|
|
2699
|
-
console.log(`[fullstack-cli] \u25CB ${
|
|
2798
|
+
console.log(`[fullstack-cli] \u25CB ${path5.basename(dirPath)} (not found)`);
|
|
2700
2799
|
}
|
|
2701
2800
|
}
|
|
2702
2801
|
function addScript(packageJsonPath, name, command, overwrite) {
|
|
2703
|
-
if (!
|
|
2802
|
+
if (!fs7.existsSync(packageJsonPath)) {
|
|
2704
2803
|
console.log(`[fullstack-cli] \u25CB package.json (not found)`);
|
|
2705
2804
|
return;
|
|
2706
2805
|
}
|
|
2707
|
-
const content =
|
|
2806
|
+
const content = fs7.readFileSync(packageJsonPath, "utf-8");
|
|
2708
2807
|
const pkg2 = JSON.parse(content);
|
|
2709
2808
|
if (!pkg2.scripts) {
|
|
2710
2809
|
pkg2.scripts = {};
|
|
@@ -2716,42 +2815,42 @@ function addScript(packageJsonPath, name, command, overwrite) {
|
|
|
2716
2815
|
}
|
|
2717
2816
|
}
|
|
2718
2817
|
pkg2.scripts[name] = command;
|
|
2719
|
-
|
|
2818
|
+
fs7.writeFileSync(packageJsonPath, JSON.stringify(pkg2, null, 2) + "\n");
|
|
2720
2819
|
console.log(`[fullstack-cli] \u2713 scripts.${name}`);
|
|
2721
2820
|
}
|
|
2722
2821
|
function addLineToFile(filePath, line) {
|
|
2723
|
-
const fileName =
|
|
2724
|
-
if (!
|
|
2822
|
+
const fileName = path5.basename(filePath);
|
|
2823
|
+
if (!fs7.existsSync(filePath)) {
|
|
2725
2824
|
console.log(`[fullstack-cli] \u25CB ${fileName} (not found, skipped)`);
|
|
2726
2825
|
return;
|
|
2727
2826
|
}
|
|
2728
|
-
const content =
|
|
2827
|
+
const content = fs7.readFileSync(filePath, "utf-8");
|
|
2729
2828
|
const lines = content.split("\n").map((l) => l.trim());
|
|
2730
2829
|
if (lines.includes(line)) {
|
|
2731
2830
|
console.log(`[fullstack-cli] \u25CB ${fileName} (line already exists: ${line})`);
|
|
2732
2831
|
return;
|
|
2733
2832
|
}
|
|
2734
2833
|
const appendContent = (content.endsWith("\n") ? "" : "\n") + line + "\n";
|
|
2735
|
-
|
|
2834
|
+
fs7.appendFileSync(filePath, appendContent);
|
|
2736
2835
|
console.log(`[fullstack-cli] \u2713 ${fileName} (added: ${line})`);
|
|
2737
2836
|
}
|
|
2738
2837
|
function mergeJsonFile(src, dest, arrayMerge) {
|
|
2739
|
-
const fileName =
|
|
2740
|
-
if (!
|
|
2838
|
+
const fileName = path5.basename(dest);
|
|
2839
|
+
if (!fs7.existsSync(src)) {
|
|
2741
2840
|
console.warn(`[fullstack-cli] Source not found: ${src}`);
|
|
2742
2841
|
return;
|
|
2743
2842
|
}
|
|
2744
|
-
const templateContent = JSON.parse(
|
|
2745
|
-
if (!
|
|
2746
|
-
const destDir =
|
|
2747
|
-
if (!
|
|
2748
|
-
|
|
2843
|
+
const templateContent = JSON.parse(fs7.readFileSync(src, "utf-8"));
|
|
2844
|
+
if (!fs7.existsSync(dest)) {
|
|
2845
|
+
const destDir = path5.dirname(dest);
|
|
2846
|
+
if (!fs7.existsSync(destDir)) {
|
|
2847
|
+
fs7.mkdirSync(destDir, { recursive: true });
|
|
2749
2848
|
}
|
|
2750
|
-
|
|
2849
|
+
fs7.writeFileSync(dest, JSON.stringify(templateContent, null, 2) + "\n");
|
|
2751
2850
|
console.log(`[fullstack-cli] \u2713 ${fileName} (created)`);
|
|
2752
2851
|
return;
|
|
2753
2852
|
}
|
|
2754
|
-
const userContent = JSON.parse(
|
|
2853
|
+
const userContent = JSON.parse(fs7.readFileSync(dest, "utf-8"));
|
|
2755
2854
|
const merged = deepMergeJson(userContent, templateContent, arrayMerge ?? {});
|
|
2756
2855
|
const userStr = JSON.stringify(userContent, null, 2);
|
|
2757
2856
|
const mergedStr = JSON.stringify(merged, null, 2);
|
|
@@ -2759,7 +2858,7 @@ function mergeJsonFile(src, dest, arrayMerge) {
|
|
|
2759
2858
|
console.log(`[fullstack-cli] \u25CB ${fileName} (already up to date)`);
|
|
2760
2859
|
return;
|
|
2761
2860
|
}
|
|
2762
|
-
|
|
2861
|
+
fs7.writeFileSync(dest, mergedStr + "\n");
|
|
2763
2862
|
console.log(`[fullstack-cli] \u2713 ${fileName} (merged)`);
|
|
2764
2863
|
}
|
|
2765
2864
|
|
|
@@ -2820,12 +2919,12 @@ async function reportCreateInstanceEvent(pluginKey, version) {
|
|
|
2820
2919
|
|
|
2821
2920
|
// src/utils/git.ts
|
|
2822
2921
|
import { execSync, spawnSync as spawnSync2 } from "child_process";
|
|
2823
|
-
import
|
|
2824
|
-
import
|
|
2922
|
+
import fs8 from "fs";
|
|
2923
|
+
import path6 from "path";
|
|
2825
2924
|
function isGitRepository(cwd = process.cwd()) {
|
|
2826
2925
|
try {
|
|
2827
|
-
const gitDir =
|
|
2828
|
-
if (
|
|
2926
|
+
const gitDir = path6.join(cwd, ".git");
|
|
2927
|
+
if (fs8.existsSync(gitDir)) {
|
|
2829
2928
|
return true;
|
|
2830
2929
|
}
|
|
2831
2930
|
const result = spawnSync2("git", ["rev-parse", "--git-dir"], {
|
|
@@ -2854,7 +2953,7 @@ function getChangedFiles(cwd = process.cwd()) {
|
|
|
2854
2953
|
function gitAddUpgradeFiles(cwd = process.cwd(), filesToStage) {
|
|
2855
2954
|
const filteredFiles = [];
|
|
2856
2955
|
for (const filePath of filesToStage) {
|
|
2857
|
-
if (
|
|
2956
|
+
if (fs8.existsSync(path6.join(cwd, filePath))) {
|
|
2858
2957
|
filteredFiles.push(filePath);
|
|
2859
2958
|
continue;
|
|
2860
2959
|
}
|
|
@@ -2938,64 +3037,6 @@ Auto-committed by fullstack-cli`;
|
|
|
2938
3037
|
}
|
|
2939
3038
|
}
|
|
2940
3039
|
|
|
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
3040
|
// src/commands/upgrade/shared/utils.ts
|
|
3000
3041
|
import path7 from "path";
|
|
3001
3042
|
import fs9 from "fs";
|
|
@@ -7264,48 +7305,26 @@ var UPLOAD_STATIC_DEFAULTS = {
|
|
|
7264
7305
|
};
|
|
7265
7306
|
|
|
7266
7307
|
// src/commands/build/api-client.ts
|
|
7267
|
-
var ERROR_RESPONSE_PREVIEW_LIMIT = 2e3;
|
|
7268
|
-
async function getErrorResponseDetails(response) {
|
|
7269
|
-
try {
|
|
7270
|
-
if (typeof response.text === "function") {
|
|
7271
|
-
const text = await response.text();
|
|
7272
|
-
if (!text) {
|
|
7273
|
-
return "";
|
|
7274
|
-
}
|
|
7275
|
-
return `, response: ${text.slice(0, ERROR_RESPONSE_PREVIEW_LIMIT)}`;
|
|
7276
|
-
}
|
|
7277
|
-
if (typeof response.json === "function") {
|
|
7278
|
-
const data = await response.json();
|
|
7279
|
-
const serialized = JSON.stringify(data);
|
|
7280
|
-
if (!serialized) {
|
|
7281
|
-
return "";
|
|
7282
|
-
}
|
|
7283
|
-
return `, response: ${serialized.slice(0, ERROR_RESPONSE_PREVIEW_LIMIT)}`;
|
|
7284
|
-
}
|
|
7285
|
-
} catch {
|
|
7286
|
-
return "";
|
|
7287
|
-
}
|
|
7288
|
-
return "";
|
|
7289
|
-
}
|
|
7290
|
-
async function throwIfResponseNotOk(response, message) {
|
|
7291
|
-
if (response.ok && response.status === 200) {
|
|
7292
|
-
return;
|
|
7293
|
-
}
|
|
7294
|
-
const details = await getErrorResponseDetails(response);
|
|
7295
|
-
throw new Error(`${message}: ${response.status} ${response.statusText}${details}`);
|
|
7296
|
-
}
|
|
7297
7308
|
async function genArtifactUploadCredential(appId, body) {
|
|
7298
7309
|
const client = getHttpClient();
|
|
7299
7310
|
const url = `/v1/app/${appId}/pipeline/gen_artifact_upload_credential`;
|
|
7300
7311
|
const response = await client.post(url, body);
|
|
7301
|
-
|
|
7312
|
+
if (!response.ok || response.status !== 200) {
|
|
7313
|
+
throw new Error(
|
|
7314
|
+
`gen_artifact_upload_credential \u8BF7\u6C42\u5931\u8D25: ${response.status} ${response.statusText}`
|
|
7315
|
+
);
|
|
7316
|
+
}
|
|
7302
7317
|
return response.json();
|
|
7303
7318
|
}
|
|
7304
7319
|
async function getDefaultBucketId(appId) {
|
|
7305
7320
|
const client = getHttpClient();
|
|
7306
7321
|
const url = `/v1/app/${appId}/storage/inner/staticBucket`;
|
|
7307
7322
|
const response = await client.post(url, {});
|
|
7308
|
-
|
|
7323
|
+
if (!response.ok || response.status !== 200) {
|
|
7324
|
+
throw new Error(
|
|
7325
|
+
`getOrCreateStaticBucket \u8BF7\u6C42\u5931\u8D25: ${response.status} ${response.statusText}`
|
|
7326
|
+
);
|
|
7327
|
+
}
|
|
7309
7328
|
const data = await response.json();
|
|
7310
7329
|
if (data.status_code !== "0") {
|
|
7311
7330
|
throw new Error(`getOrCreateStaticBucket \u8FD4\u56DE\u5F02\u5E38, status_code: ${data.status_code}`);
|
|
@@ -7320,14 +7339,22 @@ async function preUploadStaticAttachment(appId, bucketId) {
|
|
|
7320
7339
|
const client = getHttpClient();
|
|
7321
7340
|
const url = `/v1/app/${appId}/storage/bucket/${bucketId}/preUploadStatic`;
|
|
7322
7341
|
const response = await client.post(url, {});
|
|
7323
|
-
|
|
7342
|
+
if (!response.ok || response.status !== 200) {
|
|
7343
|
+
throw new Error(
|
|
7344
|
+
`preUploadStatic \u8BF7\u6C42\u5931\u8D25: ${response.status} ${response.statusText}`
|
|
7345
|
+
);
|
|
7346
|
+
}
|
|
7324
7347
|
return response.json();
|
|
7325
7348
|
}
|
|
7326
7349
|
async function uploadStaticAttachmentCallback(appId, bucketId, body) {
|
|
7327
7350
|
const client = getHttpClient();
|
|
7328
7351
|
const url = `/v1/app/${appId}/storage/bucket/${bucketId}/object/callbackStatic`;
|
|
7329
7352
|
const response = await client.post(url, body);
|
|
7330
|
-
|
|
7353
|
+
if (!response.ok || response.status !== 200) {
|
|
7354
|
+
throw new Error(
|
|
7355
|
+
`callbackStatic \u8BF7\u6C42\u5931\u8D25: ${response.status} ${response.statusText}`
|
|
7356
|
+
);
|
|
7357
|
+
}
|
|
7331
7358
|
return response.json();
|
|
7332
7359
|
}
|
|
7333
7360
|
|
|
@@ -7368,7 +7395,6 @@ import * as fs25 from "fs";
|
|
|
7368
7395
|
import * as os2 from "os";
|
|
7369
7396
|
import * as path21 from "path";
|
|
7370
7397
|
import { execFileSync } from "child_process";
|
|
7371
|
-
import { HttpError } from "@lark-apaas/http-client";
|
|
7372
7398
|
function readCredentialsFromEnv() {
|
|
7373
7399
|
const uploadPrefix = process.env.STATIC_UPLOAD_PREFIX;
|
|
7374
7400
|
const uploadID = process.env.STATIC_UPLOAD_ID;
|
|
@@ -7448,7 +7474,7 @@ async function uploadStatic(options) {
|
|
|
7448
7474
|
console.error(`${LOG_PREFIX} \u8C03\u7528 callbackStatic (uploadID: ${uploadID})...`);
|
|
7449
7475
|
const callbackResp = await uploadStaticAttachmentCallback(appId, bucketId, { uploadID });
|
|
7450
7476
|
if (callbackResp.status_code !== "0") {
|
|
7451
|
-
throw new Error(`callbackStatic \u8FD4\u56DE\u5F02\u5E38, status_code: ${callbackResp.status_code}
|
|
7477
|
+
throw new Error(`callbackStatic \u8FD4\u56DE\u5F02\u5E38, status_code: ${callbackResp.status_code}`);
|
|
7452
7478
|
}
|
|
7453
7479
|
const attachments = callbackResp.data?.attachments || [];
|
|
7454
7480
|
console.error(`${LOG_PREFIX} \u4E0A\u4F20\u5B8C\u6210\uFF0C\u5171 ${attachments.length} \u4E2A\u6587\u4EF6`);
|
|
@@ -7456,23 +7482,7 @@ async function uploadStatic(options) {
|
|
|
7456
7482
|
} catch (error) {
|
|
7457
7483
|
const message = error instanceof Error ? error.message : String(error);
|
|
7458
7484
|
console.error(`${LOG_PREFIX} Error: ${message}`);
|
|
7459
|
-
|
|
7460
|
-
const logId = error.response.headers.get("x-tt-logid") || error.response.headers.get("X-Tt-Logid");
|
|
7461
|
-
if (logId) {
|
|
7462
|
-
console.error(`${LOG_PREFIX} logid: ${logId}`);
|
|
7463
|
-
}
|
|
7464
|
-
try {
|
|
7465
|
-
const responseText = await error.response.text();
|
|
7466
|
-
if (responseText) {
|
|
7467
|
-
console.error(`${LOG_PREFIX} response: ${responseText}`);
|
|
7468
|
-
}
|
|
7469
|
-
} catch {
|
|
7470
|
-
}
|
|
7471
|
-
if (error.config?.url) {
|
|
7472
|
-
console.error(`${LOG_PREFIX} request url: ${error.config.url}`);
|
|
7473
|
-
}
|
|
7474
|
-
}
|
|
7475
|
-
process.exitCode = 1;
|
|
7485
|
+
process.exit(1);
|
|
7476
7486
|
}
|
|
7477
7487
|
}
|
|
7478
7488
|
function resolveTosutilPath(tosutilPath) {
|
|
@@ -7489,14 +7499,14 @@ function resolveTosutilPath(tosutilPath) {
|
|
|
7489
7499
|
async function fetchPreUpload(appId, bucketId) {
|
|
7490
7500
|
const response = await preUploadStaticAttachment(appId, bucketId);
|
|
7491
7501
|
if (response.status_code !== "0") {
|
|
7492
|
-
throw new Error(`preUploadStatic \u8FD4\u56DE\u5F02\u5E38, status_code: ${response.status_code}
|
|
7502
|
+
throw new Error(`preUploadStatic \u8FD4\u56DE\u5F02\u5E38, status_code: ${response.status_code}`);
|
|
7493
7503
|
}
|
|
7494
7504
|
const { uploadPrefix, uploadID, uploadCredential } = response.data || {};
|
|
7495
7505
|
if (!uploadPrefix || !uploadID) {
|
|
7496
|
-
throw new Error(
|
|
7506
|
+
throw new Error("preUploadStatic \u8FD4\u56DE\u6570\u636E\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11 uploadPrefix \u6216 uploadID");
|
|
7497
7507
|
}
|
|
7498
7508
|
if (!uploadCredential?.AccessKeyID || !uploadCredential?.SecretAccessKey || !uploadCredential?.SessionToken) {
|
|
7499
|
-
throw new Error(
|
|
7509
|
+
throw new Error("preUploadStatic \u8FD4\u56DE\u7684\u51ED\u8BC1\u5B57\u6BB5\u4E0D\u5B8C\u6574");
|
|
7500
7510
|
}
|
|
7501
7511
|
return response;
|
|
7502
7512
|
}
|
|
@@ -7541,18 +7551,15 @@ async function preUploadStatic(options) {
|
|
|
7541
7551
|
const response = await preUploadStaticAttachment(appId, bucketId);
|
|
7542
7552
|
if (response.status_code !== "0") {
|
|
7543
7553
|
console.error(`${LOG_PREFIX2} preUploadStatic \u8FD4\u56DE\u5F02\u5E38, status_code: ${response.status_code}`);
|
|
7544
|
-
console.error(`${LOG_PREFIX2} preUploadStatic \u54CD\u5E94: ${JSON.stringify(response)}`);
|
|
7545
7554
|
return;
|
|
7546
7555
|
}
|
|
7547
7556
|
const { downloadURLPrefix, uploadPrefix, uploadID, uploadCredential } = response.data || {};
|
|
7548
7557
|
if (!downloadURLPrefix || !uploadPrefix || !uploadID) {
|
|
7549
7558
|
console.error(`${LOG_PREFIX2} preUploadStatic \u8FD4\u56DE\u6570\u636E\u4E0D\u5B8C\u6574`);
|
|
7550
|
-
console.error(`${LOG_PREFIX2} preUploadStatic \u54CD\u5E94: ${JSON.stringify(response)}`);
|
|
7551
7559
|
return;
|
|
7552
7560
|
}
|
|
7553
7561
|
if (!uploadCredential?.AccessKeyID || !uploadCredential?.SecretAccessKey || !uploadCredential?.SessionToken) {
|
|
7554
7562
|
console.error(`${LOG_PREFIX2} preUploadStatic \u8FD4\u56DE\u7684\u51ED\u8BC1\u5B57\u6BB5\u4E0D\u5B8C\u6574`);
|
|
7555
|
-
console.error(`${LOG_PREFIX2} preUploadStatic \u54CD\u5E94: ${JSON.stringify(response)}`);
|
|
7556
7563
|
return;
|
|
7557
7564
|
}
|
|
7558
7565
|
console.log(`export STATIC_ASSETS_BASE_URL="${shellEscape(downloadURLPrefix)}"`);
|
package/package.json
CHANGED
|
@@ -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
|
+
});
|