feat: Add script to get npm package data (#499)

* Add zod schemas for CI validation

* Require npm field for components.json

* Remove svelte-layout-resizable

* Stricter Zod validation

* Stricter repository field validation

* Implement requested changes

* Add back accidentally removed field

* Move SvelteStore to templates.json

* Update category and tags

* Add script to get npm data

* Re-run updateNpm.js

* Implement initial feedback

* Update npm.js

* Switch async/await to then/catch

Co-authored-by: MacFJA <MacFJA@users.noreply.github.com>

* Fix prettier

* Run script

* Re-run updateNpm

* Also read tools.json

* Add @types/node

* Restructure npm.json output

* Display last update on components page

* Add to weekly workflow

* Clarify updating npm data

* Update src/lib/utils/injectNpmData.ts

Co-authored-by: MacFJA <MacFJA@users.noreply.github.com>

* Smaller date font, add version

* Improve error text

* Fix property title

---------

Co-authored-by: MacFJA <MacFJA@users.noreply.github.com>
This commit is contained in:
Lachlan Collins
2023-12-18 10:37:40 +11:00
committed by GitHub
parent 3ee0d8f570
commit 77451078d7
10 changed files with 1562 additions and 30 deletions

View File

@@ -1,4 +1,4 @@
name: Update stars count
name: Fetch latest data
on:
schedule:
@@ -19,10 +19,12 @@ jobs:
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Run script
- name: Update stars
uses: ./.github/actions/update-stars
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Update npm data
run: node scripts/updateNpm.js
- name: Run format
run: pnpm run format
- name: Create Pull Request

View File

@@ -26,6 +26,7 @@
"@types/node": "^20.10.4",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"dayjs": "^1.11.10",
"eslint": "^8.55.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.35.1",

7
pnpm-lock.yaml generated
View File

@@ -35,6 +35,9 @@ devDependencies:
'@typescript-eslint/parser':
specifier: ^6.14.0
version: 6.14.0(eslint@8.55.0)(typescript@5.3.3)
dayjs:
specifier: ^1.11.10
version: 1.11.10
eslint:
specifier: ^8.55.0
version: 8.55.0
@@ -1169,6 +1172,10 @@ packages:
hasBin: true
dev: true
/dayjs@1.11.10:
resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
dev: true
/debug@4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}

33
scripts/updateNpm.js Normal file
View File

@@ -0,0 +1,33 @@
// @ts-check
import { writeFileSync } from 'node:fs';
import { promisify } from 'node:util';
import { exec } from 'node:child_process';
import { componentsSchema, toolsSchema } from '../src/lib/schemas.js';
import components from '../src/routes/components/components.json' assert { type: 'json' };
import tools from '../src/routes/tools/tools.json' assert { type: 'json' };
const execAsync = promisify(exec);
const data = [...componentsSchema.parse(components), ...toolsSchema.parse(tools)];
const npm = await Promise.all(
data.map((pkg) => processPackage(pkg).catch((error) => console.log(error.message)))
).then((values) => {
return values.reduce((result, value) => Object.assign(result, value), {});
});
writeFileSync('src/lib/npm.json', JSON.stringify(npm));
/** @param {ReturnType<typeof data>[0]} pkg */
async function processPackage(pkg) {
if (!pkg.npm) {
throw new Error(`npm field missing from ${pkg.title} (skipping)`);
}
const { stdout } = await execAsync(`npm view ${pkg.npm} --json`);
const data = JSON.parse(stdout.toString());
const version = data.version;
const date = data.time[version];
const support = data.peerDependencies?.svelte ? data.peerDependencies.svelte : 'Unknown';
return { [pkg.npm]: { version: version, date: date, support: support } };
}

View File

