@grove-dev/astro 0.1.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.
Files changed (109) hide show
  1. package/README.md +81 -0
  2. package/dist/index.d.ts +26 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +26 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +58 -0
  7. package/src/components/ActiveFilterChips.astro +20 -0
  8. package/src/components/CategoryGrid.astro +27 -0
  9. package/src/components/DirectoryFilters.astro +100 -0
  10. package/src/components/DirectoryHero.astro +32 -0
  11. package/src/components/HealthBadge.astro +12 -0
  12. package/src/components/ItemCard.astro +41 -0
  13. package/src/components/LensTabs.astro +26 -0
  14. package/src/components/MethodologyPanel.astro +20 -0
  15. package/src/components/Pagination.astro +24 -0
  16. package/src/components/ProjectDetail.astro +166 -0
  17. package/src/components/ScoreBars.astro +32 -0
  18. package/src/components/SubmitDraft.astro +137 -0
  19. package/src/index.ts +17 -0
  20. package/src/layouts/BaseLayout.astro +32 -0
  21. package/src/styles.css +675 -0
  22. package/templates/default/astro.config.mjs +23 -0
  23. package/templates/default/data/generated/apps.json +1 -0
  24. package/templates/default/data/generated/repo-stats.json +1 -0
  25. package/templates/default/package.json +35 -0
  26. package/templates/default/public/icons/platforms/android.svg +1 -0
  27. package/templates/default/public/icons/platforms/apple-dark.svg +1 -0
  28. package/templates/default/public/icons/platforms/apple-light.svg +1 -0
  29. package/templates/default/public/icons/platforms/chrome.svg +1 -0
  30. package/templates/default/public/icons/platforms/chromeos.svg +5 -0
  31. package/templates/default/public/icons/platforms/desktop.svg +4 -0
  32. package/templates/default/public/icons/platforms/embedded.svg +5 -0
  33. package/templates/default/public/icons/platforms/ios.svg +1 -0
  34. package/templates/default/public/icons/platforms/linux.svg +1 -0
  35. package/templates/default/public/icons/platforms/macos.svg +1 -0
  36. package/templates/default/public/icons/platforms/ubuntu.svg +1 -0
  37. package/templates/default/public/icons/platforms/web.svg +5 -0
  38. package/templates/default/public/icons/platforms/windows.svg +6 -0
  39. package/templates/default/public/icons/stacks/android.svg +1 -0
  40. package/templates/default/public/icons/stacks/apple-dark.svg +1 -0
  41. package/templates/default/public/icons/stacks/apple-light.svg +1 -0
  42. package/templates/default/public/icons/stacks/capacitor.svg +14 -0
  43. package/templates/default/public/icons/stacks/dart.svg +1 -0
  44. package/templates/default/public/icons/stacks/firebase.svg +1 -0
  45. package/templates/default/public/icons/stacks/flutter.svg +1 -0
  46. package/templates/default/public/icons/stacks/graphql.svg +1 -0
  47. package/templates/default/public/icons/stacks/ionic.svg +4 -0
  48. package/templates/default/public/icons/stacks/java.svg +1 -0
  49. package/templates/default/public/icons/stacks/javascript.svg +1 -0
  50. package/templates/default/public/icons/stacks/kotlin.svg +1 -0
  51. package/templates/default/public/icons/stacks/mongodb.svg +1 -0
  52. package/templates/default/public/icons/stacks/nodejs.svg +1 -0
  53. package/templates/default/public/icons/stacks/python.svg +1 -0
  54. package/templates/default/public/icons/stacks/react-native.svg +1 -0
  55. package/templates/default/public/icons/stacks/react.svg +1 -0
  56. package/templates/default/public/icons/stacks/rust.svg +1 -0
  57. package/templates/default/public/icons/stacks/solidity.svg +1 -0
  58. package/templates/default/public/icons/stacks/swift.svg +1 -0
  59. package/templates/default/public/icons/stacks/tensorflow.svg +1 -0
  60. package/templates/default/public/icons/stacks/typescript.svg +1 -0
  61. package/templates/default/public/og-image.svg +48 -0
  62. package/templates/default/public/robots.txt +53 -0
  63. package/templates/default/src/components/AppCard.astro +248 -0
  64. package/templates/default/src/components/AppSection.astro +58 -0
  65. package/templates/default/src/components/AppsIndexRow.astro +230 -0
  66. package/templates/default/src/components/AppsPagination.astro +147 -0
  67. package/templates/default/src/components/CategoryGrid.astro +80 -0
  68. package/templates/default/src/components/ContributorsGrid.astro +105 -0
  69. package/templates/default/src/components/DecisionRow.astro +219 -0
  70. package/templates/default/src/components/ExploreByCategory.astro +74 -0
  71. package/templates/default/src/components/ExploreByStack.astro +102 -0
  72. package/templates/default/src/components/Hero.astro +176 -0
  73. package/templates/default/src/components/MinimalAbout.astro +57 -0
  74. package/templates/default/src/components/OriginalCollection.astro +144 -0
  75. package/templates/default/src/components/RefinePanel.astro +272 -0
  76. package/templates/default/src/components/ScoreBars.astro +72 -0
  77. package/templates/default/src/components/SmartLensTabs.astro +65 -0
  78. package/templates/default/src/components/StackGrid.astro +95 -0
  79. package/templates/default/src/components/WhyThisExists.astro +81 -0
  80. package/templates/default/src/components/icons/Icon.astro +148 -0
  81. package/templates/default/src/components/layout/BaseLayout.astro +94 -0
  82. package/templates/default/src/components/layout/Container.astro +18 -0
  83. package/templates/default/src/components/layout/Footer.astro +145 -0
  84. package/templates/default/src/components/layout/Header.astro +144 -0
  85. package/templates/default/src/components/layout/SectionHeader.astro +42 -0
  86. package/templates/default/src/components/layout/Seo.astro +120 -0
  87. package/templates/default/src/components/layout/ThemeToggle.astro +97 -0
  88. package/templates/default/src/data/apps.ts +149 -0
  89. package/templates/default/src/data/categories.ts +27 -0
  90. package/templates/default/src/data/config.ts +28 -0
  91. package/templates/default/src/data/contributors.ts +96 -0
  92. package/templates/default/src/data/stacks.ts +47 -0
  93. package/templates/default/src/data/stats.ts +65 -0
  94. package/templates/default/src/data/types.ts +248 -0
  95. package/templates/default/src/lib/apps-search.ts +228 -0
  96. package/templates/default/src/lib/format.ts +36 -0
  97. package/templates/default/src/lib/lenses.ts +98 -0
  98. package/templates/default/src/lib/repo.ts +12 -0
  99. package/templates/default/src/lib/scores.ts +51 -0
  100. package/templates/default/src/lib/taxonomy-counts.ts +31 -0
  101. package/templates/default/src/pages/about.astro +433 -0
  102. package/templates/default/src/pages/apps/[slug].astro +709 -0
  103. package/templates/default/src/pages/apps/index.astro +669 -0
  104. package/templates/default/src/pages/contributors.astro +142 -0
  105. package/templates/default/src/pages/index.astro +66 -0
  106. package/templates/default/src/pages/sitemap.xml.ts +77 -0
  107. package/templates/default/src/pages/submit.astro +416 -0
  108. package/templates/default/src/styles/global.css +84 -0
  109. package/templates/default/tailwind.config.mjs +130 -0
