@pinpt/cli
Advanced tools
+128
| import fs from 'fs'; | ||
| import path from 'path'; | ||
| import FormData from 'form-data'; | ||
| import AdmZip from 'adm-zip'; | ||
| import vm from 'vm'; | ||
| import tmp from 'tmp'; | ||
| import picomatch from 'picomatch'; | ||
| import ora from 'ora'; | ||
| import c from 'kleur'; | ||
| import progress from 'cli-progress'; | ||
| import { debug, error, walkSync, apiRequest } from './util.mjs'; | ||
| const createMatcher = (buf) => { | ||
| const tok = buf.toString().trim().split(/\n/); | ||
| const matchers = tok.filter(Boolean).map((c) => picomatch(c, { posixSlashes: true })); | ||
| return (filepath) => { | ||
| for (let c = 0; c < matchers.length; c++) { | ||
| if (matchers[c](filepath)) { | ||
| // console.log('MATCH ->', filepath); | ||
| return true; | ||
| } | ||
| } | ||
| // console.log('NOT MATCH ->', filepath); | ||
| return false; | ||
| }; | ||
| }; | ||
| // none of these files we actually need to build the app for deployment or to run it | ||
| const builtIns = createMatcher(` | ||
| node_modules/** | ||
| .git/** | ||
| .github/** | ||
| .next/** | ||
| .gitignore | ||
| .npmignore | ||
| .dockerignore | ||
| .prettierignore | ||
| .pinpointignore | ||
| README* | ||
| LICENSE* | ||
| prettier.config.js | ||
| **/*.zip | ||
| **/*.tar | ||
| **/*.gzip | ||
| `); | ||
| const createFilter = (basedir, matcher) => (filepath) => { | ||
| const fn = path.relative(basedir, filepath); | ||
| if (matcher) { | ||
| if (matcher(fn)) { | ||
| return false; | ||
| } | ||
| } | ||
| return builtIns(fn) === false; | ||
| }; | ||
| const upload = async (fn, config) => { | ||
| const buf = fs.readFileSync(config); | ||
| const script = new vm.Script(buf.toString(), { filename: config }); | ||
| const context = { module: { exports: {} } }; | ||
| script.runInNewContext(context); | ||
| const siteId = context.module.exports.siteId; | ||
| if (!siteId) { | ||
| throw new Error(`No required siteId variable found in ${config}`); | ||
| } | ||
| const p = new progress.SingleBar({ | ||
| format: '' + c.green('{bar}') + ' {percentage}% || {value}/{total} Chunks', | ||
| barCompleteChar: '\u2588', | ||
| barIncompleteChar: '\u2591', | ||
| hideCursor: true, | ||
| clearOnComplete: true, | ||
| }); | ||
| p.start(100, 0); | ||
| const spinner = ora('Validating ...'); | ||
| const body = new FormData(); | ||
| body.append('upload', fs.createReadStream(fn)); | ||
| await apiRequest('', `/site/${siteId}/theme/upload2`, { | ||
| noSpinner: true, | ||
| method: 'POST', | ||
| body, | ||
| onProgress: (progress) => { | ||
| p.update(100 * progress.percent); | ||
| if (progress.percent === 1) { | ||
| p.stop(); | ||
| spinner.start(); | ||
| } | ||
| }, | ||
| }); | ||
| spinner.succeed(c.bold('Deployed ... 🚀')); | ||
| }; | ||
| export const deploy = async ({ args }) => { | ||
| if (args && args[0] === 'deploy') { | ||
| const config = path.join(process.cwd(), 'pinpoint.config.js'); | ||
| if (!fs.existsSync(config)) { | ||
| error(`Couldn't find ${config}. Please make sure you're running this command from a Pinpoint project`, true); | ||
| } | ||
| const tmpfile = tmp.fileSync(); | ||
| const spinner = ora('Creating deployment package ...').start(); | ||
| try { | ||
| const ignore = path.join(process.cwd(), '.pinpointignore'); | ||
| let matcher = null; | ||
| if (fs.existsSync(ignore)) { | ||
| matcher = createMatcher(fs.readFileSync(ignore).toString()); | ||
| } | ||
| const files = walkSync(process.cwd(), createFilter(process.cwd(), matcher)); | ||
| const zip = new AdmZip(); | ||
| files.forEach((fn) => { | ||
| debug(`Adding ${fn} to zip...`); | ||
| zip.addLocalFile(fn); | ||
| }); | ||
| zip.writeZip(tmpfile.name); | ||
| spinner.succeed(c.bold('Created deployment package')); | ||
| await upload(tmpfile.name, config); | ||
| } catch (ex) { | ||
| spinner.fail(ex.message); | ||
| process.exit(1); | ||
| } finally { | ||
| tmpfile.removeCallback(); | ||
| } | ||
| } | ||
| }; |
+2
-0
@@ -8,2 +8,3 @@ import { init } from './util.mjs'; | ||
| import { signup } from './signup.mjs'; | ||
| import { deploy } from './deploy.mjs'; | ||
@@ -18,2 +19,3 @@ export const run = async ({ meta, args, options, optionSpec, commandSpec }) => { | ||
| await logout({ meta, args, options, optionSpec, commandSpec }); | ||
| await deploy({ meta, args, options, optionSpec, commandSpec }); | ||
| }; |
+76
-11
@@ -9,5 +9,7 @@ import CFonts from 'cfonts'; | ||
| import terminalLink from 'terminal-link'; | ||
| import getstream from 'get-stream'; | ||
| import si from 'systeminformation'; | ||
| import c from 'kleur'; | ||
| import { fileURLToPath } from 'url'; | ||
| import FormData from 'form-data'; | ||
@@ -174,5 +176,16 @@ export const tick = (message) => { | ||
| export const apiRequest = async (help, basepath, params) => { | ||
| const { body = null, method = 'POST', quiet = false, failOnError = true, apiKey, checkSuccess } = params; | ||
| const { | ||
| body = null, | ||
| method = 'POST', | ||
| quiet = false, | ||
| failOnError = true, | ||
| apiKey, | ||
| checkSuccess, | ||
| onProgress, | ||
| noSpinner, | ||
| } = params; | ||
| const spinner = ora({ text: quiet ? '' : c.magenta(help), discardStdin: true, interval: 80 }); | ||
| spinner.start(); | ||
| if (!noSpinner) { | ||
| spinner.start(); | ||
| } | ||
| const _apiKey = apiKey || getAPIKey(); | ||
@@ -187,15 +200,43 @@ const headers = {}; | ||
| const url = `https://${_options.host}${basepath}`; | ||
| if (body) { | ||
| const _method = body ? method : 'GET'; | ||
| const isStream = body instanceof FormData; | ||
| const _body = body ? (isStream ? body : JSON.stringify(body)) : undefined; | ||
| if (body && !isStream) { | ||
| headers['Content-Type'] = 'application/json'; | ||
| } | ||
| const _method = body ? method : 'GET'; | ||
| debug(`requesting ${_method} ${url}`); | ||
| try { | ||
| const res = await got(url, { | ||
| headers, | ||
| method: _method, | ||
| responseType: 'json', | ||
| body: body ? JSON.stringify(body) : undefined, | ||
| throwHttpErrors: false, | ||
| }); | ||
| let g; | ||
| let p; | ||
| if (isStream) { | ||
| g = got.stream.post(url, { | ||
| headers, | ||
| responseType: 'json', | ||
| body: _body, | ||
| throwHttpErrors: false, | ||
| }); | ||
| if (onProgress) { | ||
| g.on('uploadProgress', onProgress); | ||
| } | ||
| g.resume(); | ||
| p = new Promise((resolve, reject) => { | ||
| g.on('response', async (response) => { | ||
| // stream the stream as a response body | ||
| const resp = JSON.parse(await getstream(g)); | ||
| const _response = { ...response, body: resp }; | ||
| resolve(_response); | ||
| }); | ||
| g.on('error', reject); | ||
| }); | ||
| } else { | ||
| g = got(url, { | ||
| headers, | ||
| method: _method, | ||
| responseType: 'json', | ||
| body: _body, | ||
| throwHttpErrors: false, | ||
| }); | ||
| p = g; | ||
| } | ||
| const res = await p; | ||
| debug(`responded ${_method} ${url} ${JSON.stringify(res.body)}`); | ||
@@ -242,1 +283,25 @@ if (res.body && res.body.success) { | ||
| }; | ||
| export const walkSync = (dir, filter, found = []) => { | ||
| const files = fs.readdirSync(dir); | ||
| files.forEach((file) => { | ||
| const filepath = path.join(dir, file); | ||
| const stats = fs.statSync(filepath); | ||
| if (stats.isDirectory()) { | ||
| if (filter) { | ||
| if (!filter(filepath)) { | ||
| return; | ||
| } | ||
| } | ||
| walkSync(filepath, filter, found); | ||
| } else if (stats.isFile()) { | ||
| if (filter) { | ||
| if (!filter(filepath)) { | ||
| return; | ||
| } | ||
| } | ||
| found.push(filepath); | ||
| } | ||
| }); | ||
| return found; | ||
| }; |
+6
-1
| { | ||
| "name": "@pinpt/cli", | ||
| "version": "0.0.2", | ||
| "version": "0.0.3", | ||
| "description": "Pinpoint CLI", | ||
@@ -32,9 +32,14 @@ "bin": { | ||
| "dependencies": { | ||
| "adm-zip": "^0.5.5", | ||
| "arg": "^5.0.0", | ||
| "cfonts": "^2.9.3", | ||
| "cli-progress": "^3.9.0", | ||
| "execa": "^5.1.1", | ||
| "form-data": "^4.0.0", | ||
| "fs-extra": "^10.0.0", | ||
| "get-stream": "^6.0.1", | ||
| "got": "^11.8.2", | ||
| "kleur": "^4.1.4", | ||
| "ora": "^5.4.1", | ||
| "picomatch": "^2.3.0", | ||
| "prompts": "^2.4.1", | ||
@@ -41,0 +46,0 @@ "recursive-copy": "^2.0.13", |
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
25622
23.36%13
8.33%891
25.85%20
33.33%10
25%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added