@@ -2,6 +2,7 @@
import Tag from '../Tag.svelte';
import { copyToClipboard } from '$lib/utils/clipboard';
import { packageManager as manager } from '$stores/packageManager';
import { relativeDate } from '$utils/relativeDate';
export let active = false;
export let title = '';
@@ -11,6 +12,8 @@
export let url = '';
export let npm = '';
export let repository = undefined;
export let date = undefined;
export let version = undefined;
let clipboardCopy = false;
@@ -33,15 +36,35 @@
</script>
<div class="card" class:active id="component-{title}">
<div class="card__top">
<div>
<h3>
<a href="#component-{title}">#</a>
{#if url || repository}<a href={url || repository}>{title}</a>{:else}<span>{title}</span>{/if}
{#if npm}<Tag
{#if url || repository}<a href={url || repository}>{title}</a>{:else}<span>{title}</span
>{/if}
</h3>
</div>
<div>
{#if (repository || url || '').includes('github')}
<a class="repo" title="Go to the source code" href={repository || url}
><img style="display:inline" src="/images/github_logo.svg" alt="github logo" /></a
>
{:else if (repository || url || '').includes('gitlab')}
<a class="repo" title="Go to the source code" href={repository || url}
><img style="display:inline" src="/images/gitlab_logo.svg" alt="gitlab logo" /></a
>
<!-- {:else} -->
{/if}
</div>
</div>
{#if npm}
<Tag
click={() => copy()}
variant="copy"
title={clipboardCopy ? 'copied!' : `${packageManagers[$manager]} ${cleanupNpm(npm)}`}
/>{/if}
</h3>
/>
{/if}
<p class="flex-grow">{description}</p>
{#if tags}
<div class="card__tags">
@@ -51,26 +74,13 @@
</div>
{/if}
<div class="card__bottom">
<div>
{#if (repository || url || '').includes('github')}
<a title="Go to the source code" href={repository || url}
><img style="display:inline" src="/images/github_logo.svg" alt="github logo" /></a
>
{:else if (repository || url || '').includes('gitlab')}
<a title="Go to the source code" href={repository || url}
><img style="display:inline" src="/images/gitlab_logo.svg" alt="gitlab logo" /></a
>
<!-- {:else} -->
{/if}
</div>
<div>
{#if typeof stars !== 'undefined'}
&#9733;
<code>{stars}</code>
{/if}
</div>
<!-- commenting out dates just cause it is not very updated yet - all the cards show same date. put back in when we have better scraping -->
<!-- <datetime value={addedOn}>{new Intl.DateTimeFormat('en-Us').format(Date.parse(addedOn))}</datetime> -->
{#if date && version}<span class="date">Updated {relativeDate(date)} ({version})</span>{/if}
</div>
</div>
@@ -97,6 +107,11 @@
flex-wrap: wrap;
margin-bottom: 1rem;
}
.card__top {
display: flex;
justify-content: space-between;
align-items: top;
}
.card__bottom {
display: flex;
justify-content: space-between;
@@ -105,7 +120,7 @@
.card__bottom > * {
white-space: nowrap;
}
.card__bottom a {
.repo {
border-bottom: none;
aspect-ratio: 1/1;
display: flex;
@@ -117,10 +132,12 @@
background-color: rgba(0, 0, 0, 0);
transition: background-color 200ms ease-out;
}
.card__bottom a:hover {
.repo:hover {
background-color: rgba(0, 0, 0, 0.25);
}
.date {
font-size: 14px;
}
.flex-grow {
flex-grow: 1;
}

1451
src/lib/npm.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
import npm from '$lib/npm.json';
import type { z } from 'zod';
import type { componentsSchema } from '$lib/schemas';
export const injectNpmData = (input: z.infer<typeof componentsSchema>) => {
const output = [];
for (const item of input) {
const extra = npm[item.npm] ?? {};
output.push({ ...item, ...extra });
}
return output;
};

View File

@@ -0,0 +1,7 @@
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
export const relativeDate = (input: string | Date): string => {
dayjs.extend(relativeTime);
return dayjs(input).fromNow();
};

View File

@@ -1,11 +1,12 @@
<script lang="ts">
import components from './components.json';
import SearchableJson from '$lib/SearchableJson.svelte';
import { injectNpmData } from '$utils/injectNpmData';
import { injectStars } from '$utils/stars';
</script>
<SearchableJson
data={injectStars(components)}
data={injectNpmData(injectStars(components))}
displayTitle="Components"
displayTitleSingular="component"
submittingType="component"

View File

@@ -1,11 +1,12 @@
<script lang="ts">
import tools from '../tools/tools.json';
import SearchableJson from '$lib/SearchableJson.svelte';
import { injectNpmData } from '$utils/injectNpmData';
import { injectStars } from '$utils/stars';
</script>
<SearchableJson
data={injectStars(tools)}
data={injectNpmData(injectStars(tools))}
displayTitle="Tools"
displayTitleSingular="tool"
submittingType="tool"