Update logging

This commit is contained in:
Bret Comnes 2020-02-12 16:52:32 -07:00
parent e49d0b8d3a
commit 383cb4ca53
No known key found for this signature in database
GPG Key ID: 3705F4634DC3A1AC
36 changed files with 824 additions and 597 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ sandbox.js
.nyc_output
config.json
public
node_modules

View File

@ -2,25 +2,100 @@ const core = require('@actions/core')
// const github = require('@actions/github')
const Neocities = require('async-neocities')
const path = require('path')
const exec = require('child_process').exec
const prettyTime = require('pretty-time')
const prettyBytes = require('pretty-bytes')
async function doDeploy () {
const token = core.getInput('api-token')
const distDir = path.join(process.cwd(), core.getInput('dist-dir'))
const cleanup = core.getInput('cleanup')
const time = (new Date()).toTimeString()
core.setOutput('time', time)
const client = new Neocities(token)
return client.deploy(distDir, {
const finalStats = await client.deploy(distDir, {
cleanup,
statusCb: console.log
statsCb: statsHandler({ cleanup, distDir })
})
return finalStats
}
doDeploy().then(() => {}).catch(err => {
console.error(err)
doDeploy().then((finalStats) => {}).catch(err => {
core.setFailed(err.message)
})
function statsHandler (opts = {}) {
return (stats) => {
switch (stats.stage) {
case 'inspecting': {
switch (stats.status) {
case 'start': {
core.startGroup('Inspecting')
console.log(`Inspecting local (${opts.distDir}) and remote files...`)
break
}
case 'progress': {
break
}
case 'stop': {
console.log(`Done inspecting local and remote files in ${prettyTime([0, stats.timer.elapsed])}`)
const { tasks: { localScan, remoteScan } } = stats
console.log(`Scanned ${localScan.numberOfFiles} local files (${prettyBytes(localScan.totalSize)}) in ${prettyTime([0, localScan.timer.elapsed])}`)
console.log(`Scanned ${remoteScan.numberOfFiles} remote files (${prettyBytes(remoteScan.totalSize)}) in ${prettyTime([0, remoteScan.timer.elapsed])}`)
core.endGroup()
break
}
}
break
}
case 'diffing': {
switch (stats.status) {
case 'start': {
core.startGroup('Diffing files')
console.log('Diffing local and remote files...')
break
}
case 'progress': {
// No progress on diffing
break
}
case 'stop': {
const { tasks: { diffing } } = stats
console.log(`Done diffing local and remote files in ${prettyTime([0, stats.timer.elapsed])}`)
console.log(`${diffing.uploadCount} files to upload`)
console.log(`${diffing.deleteCount} ` + opts.cleanup ? 'files to delete' : 'orphaned files')
console.log(`${diffing.skipCoount} files to skip`)
core.endGroup()
break
}
}
break
}
case 'applying': {
switch (stats.status) {
case 'start': {
core.startGroup('Applying diff')
console.log('Uploading changes' + opts.cleanup ? ' and deleting orphaned files...' : '...')
break
}
case 'progress': {
break
}
case 'stop': {
const { tasks: { uploadFiles, deleteFiles, skippedFiles } } = stats
console.log('Done uploading changes' + opts.cleanup ? ' and deleting orphaned files' : '' + ` in ${prettyTime([0, stats.timer.elapsed])}`)
console.log(`Average upload speed: ${prettyBytes(uploadFiles.speed)}/s`)
if (opts.cleanup) console.log(`Average delete speed: ${prettyBytes(deleteFiles.speed)}/s`)
console.log(`Skipped ${skippedFiles.count} files (${prettyBytes(skippedFiles.size)})`)
core.endGroup()
break
}
}
break
}
default: {
console.log(stats)
}
}
}
}

View File

@ -1,172 +0,0 @@
const assert = require('nanoassert')
const fetch = require('node-fetch')
const { URL } = require('url')
const qs = require('qs')
const os = require('os')
const path = require('path')
const { createReadStream } = require('fs')
const FormData = require('form-data')
const { handleResponse } = require('fetch-errors')
const afw = require('async-folder-walker')
const pkg = require('../package.json')
const { neocitiesLocalDiff } = require('./folder-diff')
const defaultURL = 'https://neocities.org'
class NeocitiesAPIClient {
static getKey (sitename, password, opts) {
assert(sitename, 'must pass sitename as first arg')
assert(typeof sitename === 'string', 'user arg must be a string')
assert(password, 'must pass a password as the second arg')
assert(typeof password, 'password arg must be a string')
opts = Object.assign({
baseURL: defaultURL
}, opts)
const baseURL = opts.baseURL
delete opts.baseURL
const url = new URL('/api/key', baseURL)
url.username = sitename
url.password = password
return fetch(url, opts)
}
constructor (apiKey, opts) {
assert(apiKey, 'must pass apiKey as first argument')
assert(typeof apiKey === 'string', 'apiKey must be a string')
opts = Object.assign({
url: defaultURL
})
this.opts = opts
this.url = opts.url
this.apiKey = apiKey
}
get defaultHeaders () {
return {
Authorization: `Bearer ${this.apiKey}`,
Accept: 'application/json',
'User-Agent': `deploy-to-neocities/${pkg.version} (${os.type()})`
}
}
/**
* Generic get request to neocities
*/
get (endpoint, quieries, opts) {
assert(endpoint, 'must pass endpoint as first argument')
opts = Object.assign({
method: 'GET'
}, opts)
opts.headers = Object.assign({}, this.defaultHeaders, opts.headers)
let path = `/api/${endpoint}`
if (quieries) path += `?${qs.stringify(quieries)}`
const url = new URL(path, this.url)
return fetch(url, opts)
}
/**
* Generic post request to neocities
*/
post (endpoint, formEntries, opts) {
assert(endpoint, 'must pass endpoint as first argument')
const form = new FormData()
opts = Object.assign({
method: 'POST',
body: form
}, opts)
for (const { name, value } of formEntries) {
form.append(name, value)
}
opts.headers = Object.assign(
{},
this.defaultHeaders,
form.getHeaders(),
opts.headers)
const url = new URL(`/api/${endpoint}`, this.url)
return fetch(url, opts)
}
/**
* Upload files to neocities
*/
upload (files) {
const formEntries = files.map(({ name, path }) => {
return {
name,
value: createReadStream(path)
}
})
return this.post('upload', formEntries).then(handleResponse)
}
/**
* delete files from your website
*/
delete (filenames) {
assert(filenames, 'filenames is a required first argument')
assert(Array.isArray(filenames), 'filenames argument must be an array of file paths in your website')
const formEntries = filenames.map(file => ({
name: 'filenames[]',
value: file
}))
return this.post('delete', formEntries).then(handleResponse)
}
list (queries) {
// args.path: Path to list
return this.get('list', queries).then(handleResponse)
}
/**
* info returns info on your site, or optionally on a sitename querystrign
* @param {Object} args Querystring arguments to include (e.g. sitename)
* @return {Promise} Fetch request promise
*/
info (queries) {
// args.sitename: sitename to get info on
return this.get('info', queries).then(handleResponse)
}
/**
* Deploy a folder to neocities, skipping already uploaded files and optionally cleaning orphaned files.
* @param {String} folder The path of the folder to deploy.
* @param {Object} opts Options object.
* @param {Boolean} opts.cleanup Boolean to delete orphaned files nor not. Defaults to false.
* @param {Boolean} opts.statsCb Get access to stat info before uploading is complete.
* @return {Promise} Promise containing stats about the deploy
*/
async deploy (folder, opts) {
opts = {
cleanup: false, // delete remote orphaned files
statsCb: () => {},
...opts
}
const [localFiles, remoteFiles] = Promise.all([
afw.allFiles(path.join(folder), { shaper: f => f }),
this.list()
])
const { filesToUpload, filesToDelete, filesSkipped } = await neocitiesLocalDiff(remoteFiles, localFiles)
opts.statsCb({ filesToUpload, filesToDelete, filesSkipped })
const work = [this.upload(filesToUpload)]
if (opts.cleanup) work.push(this.delete(filesToDelete))
await work
return { filesToUpload, filesToDelete, filesSkipped }
}
}
module.exports = { NeocitiesAPIClient }

View File

@ -1,159 +0,0 @@
const crypto = require('crypto')
const util = require('util')
const fs = require('fs')
const ppump = util.promisify(require('pump'))
/**
* neocitiesLocalDiff returns an array of files to delete and update and some useful stats.
*/
async function neocitiesLocalDiff (neocitiesFiles, localListing, opts = {}) {
opts = {
...opts
}
const localIndex = {}
const ncIndex = {}
const neoCitiesFiltered = neocitiesFiles.filter(f => !f.is_directory)
neoCitiesFiltered.forEach(f => { ncIndex[f.path] = f }) // index
const ncFiles = new Set(neoCitiesFiltered.map(f => f.path)) // shape
const localListingFiltered = localListing.filter(f => !f.stat.isDirectory()) // files only
localListingFiltered.forEach(f => { localIndex[f.relname] = f }) // index
// TODO: convert windows to unix paths
const localFiles = new Set(localListingFiltered.map(f => f.relname)) // shape
const filesToAdd = difference(localFiles, ncFiles)
const filesToDelete = difference(ncFiles, localFiles)
const maybeUpdate = intersection(localFiles, ncFiles)
const skipped = new Set()
for (const p of maybeUpdate) {
const local = localIndex[p]
const remote = ncIndex[p]
if (local.stat.size !== remote.size) { filesToAdd.add(p); continue }
const localSha1 = await sha1FromPath(local.filepath)
if (localSha1 !== remote.sha1_hash) { filesToAdd.add(p); continue }
skipped.add(p)
}
return {
filesToUpload: Array.from(filesToAdd).map(p => ({
name: localIndex[p].relname,
path: localIndex[p].filepath
})),
filesToDelete: Array.from(filesToDelete).map(p => ncIndex[p].path),
filesSkipped: Array.from(skipped).map(p => localIndex[p])
}
}
module.exports = {
neocitiesLocalDiff
}
/**
* sha1FromPath returns a sha1 hex from a path
* @param {String} p string of the path of the file to hash
* @return {Promise<String>} the hex representation of the sha1
*/
async function sha1FromPath (p) {
const rs = fs.createReadStream(p)
const hash = crypto.createHash('sha1')
await ppump(rs, hash)
return hash.digest('hex')
}
// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#Implementing_basic_set_operations
/**
* difference betwen setA and setB
* @param {Set} setA LHS set
* @param {Set} setB RHS set
* @return {Set} The difference Set
*/
function difference (setA, setB) {
const _difference = new Set(setA)
for (const elem of setB) {
_difference.delete(elem)
}
return _difference
}
function intersection (setA, setB) {
const _intersection = new Set()
for (const elem of setB) {
if (setA.has(elem)) {
_intersection.add(elem)
}
}
return _intersection
}
// [
// {
// path: 'img',
// is_directory: true,
// updated_at: 'Thu, 21 Nov 2019 04:06:17 -0000'
// },
// {
// path: 'index.html',
// is_directory: false,
// size: 1094,
// updated_at: 'Mon, 11 Nov 2019 22:23:16 -0000',
// sha1_hash: '7f15617e87d83218223662340f4052d9bb9d096d'
// },
// {
// path: 'neocities.png',
// is_directory: false,
// size: 13232,
// updated_at: 'Mon, 11 Nov 2019 22:23:16 -0000',
// sha1_hash: 'fd2ee41b1922a39a716cacb88c323d613b0955e4'
// },
// {
// path: 'not_found.html',
// is_directory: false,
// size: 347,
// updated_at: 'Mon, 11 Nov 2019 22:23:16 -0000',
// sha1_hash: 'd7f004e9d3b2eaaa8827f741356f1122dc9eb030'
// },
// {
// path: 'style.css',
// is_directory: false,
// size: 298,
// updated_at: 'Mon, 11 Nov 2019 22:23:16 -0000',
// sha1_hash: 'e516457acdb0d00710ab62cc257109ef67209ce8'
// }
// ]
// [{
// root: '/Users/bret/repos/async-folder-walker/fixtures',
// filepath: '/Users/bret/repos/async-folder-walker/fixtures/sub-folder/sub-sub-folder',
// stat: Stats {
// dev: 16777220,
// mode: 16877,
// nlink: 3,
// uid: 501,
// gid: 20,
// rdev: 0,
// blksize: 4096,
// ino: 30244023,
// size: 96,
// blocks: 0,
// atimeMs: 1574381262779.8396,
// mtimeMs: 1574380914743.5474,
// ctimeMs: 1574380914743.5474,
// birthtimeMs: 1574380905232.5996,
// atime: 2019-11-22T00:07:42.780Z,
// mtime: 2019-11-22T00:01:54.744Z,
// ctime: 2019-11-22T00:01:54.744Z,
// birthtime: 2019-11-22T00:01:45.233Z
// },
// relname: 'sub-folder/sub-sub-folder',
// basename: 'sub-sub-folder'
// }]

View File

@ -1,65 +0,0 @@
const tap = require('tap')
const afw = require('async-folder-walker')
const path = require('path')
const { neocitiesLocalDiff } = require('./folder-diff')
const remoteFiles = [
{
path: 'img',
is_directory: true,
updated_at: 'Thu, 21 Nov 2019 04:06:17 -0000'
},
{
path: 'index.html',
is_directory: false,
size: 1094,
updated_at: 'Mon, 11 Nov 2019 22:23:16 -0000',
sha1_hash: '7f15617e87d83218223662340f4052d9bb9d096d'
},
{
path: 'neocities.png',
is_directory: false,
size: 13232,
updated_at: 'Mon, 11 Nov 2019 22:23:16 -0000',
sha1_hash: 'fd2ee41b1922a39a716cacb88c323d613b0955e4'
},
{
path: 'not_found.html',
is_directory: false,
size: 347,
updated_at: 'Mon, 11 Nov 2019 22:23:16 -0000',
sha1_hash: 'd7f004e9d3b2eaaa8827f741356f1122dc9eb030'
},
{
path: 'style.css',
is_directory: false,
size: 298,
updated_at: 'Mon, 11 Nov 2019 22:23:16 -0000',
sha1_hash: 'e516457acdb0d00710ab62cc257109ef67209ce8'
}
]
tap.test('test differ', async t => {
const localFiles = await afw.allFiles(path.join(__dirname, '../fixtures'), {
shaper: f => f
})
const { filesToUpload, filesToDelete, filesSkipped } = await neocitiesLocalDiff(remoteFiles, localFiles)
t.true(['tootzzz.png', 'toot.gif', 'cat.png'].every(path => {
const found = filesToUpload.find(ftu => ftu.name === path)
t.ok(found.path && found.name, 'each file to upload has a name and path')
return found
}), 'every file to upload is included')
t.deepEqual(filesToDelete, [
'index.html',
'not_found.html',
'style.css'
], 'filesToDelete returned correctly')
t.true(['neocities.png'].every(path => {
const found = filesSkipped.find(fs => fs.relname === path)
return found
}), 'every file skipped is included')
})

2
node_modules/@types/node/README.md generated vendored
View File

@ -8,7 +8,7 @@ This package contains type definitions for Node.js (http://nodejs.org/).
Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node.
### Additional Details
* Last updated: Fri, 31 Jan 2020 21:34:20 GMT
* Last updated: Tue, 11 Feb 2020 17:16:28 GMT
* Dependencies: none
* Global values: `Buffer`, `Symbol`, `__dirname`, `__filename`, `clearImmediate`, `clearInterval`, `clearTimeout`, `console`, `exports`, `global`, `module`, `process`, `queueMicrotask`, `require`, `setImmediate`, `setInterval`, `setTimeout`

View File

@ -239,6 +239,12 @@ declare class Buffer extends Uint8Array {
*/
static from(data: number[]): Buffer;
static from(data: Uint8Array): Buffer;
/**
* Creates a new buffer containing the coerced value of an object
* A `TypeError` will be thrown if {obj} has not mentioned methods or is not of other type appropriate for `Buffer.from()` variants.
* @param obj An object supporting `Symbol.toPrimitive` or `valueOf()`.
*/
static from(obj: { valueOf(): string | object } | { [Symbol.toPrimitive](hint: 'string'): string }, byteOffset?: number, length?: number): Buffer;
/**
* Creates a new Buffer containing the given JavaScript string {str}.
* If provided, the {encoding} parameter identifies the character encoding.

View File

@ -1,8 +1,8 @@
{
"_from": "@types/node@>= 8",
"_id": "@types/node@13.7.0",
"_id": "@types/node@13.7.1",
"_inBundle": false,
"_integrity": "sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ==",
"_integrity": "sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA==",
"_location": "/@types/node",
"_phantomChildren": {},
"_requested": {
@ -20,8 +20,8 @@
"/@octokit/types",
"/@types/glob"
],
"_resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.0.tgz",
"_shasum": "b417deda18cf8400f278733499ad5547ed1abec4",
"_resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.1.tgz",
"_shasum": "238eb34a66431b71d2aaddeaa7db166f25971a0d",
"_spec": "@types/node@>= 8",
"_where": "/Users/bret/repos/deploy-to-neocities/node_modules/@octokit/types",
"bugs": {
@ -209,7 +209,7 @@
"scripts": {},
"typeScriptVersion": "2.8",
"types": "index.d.ts",
"typesPublisherContentHash": "b0cd8dccd6d2eca8bb1de5a05bebf13796984567eb809f63164972122c4ddbaf",
"typesPublisherContentHash": "8b99aa0031fac941d520282519c47b0255109858a20251313b1210c28769f463",
"typesVersions": {
">=3.5.0-0": {
"*": [
@ -217,5 +217,5 @@
]
}
},
"version": "13.7.0"
"version": "13.7.1"
}

View File

@ -1,25 +0,0 @@
name: tests
on: [push]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
node: [12]
steps:
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- name: npm install && npm test
run: |
npm i
npm test
env:
CI: true

View File

@ -1,8 +1,74 @@
# async-neocities Change Log
# Changelog
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.0.1 - 2020-02-10
* wip release
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
## [v1.0.0](https://github.com/bcomnes/async-neocities/compare/v0.0.10...v1.0.0) - 2020-02-12
### Commits
- feat: progress API sketch [`be8b9ec`](https://github.com/bcomnes/async-neocities/commit/be8b9ec062b5ea23157a6a841c9d66d03a85a8ca)
- docs: update README [`ec4f5f1`](https://github.com/bcomnes/async-neocities/commit/ec4f5f154b690dba0814ec0955fee674e8e94692)
- CHANGELOG [`c9b64ed`](https://github.com/bcomnes/async-neocities/commit/c9b64edd4d3db025adc737982477ce0d760f3254)
## [v0.0.10](https://github.com/bcomnes/async-neocities/compare/v0.0.9...v0.0.10) - 2020-02-10
### Commits
- dont do work unless there is work [`616a306`](https://github.com/bcomnes/async-neocities/commit/616a306ba3ca091da11c9c85bae2b07cb0b2768e)
## [v0.0.9](https://github.com/bcomnes/async-neocities/compare/v0.0.8...v0.0.9) - 2020-02-10
### Commits
- Use stream ctor [`e8201a0`](https://github.com/bcomnes/async-neocities/commit/e8201a053950848962a220b83ffa1a97ebab6e70)
## [v0.0.8](https://github.com/bcomnes/async-neocities/compare/v0.0.7...v0.0.8) - 2020-02-10
### Commits
- Fix more bugs [`95da7b7`](https://github.com/bcomnes/async-neocities/commit/95da7b7218082ab51c1463851f87428dc0c501ac)
## [v0.0.7](https://github.com/bcomnes/async-neocities/compare/v0.0.6...v0.0.7) - 2020-02-10
### Commits
- bugs [`71ead78`](https://github.com/bcomnes/async-neocities/commit/71ead78e0f48f619816b3ae3ea8154e8301c77ac)
## [v0.0.6](https://github.com/bcomnes/async-neocities/compare/v0.0.5...v0.0.6) - 2020-02-10
### Commits
- bugs [`c1d9973`](https://github.com/bcomnes/async-neocities/commit/c1d9973afef3abd7d6edfc5a6ae1c9d37f6cb34d)
## [v0.0.5](https://github.com/bcomnes/async-neocities/compare/v0.0.4...v0.0.5) - 2020-02-10
### Commits
- bugs [`e542111`](https://github.com/bcomnes/async-neocities/commit/e542111f3404ab923be3490e62ba16b4f6b66a70)
## [v0.0.4](https://github.com/bcomnes/async-neocities/compare/v0.0.3...v0.0.4) - 2020-02-10
### Commits
- bump version [`a3da5f7`](https://github.com/bcomnes/async-neocities/commit/a3da5f77cda15fb3e9ec5861b588f616d8b0055c)
## [v0.0.3](https://github.com/bcomnes/async-neocities/compare/v0.0.2...v0.0.3) - 2020-02-10
### Commits
- tmp releases [`16a6db4`](https://github.com/bcomnes/async-neocities/commit/16a6db49a06bebef89007b94e03dd34e6d17b298)
## [v0.0.2](https://github.com/bcomnes/async-neocities/compare/v0.0.1...v0.0.2) - 2020-02-10
## v0.0.1 - 2020-02-10
### Commits
- Init [`bb055ae`](https://github.com/bcomnes/async-neocities/commit/bb055ae8e76b0344acc929e8ffd3974d19144001)
- fix tests [`c294b52`](https://github.com/bcomnes/async-neocities/commit/c294b528a64a50638c4374a8782b177fe3634eb2)
- Init [`9ec8fb5`](https://github.com/bcomnes/async-neocities/commit/9ec8fb557ebf8578c9eb07dedffcb1b7eedbd3e6)

View File

@ -1,4 +1,4 @@
# Code of conduct
- This repo is governed as a dictatorship starting with the originator of the project.
- No malevolence tolerated whatsoever.
- This is a malevolence free zone.

View File

@ -1,5 +1,17 @@
# Contributing
## Releasing
Changelog, and releasing is autmated with npm scripts. To create a release:
- Ensure a clean working git workspace.
- Run `npm version {patch,minor,major}`.
- This wills update the version number and generate the changelog.
- Run `npm publish`.
- This will push your local git branch and tags to the default remote, perform a [gh-release](https://ghub.io/gh-release), and create an npm publication.
## Guidelines
- Patches, ideas and changes welcome.
- Fixes almost always welcome.
- Features sometimes welcome.

View File

@ -1,9 +1,11 @@
# async-neocities
[![Actions Status](https://github.com/bcomnes/async-neocities/workflows/tests/badge.svg)](https://github.com/bcomnes/async-neocities/actions)
WIP - nothing to see here
An api client for [neocities][nc] with an async/promise API and an efficient deploy algorithm.
```
<center><img src="logo.jpg"></center>
```console
npm install async-neocities
```
@ -28,6 +30,216 @@ deploySite.then(info => { console.log('done deploying site!') })
.catch(e => { throw e })
```
## API
### `Neocities = require('async-neocities')`
Import the Neocities API client.
### `apiKey = await Neocities.getKey(sitename, password, [opts])`
Static class method that will get an API Key from a sitename and password.
`opts` include:
```js
{
url: 'https://neocities.org' // Base URL to use for requests
}
```
### `client = new Neocities(apiKey, [opts])`
Create a new API client for a given API key.
`opts` include:
```js
{
url: 'https://neocities.org' // Base URL to use for requests
}
```
### `response = await client.upload(files)`
Pass an array of objects with the `{ name, path }` pair to upload these files to neocities, where `name` is desired remote unix path on neocities and `path` is the local path on disk in whichever format the local operating system desires.
A successful `response`:
```js
{
result: 'success',
message: 'your file(s) have been successfully uploaded'
}
```
### `response = await client.delete(filenames)`
Pass an array of path strings to delete on neocities. The path strings should be the unix style path of the file you want to delete.
A successful `response`:
```js
{ result: 'success', message: 'file(s) have been deleted' }
```
### `response = await client.list([queries])`
Get a list of files for your site. The optional `queries` object is passed through [qs][qs] and added to the request.
Available queries:
```js
{
path // list the contents of a subdirectory on neocities
}
```
Example `responses`:
```json
{
"result": "success",
"files": [
{
"path": "index.html",
"is_directory": false,
"size": 1023,
"updated_at": "Sat, 13 Feb 2016 03:04:00 -0000",
"sha1_hash": "c8aac06f343c962a24a7eb111aad739ff48b7fb1"
},
{
"path": "not_found.html",
"is_directory": false,
"size": 271,
"updated_at": "Sat, 13 Feb 2016 03:04:00 -0000",
"sha1_hash": "cfdf0bda2557c322be78302da23c32fec72ffc0b"
},
{
"path": "images",
"is_directory": true,
"updated_at": "Sat, 13 Feb 2016 03:04:00 -0000"
},
{
"path": "images/cat.png",
"is_directory": false,
"size": 16793,
"updated_at": "Sat, 13 Feb 2016 03:04:00 -0000",
"sha1_hash": "41fe08fc0dd44e79f799d03ece903e62be25dc7d"
}
]
}
```
With the `path` query:
```json
{
"result": "success",
"files": [
{
"path": "images/cat.png",
"is_directory": false,
"size": 16793,
"updated_at": "Sat, 13 Feb 2016 03:04:00 -0000",
"sha1_hash": "41fe08fc0dd44e79f799d03ece903e62be25dc7d"
}
]
}
```
### `response = await client.info([queries])`
Get info about your or other sites. The optional `queries` object is passed through [qs][qs] and added to the request.
Available queries:
```js
{
sitename // get info on a given sitename
}
```
Example `responses`:
```json
{
"result": "success",
"info": {
"sitename": "youpi",
"hits": 5072,
"created_at": "Sat, 29 Jun 2013 10:11:38 +0000",
"last_updated": "Tue, 23 Jul 2013 20:04:03 +0000",
"domain": null,
"tags": []
}
}
```
### `stats = await client.deploy(directory, [opts])`
Deploy a path to a `directory`, efficiently only uploading missing and changed files. Files are determined to be different by size, and sha1 hash, if the size is the same.
`opts` include:
```js
{
cleanup: false // delete orphaned files on neocities that are not in the `directory`
statsCb: () => {} // WIP progress API
}
```
The return value of this method is subject to change.
### `client.get(endpoint, [quieries], [opts])`
Low level GET request to a given `endpoint`.
**NOTE**: The `/api/` prefix is automatically added: `/api/${endpoint}` so that must be omitted from `endpoint.
The optional `queries` object is stringified to a querystring using [`qs`][qs]a and added to the request.
`opts` includes:
```js
{
method: 'GET',
headers: { ...client.defaultHeaders, ...opts.headers },
}
```
Note, that `opts` is passed internally to [`node-fetch`][nf] and you can include any options that work for that client here.
### `client.post(endpoint, formEntries, [opts])`
Low level POST request to a given `endpoint`.
**NOTE**: The `/api/` prefix is automatically adeded: `/api/${endpoint}` so that must be omitted from `endpoint.
Pass a `formEntries` array or iterator containing objects with `{name, value}` pairs to be sent with the POST request as [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData). The [form-datat][fd] module is used internally.
`opts` include:
```js
{
method: 'POST',
body: new FormData(), // Don't override this.
headers: { ...client.defafultHeaders, ...formHeaders, opts.headers }
}
```
Note, that `opts` is passed internally to [`node-fetch`][nf] and you can include any options that work for that client here.
## See also
- [Neocities API docs](https://neocities.org/api)
- [Official Node.js API client](https://github.com/neocities/neocities-node)
## License
MIT
[qs]: https://ghub.io/qs
[nf]: https://ghub.io/node-fetch
[fd]: https://ghub.io/form-data
[nc]: https://neocities.org

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

312
node_modules/async-neocities/index.js generated vendored
View File

@ -1,18 +1,41 @@
const { handleResponse } = require('fetch-errors')
const { createReadStream } = require('fs')
const afw = require('async-folder-walker')
const FormData = require('form-data')
const assert = require('nanoassert')
const fetch = require('node-fetch')
const { URL } = require('url')
const qs = require('qs')
const os = require('os')
const { createReadStream } = require('fs')
const FormData = require('form-data')
const { handleResponse } = require('fetch-errors')
const afw = require('async-folder-walker')
const pkg = require('./package.json')
const { neocitiesLocalDiff } = require('./lib/folder-diff')
const pkg = require('./package.json')
const SimpleTimer = require('./lib/timer')
const { getStreamLength, meterStream } = require('./lib/stream-meter')
const defaultURL = 'https://neocities.org'
// Progress API constants
const START = 'start'
const PROGRESS = 'progress' // progress updates
const STOP = 'stop'
// Progress stages
const INSPECTING = 'inspecting'
const DIFFING = 'diffing'
const APPLYING = 'applying'
/**
* NeocitiesAPIClient class representing a neocities api client.
*/
class NeocitiesAPIClient {
/**
* getKey returns an apiKey from a sitename and password.
* @param {String} sitename username/sitename to log into.
* @param {String} password password to log in with.
* @param {Object} [opts] Options object.
* @param {Object} [opts.url=https://neocities.org] Base URL to request to.
* @return {Promise<String>} An api key for the sitename..
*/
static getKey (sitename, password, opts) {
assert(sitename, 'must pass sitename as first arg')
assert(typeof sitename === 'string', 'user arg must be a string')
@ -20,11 +43,11 @@ class NeocitiesAPIClient {
assert(typeof password, 'password arg must be a string')
opts = Object.assign({
baseURL: defaultURL
url: defaultURL
}, opts)
const baseURL = opts.baseURL
delete opts.baseURL
const baseURL = opts.url
delete opts.url
const url = new URL('/api/key', baseURL)
url.username = sitename
@ -32,6 +55,13 @@ class NeocitiesAPIClient {
return fetch(url, opts)
}
/**
* Create an async-neocities api client.
* @param {string} apiKey An apiKey to make requests with.
* @param {Object} [opts] Options object.
* @param {Object} [opts.url=https://neocities.org] Base URL to make requests to.
* @return {Object} An api client instance.
*/
constructor (apiKey, opts) {
assert(apiKey, 'must pass apiKey as first argument')
assert(typeof apiKey === 'string', 'apiKey must be a string')
@ -39,7 +69,6 @@ class NeocitiesAPIClient {
url: defaultURL
})
this.opts = opts
this.url = opts.url
this.apiKey = apiKey
}
@ -48,12 +77,18 @@ class NeocitiesAPIClient {
return {
Authorization: `Bearer ${this.apiKey}`,
Accept: 'application/json',
'User-Agent': `deploy-to-neocities/${pkg.version} (${os.type()})`
'User-Agent': `async-neocities/${pkg.version} (${os.type()})`
}
}
/**
* Generic get request to neocities
* Generic GET request to neocities.
* @param {String} endpoint An endpoint path to GET request.
* @param {Object} [quieries] An object that gets added to the request in the form of a query string.
* @param {Object} [opts] Options object.
* @param {String} [opts.method=GET] The http method to use.
* @param {Object} [opts.headers] Headers to include in the request.
* @return {Object} The parsed JSON from the request response.
*/
get (endpoint, quieries, opts) {
assert(endpoint, 'must pass endpoint as first argument')
@ -70,19 +105,46 @@ class NeocitiesAPIClient {
}
/**
* Generic post request to neocities
* Low level POST request to neocities with FormData.
* @param {String} endpoint The endpoint to make the request to.
* @param {Array.<{name: String, value: String}>} formEntries Array of form entries.
* @param {Object} [opts] Options object.
* @param {String} [opts.method=POST] HTTP Method.
* @param {Object} [opts.headers] Additional headers to send.
* @return {Object} The parsed JSON response object.
*/
post (endpoint, formEntries, opts) {
async post (endpoint, formEntries, opts) {
assert(endpoint, 'must pass endpoint as first argument')
const form = new FormData()
assert(formEntries, 'must pass formEntries as second argument')
function createForm () {
const form = new FormData()
for (const { name, value } of formEntries) {
form.append(name, value)
}
return form
}
opts = Object.assign({
method: 'POST',
body: form
statsCb: () => {}
}, opts)
const statsCb = opts.statsCb
delete opts.statsCb
for (const { name, value } of formEntries) {
form.append(name, value)
const stats = {
totalBytes: await getStreamLength(createForm()),
bytesWritten: 0
}
statsCb(stats)
const form = createForm()
opts.body = meterStream(form, bytesRead => {
stats.bytesWritten = bytesRead
statsCb(stats)
})
opts.headers = Object.assign(
{},
@ -97,7 +159,11 @@ class NeocitiesAPIClient {
/**
* Upload files to neocities
*/
upload (files) {
upload (files, opts = {}) {
opts = {
statsCb: () => {},
...opts
}
const formEntries = files.map(({ name, path }) => {
const streamCtor = (next) => next(createReadStream(path))
streamCtor.path = path
@ -107,22 +173,26 @@ class NeocitiesAPIClient {
}
})
return this.post('upload', formEntries).then(handleResponse)
return this.post('upload', formEntries, { statsCb: opts.statsCb }).then(handleResponse)
}
/**
* delete files from your website
*/
delete (filenames) {
delete (filenames, opts = {}) {
assert(filenames, 'filenames is a required first argument')
assert(Array.isArray(filenames), 'filenames argument must be an array of file paths in your website')
opts = {
statsCb: () => {},
...opts
}
const formEntries = filenames.map(file => ({
name: 'filenames[]',
value: file
}))
return this.post('delete', formEntries).then(handleResponse)
return this.post('delete', formEntries, { statsCb: opts.statsCb }).then(handleResponse)
}
list (queries) {
@ -141,34 +211,210 @@ class NeocitiesAPIClient {
}
/**
* Deploy a folder to neocities, skipping already uploaded files and optionally cleaning orphaned files.
* @param {String} folder The path of the folder to deploy.
* Deploy a directory to neocities, skipping already uploaded files and optionally cleaning orphaned files.
* @param {String} directory The path of the directory to deploy.
* @param {Object} opts Options object.
* @param {Boolean} opts.cleanup Boolean to delete orphaned files nor not. Defaults to false.
* @param {Boolean} opts.statsCb Get access to stat info before uploading is complete.
* @return {Promise} Promise containing stats about the deploy
*/
async deploy (folder, opts) {
async deploy (directory, opts) {
opts = {
cleanup: false, // delete remote orphaned files
statsCb: () => {},
...opts
}
const [localFiles, remoteFiles] = await Promise.all([
afw.allFiles(folder, { shaper: f => f }),
this.list()
])
const statsCb = opts.statsCb
const startDeployTime = Date.now()
const totalTime = new SimpleTimer(startDeployTime)
// Inspection stage stats initializer
const inspectionStats = {
stage: INSPECTING,
status: START,
timer: new SimpleTimer(startDeployTime),
totalTime,
tasks: {
localScan: {
numberOfFiles: 0,
totalSize: 0,
timer: new SimpleTimer(startDeployTime)
},
remoteScan: {
numberOfFiles: 0,
totalSize: 0,
timer: new SimpleTimer(startDeployTime)
}
}
}
const sendInspectionUpdate = (status) => {
if (status) inspectionStats.status = status
statsCb(inspectionStats)
}
sendInspectionUpdate(START)
// Remote scan timers
const remoteScanJob = this.list()
remoteScanJob.then(({ files }) => { // Comes in the form of a response object
const { tasks: { remoteScan } } = inspectionStats
remoteScan.numberOfFiles = files.length
remoteScan.totalSize = files.reduce((accum, cur) => {
return accum + cur.size || 0
}, 0)
remoteScan.timer.stop()
sendInspectionUpdate(PROGRESS)
})
// Local scan timers and progress accumulator
const localScanJob = progressAccum(
afw.asyncFolderWalker(directory, { shaper: f => f })
)
async function progressAccum (iterator) {
const localFiles = []
const { tasks: { localScan } } = inspectionStats
for await (const file of iterator) {
localFiles.push(file)
localScan.numberOfFiles += 1
localScan.totalSize += file.stat.blksize
sendInspectionUpdate(PROGRESS)
}
return localFiles
}
localScanJob.then(files => {
const { tasks: { localScan } } = inspectionStats
localScan.timer.stop()
sendInspectionUpdate(PROGRESS)
})
// Inspection stage finalizer
const [localFiles, remoteFiles] = await Promise.all([
localScanJob,
this.list().then(res => res.files)
])
inspectionStats.timer.stop()
sendInspectionUpdate(STOP)
// DIFFING STAGE
const diffingStats = {
stage: DIFFING,
status: START,
timer: new SimpleTimer(Date.now()),
totalTime,
tasks: {
diffing: {
uploadCount: 0,
deleteCount: 0,
skipCount: 0
}
}
}
statsCb(diffingStats)
const { tasks: { diffing } } = diffingStats
const { filesToUpload, filesToDelete, filesSkipped } = await neocitiesLocalDiff(remoteFiles, localFiles)
diffingStats.timer.stop()
diffingStats.status = STOP
diffing.uploadCount = filesToUpload.length
diffing.deleteCount = filesToDelete.length
diffing.skipCount = filesSkipped.length
statsCb(diffingStats)
const applyingStartTime = Date.now()
const applyingStats = {
stage: APPLYING,
status: START,
timer: new SimpleTimer(applyingStartTime),
totalTime,
tasks: {
uploadFiles: {
timer: new SimpleTimer(applyingStartTime),
bytesWritten: 0,
totalBytes: 0,
get percent () {
return this.totalBytes === 0 ? 0 : this.bytesWritten / this.totalBytes
},
get speed () {
return this.bytesWritten / this.timer.elapsed
}
},
deleteFiles: {
timer: new SimpleTimer(applyingStartTime),
bytesWritten: 0,
totalBytes: 0,
get percent () {
return this.totalBytes === 0 ? 0 : this.bytesWritten / this.totalBytes
},
get speed () {
return this.bytesWritten / this.timer.elapsed
}
},
skippedFiles: {
count: filesSkipped.length,
size: filesSkipped.reduce((accum, file) => accum + file.stat.blksize, 0)
}
}
}
const sendApplyingUpdate = (status) => {
if (status) applyingStats.status = status
statsCb(applyingStats)
}
sendApplyingUpdate(START)
const { filesToUpload, filesToDelete, filesSkipped } = await neocitiesLocalDiff(remoteFiles.files, localFiles)
opts.statsCb({ filesToUpload, filesToDelete, filesSkipped })
const work = []
if (filesToUpload.length > 0) work.push(this.upload(filesToUpload))
if (opts.cleanup && filesToDelete.length > 0) { work.push(this.delete(filesToDelete)) }
const { tasks: { uploadFiles, deleteFiles } } = applyingStats
if (filesToUpload.length > 0) {
const uploadJob = this.upload(filesToUpload, {
statsCb: ({ bytesWritten, totalBytes }) => {
uploadFiles.bytesWritten = bytesWritten
uploadFiles.totalBytes = totalBytes
sendApplyingUpdate(PROGRESS)
}
})
work.push(uploadJob)
uploadJob.then(res => {
uploadFiles.timer.stop()
sendApplyingUpdate(PROGRESS)
})
} else {
uploadFiles.timer.stop()
}
if (opts.cleanup && filesToDelete.length > 0) {
const deleteJob = this.delete(filesToDelete, {
statsCb: ({ bytesWritten, totalBytes }) => {
deleteFiles.bytesWritten = bytesWritten
deleteFiles.totalBytes = totalBytes
sendApplyingUpdate(PROGRESS)
}
})
work.push(deleteJob)
deleteJob.then(res => {
deleteFiles.timer.stop()
sendApplyingUpdate(PROGRESS)
})
} else {
deleteFiles.timer.stop()
}
await Promise.all(work)
applyingStats.timer.stop()
sendApplyingUpdate(STOP)
return { filesToUpload, filesToDelete, filesSkipped }
totalTime.stop()
const statsSummary = {
time: totalTime,
inspectionStats,
diffingStats,
applyingStats
}
return statsSummary
}
}
module.exports = NeocitiesAPIClient

View File

@ -1,16 +1,16 @@
const crypto = require('crypto')
const util = require('util')
const fs = require('fs')
const ppump = util.promisify(require('pump'))
/**
* neocitiesLocalDiff returns an array of files to delete and update and some useful stats.
* @param {Array} neocitiesFiles Array of files returned from the neocities list api.
* @param {Array} localListing Array of files returned by a full data async-folder-walker run.
* @return {Promise<Object>} Object of filesToUpload, filesToDelete and filesSkipped.
*/
async function neocitiesLocalDiff (neocitiesFiles, localListing, opts = {}) {
opts = {
...opts
}
async function neocitiesLocalDiff (neocitiesFiles, localListing) {
const localIndex = {}
const ncIndex = {}
@ -19,9 +19,8 @@ async function neocitiesLocalDiff (neocitiesFiles, localListing, opts = {}) {
const ncFiles = new Set(neoCitiesFiltered.map(f => f.path)) // shape
const localListingFiltered = localListing.filter(f => !f.stat.isDirectory()) // files only
localListingFiltered.forEach(f => { localIndex[f.relname] = f }) // index
// TODO: convert windows to unix paths
const localFiles = new Set(localListingFiltered.map(f => f.relname)) // shape
localListingFiltered.forEach(f => { localIndex[forceUnixRelname(f.relname)] = f }) // index
const localFiles = new Set(localListingFiltered.map(f => forceUnixRelname(f.relname))) // shape
const filesToAdd = difference(localFiles, ncFiles)
const filesToDelete = difference(ncFiles, localFiles)
@ -43,7 +42,7 @@ async function neocitiesLocalDiff (neocitiesFiles, localListing, opts = {}) {
return {
filesToUpload: Array.from(filesToAdd).map(p => ({
name: localIndex[p].relname,
name: forceUnixRelname(localIndex[p].relname),
path: localIndex[p].filepath
})),
filesToDelete: Array.from(filesToDelete).map(p => ncIndex[p].path),
@ -72,7 +71,7 @@ async function sha1FromPath (p) {
// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#Implementing_basic_set_operations
/**
* difference betwen setA and setB
* difference returnss the difference betwen setA and setB.
* @param {Set} setA LHS set
* @param {Set} setB RHS set
* @return {Set} The difference Set
@ -85,6 +84,12 @@ function difference (setA, setB) {
return _difference
}
/**
* intersection returns the interesction between setA and setB.
* @param {Set} setA setA LHS set
* @param {Set} setB setB RHS set
* @return {Set} The intersection set between setA and setB.
*/
function intersection (setA, setB) {
const _intersection = new Set()
for (const elem of setB) {
@ -95,6 +100,18 @@ function intersection (setA, setB) {
return _intersection
}
/**
* forceUnixRelname forces a OS dependent path to a unix style path.
* @param {String} relname String path to convert to unix style.
* @return {String} The unix variant of the path
*/
function forceUnixRelname (relname) {
return relname.split(relname.sep).join('/')
}
/**
* Example of neocitiesFiles
*/
// [
// {
// path: 'img',
@ -131,6 +148,9 @@ function intersection (setA, setB) {
// }
// ]
/**
* Example of localListing
*/
// [{
// root: '/Users/bret/repos/async-folder-walker/fixtures',
// filepath: '/Users/bret/repos/async-folder-walker/fixtures/sub-folder/sub-sub-folder',

View File

@ -1,6 +1,7 @@
const tap = require('tap')
const afw = require('async-folder-walker')
const path = require('path')
const tap = require('tap')
const { neocitiesLocalDiff } = require('./folder-diff')
const remoteFiles = [
@ -53,7 +54,6 @@ tap.test('test differ', async t => {
}), 'every file to upload is included')
t.deepEqual(filesToDelete, [
'index.html',
'not_found.html',
'style.css'
], 'filesToDelete returned correctly')

View File

@ -1,26 +1,26 @@
{
"_from": "async-neocities@0.0.10",
"_id": "async-neocities@0.0.10",
"_from": "async-neocities@1.0.0",
"_id": "async-neocities@1.0.0",
"_inBundle": false,
"_integrity": "sha512-K6QNpBPNlZtRX7wGkL3f+i1HNJ5NNnq0dURGbDVYtrwwYN+PUfbqElugIsvyj+OIsO2wp5A7NBo1Wm6T3prSeg==",
"_integrity": "sha512-iRdvlFfyyqS390fGzs/FJOFG5izOJFVG/0w/xRoqZ6ochmjkxiByp16zjBb1Ade5lvXuKTuBdM/sdqmIQvWe5w==",
"_location": "/async-neocities",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "async-neocities@0.0.10",
"raw": "async-neocities@1.0.0",
"name": "async-neocities",
"escapedName": "async-neocities",
"rawSpec": "0.0.10",
"rawSpec": "1.0.0",
"saveSpec": null,
"fetchSpec": "0.0.10"
"fetchSpec": "1.0.0"
},
"_requiredBy": [
"/"
],
"_resolved": "https://registry.npmjs.org/async-neocities/-/async-neocities-0.0.10.tgz",
"_shasum": "fcb1e7e49c1577a2f2735256e51ca9e51893b792",
"_spec": "async-neocities@0.0.10",
"_resolved": "https://registry.npmjs.org/async-neocities/-/async-neocities-1.0.0.tgz",
"_shasum": "cdb2d2c4f3a431ab2aba7982693f8922f94d4360",
"_spec": "async-neocities@1.0.0",
"_where": "/Users/bret/repos/deploy-to-neocities",
"author": {
"name": "Bret Comnes",
@ -38,18 +38,27 @@
"nanoassert": "^2.0.0",
"node-fetch": "^2.6.0",
"pump": "^3.0.0",
"qs": "^6.9.1"
"pumpify": "^2.0.1",
"qs": "^6.9.1",
"streamx": "^2.6.0"
},
"deprecated": false,
"description": "WIP - nothing to see here",
"devDependencies": {
"auto-changelog": "^1.16.2",
"dependency-check": "^4.1.0",
"gh-release": "^3.5.0",
"npm-run-all": "^4.1.5",
"standard": "^13.1.0",
"tap": "^14.10.2"
},
"homepage": "https://github.com/bcomnes/async-neocities",
"keywords": [],
"keywords": [
"neocities",
"async",
"api client",
"static hosting"
],
"license": "MIT",
"main": "index.js",
"name": "async-neocities",
@ -58,15 +67,17 @@
"url": "git+https://github.com/bcomnes/async-neocities.git"
},
"scripts": {
"prepublishOnly": "git push --follow-tags && gh-release",
"test": "run-s test:*",
"test:deps": "dependency-check . --no-dev --no-peer",
"test:standard": "standard",
"test:tape": "tap"
"test:tape": "tap",
"version": "auto-changelog -p --template keepachangelog auto-changelog --breaking-pattern 'BREAKING:' && git add CHANGELOG.md"
},
"standard": {
"ignore": [
"dist"
]
},
"version": "0.0.10"
"version": "1.0.0"
}

48
node_modules/async-neocities/test.js generated vendored
View File

@ -83,4 +83,52 @@ if (!fakeToken) {
// console.log(deleteResults)
t.equal(deleteResults.result, 'success', 'list result successfull')
})
tap.test('can deploy folders', async t => {
const client = new NeocitiesAPIClient(token)
// const statsCb = (stats) => {
// let logLine = `${stats.stage} ${stats.status} ${stats.timer.elapsed}`
// Object.entries(stats.tasks).forEach(([key, val]) => {
// logLine += ` ${key}: ${JSON.stringify(val)}`
// })
// console.log(logLine)
// }
const deployStats = await client.deploy(
resolve(__dirname, 'fixtures'),
{
// statsCb,
cleanup: false
}
)
t.ok(deployStats)
// console.dir(deployStats, { depth: 99, colors: true })
const redeployStats = await client.deploy(
resolve(__dirname, 'fixtures'),
{
// statsCb,
cleanup: false
}
)
t.ok(redeployStats)
// console.dir(redeployStats, { depth: 99, colors: true })
const cleanupStats = await client.deploy(
resolve(__dirname, 'fixtures/empty'),
{
// statsCb,
cleanup: true
}
)
t.ok(cleanupStats)
// console.dir(cleanupStats, { depth: 99, colors: true })
})
}

View File

@ -16,6 +16,7 @@
"fetchSpec": "^1.1.0"
},
"_requiredBy": [
"/duplexify",
"/pump"
],
"_resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",

View File

@ -17,7 +17,9 @@
},
"_requiredBy": [
"/execa",
"/hasha"
"/got",
"/hasha",
"/term-size/execa"
],
"_resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"_shasum": "12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44",

View File

@ -16,7 +16,8 @@
"fetchSpec": "^2.0.0"
},
"_requiredBy": [
"/async-neocities"
"/async-neocities",
"/streamx"
],
"_resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz",
"_shasum": "a05f86de6c7a51618038a620f88878ed1e490c09",

View File

@ -17,7 +17,8 @@
},
"_requiredBy": [
"/@octokit/request",
"/async-neocities"
"/async-neocities",
"/auto-changelog"
],
"_resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
"_shasum": "e633456386d4aa55863f676a7ab0daa8fdecb0fd",

View File

@ -16,7 +16,8 @@
"fetchSpec": "^2.0.0"
},
"_requiredBy": [
"/execa"
"/execa",
"/term-size/execa"
],
"_resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
"_shasum": "35a9232dfa35d7067b4cb2ddf2357b1871536c5f",

View File

@ -16,7 +16,8 @@
"fetchSpec": "^1.0.0"
},
"_requiredBy": [
"/execa"
"/execa",
"/term-size/execa"
],
"_resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"_shasum": "3fbcfb15b899a44123b34b6dcc18b724336a2cae",

3
node_modules/pump/package.json generated vendored
View File

@ -17,7 +17,8 @@
},
"_requiredBy": [
"/async-neocities",
"/get-stream"
"/get-stream",
"/pumpify"
],
"_resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"_shasum": "b4a2116815bde2f4e1ea602354e8c75565107a64",

11
node_modules/semver/package.json generated vendored
View File

@ -16,9 +16,16 @@
"fetchSpec": "^5.5.0"
},
"_requiredBy": [
"/caching-transform/make-dir",
"/cp-file/make-dir",
"/cross-spawn",
"/make-dir",
"/normalize-package-data"
"/find-cache-dir/make-dir",
"/istanbul-lib-report/make-dir",
"/istanbul-lib-source-maps/make-dir",
"/normalize-package-data",
"/nyc/make-dir",
"/package-json",
"/semver-diff"
],
"_resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"_shasum": "a954f931aeba508d307bbf069eff0c01c96116f7",

View File

@ -16,7 +16,8 @@
"fetchSpec": "^1.2.0"
},
"_requiredBy": [
"/cross-spawn"
"/cross-spawn",
"/term-size/cross-spawn"
],
"_resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
"_shasum": "44aac65b695b03398968c39f363fee5deafdf1ea",

View File

@ -16,12 +16,13 @@
"fetchSpec": "^3.0.0"
},
"_requiredBy": [
"/caching-transform/write-file-atomic",
"/execa",
"/foreground-child",
"/gauge",
"/nyc",
"/restore-cursor",
"/spawn-wrap",
"/term-size/execa",
"/write-file-atomic"
],
"_resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",

View File

@ -16,7 +16,8 @@
"fetchSpec": "^1.0.0"
},
"_requiredBy": [
"/execa"
"/execa",
"/term-size/execa"
],
"_resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"_shasum": "bb43ff5598a6eb05d89b59fcd129c983313606bf",

3
node_modules/which/package.json generated vendored
View File

@ -18,7 +18,8 @@
"_requiredBy": [
"/cross-spawn",
"/foreground-child/cross-spawn",
"/spawn-wrap"
"/spawn-wrap",
"/term-size/cross-spawn"
],
"_resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"_shasum": "a45043d54f5805316da8d62f9f50918d3da70b0a",

View File

@ -9,6 +9,11 @@
"test-skip:deps": "dependency-check . --no-dev --no-peer",
"test:standard": "standard",
"test:tape": "tap",
"release": "git push --follow-tags && gh-release",
"version": "run-s version:*",
"version:1-changelog": "auto-changelog -p --template keepachangelog auto-changelog --breaking-pattern 'BREAKING:' && git add CHANGELOG.md",
"version:2-cleandeps": "rm -rf node_modules && npm i --only=prod && git add node_modules --force",
"postversion": "npm i",
"clean": "rimraf dist && mkdirp dist",
"build": "mkdir public && cp package.json public/test.json"
},
@ -27,12 +32,16 @@
"dependency-check": "^4.1.0",
"npm-run-all": "^4.1.5",
"standard": "^14.3.1",
"tap": "^14.10.5"
"gh-release": "^3.5.0",
"tap": "^14.10.5",
"auto-changelog": "^1.16.2"
},
"dependencies": {
"@actions/core": "1.2.2",
"@actions/github": "2.1.0",
"async-neocities": "0.0.10"
"async-neocities": "1.0.0",
"pretty-bytes": "^5.3.0",
"pretty-time": "^1.1.0"
},
"standard": {
"ignore": [

81
test.js
View File

@ -1,82 +1,5 @@
const tap = require('tap')
const { readFileSync } = require('fs')
const { resolve } = require('path')
const { NeocitiesAPIClient } = require('./lib/client')
let token = process.env.NEOCITIES_API_TOKEN
if (!token) {
try {
const config = JSON.parse(readFileSync(resolve(__dirname, 'config.json')))
token = config.token
} catch (e) {
console.warn('error loading config.json')
console.warn(e)
}
}
tap.test('token loaded', async t => {
t.ok(token)
})
tap.test('basic client api', async t => {
const client = new NeocitiesAPIClient(token)
t.ok(client.info, 'info method available')
t.ok(client.list, 'list method available')
t.ok(client.get, 'get method available')
t.ok(client.post, 'post method available')
})
tap.test('can get info about site', async t => {
const client = new NeocitiesAPIClient(token)
const info = await client.info()
// console.log(info)
t.equal(info.result, 'success', 'info requesst successfull')
const list = await client.list()
// console.log(list)
t.equal(list.result, 'success', 'list result successfull')
})
// test('form data works the way I think', t => {
// const form = new FormData();
// const p = resolve(__dirname, 'package.json');
// form.append('package.json', next => next(createReadStream(p)));
//
// const concatStream = concat((data) => {
// console.log(data);
// t.end();
// });
//
// form.on('error', (err) => {
// t.error(err);
// });
// form.pipe(concatStream);
// });
tap.test('can upload and delete files', async t => {
const client = new NeocitiesAPIClient(token)
const uploadResults = await client.upload([
{
name: 'toot.gif',
path: resolve(__dirname, 'fixtures/toot.gif')
},
{
name: 'img/tootzzz.png',
path: resolve(__dirname, 'fixtures/tootzzz.png')
}
])
// console.log(uploadResults)
t.equal(uploadResults.result, 'success', 'list result successfull')
const deleteResults = await client.delete([
'toot.gif',
'img/tootzzz.png'
])
// console.log(deleteResults)
t.equal(deleteResults.result, 'success', 'list result successfull')
tap.test('test', async t => {
t.ok(true)
})