@@ -0,0 +1,142 @@
1
+ ---
2
+ import { siteConfig } from "../data/config";
3
+
4
+ // SPDX-License-Identifier: MIT
5
+
6
+ /**
7
+ * /contributors — full shout-out page. Every contributor is listed
8
+ * with avatar, name (if known), and a link to their GitHub
9
+ * profile. The page is what the homepage "View all" link points
10
+ * to, so visitors can see who has actually shaped the directory
11
+ * over time. Order matches the synced list (most active first)
12
+ * but the page intentionally avoids leaderboard framing — no
13
+ * commit counts, no rank numbering.
14
+ */
15
+ import BaseLayout from "../components/layout/BaseLayout.astro";
16
+ import Container from "../components/layout/Container.astro";
17
+ import { contributors as allContributors } from "../data/contributors";
18
+
19
+ // `contributors.ts` is already sorted by contributions desc by the
20
+ // sync script; the spread + sort is kept only as a defensive copy
21
+ // in case the source order ever changes upstream.
22
+ const contributors = [...allContributors].sort(
23
+ (a, b) => (b.contributions ?? 0) - (a.contributions ?? 0),
24
+ );
25
+ const total = contributors.length;
26
+ const withNames = contributors.filter((c) => c.name).length;
27
+
28
+ const title = "Contributors — ${siteConfig.name}";
29
+ const description = `Every person who has shaped the ${siteConfig.name} directory. ${total} contributors.`;
30
+ ---
31
+
32
+ <BaseLayout title={title} description={description}>
33
+ <Container>
34
+ <div class="py-14 sm:py-20">
35
+ <span
36
+ class="inline-flex items-center gap-1.5 text-2xs font-medium text-ink-500 dark:text-ink-400"
37
+ >
38
+ <span
39
+ aria-hidden="true"
40
+ class="inline-block h-1.5 w-1.5 rounded-full bg-emerald-500"
41
+ ></span>
42
+ Community · {total} contributors
43
+ </span>
44
+
45
+ <h1
46
+ class="mt-3 text-[2.25rem] sm:text-[2.75rem] md:text-[3rem] font-semibold tracking-[-0.02em] leading-[1.05] text-ink-900 dark:text-ink-100 m-0"
47
+ >
48
+ Thanks to every contributor.
49
+ </h1>
50
+
51
+ <p
52
+ class="mt-4 text-[0.9375rem] sm:text-[1rem] leading-relaxed text-ink-500 dark:text-ink-400 m-0 max-w-[64ch]"
53
+ >
54
+ This directory exists because {total} people keep discovering,
55
+ submitting, reviewing, curating, and maintaining open-source app
56
+ references. Below is the full list.
57
+ {withNames > 0 && (
58
+ <span class="block mt-1 text-ink-400 dark:text-ink-500">
59
+ {withNames} of {total} have a public display name on GitHub; the rest are known by their username.
60
+ </span>
61
+ )}
62
+ </p>
63
+
64
+ <ul
65
+ class="mt-8 grid list-none grid-cols-1 gap-x-6 gap-y-3 p-0 sm:grid-cols-2 lg:grid-cols-3"
66
+ >
67
+ {
68
+ contributors.map((c) => (
69
+ <li
70
+ class="flex items-center gap-3 rounded-lg border border-ink-200 bg-white p-3 transition-colors hover:border-ink-300 dark:border-ink-800 dark:bg-ink-950 dark:hover:border-ink-700"
71
+ >
72
+ <img
73
+ src={c.avatarUrl}
74
+ alt={`${c.name ?? c.username} avatar`}
75
+ loading="lazy"
76
+ width="40"
77
+ height="40"
78
+ class="h-10 w-10 shrink-0 rounded-full border border-ink-200 bg-white object-cover dark:border-ink-800 dark:bg-ink-900"
79
+ />
80
+ <div class="min-w-0 flex-1">
81
+ <a
82
+ href={c.profileUrl}
83
+ target="_blank"
84
+ rel="noopener noreferrer"
85
+ class="block truncate text-sm font-medium text-ink-900 hover:underline dark:text-ink-100"
86
+ >
87
+ {c.name ?? c.username}
88
+ </a>
89
+ {c.name && (
90
+ <div class="mt-0.5 truncate text-2xs text-ink-500 dark:text-ink-400">
91
+ @{c.username}
92
+ </div>
93
+ )}
94
+ </div>
95
+ <a
96
+ href={c.profileUrl}
97
+ target="_blank"
98
+ rel="noopener noreferrer"
99
+ aria-label={`${c.name ?? c.username} on GitHub`}
100
+ class="shrink-0 text-ink-400 hover:text-ink-700 dark:text-ink-500 dark:hover:text-ink-200"
101
+ >
102
+ <svg
103
+ viewBox="0 0 16 16"
104
+ width="14"
105
+ height="14"
106
+ aria-hidden="true"
107
+ fill="currentColor"
108
+ >
109
+ <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38v-1.33c-2.22.48-2.69-1.07-2.69-1.07-.36-.92-.89-1.17-.89-1.17-.73-.5.05-.49.05-.49.8.06 1.23.83 1.23.83.72 1.22 1.87.87 2.33.67.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48v2.19c0 .21.15.46.55.38A8 8 0 0 0 8 0Z" />
110
+ </svg>
111
+ </a>
112
+ </li>
113
+ ))
114
+ }
115
+ </ul>
116
+
117
+ <div
118
+ class="mt-10 flex flex-wrap items-center gap-x-4 gap-y-2 rounded-lg border border-ink-200 bg-ink-50 p-4 text-sm text-ink-500 dark:border-ink-800 dark:bg-ink-900/40 dark:text-ink-400"
119
+ >
120
+ <span>
121
+ Want to be on this list? Submit an app, fix a typo, or
122
+ open a PR — every commit counts.
123
+ </span>
124
+ <a
125
+ href="/submit"
126
+ class="font-medium text-ink-700 hover:text-ink-900 dark:text-ink-300 dark:hover:text-ink-100"
127
+ >
128
+ Submit an app →
129
+ </a>
130
+ <span aria-hidden="true">·</span>
131
+ <a
132
+ href="https://github.com/tortuvshin/open-apps/graphs/contributors"
133
+ target="_blank"
134
+ rel="noopener noreferrer"
135
+ class="font-medium text-ink-700 hover:text-ink-900 dark:text-ink-300 dark:hover:text-ink-100"
136
+ >
137
+ View on GitHub →
138
+ </a>
139
+ </div>
140
+ </div>
141
+ </Container>
142
+ </BaseLayout>
@@ -0,0 +1,66 @@
1
+ ---
2
+ import { siteConfig } from "../data/config";
3
+ import BaseLayout from "../components/layout/BaseLayout.astro";
4
+ import Hero from "../components/Hero.astro";
5
+ import WhyThisExists from "../components/WhyThisExists.astro";
6
+ import AppSection from "../components/AppSection.astro";
7
+ import StackGrid from "../components/StackGrid.astro";
8
+ import CategoryGrid from "../components/CategoryGrid.astro";
9
+ import ContributorsGrid from "../components/ContributorsGrid.astro";
10
+ import OriginalCollection from "../components/OriginalCollection.astro";
11
+
12
+ import { newApps, hotApps, matureApps } from "../data/apps";
13
+ import { stacks } from "../data/stacks";
14
+ import { categories } from "../data/categories";
15
+ import { contributors } from "../data/contributors";
16
+ import { stats } from "../data/stats";
17
+
18
+ const hotList = hotApps().slice(0, 6);
19
+ const newList = newApps().slice(0, 6);
20
+ const matureList = matureApps().slice(0, 6);
21
+
22
+ const title = `${siteConfig.name} — A directory of real open-source applications`;
23
+ const description =
24
+ "A searchable directory of real open-source applications — organized by stack, category, platform, license, activity, and maturity.";
25
+ ---
26
+
27
+ <BaseLayout title={title} description={description}>
28
+ <Hero />
29
+
30
+ <WhyThisExists />
31
+
32
+ <AppSection
33
+ id="hot"
34
+ title="Hot open-source apps"
35
+ description="Projects gaining stars, activity, or attention recently."
36
+ apps={hotList}
37
+ viewAllHref="/apps?label=hot"
38
+ viewAllLabel="View hot apps"
39
+ />
40
+
41
+ <AppSection
42
+ id="new"
43
+ title="New open-source apps"
44
+ description="Recently added apps from the community."
45
+ apps={newList}
46
+ viewAllHref="/apps?label=new"
47
+ viewAllLabel="View all new"
48
+ />
49
+
50
+ <AppSection
51
+ id="mature"
52
+ title="Mature open-source apps"
53
+ description="Established apps with strong history, active maintainers, and real-world usage."
54
+ apps={matureList}
55
+ viewAllHref="/apps?label=mature"
56
+ viewAllLabel="View mature apps"
57
+ />
58
+
59
+ <StackGrid stacks={stacks} />
60
+
61
+ <CategoryGrid categories={categories} />
62
+
63
+ <ContributorsGrid contributors={contributors} />
64
+
65
+ <OriginalCollection stats={stats} />
66
+ </BaseLayout>
@@ -0,0 +1,77 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // `/sitemap.xml` — single-file sitemap, served as `application/xml`.
4
+ //
5
+ // Replaces `@astrojs/sitemap` so the public URL is exactly
6
+ // `/sitemap.xml` (the integration's default output is
7
+ // `/sitemap-index.xml`, which doesn't match what `robots.txt` and
8
+ // most crawler configs expect). For 79 apps + 4 static pages this
9
+ // is well under any per-sitemap limit, so a single file is fine.
10
+ //
11
+ // Excludes `/submit` (noindex form wrapper) and any other pages
12
+ // that opt out by setting `noindex` in their SEO props.
13
+
14
+ import type { APIRoute } from "astro";
15
+ import generatedJson from "../../data/generated/apps.json";
16
+
17
+ type GeneratedApp = {
18
+ slug: string;
19
+ name: string;
20
+ lastCommitAt?: string;
21
+ activity?: { lastCommitAt?: string };
22
+ };
23
+
24
+ const apps = (generatedJson as { apps?: GeneratedApp[] }).apps ?? [];
25
+
26
+ const STATIC_PAGES: { path: string; priority: string; changefreq: string }[] = [
27
+ { path: "/", priority: "1.0", changefreq: "weekly" },
28
+ { path: "/about", priority: "0.8", changefreq: "monthly" },
29
+ { path: "/apps", priority: "0.9", changefreq: "daily" },
30
+ ];
31
+
32
+ function isoDate(iso?: string): string | null {
33
+ if (!iso) return null;
34
+ const d = new Date(iso);
35
+ if (isNaN(d.getTime())) return null;
36
+ return d.toISOString().slice(0, 10);
37
+ }
38
+
39
+ function urlEntry(loc: string, lastmod?: string, priority = "0.7", changefreq = "weekly"): string {
40
+ const lm = isoDate(lastmod);
41
+ return [
42
+ " <url>",
43
+ ` <loc>${loc}</loc>`,
44
+ lm ? ` <lastmod>${lm}</lastmod>` : null,
45
+ ` <changefreq>${changefreq}</changefreq>`,
46
+ ` <priority>${priority}</priority>`,
47
+ " </url>",
48
+ ]
49
+ .filter(Boolean)
50
+ .join("\n");
51
+ }
52
+
53
+ export const GET: APIRoute = ({ site }) => {
54
+ const base = (site?.toString() ?? "https://open-apps.dev.mn").replace(/\/$/, "");
55
+ const today = new Date().toISOString().slice(0, 10);
56
+
57
+ const lines: string[] = [];
58
+ lines.push('<?xml version="1.0" encoding="UTF-8"?>');
59
+ lines.push('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">');
60
+ for (const p of STATIC_PAGES) {
61
+ lines.push(urlEntry(`${base}${p.path}`, today, p.priority, p.changefreq));
62
+ }
63
+ for (const app of apps) {
64
+ const lastmod = app.lastCommitAt ?? app.activity?.lastCommitAt ?? today;
65
+ lines.push(urlEntry(`${base}/apps/${app.slug}`, lastmod));
66
+ }
67
+ lines.push("</urlset>");
68
+ lines.push("");
69
+
70
+ const body = lines.join("\n");
71
+ return new Response(body, {
72
+ headers: {
73
+ "Content-Type": "application/xml; charset=utf-8",
74
+ "Cache-Control": "public, max-age=3600",
75
+ },
76
+ });
77
+ };