Merge pull request #1 from simonecorsi/develop
feat: generate awesome list from stars
This commit is contained in:
commit
058903d5d8
3
.eslintignore
Normal file
3
.eslintignore
Normal file
@ -0,0 +1,3 @@
|
||||
*fixtures*
|
||||
*test*
|
||||
*dist*
|
21
.eslintrc
Normal file
21
.eslintrc
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"prettier/@typescript-eslint",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"prefer-rest-params": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off"
|
||||
}
|
||||
}
|
32
.github/workflows/release.yaml
vendored
Normal file
32
.github/workflows/release.yaml
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
name: Publish workflow
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: main
|
||||
|
||||
jobs:
|
||||
version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Bump version and push tag
|
||||
id: changelog
|
||||
uses: TriPSs/conventional-changelog-action@v2
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
git-message: 'chore(release): {version}'
|
||||
tag-prefix: ''
|
||||
preset: ''
|
||||
|
||||
- name: Create github Release
|
||||
uses: actions/create-release@v1
|
||||
if: ${{ steps.changelog.outputs.skipped == 'false' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ steps.changelog.outputs.tag }}
|
||||
release_name: ${{ steps.changelog.outputs.tag }}
|
||||
body: ${{ steps.changelog.outputs.changelog }}
|
119
.gitignore
vendored
Normal file
119
.gitignore
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
.env*
|
||||
fixtures/data/*
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
7
.huskyrc.js
Normal file
7
.huskyrc.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
hooks: {
|
||||
'prepare-commit-msg': 'exec < /dev/tty && git cz --hook || true',
|
||||
'pre-commit': 'npm run build && lint-staged',
|
||||
'commit-msg': 'commitlint -E HUSKY_GIT_PARAMS',
|
||||
},
|
||||
};
|
2
.npmignore
Normal file
2
.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!dist/**/**
|
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
}
|
14
CHANGELOG.md
Normal file
14
CHANGELOG.md
Normal file
@ -0,0 +1,14 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
## 1.0.0-0 (2021-01-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* generating awesome list from user star 5f9ce51
|
||||
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
5
action.yml
Normal file
5
action.yml
Normal file
@ -0,0 +1,5 @@
|
||||
name: 'Mawesome'
|
||||
description: 'Generate awesome list from user starred repositories'
|
||||
runs:
|
||||
using: 'node14'
|
||||
main: 'dist/index.js'
|
1
commitlint.config.js
Normal file
1
commitlint.config.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = { extends: ['@commitlint/config-conventional'] };
|
6
lint-staged.config.js
Normal file
6
lint-staged.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
'*.{js,css,json,md,yaml,yml}': ['prettier --write'],
|
||||
'*.md': (filenames) =>
|
||||
filenames.map((filename) => `'markdown-toc -i ${filename}`),
|
||||
'*.ts': ['npm run style:lint', 'npm run style:prettier'],
|
||||
};
|
3
nyc.config.js
Normal file
3
nyc.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
exclude: ['*fixtures*', '*test*'],
|
||||
};
|
9896
package-lock.json
generated
Normal file
9896
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
70
package.json
Normal file
70
package.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "mawesome",
|
||||
"version": "1.0.0-0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"style:lint": "eslint src --ext .ts",
|
||||
"style:prettier": "prettier \"src/**/*.ts\" --list-different --write",
|
||||
"build": "./node_modules/.bin/ncc build src/index.ts -o ./",
|
||||
"dev": "ts-node-dev src/index.ts",
|
||||
"prerelease": "npm run build",
|
||||
"release": "HUSKY_SKIP_HOOKS=1 standard-version",
|
||||
"prenext": "npm run build",
|
||||
"next": "HUSKY_SKIP_HOOKS=1 standard-version --prerelease"
|
||||
},
|
||||
"keywords": [
|
||||
"github",
|
||||
"action",
|
||||
"awesome",
|
||||
"list",
|
||||
"generator",
|
||||
"typescript",
|
||||
"javascript"
|
||||
],
|
||||
"author": "Simone Corsi",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^11.0.0",
|
||||
"@commitlint/config-conventional": "^11.0.0",
|
||||
"@octokit/rest": "^18.0.12",
|
||||
"@octokit/types": "^6.2.1",
|
||||
"@types/ejs": "^3.0.5",
|
||||
"@types/got": "^9.6.11",
|
||||
"@types/node": "^14.14.5",
|
||||
"@typescript-eslint/eslint-plugin": "^4.6.0",
|
||||
"@typescript-eslint/parser": "^4.6.0",
|
||||
"@vercel/ncc": "^0.26.2",
|
||||
"ava": "^3.8.2",
|
||||
"benchmark": "^2.1.4",
|
||||
"conventional-gitlab-releaser": "^4.0.1",
|
||||
"cz-conventional-changelog": "^3.2.0",
|
||||
"eslint": "^7.17.0",
|
||||
"eslint-config-prettier": "^7.1.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"husky": "^4.2.5",
|
||||
"lint-staged": "^10.2.2",
|
||||
"markdown-toc": "^1.2.0",
|
||||
"ninos": "^3.0.0",
|
||||
"nyc": "^15.0.1",
|
||||
"prettier": "^2.0.5",
|
||||
"standard-version": "^9.0.0",
|
||||
"ts-node-dev": "^1.1.1",
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/exec": "^1.0.4",
|
||||
"@actions/core": "^1.2.6",
|
||||
"@actions/github": "^4.0.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"ejs": "^3.1.5",
|
||||
"got": "^11.8.1",
|
||||
"remark": "^13.0.0",
|
||||
"remark-toc": "^7.0.0"
|
||||
},
|
||||
"volta": {
|
||||
"node": "14.15.4"
|
||||
}
|
||||
}
|
11
src/api.ts
Normal file
11
src/api.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import got from 'got';
|
||||
import core from '@actions/core';
|
||||
|
||||
const GITHUB_TOKEN = core.getInput('githubToken', { required: true });
|
||||
|
||||
export default got.extend({
|
||||
headers: {
|
||||
Authorization: `token ${GITHUB_TOKEN}`,
|
||||
},
|
||||
responseType: 'json',
|
||||
});
|
100
src/git.ts
Normal file
100
src/git.ts
Normal file
@ -0,0 +1,100 @@
|
||||
// original content by: github.com/TriPSs/conventional-changelog-action/blob/master/src/helpers/git.js
|
||||
|
||||
import core from '@actions/core';
|
||||
import exec from '@actions/exec';
|
||||
|
||||
const { GITHUB_REPOSITORY, GITHUB_REF } = process.env;
|
||||
|
||||
const IS_TEST = process.env.NODE_ENV === 'test';
|
||||
|
||||
const branch = GITHUB_REF?.replace('refs/heads/', '');
|
||||
|
||||
export default new (class Git {
|
||||
commandsRun: string[] = [];
|
||||
|
||||
constructor() {
|
||||
const githubToken = core.getInput('github-token', { required: true });
|
||||
core.setSecret(githubToken);
|
||||
|
||||
const gitUserName = core.getInput('git-user-name');
|
||||
const gitUserEmail = core.getInput('git-user-email');
|
||||
|
||||
// Set config
|
||||
this.config('user.name', gitUserName);
|
||||
this.config('user.email', gitUserEmail);
|
||||
|
||||
// Update the origin
|
||||
this.updateOrigin(
|
||||
`https://x-access-token:${githubToken}@github.com/${GITHUB_REPOSITORY}.git`
|
||||
);
|
||||
}
|
||||
|
||||
exec = (command: string): Promise<string> => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (IS_TEST) {
|
||||
const fullCommand = `git ${command}`;
|
||||
|
||||
console.log(`Skipping "${fullCommand}" because of test env`);
|
||||
|
||||
if (!fullCommand.includes('git remote set-url origin')) {
|
||||
this.commandsRun.push(fullCommand);
|
||||
}
|
||||
return resolve('done');
|
||||
}
|
||||
let execOutput = '';
|
||||
|
||||
const options = {
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => {
|
||||
execOutput += data.toString();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const exitCode = await exec.exec(`git ${command}`, undefined, options);
|
||||
|
||||
if (exitCode === 0) {
|
||||
return resolve(execOutput);
|
||||
} else {
|
||||
return reject(`Command "git ${command}" exited with code ${exitCode}.`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
config = (prop: string, value: string) =>
|
||||
this.exec(`config ${prop} "${value}"`);
|
||||
|
||||
add = (file: string) => this.exec(`add ${file}`);
|
||||
|
||||
commit = (message: string) => this.exec(`commit -m "${message}"`);
|
||||
|
||||
pull = async () => {
|
||||
const args = ['pull'];
|
||||
|
||||
// Check if the repo is unshallow
|
||||
if (await this.isShallow()) {
|
||||
args.push('--unshallow');
|
||||
}
|
||||
|
||||
args.push('--tags');
|
||||
args.push(core.getInput('git-pull-method'));
|
||||
|
||||
return this.exec(args.join(' '));
|
||||
};
|
||||
|
||||
push = () => this.exec(`push origin ${branch} --follow-tags`);
|
||||
|
||||
isShallow = async () => {
|
||||
if (IS_TEST) return false;
|
||||
|
||||
const isShallow: string = await this.exec(
|
||||
'rev-parse --is-shallow-repository'
|
||||
);
|
||||
|
||||
return isShallow.trim().replace('\n', '') === 'true';
|
||||
};
|
||||
|
||||
updateOrigin = (repo: string) => this.exec(`remote set-url origin ${repo}`);
|
||||
|
||||
createTag = (tag: string) => this.exec(`tag -a ${tag} -m "${tag}"`);
|
||||
})();
|
143
src/index.ts
Normal file
143
src/index.ts
Normal file
@ -0,0 +1,143 @@
|
||||
import dotnenv from 'dotenv';
|
||||
import fs from 'fs/promises';
|
||||
|
||||
import ejs from 'ejs';
|
||||
import remark from 'remark';
|
||||
import toc from 'remark-toc';
|
||||
import GithubApi from './api';
|
||||
import link from './link';
|
||||
import git from './git';
|
||||
import core from '@actions/core';
|
||||
|
||||
import type {
|
||||
SortedLanguageList,
|
||||
PaginationLink,
|
||||
Stars,
|
||||
Star,
|
||||
ApiGetStarResponse,
|
||||
} from './types';
|
||||
|
||||
const REPO_USERNAME = process.env.GITHUB_REPOSITORY?.split('/')[0];
|
||||
const OUTPUT_FILENAME: string = core.getInput('output-filename') || 'README.md';
|
||||
const IS_PROD = process.env.NODE_ENV === 'production';
|
||||
|
||||
const USERNAME = process.env.GITHUB_ACTOR || 'simonecorsi';
|
||||
const API_STARRED_URL = `'https://api.github.com/users/${REPO_USERNAME}/starred'`;
|
||||
|
||||
const renderer = async (data: any) => {
|
||||
try {
|
||||
const MD_TEMPLATE = await fs.readFile('fixtures/template.md.ejs', 'utf-8');
|
||||
return ejs.render(MD_TEMPLATE, data);
|
||||
} catch (error) {
|
||||
core.error(error);
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
if (!IS_PROD) {
|
||||
dotnenv.config();
|
||||
}
|
||||
|
||||
const wait = (time = 200) =>
|
||||
new Promise((resolve) => setTimeout(resolve, time));
|
||||
|
||||
async function apiGetStar(url: string): Promise<ApiGetStarResponse> {
|
||||
const { headers, body }: any = await (async () => {
|
||||
if (!IS_PROD)
|
||||
return JSON.parse(
|
||||
await fs.readFile('fixtures/stars-response.json', 'utf-8')
|
||||
);
|
||||
return GithubApi.get(url);
|
||||
})();
|
||||
return {
|
||||
data: body,
|
||||
links: link.parse(headers.link).refs.reduce(
|
||||
(acc, val) => ({
|
||||
...acc,
|
||||
[val.rel]: val.uri,
|
||||
}),
|
||||
{}
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function isLastPage(links: PaginationLink): boolean {
|
||||
return links.next === links.last;
|
||||
}
|
||||
|
||||
export function generateMd(data: string): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
remark()
|
||||
.use(toc)
|
||||
.process(data, function (error, file) {
|
||||
if (error) {
|
||||
core.error(error);
|
||||
return resolve('');
|
||||
}
|
||||
return resolve(String(file));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function main(): Promise<any> {
|
||||
let links: PaginationLink = {
|
||||
next: API_STARRED_URL,
|
||||
last: undefined,
|
||||
};
|
||||
let results: Stars = [];
|
||||
do {
|
||||
const r = await apiGetStar(links.next);
|
||||
links = r.links;
|
||||
results = results.concat(r.data);
|
||||
if (!IS_PROD) break;
|
||||
await wait();
|
||||
} while (!isLastPage(links));
|
||||
|
||||
const sortedByLanguages = results.reduce(
|
||||
(acc: SortedLanguageList, val: Star) => {
|
||||
const language = val.language || 'generic';
|
||||
if (!acc[language]) {
|
||||
acc[language] = [val];
|
||||
} else {
|
||||
acc[language].push(val);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const rendered = await renderer({
|
||||
username: USERNAME,
|
||||
stars: Object.entries(sortedByLanguages),
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
|
||||
const markdown: string = await generateMd(rendered);
|
||||
|
||||
await fs.writeFile(OUTPUT_FILENAME, markdown);
|
||||
|
||||
await git.add(OUTPUT_FILENAME);
|
||||
|
||||
await git.commit(`chore(${OUTPUT_FILENAME}): updated list`);
|
||||
|
||||
await git.push();
|
||||
}
|
||||
|
||||
export async function run(): Promise<any> {
|
||||
try {
|
||||
await main();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
process.stderr.write(error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const catchAll = (info: any) => {
|
||||
core.error(info);
|
||||
process.exit(1);
|
||||
};
|
||||
process.on('unhandledRejection', catchAll);
|
||||
process.on('uncaughtException', catchAll);
|
||||
|
||||
run();
|
383
src/link.js
Normal file
383
src/link.js
Normal file
@ -0,0 +1,383 @@
|
||||
/**
|
||||
* Copyright (c) 2016 Jonas Hermsmeier
|
||||
* https://github.com/jhermsmeier/node-http-link-header
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var COMPATIBLE_ENCODING_PATTERN = /^utf-?8|ascii|utf-?16-?le|ucs-?2|base-?64|latin-?1$/i;
|
||||
var WS_TRIM_PATTERN = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
|
||||
var WS_CHAR_PATTERN = /\s|\uFEFF|\xA0/;
|
||||
var WS_FOLD_PATTERN = /\r?\n[\x20\x09]+/g;
|
||||
var DELIMITER_PATTERN = /[;,"]/;
|
||||
var WS_DELIMITER_PATTERN = /[;,"]|\s/;
|
||||
|
||||
/**
|
||||
* Token character pattern
|
||||
* @type {RegExp}
|
||||
* @see https://tools.ietf.org/html/rfc7230#section-3.2.6
|
||||
*/
|
||||
var TOKEN_PATTERN = /^[!#$%&'*+\-\.^_`|~\da-zA-Z]+$/;
|
||||
|
||||
var STATE = {
|
||||
IDLE: 1 << 0,
|
||||
URI: 1 << 1,
|
||||
ATTR: 1 << 2,
|
||||
};
|
||||
|
||||
function trim(value) {
|
||||
return value.replace(WS_TRIM_PATTERN, '');
|
||||
}
|
||||
|
||||
function hasWhitespace(value) {
|
||||
return WS_CHAR_PATTERN.test(value);
|
||||
}
|
||||
|
||||
function skipWhitespace(value, offset) {
|
||||
while (hasWhitespace(value[offset])) {
|
||||
offset++;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
function needsQuotes(value) {
|
||||
return WS_DELIMITER_PATTERN.test(value) || !TOKEN_PATTERN.test(value);
|
||||
}
|
||||
|
||||
class Link {
|
||||
/**
|
||||
* Link
|
||||
* @constructor
|
||||
* @param {String} [value]
|
||||
* @returns {Link}
|
||||
*/
|
||||
constructor(value) {
|
||||
/** @type {Array} URI references */
|
||||
this.refs = [];
|
||||
|
||||
if (value) {
|
||||
this.parse(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get refs with given relation type
|
||||
* @param {String} value
|
||||
* @returns {Array<Object>}
|
||||
*/
|
||||
rel(value) {
|
||||
var links = [];
|
||||
var type = value.toLowerCase();
|
||||
|
||||
for (var i = 0; i < this.refs.length; i++) {
|
||||
if (this.refs[i].rel.toLowerCase() === type) {
|
||||
links.push(this.refs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get refs where given attribute has a given value
|
||||
* @param {String} attr
|
||||
* @param {String} value
|
||||
* @returns {Array<Object>}
|
||||
*/
|
||||
get(attr, value) {
|
||||
attr = attr.toLowerCase();
|
||||
|
||||
var links = [];
|
||||
|
||||
for (var i = 0; i < this.refs.length; i++) {
|
||||
if (this.refs[i][attr] === value) {
|
||||
links.push(this.refs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
set(link) {
|
||||
this.refs.push(link);
|
||||
return this;
|
||||
}
|
||||
|
||||
has(attr, value) {
|
||||
attr = attr.toLowerCase();
|
||||
|
||||
for (var i = 0; i < this.refs.length; i++) {
|
||||
if (this.refs[i][attr] === value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
parse(value, offset) {
|
||||
offset = offset || 0;
|
||||
value = offset ? value.slice(offset) : value;
|
||||
|
||||
// Trim & unfold folded lines
|
||||
value = trim(value).replace(WS_FOLD_PATTERN, '');
|
||||
|
||||
var state = STATE.IDLE;
|
||||
var length = value.length;
|
||||
var offset = 0;
|
||||
var ref = null;
|
||||
|
||||
while (offset < length) {
|
||||
if (state === STATE.IDLE) {
|
||||
if (hasWhitespace(value[offset])) {
|
||||
offset++;
|
||||
continue;
|
||||
} else if (value[offset] === '<') {
|
||||
if (ref != null) {
|
||||
ref.rel != null
|
||||
? this.refs.push(...Link.expandRelations(ref))
|
||||
: this.refs.push(ref);
|
||||
}
|
||||
var end = value.indexOf('>', offset);
|
||||
if (end === -1)
|
||||
throw new Error(
|
||||
'Expected end of URI delimiter at offset ' + offset
|
||||
);
|
||||
ref = { uri: value.slice(offset + 1, end) };
|
||||
// this.refs.push( ref )
|
||||
offset = end;
|
||||
state = STATE.URI;
|
||||
} else {
|
||||
throw new Error(
|
||||
'Unexpected character "' + value[offset] + '" at offset ' + offset
|
||||
);
|
||||
}
|
||||
offset++;
|
||||
} else if (state === STATE.URI) {
|
||||
if (hasWhitespace(value[offset])) {
|
||||
offset++;
|
||||
continue;
|
||||
} else if (value[offset] === ';') {
|
||||
state = STATE.ATTR;
|
||||
offset++;
|
||||
} else if (value[offset] === ',') {
|
||||
state = STATE.IDLE;
|
||||
offset++;
|
||||
} else {
|
||||
throw new Error(
|
||||
'Unexpected character "' + value[offset] + '" at offset ' + offset
|
||||
);
|
||||
}
|
||||
} else if (state === STATE.ATTR) {
|
||||
if (value[offset] === ';' || hasWhitespace(value[offset])) {
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
var end = value.indexOf('=', offset);
|
||||
if (end === -1)
|
||||
throw new Error('Expected attribute delimiter at offset ' + offset);
|
||||
var attr = trim(value.slice(offset, end)).toLowerCase();
|
||||
var attrValue = '';
|
||||
offset = end + 1;
|
||||
offset = skipWhitespace(value, offset);
|
||||
if (value[offset] === '"') {
|
||||
offset++;
|
||||
while (offset < length) {
|
||||
if (value[offset] === '"') {
|
||||
offset++;
|
||||
break;
|
||||
}
|
||||
if (value[offset] === '\\') {
|
||||
offset++;
|
||||
}
|
||||
attrValue += value[offset];
|
||||
offset++;
|
||||
}
|
||||
} else {
|
||||
var end = offset + 1;
|
||||
while (!DELIMITER_PATTERN.test(value[end]) && end < length) {
|
||||
end++;
|
||||
}
|
||||
attrValue = value.slice(offset, end);
|
||||
offset = end;
|
||||
}
|
||||
if (ref[attr] && Link.isSingleOccurenceAttr(attr)) {
|
||||
// Ignore multiples of attributes which may only appear once
|
||||
} else if (attr[attr.length - 1] === '*') {
|
||||
ref[attr] = Link.parseExtendedValue(attrValue);
|
||||
} else {
|
||||
attrValue = attr === 'type' ? attrValue.toLowerCase() : attrValue;
|
||||
if (ref[attr] != null) {
|
||||
if (Array.isArray(ref[attr])) {
|
||||
ref[attr].push(attrValue);
|
||||
} else {
|
||||
ref[attr] = [ref[attr], attrValue];
|
||||
}
|
||||
} else {
|
||||
ref[attr] = attrValue;
|
||||
}
|
||||
}
|
||||
switch (value[offset]) {
|
||||
case ',':
|
||||
state = STATE.IDLE;
|
||||
break;
|
||||
case ';':
|
||||
state = STATE.ATTR;
|
||||
break;
|
||||
}
|
||||
offset++;
|
||||
} else {
|
||||
throw new Error('Unknown parser state "' + state + '"');
|
||||
}
|
||||
}
|
||||
|
||||
if (ref != null) {
|
||||
ref.rel != null
|
||||
? this.refs.push(...Link.expandRelations(ref))
|
||||
: this.refs.push(ref);
|
||||
}
|
||||
|
||||
ref = null;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
toString() {
|
||||
var refs = [];
|
||||
var link = '';
|
||||
var ref = null;
|
||||
|
||||
for (var i = 0; i < this.refs.length; i++) {
|
||||
ref = this.refs[i];
|
||||
link = Object.keys(this.refs[i]).reduce(function (link, attr) {
|
||||
if (attr === 'uri') return link;
|
||||
return link + '; ' + Link.formatAttribute(attr, ref[attr]);
|
||||
}, '<' + ref.uri + '>');
|
||||
refs.push(link);
|
||||
}
|
||||
|
||||
return refs.join(', ');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether an encoding can be
|
||||
* natively handled with a `Buffer`
|
||||
* @param {String} value
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
Link.isCompatibleEncoding = function (value) {
|
||||
return COMPATIBLE_ENCODING_PATTERN.test(value);
|
||||
};
|
||||
|
||||
Link.parse = function (value, offset) {
|
||||
return new Link().parse(value, offset);
|
||||
};
|
||||
|
||||
Link.isSingleOccurenceAttr = function (attr) {
|
||||
return (
|
||||
attr === 'rel' ||
|
||||
attr === 'type' ||
|
||||
attr === 'media' ||
|
||||
attr === 'title' ||
|
||||
attr === 'title*'
|
||||
);
|
||||
};
|
||||
|
||||
Link.isTokenAttr = function (attr) {
|
||||
return attr === 'rel' || attr === 'type' || attr === 'anchor';
|
||||
};
|
||||
|
||||
Link.escapeQuotes = function (value) {
|
||||
return value.replace(/"/g, '\\"');
|
||||
};
|
||||
|
||||
Link.expandRelations = function (ref) {
|
||||
var rels = ref.rel.split(' ');
|
||||
return rels.map(function (rel) {
|
||||
var value = Object.assign({}, ref);
|
||||
value.rel = rel;
|
||||
return value;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses an extended value and attempts to decode it
|
||||
* @internal
|
||||
* @param {String} value
|
||||
* @return {Object}
|
||||
*/
|
||||
Link.parseExtendedValue = function (value) {
|
||||
var parts = /([^']+)?(?:'([^']+)')?(.+)/.exec(value);
|
||||
return {
|
||||
language: parts[2].toLowerCase(),
|
||||
encoding: Link.isCompatibleEncoding(parts[1])
|
||||
? null
|
||||
: parts[1].toLowerCase(),
|
||||
value: Link.isCompatibleEncoding(parts[1])
|
||||
? decodeURIComponent(parts[3])
|
||||
: parts[3],
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Format a given extended attribute and it's value
|
||||
* @param {String} attr
|
||||
* @param {Object} data
|
||||
* @return {String}
|
||||
*/
|
||||
Link.formatExtendedAttribute = function (attr, data) {
|
||||
var encoding = (data.encoding || 'utf-8').toUpperCase();
|
||||
var language = data.language || 'en';
|
||||
|
||||
var encodedValue = '';
|
||||
|
||||
if (Buffer.isBuffer(data.value) && Link.isCompatibleEncoding(encoding)) {
|
||||
encodedValue = data.value.toString(encoding);
|
||||
} else if (Buffer.isBuffer(data.value)) {
|
||||
encodedValue = data.value.toString('hex').replace(/[0-9a-f]{2}/gi, '%$1');
|
||||
} else {
|
||||
encodedValue = encodeURIComponent(data.value);
|
||||
}
|
||||
|
||||
return attr + '=' + encoding + "'" + language + "'" + encodedValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Format a given attribute and it's value
|
||||
* @param {String} attr
|
||||
* @param {String|Object} value
|
||||
* @return {String}
|
||||
*/
|
||||
Link.formatAttribute = function (attr, value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
.map((item) => {
|
||||
return Link.formatAttribute(attr, item);
|
||||
})
|
||||
.join('; ');
|
||||
}
|
||||
|
||||
if (attr[attr.length - 1] === '*' || typeof value !== 'string') {
|
||||
return Link.formatExtendedAttribute(attr, value);
|
||||
}
|
||||
|
||||
if (Link.isTokenAttr(attr)) {
|
||||
value = needsQuotes(value)
|
||||
? '"' + Link.escapeQuotes(value) + '"'
|
||||
: Link.escapeQuotes(value);
|
||||
} else if (needsQuotes(value)) {
|
||||
value = encodeURIComponent(value);
|
||||
// We don't need to escape <SP> <,> <;> within quotes
|
||||
value = value
|
||||
.replace(/%20/g, ' ')
|
||||
.replace(/%2C/g, ',')
|
||||
.replace(/%3B/g, ';');
|
||||
|
||||
value = '"' + value + '"';
|
||||
}
|
||||
|
||||
return attr + '=' + value;
|
||||
};
|
||||
|
||||
module.exports = Link;
|
18
src/types.ts
Normal file
18
src/types.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Endpoints } from '@octokit/types';
|
||||
|
||||
export type SortedLanguageList = {
|
||||
[language: string]: Star[];
|
||||
};
|
||||
|
||||
export type PaginationLink = {
|
||||
next: string;
|
||||
last: string | undefined | null;
|
||||
};
|
||||
|
||||
export type Stars = Endpoints['GET /user/starred']['response']['data'];
|
||||
export type Star = Stars[number] | { language: string };
|
||||
|
||||
export type ApiGetStarResponse = {
|
||||
links: PaginationLink;
|
||||
data: Stars;
|
||||
};
|
72
tsconfig.json
Normal file
72
tsconfig.json
Normal file
@ -0,0 +1,72 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "ES6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
|
||||
"module": "CommonJS" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
"allowJs": true /* Allow javascript files to be compiled. */,
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./dist" /* Redirect output structure to the directory. */,
|
||||
// "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||
|
||||
/* Module Resolution Options */
|
||||
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true /* Skip type checking of declaration files. */,
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules/**"]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user