github-stars-organizer 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/assets/{backups-r4Hq-y_s.js → backups-DrK5WGqD.js} +1 -1
- package/dist/app/assets/badge-Drt9JvOu.js +1 -0
- package/dist/app/assets/{button-C0MQwO-R.js → button-r9cVFa0_.js} +3 -3
- package/dist/app/assets/coerce-Cxjr6MIU.js +1 -0
- package/dist/app/assets/{esm-Cyyp48ez.js → esm-CVXsiVX8.js} +1 -1
- package/dist/app/assets/explore-Da5fm3tF.js +33 -0
- package/dist/app/assets/{history-OZ9l4TiG.js → history-CN5yGwhZ.js} +1 -1
- package/dist/app/assets/index-B-eCk0oM.js +10 -0
- package/dist/app/assets/index-DIdY6bcj.css +2 -0
- package/dist/app/assets/list._listId-CNuYrPaQ.js +2 -0
- package/dist/app/assets/list._listId-DBB3l--n.js +1 -0
- package/dist/app/assets/{local-backups-BGSAd5Gu.js → local-backups-DLhiWDk4.js} +1 -1
- package/dist/app/assets/{local-sync-Dd3hEb9I.js → local-sync-DiQe8mhd.js} +1 -1
- package/dist/app/assets/registry-BKf8LDk2.js +4 -0
- package/dist/app/assets/routes-C0Cvc6Pi.js +2 -0
- package/dist/app/assets/routes-CY3o7jPt.js +3 -0
- package/dist/app/assets/{schemas-B0gCwksU.js → schemas-DVvZEnxp.js} +1 -1
- package/dist/app/assets/star-BPue5Y4t.js +7 -0
- package/dist/app/assets/{triangle-alert-Cgw2r-Xm.js → triangle-alert-BL8M65pd.js} +1 -1
- package/dist/app/index.html +10 -10
- package/dist/cli.js +475 -1
- package/package.json +1 -1
- package/dist/app/assets/badge-DWmLT6OI.js +0 -1
- package/dist/app/assets/coerce-D595RIhb.js +0 -1
- package/dist/app/assets/explore-Cizp4zKW.js +0 -33
- package/dist/app/assets/index-CTD9BpPN.js +0 -10
- package/dist/app/assets/index-DR-cW8P1.css +0 -2
- package/dist/app/assets/list._listId-C5TBL1sd.js +0 -2
- package/dist/app/assets/list._listId-Cq8rSqKM.js +0 -1
- package/dist/app/assets/registry-DZ4hp3Pl.js +0 -4
- package/dist/app/assets/routes-CqMycHWV.js +0 -2
- package/dist/app/assets/routes-DcRfe2TL.js +0 -3
- package/dist/app/assets/star-BoN7NVgv.js +0 -7
- /package/dist/app/assets/{sync-persistence-f8LlQoHO.js → sync-persistence-BJbWXISF.js} +0 -0
package/dist/cli.js
CHANGED
|
@@ -668,6 +668,429 @@ function withItems(graphql, lists) {
|
|
|
668
668
|
async function fetchViewerLists(graphql) {
|
|
669
669
|
return withItems(graphql, await fetchListNodes(graphql, VIEWER_LISTS_QUERY, null));
|
|
670
670
|
}
|
|
671
|
+
var README_LEAD_CHARS = 4e3;
|
|
672
|
+
var PER_PAGE_LIMIT = 30;
|
|
673
|
+
var RESULT_LIMIT = 8;
|
|
674
|
+
var MIN_STARS = 10;
|
|
675
|
+
var DAY_MS$1 = 864e5;
|
|
676
|
+
var MIN_SCORE = 2.5;
|
|
677
|
+
var LIST_NAME = /awesome|dotfiles|^\.?config|examples|templates|cookbook|tips|resources|curated|^free-for/i;
|
|
678
|
+
var LIST_DESCRIPTION = /curated list|list of|collection of|my (dotfiles|config|setup)/i;
|
|
679
|
+
var BROAD_TOPICS = new Set([
|
|
680
|
+
"javascript",
|
|
681
|
+
"typescript",
|
|
682
|
+
"python",
|
|
683
|
+
"python3",
|
|
684
|
+
"go",
|
|
685
|
+
"golang",
|
|
686
|
+
"rust",
|
|
687
|
+
"java",
|
|
688
|
+
"kotlin",
|
|
689
|
+
"swift",
|
|
690
|
+
"c",
|
|
691
|
+
"cpp",
|
|
692
|
+
"c-plus-plus",
|
|
693
|
+
"csharp",
|
|
694
|
+
"dotnet",
|
|
695
|
+
"php",
|
|
696
|
+
"ruby",
|
|
697
|
+
"scala",
|
|
698
|
+
"elixir",
|
|
699
|
+
"haskell",
|
|
700
|
+
"lua",
|
|
701
|
+
"shell",
|
|
702
|
+
"bash",
|
|
703
|
+
"zsh",
|
|
704
|
+
"nodejs",
|
|
705
|
+
"node",
|
|
706
|
+
"node-js",
|
|
707
|
+
"deno",
|
|
708
|
+
"react",
|
|
709
|
+
"reactjs",
|
|
710
|
+
"vue",
|
|
711
|
+
"vuejs",
|
|
712
|
+
"svelte",
|
|
713
|
+
"sveltekit",
|
|
714
|
+
"nextjs",
|
|
715
|
+
"angular",
|
|
716
|
+
"flutter",
|
|
717
|
+
"dart",
|
|
718
|
+
"html",
|
|
719
|
+
"css",
|
|
720
|
+
"tailwindcss",
|
|
721
|
+
"cli",
|
|
722
|
+
"terminal",
|
|
723
|
+
"command-line",
|
|
724
|
+
"command-line-tool",
|
|
725
|
+
"tool",
|
|
726
|
+
"tools",
|
|
727
|
+
"library",
|
|
728
|
+
"framework",
|
|
729
|
+
"api",
|
|
730
|
+
"rest-api",
|
|
731
|
+
"app",
|
|
732
|
+
"application",
|
|
733
|
+
"core",
|
|
734
|
+
"main",
|
|
735
|
+
"web",
|
|
736
|
+
"webapp",
|
|
737
|
+
"website",
|
|
738
|
+
"server",
|
|
739
|
+
"client",
|
|
740
|
+
"linux",
|
|
741
|
+
"macos",
|
|
742
|
+
"windows",
|
|
743
|
+
"android",
|
|
744
|
+
"ios",
|
|
745
|
+
"mobile",
|
|
746
|
+
"mobile-app",
|
|
747
|
+
"desktop",
|
|
748
|
+
"cross-platform",
|
|
749
|
+
"docker",
|
|
750
|
+
"kubernetes",
|
|
751
|
+
"self-hosted",
|
|
752
|
+
"selfhosted",
|
|
753
|
+
"open-source",
|
|
754
|
+
"opensource",
|
|
755
|
+
"free",
|
|
756
|
+
"hacktoberfest",
|
|
757
|
+
"awesome",
|
|
758
|
+
"awesome-list",
|
|
759
|
+
"list",
|
|
760
|
+
"github",
|
|
761
|
+
"git",
|
|
762
|
+
"productivity",
|
|
763
|
+
"utility",
|
|
764
|
+
"ai",
|
|
765
|
+
"llm",
|
|
766
|
+
"machine-learning",
|
|
767
|
+
"deep-learning",
|
|
768
|
+
"artificial-intelligence",
|
|
769
|
+
"chatgpt",
|
|
770
|
+
"openai",
|
|
771
|
+
"browser",
|
|
772
|
+
"database",
|
|
773
|
+
"backend",
|
|
774
|
+
"frontend",
|
|
775
|
+
"fullstack",
|
|
776
|
+
"devops",
|
|
777
|
+
"security",
|
|
778
|
+
"automation",
|
|
779
|
+
"javascript-library",
|
|
780
|
+
"typescript-library",
|
|
781
|
+
"python-library",
|
|
782
|
+
"npm",
|
|
783
|
+
"pypi",
|
|
784
|
+
"crate",
|
|
785
|
+
"plugin"
|
|
786
|
+
]);
|
|
787
|
+
var STOPWORDS = new Set([
|
|
788
|
+
"a",
|
|
789
|
+
"an",
|
|
790
|
+
"the",
|
|
791
|
+
"and",
|
|
792
|
+
"or",
|
|
793
|
+
"for",
|
|
794
|
+
"of",
|
|
795
|
+
"to",
|
|
796
|
+
"in",
|
|
797
|
+
"on",
|
|
798
|
+
"with",
|
|
799
|
+
"by",
|
|
800
|
+
"from",
|
|
801
|
+
"as",
|
|
802
|
+
"at",
|
|
803
|
+
"is",
|
|
804
|
+
"are",
|
|
805
|
+
"be",
|
|
806
|
+
"it",
|
|
807
|
+
"its",
|
|
808
|
+
"that",
|
|
809
|
+
"this",
|
|
810
|
+
"your",
|
|
811
|
+
"you",
|
|
812
|
+
"into",
|
|
813
|
+
"via",
|
|
814
|
+
"using",
|
|
815
|
+
"use",
|
|
816
|
+
"based",
|
|
817
|
+
"written",
|
|
818
|
+
"simple",
|
|
819
|
+
"fast",
|
|
820
|
+
"blazing",
|
|
821
|
+
"modern",
|
|
822
|
+
"lightweight",
|
|
823
|
+
"tiny",
|
|
824
|
+
"small",
|
|
825
|
+
"powerful",
|
|
826
|
+
"easy",
|
|
827
|
+
"minimal",
|
|
828
|
+
"beautiful",
|
|
829
|
+
"highly",
|
|
830
|
+
"customizable",
|
|
831
|
+
"open",
|
|
832
|
+
"source",
|
|
833
|
+
"free",
|
|
834
|
+
"high",
|
|
835
|
+
"performance",
|
|
836
|
+
"best",
|
|
837
|
+
"new",
|
|
838
|
+
"awesome",
|
|
839
|
+
"official",
|
|
840
|
+
"library",
|
|
841
|
+
"tool",
|
|
842
|
+
"tools",
|
|
843
|
+
"app",
|
|
844
|
+
"project",
|
|
845
|
+
"framework",
|
|
846
|
+
"implementation",
|
|
847
|
+
"collection",
|
|
848
|
+
"solution",
|
|
849
|
+
"way",
|
|
850
|
+
"just",
|
|
851
|
+
"like",
|
|
852
|
+
"clone",
|
|
853
|
+
"alternative",
|
|
854
|
+
"replacement",
|
|
855
|
+
"supercharge",
|
|
856
|
+
"even",
|
|
857
|
+
"works",
|
|
858
|
+
"entire",
|
|
859
|
+
"made",
|
|
860
|
+
"command-line",
|
|
861
|
+
"command",
|
|
862
|
+
"line",
|
|
863
|
+
"cli",
|
|
864
|
+
"user-friendly",
|
|
865
|
+
"cross-platform",
|
|
866
|
+
"fancy",
|
|
867
|
+
"system",
|
|
868
|
+
"platform",
|
|
869
|
+
"code",
|
|
870
|
+
"elements",
|
|
871
|
+
"linux",
|
|
872
|
+
"macos",
|
|
873
|
+
"windows",
|
|
874
|
+
"can",
|
|
875
|
+
"will",
|
|
876
|
+
"also",
|
|
877
|
+
"all",
|
|
878
|
+
"any",
|
|
879
|
+
"get",
|
|
880
|
+
"make",
|
|
881
|
+
"run",
|
|
882
|
+
"running",
|
|
883
|
+
"see",
|
|
884
|
+
"more",
|
|
885
|
+
"other",
|
|
886
|
+
"one",
|
|
887
|
+
"software",
|
|
888
|
+
"support",
|
|
889
|
+
"supports",
|
|
890
|
+
"feature",
|
|
891
|
+
"features",
|
|
892
|
+
"install",
|
|
893
|
+
"installation",
|
|
894
|
+
"using",
|
|
895
|
+
"about",
|
|
896
|
+
"how",
|
|
897
|
+
"what",
|
|
898
|
+
"which",
|
|
899
|
+
"when",
|
|
900
|
+
"there",
|
|
901
|
+
"here",
|
|
902
|
+
"they",
|
|
903
|
+
"them",
|
|
904
|
+
"their",
|
|
905
|
+
"our",
|
|
906
|
+
"we",
|
|
907
|
+
"has",
|
|
908
|
+
"have",
|
|
909
|
+
"not",
|
|
910
|
+
"but",
|
|
911
|
+
"if",
|
|
912
|
+
"so",
|
|
913
|
+
"up",
|
|
914
|
+
"out",
|
|
915
|
+
"only",
|
|
916
|
+
"most",
|
|
917
|
+
"some",
|
|
918
|
+
"such",
|
|
919
|
+
"than",
|
|
920
|
+
"then",
|
|
921
|
+
"these",
|
|
922
|
+
"those",
|
|
923
|
+
"very",
|
|
924
|
+
"well",
|
|
925
|
+
"want",
|
|
926
|
+
"need",
|
|
927
|
+
"needs",
|
|
928
|
+
"please",
|
|
929
|
+
"note",
|
|
930
|
+
"help",
|
|
931
|
+
"docs",
|
|
932
|
+
"documentation",
|
|
933
|
+
"license",
|
|
934
|
+
"contributing",
|
|
935
|
+
"contributors",
|
|
936
|
+
"release",
|
|
937
|
+
"releases",
|
|
938
|
+
"version",
|
|
939
|
+
"download",
|
|
940
|
+
"build",
|
|
941
|
+
"github",
|
|
942
|
+
"com",
|
|
943
|
+
"www",
|
|
944
|
+
"readme"
|
|
945
|
+
]);
|
|
946
|
+
function parseTopics(topics) {
|
|
947
|
+
return topics?.split(",").map((t) => t.trim()).filter(Boolean) ?? [];
|
|
948
|
+
}
|
|
949
|
+
function ownNames(source) {
|
|
950
|
+
const [owner, name] = source.full_name.toLowerCase().split("/");
|
|
951
|
+
const project = BROAD_TOPICS.has(name) ? owner : name;
|
|
952
|
+
const joined = project.replace(/[-_.]/g, "");
|
|
953
|
+
return joined === project ? [project] : [project, joined];
|
|
954
|
+
}
|
|
955
|
+
function mentionsOwnName(text, names) {
|
|
956
|
+
const lower = text.toLowerCase();
|
|
957
|
+
return names.some((n) => new RegExp(`(^|[^a-z0-9])${n.replace(/[.]/g, "\\.")}([^a-z0-9]|$)`).test(lower));
|
|
958
|
+
}
|
|
959
|
+
function hasWord(words, part) {
|
|
960
|
+
return words.findIndex((w) => w === part || w === `${part}s` || w === part.replace(/s$/, "") || w.split("-").includes(part));
|
|
961
|
+
}
|
|
962
|
+
function specificTopics(source) {
|
|
963
|
+
const language = source.language?.toLowerCase();
|
|
964
|
+
const names = ownNames(source);
|
|
965
|
+
const words = descriptionWords(source.description);
|
|
966
|
+
return parseTopics(source.topics).filter((t) => !BROAD_TOPICS.has(t) && t !== language && !mentionsOwnName(t, names)).map((topic) => {
|
|
967
|
+
const parts = topic.split("-").filter((part) => !STOPWORDS.has(part));
|
|
968
|
+
const positions = parts.map((part) => hasWord(words, part)).filter((i) => i >= 0);
|
|
969
|
+
const fraction = positions.length / parts.length;
|
|
970
|
+
const first = positions.length > 0 ? Math.min(...positions) : words.length;
|
|
971
|
+
return {
|
|
972
|
+
topic,
|
|
973
|
+
fraction,
|
|
974
|
+
relevance: 3 * fraction + .3 * Math.min(parts.length, 3) - .05 * first
|
|
975
|
+
};
|
|
976
|
+
}).filter((t) => t.fraction > 0).sort((a, b) => b.relevance - a.relevance).map((t) => t.topic);
|
|
977
|
+
}
|
|
978
|
+
function readmeLead(markdown) {
|
|
979
|
+
return markdown.replace(/```[\s\S]*?```/g, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/<[^>]+>/g, " ").replace(/!\[[^\]]*\]\([^)]*\)/g, " ").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/https?:\/\/\S+/g, " ").replace(/`[^`]*`/g, " ").replace(/&[a-z]+;|&#\d+;/g, " ").slice(0, README_LEAD_CHARS);
|
|
980
|
+
}
|
|
981
|
+
function keyWords(source) {
|
|
982
|
+
const names = ownNames(source);
|
|
983
|
+
const language = source.language?.toLowerCase();
|
|
984
|
+
const described = descriptionWords(source.description).filter((w) => !mentionsOwnName(w, names) && w !== language);
|
|
985
|
+
const counts = /* @__PURE__ */ new Map();
|
|
986
|
+
for (const w of source.readme ? wordsOf(source.readme) : []) if (!mentionsOwnName(w, names) && w !== language) counts.set(w, (counts.get(w) ?? 0) + 1);
|
|
987
|
+
const fromReadme = [...counts.entries()].filter(([w, n]) => n >= 3 && !described.includes(w)).sort((a, b) => b[1] - a[1]).map(([w]) => w);
|
|
988
|
+
const rank = (w) => counts.get(w) ?? 0;
|
|
989
|
+
const hasReadme = counts.size > 0;
|
|
990
|
+
return [...described.map((w, i) => ({
|
|
991
|
+
w,
|
|
992
|
+
score: (hasReadme ? 2 * rank(w) + 4 : 100) - .1 * i
|
|
993
|
+
})), ...fromReadme.map((w, i) => ({
|
|
994
|
+
w,
|
|
995
|
+
score: rank(w) - .1 * i
|
|
996
|
+
}))].sort((a, b) => b.score - a.score).map(({ w }) => w);
|
|
997
|
+
}
|
|
998
|
+
function wordsOf(text) {
|
|
999
|
+
return text.toLowerCase().replace(/:[a-z0-9_+-]+:/g, " ").split(/[^a-z0-9+#.-]+/).map((w) => w.replace(/^[.-]+|[.-]+$/g, "")).filter((w) => w.length > 2 && /[a-z]/.test(w) && !STOPWORDS.has(w));
|
|
1000
|
+
}
|
|
1001
|
+
function descriptionWords(description) {
|
|
1002
|
+
if (!description) return [];
|
|
1003
|
+
const words = description.toLowerCase().replace(/:[a-z0-9_+-]+:/g, " ").replace(/\([^)]*\)/g, " ").split(/[^a-z0-9+#.-]+/).map((w) => w.replace(/^[.-]+|[.-]+$/g, "")).filter((w) => w.length > 1 && /[a-z]/.test(w) && !STOPWORDS.has(w));
|
|
1004
|
+
return [...new Set(words)];
|
|
1005
|
+
}
|
|
1006
|
+
function minStars(source) {
|
|
1007
|
+
return Math.round(Math.min(300, Math.max(MIN_STARS, source.stars / 200)));
|
|
1008
|
+
}
|
|
1009
|
+
function buildQueries(source) {
|
|
1010
|
+
const filters = `fork:false archived:false stars:>=${minStars(source)}`;
|
|
1011
|
+
const topics = specificTopics(source);
|
|
1012
|
+
const names = ownNames(source);
|
|
1013
|
+
const topicWords = new Set(topics.flatMap((t) => t.split("-")));
|
|
1014
|
+
const words = keyWords(source).map((w, i) => ({
|
|
1015
|
+
w,
|
|
1016
|
+
rank: (topicWords.has(w) ? 0 : 10) + i
|
|
1017
|
+
})).sort((a, b) => a.rank - b.rank).map(({ w }) => w).slice(0, 2);
|
|
1018
|
+
const queries = [];
|
|
1019
|
+
if (topics.length >= 1) queries.push({
|
|
1020
|
+
kind: "topic",
|
|
1021
|
+
q: `topic:${topics[0]} ${filters}`,
|
|
1022
|
+
sort: "stars"
|
|
1023
|
+
});
|
|
1024
|
+
if (words.length >= 2) queries.push({
|
|
1025
|
+
kind: "words",
|
|
1026
|
+
q: `${words.join(" ")} in:name,description,topics ${filters}`,
|
|
1027
|
+
sort: "best-match"
|
|
1028
|
+
});
|
|
1029
|
+
queries.push({
|
|
1030
|
+
kind: "readme",
|
|
1031
|
+
q: `${names[0]} in:readme ${words[0] ?? ""} ${filters}`.replace(/\s+/g, " "),
|
|
1032
|
+
sort: "stars"
|
|
1033
|
+
});
|
|
1034
|
+
return queries.slice(0, 3);
|
|
1035
|
+
}
|
|
1036
|
+
function overlap(a, b) {
|
|
1037
|
+
return a.filter((x) => b.has(x)).length;
|
|
1038
|
+
}
|
|
1039
|
+
function rankSimilar(source, results, now = Date.now()) {
|
|
1040
|
+
const self = source.full_name.toLowerCase();
|
|
1041
|
+
const sourceTopics = parseTopics(source.topics);
|
|
1042
|
+
const specific = new Set(specificTopics(source));
|
|
1043
|
+
const sourceWords = new Set(keyWords(source).slice(0, 12));
|
|
1044
|
+
const names = ownNames(source);
|
|
1045
|
+
const pool = /* @__PURE__ */ new Map();
|
|
1046
|
+
for (const { kind, items } of results) for (const item of items.slice(0, PER_PAGE_LIMIT)) {
|
|
1047
|
+
const key = item.full_name.toLowerCase();
|
|
1048
|
+
if (key === self || item.fork || item.archived) continue;
|
|
1049
|
+
const entry = pool.get(key) ?? {
|
|
1050
|
+
item,
|
|
1051
|
+
hits: 0,
|
|
1052
|
+
comparesItself: false
|
|
1053
|
+
};
|
|
1054
|
+
entry.hits++;
|
|
1055
|
+
if (kind === "readme") entry.comparesItself = true;
|
|
1056
|
+
pool.set(key, entry);
|
|
1057
|
+
}
|
|
1058
|
+
const maxStars = Math.max(1, ...[...pool.values()].map(({ item }) => item.stargazers_count));
|
|
1059
|
+
const related = [...pool.values()].map(({ item, hits, comparesItself }) => {
|
|
1060
|
+
const topics = item.topics ?? [];
|
|
1061
|
+
const topicSet = new Set(topics);
|
|
1062
|
+
const shared = sourceTopics.filter((t) => topicSet.has(t));
|
|
1063
|
+
const sharedSpecific = shared.filter((t) => specific.has(t));
|
|
1064
|
+
const words = [...descriptionWords(item.description), ...topics.flatMap((t) => t.split("-"))];
|
|
1065
|
+
const wordOverlap = sourceWords.size > 0 ? overlap(words, sourceWords) / Math.sqrt(sourceWords.size * Math.max(1, words.length)) : 0;
|
|
1066
|
+
const days = item.pushed_at ? (now - Date.parse(item.pushed_at)) / DAY_MS$1 : Infinity;
|
|
1067
|
+
const addOn = mentionsOwnName(`${item.full_name.split("/")[1]} ${item.description ?? ""}`, names);
|
|
1068
|
+
const roundup = LIST_NAME.test(item.full_name.split("/")[1]) || LIST_DESCRIPTION.test(item.description ?? "");
|
|
1069
|
+
const score = 2 * sharedSpecific.length + .5 * (shared.length - sharedSpecific.length) + 3 * wordOverlap + 1 * (hits - 1) + (comparesItself ? 1.5 : 0) + (source.language && item.language === source.language ? .5 : 0) + 2 * (Math.log10(item.stargazers_count + 1) / Math.log10(maxStars + 1)) - (days > 730 ? 1 : days > 365 ? .4 : 0);
|
|
1070
|
+
return {
|
|
1071
|
+
full_name: item.full_name,
|
|
1072
|
+
url: item.html_url,
|
|
1073
|
+
description: item.description,
|
|
1074
|
+
language: item.language,
|
|
1075
|
+
stars: item.stargazers_count,
|
|
1076
|
+
pushed_at: item.pushed_at,
|
|
1077
|
+
shared_topics: [...sharedSpecific, ...shared.filter((t) => !specific.has(t))],
|
|
1078
|
+
score: Math.round(score * 100) / 100,
|
|
1079
|
+
related: !addOn && !roundup && (shared.some((t) => !BROAD_TOPICS.has(t)) || wordOverlap > 0)
|
|
1080
|
+
};
|
|
1081
|
+
}).filter((r) => r.related).sort((a, b) => b.score - a.score);
|
|
1082
|
+
const cutoff = Math.max(MIN_SCORE, (related[0]?.score ?? 0) * .55);
|
|
1083
|
+
return related.filter((r) => r.score >= cutoff).slice(0, RESULT_LIMIT).map(({ related: _, ...repo }) => repo);
|
|
1084
|
+
}
|
|
1085
|
+
async function findSimilar(source, search, now = Date.now()) {
|
|
1086
|
+
const queries = buildQueries(source);
|
|
1087
|
+
const results = [];
|
|
1088
|
+
for (const { kind, q, sort } of queries) results.push({
|
|
1089
|
+
kind,
|
|
1090
|
+
items: await search(q, sort)
|
|
1091
|
+
});
|
|
1092
|
+
return rankSimilar(source, results, now);
|
|
1093
|
+
}
|
|
671
1094
|
//#endregion
|
|
672
1095
|
//#region server/github.ts
|
|
673
1096
|
var DEFAULT_API_URL = "https://api.github.com";
|
|
@@ -702,6 +1125,12 @@ function createGitHubClient(token, apiUrl = process.env.GITHUB_API_URL ?? DEFAUL
|
|
|
702
1125
|
if (json.errors?.length) throw new GitHubError(502, json.errors[0].message);
|
|
703
1126
|
return json.data;
|
|
704
1127
|
},
|
|
1128
|
+
get(path, accept = "application/vnd.github+json") {
|
|
1129
|
+
return request(`${apiUrl}${path}`, { headers: {
|
|
1130
|
+
...headers,
|
|
1131
|
+
Accept: accept
|
|
1132
|
+
} });
|
|
1133
|
+
},
|
|
705
1134
|
async rest(method, path) {
|
|
706
1135
|
const response = await request(`${apiUrl}${path}`, {
|
|
707
1136
|
method,
|
|
@@ -844,6 +1273,42 @@ async function addStar(client, repoNodeId) {
|
|
|
844
1273
|
async function unstar(client, owner, name) {
|
|
845
1274
|
await client.rest("DELETE", `/user/starred/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`);
|
|
846
1275
|
}
|
|
1276
|
+
async function tokenInfo(client) {
|
|
1277
|
+
const response = await client.get("/user");
|
|
1278
|
+
const json = await response.json().catch(() => ({}));
|
|
1279
|
+
if (!response.ok) throw new GitHubError(response.status, json.message ?? `GitHub returned ${response.status}.`);
|
|
1280
|
+
const header = response.headers.get("x-oauth-scopes");
|
|
1281
|
+
return {
|
|
1282
|
+
login: json.login ?? "",
|
|
1283
|
+
scopes: header === null ? null : header.split(",").map((s) => s.trim()).filter(Boolean)
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
var SEARCH_PAGE_SIZE = 30;
|
|
1287
|
+
async function searchRepositories(client, q, sort) {
|
|
1288
|
+
const params = new URLSearchParams({
|
|
1289
|
+
q,
|
|
1290
|
+
per_page: String(SEARCH_PAGE_SIZE)
|
|
1291
|
+
});
|
|
1292
|
+
if (sort === "stars") params.set("sort", "stars");
|
|
1293
|
+
const response = await client.get(`/search/repositories?${params}`);
|
|
1294
|
+
const json = await response.json().catch(() => ({}));
|
|
1295
|
+
if (response.status === 403 || response.status === 429) throw new GitHubError(429, "GitHub allows 30 searches a minute. Try again in a minute.");
|
|
1296
|
+
if (!response.ok) throw new GitHubError(response.status, json.message ?? `GitHub returned ${response.status}.`);
|
|
1297
|
+
return json.items ?? [];
|
|
1298
|
+
}
|
|
1299
|
+
/** The README's opening prose, or null when the repo has none. */
|
|
1300
|
+
async function readmeOpening(client, fullName) {
|
|
1301
|
+
const response = await client.get(`/repos/${fullName}/readme`, "application/vnd.github.raw");
|
|
1302
|
+
if (!response.ok) return null;
|
|
1303
|
+
return readmeLead(await response.text());
|
|
1304
|
+
}
|
|
1305
|
+
async function similarTo(client, source) {
|
|
1306
|
+
const readme = await readmeOpening(client, source.full_name);
|
|
1307
|
+
return findSimilar({
|
|
1308
|
+
...source,
|
|
1309
|
+
readme
|
|
1310
|
+
}, (q, sort) => searchRepositories(client, q, sort));
|
|
1311
|
+
}
|
|
847
1312
|
//#endregion
|
|
848
1313
|
//#region src/lib/local-api.ts
|
|
849
1314
|
var LOCAL_APP_SIGNATURE = "github-stars-organizer";
|
|
@@ -6917,6 +7382,15 @@ function createLocalServer({ github, appDir, port, store, login }) {
|
|
|
6917
7382
|
await unstar(github, owner, name);
|
|
6918
7383
|
return c.json({});
|
|
6919
7384
|
});
|
|
7385
|
+
api.get("/token", async (c) => c.json({
|
|
7386
|
+
connected: true,
|
|
7387
|
+
...await tokenInfo(github)
|
|
7388
|
+
}));
|
|
7389
|
+
api.post("/similar", async (c) => {
|
|
7390
|
+
const { repo } = await c.req.json();
|
|
7391
|
+
if (!repo?.full_name) return c.json({ error: "Which repo?" }, 400);
|
|
7392
|
+
return c.json({ similar: await similarTo(github, repo) });
|
|
7393
|
+
});
|
|
6920
7394
|
api.get("/backups", async (c) => c.json({
|
|
6921
7395
|
directory: store.directory,
|
|
6922
7396
|
backups: await store.list()
|
|
@@ -6992,7 +7466,7 @@ async function launch({ github, login, home, appDir, port }) {
|
|
|
6992
7466
|
//#region cli/src/cli.ts
|
|
6993
7467
|
var DEFAULT_PORT = 5185;
|
|
6994
7468
|
var EXIT_FAILURE = 1;
|
|
6995
|
-
var NOTICE = `Stars Organizer v1.
|
|
7469
|
+
var NOTICE = `Stars Organizer v1.3.0 Copyright (C) 2026 Armanc Keser
|
|
6996
7470
|
Free software under the GNU AGPL v3 or later, with ABSOLUTELY NO WARRANTY.
|
|
6997
7471
|
Source, license and bundled licenses: https://github.com/armanckeser/github-stars-organizer
|
|
6998
7472
|
Not affiliated with or endorsed by GitHub.`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "github-stars-organizer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Search, tag, rate and sort your GitHub stars into Lists, using your gh login. Runs locally; nothing to host.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"author": "Armanc Keser",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{R as e,a as t,m as n,n as r,r as i}from"./button-C0MQwO-R.js";var a=e(`external-link`,[[`path`,{d:`M15 3h6v6`,key:`1q9fwt`}],[`path`,{d:`M10 14 21 3`,key:`gplh6r`}],[`path`,{d:`M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6`,key:`a6xqqp`}]]);function o(e){return t(e.defaultTagName??`div`,e,e)}var s=i(`group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!`,{variants:{variant:{default:`bg-primary text-primary-foreground [a]:hover:bg-primary/80`,secondary:`bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80`,destructive:`bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20`,outline:`border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground`,ghost:`hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50`,link:`text-primary underline-offset-4 hover:underline`}},defaultVariants:{variant:`default`}});function c({className:e,variant:t=`default`,render:i,...a}){return o({defaultTagName:`span`,props:n({className:r(s({variant:t}),e)},a),render:i,state:{slot:`badge`,variant:t}})}export{a as n,c as t};
|