@4399ywkf/cli 2.0.11 → 2.0.12

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.
Files changed (2) hide show
  1. package/dist/index.js +187 -13
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -492,6 +492,10 @@ async function injectMasterAppPages(pagesDir, appName) {
492
492
  path.join(pagesDir, "micro/layout.tsx"),
493
493
  MASTER_MICRO_LAYOUT
494
494
  );
495
+ await writeFile(
496
+ path.join(pagesDir, "micro/$.tsx"),
497
+ MASTER_MICRO_CATCHALL
498
+ );
495
499
  await writeFile(
496
500
  path.join(pagesDir, "page.tsx"),
497
501
  MASTER_HOME_PAGE
@@ -626,35 +630,172 @@ export default function MicroLayout() {
626
630
  );
627
631
  }
628
632
  `;
629
- const MASTER_MICRO_PAGE = `import { Typography, Card, Empty, Space, Tag, Descriptions } from "antd";
633
+ const MASTER_MICRO_PAGE = `import { useMemo, useState, useEffect, useCallback } from "react";
634
+ import { useNavigate } from "react-router";
635
+ import {
636
+ Typography,
637
+ Card,
638
+ Space,
639
+ Tag,
640
+ Table,
641
+ Button,
642
+ Empty,
643
+ Tooltip,
644
+ Descriptions,
645
+ } from "antd";
646
+ import type { ColumnsType } from "antd/es/table";
647
+ import Garfish from "garfish";
648
+
649
+ const { Title, Text } = Typography;
650
+
651
+ interface SubAppInfo {
652
+ name: string;
653
+ entry: string;
654
+ activeWhen?: string | ((path: string) => boolean);
655
+ basename?: string;
656
+ }
630
657
 
631
- const { Title, Paragraph } = Typography;
658
+ type AppStatus = "online" | "offline" | "checking";
659
+
660
+ function useSubApps() {
661
+ return useMemo<SubAppInfo[]>(() => {
662
+ const infos = Garfish.appInfos ?? {};
663
+ return Object.values(infos).map((info) => ({
664
+ name: info.name,
665
+ entry: info.entry,
666
+ activeWhen: info.activeWhen,
667
+ basename: info.basename,
668
+ }));
669
+ }, []);
670
+ }
671
+
672
+ function useAppStatusMap(apps: SubAppInfo[]) {
673
+ const [statusMap, setStatusMap] = useState<Record<string, AppStatus>>(() =>
674
+ Object.fromEntries(apps.map((a) => [a.name, "checking" as const]))
675
+ );
676
+
677
+ const checkStatus = useCallback(async (app: SubAppInfo) => {
678
+ try {
679
+ const res = await fetch(app.entry, { mode: "no-cors", cache: "no-store" });
680
+ return (res.status === 0 || res.ok) ? "online" : "offline";
681
+ } catch {
682
+ return "offline";
683
+ }
684
+ }, []);
685
+
686
+ useEffect(() => {
687
+ if (apps.length === 0) return;
688
+ let cancelled = false;
689
+
690
+ async function checkAll() {
691
+ const results = await Promise.all(
692
+ apps.map(async (app) => [app.name, await checkStatus(app)] as const)
693
+ );
694
+ if (!cancelled) setStatusMap(Object.fromEntries(results));
695
+ }
696
+
697
+ checkAll();
698
+ const timer = setInterval(checkAll, 15_000);
699
+ return () => { cancelled = true; clearInterval(timer); };
700
+ }, [apps, checkStatus]);
701
+
702
+ return statusMap;
703
+ }
704
+
705
+ const STATUS_TAG: Record<AppStatus, { color: string; text: string }> = {
706
+ online: { color: "success", text: "\u5728\u7EBF" },
707
+ offline: { color: "error", text: "\u79BB\u7EBF" },
708
+ checking: { color: "processing", text: "\u68C0\u6D4B\u4E2D" },
709
+ };
632
710
 
633
711
  export default function MicroPage() {
712
+ const navigate = useNavigate();
713
+ const apps = useSubApps();
714
+ const statusMap = useAppStatusMap(apps);
715
+
716
+ const columns: ColumnsType<SubAppInfo> = [
717
+ {
718
+ title: "\u5E94\u7528\u540D\u79F0",
719
+ dataIndex: "name",
720
+ render: (name: string) => <Text strong>{name}</Text>,
721
+ },
722
+ {
723
+ title: "\u5165\u53E3\u5730\u5740",
724
+ dataIndex: "entry",
725
+ render: (entry: string) => (
726
+ <Tooltip title="\u70B9\u51FB\u5728\u65B0\u7A97\u53E3\u6253\u5F00">
727
+ <a href={entry} target="_blank" rel="noopener noreferrer">{entry}</a>
728
+ </Tooltip>
729
+ ),
730
+ },
731
+ {
732
+ title: "\u6FC0\u6D3B\u8DEF\u7531",
733
+ dataIndex: "activeWhen",
734
+ render: (val: SubAppInfo["activeWhen"]) =>
735
+ typeof val === "string" ? <code>{val}</code> : <Tag>\u81EA\u5B9A\u4E49\u51FD\u6570</Tag>,
736
+ },
737
+ {
738
+ title: "\u72B6\u6001",
739
+ key: "status",
740
+ width: 100,
741
+ render: (_, record) => {
742
+ const s = statusMap[record.name] ?? "checking";
743
+ const tag = STATUS_TAG[s];
744
+ return <Tag color={tag.color}>{tag.text}</Tag>;
745
+ },
746
+ },
747
+ {
748
+ title: "\u64CD\u4F5C",
749
+ key: "action",
750
+ width: 100,
751
+ render: (_, record) => {
752
+ const route = typeof record.activeWhen === "string" ? record.activeWhen : null;
753
+ if (!route) return null;
754
+ return (
755
+ <Button type="link" size="small" onClick={() => navigate(route)}>
756
+ \u8BBF\u95EE
757
+ </Button>
758
+ );
759
+ },
760
+ },
761
+ ];
762
+
634
763
  return (
635
- <div style={{ maxWidth: 800, margin: "0 auto" }}>
764
+ <div style={{ maxWidth: 960, margin: "0 auto" }}>
636
765
  <Title level={2}>
637
766
  \u5FAE\u524D\u7AEF\u7BA1\u7406 <Tag color="green">Garfish</Tag>
638
767
  </Title>
639
768
 
640
769
  <Space direction="vertical" size="middle" style={{ width: "100%" }}>
641
- <Card title="\u5B50\u5E94\u7528\u5BB9\u5668">
642
- <Empty
643
- description={
644
- <Paragraph type="secondary">
645
- \u6682\u672A\u6CE8\u518C\u5B50\u5E94\u7528\u3002\u8BF7\u5728 <code>ywkf.config.ts</code> \u4E2D\u914D\u7F6E <code>garfishPlugin</code> \u7684 apps \u5217\u8868\u3002
646
- </Paragraph>
647
- }
648
- />
770
+ <Card title={\`\u5B50\u5E94\u7528\u5217\u8868\uFF08\${apps.length}\uFF09\`}>
771
+ {apps.length > 0 ? (
772
+ <Table<SubAppInfo>
773
+ rowKey="name"
774
+ columns={columns}
775
+ dataSource={apps}
776
+ pagination={false}
777
+ size="middle"
778
+ />
779
+ ) : (
780
+ <Empty
781
+ description={
782
+ <Text type="secondary">
783
+ \u6682\u672A\u6CE8\u518C\u5B50\u5E94\u7528\u3002\u8BF7\u5728 <code>ywkf.config.ts</code> \u4E2D\u914D\u7F6E{" "}
784
+ <code>garfishPlugin</code> \u7684 apps \u5217\u8868\u3002
785
+ </Text>
786
+ }
787
+ />
788
+ )}
649
789
  </Card>
650
790
 
651
791
  <Card title="\u63A5\u5165\u8BF4\u660E">
652
792
  <Descriptions bordered column={1} size="small">
653
793
  <Descriptions.Item label="\u5B50\u5E94\u7528\u6CE8\u518C">
654
- <code>garfishPlugin({'{'} master: true, apps: [...] {'}'})</code>
794
+ <code>garfishPlugin({"{"} master: true, apps: [...] {"}"})</code>
655
795
  </Descriptions.Item>
656
796
  <Descriptions.Item label="\u6302\u8F7D\u5BB9\u5668">
657
- <code>&lt;div id="subapp-container" /&gt;</code>\uFF08\u5DF2\u5185\u7F6E\u5728\u5F53\u524D\u5E03\u5C40\u4E2D\uFF09
797
+ <code>&lt;div id=&quot;subapp-container&quot; /&gt;</code>
798
+ \uFF08\u5DF2\u5185\u7F6E\u5728\u5F53\u524D\u5E03\u5C40\u4E2D\uFF09
658
799
  </Descriptions.Item>
659
800
  <Descriptions.Item label="\u8DEF\u7531\u524D\u7F00">
660
801
  \u5B50\u5E94\u7528 <code>activeRule</code> \u5EFA\u8BAE\u4EE5 <code>/micro/xxx</code> \u5F00\u5934
@@ -669,6 +810,39 @@ export default function MicroPage() {
669
810
  );
670
811
  }
671
812
  `;
