├── bin ├── run.cmd └── run ├── .gitignore ├── .editorconfig ├── tsconfig.json ├── src ├── index.ts └── main.ts ├── README.md └── package.json /bin/run.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | node "%~dp0\run" %* 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *-debug.log 2 | *-error.log 3 | /.nyc_output 4 | /dist 5 | /lib 6 | /tmp 7 | /yarn.lock 8 | node_modules 9 | .idea 10 | .vscode 11 | /.installmemaybe 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /bin/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const project = path.join(__dirname, '../tsconfig.json') 6 | const dev = fs.existsSync(project) 7 | 8 | if (dev) { 9 | require('ts-node').register({project}) 10 | } 11 | 12 | require(`../${dev ? 'src' : 'lib'}`).run() 13 | .catch(require('@oclif/errors/handle')) 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "importHelpers": true, 5 | "module": "commonjs", 6 | "outDir": "lib", 7 | "rootDir": "src", 8 | "strict": true, 9 | "target": "es2017", 10 | "composite": true, 11 | "allowSyntheticDefaultImports": true, 12 | "esModuleInterop": true 13 | }, 14 | "include": [ 15 | "src/**/*" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {Command, flags} from '@oclif/command' 2 | import main from './main' 3 | 4 | class NpmInstallMeMaybe extends Command { 5 | static description = 'describe the command here' 6 | 7 | static flags = { 8 | // add --version flag to show CLI version 9 | version: flags.version({char: 'v'}), 10 | help: flags.help({char: 'h'}), 11 | } 12 | 13 | static args = [{ 14 | name: 'file', 15 | required: false, 16 | description: 'output file', 17 | hidden: true, // hide this arg from help 18 | parse: (input: string) => 'output', // instead of the user input, return a different value 19 | default: 'world', // default value if no arg input 20 | options: ['a', 'b'], // only allow input to be from a discrete set 21 | } 22 | ] 23 | 24 | async run() { 25 | const {args, flags} = this.parse(NpmInstallMeMaybe) 26 | 27 | main(args,flags) 28 | 29 | 30 | 31 | } 32 | } 33 | 34 | export = NpmInstallMeMaybe 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Idempotent NPM Installs 2 | ==================== 3 | 4 | [![oclif](https://img.shields.io/badge/cli-oclif-brightgreen.svg)](https://oclif.io) 5 | [![Version](https://img.shields.io/npm/v/install-me-maybe.svg)](https://npmjs.org/package/install-me-maybe) 6 | [![Downloads/week](https://img.shields.io/npm/dw/install-me-maybe.svg)](https://npmjs.org/package/install-me-maybe) 7 | [![License](https://img.shields.io/npm/l/install-me-maybe.svg)](https://github.com/rlancer/npm-install-me-maybe/blob/master/package.json) 8 | 9 | **Works with Yarn or NPM!** 10 | 11 | Designed for CI / CD systems. Run `install-me-maybe` as many times as you'd like, it will only run `npm install` or `yarn` when the lock file changed. 12 | 13 | ## How it works 14 | 15 | Creates a hash of your package-lock.json or yarn.lock and writes that hash to a file called .installmemaybe 16 | 17 | On the next call to install-me-maybe the contents of the hash file are compared with the new hash, only then will an install be triggered. 18 | 19 | Installs your NPM modules only if they are out of date, designed for CI / CD systems 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import crypto from 'crypto' 3 | import {spawn} from 'child_process' 4 | 5 | 6 | export default (args: any, flags: any) => { 7 | 8 | 9 | const outfile = '.installmemaybe' 10 | 11 | 12 | const existingHash = fs.existsSync(outfile) ? fs.readFileSync(outfile, {encoding: 'utf8'}) : '' 13 | 14 | 15 | if (!existingHash) { 16 | console.log('Could not find hash file, installing packages') 17 | } 18 | 19 | const isYarn = fs.existsSync('yarn.lock') 20 | 21 | const data = fs.readFileSync(isYarn ? 'yarn.lock' : 'package-lock.json', {encoding: 'utf8'}) 22 | 23 | const hash = crypto 24 | .createHash('sha1') 25 | .update(data.toString(), 'utf8') 26 | .digest('hex') 27 | 28 | // if there is an eviorment varble then delete stored hash 29 | 30 | if (hash !== existingHash) { 31 | const dir = spawn('npm', ['i']) 32 | 33 | 34 | dir.stdout.on('data', data => console.log(data.toString())) 35 | dir.stderr.on('data', data => console.log(data.toString())) 36 | // dir.on('close', code => console.log(`child process exited with code ${code}`)) 37 | 38 | } else { 39 | console.log('Not installing ') 40 | } 41 | 42 | fs.writeFileSync('.installmemaybe', hash, {encoding: 'utf8'}) 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "install-me-maybe", 3 | "description": "Installs your NPM modules only if they are out of date, designed for CI / CD systems ", 4 | "version": "0.0.6", 5 | "author": "Robert Lancer", 6 | "bin": { 7 | "install-me-maybe": "./bin/run" 8 | }, 9 | "bugs": "https://github.com/rlancer/npm-install-me-maybe/issues", 10 | "dependencies": { 11 | "@oclif/command": "^1.5.8", 12 | "@oclif/config": "^1.12.4", 13 | "@oclif/plugin-help": "^2.1.6", 14 | "tslib": "^1.9.3" 15 | }, 16 | "devDependencies": { 17 | "@types/chai": "^4.1.4", 18 | "@types/mocha": "^5.2.5", 19 | "@types/node": "^10.12.21", 20 | "ts-node": "^7.0.1", 21 | "typescript": "^3.3.1" 22 | }, 23 | "engines": { 24 | "node": ">=8.0.0" 25 | }, 26 | "files": [ 27 | "/bin", 28 | "/lib" 29 | ], 30 | "homepage": "https://github.com/rlancer/npm-install-me-maybe", 31 | "keywords": [ 32 | "oclif", 33 | "install", 34 | "idempotent insall", 35 | "CI / CD" 36 | ], 37 | "license": "MIT", 38 | "main": "lib/index.js", 39 | "oclif": { 40 | "bin": "install-me-maybe" 41 | }, 42 | "repository": "rlancer/npm-install-me-maybe", 43 | "scripts": { 44 | "prepack": "rm -rf lib && tsc -b", 45 | "test": "echo NO TESTS" 46 | }, 47 | "types": "lib/index.d.ts" 48 | } 49 | --------------------------------------------------------------------------------