Compare commits
9 Commits
main
...
v1.1.0-nex
Author | SHA1 | Date | |
---|---|---|---|
|
17e7434940 | ||
|
feff4b4d2a | ||
|
91f90aedbe | ||
|
cd1c89b2ef | ||
|
52bf53cb39 | ||
|
a8b657735b | ||
|
7fb87741a2 | ||
|
602befcb54 | ||
|
791de9ab50 |
21
.eslintrc
21
.eslintrc
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
17
.eslintrc.js
Normal file
17
.eslintrc.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2022,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['@typescript-eslint'],
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
],
|
||||||
|
};
|
50
CHANGELOG.md
50
CHANGELOG.md
@@ -1,35 +1,43 @@
|
|||||||
|
# [1.1.0-next.2](https://github.com/simonecorsi/mawesome/compare/v1.1.0-next.1...v1.1.0-next.2) (2022-05-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* using gh-star-fetch ([a8b6577](https://github.com/simonecorsi/mawesome/commit/a8b657735b9879636cc039d79fddcdca33ccf38e))
|
||||||
|
|
||||||
|
# [1.1.0-next.1](https://github.com/simonecorsi/mawesome/compare/v1.0.45...v1.1.0-next.1) (2022-04-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* pre-tags ([602befc](https://github.com/simonecorsi/mawesome/commit/602befcb5494b5bfd74d2333899311b81ea9da6d))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **template:** adds templates ([791de9a](https://github.com/simonecorsi/mawesome/commit/791de9ab504de50e2e9cb031b9e373d7cc0589c0)), closes [#14](https://github.com/simonecorsi/mawesome/issues/14)
|
||||||
|
|
||||||
## <small>1.0.45 (2022-01-17)</small>
|
## <small>1.0.45 (2022-01-17)</small>
|
||||||
|
|
||||||
* build(deps): bump shelljs from 0.8.4 to 0.8.5 ([1da350a](https://github.com/simonecorsi/mawesome/commit/1da350a))
|
- build(deps): bump shelljs from 0.8.4 to 0.8.5 ([1da350a](https://github.com/simonecorsi/mawesome/commit/1da350a))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## <small>1.0.44 (2021-10-26)</small>
|
## <small>1.0.44 (2021-10-26)</small>
|
||||||
|
|
||||||
* chore(release): v1.0.44 ([6ffe17f](https://github.com/simonecorsi/mawesome/commit/6ffe17f))
|
- chore(release): v1.0.44 ([6ffe17f](https://github.com/simonecorsi/mawesome/commit/6ffe17f))
|
||||||
* feat: git add multiple files at once ([4aef61f](https://github.com/simonecorsi/mawesome/commit/4aef61f))
|
- feat: git add multiple files at once ([4aef61f](https://github.com/simonecorsi/mawesome/commit/4aef61f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## <small>1.0.43 (2021-10-15)</small>
|
## <small>1.0.43 (2021-10-15)</small>
|
||||||
|
|
||||||
* chore(release): v1.0.43 ([5e0ef82](https://github.com/simonecorsi/mawesome/commit/5e0ef82))
|
- chore(release): v1.0.43 ([5e0ef82](https://github.com/simonecorsi/mawesome/commit/5e0ef82))
|
||||||
* feat: reduce json output size ([94635fe](https://github.com/simonecorsi/mawesome/commit/94635fe))
|
- feat: reduce json output size ([94635fe](https://github.com/simonecorsi/mawesome/commit/94635fe))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## <small>1.0.42 (2021-10-14)</small>
|
## <small>1.0.42 (2021-10-14)</small>
|
||||||
|
|
||||||
* chore(release): v1.0.42 ([5b36813](https://github.com/simonecorsi/mawesome/commit/5b36813))
|
- chore(release): v1.0.42 ([5b36813](https://github.com/simonecorsi/mawesome/commit/5b36813))
|
||||||
* fix(paginator): last page now correctly matches rex ([dcf9898](https://github.com/simonecorsi/mawesome/commit/dcf9898))
|
- fix(paginator): last page now correctly matches rex ([dcf9898](https://github.com/simonecorsi/mawesome/commit/dcf9898))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## <small>1.0.41 (2021-10-14)</small>
|
## <small>1.0.41 (2021-10-14)</small>
|
||||||
|
|
||||||
* chore(release): v1.0.41 ([884fc90](https://github.com/simonecorsi/mawesome/commit/884fc90))
|
- chore(release): v1.0.41 ([884fc90](https://github.com/simonecorsi/mawesome/commit/884fc90))
|
||||||
* test: fixs suite ([b97833f](https://github.com/simonecorsi/mawesome/commit/b97833f))
|
- test: fixs suite ([b97833f](https://github.com/simonecorsi/mawesome/commit/b97833f))
|
||||||
* fix: should avoid index lock ([1d6848c](https://github.com/simonecorsi/mawesome/commit/1d6848c))
|
- fix: should avoid index lock ([1d6848c](https://github.com/simonecorsi/mawesome/commit/1d6848c))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
12
action.yml
12
action.yml
@@ -12,8 +12,20 @@ inputs:
|
|||||||
required: true
|
required: true
|
||||||
github-name:
|
github-name:
|
||||||
description: 'Name shown in the commit'
|
description: 'Name shown in the commit'
|
||||||
|
default: 'GitHub Actions'
|
||||||
|
required: false
|
||||||
github-email:
|
github-email:
|
||||||
description: 'Email shown in the commit'
|
description: 'Email shown in the commit'
|
||||||
|
default: 'actions@users.noreply.github.com'
|
||||||
|
required: false
|
||||||
|
template-path:
|
||||||
|
required: false
|
||||||
|
description: 'EJS template path relative to project root directory'
|
||||||
|
default: 'TEMPLATE.ejs'
|
||||||
|
output-filename:
|
||||||
|
description: 'The output file name, default to README.md'
|
||||||
|
required: false
|
||||||
|
default: 'README.md'
|
||||||
runs:
|
runs:
|
||||||
using: 'node12'
|
using: 'node12'
|
||||||
main: 'index.js'
|
main: 'index.js'
|
||||||
|
695
package-lock.json
generated
695
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mawesome",
|
"name": "mawesome",
|
||||||
"version": "1.0.45",
|
"version": "1.1.0-next.1",
|
||||||
"description": "Generate awesome list from user starred repositories",
|
"description": "Generate awesome list from user starred repositories",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"author": "Simone Corsi<simonecorsi.dev@gmail.com>",
|
"author": "Simone Corsi<simonecorsi.dev@gmail.com>",
|
||||||
@@ -39,8 +39,8 @@
|
|||||||
"@types/got": "^9.6.12",
|
"@types/got": "^9.6.12",
|
||||||
"@types/node": "^14.14.5",
|
"@types/node": "^14.14.5",
|
||||||
"@types/sinon": "^9.0.10",
|
"@types/sinon": "^9.0.10",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
"@typescript-eslint/eslint-plugin": "^5.22.0",
|
||||||
"@typescript-eslint/parser": "^4.6.0",
|
"@typescript-eslint/parser": "^5.22.0",
|
||||||
"@vercel/ncc": "^0.33.3",
|
"@vercel/ncc": "^0.33.3",
|
||||||
"ava": "^3.8.2",
|
"ava": "^3.8.2",
|
||||||
"eslint": "^7.17.0",
|
"eslint": "^7.17.0",
|
||||||
@@ -54,12 +54,13 @@
|
|||||||
"prettier": "^2.0.5",
|
"prettier": "^2.0.5",
|
||||||
"sinon": "^9.2.3",
|
"sinon": "^9.2.3",
|
||||||
"ts-node-dev": "^1.1.1",
|
"ts-node-dev": "^1.1.1",
|
||||||
"typescript": "^4.1.3"
|
"typescript": "^4.6.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.2.6",
|
"@actions/core": "^1.2.6",
|
||||||
"@actions/exec": "^1.1.1",
|
"@actions/exec": "^1.1.1",
|
||||||
"ejs": "^3.1.6",
|
"ejs": "^3.1.6",
|
||||||
|
"gh-star-fetch": "^1.1.0",
|
||||||
"got": "^11.8.1",
|
"got": "^11.8.1",
|
||||||
"remark": "^13.0.0",
|
"remark": "^13.0.0",
|
||||||
"remark-toc": "^7.0.0"
|
"remark-toc": "^7.0.0"
|
||||||
|
11
src/api.ts
11
src/api.ts
@@ -1,11 +0,0 @@
|
|||||||
import got from 'got';
|
|
||||||
import * as core from '@actions/core';
|
|
||||||
|
|
||||||
const GITHUB_TOKEN = core.getInput('api-token', { required: true });
|
|
||||||
|
|
||||||
export default got.extend({
|
|
||||||
headers: {
|
|
||||||
Authorization: `token ${GITHUB_TOKEN}`,
|
|
||||||
},
|
|
||||||
responseType: 'json',
|
|
||||||
});
|
|
34
src/git.ts
34
src/git.ts
@@ -34,28 +34,26 @@ class Git {
|
|||||||
return isShallow.trim().replace('\n', '') === 'true';
|
return isShallow.trim().replace('\n', '') === 'true';
|
||||||
};
|
};
|
||||||
|
|
||||||
exec = (command: string): Promise<string> => {
|
async exec(command: string): Promise<string> {
|
||||||
return new Promise(async (resolve, reject) => {
|
let execOutput = '';
|
||||||
let execOutput = '';
|
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
listeners: {
|
listeners: {
|
||||||
stdout: (data: Buffer) => {
|
stdout: (data: Buffer) => {
|
||||||
execOutput += data.toString();
|
execOutput += data.toString();
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const exitCode = await exec.exec(`git ${command}`, undefined, options);
|
const exitCode = await exec.exec(`git ${command}`, undefined, options);
|
||||||
|
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
return resolve(execOutput);
|
return execOutput;
|
||||||
} else {
|
} else {
|
||||||
core.error(`Command "git ${command}" exited with code ${exitCode}.`);
|
core.error(`Command "git ${command}" exited with code ${exitCode}.`);
|
||||||
return reject(`Command "git ${command}" exited with code ${exitCode}.`);
|
throw new Error(`Command "git ${command}" exited with code ${exitCode}.`);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
};
|
|
||||||
|
|
||||||
config = (prop: string, value: string) =>
|
config = (prop: string, value: string) =>
|
||||||
this.exec(`config ${prop} "${value}"`);
|
this.exec(`config ${prop} "${value}"`);
|
||||||
|
@@ -3,10 +3,6 @@ import ejs from 'ejs';
|
|||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import remark from 'remark';
|
import remark from 'remark';
|
||||||
import toc from 'remark-toc';
|
import toc from 'remark-toc';
|
||||||
|
|
||||||
import MD_TEMPLATE from './template';
|
|
||||||
import GithubApi from './api';
|
|
||||||
import link from './link';
|
|
||||||
import git from './git';
|
import git from './git';
|
||||||
|
|
||||||
import type { PaginationLink, ApiGetStarResponse, Stars, Star } from './types';
|
import type { PaginationLink, ApiGetStarResponse, Stars, Star } from './types';
|
||||||
@@ -16,13 +12,9 @@ export const API_STARRED_URL = `${process.env.GITHUB_API_URL}/users/${REPO_USERN
|
|||||||
|
|
||||||
const fsp = fs.promises;
|
const fsp = fs.promises;
|
||||||
|
|
||||||
export function wait(time = 200): Promise<void> {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, time));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function renderer(
|
export async function renderer(
|
||||||
data: { [key: string]: any },
|
data: { [key: string]: any },
|
||||||
templateString = MD_TEMPLATE
|
templateString: string
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
try {
|
try {
|
||||||
return ejs.render(templateString, data);
|
return ejs.render(templateString, data);
|
||||||
@@ -32,74 +24,6 @@ export async function renderer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNextPage(links: PaginationLink[]): string | null {
|
|
||||||
const next = links.find((l) => l.rel === 'next');
|
|
||||||
const last = links.find((l) => l.rel === 'last');
|
|
||||||
if (!next || !last) return null;
|
|
||||||
const matchNext = next.uri.match(/page=([0-9]*)/);
|
|
||||||
const matchLast = last.uri.match(/page=([0-9]*)/);
|
|
||||||
if (!matchNext || !matchLast) return null;
|
|
||||||
if (matchNext[1] === matchLast[1]) return null;
|
|
||||||
return matchNext[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function* paginateStars(url: string): AsyncGenerator<Stars> {
|
|
||||||
let nextPage: string | null = '1';
|
|
||||||
while (nextPage) {
|
|
||||||
try {
|
|
||||||
const { headers, body } = await GithubApi.get(url, {
|
|
||||||
searchParams: {
|
|
||||||
page: nextPage,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
yield (body as unknown) as Stars;
|
|
||||||
nextPage = getNextPage(link.parse(headers.link).refs);
|
|
||||||
await wait(1000); // avoid limits
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function apiGetStar(
|
|
||||||
url: string = API_STARRED_URL
|
|
||||||
): Promise<ApiGetStarResponse> {
|
|
||||||
const data: Partial<Star>[] = [];
|
|
||||||
for await (const stars of paginateStars(url)) {
|
|
||||||
for (const star of stars) {
|
|
||||||
data.push({
|
|
||||||
id: star.id,
|
|
||||||
node_id: star.node_id,
|
|
||||||
name: star.name,
|
|
||||||
full_name: star.full_name,
|
|
||||||
owner: {
|
|
||||||
login: star?.owner?.login,
|
|
||||||
id: star?.owner?.id,
|
|
||||||
avatar_url: star?.owner?.avatar_url,
|
|
||||||
url: star?.owner?.url,
|
|
||||||
html_url: star?.owner?.html_url,
|
|
||||||
},
|
|
||||||
html_url: star.html_url,
|
|
||||||
description: star.description,
|
|
||||||
url: star.url,
|
|
||||||
languages_url: star.languages_url,
|
|
||||||
created_at: star.created_at,
|
|
||||||
updated_at: star.updated_at,
|
|
||||||
git_url: star.git_url,
|
|
||||||
ssh_url: star.ssh_url,
|
|
||||||
clone_url: star.clone_url,
|
|
||||||
homepage: star.homepage,
|
|
||||||
stargazers_count: star.stargazers_count,
|
|
||||||
watchers_count: star.watchers_count,
|
|
||||||
language: star.language,
|
|
||||||
topics: star.topics,
|
|
||||||
} as Partial<Star>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (data as unknown) as Stars;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateMd(data: string): Promise<string> {
|
export function generateMd(data: string): Promise<string> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
remark()
|
remark()
|
||||||
@@ -115,8 +39,7 @@ export function generateMd(data: string): Promise<string> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MARKDOWN_FILENAME: string =
|
export const MARKDOWN_FILENAME: string = core.getInput('output-filename');
|
||||||
core.getInput('output-filename') || 'README.md';
|
|
||||||
|
|
||||||
type File = {
|
type File = {
|
||||||
filename: string;
|
filename: string;
|
||||||
|
52
src/index.ts
52
src/index.ts
@@ -1,4 +1,6 @@
|
|||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
|
import { readdir, readFile } from 'fs/promises';
|
||||||
|
import ghStarFetch from 'gh-star-fetch';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
renderer,
|
renderer,
|
||||||
@@ -6,33 +8,38 @@ import {
|
|||||||
generateMd,
|
generateMd,
|
||||||
pushNewFiles,
|
pushNewFiles,
|
||||||
MARKDOWN_FILENAME,
|
MARKDOWN_FILENAME,
|
||||||
apiGetStar,
|
|
||||||
} from './helpers';
|
} from './helpers';
|
||||||
|
import MD_TEMPLATE from './template';
|
||||||
|
|
||||||
import type { SortedLanguageList, Stars, Star } from './types';
|
export async function main() {
|
||||||
|
// set default template
|
||||||
|
let template = MD_TEMPLATE;
|
||||||
|
|
||||||
export async function main(): Promise<any> {
|
// get template if found in the repo
|
||||||
const results: Stars = await apiGetStar();
|
const customTemplatePath = core.getInput('template-path');
|
||||||
|
console.log(`check if customTemplatePath: ${customTemplatePath} exists`);
|
||||||
|
try {
|
||||||
|
const dir = await readdir('./');
|
||||||
|
console.log(dir.join('\n'));
|
||||||
|
template = await readFile('TEMPLATE.ejs', 'utf8');
|
||||||
|
} catch {
|
||||||
|
console.log("Couldn't find template file, using default");
|
||||||
|
}
|
||||||
|
|
||||||
const sortedByLanguages = results.reduce(
|
const sortedByLanguages = await ghStarFetch({
|
||||||
(acc: SortedLanguageList, val: Star) => {
|
accessToken: core.getInput('api-token', { required: true }),
|
||||||
const language = val.language || 'generic';
|
compactByLanguage: true,
|
||||||
if (!acc[language]) {
|
|
||||||
acc[language] = [val];
|
|
||||||
} else {
|
|
||||||
acc[language].push(val);
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
|
|
||||||
const rendered = await renderer({
|
|
||||||
username: REPO_USERNAME,
|
|
||||||
stars: Object.entries(sortedByLanguages),
|
|
||||||
updatedAt: Date.now(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const rendered = await renderer(
|
||||||
|
{
|
||||||
|
username: REPO_USERNAME,
|
||||||
|
stars: Object.entries(sortedByLanguages),
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
},
|
||||||
|
template
|
||||||
|
);
|
||||||
|
|
||||||
const markdown: string = await generateMd(rendered);
|
const markdown: string = await generateMd(rendered);
|
||||||
|
|
||||||
await pushNewFiles([
|
await pushNewFiles([
|
||||||
@@ -57,8 +64,9 @@ export async function run(): Promise<any> {
|
|||||||
|
|
||||||
const catchAll = (info: any) => {
|
const catchAll = (info: any) => {
|
||||||
core.setFailed(`#catchAll: ${info}`);
|
core.setFailed(`#catchAll: ${info}`);
|
||||||
|
core.error(info);
|
||||||
};
|
};
|
||||||
process.on('unhandledRejection', catchAll);
|
process.on('unhandledRejection', catchAll);
|
||||||
process.on('uncaughtException', catchAll);
|
process.on('uncaughtException', catchAll);
|
||||||
|
|
||||||
run();
|
run().catch(core.error);
|
||||||
|
384
src/link.js
384
src/link.js
@@ -1,384 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2016 Jonas Hermsmeier
|
|
||||||
* https://github.com/jhermsmeier/node-http-link-header
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* istanbul ignore file */
|
|
||||||
'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;
|
|
@@ -5,16 +5,6 @@ import fs from 'fs';
|
|||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
sinon.replace(core, 'getInput', sinon.fake());
|
sinon.replace(core, 'getInput', sinon.fake());
|
||||||
|
|
||||||
import GithubApi from '../src/api';
|
|
||||||
const GithubApiFake = sinon.fake((rul) => ({
|
|
||||||
body: [],
|
|
||||||
headers: {
|
|
||||||
link:
|
|
||||||
'<https://api.github.com/user/5617452/starred?page=2>; rel="next", <https://api.github.com/user/5617452/starred?page=2>; rel="last"',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
sinon.replace(GithubApi, 'get', GithubApiFake);
|
|
||||||
|
|
||||||
import Git from '../src/git';
|
import Git from '../src/git';
|
||||||
const pull = sinon.fake();
|
const pull = sinon.fake();
|
||||||
sinon.replace(Git, 'pull', pull);
|
sinon.replace(Git, 'pull', pull);
|
||||||
@@ -31,30 +21,13 @@ const fsp = fs.promises;
|
|||||||
const writeFile = sinon.fake();
|
const writeFile = sinon.fake();
|
||||||
sinon.replace(fsp, 'writeFile', writeFile);
|
sinon.replace(fsp, 'writeFile', writeFile);
|
||||||
|
|
||||||
import {
|
import { renderer, generateMd, pushNewFiles } from '../src/helpers';
|
||||||
wait,
|
|
||||||
renderer,
|
|
||||||
apiGetStar,
|
|
||||||
generateMd,
|
|
||||||
pushNewFiles,
|
|
||||||
} from '../src/helpers';
|
|
||||||
|
|
||||||
test('wait should wait', async (t) => {
|
|
||||||
await wait(200);
|
|
||||||
t.pass();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('renderer should render', async (t) => {
|
test('renderer should render', async (t) => {
|
||||||
const output = await renderer({ variable: 123 }, 'Test: <%= variable %>');
|
const output = await renderer({ variable: 123 }, 'Test: <%= variable %>');
|
||||||
t.is(output, 'Test: 123');
|
t.is(output, 'Test: 123');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('apiGetStar', async (t) => {
|
|
||||||
let stars = await apiGetStar('url');
|
|
||||||
t.true(GithubApiFake.called);
|
|
||||||
t.true(Array.isArray(stars));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('generateMd should create TOC', async (t) => {
|
test('generateMd should create TOC', async (t) => {
|
||||||
const tpl = `# title
|
const tpl = `# title
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user