Skip to content

Commit

Permalink
feat(project-files): project files tool (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
dangreen committed Oct 4, 2022
1 parent e772c63 commit f7ef29a
Show file tree
Hide file tree
Showing 15 changed files with 1,116 additions and 452 deletions.
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
strict-peer-dependencies=false
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,20 @@ Scripts and configs for TrigenSoftware's projects.
|---------|---------|--------------|
| [`@trigen/scripts`](packages/scripts#readme) | [![NPM version][npm]][npm-url] | [![Dependencies status][deps]][deps-url] |
| [`@trigen/babel-preset`](packages/babel-preset#readme) | [![NPM version][babel-preset-npm]][babel-preset-npm-url] | [![Dependencies status][babel-preset-deps]][babel-preset-deps-url] |
| [`@trigen/project-files`](packages/project-files#readme) | [![NPM version][project-files-npm]][project-files-npm-url] | [![Dependencies status][project-files-deps]][project-files-deps-url] |
| [`@trigen/browserslist-config`](packages/browserslist-config#readme) | [![NPM version][browserslist-config-npm]][browserslist-config-npm-url] | |
| [`@trigen/eslint-config`](packages/eslint-config#readme) | [![NPM version][eslint-config-npm]][eslint-config-npm-url] | [![Dependencies status][eslint-config-deps]][eslint-config-deps-url] |
| [`@trigen/lint-package-json`](packages/lint-package-json#readme) | [![NPM version][lint-package-json-npm]][lint-package-json-npm-url] | [![Dependencies status][lint-package-json-deps]][lint-package-json-deps-url] |
| [`@trigen/npm-package-json-lint-config`](packages/npm-package-json-lint-config#readme) | [![NPM version][npm-package-json-lint-config-npm]][npm-package-json-lint-config-npm-url] | [![Dependencies status][npm-package-json-lint-config-deps]][npm-package-json-lint-config-deps-url] |

<!-- project-files -->

[project-files-npm]: https://img.shields.io/npm/v/%40trigen/project-files.svg
[project-files-npm-url]: https://www.npmjs.com/package/@trigen/project-files

[project-files-deps]: https://img.shields.io/librariesio/release/npm/@trigen/project-files
[project-files-deps-url]: https://libraries.io/npm/@trigen%2Fproject-files/tree

<!-- babel-preset -->

[babel-preset-npm]: https://img.shields.io/npm/v/%40trigen/babel-preset.svg
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@trigen/browserslist-config": "workspace:*",
"@trigen/eslint-config": "workspace:*",
"@trigen/lint-package-json": "workspace:*",
"@trigen/project-files": "workspace:*",
"@trigen/scripts": "workspace:*",
"@types/node": "^17.0.8",
"clean-publish": "^4.0.0",
Expand Down
6 changes: 6 additions & 0 deletions packages/project-files/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": [
"@trigen/eslint-config/esm",
"@trigen/eslint-config/jest"
]
}
41 changes: 41 additions & 0 deletions packages/project-files/files.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# GitHub
.github/ISSUE_TEMPLATE/config.yml
.github/ISSUE_TEMPLATE/bug-report.yml
.github/ISSUE_TEMPLATE/feature-request.yml
.github/ISSUE_TEMPLATE/question.yml
# GitHub Actions
.github/workflows/ci.yml
.github/workflows/commit.yml
.github/workflows/checks.yml
.github/workflows/release.yml
.github/workflows/update-snapshots.yml
.github/workflows/website.yml
# CI
.github/renovate.json
.clean-publish
.eslintrc.json
test/.eslintrc.json
.size-limit.json
.npmpackagejsonlintrc.json
jest.config.json
# TypeScript
test/tsconfig.json
tsconfig.json
# Build
env.d.ts
.browserslistrc
rollup.config.js
# Monorepo
lerna.json
pnpm-workspace.yaml
# Git Flow
.gitignore
.nano-staged.json
.simple-git-hooks.json
.commitlintrc.json
.czrc
# Basics
.npmrc
.editorconfig
LICENSE
README.md
38 changes: 38 additions & 0 deletions packages/project-files/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@trigen/project-files",
"type": "module",
"version": "8.0.0-alpha.9",
"description": "CLI tool for download snippets files.",
"author": "dangreen",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/TrigenSoftware/scripts.git",
"directory": "packages/project-files"
},
"bugs": {
"url": "https://github.com/TrigenSoftware/scripts/issues"
},
"keywords": [
"files",
"snippets"
],
"engines": {
"node": ">=14"
},
"bin": "./src/bin.js",
"exports": "./src/index.js",
"publishConfig": {
"access": "public",
"directory": "package"
},
"scripts": {
"prepublishOnly": "del ./package && clean-publish",
"postpublish": "del ./package"
},
"dependencies": {
"@octokit/rest": "^19.0.4",
"inquirer": "^8.2.0"
},
"readme": ""
}
7 changes: 7 additions & 0 deletions packages/project-files/sources.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
TrigenSoftware/scripts
TrigenSoftware/simple-github-release
TrigenSoftware/Argue
canvg/canvg
reactchartjs/react-chartjs-2
chartist-js/chartist
TrigenSoftware/flexis-srcset
129 changes: 129 additions & 0 deletions packages/project-files/src/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { dirname, join } from 'path'
import { Octokit } from '@octokit/rest'
import { writeFile } from './fs.js'

/**
* @typedef {import('./parse.js').SourceItem} Repo
*/

const OK = 200
const { GITHUB_TOKEN } = process.env
const client = new Octokit({
auth: GITHUB_TOKEN ? `token ${GITHUB_TOKEN}` : ''
})

/**
* @typedef RepoFile
* @property {string} path - File path.
* @property {string} name - File name.
* @property {string} sha - The SHA of the file.
* @property {string} download_url - Download url.
* @property {string} html_url - Html url.
*/

/**
* Get files list from repository.
* @param {Repo} repo
* @param {string} path
* @returns {Promise<RepoFile[]>} File from the repository.
*/
export async function getDirectory(repo, path) {
let status
let data

try {
({
status,
data
} = await client.request('GET /repos/{owner}/{repo}/contents/{path}', {
owner: repo.owner,
repo: repo.repo,
path: path === '.' ? '' : path
}))
} catch (err) {
return []
}

if (status !== OK || !data) {
return []
}

return data
}

/**
* Get files filtered by sha from repository.
* @param {Repo[]} repos
* @param {string[]} paths
* @returns {Promise<Map<string, RepoFile[]>>} Files from the repository.
*/
export async function getFiles(repos, paths) {
const dirs = new Set()
const tasks = []
const shas = []
const filesMap = new Map()
let task
let dir
let file
let sha
let files

paths.forEach((path) => {
dirs.add(dirname(path))
})

repos.forEach((repo) => {
dirs.forEach((dir) => {
tasks.push(getDirectory(repo, dir))
})
})

for (task of tasks) {
dir = await task

for (file of dir) {
if (paths.includes(file.path)) {
sha = file.sha + file.path

if (!shas.includes(sha)) {
shas.push(sha)

files = filesMap.get(file.path)

if (!files) {
files = []
filesMap.set(file.path, files)
}

files.push(file)
}
}
}
}

return filesMap
}

/**
* Download file.
* @param {RepoFile} file
* @param {boolean | string} write
* @returns {Promise<string>} File contents.
*/
export async function downloadFile(file, write) {
const { data } = await client.request(`GET ${file.download_url}`)

if (write) {
await writeFile(
join(
write === true
? '.'
: write,
file.path
),
data
)
}

return data
}
39 changes: 39 additions & 0 deletions packages/project-files/src/bin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env node
import { fileURLToPath } from 'url'
import { dirname, join } from 'path'
import {
readFile,
writeFile,
parseFilesList,
parseSourcesList,
getFiles,
downloadFile,
askFiles,
askSource
} from './index.js'

const { PROJECT_FILES_DIR } = process.env
const cwd = PROJECT_FILES_DIR ? join(process.cwd(), PROJECT_FILES_DIR) : process.cwd()
const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), '..')
const parseFilesListTask = parseFilesList(readFile(join(pkgRoot, 'files.txt'), 'utf8'))
const parseSourcesListTask = parseSourcesList(readFile(join(pkgRoot, 'sources.txt'), 'utf8'))
const [filesList, sourcesList] = await Promise.all([parseFilesListTask, parseSourcesListTask])
const selectedFiles = await askFiles(filesList)
const files = await getFiles(sourcesList, selectedFiles)
let file
let fileSources
const tasks = []

for (file of selectedFiles) {
fileSources = files.get(file)

if (!fileSources) {
tasks.push(writeFile(join(cwd, file), ''))
} else if (fileSources.length === 1) {
tasks.push(downloadFile(fileSources[0], cwd))
} else {
tasks.push(downloadFile(await askSource(fileSources), cwd))
}
}

await Promise.all(tasks)
26 changes: 26 additions & 0 deletions packages/project-files/src/fs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { dirname } from 'path'
import { promises } from 'fs'

const { readFile, writeFile: wf, mkdir } = promises

export { readFile }

/**
* Write file.
* @param {string} path
* @param {string} contents
* @returns {Promise<void>}
*/
export async function writeFile(path, contents) {
const dir = dirname(path)

try {
await mkdir(dir, {
recursive: true
})
} catch (err) {
/* Ignore error */
}

return wf(path, contents)
}
4 changes: 4 additions & 0 deletions packages/project-files/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './parse.js'
export * from './api.js'
export * from './ui.js'
export * from './fs.js'
59 changes: 59 additions & 0 deletions packages/project-files/src/parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* @typedef FileItem
* @property {string} type - File type description
* @property {string} path - File path
*/

/**
* Parse files list file.
* @param {string | Promise<string>} contents
* @returns {Promise<FileItem[]>} Parsed files info.
*/
export async function parseFilesList(contents) {
const text = await contents
const files = []
let type

text.split('\n').forEach((line) => {
if (line[0] === '#') {
type = line.slice(2)
} else if (line) {
files.push({
type,
path: line
})
}
})

return files
}

/**
* @typedef SourceItem
* @property {string} owner - Source repository owner
* @property {string} repo - Source repository name
*/

/**
* Parse sources list file.
* @param {string | Promise<string>} contents
* @returns {Promise<SourceItem[]>} Parsed sources info.
*/
export async function parseSourcesList(contents) {
const text = await contents
const sources = []
let owner
let repo

text.split('\n').forEach((line) => {
if (line) {
[owner, repo] = line.split('/')
sources.push({
owner,
repo
})
}
})

return sources
}

0 comments on commit f7ef29a

Please sign in to comment.