813
+ const MASTER_MICRO_CATCHALL = `import { Typography, Spin, Alert } from "antd";
814
+ import { useLocation } from "react-router";
815
+
816
+ const { Paragraph } = Typography;
817
+
818
+ export default function MicroCatchAll() {
819
+ const { pathname } = useLocation();
820
+
821
+ return (
822
+ <div style={{ maxWidth: 720, margin: "0 auto", padding: "40px 0" }}>
823
+ <Alert
824
+ type="warning"
825
+ showIcon
826
+ message="\u5B50\u5E94\u7528\u8DEF\u7531\u533A\u57DF"
827
+ description={
828
+ <>
829
+ <Paragraph>
830
+ \u5F53\u524D\u8DEF\u5F84 <code>{pathname}</code> \u9884\u671F\u7531 Garfish \u5B50\u5E94\u7528\u63A5\u7BA1\u3002
831
+ </Paragraph>
832
+ <Paragraph type="secondary">
833
+ \u5982\u679C\u4F60\u770B\u5230\u6B64\u9875\u9762\uFF0C\u8BF4\u660E\u5BF9\u5E94\u7684\u5B50\u5E94\u7528\u5C1A\u672A\u6CE8\u518C\u6216\u672A\u542F\u52A8\u3002
834
+ \u8BF7\u68C0\u67E5 <code>ywkf.config.ts</code> \u4E2D <code>garfishPlugin</code> \u7684 apps \u914D\u7F6E\u3002
835
+ </Paragraph>
836
+ </>
837
+ }
838
+ />
839
+ <div style={{ textAlign: "center", marginTop: 24 }}>
840
+ <Spin tip="\u7B49\u5F85\u5B50\u5E94\u7528\u52A0\u8F7D..." />
841
+ </div>
842
+ </div>
843
+ );
844
+ }
845
+ `;
672
846
  const SUB_LAYOUT = `import { Outlet, Link, useLocation } from "react-router";
673
847
  import { Layout, Menu, Tag } from "antd";
674
848
  import { useEffect, useState } from "react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4399ywkf/cli",
3
- "version": "2.0.11",
3
+ "version": "2.0.12",
4
4
  "description": "运维开发部脚手架",
5
5
  "main": "dist/index.js",
6
6
  "bin": {