Repository: Unitech/pm2 Branch: master Commit: ff1ca974afad Files: 691 Total size: 1.3 MB Directory structure: gitextract_4g07d2ov/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── stale.yml │ └── workflows/ │ └── node.js.yml ├── .gitignore ├── .mocharc.js ├── .npmignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── GNU-AGPL-3.0.txt ├── LICENSE ├── README.md ├── bin/ │ ├── pm2 │ ├── pm2-dev │ ├── pm2-docker │ ├── pm2-runtime │ ├── pm2-windows │ └── pm2.ps1 ├── constants.js ├── examples/ │ ├── api-pm2/ │ │ ├── README.md │ │ ├── api.js │ │ └── http.js │ ├── c-compile/ │ │ └── hello.c │ ├── cluster-http/ │ │ ├── README.md │ │ ├── ecosystem.config.js │ │ ├── http-no-exit.js │ │ └── http.js │ ├── cluster-tcp/ │ │ ├── README.md │ │ ├── http.js │ │ ├── process.config.js │ │ └── tcp.js │ ├── custom-nodejs-version/ │ │ ├── ecosystem.config.js │ │ └── http.js │ ├── docker-pm2/ │ │ ├── Dockerfile │ │ ├── README.md │ │ └── app/ │ │ ├── app.js │ │ ├── package.json │ │ └── process.config.js │ ├── echo/ │ │ ├── log.js │ │ └── stdout.js │ ├── ecosystem-file/ │ │ ├── README.md │ │ ├── apps/ │ │ │ ├── connection_check.sh │ │ │ ├── http.js │ │ │ └── worker.js │ │ ├── process.config.js │ │ ├── process.json │ │ └── process.yml │ ├── esm/ │ │ ├── addTwo.js │ │ ├── app.js │ │ └── package.json │ ├── exception/ │ │ ├── fast.js │ │ └── slow.js │ ├── expose-custom-metrics/ │ │ ├── README.md │ │ ├── process-metrics.js │ │ └── process.config.js │ ├── expose-triggerable-functions/ │ │ ├── index.js │ │ ├── process.yml │ │ ├── trigger-param.js │ │ └── trigger-ping.js │ ├── import/ │ │ ├── circle.js │ │ ├── index.js │ │ └── package.json │ ├── interact-via-stdin/ │ │ ├── README.md │ │ └── stdin.js │ ├── misc-examples/ │ │ ├── 001-test.js │ │ ├── apps/ │ │ │ ├── all-pm2.json │ │ │ ├── args.json │ │ │ ├── auto-kill-echo.json │ │ │ ├── cluster-pm2.json │ │ │ ├── cron-pm2.json │ │ │ ├── default-path-echo.json │ │ │ ├── echo-pm2.json │ │ │ ├── env-pm2.json │ │ │ ├── killfast.json │ │ │ ├── multi-pm2.json │ │ │ └── no-name-echo.json │ │ ├── args.js │ │ ├── auto-restart-all.js │ │ ├── auto-restart-threshold.js │ │ ├── auto-save.js │ │ ├── beforeExit.js │ │ ├── child-echo.json │ │ ├── child-env.js │ │ ├── child-pm2.json │ │ ├── child.js │ │ ├── child.js-pm2.json │ │ ├── child2.js │ │ ├── client.js │ │ ├── cwd.js │ │ ├── echo.coffee │ │ ├── echo.js │ │ ├── echokill.js │ │ ├── env.js │ │ ├── env_args.js │ │ ├── exec_watch.json │ │ ├── exit.js │ │ ├── expose_method.js │ │ ├── graceful-exit.js │ │ ├── harmony.js │ │ ├── http.js │ │ ├── infinite-recurse.js │ │ ├── inside.js │ │ ├── inside.json │ │ ├── interact.js │ │ ├── json.js │ │ ├── kill-not-so-fast.js │ │ ├── kill-slow.js │ │ ├── killfast.js │ │ ├── killslow.js │ │ ├── killtoofast.js │ │ ├── leak.js │ │ ├── malformated.json │ │ ├── modulechild.js │ │ ├── moduleparent.js │ │ ├── null.js │ │ ├── package.json │ │ ├── process.json │ │ ├── programmatic.js │ │ ├── require.js │ │ ├── sendmsg.js │ │ ├── sigint.js │ │ ├── start-args.js │ │ ├── stop-after5.js │ │ ├── string-crash.js │ │ ├── throw.js │ │ ├── tree.js │ │ ├── udp.js │ │ └── wrap.js │ ├── module-extra-meta/ │ │ ├── index.js │ │ └── package.json │ ├── module-test/ │ │ ├── README.md │ │ ├── app.js │ │ └── package.json │ ├── npm-start/ │ │ ├── http.js │ │ └── package.json │ ├── pmx-test-all/ │ │ ├── elements/ │ │ │ ├── cluster.js │ │ │ ├── counter.js │ │ │ ├── error.js │ │ │ ├── event.js │ │ │ ├── histogram.js │ │ │ ├── http.js │ │ │ ├── log-cluster.js │ │ │ ├── log.js │ │ │ ├── meter.js │ │ │ ├── metric.js │ │ │ ├── notify.js │ │ │ └── trace.js │ │ └── process.config.js │ ├── run-php-python-ruby-bash/ │ │ ├── bashscript.sh │ │ ├── echo.php │ │ ├── echo.py │ │ ├── echo.rb │ │ ├── leak-mem.py │ │ └── process.yml │ ├── send-msg/ │ │ ├── pm2-app.js │ │ ├── pm2-msg.js │ │ └── t2.js │ ├── signal-catching/ │ │ └── graceful-http.js │ ├── sourcemap-auto-resolve/ │ │ ├── API.js │ │ ├── README.md │ │ └── process.config.js │ ├── start-a-binary/ │ │ ├── ls │ │ └── ls.yml │ ├── test-all-keymetrics-features/ │ │ ├── README.md │ │ ├── actions-fibonacci.js │ │ ├── custom_action.js │ │ ├── custom_action_with_params.js │ │ ├── event.js │ │ ├── http_app.js │ │ ├── http_transaction.js │ │ ├── pm2_probe.js │ │ ├── probes.js │ │ ├── process-load.config.js │ │ ├── process-transpose.js │ │ ├── process.config.js │ │ ├── scoped-actions.js │ │ ├── test-threshold.js │ │ └── throw.js │ ├── treekill/ │ │ ├── app.js │ │ └── process.json │ ├── udp/ │ │ ├── client.js │ │ └── server.js │ ├── using-pm2-and-transpilers/ │ │ ├── README.md │ │ ├── echo.coffee │ │ ├── echo.ls │ │ ├── echo.ts │ │ ├── http.ts │ │ └── node.d.ts │ └── wait-ready/ │ ├── app.js │ ├── ecosystem.json │ └── http-simple.js ├── index.js ├── lib/ │ ├── API/ │ │ ├── Configuration.js │ │ ├── Containerizer.js │ │ ├── Dashboard.js │ │ ├── Deploy.js │ │ ├── Extra.js │ │ ├── ExtraMgmt/ │ │ │ └── Docker.js │ │ ├── Log.js │ │ ├── LogManagement.js │ │ ├── Modules/ │ │ │ ├── LOCAL.js │ │ │ ├── Modularizer.js │ │ │ ├── NPM.js │ │ │ ├── TAR.js │ │ │ ├── flagExt.js │ │ │ └── index.js │ │ ├── Monit.js │ │ ├── Serve.js │ │ ├── Startup.js │ │ ├── UX/ │ │ │ ├── helpers.js │ │ │ ├── index.js │ │ │ ├── pm2-describe.js │ │ │ ├── pm2-ls-minimal.js │ │ │ └── pm2-ls.js │ │ ├── Version.js │ │ ├── interpreter.json │ │ ├── pm2-plus/ │ │ │ ├── PM2IO.js │ │ │ ├── auth-strategies/ │ │ │ │ ├── CliAuth.js │ │ │ │ └── WebAuth.js │ │ │ ├── helpers.js │ │ │ ├── link.js │ │ │ ├── pres/ │ │ │ │ ├── motd │ │ │ │ ├── motd.update │ │ │ │ └── welcome │ │ │ └── process-selector.js │ │ └── schema.json │ ├── API.js │ ├── Client.js │ ├── Common.js │ ├── Configuration.js │ ├── Daemon.js │ ├── Event.js │ ├── God/ │ │ ├── ActionMethods.js │ │ ├── ClusterMode.js │ │ ├── ForkMode.js │ │ ├── Methods.js │ │ └── Reload.js │ ├── God.js │ ├── HttpInterface.js │ ├── ProcessContainer.js │ ├── ProcessContainerBun.js │ ├── ProcessContainerFork.js │ ├── ProcessContainerForkBun.js │ ├── ProcessUtils.js │ ├── TreeKill.js │ ├── Utility.js │ ├── VersionCheck.js │ ├── Watcher.js │ ├── Worker.js │ ├── binaries/ │ │ ├── CLI.js │ │ ├── DevCLI.js │ │ ├── Runtime.js │ │ └── Runtime4Docker.js │ ├── completion.js │ ├── completion.sh │ ├── motd │ ├── templates/ │ │ ├── Dockerfiles/ │ │ │ ├── Dockerfile-java.tpl │ │ │ ├── Dockerfile-nodejs.tpl │ │ │ └── Dockerfile-ruby.tpl │ │ ├── ecosystem-es.tpl │ │ ├── ecosystem-simple-es.tpl │ │ ├── ecosystem-simple.tpl │ │ ├── ecosystem.tpl │ │ ├── init-scripts/ │ │ │ ├── launchd.tpl │ │ │ ├── openrc.tpl │ │ │ ├── pm2-init-amazon.sh │ │ │ ├── rcd-openbsd.tpl │ │ │ ├── rcd.tpl │ │ │ ├── smf.tpl │ │ │ ├── systemd-online.tpl │ │ │ ├── systemd.tpl │ │ │ └── upstart.tpl │ │ ├── logrotate.d/ │ │ │ └── pm2 │ │ └── sample-apps/ │ │ ├── http-server/ │ │ │ ├── README.md │ │ │ ├── api.js │ │ │ ├── ecosystem.config.js │ │ │ └── package.json │ │ ├── pm2-plus-metrics-actions/ │ │ │ ├── README.md │ │ │ ├── custom-metrics.js │ │ │ ├── ecosystem.config.js │ │ │ └── package.json │ │ └── python-app/ │ │ ├── README.md │ │ ├── echo.py │ │ ├── ecosystem.config.js │ │ └── package.json │ └── tools/ │ ├── Config.js │ ├── IsAbsolute.js │ ├── copydirSync.js │ ├── deleteFolderRecursive.js │ ├── find-package-json.js │ ├── fmt.js │ ├── isbinaryfile.js │ ├── json5.js │ ├── open.js │ ├── passwd.js │ ├── sexec.js │ ├── treeify.js │ ├── which.js │ └── xdg-open ├── package.json ├── packager/ │ ├── alpine/ │ │ ├── pm2/ │ │ │ └── APKBUILD │ │ └── pm2_io.rsa.pub │ ├── build-deb-rpm.sh │ ├── build-dist.sh │ ├── debian/ │ │ ├── control │ │ ├── copyright │ │ ├── description │ │ ├── lintian-overrides │ │ ├── postinst │ │ ├── postrm │ │ └── prerm │ ├── publish_deb_rpm.sh │ ├── rhel/ │ │ ├── postinst │ │ ├── postrm │ │ └── prerm │ ├── setup.deb.sh │ └── setup.rpm.sh ├── paths.js ├── pm2 ├── preinstall.js ├── pres/ │ └── TMP.md ├── run.sh ├── test/ │ ├── Dockerfile │ ├── README.md │ ├── benchmarks/ │ │ ├── monit-daemon.sh │ │ ├── monit.sh │ │ └── result.monit │ ├── docker_parallel_test.sh │ ├── e2e/ │ │ ├── binaries/ │ │ │ ├── pm2-dev.sh │ │ │ └── pm2-runtime.sh │ │ ├── cli/ │ │ │ ├── app-configuration.sh │ │ │ ├── args.sh │ │ │ ├── attach.sh │ │ │ ├── binary.sh │ │ │ ├── bun.sh │ │ │ ├── cli-actions-1.sh │ │ │ ├── cli-actions-2.sh │ │ │ ├── dump.sh │ │ │ ├── ecosystem.e2e.sh │ │ │ ├── env-refresh.sh │ │ │ ├── extra-lang.sh │ │ │ ├── fork.sh │ │ │ ├── mjs.sh │ │ │ ├── monit.sh │ │ │ ├── multiparam.sh │ │ │ ├── operate-regex.sh │ │ │ ├── piped-config.sh │ │ │ ├── plus.sh │ │ │ ├── python-support.sh │ │ │ ├── reload.sh │ │ │ ├── reset.sh │ │ │ ├── resurrect.sh │ │ │ ├── right-exit-code.sh │ │ │ ├── serve.sh │ │ │ ├── smart-start.sh │ │ │ ├── sort.sh │ │ │ ├── start-app.sh │ │ │ ├── startOrX.sh │ │ │ └── watch.sh │ │ ├── docker.sh │ │ ├── esmodule.sh │ │ ├── file-descriptor.sh │ │ ├── include.sh │ │ ├── internals/ │ │ │ ├── daemon-paths-override.sh │ │ │ ├── increment-var.sh │ │ │ ├── infinite-loop.sh │ │ │ ├── listen-timeout.sh │ │ │ ├── options-via-env.sh │ │ │ ├── promise.sh │ │ │ ├── signal.sh │ │ │ ├── source_map.sh │ │ │ ├── start-consistency.sh │ │ │ ├── wait-ready-event.sh │ │ │ └── wrapped-fork.sh │ │ ├── logs/ │ │ │ ├── log-create-not-exist-dir.sh │ │ │ ├── log-custom.sh │ │ │ ├── log-entire.sh │ │ │ ├── log-json.sh │ │ │ ├── log-namespace.sh │ │ │ ├── log-null.sh │ │ │ ├── log-reload.sh │ │ │ └── log-timestamp.sh │ │ ├── misc/ │ │ │ ├── cron-system.sh │ │ │ ├── inside-pm2.sh │ │ │ ├── instance-number.sh │ │ │ ├── misc.sh │ │ │ ├── nvm-node-version.sh │ │ │ ├── port-release.sh │ │ │ ├── startup.sh │ │ │ ├── versioning-cmd.sh │ │ │ └── vizion.sh │ │ ├── modules/ │ │ │ ├── get-set.sh │ │ │ ├── module-safeguard.sh │ │ │ └── module.sh │ │ ├── process-file/ │ │ │ ├── app-config-update.sh │ │ │ ├── append-env-to-name.sh │ │ │ ├── homogen-json-action.sh │ │ │ ├── js-configuration.sh │ │ │ ├── json-file.sh │ │ │ ├── json-reload.sh │ │ │ └── yaml-configuration.sh │ │ └── pull.sh │ ├── e2e.sh │ ├── fixtures/ │ │ ├── 001-test.js │ │ ├── all.json │ │ ├── all2.json │ │ ├── app-config-update/ │ │ │ ├── args1.json │ │ │ ├── args2.json │ │ │ └── echo.js │ │ ├── append-env-to-name.json │ │ ├── args/ │ │ │ ├── echo.js │ │ │ └── params_check.js │ │ ├── args.js │ │ ├── bashscript.sh │ │ ├── big-array-es6.js │ │ ├── big-array-listen.js │ │ ├── big-array.js │ │ ├── binary-js-file │ │ ├── binary-js-file.js │ │ ├── binary-py-file.py │ │ ├── c-compile/ │ │ │ └── hello.c │ │ ├── change_cwd.json │ │ ├── child.js │ │ ├── child_no_http.js │ │ ├── cluster/ │ │ │ └── sigint_catcher.js │ │ ├── cluster-alias.json │ │ ├── cluster-pm2.json │ │ ├── conf.json │ │ ├── configuration.js │ │ ├── configuration.json │ │ ├── containerizer/ │ │ │ ├── Dockerfile.dev │ │ │ └── Dockerfile.prod │ │ ├── cron-stop.js │ │ ├── cron.js │ │ ├── cron_no_autorestart.js │ │ ├── custom_actions/ │ │ │ └── index.js │ │ ├── delayed_exit.js │ │ ├── docker/ │ │ │ └── expressor/ │ │ │ ├── Dockerfile.bak │ │ │ ├── app.js │ │ │ ├── package.json │ │ │ ├── process.json │ │ │ └── worker.js │ │ ├── echo-env.js │ │ ├── echo-pm2.json │ │ ├── echo-post.json │ │ ├── echo-to-pm2.json │ │ ├── echo.coffee │ │ ├── echo.js │ │ ├── echo2.js │ │ ├── echo3.js │ │ ├── echoto-pm2.json │ │ ├── ecosystem/ │ │ │ └── ecosystem.config.js │ │ ├── ecosystem.config.js │ │ ├── ecosystem.json │ │ ├── ecosystem.json5 │ │ ├── empty.js │ │ ├── env-ecosystem.json │ │ ├── env-refreshed.json │ │ ├── env-switching/ │ │ │ ├── app.json │ │ │ └── child.js │ │ ├── env.js │ │ ├── env.json │ │ ├── esmodules/ │ │ │ ├── mjs/ │ │ │ │ ├── circle.mjs │ │ │ │ └── index.mjs │ │ │ └── packagemodule/ │ │ │ ├── circle.js │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── events/ │ │ │ ├── custom_action.js │ │ │ ├── custom_action_with_params.js │ │ │ └── own_event.js │ │ ├── exitcode42.js │ │ ├── extra-lang/ │ │ │ ├── app-python.config.js │ │ │ ├── apps.json │ │ │ ├── echo.php │ │ │ └── echo.py │ │ ├── git/ │ │ │ ├── COMMIT_EDITMSG │ │ │ ├── HEAD │ │ │ ├── config │ │ │ ├── description │ │ │ ├── hooks/ │ │ │ │ ├── applypatch-msg.sample │ │ │ │ ├── commit-msg.sample │ │ │ │ ├── post-update.sample │ │ │ │ ├── pre-applypatch.sample │ │ │ │ ├── pre-commit.sample │ │ │ │ ├── pre-push.sample │ │ │ │ ├── pre-rebase.sample │ │ │ │ ├── prepare-commit-msg.sample │ │ │ │ └── update.sample │ │ │ ├── index │ │ │ ├── info/ │ │ │ │ └── exclude │ │ │ ├── logs/ │ │ │ │ ├── HEAD │ │ │ │ └── refs/ │ │ │ │ ├── heads/ │ │ │ │ │ └── master │ │ │ │ └── remotes/ │ │ │ │ └── origin/ │ │ │ │ └── master │ │ │ ├── objects/ │ │ │ │ ├── 5d/ │ │ │ │ │ └── a45b6659e5cba8f444616f4e18529ad5dde72f │ │ │ │ ├── 7a/ │ │ │ │ │ └── e6d7c64d39da2167c33993e1f4f0233e8eb6f0 │ │ │ │ └── e6/ │ │ │ │ └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391 │ │ │ └── refs/ │ │ │ ├── heads/ │ │ │ │ └── master │ │ │ └── remotes/ │ │ │ └── origin/ │ │ │ └── master │ │ ├── graceful-exit-no-listen.js │ │ ├── graceful-exit-send.js │ │ ├── graceful-exit.js │ │ ├── homogen-json-action/ │ │ │ ├── all.json │ │ │ └── http.js │ │ ├── http.js │ │ ├── increment-var/ │ │ │ ├── ecosystem.json │ │ │ └── sample.js │ │ ├── inside/ │ │ │ ├── echo.js │ │ │ ├── inner_restart.sh │ │ │ ├── reload_inside.js │ │ │ ├── restart_inside.js │ │ │ └── start_inside.js │ │ ├── insidePm2Process.js │ │ ├── interface/ │ │ │ ├── child.js │ │ │ ├── http_transaction.js │ │ │ ├── human_event.js │ │ │ ├── log_out.js │ │ │ ├── process_exception.js │ │ │ ├── process_exception_with_logs.js │ │ │ └── promise_rejection.js │ │ ├── interpreter/ │ │ │ ├── echo.coffee │ │ │ ├── echo.ls │ │ │ ├── echo.ts │ │ │ └── echo.tsx │ │ ├── js-configuration/ │ │ │ ├── app.js │ │ │ └── ecosystem.config.js │ │ ├── json-reload/ │ │ │ ├── big-array.js │ │ │ ├── echo-env.js │ │ │ ├── echo-post.json │ │ │ ├── echo-pre.json │ │ │ ├── max-mem-0.json │ │ │ └── max-mem.json │ │ ├── killnotsofast.js │ │ ├── killtoofast.js │ │ ├── listen-timeout/ │ │ │ └── wait-ready.js │ │ ├── local_require.js │ │ ├── log-create-not-exist-dir/ │ │ │ └── echo.js │ │ ├── log-json/ │ │ │ ├── ecosystem.json │ │ │ └── one-echo.js │ │ ├── log-namespace/ │ │ │ └── echo.js │ │ ├── mjs/ │ │ │ ├── ecosystem.config.js │ │ │ ├── index.mjs │ │ │ ├── package.json │ │ │ └── test.mjs │ │ ├── module-fixture/ │ │ │ ├── package.json │ │ │ └── scoped-action.js │ │ ├── multi-echo.json │ │ ├── network.js │ │ ├── no-restart.json │ │ ├── no-vizion.json │ │ ├── no_cwd_change.json │ │ ├── nvm-node-version/ │ │ │ ├── ecosystem-change.json │ │ │ ├── ecosystem.json │ │ │ └── http.js │ │ ├── path-check.js │ │ ├── path-resolution/ │ │ │ ├── echo.js │ │ │ ├── ecosystem.config.js │ │ │ ├── ecosystem2.config.js │ │ │ └── ecosystem3.config.js │ │ ├── path1/ │ │ │ ├── iminpath1.js │ │ │ └── path2/ │ │ │ └── iminpath2.js │ │ ├── pm2-dev/ │ │ │ ├── app.js │ │ │ ├── app.json │ │ │ └── exited_app.js │ │ ├── pm2-ecosystem.json │ │ ├── probes.js │ │ ├── process.json │ │ ├── promise/ │ │ │ ├── empty-rejection.js │ │ │ └── rejection.js │ │ ├── push.json │ │ ├── python-script.py │ │ ├── quit.js │ │ ├── send-data-process/ │ │ │ └── return-data.js │ │ ├── serve/ │ │ │ ├── 404.html │ │ │ ├── ecosystem-serve.json │ │ │ ├── ecosystem.json │ │ │ ├── index.html │ │ │ └── other.html │ │ ├── server.js │ │ ├── signal-send.js │ │ ├── signal.js │ │ ├── signals/ │ │ │ ├── delayed_send.js │ │ │ └── delayed_sigint.js │ │ ├── sort/ │ │ │ ├── http.js │ │ │ └── other.js │ │ ├── source-map/ │ │ │ ├── main.js │ │ │ └── models.js │ │ ├── start-app/ │ │ │ ├── ecosystem-bun.config.js │ │ │ ├── ecosystem.config.js │ │ │ └── http.js │ │ ├── start-consistency/ │ │ │ ├── child.js │ │ │ └── child.json │ │ ├── startProcessInsidePm2.js │ │ ├── startProcessInsidePm2.json │ │ ├── stdin/ │ │ │ └── stdin.js │ │ ├── stdout-stderr.js │ │ ├── stop-exit-codes.json │ │ ├── throw-later.js │ │ ├── throw-later.json │ │ ├── throw-later1.json │ │ ├── throw-string.js │ │ ├── throw.js │ │ ├── toto.js │ │ ├── wait_ready_event/ │ │ │ ├── http-wait-start.js │ │ │ └── http-wait-start_nocb.js │ │ ├── watch/ │ │ │ ├── app-watch-rename.json │ │ │ ├── app-watch.json │ │ │ ├── app.json │ │ │ └── http.js │ │ ├── watcher/ │ │ │ ├── donotwatchme.dir/ │ │ │ │ └── .gitkeep │ │ │ ├── server-watch.bak.js │ │ │ └── server-watch.json │ │ └── yaml-configuration/ │ │ ├── apps.yaml │ │ ├── apps.yml │ │ ├── child.js │ │ ├── echo.js │ │ ├── echo.py │ │ └── malformated.yml │ ├── helpers/ │ │ ├── apps.js │ │ └── plan.js │ ├── interface/ │ │ ├── README.md │ │ ├── bus.fork.spec.mocha.js │ │ ├── bus.spec.mocha.js │ │ ├── mocha.opts │ │ └── utility.mocha.js │ ├── parallel.js │ ├── pm2_check_dependencies.sh │ ├── programmatic/ │ │ ├── api.backward.compatibility.mocha.js │ │ ├── api.mocha.js │ │ ├── auto_restart.mocha.js │ │ ├── client.mocha.js │ │ ├── cluster.mocha.js │ │ ├── common.mocha.js │ │ ├── conf_update.mocha.js │ │ ├── configuration.mocha.js │ │ ├── containerizer.mocha.js │ │ ├── custom_action.mocha.js │ │ ├── dump.mocha.js │ │ ├── env_switching.js │ │ ├── exp_backoff_restart_delay.mocha.js │ │ ├── filter_env.mocha.js │ │ ├── fixtures/ │ │ │ ├── auto-restart/ │ │ │ │ └── throw.js │ │ │ ├── exp-backoff/ │ │ │ │ ├── throw-stable.js │ │ │ │ └── throw.js │ │ │ ├── instances/ │ │ │ │ ├── echo.js │ │ │ │ └── http.js │ │ │ ├── json-env-passing/ │ │ │ │ ├── echo.js │ │ │ │ └── ecosystem.config.js │ │ │ ├── tar-module/ │ │ │ │ ├── mono-app-module/ │ │ │ │ │ ├── README.md │ │ │ │ │ ├── ecosystem.config.js │ │ │ │ │ ├── http.js │ │ │ │ │ └── package.json │ │ │ │ └── multi-app-module/ │ │ │ │ ├── README.md │ │ │ │ ├── ecosystem.config.js │ │ │ │ ├── http.js │ │ │ │ └── package.json │ │ │ └── version-test/ │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── flagExt.mocha.js │ │ ├── flush.mocha.js │ │ ├── god.mocha.js │ │ ├── graceful.mocha.js │ │ ├── id.mocha.js │ │ ├── inside.mocha.js │ │ ├── instances.mocha.js │ │ ├── internal_config.mocha.js │ │ ├── issues/ │ │ │ └── json_env_passing_4080.mocha.js │ │ ├── json_validation.mocha.js │ │ ├── lazy_api.mocha.js │ │ ├── logs.js │ │ ├── max_memory_limit.js │ │ ├── misc_commands.js │ │ ├── module_configuration.mocha.js │ │ ├── module_tar.mocha.js │ │ ├── modules.mocha.js │ │ ├── namespace.mocha.js │ │ ├── path_resolution.mocha.js │ │ ├── programmatic.js │ │ ├── reload-locker.mocha.js │ │ ├── resurect_state.mocha.js │ │ ├── send_data_process.mocha.js │ │ ├── signals.js │ │ ├── sys_infos.mocha.js │ │ ├── user_management.mocha.js │ │ ├── version.mocha.js │ │ └── watcher.js │ └── unit.sh └── types/ ├── index.d.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # top-most EditorConfig file root = true # Unix-style newlines with a newline ending every file [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 2 # Makefile [Makefile] indent_style = tab ================================================ FILE: .gitattributes ================================================ * text=auto *.sh eol=lf bin/** eol=lf test/fixtures/** eol=lf ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributing to PM2 ## Pull-Requests 1. Fork pm2 2. Create a different branch to do your fixes/improvements if it's core-related. 3. Please add unit tests! There are lots of tests take examples from there! 4. Try to be as clear as possible in your commits 5. Pull request on the **development branch** from your fork We 'd like to keep our master branch as clean as possible, please avoid PRs on master, thanks! ## Fire an issue When you got an issue by using pm2, you will fire an issue on [github](https://github.com/Unitech/pm2). We'll be glad to help or to fix it but the more data you give the most fast it would be resolved. Please try following these rules it will make the task easier for you and for us: #### 1. Search through issues if it hasn't been resolved yet #### 2. Make sure that you provide following informations: - pm2 version `pm2 --version` - nodejs version `node --version` - operating system - `head -n 50 ~/.pm2/pm2.log` output #### 3. Provide details about your issue: - What are the steps that brought me to the issue? - How may I reproduce this? (this isn't easy in some cases) - Are you using a cluster module? Are you trying to catch SIGTERM signals? With `code` if possible. #### 4. Think global If your issue is too specific we might not be able to help and stackoverflow might be a better place to seak for an answer #### 5. Be clear and format issues with [markdown](http://daringfireball.net/projects/markdown/) Note that we might understand english, german and french but english is prefered. #### 6. Use debugging functions: ```DEBUG=pm2:* PM2_DEBUG=true ./bin/pm2 --no-daemon start my-buggy-thing.js``` If your issue is flagged as `need data` be sure that there won't be any upgrade unless we can have enough data to reproduce. ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ ## What's going wrong? ## How could we reproduce this issue? ## Supporting information ``` # Run the following commands $ pm2 report ``` ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ | Q | A | ------------- | --- | Bug fix? | yes/no | New feature? | yes/no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #1234, #5678 | License | MIT | Doc PR | https://github.com/pm2-hive/pm2-hive.github.io/pulls ================================================ FILE: .github/stale.yml ================================================ # Number of days of inactivity before an issue becomes stale daysUntilStale: 30 # Number of days of inactivity before a stale issue is closed daysUntilClose: 14 # Issues with these labels will never be considered stale exemptLabels: - Inspecting - security - "P3: Medium" - "T: Bug" - "S: Open for PR" - "T: Enhancement" - "T: Feature" - "S: In Progress" - "S: Pending Release" # Label to use when marking an issue as stale staleLabel: stale # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: false limitPerRun: 30 only: issues ================================================ FILE: .github/workflows/node.js.yml ================================================ name: Node.js CI on: [push, pull_request] jobs: node-tests: runs-on: ubuntu-latest timeout-minutes: 30 strategy: matrix: node-version: [16.x, 24.x] steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: 'npm' - name: Install Python run: sudo apt install python3 - name: Install PHP CLI run: sudo apt install php-cli - name: Install Node.js dependencies run: npm install - name: Run end-to-end tests run: npm run test:e2e - name: Run unit tests run: npm run test:unit bun-tests: runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Checkout code uses: actions/checkout@v4 - name: Remove Node.js installed by setup-node action (if any) run: | if command -v node &> /dev/null; then sudo rm -rf "$(which node)" fi if command -v npm &> /dev/null; then sudo rm -rf "$(which npm)" fi - name: Setup Bun uses: oven-sh/setup-bun@v1 - name: Install dependencies using Bun run: bun install - name: Run end-to-end tests with Bun run: bun run test:e2e - name: Run unit tests with Bun run: bun run test:unit ================================================ FILE: .gitignore ================================================ /node_modules *.log *.pid test/child *.iml .idea/** *.heapsnapshot *.cpuprofile .cache-require-paths.json dist/ *.deb *.rpm .DS_Store *.swp *.swo currentTagChangelog.md joblog-X test/fixtures/path-check*.txt yarn.lock *.tar.gz e2e_time unit_time *.heapprofile a.out ================================================ FILE: .mocharc.js ================================================ module.exports = { 'allow-uncaught' : false, 'async-only': false, bail: true, color: true, delay: false, diff: true, exit: true, timeout: 10000, 'trace-warnings': true, ui: 'bdd', retries: 2 } ================================================ FILE: .npmignore ================================================ test apps doc /pres *.log *.pid examples *.pyc .github .cache-require-paths.json *.heapsnapshot packager artifacts .editorconfig .bithoundrc dist ================================================ FILE: CHANGELOG.md ================================================ ## 6.0.14 - Fixed version of @pm2/pm2-version-check #6055 - CVE-2025-64718 Update js-yaml - replace fs.R_OK with fs.constants.T_OK #6012 #6019 ## 6.0.13 - Fix blessed package import ## 6.0.12 - #6037 Drop npm-shrinkwrap in favor of fixed dependencies versions - #5577 fix pm2 monit crash ## 6.0.11 - #6034 replace package-lock.json by npm-shrinkwrap.json - #5915 fix allowing to update namespaced pm2 NPM module (@org/module-name) ## 6.0.10 - revert #5971 #6031 ## 6.0.9 - updates all typescript definitions - upgrade github ci workflows - upgrade mocha dep and adapt tests - bump packages - fix:Potential ReDoS Vulnerability or Inefficient Regular Expression in Project: Need for Assessment and Mitigation #5971 ## 6.0.8 - fix: package-lock update ## 6.0.7 - fix: ansis-node10 https://github.com/Unitech/pm2/commit/99d9224e940d119a1ad5b241b4fc4e0db7c830ed @webdiscus ## 6.0.6 - refactor: replace chalk with smaller alternative by @webdiscus ## 6.0.5 - Bun support - Fixes #5893 #5774 #5682 #5675 #5777 - Disable git parsing by default #5909 #2182 #5801 #5051 #5696 - Add WEBP content type for pm2 serve #5900 @tbo47 - Enable PM2 module update from tarball #5906 @AYOKINYA - Fix treekil on FreeBSD #5896 @skeyby - fix allowing to update namespaced pm2 NPM module (@org/module-name) #5915 @endelendel ## 5.4.3 - Update sub packages ## 5.4.2 - Update sub packages ## 5.4.1 - @pm2/io DeprecationWarning: The util._extend API is deprecated https://github.com/keymetrics/pm2-io-apm/issues/301 @egoroof ## 5.4.0 - #5782 add autostart true||false feature by @ultimate-tester - fix UUID deprecation - updates modules ## 5.3.1 - #5686 Switch from Travis CI to Github Actions - #5680 Fixed reserved keyword for ES6 Strict Mode when Bundling @juaneth - #5683 update badges - #5684 auto switch light and dark mode logos - #5678 Bugfix/deploy ecosystem filename extension / esm module default ecosystem config name @TeleMediaCC - #5660 Fix matching logic for logs from namespace when lines = 0 @bawjensen - fix "vulnerabilities" in axios module ## 5.3.0 - fix: replace non-working condition that blocks flush from clearing the logs #5533 @Sailboat265 - fix: ESM script loader #5524 @BlueWater86 ## 5.2.2 - fix: correct pm2 ls display when there is a (very) long process id (@dko-slapdash) - typo: corrections ## 5.2.1 - fix cluster error avoiding process restart (#5396) - ensure increment_var value is a number (#5435) - update dependencies - add node latest to travis testing ## 5.2.0 - replace node-cron by croner (#5183 #5035) - upgrade mocha deps - fix pm2 report when daemon not running - remove semver check for legacy node.js versions - update node version in setup.deb.sh by using lts (#5201) + openrc - replace legacy util._extend by Object.assign (#5239) - add missing start options types (#5242) - recursive detection of package.json (#5267) - make tarball module uninstall cross-platform (#5269) - Fix unnecessary "ENOENT" console.error when serving a spa (#5272) - fix: used env variable instead of hardcode datetime format (#5277) - copyright update (#5278) - fix: remove constants import from VersionCheck (not needed) (#5279) - Reduce async import (#5280) ## 5.1.2 - easily disable cron-restart strategy via `$ pm2 restart --cron-restart 0` - allow to update cron-restart on restart ## 5.1.1 - remove fast-printf and replace with sprintfjs ## 5.1.0 - add back Node 10.x support - make pm2-sysmonit module optional ## 5.0.3 - skip system monitoring on Windows ## 5.0.1/5.0.2 - fix npm install --no-optional pm2 ## 5.0.0 ### System Monitoring A new local system monitoring feature has been added, allowing to monitor numerous vital server metrics. Most important metrics will be displayed when doing a pm2 ls: ```bash ┌─────┬─────────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐ │ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │ ├─────┼─────────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤ │ 4 │ app │ default │ 1.0.0 │ fork │ 164618 │ 2s │ 1670 │ online │ 0% │ 41.8mb │ unitech │ disabled │ └─────┴─────────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘ host metrics | cpu: 1.6% 42.9º | mem free: 52.0% | wlp0s20f3: ⇓ 0mb/s ⇑ 0mb/s | disk: ⇓ 0.199mb/s ⇑ 0mb/s /dev/nvme0n1p3 88.25% | ``` All server metrics will be available to pm2.io: ``` ┌─────────────────────────────┬──────────────────────────────────┐ │ PM2 CPU Usage │ 0.0 % │ │ PM2 Memory Usage │ 67.4 mb │ │ PM2 Agent CPU Usage │ 0 % │ │ PM2 Agent Memory Usage │ 0 mb │ │ CPU Usage │ 1.5 % │ │ CPUs Usage │ 0|0|0|0|2|0|0|1|0|0|0|1|0|1|2|8 │ │ CPU Temperature │ 42.9 °C │ │ RAM Total │ 15.34 gb │ │ RAM Free │ 1.18 gb │ │ RAM Active │ 7.35 gb │ │ RAM Available │ 7.99 gb │ │ RAM Usage │ 47.9 % │ │ FD Opened │ 15072 │ │ Disk Writes │ 0 mb/s │ │ Disk Reads │ 0.24 mb/s │ │ Disk Usage │ 88.25 % │ │ Disk Size │ 465.60 gb │ │ Total TX │ 0.005 mb/s │ │ Total RX │ 0.004 mb/s │ │ fs:use:/dev/nvme0n1p3 │ 88.25 % │ │ fs:size:/dev/nvme0n1p3 │ 465.60 gb │ │ net:tx_5:wlp0s20f3 │ 0.005 mb/s │ │ net:rx_5:wlp0s20f3 │ 0.004 mb/s │ │ net:rx_errors_60:wlp0s20f3 │ 0 /min │ │ net:tx_errors_60:wlp0s20f3 │ 0 /min │ │ net:rx_dropped_60:wlp0s20f3 │ 0 /min │ │ net:tx_dropped_60:wlp0s20f3 │ 0 /min │ │ graphics:mem:total │ 3878 mb │ │ graphics:mem:used │ 1260 mb │ │ graphics:temp │ 46 °C │ └─────────────────────────────┴──────────────────────────────────┘ ``` #### Disabling system monitoring ``` # Disable system monitoring pm2 set pm2:sysmonit false # Enable system monitoring pm2 set pm2:sysmonit true ``` ### PM2.io bandwidth reduction Data quantity sent from PM2 to PM2.io has been reduced by 80%, thanks for a json patch differential system. Much more data can now be exposed (metrics, actions) to PM2.io ### Other fixes - feat: added args and full script path to monitoring data - fix: regular local ip check - fix: pm2 agent watchdog has been consolidated and fixes freezing issues - fix: modules bumped ## 4.5.6 - all submodules version bumped - supress all "security" warnings ## 4.5.5 - bump debug - remove systeminformation ## 4.5.4 - drop non used package ## 4.5.3 - bump vizion from 0.2.13 to 2.2.1 (Snyk CVE) - bump chokidar to 3.5.1 ## 4.5.2 - bump @pm2/js-api ## 4.5.1 - fix: cron in cluster mode was not restarting process after one pass - fixes #4834 #4733 #4307 #4834 - fix: restore --sort option on `pm2 ls` - fix #4536 - fix: fix tests with npm7 - fix: restore warning message about process list changed and not dumped - chore: alias `--cron` with `--cron-restart` - chore: test PM2 againt Node.js 15.x - chore: upgrade systeminformation and debug module to latest - PR #4892 by @AdamMajer - chore: drop Node 9.x from travis testing - chore: remove ps-list module ## 4.5.0 - fix: creating log folder in sync mode. #4846 - PR #4844 by @QS20199 - fix: PM2 unable to run ESM packages: ERR_UNSUPPORTED_ESM_URL_SCHEME #4839 - PR #4841 by @ox-harris - fix: Use opts.namespace if it was passed in. Previously, it was ignored. #4778 - PR #4791 by @sbleon - fix: Prevent reloadLogs command from hanging. #4781 - PR #4782 by @mbrooks and Franck Danard - fix: backward compatibility fix for tarball modules - PR #4767 by @ykolbin - fix: Operation not permitted on call setgid #2957 - PR #4681 by @guard43ru - chore: upgrade dependencies ## 4.4.1 - feature drop: autodump fixes #4763 - fix: fix starting pm2 script from inside an app #4761 (need use PM2_PROGRAMMATIC) ## 4.4.0 - feature: support Node v14.0 ## 4.3.1 - fix: downgrade vizion ## 4.3.0 - feature: reduce by 1/3 pm2 package size (drop moment, lodash) - feature: pm2 start app.js --filter-env NODE_ENV #4596 - feature: print logs of a particular namespace #4517 @bvsubhash - feature: trigger all the process using 'all' or trigger all processes in a particular namespace #4518 @bvsubhash - feature: support ecosystem.config.cjs #4662 @Timic3 - fix: disable log (--error /dev/null --output /dev/null) on Windows #4560 @codpot - fix: pm2 install adaptation #4593 @adunkey - fix: add type for pm2.reload with optional options parameter #4615 @kevindoveton - fix: add `ignore_watch` to StartOptions types #4629 @jlvcm - fix: fix --cwd CLI option description #4639 @warpdesign - fix: do not require cron if not necessary in CLI - fix: upgrade mkdirp to 1.0.4 (sec vuln) #4638 - chore: bump mocha to latest + drop mocha.opts + add .mocharc.yml - chore: pm2 init ecosyst file template changes - chore: pm2-deploy@1.0.2 (revert fix) - chore: pm2-io-apm@4.3.4 (instant trace + broadcast trace threshold + boolean metrics support) ## 4.2.3 - fix: Fix an import error on Node 9.x ## 4.2.2 - feat: Support ESM on Node 13 via .mjs file or `"type":"module"` in package.json #4540 - fix: Fix an error for pm2 path on Windows. #4563 ## 4.2.1 - fix: col size too small for certain app name with tracing enabled - chore: check for pm2 updates ## 4.2.0 - feature: `pm2 logs --highlight ` to highlight specified string when using `pm2 logs` #4013 by @bvsubhash - feature: `pm2 sysmonit` activate system wide monitoring && `pm2 sysinfos` display system informations - feature: new App Namespace feature via `namespace` attr or `pm2 start app.js --namespace ` #3471 by @bvsubhash - feature: enforce message sending for Windows Graceful Shutdown via `pm2 start app.js --shutdown-with-message` to use message sending for specific process or via `PM2_KILL_USE_MESSAGE=true pm2 update` to default this behavior on PM2. #3561 #3691 #3555 #4469 #4470 #4474 by @aleksk and @8ai - feature: `pm2 ls` now display a hint when process list differ from dump file - fix: if id > 100 not shown in pm2 ls #4475 - fix: stop and delete cron immediately on deletion of a process #4492 by @bvsubhash - fix: display correct username by @bvsubhash - chore: add test on node 13.x - chore: cleanup some unused files - chore: enforce node >= 8.10.0 ## 4.1.2 - fix: temporarily disable system metrics retrieval ## 4.1.1 - fix: #4452 disable network collection metric ## 4.1.0 - fix: #4270 fix ANSI escape for `pm2 ls --watch` - fix: `pm2 start app.js -i 1` start app in cluster mode - fix: #4254 add HOST environment variable on pm2 serve - fix: #4267 Allow usernames in uid/gid/user again - fix: #4376 make process.send('ready') trigger sigint - fix: #4443 allow pm2-runtime to auto-exit even if modules are still running - fix: #4364 typos - fix: #4288 add 'max' type in typescript definition ## 4.0.2 - fix: #4450 do not open sysinfo window on Windows ## 4.0.1 - chore: switch Systeminfo logging to debug ## 4.0.0 - feat: make pm2 fully standalone with node embedded - feat: startup, npm, node system adaptation for standalone installs - feat: system information worker - retrieve: - network I/O + latency - disk I/O + space - cpu usage + temperature - memory usage - intelligent display of information (e.g. display disks > 80% cpu usage) - feat: listing of docker container on host machine with independent pm2 list - feat: upgrade of Chokidar to 3.x - Massive CPU & RAM consumption improvements for watch feature - r&d: manage container like pm2 processes - feat: intelligent display of high loaded processes in an independent pm2 list - feat: #4224 --ignore-watch now accepts globs (@vaskevich) - feat: pm2 save --force allow to force save of empty process list - fix: pm2 monit dashboard without leaks - fix: pm2 register fixed - refactor/fix: pm2 listing systems refactoring - chore: remove old legacy code for < 8 Node.js versions - chore: make the repo lighter - chore: better display when pm2-runtime is linked to on-premise - chore: pm2 CLI refactoring #4391 ## 3.5.0 - feat: #4220 #2941 #4196 improve pm2 serve for SPA - autoredirect requests to index.html if --spa - feat: on pm2 show , display metric unit and divergent environment variables - feat: #4157 tweak systemd script to auto restart PM2 in case of crash failure - fix: #4212 on pm2 show, avoid crash when versioning comment is not present - fix: #4171 fix pm2 list when small screen - fix: #4197 fix pm2 unstartup for macOS - fix: #2764 in pm2 monit, only display log of selected application - fix: #2793 pm2 monit, rolling log buffer, avoid crash and performance issues - fix: #4060 do not emit online when application is errored - chore: remove nssocket in dependencies ## 3.4.1 - fix: allow pm2 register / pm2 monitor - fix: restore trace indicator ## 3.4.0 - use @pm2/io version 4 - disable @pm2/io for node.js v4 and v5 /!\ Warning, built-in custom metrics are not supported anymore on Node 4 and 5 New builtin metrics when starting a Node.js application: - Heap Size - Heap Usage - Used Heap Size - Active Requests - Active handles - Event loop latency - Event loop latency p95 - HTTP queries per minutes - HTTP Mean Latency - HTTP P95 Latency ## 3.3.1 (18/02/19) - add pm2 profile:cpu [timeout] - add pm2 profile:mem [timeout] ## 3.3.0 (14/02/19) - Upgrade pmx to ^3 ## 3.2.9 (17/01/19) - #4128 fix: force detached process ## 3.2.6-8 (11/01/19) - rollback: node bin path handling adaptation ## 3.2.5 (09/01/19) - feat: enhance pm2 report - feat: support snap Ubuntu system - fix: pm2 register/monitor command - fix: consolidate spawn function on unhealthy systems to avoid pm2 crash - fix: error message if extra lang interpreter are not installed when runing tests - fix: (pm2 deploy) command line bug when passing env variables to post-deploy hook - fix: (pm2 deploy) always deploy to default branch problem - fix: (pm2 deploy) pm2 deploy exec now accept multiple commands - fix: print full env + skip extra internal fields when using programmatic pm2 ## 3.2.4 (19/12/18) ### Feat - display cron configuration when doing `pm2 desc ` - refactor test suite (benchmark/simplification) ### Fix - pm2 flush flush only one app - resolve uid properly on pm2 ls / pm2 desc - keep wait_ready option on process reload - keep stringification of environment variable behavior - return an error when using pm2 api on starting json configuration if one app is errored ## 3.2.3 (4/12/18) ### Fix - medium rare bug: fix issue when acting on process file (#3987 + #3192) - concurrent action to 1 if acting on only 2 processes - fix cluster syntax - add more test on port release ## 3.2.2 (5/10/18) ### Fix - minor bug: fix bug when passing -i 'max' or -i 0 ## 3.2.1 (3/10/18) ### Fix - minor bug: get internal pm2 config after creation on new pm2 boots ## 3.2.0 (3/10/18) ### Features - package.json version field retrieval and display in pm2 ls, pm2 show, pm2 monit - pm2 internal configuration system via `pm2 set pm2:key value`, attached to pm2.user_conf - add the .user field (CLI + Config) to set the user to start the application with - add the .time field (CLI + Config) to enable default logs date prefix - max_memory_restart now triggers a reload - pm2 env command to display the environment the application is running with - exponential backoff restart delay via `--exp-backoff-restart-delay ` with reset mechanism - new timing library on PM2 daemon (increase log througput, reduce CPU usage and memory usage) - better user management system with username resolution to uid - websocket default switch for pm2 plus - new module management system (`pm2 package `, `pm2 publish `, `pm2 install `) ### Fix - @pm2/io 2.4 (restart > 10.0) - restart behavior tested - fix module version parsing - module system refactoring (TAR + NPM) - fix watch_delay in config file ## 3.1.3 (20/09/18) ### Features - allow non-node application to run multiple instances without auto switch to cluster mode - allow to call `pm2 logs` even without application (#3820) - switch `pm2 link` and `pm2 plus` protocol to websocket by default instead of axon - enhance the `pm2 init` template that generates ecosystem files by adding some extra fields ### Fix - remove deprecation message for node 0.10 - pm2 login/register/monitor now hit the new oauth pm2 plus system ## 3.1.2 (10/09/18) - version bump on @pm2/io ## 3.1.1 ( Mon Sep 10 2018 16:18:25 GMT+0200 (CEST) ) ## Hot Fixes - #3901 fix error when installing module ([7b43fea5](https://github.com/Unitech/pm2/commit/7b43fea55d7c2853a3032b3bddd12201cd6a29e9)) ## 3.1.0 ( Mon Sep 10 2018 10:25:13 GMT+0200 (CEST) ) ## Bug Fixes - tmp fix io@beta + rename metric ([04ab7ac4](https://github.com/Unitech/pm2/commit/04ab7ac4e1312c5a5332f37cbb81b0d98686936d)) - remove ending \n on git version comment ([9a36bfeb](https://github.com/Unitech/pm2/commit/9a36bfeb7e9f5ab1719ca3858510da08bb0cad6b)) - #3883 fix typings for max_memory_restart and add wait_ready ([b35ea237](https://github.com/Unitech/pm2/commit/b35ea237e3b448088112b2f3a771a9c5286417a7)) - restore monitored indicator ([34966432](https://github.com/Unitech/pm2/commit/349664329eb56232321694be9e08f16a3cda6fbd)) - remove install of modules on pm2 plus command ([6a8bb269](https://github.com/Unitech/pm2/commit/6a8bb26952a7dcf109d28af7224b89faf0977a71)) - invert kill/link ([3c37b528](https://github.com/Unitech/pm2/commit/3c37b5283bf0dea130fd375a5563974bd84543a9)) - #3877 #3831 ([16f4f2bc](https://github.com/Unitech/pm2/commit/16f4f2bc6589e8f0666f46d37c3f7f7739de7261)) - #3865 ensure pm2 never run simultaneous gracefullExit, prevent dump file corruption ([79679db1](https://github.com/Unitech/pm2/commit/79679db1b321bbcc7296dbc41d005500cf61d273)) - #3786 fix issue when triggering an action that does not exist ([1ff7fd3d](https://github.com/Unitech/pm2/commit/1ff7fd3d49ccaf3f65540774426b62fdc811e4f1)) - fixed unstartup when launchd ([3d0461c3](https://github.com/Unitech/pm2/commit/3d0461c3e0a2362aef009e6f158b6f16b3d6510c)) - access gl_retry as class property ([bbcb2b6b](https://github.com/Unitech/pm2/commit/bbcb2b6b5c5fa0ef872b64a648461c266350423a)) - #3831 switch registerToKM() to register() ([8df2451e](https://github.com/Unitech/pm2/commit/8df2451e05bf5494b11f0546965718efe1f351b9)) ## Features - add id column in stacked mode (80 char mode) ([83033d4c](https://github.com/Unitech/pm2/commit/83033d4cdeb899bc4c1d1fe7a8c6391e64e9d0d0)) ## Refactor - only enable deep monitoring if explicitly stated ([f67e14f0](https://github.com/Unitech/pm2/commit/f67e14f0bd6d65bff6ef8f7e27e3f0aa93c60e40)) - #3786 clean code ([6cbca8bc](https://github.com/Unitech/pm2/commit/6cbca8bccc0126f1557bf8326c81facc62100704)) - removes unused imports. ([b8b48e83](https://github.com/Unitech/pm2/commit/b8b48e83f7f041508e39815e22501509259d4f26)) - only import the necessary methods from async. ([6466ee44](https://github.com/Unitech/pm2/commit/6466ee44c1b85858f9b7e56b01aa6f2a08bde508)) - removes unused async imports. ([679b14ff](https://github.com/Unitech/pm2/commit/679b14ff4b24519b5479c9e5f4ce0d9c32e39e55)) ## Chore - upgrade to 3.1.0 ([0285d12d](https://github.com/Unitech/pm2/commit/0285d12df335667e9e0311a7abe175796bb517f4)) - update apm version ([cc27de4a](https://github.com/Unitech/pm2/commit/cc27de4a8b400f1c20ba2e4b12dadcef1dd34fae)) - README update ([c505dcc1](https://github.com/Unitech/pm2/commit/c505dcc1685380728b23f8757aa80fa4387d7fd3)) - remove unused console.log ([61e32a43](https://github.com/Unitech/pm2/commit/61e32a4305490cc64c0a40cd83e2ad48c133b272)) - upgrade vizion to 2.0.2 ([c231e286](https://github.com/Unitech/pm2/commit/c231e28604aa4628d8f8ba10ea1f9f82e73269e6)) - #3415 try to update vizion to 2.0.1 ([9b80d8c1](https://github.com/Unitech/pm2/commit/9b80d8c1b69c07d21e63441c266b7acafffe0673)) - #3415 try to update vizion to 2.0.0 ([2c3df093](https://github.com/Unitech/pm2/commit/2c3df09378a92bac9de2d3b3b83103e02bd1bb82)) - update readme with 3.0.3 commits ([476542fb](https://github.com/Unitech/pm2/commit/476542fbad038b951b6cfe6d6903d7b6bc8540a5)) ## Branchs merged - Merge branch 'master' into development ([95321c6d](https://github.com/Unitech/pm2/commit/95321c6dd2602e9ef71028731fd7a2e7b40a0d3c)) - Merge branch 'master' into development ([c3c0e423](https://github.com/Unitech/pm2/commit/c3c0e423f9beeab25f53c0267d5f8a9e79d5c2e3)) - Merge branch 'master' into development ([8e6481bc](https://github.com/Unitech/pm2/commit/8e6481bc9a6d23283895bf9cd3c7831c49a811ae)) - Merge branch 'development' into development ([83294afe](https://github.com/Unitech/pm2/commit/83294afee7cf0204208e9cc7f4cf687469556492)) - Merge branch 'development' into flag--ext ([79ab9242](https://github.com/Unitech/pm2/commit/79ab92425fef22cdf679fa77840d86a6e7cfc755)) - Merge branch 'development' into post_install ([d5604300](https://github.com/Unitech/pm2/commit/d5604300685ace1c7dbd18776fd3df79da96f638)) ## Pull requests merged - Merge pull request #3885 from Unitech/typings ([19a35e9b](https://github.com/Unitech/pm2/commit/19a35e9b23716df8f7d1301acf7b0f0b601f93dd)) - Merge pull request #3878 from cuspymd/fix-command-help ([2d3d2044](https://github.com/Unitech/pm2/commit/2d3d204427ce02617aa134ca0831a844de1a697d)) - Merge pull request #3876 from Unitech/lost_apps_sigterm ([4fa247a3](https://github.com/Unitech/pm2/commit/4fa247a3e370607cf4198743de41dfa0a94bfbb5)) - Merge pull request #3874 from Unitech/trigger_no_action ([e868f003](https://github.com/Unitech/pm2/commit/e868f003e3063a57236cb8d0ead33af808e0df70)) - Merge pull request #3872 from Unitech/column_id_stacked ([55b6ccc3](https://github.com/Unitech/pm2/commit/55b6ccc32ae02e574ec1f80a36b4531761b94777)) - Merge pull request #3723 from livankrekh/development ([98f49dc3](https://github.com/Unitech/pm2/commit/98f49dc393efd1fed03a1ef8a5752c0e490dd4b8)) - Merge pull request #3821 from imarakho/post_install ([4217b150](https://github.com/Unitech/pm2/commit/4217b1505419904252d0ae7640a51128a2459d98)) - Merge pull request #3823 from imarakho/flag--ext ([cc68dc1f](https://github.com/Unitech/pm2/commit/cc68dc1f9faf010af0648992193230af609413c5)) - Merge pull request #3822 from imarakho/flush_parameter ([bbcc85a4](https://github.com/Unitech/pm2/commit/bbcc85a41683f5fa573bf504894f8e817c89784a)) - Merge pull request #3807 from medanat/minimize-async-lib-footprint ([7e92855f](https://github.com/Unitech/pm2/commit/7e92855ff5c394b5452db526d21262e343b89ef8)) - Merge pull request #3829 from soyuka/patch-pidusage ([a668f576](https://github.com/Unitech/pm2/commit/a668f5762190061dd05de5c5d888b53f35fa386e)) ## 3.0.3 ( Tue Aug 07 2018 23:35:05 GMT+0200 (CEST) ) ## Bug Fixes - pm2 plus + register ([277ec6ba](https://github.com/Unitech/pm2/commit/277ec6ba8d1cdda7f8fdf11eb9d9d33c2c095d65)) ## 3.0.2 ( Tue Aug 07 2018 23:35:05 GMT+0200 (CEST) ) ## Bug Fixes - allow tracing activation ([f297ef1e](https://github.com/Unitech/pm2/commit/f297ef1ebbec292aedcfa48c27e3f31b8f206633)) ## Branchs merged - Merge branch 'development' ([80c94dd3](https://github.com/Unitech/pm2/commit/80c94dd3261544f627612ce4b541356e4adbc51f)) ## 3.0.1 ( Mon Jul 23 2018 14:13:35 GMT+0200 (CEST) ) ## Bug Fixes - allow to set a name via pm2 link ([ebffb609](https://github.com/Unitech/pm2/commit/ebffb609cf4da195c72ee67d8341c63b78f0654e)) - disable network monitoring as long as ampq not supported ([ae1547bf](https://github.com/Unitech/pm2/commit/ae1547bfa9505b2d13e30df39ce614eee29463b0)) - display error message from pm2-deploy ([9171b810](https://github.com/Unitech/pm2/commit/9171b81024641c3e104f3eeb2e2c6eb852dbe7f4)) - protect geteuid/getegid from being called on windows #3793 ([0495bd8e](https://github.com/Unitech/pm2/commit/0495bd8e4ffaeb1db729b35fa569696145d79c5f)) - put message module at the right level ([56f5e047](https://github.com/Unitech/pm2/commit/56f5e04787da29e8b582bf4fa8325f72404a2fbe)) - do not ignore child pres folder ([10ee9987](https://github.com/Unitech/pm2/commit/10ee99876d75679723e1e8522da07413a618e48c)) - let->var ([89e2a125](https://github.com/Unitech/pm2/commit/89e2a125c22aee27014c279c86d1d9e0a0df0235)) - method renaming ([f3faa3d8](https://github.com/Unitech/pm2/commit/f3faa3d846d1e895232743dd619f5ecb15fdf7ad)) - path ([4f980550](https://github.com/Unitech/pm2/commit/4f9805508d2c1c575aabc4abbab25728f1c6a28a)) - #3791 mitigate pidusage errores ([88551b8c](https://github.com/Unitech/pm2/commit/88551b8cfe8bf8dd330d582e71b808faadfaf161)) - pm2 plus ([9bc34e56](https://github.com/Unitech/pm2/commit/9bc34e56b7ad66cbc6efbd26d4017f1e1813a720)) - #3764 ([3a582b42](https://github.com/Unitech/pm2/commit/3a582b42f9cca57779b99964c95a2cd0516efa11)) - drop coffee-script (installed via pm2 install coffeescript) ([76ceb2fd](https://github.com/Unitech/pm2/commit/76ceb2fd52a2e5acbf03deacc3fa8a120a197023)) - restore no_interaction for pm2-dev ([902e5a5a](https://github.com/Unitech/pm2/commit/902e5a5a1225d2072ab6337aa067caf9c6a7cca4)) - option -w doesn't work ([165a05c8](https://github.com/Unitech/pm2/commit/165a05c854f9b3dd1418b988c954d333f81ba88f)) - retab shell script to use space for indent consistency ([e3b4327d](https://github.com/Unitech/pm2/commit/e3b4327d9a6120c5ad589734ca926d3b49a8b706)) - set Makefile indent to tab instead of common space ([4db0ae01](https://github.com/Unitech/pm2/commit/4db0ae011c161cbfca9e250da40deff9fdc36069)) - set yaml file indent to 2 spaces instead of 3 ([e4ecb0b2](https://github.com/Unitech/pm2/commit/e4ecb0b29dbcc4c6ca2d67b6bdc7da4c0a5d17a5)) - remove trailing spaces ([5c115983](https://github.com/Unitech/pm2/commit/5c1159832680231bff5da79f1c91caf32ce3b5e0)) - fixes #3735 ([0548cb82](https://github.com/Unitech/pm2/commit/0548cb82aa1193a5725ca22e1babfc38db2e3b77)) ## Hot Fixes - fix #3767, do not consider as a command if space and slash are found ([d15a12ce](https://github.com/Unitech/pm2/commit/d15a12ceae8b0c9c27625180ae002178b0bfe5d0)) - fix #3767, do not consider as a command if space and slash are found ([f8ec1503](https://github.com/Unitech/pm2/commit/f8ec1503c3e92bc0dec10d395ac682b116e2914e)) ## Features - add inspector for node 10 and heap snapshot ([dc61bca6](https://github.com/Unitech/pm2/commit/dc61bca66828c16cf6fd04a6f749f127da697cec)) - pm2 plus xx yy now generates a name with hostname-UID ([fcf75e2c](https://github.com/Unitech/pm2/commit/fcf75e2cc321791273f6afe86c07fd147c6e8414)) - #3757 --only='app1,app2' ([bea98330](https://github.com/Unitech/pm2/commit/bea983306c4736d3a2b1090f2708b7b29c44ed03)) - pm2 plus cli ([1da6edde](https://github.com/Unitech/pm2/commit/1da6edde80e3029d99084992ec1a4ada7b2cc279)) - reload all apps after connection to pm2 plus ([35a1ed2a](https://github.com/Unitech/pm2/commit/35a1ed2a1328a859a7797ec8e22024d171599d86)) - ask to install module after connection with KM ([68e87b39](https://github.com/Unitech/pm2/commit/68e87b39ae2b57e9fbb0b0abde68112c839f05ee)) - with pm2 plus command ask to install modules ([28c61716](https://github.com/Unitech/pm2/commit/28c61716ee5e8f2402205e4b06ed7ee0a942a3cc)) ## Test - test with development packages ([d361c840](https://github.com/Unitech/pm2/commit/d361c8405db47969bd68c7b1058a54f38e8e0e52)) ## Chore - clean old snapshot method ([d064750b](https://github.com/Unitech/pm2/commit/d064750be0d437945efdcd6a5ce4e56547b1bce6)) - update version to 3.0.1 ([efbcb021](https://github.com/Unitech/pm2/commit/efbcb02180ae38dd930e43282113dbcb24288eab)) - bump to 3.0.1 ([fb8357e3](https://github.com/Unitech/pm2/commit/fb8357e32f9f015e5b6e7ed8ef150f59de382c6d)) - new ascii logo + refactor pm2 plus command ([8692a1da](https://github.com/Unitech/pm2/commit/8692a1daf7b4b7dfb8a4d6ec3363ac0cc62203a8)) - change motd.update + alias register to pm2 plus ([cdc4a767](https://github.com/Unitech/pm2/commit/cdc4a767d5f1ff5873d0466b471daa3006608604)) - btn ([319fa0dc](https://github.com/Unitech/pm2/commit/319fa0dcbea331a88a9888c207368e52665309ce)) - README button ([1c6fb68c](https://github.com/Unitech/pm2/commit/1c6fb68c758d76cf81e53c43c2423ecd742265e5)) - remove duplicate configs in .editorconfig ([86ad52b8](https://github.com/Unitech/pm2/commit/86ad52b837e23a7ec92705d21a152394c244571f)) ## Branchs merged - Merge branch 'development' into uid-gen ([5324c878](https://github.com/Unitech/pm2/commit/5324c878fd0d37e068bc25c8e37f19f73bfebf30)) - Merge branch 'master' into development ([7d04f638](https://github.com/Unitech/pm2/commit/7d04f63835845e92d32d6ad7ffab166a2954302f)) ## Pull requests merged - Merge pull request #3811 from Unitech/memory_inspector ([62018044](https://github.com/Unitech/pm2/commit/62018044d7a1ef7fd0b37fe3082da4bf05989de0)) - Merge pull request #3801 from vkotovv/grammar-fixes ([9bb37a66](https://github.com/Unitech/pm2/commit/9bb37a662a91369caaa5a1a43751541e41970a51)) - Merge pull request #3799 from Unitech/refactor-agent ([bcc4fea8](https://github.com/Unitech/pm2/commit/bcc4fea80885ce941e11b17936aab6582660fc7f)) - Merge pull request #3787 from Unitech/multi-only ([ea5d74a8](https://github.com/Unitech/pm2/commit/ea5d74a87f6911b238634419665c716bc877be10)) - Merge pull request #3788 from Unitech/uid-gen ([f70444f3](https://github.com/Unitech/pm2/commit/f70444f39b7cc8fe05faf57dac1b46fc15a2053c)) - Merge pull request #3784 from Unitech/pm2-plus-cli ([e8c13c37](https://github.com/Unitech/pm2/commit/e8c13c374dfeabf42f75af50b838adb7ac4a50aa)) - Merge pull request #3780 from Unitech/plus_modules ([466d2701](https://github.com/Unitech/pm2/commit/466d2701ca48d0c4b8466d6867135e43b22deeb5)) - Merge pull request #3768 from Unitech/spaces ([0477354b](https://github.com/Unitech/pm2/commit/0477354b502aef612012e833bd47ce1940da1a0b)) - Merge pull request #3771 from chinesedfan/patch-2 ([8de987a6](https://github.com/Unitech/pm2/commit/8de987a604679774ec39e7d5a1a905556524c53d)) - Merge pull request #3762 from shaharmor/issue-3441 ([429e455d](https://github.com/Unitech/pm2/commit/429e455db96d2a56448a11b7602333324c9bf433)) - Merge pull request #3761 from PeterDaveHello/fix-sh-indent-style ([24cddc25](https://github.com/Unitech/pm2/commit/24cddc257734beebb33ee5abac5a4107a5d86093)) - Merge pull request #3737 from morugu/add-node-env-output ([6628f163](https://github.com/Unitech/pm2/commit/6628f1637497771bbc5c4f0ba0e9423c63660e0e)) - Merge pull request #3743 from vivex/master ([06872c25](https://github.com/Unitech/pm2/commit/06872c2520f73bcabb6198a96c4dafb46706c9e9)) - Merge pull request #3748 from JimiC/support_nvm4win ([2dac235b](https://github.com/Unitech/pm2/commit/2dac235bc8956d170fee2341517739d3781048d7)) - Merge pull request #3752 from PeterDaveHello/upstart.tpl ([d4e66e3a](https://github.com/Unitech/pm2/commit/d4e66e3a9d954ab5c15d5bc35910cdfb71ba8321)) - Merge pull request #3753 from PeterDaveHello/fix-editorconfig ([d1478680](https://github.com/Unitech/pm2/commit/d1478680325822c206afbcb197a9a732318f6d64)) - Merge pull request #3754 from PeterDaveHello/remove-trailing-space ([b660f03e](https://github.com/Unitech/pm2/commit/b660f03eba71bb80a1a3d313be4525160727921f)) ## 3.0.0 ( Wed Jun 20 2018 11:06:21 GMT+0200 (CEST) ) ## Breaking changes - merge_logs is now activated by default if not in cluster mode. Logs will not be suffixed by the pm_id if only one app is started ([ae02adf6](https://github.com/Unitech/pm2/commit/ae02adf63f70ceb3bf101be968996ca68d9ce277)) - Drop support for node 0.12 - Drop gracefulReload command - Remove Interactor from PM2 source code - Replace pmx with [pm2-io-apm](https://github.com/keymetrics/pm2-io-apm) ## Bug Fixes - return the configuration and allow custom conf to override default values ([37dc7de1](https://github.com/Unitech/pm2/commit/37dc7de11e930aa4fce6a485e892f11ee714acd6)) - add use strict for node 4 compatibility ([ba2ee3b1](https://github.com/Unitech/pm2/commit/ba2ee3b1ea9aa5fa665e706b3d49a205eac44d53)) - #3605 fix parameters definition, don't use camelcase for properties ([c8616276](https://github.com/Unitech/pm2/commit/c8616276e4e08b4d90a742e219372e775bb81098)) - #3695 change version check method in order to make it work with alpha/beta versions ([052d6c55](https://github.com/Unitech/pm2/commit/052d6c55df0e941e1dd11430bbcbcaa34061a06e)) - deprecated warning on isbinaryfile ([db09275f](https://github.com/Unitech/pm2/commit/db09275f8e353e257c89e12fed754236b15cee74)) - #3688 test adaptation + pm2 serve --port option ([f0249684](https://github.com/Unitech/pm2/commit/f0249684bcbfdb75749a516f447c8e8d32020709)) - startup script issue 18.04 #3645 ([ff1a7f31](https://github.com/Unitech/pm2/commit/ff1a7f315bfee38eb9fd9cdd63efcc0d971585f8)) - that this - uncache node_modules ([294038d7](https://github.com/Unitech/pm2/commit/294038d76272a915e3addc67d3694717a9f7d704)) - verify default conf variable via package.json on public module ([157b106d](https://github.com/Unitech/pm2/commit/157b106df78af1d28d37bbea069b926de4dceca5)) - bug because of const ([56f05a90](https://github.com/Unitech/pm2/commit/56f05a900b03fb0c8dd635aede666c7d2f213271)) - do not run two pm2 para cmds ([3274132b](https://github.com/Unitech/pm2/commit/3274132b866ba5c93d5786e755acbada922f5f1e)) - version ([3ec178e5](https://github.com/Unitech/pm2/commit/3ec178e577e79730aae02c913301cd905ea8ce52)) - re-enable agent tests ([e6febcd7](https://github.com/Unitech/pm2/commit/e6febcd70dd0f1e68b74df8563d3046ee3b32b89)) - test/display summary ([b075e6d0](https://github.com/Unitech/pm2/commit/b075e6d09b09ff371adf045dc5079bb8ef82f1cf)) - skip interactor tests ([36c4d6bc](https://github.com/Unitech/pm2/commit/36c4d6bca7445b46afc1236dc8ab4b8bf921148b)) - remove unused tests ([234c6314](https://github.com/Unitech/pm2/commit/234c63143e723a508796bc1d323c7241979bf4c2)) - add missing libraries in travis ([88fbb845](https://github.com/Unitech/pm2/commit/88fbb84597cee7029ce33f5b7e20e45f5a815b4b)) - remove unused variable when trying to use tracing ([3aeeba02](https://github.com/Unitech/pm2/commit/3aeeba02f628bf4f19e8d5b93657fd94a6ef0ec7)) - remove useless tests from .sh ([e0be81c8](https://github.com/Unitech/pm2/commit/e0be81c86c7defb5e7a271edd5cc37f960c6aa69)) - conflict ([e13f39c9](https://github.com/Unitech/pm2/commit/e13f39c90b6a5e803c59c5424332520564703f5c)) - fix bug with interpreter args ([b26efa0d](https://github.com/Unitech/pm2/commit/b26efa0d4cd72cf04762df7b7d2eaddc4f4117d2)) - improve error message if action has failed ([d9f44f17](https://github.com/Unitech/pm2/commit/d9f44f170f115c2d6dfb6a7fe71dc31bd7fb66fb)) - use polyfill module for copySync with node 4.x ([bc07f43b](https://github.com/Unitech/pm2/commit/bc07f43b115066f6077606df8f59379777f2a917)) - improve error message if action has failed ([dacc6542](https://github.com/Unitech/pm2/commit/dacc654207cbe494af0d12a3f9f27c3b16541802)) - solve empty list when no process and try to update pm2 ([89511846](https://github.com/Unitech/pm2/commit/8951184688c720ded5b4b46bd5b393c3793f9b03)) - #3485 fix issue when there is empty dump file ([f2523f6a](https://github.com/Unitech/pm2/commit/f2523f6a6b9d8b61ba6ace7b89a0353bee76360b)) - #3456 use homedir() instead of process.env.HOME, make module installation work on windows ([1e001732](https://github.com/Unitech/pm2/commit/1e0017325fc8cf658263fb4e02c7bf8912f422b3)) ## Features - add support for openbsd rc.d init scripts ([fdeb0c32](https://github.com/Unitech/pm2/commit/fdeb0c327afd91b113b214c4c4de187848f9f1cb)) - add kill_retry_time argument ([b2cc0031](https://github.com/Unitech/pm2/commit/b2cc003114b44f1a9a31876ee4a2f4cb91e210b3)) - **bin/pm2** - improve usage ([2c310084](https://github.com/Unitech/pm2/commit/2c310084453dd7b1546957e59b1fc7ef964d425b)) ## Refactor - use @pm2/js-api for login/register on pm2.io via CLI ([cb6521ac](https://github.com/Unitech/pm2/commit/cb6521ac32f4737c42fc97fef972960bfe16c829)) - keymetrics examples ([109b331d](https://github.com/Unitech/pm2/commit/109b331ddf37e061d1890ef952f4cd167ce53f64)) - faster cli with less require ([ee5e6a06](https://github.com/Unitech/pm2/commit/ee5e6a06cbf93f2d1fa7fa022d6bdcad55a39695)) - replace fs-extra with node calls ([4576b4c9](https://github.com/Unitech/pm2/commit/4576b4c97bc685c9d774018d6b29c918abd7cb8d)) - centralize SECRET/PUBLIC/MACHINE_NAME + change some wordings ([d0a2a30e](https://github.com/Unitech/pm2/commit/d0a2a30e4110496b178199fb33e026d6402dd00d)) - remove test deported to keymetrics-agent ([299a52a2](https://github.com/Unitech/pm2/commit/299a52a253d70edcde23cbd7e0c201d492984df4)) - parallel test v1 ([08612de5](https://github.com/Unitech/pm2/commit/08612de5b7893a004ae33ed77fcb2ee3ff7b2251)) - e2e test rewrite ([2b9ffd4e](https://github.com/Unitech/pm2/commit/2b9ffd4eb493f1ff32c979e3811f4f1fedfae97d)) - drop gracefullreload ([bb57c76d](https://github.com/Unitech/pm2/commit/bb57c76d4191343925013d4353299092d80732c9)) - add node 4.x support ([d322dd00](https://github.com/Unitech/pm2/commit/d322dd00de0f527224c027b4fec5e86f12fd69ed)) - create alias method instead of modify prototype ([6d8f0dfa](https://github.com/Unitech/pm2/commit/6d8f0dfae8106deb2fee0a7ae15b6ca9802a066d)) - change safety var to const ([047aa494](https://github.com/Unitech/pm2/commit/047aa494d5c4dd4342915766b54d673db0d5cdf1)) - drop some 0.x patch ([0cab8880](https://github.com/Unitech/pm2/commit/0cab8880ffa362cf27ab7d7b6a64d6b478dce7cd)) - remove prototype from API and create method ([9552bd61](https://github.com/Unitech/pm2/commit/9552bd61b72692beb620a91765ad440cdf6abefe)) - transform API into class ([e3831f95](https://github.com/Unitech/pm2/commit/e3831f95c8d71f98e8840da37f7e883727eccd59)) - name tests well ([c3ccc651](https://github.com/Unitech/pm2/commit/c3ccc651d09ed7291090f516637b75bda99ff71c)) - refactor e2e one line parallel ([93802711](https://github.com/Unitech/pm2/commit/938027117cdb2f300ee772ab27f008cbe22a4b19)) - e2e rename ([8a7db95a](https://github.com/Unitech/pm2/commit/8a7db95aabc8437f292af0316cec81ab80ec41f5)) - change params ([282186f2](https://github.com/Unitech/pm2/commit/282186f24b19b010999f7c7c49750935ef19c190)) - parallelize bash test ([d4b4375e](https://github.com/Unitech/pm2/commit/d4b4375e16fe7ac463b252702da662d3a21bf8b4)) ## Test - adapt test to new api ([7a275e27](https://github.com/Unitech/pm2/commit/7a275e279ea01b1239e9dd8b9cf8e088e407b96d)) - refactor before/after ([b85ca3ca](https://github.com/Unitech/pm2/commit/b85ca3caa3c68e18f7ce6954cc85e90a9d33efef)) - 3 concurrent jobs ([472aba34](https://github.com/Unitech/pm2/commit/472aba3499ff2d9d0eb834e819410026b1a44503)) - move test ([9c973324](https://github.com/Unitech/pm2/commit/9c9733246dbe6afff1b488bc3ba3b6fea3877ea5)) - move test ([952b7631](https://github.com/Unitech/pm2/commit/952b7631d19e1074ea73cc7a67bbaefe20950603)) - fix test with km_link ([23fd8ecf](https://github.com/Unitech/pm2/commit/23fd8ecfea9b2bf61359f62a8e6e1a582c3b0d6e)) ## Chore - shorten ecosystem file ([992a0452](https://github.com/Unitech/pm2/commit/992a045227aed559e708ac4e6bb3f54beabe48e0)) - change motd wording ([aa183ba1](https://github.com/Unitech/pm2/commit/aa183ba19d88777d82619aa40499c2661d67879e)) - merge master in development ([0e4453d9](https://github.com/Unitech/pm2/commit/0e4453d9cc789aa08ee778ff400572337e90d2e3)) - keymetrics -> pm2 ([2c8170c2](https://github.com/Unitech/pm2/commit/2c8170c25e231eb8827bb0944b76c2f4b041d84e)) - upgrade all modules + keymetrics-agent -> pm2/agent + increase version enabling v8-compile-cache ([53ca18c1](https://github.com/Unitech/pm2/commit/53ca18c12868ab177b60a4edff2ccaa8127e301f)) - pm2.io -> @pm2/io ([ae098962](https://github.com/Unitech/pm2/commit/ae098962df35eee7f482dc0a514fd29a02a5f4ad)) - right names as pm2 maintainers ([e8cd7131](https://github.com/Unitech/pm2/commit/e8cd7131a6b9c9d497a2079bcbfc03770a753a06)) - add changelog generation into contributing.md ([d77bfbc3](https://github.com/Unitech/pm2/commit/d77bfbc3c8929851ee19ea604b2a6481d03771e3)) - cache node_modules ([81627e94](https://github.com/Unitech/pm2/commit/81627e94c72efa1f4d726e20bbf67f0bbd5c116f)) - clone last 5 commits ([dad38ed1](https://github.com/Unitech/pm2/commit/dad38ed1bae849147f66e44186cd71c4b9cb022d)) - delete old stagnating pmx inside test ([36834c2c](https://github.com/Unitech/pm2/commit/36834c2c00d496e04c38abaca30202eb650015c4)) - pmx -> pm2.io ([adcbebc3](https://github.com/Unitech/pm2/commit/adcbebc3f6419cd97c5ea99f3c3a6789585bda66)) - updgrade pmx-2 ([eeeb2988](https://github.com/Unitech/pm2/commit/eeeb2988f8886e405aea107db3b888fc1fc929f8)) - disable legacy test ([13723bd9](https://github.com/Unitech/pm2/commit/13723bd938d0e6fb1cbf35f15eabe91c52d87b58)) - remove test for pmx alert system ([c43414a6](https://github.com/Unitech/pm2/commit/c43414a63438d724b8099eb531ec72bab23b8ca2)) - sync from master ([3424ee27](https://github.com/Unitech/pm2/commit/3424ee27870feaf62fdf4509cce9015f8b1a8a2e)) - add unique id for each process ([85a5ee0f](https://github.com/Unitech/pm2/commit/85a5ee0f1fd16da9635fb4b16ddcd8d53aca8224)) - use npm install for CI as yarn has issue with npm ([52902186](https://github.com/Unitech/pm2/commit/5290218626af815f6cae8173bc78d21881a4dda8)) - remove unused dependency ([830fc15f](https://github.com/Unitech/pm2/commit/830fc15fad1aee95e65b2681482b03369f1f97d7)) - upgrade PM2 to 3.0 ([4bc2eb4c](https://github.com/Unitech/pm2/commit/4bc2eb4c9a8179b9ae38438e98ce7650a91b64db)) - remove unused console.log ([33db5084](https://github.com/Unitech/pm2/commit/33db5084814ae7940c90b7f933f9514d28008b78)) - wording on error message ([c251c8c9](https://github.com/Unitech/pm2/commit/c251c8c97e6f18aae584cac6b7f3c83cf4f2de9c)) - revert PR #3496 ([aae1d55e](https://github.com/Unitech/pm2/commit/aae1d55e410c4dcfbbca83eaabbdf1a65d55f3aa)) - fix issue with snapshot command + remove command forceGc ([97fd1010](https://github.com/Unitech/pm2/commit/97fd1010d005e59f2411042fa95891f9717fa8b7)) - wording on error message ([5f78ecbf](https://github.com/Unitech/pm2/commit/5f78ecbf90f9f46a7feb2a169968e86b0ecac91e)) - drop 0.12 test on travis ([beb6e487](https://github.com/Unitech/pm2/commit/beb6e48787c39c66569141d0fd8d090736114d23)) - downgrade promptly ([074a7a40](https://github.com/Unitech/pm2/commit/074a7a407a31b4d88442f5834d253d62f4e543b8)) - remove coffee and livescript dependencies ([13d6565c](https://github.com/Unitech/pm2/commit/13d6565c72e3596d05f87bfc8be15d3ee45fb279)) - upgrade module version and engine version ([84796956](https://github.com/Unitech/pm2/commit/84796956347ca638750fe89cb5545e2a90a0f2c2)) ## Branchs merged - Merge branch 'development' into chore/dev-cache-node-modules ([146c4e11](https://github.com/Unitech/pm2/commit/146c4e113c88e8ade17c7558c8e14cf523a3b2d6)) - Merge branch 'development' of https://github.com/Unitech/pm2 into new-agent ([3514e7fa](https://github.com/Unitech/pm2/commit/3514e7fac624bb83b4cc22651ebc05385f9c284d)) - Merge branch 'development' into master ([f5668331](https://github.com/Unitech/pm2/commit/f5668331dbe7346304258317a3b84450f421ed03)) - Merge branch 'development' into new-usage-cli ([4ae27694](https://github.com/Unitech/pm2/commit/4ae27694e34c4bc6ed389566d71fc5ec48b69652)) - Merge branch 'Eywek-improv/agent' into new-agent ([3e259dd1](https://github.com/Unitech/pm2/commit/3e259dd1d6bb96ea41897c49f3a84557c00c7dad)) - Merge branch 'ecosystem-documentation' of github.com:rmonnier/pm2 into ecosystem-documentation ([98348955](https://github.com/Unitech/pm2/commit/98348955a6eb3a9cd524b991bd1dd6ed03d2c857)) - Merge branch 'development' into ecosystem-documentation ([40157784](https://github.com/Unitech/pm2/commit/40157784a63bcb0e744d4ed56f6c687e28379fdd)) - Merge branch 'inspect_mode' of github.com:Unitech/pm2 into inspect_mode ([7e1494c7](https://github.com/Unitech/pm2/commit/7e1494c7f7971aaf1f4d00d2ee691c3c41775001)) - Merge branch 'development' of github.com:Unitech/pm2 into development ([48f81a8b](https://github.com/Unitech/pm2/commit/48f81a8b2f6f0db39edd86083fb369b74845c387)) - Merge branch 'development' into master ([47e54109](https://github.com/Unitech/pm2/commit/47e5410987ab3d824a34c062d70c24ab686e57db)) - Merge branch 'development' into module_install_windows ([7b82fb91](https://github.com/Unitech/pm2/commit/7b82fb916ed453c1c263bae43c962f6a5294d810)) - Merge branch 'development' into module_install_windows ([80b0495f](https://github.com/Unitech/pm2/commit/80b0495f63d1224b850af4b14cdeb055e3fef50b)) ## Pull requests merged - Merge pull request #3726 from soyuka/fix-list ([0255c5a6](https://github.com/Unitech/pm2/commit/0255c5a6ab1b8a8f609d2183d998695b8c42838d)) - Merge pull request #3725 from soyuka/fix-list ([a39eb4f8](https://github.com/Unitech/pm2/commit/a39eb4f806e87565f53758a19f0ee289b6489b67)) - Merge pull request #3718 from AaronM04/openbsd-init-script ([85458261](https://github.com/Unitech/pm2/commit/85458261d2673c609cb252d64ad4dfbaa466d848)) - Merge pull request #3721 from Unitech/io_conf ([70ec1f81](https://github.com/Unitech/pm2/commit/70ec1f81eae089f75e82723fde7b0b3926d0a9bc)) - Merge pull request #3716 from Unitech/io_conf ([0bc000b9](https://github.com/Unitech/pm2/commit/0bc000b9aae7dd37b456bc2d4fbc9eb4a9f047ef)) - Merge pull request #3714 from Unitech/definition ([d8cff0de](https://github.com/Unitech/pm2/commit/d8cff0dec5160a620d1512ff56726c073368d1a4)) - Merge pull request #3700 from Unitech/report_error ([4b2cad40](https://github.com/Unitech/pm2/commit/4b2cad407b76994e978074a2a3825fe70656304d)) - Merge pull request #3670 from Unitech/changelog ([4bcbcce1](https://github.com/Unitech/pm2/commit/4bcbcce16ced596f6ca2bab2b77d608a174a7c1a)) - Merge pull request #3662 from DanielRuf/chore/dev-cache-node-modules ([540590ee](https://github.com/Unitech/pm2/commit/540590ee056b44eed3b688a7b0b16ca78ec82cd9)) - Merge pull request #3663 from DanielRuf/chore/dev-clone-last-5-commits ([bdf95fc9](https://github.com/Unitech/pm2/commit/bdf95fc997f9ab2995b23668f25f11b6e98b5c47)) - Merge pull request #3584 from ngtmuzi/development ([33984b64](https://github.com/Unitech/pm2/commit/33984b64a2969ca4a3a5913f0f7da0242b6c5ec1)) - Merge pull request #3500 from Unitech/test-parallel ([da56c7af](https://github.com/Unitech/pm2/commit/da56c7aff18d3a38b3ad068b22cd75b290bac9d0)) - Merge pull request #3539 from KimSeongIl/master ([1325704d](https://github.com/Unitech/pm2/commit/1325704d95d324e56b0ebc86aed8137e0d0aa450)) - Merge pull request #3556 from N-Nagorny/logs-smart-app-name-cutting ([bfddf4fd](https://github.com/Unitech/pm2/commit/bfddf4fdef5ec293119d850cc2532ac5d6490ae3)) - Merge pull request #3553 from Unitech/fix_tracing_not_working ([9d51fe08](https://github.com/Unitech/pm2/commit/9d51fe0819182339f3a6a4aee7ea603ea3f4dd76)) - Merge pull request #3549 from Eywek/new-agent ([2f04027b](https://github.com/Unitech/pm2/commit/2f04027b536094d192b399677b3a113102f06b8e)) - Merge pull request #3548 from rmonnier/start-ecosystem-default ([55412f26](https://github.com/Unitech/pm2/commit/55412f263250395de0085144932cfe06b8c7180d)) - Merge pull request #3546 from soyuka/improve-monitor-perf ([e4e29233](https://github.com/Unitech/pm2/commit/e4e29233f99db36462a6e8f48eb8ebd3d2fd9fa5)) - Merge pull request #3534 from rmonnier/new-usage-cli ([5dfba8a4](https://github.com/Unitech/pm2/commit/5dfba8a4491f0bb83f2879915f0c4b164be2552c)) - Merge pull request #3542 from rmonnier/default-start-ecosystem ([c65595f4](https://github.com/Unitech/pm2/commit/c65595f4a70659e1e0d753e6c28a1fcedf45a91a)) - Merge pull request #3545 from rmonnier/default-ecosystem ([b3718656](https://github.com/Unitech/pm2/commit/b3718656f630aa54880343d9742534a2a508daec)) - Merge pull request #3543 from rmonnier/ecosystem-documentation ([a60580a1](https://github.com/Unitech/pm2/commit/a60580a12b4a0066c8df6620317fbc8bf599b0b6)) - Merge pull request #3541 from soyuka/development ([67e7a015](https://github.com/Unitech/pm2/commit/67e7a015cabaa7b08206a3b1bf9c0399af88f76b)) - Merge pull request #3511 from Unitech/inspect_mode ([75fb87f8](https://github.com/Unitech/pm2/commit/75fb87f8a1c46a6db8e974b421e857175e69b535)) - Merge pull request #3517 from Unitech/polyfill_fs_copy_node4 ([524f5494](https://github.com/Unitech/pm2/commit/524f54948de5080632d43bb512038d7bd7271619)) - Merge pull request #3516 from Unitech/drop_unused_feature ([9436f11a](https://github.com/Unitech/pm2/commit/9436f11aeecfc07e77aa9d6b108df4478b43402e)) - Merge pull request #3510 from Unitech/dump_refacto ([674e4469](https://github.com/Unitech/pm2/commit/674e4469554e6a765bb3d57a3c083e6ab53b20cc)) - Merge pull request #3501 from Unitech/refactor_api ([9f2c4ca4](https://github.com/Unitech/pm2/commit/9f2c4ca4c9eadf6c7730e3889c72e908cd2d8f5d)) - Merge pull request #3496 from rmonnier/master ([829cc303](https://github.com/Unitech/pm2/commit/829cc3032b2d61e20f7a2e7d1d819c0ddc0845e8)) - Merge pull request #3484 from Unitech/pull_by_name ([24d29404](https://github.com/Unitech/pm2/commit/24d294049008a0d01b2bc407b9b2b880d5843fbd)) - Merge pull request #3482 from Unitech/mjs_support ([ebe7b048](https://github.com/Unitech/pm2/commit/ebe7b0487218557858aaa98527360eca1776b140)) - Merge pull request #3495 from Unitech/module_install_windows ([e9c625d3](https://github.com/Unitech/pm2/commit/e9c625d3088c71eef4237ecd866b806957c61815)) - Merge pull request #3507 from cheapsteak/patch-1 ([a49287d6](https://github.com/Unitech/pm2/commit/a49287d6a1d22b39270e2d05dee2a17c0ed55797)) ## 2.10.4 ( Thu May 17 2018 14:32:40 GMT+0200 (CEST) ) ## Bug Fixes - #3645 throttle startup ([d529f675](https://github.com/Unitech/pm2/commit/d529f675d0240777cba95442ba35205c370cdb43)) ## Chore - update issue and PR templates to use comments to hide instructions in the frontend ([9e0180ed](https://github.com/Unitech/pm2/commit/9e0180eddab071916144ad7008817bd6aef1c8ce)) ## Pull requests merged - Merge pull request #3664 from DanielRuf/chore/update-issue-pr-templates ([067446f2](https://github.com/Unitech/pm2/commit/067446f2133ba7f761b0ad3c9f3692b167affd8b)) ## v2.10.3 ( Fri Apr 27 2018 11:42:16 GMT+0200 (CEST) ) ### Chore - upgrade for node 10 ([cf7630e](https://github.com/Unitech/pm2/commit/cf7630e259742bdff8257cff4dbed2732bf24f9c)) ## v2.10.2 ( Thu Mar 29 2018 13:06:11 GMT+0200 (CEST) ) ## Bug Fixes - reinforce pm2-runtime auto exit strategy #3567 #3206 ([e09cdbab](https://github.com/Unitech/pm2/commit/e09cdbabd0b479acda3cb24154bbaa071aa35407)) ## Pull requests merged - Merge pull request #3569 from Unitech/pm2-runtime-hot-fix ([473a2d6d](https://github.com/Unitech/pm2/commit/473a2d6d3867c617e4a41571d1780618c5025b87)) - Merge pull request #3547 from Unitech/revert-3532-logs-smart-app-name-cutting ([438e3030](https://github.com/Unitech/pm2/commit/438e303013e82ecc199cb68d018144cde8a0b2e6)) - Merge pull request #3532 from N-Nagorny/logs-smart-app-name-cutting ([067c18e6](https://github.com/Unitech/pm2/commit/067c18e601aca4fac10101a7c23cc4c3525ad776)) ## v2.10.1 ( Mon Feb 26 2018 11:38:18 GMT+0100 (CET) ) ## Bug Fixes - restore --raw option #3476 ([340011ca](https://github.com/Unitech/pm2/commit/340011cace2b90c2a1ead8d86baba517f5570e15)) ## v2.10.0 ( Mon Feb 19 2018 14:51:19 GMT+0100 (CET) ) ### Bug Fixes - add livescript in default modules ([a315eeb6](https://github.com/Unitech/pm2/commit/a315eeb65f04b22643a903f0cb1c0f416615ad8b)) - replace dash with underscore ([203df768](https://github.com/Unitech/pm2/commit/203df7688ca348967c00bc45289ae70fd2c4aaaa)) - make sure not pm2 is running ([bd798fd7](https://github.com/Unitech/pm2/commit/bd798fd748665e935db4bb91f9d1d66952d9842a)) - auto-exit edge case fix + pm2 no daemon mode + log in raw by default + less logs ([704ae518](https://github.com/Unitech/pm2/commit/704ae518f5d7df0a631349e518d81cef51249a58)) - impact v8 flag in fork mode also ([41bf6ef7](https://github.com/Unitech/pm2/commit/41bf6ef7d3633180b4c1e90f36eb206d82fab2b1)) - fixup! #2182 Get rid of annoying popups in Windows 10 ([3a85b59d](https://github.com/Unitech/pm2/commit/3a85b59de4a76796ad0880368d8d085a7ba55d36)) ### Hot Fixes - \#3420 ([673acf36](https://github.com/Unitech/pm2/commit/673acf36b4ca1fd65c5135a92d56081f76237a8b)) ### Features - add dependencies section into ecosystem.json file. ([828a30d0](https://github.com/Unitech/pm2/commit/828a30d0ccc88b3f6e2b66d517ccf5f2394bd08b)) - --deep-monitoring available from pm2-runtime ([99e62e3b](https://github.com/Unitech/pm2/commit/99e62e3bb808f071d6e4850c234b34f7de65b1c2)) - add deep_metrics to deep_monitoring flag ([4d1bea5e](https://github.com/Unitech/pm2/commit/4d1bea5e0bbaab1f16f75d012bca25702cdff88e)) - add flag to enable deep-monitoring ([c5418688](https://github.com/Unitech/pm2/commit/c541868837a1c4421394de5dd1029d2619b5ac82)) - allow pm2 to install a set of module as one single command and add deep-monitoring. ([9dddc80d](https://github.com/Unitech/pm2/commit/9dddc80db5e496def44d4d36716b7de54e5171cf)) - pm2 pid command ([6687d499](https://github.com/Unitech/pm2/commit/6687d499415151bd62489fed5331f414576ec354)) - allow pm2 to install and enable event-loop-inspector data collecting ([e6b0c474](https://github.com/Unitech/pm2/commit/e6b0c47443d3e6a839bf29057ef0a80ef135c47e)) - ignore signal when running in --no-daemon ([b9c01c99](https://github.com/Unitech/pm2/commit/b9c01c99d54aba98ab790b8888500ac0f0af05c9)) - upgrade pmx to git development branch ([21be05a0](https://github.com/Unitech/pm2/commit/21be05a07bd93eacaddedde3b647c16468937473)) - allow pm2 to enable v8 data collecting from pmx ([aa180fa8](https://github.com/Unitech/pm2/commit/aa180fa8ab47f0c687d7c21854d005ad0ebf8475)) - allow pm2 to install gc-stats ([15634168](https://github.com/Unitech/pm2/commit/15634168582e4c7b3c5f47a3f58a0fcf8b732a76)) - feat add changelog generation support ([14f53fc0](https://github.com/Unitech/pm2/commit/14f53fc0c28be4084778785aeace3763ed0d827f)) - **pm2** - add pm2 init option to generate an ecosystem file ([5d56fac7](https://github.com/Unitech/pm2/commit/5d56fac7cc12590af29ee46c68ba32a82a2b813b)) - add pm2 init option to generate an ecosystem file ([a38fd199](https://github.com/Unitech/pm2/commit/a38fd199b90d27a2405f8cabab0e4f6e45c69b08)) ### Documentation - add documentation on new pm2 install command ([c90c453f](https://github.com/Unitech/pm2/commit/c90c453f85b07adb346bc55c2b685d689a2e96f7)) - add sendDataToProcessId into typescript definitions ([4a2e8d2d](https://github.com/Unitech/pm2/commit/4a2e8d2d2c4b38fe0ff2377dfe32fce9a43c8044)) ### Refactor - delete all "if" condition when installing new module, create an object with all modules and a generic installation process ([1b92a9c4](https://github.com/Unitech/pm2/commit/1b92a9c4000734367e68d8dbd60d0901009f4c56)) - deep pm2-runtime refactor #3408 #3257 #3266 ([c13b2364](https://github.com/Unitech/pm2/commit/c13b23648269529a1f998d816be10f895665861e)) - no more interactive spinner for connection to KM + change pm2 log format + remove some logs ([d1916f40](https://github.com/Unitech/pm2/commit/d1916f40962b2cc8a1866172eab7d5d89db093be)) ### Chore - pmx to 1.6.3-rc2 ([41815e0b](https://github.com/Unitech/pm2/commit/41815e0ba0298979f936b3d4badb196f8d9783d8)) - switch pmx to development ([748019d1](https://github.com/Unitech/pm2/commit/748019d1ef0cf760b5e8de9d5b6af6fee300db02)) - 2.10.0-beta ([0d2b7172](https://github.com/Unitech/pm2/commit/0d2b7172a093d0638deabb5f23383cc9eec5dda9)) - upgrade pmx to 1.6.3-next ([5a1b4343](https://github.com/Unitech/pm2/commit/5a1b4343cc1e1f5018e21451a111340351706213)) - upgrade pmx dep ([4bbeec3d](https://github.com/Unitech/pm2/commit/4bbeec3d170ba63af0c0ae0e2d07beec2ab49772)) - switch to published pmx(@next) ([859d18fb](https://github.com/Unitech/pm2/commit/859d18fbc79e2a2760fe90e9c17e71209f8177ce)) - remove --exit from mocha.opts ([36bf03e1](https://github.com/Unitech/pm2/commit/36bf03e1eed69a27e518151e2f7aa958b15db2fb)) - remove unused files ([65d233e5](https://github.com/Unitech/pm2/commit/65d233e5b5290f65796b7cf3daa20706e0f3bee6)) ### Branchs merged - Merge branch 'development' of ssh://github.com/deltasource/pm2 into hotfix/scoped-package-support ([94ea9d9e](https://github.com/Unitech/pm2/commit/94ea9d9eeff40faca8aa9f7edfc81aa29c08e740)) - Merge branch 'master' into development ([46606903](https://github.com/Unitech/pm2/commit/46606903f25d0f4d0eee226da863e20e4b396dc9)) - Merge branch 'development' of github.com:Unitech/pm2 into v8_option ([757562f7](https://github.com/Unitech/pm2/commit/757562f755b09124bbd006209ae38a096d692529)) - Merge branch 'development' of github.com:Unitech/pm2 into gc-stats ([3ed1a747](https://github.com/Unitech/pm2/commit/3ed1a7471aec7d79f7d604447ac7445720bdaced)) - Merge branch 'master' into development ([ee7651e4](https://github.com/Unitech/pm2/commit/ee7651e47e944c3c829933494c6cc765deb4bb29)) ### Pull requests merged - Merge pull request #3466 from natcl/development ([c6d7ace8](https://github.com/Unitech/pm2/commit/c6d7ace802e667def75bc68344effa4856830fb4)) - Merge pull request #3464 from andyfleming/patch-1 ([dd9ebb60](https://github.com/Unitech/pm2/commit/dd9ebb6051708ee5a13cc68dbcb8238e41860bb9)) - Merge pull request #3459 from rmonnier/master ([46948a98](https://github.com/Unitech/pm2/commit/46948a98e90c7864f7b8100db5c519fe9d37f11a)) - Merge pull request #3458 from Unitech/pm2_install_command ([f3b35726](https://github.com/Unitech/pm2/commit/f3b35726895bd82b92813f308b787d68e9df1fa4)) - Merge pull request #3453 from deltasource/hotfix/scoped-package-support ([974f9bf0](https://github.com/Unitech/pm2/commit/974f9bf0dc7a7aa7ff6860f8640da3593b802296)) - Merge pull request #3448 from Unitech/deep_monitoring_flag ([331bc741](https://github.com/Unitech/pm2/commit/331bc741d7285094738a91cd816bc9755cc76605)) - Merge pull request #3447 from Unitech/deep-monitoring ([719d328e](https://github.com/Unitech/pm2/commit/719d328e8d14871b34fd33df54fd80f4f8e7825f)) - Merge pull request #3443 from Unitech/event-loop-inspector ([77a35274](https://github.com/Unitech/pm2/commit/77a3527407f3d090c7a5fa0bedaf943a7536b5eb)) - Merge pull request #3442 from Unitech/event-loop-inspector ([dad98e6e](https://github.com/Unitech/pm2/commit/dad98e6e0738983717fee155ff0f6519955ffc1b)) - Merge pull request #3424 from Unitech/sendDataToProcessId_def ([95e85eef](https://github.com/Unitech/pm2/commit/95e85eef84510dddfb0c6b13f0ada38a7dd66cae)) - Merge pull request #3438 from Unitech/v8_option ([e46b15dc](https://github.com/Unitech/pm2/commit/e46b15dc32c18e8b24f66da0c79cc06f91cf11b5)) - Merge pull request #3437 from Unitech/gc-stats ([1a6771aa](https://github.com/Unitech/pm2/commit/1a6771aa361bb5718bafd6e33e616725f9c0d328)) - Merge pull request #3400 from toddwong/windowsHide2 ([f65e8794](https://github.com/Unitech/pm2/commit/f65e8794df6e67f4ff60dfbec7c05a37721cb6f9)) - Merge pull request #3421 from Unitech/generate_changelog ([b0690618](https://github.com/Unitech/pm2/commit/b0690618d940c11e28eeb5115c060bf363c7b62b)) - Merge pull request #3419 from Rohja/fix-build-number-deb-rpm ([b4343de2](https://github.com/Unitech/pm2/commit/b4343de2703fce03f3cf48cc303b12bc6b69b743)) ## 2.9.2 - #3364 30% faster CLI via v8-compile-cache - add process._getActiveRequests() and process._getActiveHandles() custom metrics - #3402 #3360 fix bad username - #3413 check dependencies before launching tests - #3295 add sorting feature for process list (pm2 ls --sort ) - #3404 if no gid specified - set gid to uid - #3287 add typing for env - #3374 separate stdout and stderr for pm2-docker/pm2-runtime - #3366 improve building of rpm and deb packages - #3375 sendLineToStdin/sendDataToProcessId fix - #3365 fix report command for windows - #3367 Display an error if the process is not found when running 'pm2 logs ' - #3256 TypeError: Cannot read property 'destroy' of undefined - User: append SUDO_USER if no uid has been set and SUDO_USER present - User: check permission of agent - KM: send outliers - KM: infinite retry for km connection ## 2.9.1 - #3356 hot fix on startup system ## 2.9.0 - #3278 --silent -s now does not print welcome message - #3345 #2871 #3233 pm2 -v will not spawn daemon anymore - #3341 update moment dependency - #3314 pm2 install --safe will now monitor new installation of module and will fallback to previous version if the module is failing (restart, fail on npm install) - #3314 module folder structure refactoring to keep independent dependencies for each modules - #3324 remove yarn installation of modules - #3273 pm2 --mini-list now print the right pid file - #3206 add flag to auto turn off auto exit with pm2-docker - #3036 Fix applying env PM2_CONCURRENT_ACTIONS correctly - #3346 do not chmod systemd script (was failing systemd script on orange pi) - #3347 Add --wait-ip option to override systemd initialization to wait for internet full connectivity - #3348 alias pm2-docker to pm2-runtime - #3350 Override HOME and USER when setting --uid to start module or application - #3351 alias pm2 ps to pm2 ls (docker style) ## 2.8.0 - #2070 Fix sendDataToProcessId not working (@h091237557) - #2182 Add windowHide options in cluster mode (@soyuka) - #3206 By default in docker, pm2 will auto exit when no process are online (@dguo) - #3225 fix --lines accepting invalid values (@vmarchaud) - #3036 fix when PM2_CONCURRENT_ACTIONS was overriden everytime on node > 4 (@danez) - Add node 9 tests on CI (@Unitech) - Add pm2 unlink command (eq to pm2 link delete) (@Unitech) - Fix interactor to support custom endpoints (@vmarchaud) - Allow custom PM2_HOME for docker (@lucidNTR) - Support MJS module (@vpotseluyko) - Allow custom service name for startup (@danez) - Update PMX to 1.5 (@unitech) ## 2.7.2 - #3200 Associate .tsx files with ts-node (@dguo) - #3202 Add first draft of typescript definitions (@jportela) - Allow to install http url via pm2 install (@unitech) - #3204 Given --uid add all its gids automatically (@jmeit) - #3184 bugfix: try/catch around userInfo to avoid crash (@vmarchaud) - #3181 force upgrade to latest pm2-deploy ## 2.7.1 - #3117 Add required node env on cluster mode start instance (2m0nd) - make profiler compatible with Node.js 8 ## 2.7.0 - #3150 fix watchdog on agent - #3001 dump-backup feature - #3134 edge case error handling - #3096 fix module installation - #3085 honor every pm2 args on restart - #3046 better error message if PM2 is misconfigured - #3058 pm2-docker now does not write logs by default - #3045 continue to broadcast on the bus system even if logs are disabled - [Docker] Auto Exit when no application is running - [Keymetrics] pm2 unmonitor fix - [Beta Container Support] beta pm2 start app.js --container - [Chore] upgrade modules - [Chore] enhance package.json ## 2.6.1 - #3037 bug fix cb ## 2.6.0 ### Changes - #2998 pm2 report command for automated system inspection - #2997 --disable-logs option to suppress error - #2290 allow to declare apps under "pm2" attribute (eq "apps"). Nicer in package.json - #2994 allow to specify typescript version to be installed - #2501 low memory environment pm2 setting via PM2_OPTIMIZE_MEMORY (beta) - #2968 pm2 attach to attach to process stdin / stdout - pm2-runtime -> drop in replacement for the node.js binary - #2951 pm2 reload command locker via timestamped lock file - #2977 pm2 reloadLogs protected - #2958 Allow to delete attribute via --attribute null - #2980 PM2_SILENT=true pm2 startup - #2690 --parallel command allows to change the nb of concurrent actions (reload/restart) - expose cwd on CLI via --cwd - multiple pm2-docker enhacements - Alias pm2.link and pm2.unlink to pm2.interact and pm2._pre_interact - Allow to customize kill signal via PM2_KILL_SIGNAL - Support git+http in module installation - force reverse interaction reconnection on internet discovery - `--instances -1` when having a 1 cpu is no-longer spawning no processes #2953 - refactor the context retrieving from error - add a TTL for file cache entry - #2956 Fix listen_timeout in combination with wait_ready - #2996 respect signal order on pm2 reload (delegate ready function to reload fn) ### Breaking - Drop pm2-daemon CLI (replaced by pm2-runtime) ## 2.5 - `pm2 register|login` to create new account / login on Keymetrics + auto link - `pm2 open` to open dashboard on browser - `pm2 monitor|unmonitor ` for selective monitoring - #2818 alias pm2-docker to pm2-daemon - #2809 correctly resolve git/npm repo when running pm2 install - #2861 better auto exit check for docker - #2870 avoid null error when preparing app config - #2872 avoid showing useless warning - #438 allow to override daemon config paths via env (example: `PM2_PID_FILE_PATH` to override pid file of the daemon) - #2849 better gentoo template for pm2 startup - #2868 allow tailing log with `--raw` flag - #452 Add `PM2_WEB_STRIP_ENV_VARS` to remove environnement vars from `pm2 web` endpoint - #2890 Fix wait-ready for cluster mode - #2906 randomize machine name with default pm2 link - #2888 allow to use regex for pm2 logs - #2045 allow to rename NODE_APP_INSTANCE env variable - #2809 add `increment_var` options to ask for a environnement variable to be incremented for each application started - more informations when failing to deploy on custom ecosystem file - fix tests for node 8 - fix missing callback when overriding console.log - allow to rename daemon process name via `PM2_DAEMON_NAME` - few typo in the readme ### Breaking change - the NODE_APP_INSTANCE var behavior has been changed : - old behavior : when starting multiples instances of an app each one get an unique number, but its not working anymore if you are using `pm2 scale` (simply put its possible to have two application with the same number) - new behavior : the number are consistent, if you scale up/down it will take a number that isn't used by another application (so two application should never have the same number) ## 2.4.5/6 - #2818 alias pm2-docker to pm2-runtime - #2815 polyfill for path.isAbsolute for node v0.11 ### Breaking change - rundev command has been dropped because of too low adoption ## 2.4.4 - #2806 fix reconnection to keymetrics ## 2.4.3 - #2759 disable default require of vxx in pmx - #2651 always spawn pm2 daemon with `node` binary - #2745 new issue template - #2761 Make JSON log stream timestamp in consistent format - #2770 Fix trigger API never calling callback - #2796 Fix absolute path on windows - [KM] profiler installation via `pm2 install v8-profiler` or `pm2 install profiler` - [KM] Agent rescue system ## 2.4.2 - [KM] Disable pm2-server-monit auto install ## 2.4.1 - #2720 multi user startup script - #2266 start and tail logs via `pm2 start app.js --attach` - #2699 add back previous termcaps interface via `pm2 imonit` - #2681 fix log folder create - #2724 make sure process is stopped even if there is a restart_delay - #2706 install pm2 modules via yarn if available - #2719 show 15 logs line bu default - #2703 allow custom timestamp with pm2-docker - #2698 fix unicode on pm2 monit - #2715 handle treekill edge case bug - Optimize CPU usage of pm2 monit command - [KM] URL web access dashboard - [KM] Auto install pm2-server-monit on keymetrics linking - [KM] Error reporting: add context (-B3 -A3 code lines) - [KM] Transaction Tracer: reset routes on app restart / wait some time before sending ## 2.4.0 - #2631 new pm2 monit command (blessed dashboard!) - #2670 allow to expose a folder over http via `pm2 serve ` - #2617 fix startup script generation on macosx (launchd) - #2650 new option to append env name to app name (used to allow the same app to be launched in different environment w/o name conflict) - #2671 allow to pass a delay to pm2-docker (`pm2-docker process.json --delay 10`) - `pm2 ecosystem simple` to generate a simple ecosystem file - aliasing: `pm2-dev `); } response.end(content, 'utf-8'); debug('[%s] Serving %s with content-type %s', Date.now(), filePath, contentType); }); } function parseBasicAuth(auth) { // auth is like `Basic Y2hhcmxlczoxMjM0NQ==` var tmp = auth.split(' '); var buf = Buffer.from(tmp[1], 'base64'); var plain = buf.toString(); var creds = plain.split(':'); return { username: creds[0], password: creds[1] } } function sendBasicAuthResponse(response) { response.writeHead(401, { 'Content-Type': 'text/html', 'WWW-Authenticate': 'Basic realm="Authentication service"' }); return response.end('401 Unauthorized'); } ================================================ FILE: lib/API/Startup.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ var chalk = require('ansis'); var path = require('path'); var fs = require('fs'); var forEachLimit = require('async/forEachLimit'); var eachLimit = require('async/eachLimit'); var Common = require('../Common.js'); var cst = require('../../constants.js'); var util = require('util'); var tmpPath = require('os').tmpdir; var which = require('../tools/which.js'); var sexec = require('../tools/sexec') module.exports = function(CLI) { /** * If command is launched without root right * Display helper */ function isNotRoot(startup_mode, platform, opts, cb) { Common.printOut(`${cst.PREFIX_MSG}To ${startup_mode} the Startup Script, copy/paste the following command:`); let pm2_bin_path = require.main.filename if (pm2_bin_path.includes('/lib/binaries/CLI.js') === true) { pm2_bin_path = pm2_bin_path.replace('/lib/binaries/CLI.js', '/bin/pm2') } if (opts.user) { console.log('sudo env PATH=$PATH:' + path.dirname(process.execPath) + ' pm2 ' + opts.args[1].name() + ' ' + platform + ' -u ' + opts.user + ' --hp ' + process.env.HOME); return cb(new Error('You have to run this with elevated rights')); } return sexec('whoami', {silent: true}, function(err, stdout, stderr) { console.log('sudo env PATH=$PATH:' + path.dirname(process.execPath) + ' ' + pm2_bin_path + ' ' + opts.args[1].name() + ' ' + platform + ' -u ' + stdout.trim() + ' --hp ' + process.env.HOME); return cb(new Error('You have to run this with elevated rights')); }); } /** * Detect running init system */ function detectInitSystem() { var hash_map = { 'systemctl' : 'systemd', 'update-rc.d': 'upstart', 'chkconfig' : 'systemv', 'rc-update' : 'openrc', 'launchctl' : 'launchd', 'sysrc' : 'rcd', 'rcctl' : 'rcd-openbsd', 'svcadm' : 'smf' }; var init_systems = Object.keys(hash_map); for (var i = 0; i < init_systems.length; i++) { if (which(init_systems[i]) != null) { break; } } if (i >= init_systems.length) { Common.printError(cst.PREFIX_MSG_ERR + 'Init system not found'); return null; } Common.printOut(cst.PREFIX_MSG + 'Init System found: ' + chalk.bold(hash_map[init_systems[i]])); return hash_map[init_systems[i]]; } CLI.prototype.uninstallStartup = function(platform, opts, cb) { var commands; var that = this; var actual_platform = detectInitSystem(); var user = opts.user || process.env.USER || process.env.LOGNAME; // Use LOGNAME on Solaris-like systems var service_name = (opts.serviceName || 'pm2-' + user); var openrc_service_name = 'pm2'; var launchd_service_name = (opts.serviceName || 'pm2.' + user); if (!platform) platform = actual_platform; else if (actual_platform && actual_platform !== platform) { Common.printOut('-----------------------------------------------------------') Common.printOut(' PM2 detected ' + actual_platform + ' but you precised ' + platform) Common.printOut(' Please verify that your choice is indeed your init system') Common.printOut(' If you arent sure, just run : pm2 startup') Common.printOut('-----------------------------------------------------------') } if (platform === null) throw new Error('Init system not found') if (!cb) { cb = function(err, data) { if (err) return that.exitCli(cst.ERROR_EXIT); return that.exitCli(cst.SUCCESS_EXIT); } } if (process.getuid() != 0) { return isNotRoot('unsetup', platform, opts, cb); } if (fs.existsSync('/etc/init.d/pm2-init.sh')) { platform = 'oldsystem'; } switch(platform) { case 'systemd': commands = [ 'systemctl stop ' + service_name, 'systemctl disable ' + service_name, 'rm /etc/systemd/system/' + service_name + '.service' ]; break; case 'systemv': commands = [ 'chkconfig ' + service_name + ' off', 'rm /etc/init.d/' + service_name ]; break; case 'oldsystem': Common.printOut(cst.PREFIX_MSG + 'Disabling and deleting old startup system'); commands = [ 'update-rc.d pm2-init.sh disable', 'update-rc.d -f pm2-init.sh remove', 'rm /etc/init.d/pm2-init.sh' ]; break; case 'openrc': service_name = openrc_service_name; commands = [ '/etc/init.d/' + service_name + ' stop', 'rc-update delete ' + service_name + ' default', 'rm /etc/init.d/' + service_name ]; break; case 'upstart': commands = [ 'update-rc.d ' + service_name + ' disable', 'update-rc.d -f ' + service_name + ' remove', 'rm /etc/init.d/' + service_name ]; break; case 'launchd': var destination = path.join(process.env.HOME, 'Library/LaunchAgents/' + launchd_service_name + '.plist'); commands = [ 'launchctl remove ' + launchd_service_name + ' || true', 'rm ' + destination ]; break; case 'rcd': service_name = (opts.serviceName || 'pm2_' + user); commands = [ '/usr/local/etc/rc.d/' + service_name + ' stop', 'sysrc -x ' + service_name + '_enable', 'rm /usr/local/etc/rc.d/' + service_name ]; break; case 'rcd-openbsd': service_name = (opts.serviceName || 'pm2_' + user); var destination = path.join('/etc/rc.d', service_name); commands = [ 'rcctl stop ' + service_name, 'rcctl disable ' + service_name, 'rm ' + destination ]; break; case 'smf': service_name = (opts.serviceName || 'pm2_' + user); commands = [ 'svcadm disable ' + service_name, 'svccfg delete -f ' + service_name ] }; sexec(commands.join('&& '), function(code, stdout, stderr) { Common.printOut(stdout); Common.printOut(stderr); if (code == 0) { Common.printOut(cst.PREFIX_MSG + chalk.bold('Init file disabled.')); } else { Common.printOut(cst.ERROR_MSG + chalk.bold('Return code : ' + code)); } cb(null, { commands : commands, platform : platform }); }); }; /** * Startup script generation * @method startup * @param {string} platform type (centos|redhat|amazon|gentoo|systemd|smf) */ CLI.prototype.startup = function(platform, opts, cb) { var that = this; var actual_platform = detectInitSystem(); var user = (opts.user || process.env.USER || process.env.LOGNAME); // Use LOGNAME on Solaris-like systems var service_name = (opts.serviceName || 'pm2-' + user); var openrc_service_name = 'pm2'; var launchd_service_name = (opts.serviceName || 'pm2.' + user); if (!platform) platform = actual_platform; else if (actual_platform && actual_platform !== platform) { Common.printOut('-----------------------------------------------------------') Common.printOut(' PM2 detected ' + actual_platform + ' but you precised ' + platform) Common.printOut(' Please verify that your choice is indeed your init system') Common.printOut(' If you arent sure, just run : pm2 startup') Common.printOut('-----------------------------------------------------------') } if (platform == null) throw new Error('Init system not found'); if (!cb) { cb = function(err, data) { if (err) return that.exitCli(cst.ERROR_EXIT); return that.exitCli(cst.SUCCESS_EXIT); } } if (process.getuid() != 0) { return isNotRoot('setup', platform, opts, cb); } var destination; var commands; var template; function getTemplate(type) { return fs.readFileSync(path.join(__dirname, '..', 'templates/init-scripts', type + '.tpl'), {encoding: 'utf8'}); } switch(platform) { case 'ubuntu': case 'centos': case 'arch': case 'oracle': case 'systemd': if (opts.waitIp) template = getTemplate('systemd-online'); else template = getTemplate('systemd'); destination = '/etc/systemd/system/' + service_name + '.service'; commands = [ 'systemctl enable ' + service_name ]; break; case 'ubuntu14': case 'ubuntu12': case 'upstart': template = getTemplate('upstart'); destination = '/etc/init.d/' + service_name; commands = [ 'chmod +x ' + destination, 'mkdir -p /var/lock/subsys', 'touch /var/lock/subsys/' + service_name, 'update-rc.d ' + service_name + ' defaults' ]; break; case 'systemv': case 'amazon': case 'centos6': template = getTemplate('upstart'); destination = '/etc/init.d/' + service_name; commands = [ 'chmod +x ' + destination, 'mkdir -p /var/lock/subsys', 'touch /var/lock/subsys/' + service_name, 'chkconfig --add ' + service_name, 'chkconfig ' + service_name + ' on', 'initctl list' ]; break; case 'macos': case 'darwin': case 'launchd': template = getTemplate('launchd'); destination = path.join(process.env.HOME, 'Library/LaunchAgents/' + launchd_service_name + '.plist'); commands = [ 'mkdir -p ' + path.join(process.env.HOME, 'Library/LaunchAgents'), 'launchctl load -w ' + destination ] break; case 'freebsd': case 'rcd': template = getTemplate('rcd'); service_name = (opts.serviceName || 'pm2_' + user); destination = '/usr/local/etc/rc.d/' + service_name; commands = [ 'chmod 755 ' + destination, 'sysrc ' + service_name + '_enable=YES' ]; break; case 'openbsd': case 'rcd-openbsd': template = getTemplate('rcd-openbsd'); service_name = (opts.serviceName || 'pm2_' + user); destination = path.join('/etc/rc.d/', service_name); commands = [ 'chmod 755 ' + destination, 'rcctl enable ' + service_name, 'rcctl start ' + service_name ]; break; case 'openrc': template = getTemplate('openrc'); service_name = openrc_service_name; destination = '/etc/init.d/' + service_name; commands = [ 'chmod +x ' + destination, 'rc-update add ' + service_name + ' default' ]; break; case 'smf': case 'sunos': case 'solaris': template = getTemplate('smf'); service_name = (opts.serviceName || 'pm2_' + user); destination = path.join(tmpPath(), service_name + '.xml'); commands = [ 'svccfg import ' + destination, 'svcadm enable ' + service_name ]; break; default: throw new Error('Unknown platform / init system name'); } /** * 4# Replace template variable value */ var envPath if (cst.HAS_NODE_EMBEDDED == true) envPath = util.format('%s:%s', process.env.PATH || '', path.dirname(process.execPath)) else if (new RegExp(path.dirname(process.execPath)).test(process.env.PATH)) envPath = process.env.PATH else envPath = util.format('%s:%s', process.env.PATH || '', path.dirname(process.execPath)) let pm2_bin_path = require.main.filename if (pm2_bin_path.includes('/lib/binaries/CLI.js') === true) { pm2_bin_path = pm2_bin_path.replace('/lib/binaries/CLI.js', '/bin/pm2') } template = template.replace(/%PM2_PATH%/g, pm2_bin_path) .replace(/%NODE_PATH%/g, envPath) .replace(/%USER%/g, user) .replace(/%HOME_PATH%/g, opts.hp ? path.resolve(opts.hp, '.pm2') : cst.PM2_ROOT_PATH) .replace(/%SERVICE_NAME%/g, service_name); Common.printOut(chalk.bold('Platform'), platform); Common.printOut(chalk.bold('Template')); Common.printOut(template); Common.printOut(chalk.bold('Target path')); Common.printOut(destination); Common.printOut(chalk.bold('Command list')); Common.printOut(commands); Common.printOut(cst.PREFIX_MSG + 'Writing init configuration in ' + destination); try { fs.writeFileSync(destination, template); } catch (e) { console.error(cst.PREFIX_MSG_ERR + 'Failure when trying to write startup script'); console.error(e.message || e); return cb(e); } Common.printOut(cst.PREFIX_MSG + 'Making script booting at startup...'); forEachLimit(commands, 1, function(command, next) { Common.printOut(cst.PREFIX_MSG + '[-] Executing: %s...', chalk.bold(command)); sexec(command, function(code, stdout, stderr) { if (code === 0) { Common.printOut(cst.PREFIX_MSG + chalk.bold('[v] Command successfully executed.')); return next(); } else { Common.printOut(chalk.red('[ERROR] Exit code : ' + code)) return next(new Error(command + ' failed, see error above.')); } }) }, function(err) { if (err) { console.error(cst.PREFIX_MSG_ERR + (err.message || err)); return cb(err); } Common.printOut(chalk.bold.blue('+---------------------------------------+')); Common.printOut(chalk.bold.blue((cst.PREFIX_MSG + 'Freeze a process list on reboot via:' ))); Common.printOut(chalk.bold('$ pm2 save')); Common.printOut(''); Common.printOut(chalk.bold.blue(cst.PREFIX_MSG + 'Remove init script via:')); Common.printOut(chalk.bold('$ pm2 unstartup ' + platform)); return cb(null, { destination : destination, template : template }); }); }; /** * DISABLED FEATURE * KEEPING METHOD FOR BACKWARD COMPAT */ CLI.prototype.autodump = function(cb) { return cb() } /** * Dump current processes managed by pm2 into DUMP_FILE_PATH file * @method dump * @param {} cb * @return */ CLI.prototype.dump = function(force, cb) { var env_arr = []; var that = this; if (typeof(force) === 'function') { cb = force force = false } if (!cb) Common.printOut(cst.PREFIX_MSG + 'Saving current process list...'); that.Client.executeRemote('getMonitorData', {}, function(err, list) { if (err) { Common.printError('Error retrieving process list: ' + err); return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT); } /** * Description * @method fin * @param {} err * @return */ function fin(err) { // try to fix issues with empty dump file // like #3485 if (!force && env_arr.length === 0 && !process.env.FORCE) { // fix : if no dump file, no process, only module and after pm2 update if (!fs.existsSync(cst.DUMP_FILE_PATH)) { that.clearDump(function(){}); } // if no process in list don't modify dump file // process list should not be empty if (cb) { return cb(new Error('Process list empty, cannot save empty list')); } else { Common.printOut(cst.PREFIX_MSG_WARNING + 'PM2 is not managing any process, skipping save...'); Common.printOut(cst.PREFIX_MSG_WARNING + 'To force saving use: pm2 save --force'); that.exitCli(cst.SUCCESS_EXIT); return; } } // Back up dump file try { if (fs.existsSync(cst.DUMP_FILE_PATH)) { fs.writeFileSync(cst.DUMP_BACKUP_FILE_PATH, fs.readFileSync(cst.DUMP_FILE_PATH)); } } catch (e) { console.error(e.stack || e); Common.printOut(cst.PREFIX_MSG_ERR + 'Failed to back up dump file in %s', cst.DUMP_BACKUP_FILE_PATH); } // Overwrite dump file, delete if broken and exit try { fs.writeFileSync(cst.DUMP_FILE_PATH, JSON.stringify(env_arr, '', 2)); } catch (e) { console.error(e.stack || e); try { // try to backup file if (fs.existsSync(cst.DUMP_BACKUP_FILE_PATH)) { fs.writeFileSync(cst.DUMP_FILE_PATH, fs.readFileSync(cst.DUMP_BACKUP_FILE_PATH)); } } catch (e) { // don't keep broken file fs.unlinkSync(cst.DUMP_FILE_PATH); console.error(e.stack || e); } Common.printOut(cst.PREFIX_MSG_ERR + 'Failed to save dump file in %s', cst.DUMP_FILE_PATH); return that.exitCli(cst.ERROR_EXIT); } if (cb) return cb(null, {success:true}); Common.printOut(cst.PREFIX_MSG + 'Successfully saved in %s', cst.DUMP_FILE_PATH); return that.exitCli(cst.SUCCESS_EXIT); } (function ex(apps) { if (!apps[0]) return fin(null); delete apps[0].pm2_env.instances; delete apps[0].pm2_env.pm_id; delete apps[0].pm2_env.prev_restart_delay; if (!apps[0].pm2_env.pmx_module) env_arr.push(apps[0].pm2_env); apps.shift(); return ex(apps); })(list); }); }; /** * Remove DUMP_FILE_PATH file and DUMP_BACKUP_FILE_PATH file * @method dump * @param {} cb * @return */ CLI.prototype.clearDump = function(cb) { fs.writeFileSync(cst.DUMP_FILE_PATH, JSON.stringify([])); if(cb && typeof cb === 'function') return cb(); Common.printOut(cst.PREFIX_MSG + 'Successfully created %s', cst.DUMP_FILE_PATH); return this.exitCli(cst.SUCCESS_EXIT); }; /** * Resurrect processes * @method resurrect * @param {} cb * @return */ CLI.prototype.resurrect = function(cb) { var apps = {}; var that = this; var processes; function readDumpFile(dumpFilePath) { Common.printOut(cst.PREFIX_MSG + 'Restoring processes located in %s', dumpFilePath); try { var apps = fs.readFileSync(dumpFilePath); } catch (e) { Common.printError(cst.PREFIX_MSG_ERR + 'Failed to read dump file in %s', dumpFilePath); throw e; } return apps; } function parseDumpFile(dumpFilePath, apps) { try { var processes = Common.parseConfig(apps, 'none'); } catch (e) { Common.printError(cst.PREFIX_MSG_ERR + 'Failed to parse dump file in %s', dumpFilePath); try { fs.unlinkSync(dumpFilePath); } catch (e) { console.error(e.stack || e); } throw e; } return processes; } // Read dump file, fall back to backup, delete if broken try { apps = readDumpFile(cst.DUMP_FILE_PATH); processes = parseDumpFile(cst.DUMP_FILE_PATH, apps); } catch(e) { try { apps = readDumpFile(cst.DUMP_BACKUP_FILE_PATH); processes = parseDumpFile(cst.DUMP_BACKUP_FILE_PATH, apps); } catch(e) { Common.printError(cst.PREFIX_MSG_ERR + 'No processes saved; DUMP file doesn\'t exist'); // if (cb) return cb(Common.retErr(e)); // else return that.exitCli(cst.ERROR_EXIT); return that.speedList(); } } that.Client.executeRemote('getMonitorData', {}, function(err, list) { if (err) { Common.printError(err); return that.exitCli(1); } var current = []; var target = []; list.forEach(function(app) { if (!current[app.name]) current[app.name] = 0; current[app.name]++; }); processes.forEach(function(app) { if (!target[app.name]) target[app.name] = 0; target[app.name]++; }); var tostart = Object.keys(target).filter(function(i) { return Object.keys(current).indexOf(i) < 0; }) eachLimit(processes, cst.CONCURRENT_ACTIONS, function(app, next) { if (tostart.indexOf(app.name) == -1) return next(); that.Client.executeRemote('prepare', app, function(err, dt) { if (err) Common.printError(err); else Common.printOut(cst.PREFIX_MSG + 'Process %s restored', app.pm_exec_path); next(); }); }, function(err) { return cb ? cb(null, apps) : that.speedList(); }); }); }; } ================================================ FILE: lib/API/UX/helpers.js ================================================ const chalk = require('ansis') const Helpers = {} /** * Converts Byte to Human readable size * @method bytesToSize * @param {} bytes * @param {} precision * @return */ Helpers.bytesToSize = function(bytes, precision) { var kilobyte = 1024 var megabyte = kilobyte * 1024 var gigabyte = megabyte * 1024 var terabyte = gigabyte * 1024 if ((bytes >= 0) && (bytes < kilobyte)) { return bytes + 'b ' } else if ((bytes >= kilobyte) && (bytes < megabyte)) { return (bytes / kilobyte).toFixed(precision) + 'kb ' } else if ((bytes >= megabyte) && (bytes < gigabyte)) { return (bytes / megabyte).toFixed(precision) + 'mb ' } else if ((bytes >= gigabyte) && (bytes < terabyte)) { return (bytes / gigabyte).toFixed(precision) + 'gb ' } else if (bytes >= terabyte) { return (bytes / terabyte).toFixed(precision) + 'tb ' } else { return bytes + 'b ' } } /** * Color Process state * @method colorStatus * @param {} status * @return */ Helpers.colorStatus = function(status) { switch (status) { case 'online': return chalk.green.bold('online') break case 'running': return chalk.green.bold('online') break case 'restarting': return chalk.yellow.bold('restart') break case 'created': return chalk.yellow.bold('created') break case 'launching': return chalk.blue.bold('launching') break default: return chalk.red.bold(status) } } /** * Safe Push */ Helpers.safe_push = function() { var argv = arguments var table = argv[0] for (var i = 1; i < argv.length; ++i) { var elem = argv[i] if (elem[Object.keys(elem)[0]] === undefined || elem[Object.keys(elem)[0]] === null) { elem[Object.keys(elem)[0]] = 'N/A' } else if (Array.isArray(elem[Object.keys(elem)[0]])) { elem[Object.keys(elem)[0]].forEach(function(curr, j) { if (curr === undefined || curr === null) elem[Object.keys(elem)[0]][j] = 'N/A' }) } table.push(elem) } } /** * Description * @method timeSince * @param {} date * @return BinaryExpression */ Helpers.timeSince = function(date) { var seconds = Math.floor((new Date() - date) / 1000) var interval = Math.floor(seconds / 31536000) if (interval > 1) { return interval + 'Y' } interval = Math.floor(seconds / 2592000) if (interval > 1) { return interval + 'M' } interval = Math.floor(seconds / 86400) if (interval > 1) { return interval + 'D' } interval = Math.floor(seconds / 3600) if (interval > 1) { return interval + 'h' } interval = Math.floor(seconds / 60) if (interval > 1) { return interval + 'm' } return Math.floor(seconds) + 's' } /** * Colorize Metrics * * @param {Number} value current value * @param {Number} warn value threshold * @param {Number} alert value threshold * @param {String} prefix value prefix * @return {String} value */ Helpers.colorizedMetric = function(value, warn, alert, prefix) { var inverted = false if (alert < warn) inverted = true if (!prefix) prefix = '' if (isNaN(value) === true) return 'N/A' if (value == 0) return 0 + prefix if (inverted == true) { if (value > warn) return chalk.green(value + prefix) if (value <= warn && value >= alert) return chalk.bold.yellow(value + prefix) return chalk.bold.red(value + prefix) } if (value < warn) return chalk.green(value + prefix) if (value >= warn && value <= alert) return chalk.bold.yellow(value + prefix) return chalk.bold.red(value + prefix) } /** * Get nested property * * @param {String} propertyName * @param {Object} obj * @returns {String} property value */ Helpers.getNestedProperty = function(propertyName, obj) { var parts = propertyName.split('.'), length = parts.length, property = obj || {} for (var i = 0; i < length; i++ ) { property = property[parts[i]] } return property } Helpers.openEditor = function (file, opts, cb) { var spawn = require('child_process').spawn if (typeof opts === 'function') { cb = opts opts = {} } if (!opts) opts = {} var ed = /^win/.test(process.platform) ? 'notepad' : 'vim' var editor = opts.editor || process.env.VISUAL || process.env.EDITOR || ed var args = editor.split(/\s+/) var bin = args.shift() var ps = spawn(bin, args.concat([ file ]), { windowsHide: true, stdio: 'inherit' }) ps.on('exit', function (code, sig) { if (typeof cb === 'function') cb(code, sig) }) } Helpers.dispKeys = function(kv, target_module) { Object.keys(kv).forEach(function(key) { if (target_module != null && target_module != key) return false if (typeof(kv[key]) == 'object') { var obj = {} console.log(chalk.bold('Module: ') + chalk.bold.blue(key)) Object.keys(kv[key]).forEach(function(sub_key) { console.log(`$ pm2 set ${key}:${sub_key} ${kv[key][sub_key]}`) }) } }) } module.exports = Helpers ================================================ FILE: lib/API/UX/index.js ================================================ const UX = { helpers: require('./helpers.js'), describe: require('./pm2-describe.js'), list: require('./pm2-ls.js'), list_min: require('./pm2-ls-minimal.js') } module.exports = UX ================================================ FILE: lib/API/UX/pm2-describe.js ================================================ const Table = require('cli-tableau') const chalk = require('ansis') const UxHelpers = require('./helpers.js') const Common = require('../../Common.js') var postModuleInfos = function(module_name, human_info) { var table = new Table({ style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true} }) var disp = {} human_info.unshift(['Module name', module_name]) human_info.forEach(function(info) { var obj = {} obj[chalk.bold.cyan(info[0])] = info[1] table.push(obj) }) console.log() console.log(chalk.bold.inverse(' Module %s infos '), module_name) console.log(table.toString()) } /** * Description * @method describeTable * @param {Object} proc process list */ module.exports = function(proc) { var table = new Table({ style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true} }) var pm2_env = proc.pm2_env var created_at = 'N/A' if (pm2_env.axm_options && pm2_env.axm_options.human_info) { postModuleInfos(pm2_env.name, pm2_env.axm_options.human_info) } try { if (pm2_env.created_at != null) created_at = new Date(pm2_env.created_at).toISOString() } catch (e) { } console.log(chalk.bold.inverse(' Describing process with id %d - name %s '), pm2_env.pm_id, pm2_env.name) UxHelpers.safe_push(table, { 'status' : UxHelpers.colorStatus(pm2_env.status) }, { 'name': pm2_env.name }, { 'namespace': pm2_env.namespace }, { 'version': pm2_env.version }, { 'restarts' : pm2_env.restart_time }, { 'uptime' : (pm2_env.pm_uptime && pm2_env.status == 'online') ? UxHelpers.timeSince(pm2_env.pm_uptime) : 0 }, { 'script path' : pm2_env.pm_exec_path }, { 'script args' : pm2_env.args ? (typeof pm2_env.args == 'string' ? JSON.parse(pm2_env.args.replace(/'/g, '"')):pm2_env.args).join(' ') : null }, { 'error log path' : pm2_env.pm_err_log_path }, { 'out log path' : pm2_env.pm_out_log_path }, { 'pid path' : pm2_env.pm_pid_path }, { 'interpreter' : pm2_env.exec_interpreter }, { 'interpreter args' : pm2_env.node_args.length != 0 ? pm2_env.node_args : null }, { 'script id' : pm2_env.pm_id }, { 'exec cwd' : pm2_env.pm_cwd }, { 'exec mode' : pm2_env.exec_mode }, { 'node.js version' : pm2_env.node_version }, { 'node env': pm2_env.env.NODE_ENV }, { 'watch & reload' : pm2_env.watch ? chalk.green.bold('✔') : '✘' }, { 'unstable restarts' : pm2_env.unstable_restarts }, { 'created at' : created_at } ) if ('pm_log_path' in pm2_env){ table.splice(6, 0, {'entire log path': pm2_env.pm_log_path}) } if ('cron_restart' in pm2_env){ table.splice(5, 0, {'cron restart': pm2_env.cron_restart}) } console.log(table.toString()) /** * Module conf display */ if (pm2_env.axm_options && pm2_env.axm_options.module_conf && Object.keys(pm2_env.axm_options.module_conf).length > 0) { var table_conf = new Table({ style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true} }) console.log('Process configuration') Object.keys(pm2_env.axm_options.module_conf).forEach(function(key) { var tmp = {} tmp[key] = pm2_env.axm_options.module_conf[key] UxHelpers.safe_push(table_conf, tmp) }) console.log(table_conf.toString()) } /** * Versioning metadata */ if (pm2_env.versioning) { var table2 = new Table({ style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true} }) console.log(chalk.inverse.bold(' Revision control metadata ')) UxHelpers.safe_push(table2, { 'revision control' : pm2_env.versioning.type }, { 'remote url' : pm2_env.versioning.url }, { 'repository root' : pm2_env.versioning.repo_path }, { 'last update' : pm2_env.versioning.update_time }, { 'revision' : pm2_env.versioning.revision }, { 'comment' : pm2_env.versioning.comment ? pm2_env.versioning.comment.trim().slice(0, 60) : '' }, { 'branch' : pm2_env.versioning.branch } ) console.log(table2.toString()) } if (pm2_env.axm_actions && Object.keys(pm2_env.axm_actions).length > 0) { var table_actions = new Table({ style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true} }) console.log(chalk.inverse.bold(' Actions available ')) pm2_env.axm_actions.forEach(function(action_set) { UxHelpers.safe_push(table_actions, [action_set.action_name]) }) console.log(table_actions.toString()) Common.printOut(chalk.white.italic(' Trigger via: pm2 trigger %s \n'), pm2_env.name) } if (pm2_env.axm_monitor && Object.keys(pm2_env.axm_monitor).length > 0) { var table_probes = new Table({ style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true} }) console.log(chalk.inverse.bold(' Code metrics value ')) Object.keys(pm2_env.axm_monitor).forEach(function(key) { var obj = {} var metric_name = pm2_env.axm_monitor[key].hasOwnProperty("value") ? pm2_env.axm_monitor[key].value : pm2_env.axm_monitor[key] var metric_unit = pm2_env.axm_monitor[key].hasOwnProperty("unit") ? pm2_env.axm_monitor[key].unit : '' var value = `${metric_name} ${metric_unit}` obj[key] = value UxHelpers.safe_push(table_probes, obj) }) console.log(table_probes.toString()) } var table_env = new Table({ style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true} }) console.log(chalk.inverse.bold(' Divergent env variables from local env ')) var _env = Common.safeExtend({}, pm2_env) var diff_env = {} Object.keys(process.env).forEach(k => { if (!_env[k] || _env[k] != process.env[k]) { diff_env[k] = process.env[k] } }) Object.keys(diff_env).forEach(function(key) { var obj = {} if (_env[key]) { // 1. fix env value is not a String and slice is undeinfed // 2. fix process.stdout.columns is undefined and causes empty string output // 3. columns defaults to 300 - same as specified in pm2-ls obj[key] = String(_env[key]).slice(0, (process.stdout.columns || 300) - 60) UxHelpers.safe_push(table_env, obj) } }) console.log(table_env.toString()) console.log() Common.printOut(chalk.white.italic(' Add your own code metrics: http://bit.ly/code-metrics')) Common.printOut(chalk.white.italic(' Use `pm2 logs %s [--lines 1000]` to display logs'), pm2_env.name) Common.printOut(chalk.white.italic(' Use `pm2 env %s` to display environment variables'), pm2_env.pm_id) Common.printOut(chalk.white.italic(' Use `pm2 monit` to monitor CPU and Memory usage'), pm2_env.name) } ================================================ FILE: lib/API/UX/pm2-ls-minimal.js ================================================ const UxHelpers = require('./helpers.js') const p = require('path') /** * Minimal display via pm2 ls -m * @method miniDisplay * @param {Object} list process list */ module.exports = function(list) { list.forEach(function(l) { var mode = l.pm2_env.exec_mode.split('_mode')[0] var status = l.pm2_env.status var key = l.pm2_env.name || p.basename(l.pm2_env.pm_exec_path.script) console.log('+--- %s', key) console.log('namespace : %s', l.pm2_env.namespace) console.log('version : %s', l.pm2_env.version) console.log('pid : %s', l.pid) console.log('pm2 id : %s', l.pm2_env.pm_id) console.log('status : %s', status) console.log('mode : %s', mode) console.log('restarted : %d', l.pm2_env.restart_time ? l.pm2_env.restart_time : 0) console.log('uptime : %s', (l.pm2_env.pm_uptime && status == 'online') ? UxHelpers.timeSince(l.pm2_env.pm_uptime) : 0) console.log('memory usage : %s', l.monit ? UxHelpers.bytesToSize(l.monit.memory, 1) : '') console.log('error log : %s', l.pm2_env.pm_err_log_path) console.log('watching : %s', l.pm2_env.watch ? 'yes' : 'no') console.log('PID file : %s\n', l.pm2_env.pm_pid_path) }) } ================================================ FILE: lib/API/UX/pm2-ls.js ================================================ const cst = require('../../../constants') const Common = require('../../Common') const Configuration = require('../../Configuration') const UxHelpers = require('./helpers.js') const chalk = require('ansis') const Table = require('cli-tableau') const Passwd = require('../../tools/passwd.js') const List = {} const CONDENSED_MODE = (process.stdout.columns || 300) < 134 /** * Check if dump file contains same apps that the one managed by PM2 */ function checkIfProcessAreDumped(list) { try { var dump_raw = require('fs').readFileSync(cst.DUMP_FILE_PATH) var dump = JSON.parse(dump_raw) var apps_dumped = dump.map(proc => proc.name) var apps_running = list .filter(proc => proc.pm2_env.pmx_module != true) .map(proc => proc.name) var diff = apps_dumped.filter(a => !apps_running.includes(a)) if (diff.length > 0) { Common.warn(`Current process list is not synchronized with saved list. App ${chalk.bold(diff.join(' '))} differs. Type 'pm2 save' to synchronize.`) } else if (apps_dumped.length != apps_running.length) { Common.warn(`Current process list is not synchronized with saved list. Type 'pm2 save' to synchronize.`) } } catch(e) { } } var proc_id = 0 /** * List Applications and Modules managed by PM2 */ function listModulesAndAppsManaged(list, commander) { var name_col_size = 11 if (list && list.length > 0) name_col_size = (list.reduce((p, c) => (p.name.length > c.name.length) ? p : c)).name.length + 5 var id_width = Math.max( 2 + (Math.max(...list.map((l) => String(l.pm2_env.pm_id || 0).length)) || 0), 4 ); var app_head = { id: id_width, name: name_col_size, namespace: 13, version: 9, mode: 9, pid: 10, uptime: 8, '↺': 6, status: 11, cpu: 10, mem: 10, user: 10, watching: 10 } var mod_head = { id: id_width, module: 30, version: 15, pid: 10, status: 10, '↺': 6, cpu: 10, mem: 10, user: 10 } if (CONDENSED_MODE) { app_head = { id: id_width, name: 20, mode: 10, '↺': 6, status: 11, cpu: 10, memory: 10 } mod_head = { id: id_width, name: 20, status: 10, cpu: 10, mem: 10 } } var app_table = new Table({ head : Object.keys(app_head), colWidths: Object.keys(app_head).map(k => app_head[k]), colAligns : ['left'], style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true} }) var module_table = new Table({ head : Object.keys(mod_head), colWidths: Object.keys(mod_head).map(k => mod_head[k]), colAligns : ['left'], style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true} }) var sortField = 'name', sortOrder = 'asc', sort, fields = { name: 'pm2_env.name', namespace: 'pm2_env.namespace', pid: 'pid', id: 'pm_id', cpu: 'monit.cpu', memory: 'monit.memory', uptime: 'pm2_env.pm_uptime', status: 'pm2_env.status' } if (commander && commander.sort) { sort = commander.sort.split(':'); if(fields[sort[0].toLowerCase()]) { sortField = sort[0].toLowerCase(); sortOrder = sort.length === 2 ? sort[1] : 'asc'; } } list.sort(function(a, b) { var fieldA = UxHelpers.getNestedProperty(fields[sortField], a) var fieldB = UxHelpers.getNestedProperty(fields[sortField], b) if (sortOrder === 'desc') { if (fieldA > fieldB) return -1 if (fieldA < fieldB) return 1 } else { if (fieldA < fieldB) return -1 if (fieldA > fieldB) return 1 } return 0 }) list.forEach(function(l) { var obj = {} if (l.pm2_env.pm_id > proc_id) { proc_id = l.pm2_env.pm_id } var mode = l.pm2_env.exec_mode var status = l.pm2_env.status var key = l.pm2_env.pm_id key = chalk.bold.cyan(key) if (l.pm2_env.axm_options) { var is_tracing_enabled = false if (l.pm2_env.axm_options.tracing && typeof(l.pm2_env.axm_options.tracing) == 'boolean' && l.pm2_env.axm_options.tracing == true) is_tracing_enabled = true if (l.pm2_env.axm_options.tracing && l.pm2_env.axm_options.tracing.enabled && typeof(l.pm2_env.axm_options.tracing.enabled) == 'boolean' && l.pm2_env.axm_options.tracing.enabled == true) is_tracing_enabled = true if (is_tracing_enabled == true) l.pm2_env.name = chalk.green('☵') + ' ' + l.pm2_env.name if (l.pm2_env._km_monitored) l.pm2_env.name = chalk.bold.green('◉') + ' ' + l.pm2_env.name } if (l.pm2_env.pmx_module == true) { if (l.pm2_env.name == 'pm2-sysmonit') return // pm2 ls for Modules obj[key] = [] obj[key].push(l.name) // Module version + PID if (!CONDENSED_MODE) { var pid = l.pm2_env.axm_options.pid ? l.pm2_env.axm_options.pid : l.pid obj[key].push(l.pm2_env.version || 'N/A', pid) } // Status obj[key].push(UxHelpers.colorStatus(status)) // Restart if (!CONDENSED_MODE) obj[key].push(l.pm2_env.restart_time ? l.pm2_env.restart_time : 0) // CPU + Memory obj[key].push(l.monit ? (l.monit.cpu + '%') : 'N/A', l.monit ? UxHelpers.bytesToSize(l.monit.memory, 1) : 'N/A' ) // User if (!CONDENSED_MODE) { if (l.pm2_env.uid && typeof(l.pm2_env.uid) == 'number') { // Resolve user id to username let users = Passwd.getUsers() Object.keys(users).forEach(function(username) { var user = users[username] if (user.userId == l.pm2_env.uid) { l.pm2_env.uid = user.username } }) } obj[key].push(chalk.bold(l.pm2_env.uid || l.pm2_env.username)) } UxHelpers.safe_push(module_table, obj) } else { // pm2 ls for Applications obj[key] = [] // PM2 ID obj[key].push(l.pm2_env.name) // Namespace if (!CONDENSED_MODE) obj[key].push(l.pm2_env.namespace) // Version if (!CONDENSED_MODE) obj[key].push(l.pm2_env.version) // Exec mode obj[key].push(mode == 'fork_mode' ? chalk.inverse.bold('fork') : chalk.blue.bold('cluster')) // PID if (!CONDENSED_MODE) obj[key].push(l.pid) // Uptime if (!CONDENSED_MODE) obj[key].push((l.pm2_env.pm_uptime && status == 'online') ? UxHelpers.timeSince(l.pm2_env.pm_uptime) : 0) // Restart obj[key].push(l.pm2_env.restart_time ? l.pm2_env.restart_time : 0) // Status obj[key].push(UxHelpers.colorStatus(status)) // CPU obj[key].push(l.monit ? l.monit.cpu + '%' : 'N/A') // Memory obj[key].push(l.monit ? UxHelpers.bytesToSize(l.monit.memory, 1) : 'N/A') // User if (!CONDENSED_MODE) { if (l.pm2_env.uid && typeof(l.pm2_env.uid) == 'number') { // Resolve user id to username let users = Passwd.getUsers() Object.keys(users).forEach(function(username) { var user = users[username] if (user.userId == l.pm2_env.uid) { l.pm2_env.uid = user.username } }) } obj[key].push(chalk.bold(l.pm2_env.uid || l.pm2_env.username)) } // Watch status if (!CONDENSED_MODE) obj[key].push(l.pm2_env.watch ? chalk.green.bold('enabled') : chalk.gray('disabled')) UxHelpers.safe_push(app_table, obj) } }) // Print Applications Managed console.log(app_table.toString()) // Print Modules Managed if (module_table.length > 0) { console.log(chalk.bold(`Module${module_table.length > 1 ? 's' : ''}`)) console.log(module_table.toString()) } proc_id++ } // Container display function containersListing(sys_infos) { var stacked_docker = (process.stdout.columns || 100) < 140 var docker_head = { id: 4, image: 50, status: 10, '↺': 6, cpu: 10, mem: 10, 'net I/O ⇵': 11, 'fs I/O ⇵': 11 } if (stacked_docker) { docker_head = { id: 4, image: 25, status: 10, cpu: 10, mem: 10 } } var docker_table = new Table({ colWidths: Object.keys(docker_head).map(k => docker_head[k]), head : Object.keys(docker_head), colAligns : ['left'], style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true} }) sys_infos.containers.forEach((c) => { var cpu = c.stats.cpu_percent var mem = c.stats.mem_percent == 0 ? '0' : c.stats.mem_percent var id = chalk.bold.cyan(proc_id++) var state = UxHelpers.colorStatus(c.state) if (stacked_docker) docker_table.push([id, c.image, state, `${cpu}%`, `${mem}mb`]) else { docker_table.push([ id, c.image, state, c.restartCount, `${cpu == 0 ? '0' : cpu}%`, `${mem}mb`, `${c.stats.netIO.rx}/${isNaN(c.stats.netIO.tx) == true ? '0.0' : c.stats.netIO.tx}`, `${c.stats.blockIO.r}/${c.stats.blockIO.w}` ]) } }) console.log(chalk.bold(`Container${sys_infos.containers.length > 1 ? 's' : ''}`)) console.log(docker_table.toString()) } /** * High resource processes */ function listHighResourcesProcesses(sys_infos) { const CPU_MIN_SHOW = 60 const MEM_MIN_SHOW = 30 var sys_proc_head = ['id', 'cmd', 'pid', 'cpu', 'mem', 'uid'] var sys_proc_table = new Table({ colWidths: [4, CONDENSED_MODE ? 29 : 77, 10, 10, 10, 8], head : sys_proc_head, colAligns : ['left'], style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true} }) sys_infos.processes.cpu_sorted = sys_infos.processes.cpu_sorted.filter((proc) => { return proc.cpu > CPU_MIN_SHOW && proc.cmd.includes('node') === false && proc.cmd.includes('God Daemon') === false }) sys_infos.processes.cpu_sorted.forEach(proc => { var cpu = `${UxHelpers.colorizedMetric(proc.cpu, 40, 70, '%')}` var mem = `${UxHelpers.colorizedMetric(proc.memory, 40, 70, '%')}` var cmd = proc.cmd sys_proc_table.push([chalk.bold.cyan(proc_id++), cmd, proc.pid, cpu, mem, proc.uid]) }) sys_infos.processes.mem_sorted = sys_infos.processes.mem_sorted.filter((proc) => { return proc.memory > MEM_MIN_SHOW && proc.cmd.includes('node') == false }) sys_infos.processes.mem_sorted.forEach((proc) => { var cpu = `${UxHelpers.colorizedMetric(proc.cpu, 40, 70, '%')}` var mem = `${UxHelpers.colorizedMetric(proc.memory, 40, 70, '%')}` var cmd = proc.cmd // if (proc.cmd.length > 50) // cmd = '…' + proc.cmd.slice(proc.cmd.length - 48, proc.cmd.length) sys_proc_table.push([chalk.bold.cyan(proc_id++), cmd, proc.pid, cpu, mem, proc.uid]) }) if (sys_infos.processes.cpu_sorted.length >= 1 || sys_infos.processes.mem_sorted.length >= 1) { console.log(chalk.bold('Intensive Processes')) console.log(sys_proc_table.toString()) } } /** * Sys info line */ function miniMonitBar(sys_infos) { let sys_metrics = sys_infos.pm2_env.axm_monitor let cpu = sys_metrics['CPU Usage'] if (typeof(cpu) == 'undefined') return var sys_summary_line = `${chalk.bold.cyan('host metrics')} ` sys_summary_line += `| ${chalk.bold('cpu')}: ${UxHelpers.colorizedMetric(cpu.value, 40, 70, '%')}` let temp = sys_metrics['CPU Temperature'].value if (temp && temp != '-1') { sys_summary_line += ` ${UxHelpers.colorizedMetric(temp, 50, 70, 'º')}` } let mem_total = sys_metrics['RAM Total'].value let mem_available = sys_metrics['RAM Available'].value if (mem_total) { var perc_mem_usage = (((mem_available) / mem_total) * 100).toFixed(1) sys_summary_line += ` | ${chalk.bold('mem free')}: ${UxHelpers.colorizedMetric(perc_mem_usage, 30, 10, '%')} ` } let interfaces = Object.keys(sys_metrics).filter(m => m.includes('net') && m != 'net:default').map(i => i.split(':')[2]).filter((iface, i, self) => self.indexOf(iface) === i) interfaces.forEach(iface => { if (!sys_metrics[`net:rx_5:${iface}`]) return sys_summary_line += `| ${chalk.bold(iface)}: ` sys_summary_line += `⇓ ${UxHelpers.colorizedMetric(sys_metrics[`net:rx_5:${iface}`].value, 10, 20, 'mb/s')} ` sys_summary_line += `⇑ ${UxHelpers.colorizedMetric(sys_metrics[`net:tx_5:${iface}`].value, 10, 20, 'mb/s')} ` }) if (CONDENSED_MODE == false) { let read = sys_metrics['Disk Reads'].value let write = sys_metrics['Disk Writes'].value sys_summary_line += `| ${chalk.bold('disk')}: ⇓ ${UxHelpers.colorizedMetric(read, 10, 20, 'mb/s')}` sys_summary_line += ` ⇑ ${UxHelpers.colorizedMetric(write, 10, 20, 'mb/s')} ` let disks = Object.keys(sys_metrics).filter(m => m.includes('fs:')).map(i => i.split(':')[2]).filter((iface, i, self) => self.indexOf(iface) === i) var disk_nb = 0 disks.forEach(fs => { let use = sys_metrics[`fs:use:${fs}`].value if (use > 60) sys_summary_line += `${chalk.gray(fs)} ${UxHelpers.colorizedMetric(use, 80, 90, '%')} ` }) } sys_summary_line += '|' console.log(sys_summary_line) } /** * pm2 ls * @method dispAsTable * @param {Object} list * @param {Object} system informations (via pm2 sysmonit/pm2 sysinfos) */ module.exports = function(list, commander) { var pm2_conf = Configuration.getSync('pm2') if (!list) return console.log('list empty') listModulesAndAppsManaged(list, commander) let sysmonit = list.filter(proc => proc.name == 'pm2-sysmonit') if (sysmonit && sysmonit[0]) miniMonitBar(sysmonit[0]) // Disable warning message of process list not saved //checkIfProcessAreDumped(list) } ================================================ FILE: lib/API/Version.js ================================================ var cst = require('../../constants.js'); var Common = require('../Common.js'); var fs = require('fs'); var eachSeries = require('async/eachSeries'); var child = require('child_process'); var printError = Common.printError; var printOut = Common.printOut; module.exports = function(CLI) { var EXEC_TIMEOUT = 60000; // Default: 1 min CLI.prototype._pull = function(opts, cb) { var that = this; var process_name = opts.process_name; var reload_type = opts.action; printOut(cst.PREFIX_MSG + 'Updating repository for process name %s', process_name); that.Client.getProcessByNameOrId(process_name, function (err, processes) { if (err || processes.length === 0) { printError('No processes with this name or id : %s', process_name); return cb ? cb({msg: 'Process not found: ' + process_name}) : that.exitCli(cst.ERROR_EXIT); } var proc = processes[0]; if (!proc.pm2_env.versioning) { printOut(cst.PREFIX_MSG + 'No versioning system found for process %s', process_name); return cb ? cb({success:false, msg: 'No versioning system found for process'}) : that.exitCli(cst.SUCCESS_EXIT); } require('vizion').update({ folder: proc.pm2_env.versioning.repo_path }, function(err, meta) { if (err !== null) { return cb ? cb({msg:err}) : that.exitCli(cst.ERROR_EXIT); } if (meta.success === true) { getPostUpdateCmds(proc.pm2_env.versioning.repo_path, process_name, function (command_list) { execCommands(proc.pm2_env.versioning.repo_path, command_list, function(err, res) { if (err !== null) { printError(err); return cb ? cb({msg: meta.output + err}) : that.exitCli(cst.ERROR_EXIT); } else { printOut(cst.PREFIX_MSG + 'Process successfully updated %s', process_name); printOut(cst.PREFIX_MSG + 'Current commit %s', meta.current_revision); return that[reload_type](process_name, function(err, procs) { if (err && cb) return cb(err); if (err) console.error(err); return cb ? cb(null, meta.output + res) : that.exitCli(cst.SUCCESS_EXIT); }); } }); }); } else { printOut(cst.PREFIX_MSG + 'Already up-to-date or an error occured for app: %s', process_name); return cb ? cb({success:false, msg : 'Already up to date'}) : that.exitCli(cst.SUCCESS_EXIT); } return false; }); return false; }); }; /** * CLI method for updating a repository to a specific commit id * @method pullCommitId * @param {string} process_name * @param {string} commit_id * @return */ CLI.prototype.pullCommitId = function(process_name, commit_id, cb) { var reload_type = 'reload'; var that = this; printOut(cst.PREFIX_MSG + 'Updating repository for process name %s', process_name); that.Client.getProcessByNameOrId(process_name, function (err, processes) { if (err || processes.length === 0) { printError('No processes with this name or id : %s', process_name); return cb ? cb({msg: 'Process not found: ' + process_name}) : that.exitCli(cst.ERROR_EXIT); } var proc = processes[0]; if (proc.pm2_env.versioning) { require('vizion').isUpToDate({folder: proc.pm2_env.versioning.repo_path}, function(err, meta) { if (err !== null) return cb ? cb({msg:err}) : that.exitCli(cst.ERROR_EXIT); require('vizion').revertTo( {revision: commit_id, folder: proc.pm2_env.versioning.repo_path}, function(err2, meta2) { if (!err2 && meta2.success) { getPostUpdateCmds(proc.pm2_env.versioning.repo_path, process_name, function (command_list) { execCommands(proc.pm2_env.versioning.repo_path, command_list, function(err, res) { if (err !== null) { printError(err); return cb ? cb({msg:err}) : that.exitCli(cst.ERROR_EXIT); } else { printOut(cst.PREFIX_MSG + 'Process successfully updated %s', process_name); printOut(cst.PREFIX_MSG + 'Current commit %s', commit_id); return that[reload_type](process_name, cb); } }); }); } else { printOut(cst.PREFIX_MSG + 'Already up-to-date or an error occured: %s', process_name); return cb ? cb(null, {success:meta.success}) : that.exitCli(cst.SUCCESS_EXIT); } }); }); } else { printOut(cst.PREFIX_MSG + 'No versioning system found for process %s', process_name); return cb ? cb(null, {success:false}) : that.exitCli(cst.SUCCESS_EXIT); } }); }; /** * CLI method for downgrading a repository to the previous commit (older) * @method backward * @param {string} process_name * @return */ CLI.prototype.backward = function(process_name, cb) { var that = this; printOut(cst.PREFIX_MSG + 'Downgrading to previous commit repository for process name %s', process_name); that.Client.getProcessByNameOrId(process_name, function (err, processes) { if (err || processes.length === 0) { printError('No processes with this name or id : %s', process_name); return cb ? cb({msg: 'Process not found: ' + process_name}) : that.exitCli(cst.ERROR_EXIT); } var proc = processes[0]; // in case user searched by id/pid process_name = proc.name; if (proc.pm2_env.versioning === undefined || proc.pm2_env.versioning === null) return cb({msg : 'Versioning unknown'}); require('vizion').prev({ folder: proc.pm2_env.versioning.repo_path }, function(err, meta) { if (err) return cb ? cb({msg:err, data : meta}) : that.exitCli(cst.ERROR_EXIT); if (meta.success !== true) { printOut(cst.PREFIX_MSG + 'No versioning system found for process %s', process_name); return cb ? cb({msg:err, data : meta}) : that.exitCli(cst.ERROR_EXIT); } getPostUpdateCmds(proc.pm2_env.versioning.repo_path, process_name, function (command_list) { execCommands(proc.pm2_env.versioning.repo_path, command_list, function(err, res) { if (err !== null) { require('vizion').next({folder: proc.pm2_env.versioning.repo_path}, function(err2, meta2) { printError(err); return cb ? cb({msg: meta.output + err}) : that.exitCli(cst.ERROR_EXIT); }); return false; } printOut(cst.PREFIX_MSG + 'Process successfully updated %s', process_name); printOut(cst.PREFIX_MSG + 'Current commit %s', meta.current_revision); that.reload(process_name, function(err, procs) { if (err) return cb(err); return cb ? cb(null, meta.output + res) : that.exitCli(cst.SUCCESS_EXIT); }); }); }); }); }); }; /** * CLI method for updating a repository to the next commit (more recent) * @method forward * @param {string} process_name * @return */ CLI.prototype.forward = function(process_name, cb) { var that = this; printOut(cst.PREFIX_MSG + 'Updating to next commit repository for process name %s', process_name); that.Client.getProcessByNameOrId(process_name, function (err, processes) { if (err || processes.length === 0) { printError('No processes with this name or id: %s', process_name); return cb ? cb({msg: 'Process not found: ' + process_name}) : that.exitCli(cst.ERROR_EXIT); } var proc = processes[0]; // in case user searched by id/pid process_name = proc.name; if (proc.pm2_env.versioning) { require('vizion').next({folder: proc.pm2_env.versioning.repo_path}, function(err, meta) { if (err !== null) return cb ? cb({msg:err}) : that.exitCli(cst.ERROR_EXIT); if (meta.success === true) { getPostUpdateCmds(proc.pm2_env.versioning.repo_path, process_name, function (command_list) { execCommands(proc.pm2_env.versioning.repo_path, command_list, function(err, res) { if (err !== null) { require('vizion').prev({folder: proc.pm2_env.versioning.repo_path}, function(err2, meta2) { printError(err); return cb ? cb({msg:meta.output + err}) : that.exitCli(cst.ERROR_EXIT); }); } else { printOut(cst.PREFIX_MSG + 'Process successfully updated %s', process_name); printOut(cst.PREFIX_MSG + 'Current commit %s', meta.current_revision); that.reload(process_name, function(err, procs) { if (err) return cb(err); return cb ? cb(null, meta.output + res) : that.exitCli(cst.SUCCESS_EXIT); }); } }); }); } else { printOut(cst.PREFIX_MSG + 'Already up-to-date or an error occured: %s', process_name); return cb ? cb(null, {success:meta.success}) : that.exitCli(cst.SUCCESS_EXIT); } }); } else { printOut(cst.PREFIX_MSG + 'No versioning system found for process %s', process_name); return cb ? cb({success:false, msg: 'No versioning system found'}) : that.exitCli(cst.SUCCESS_EXIT); } }); }; var exec = function (cmd, callback) { var output = ''; var c = child.exec(cmd, { env: process.env, maxBuffer: 3*1024*1024, timeout: EXEC_TIMEOUT }, function(err) { if (callback) callback(err ? err.code : 0, output); }); c.stdout.on('data', function(data) { output += data; }); c.stderr.on('data', function(data) { output += data; }); }; /** * * @method execCommands * @param {string} repo_path * @param {object} command_list * @return */ var execCommands = function(repo_path, command_list, cb) { var stdout = ''; eachSeries(command_list, function(command, callback) { stdout += '\n' + command; exec('cd '+repo_path+';'+command, function(code, output) { stdout += '\n' + output; if (code === 0) callback(); else callback('`'+command+'` failed'); }); }, function(err) { if (err) return cb(stdout + '\n' + err); return cb(null, stdout); }); } /** * Description Search process.json for post-update commands * @method getPostUpdateCmds * @param {string} repo_path * @param {string} proc_name * @return */ var getPostUpdateCmds = function(repo_path, proc_name, cb) { if (typeof repo_path !== 'string') return cb([]); if (repo_path[repo_path.length - 1] !== '/') repo_path += '/'; var searchForCommands = function(file, callback) { fs.exists(repo_path+file, function(exists) { if (exists) { try { var conf_string = fs.readFileSync(repo_path + file); var data = Common.parseConfig(conf_string, repo_path + file); } catch (e) { console.error(e.message || e); } if (data && data.apps) { eachSeries(data.apps, function(item, callb) { if (item.name && item.name === proc_name) { if (item.post_update && typeof(item.post_update) === 'object') { if (item.exec_timeout) EXEC_TIMEOUT = parseInt(item.exec_timeout); return callb(item.post_update); } else { return callb(); } } else return callb(); }, function(final) { return callback(final); }); } else { return callback(); } } else { return callback(); } }); }; eachSeries(['ecosystem.json', 'process.json', 'package.json'], searchForCommands, function(final) { return cb(final ? final : []); }); }; /** * CLI method for updating a repository * @method pullAndRestart * @param {string} process_name name of processes to pull * @return */ CLI.prototype.pullAndRestart = function (process_name, cb) { this._pull({process_name: process_name, action: 'reload'}, cb); }; /** * CLI method for updating a repository * @method pullAndReload * @param {string} process_name name of processes to pull * @return */ CLI.prototype.pullAndReload = function (process_name, cb) { this._pull({process_name: process_name, action: 'reload'}, cb); }; /** * CLI method for updating a repository to a specific commit id * @method pullCommitId * @param {object} opts * @return */ CLI.prototype._pullCommitId = function (opts, cb) { this.pullCommitId(opts.pm2_name, opts.commit_id, cb); }; } ================================================ FILE: lib/API/interpreter.json ================================================ { ".sh" : "bash", ".py" : "python", ".rb" : "ruby", ".php" : "php", ".pl" : "perl", ".js" : "node", ".coffee" : "coffee", ".ls" : "lsc", ".ts" : "bun", ".tsx" : "bun" } ================================================ FILE: lib/API/pm2-plus/PM2IO.js ================================================ 'use strict' var cst = require('../../../constants.js'); const chalk = require('ansis'); const path = require('path'); const fs = require('fs'); const Table = require('cli-tableau'); const pkg = require('../../../package.json') const IOAPI = require('@pm2/js-api') const promptly = require('promptly') var CLIStrategy = require('./auth-strategies/CliAuth') var WebStrategy = require('./auth-strategies/WebAuth') const exec = require('child_process').exec const OAUTH_CLIENT_ID_WEB = '138558311' const OAUTH_CLIENT_ID_CLI = '0943857435' module.exports = class PM2ioHandler { static usePM2Client (instance) { this.pm2 = instance } static strategy () { switch (process.platform) { case 'darwin': { return new WebStrategy({ client_id: OAUTH_CLIENT_ID_WEB }) } case 'win32': { return new WebStrategy({ client_id: OAUTH_CLIENT_ID_WEB }) } case 'linux': { const isDesktop = process.env.XDG_CURRENT_DESKTOP || process.env.XDG_SESSION_DESKTOP || process.env.DISPLAY const isSSH = process.env.SSH_TTY || process.env.SSH_CONNECTION if (isDesktop && !isSSH) { return new WebStrategy({ client_id: OAUTH_CLIENT_ID_WEB }) } else { return new CLIStrategy({ client_id: OAUTH_CLIENT_ID_CLI }) } } default: { return new CLIStrategy({ client_id: OAUTH_CLIENT_ID_CLI }) } } } static init () { this._strategy = this.strategy() /** * If you are using a local backend you should give those options : * { * services: { * API: 'http://localhost:3000', * OAUTH: 'http://localhost:3100' * } * } */ this.io = new IOAPI().use(this._strategy) } static launch (command, opts) { // first init the strategy and the io client this.init() switch (command) { case 'connect' : case 'login' : case 'register' : case undefined : case 'authenticate' : { this.authenticate() break } case 'validate' : { this.validateAccount(opts) break } case 'help' : case 'welcome': { var dt = fs.readFileSync(path.join(__dirname, './pres/welcome')); console.log(dt.toString()); return process.exit(0) } case 'logout': { this._strategy.isAuthenticated().then(isConnected => { // try to kill the agent anyway this.pm2.killAgent(err => {}) if (isConnected === false) { console.log(`${cst.PM2_IO_MSG} Already disconnected`) return process.exit(0) } this._strategy._retrieveTokens((err, tokens) => { if (err) { console.log(`${cst.PM2_IO_MSG} Successfully disconnected`) return process.exit(0) } this._strategy.deleteTokens(this.io).then(_ => { console.log(`${cst.PM2_IO_MSG} Successfully disconnected`) return process.exit(0) }).catch(err => { console.log(`${cst.PM2_IO_MSG_ERR} Unexpected error: ${err.message}`) return process.exit(1) }) }) }).catch(err => { console.error(`${cst.PM2_IO_MSG_ERR} Failed to logout: ${err.message}`) console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`) }) break } case 'create': { this._strategy.isAuthenticated().then(res => { // if the user isn't authenticated, we make them do the whole flow if (res !== true) { this.authenticate() } else { this.createBucket(this.createBucketHandler.bind(this)) } }).catch(err => { console.error(`${cst.PM2_IO_MSG_ERR} Failed to create to the bucket: ${err.message}`) console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`) }) break } case 'web': { this._strategy.isAuthenticated().then(res => { // if the user isn't authenticated, we make them do the whole flow if (res === false) { console.error(`${cst.PM2_IO_MSG_ERR} You need to be authenticated to do that, please use: pm2 plus login`) return process.exit(1) } this._strategy._retrieveTokens(() => { return this.openUI() }) }).catch(err => { console.error(`${cst.PM2_IO_MSG_ERR} Failed to open the UI: ${err.message}`) console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`) }) break } default : { console.log(`${cst.PM2_IO_MSG_ERR} Invalid command ${command}, available : login,register,validate,connect or web`) process.exit(1) } } } static openUI () { this.io.bucket.retrieveAll().then(res => { const buckets = res.data if (buckets.length === 0) { return this.createBucket((err, bucket) => { if (err) { console.error(`${cst.PM2_IO_MSG_ERR} Failed to connect to the bucket: ${err.message}`) if (bucket) { console.error(`${cst.PM2_IO_MSG_ERR} You can retry using: pm2 plus link ${bucket.secret_id} ${bucket.public_id}`) } console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`) return process.exit(0) } const targetURL = `https://app.pm2.io/#/bucket/${bucket._id}` console.log(`${cst.PM2_IO_MSG} Please follow the popup or go to this URL :`, '\n', ' ', targetURL) this.open(targetURL) return process.exit(0) }) } var table = new Table({ style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true}, head : ['Bucket name', 'Plan type'] }) buckets.forEach(function(bucket) { table.push([bucket.name, bucket.credits.offer_type]) }) console.log(table.toString()) console.log(`${cst.PM2_IO_MSG} If you don't want to open the UI to a bucket, type 'none'`) const choices = buckets.map(bucket => bucket.name) choices.push('none') promptly.choose(`${cst.PM2_IO_MSG} Type the name of the bucket you want to connect to :`, choices, (err, value) => { if (value === 'none') process.exit(0) const bucket = buckets.find(bucket => bucket.name === value) if (bucket === undefined) return process.exit(0) const targetURL = `https://app.pm2.io/#/bucket/${bucket._id}` console.log(`${cst.PM2_IO_MSG} Please follow the popup or go to this URL :`, '\n', ' ', targetURL) this.open(targetURL) return process.exit(0) }) }) } static validateAccount (token) { this.io.auth.validEmail(token) .then(res => { console.log(`${cst.PM2_IO_MSG} Email succesfully validated.`) console.log(`${cst.PM2_IO_MSG} You can now proceed and use: pm2 plus connect`) return process.exit(0) }).catch(err => { if (err.status === 401) { console.error(`${cst.PM2_IO_MSG_ERR} Invalid token`) return process.exit(1) } else if (err.status === 301) { console.log(`${cst.PM2_IO_MSG} Email succesfully validated.`) console.log(`${cst.PM2_IO_MSG} You can now proceed and use: pm2 plus connect`) return process.exit(0) } const msg = err.data ? err.data.error_description || err.data.msg : err.message console.error(`${cst.PM2_IO_MSG_ERR} Failed to validate your email: ${msg}`) console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`) return process.exit(1) }) } static createBucketHandler (err, bucket) { if (err) { console.trace(`${cst.PM2_IO_MSG_ERR} Failed to connect to the bucket: ${err.message}`) if (bucket) { console.error(`${cst.PM2_IO_MSG_ERR} You can retry using: pm2 plus link ${bucket.secret_id} ${bucket.public_id}`) } console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`) return process.exit(0) } if (bucket === undefined) { return process.exit(0) } console.log(`${cst.PM2_IO_MSG} Successfully connected to bucket ${bucket.name}`) var targetURL = `https://app.pm2.io/#/bucket/${bucket._id}` console.log(`${cst.PM2_IO_MSG} You can use the web interface over there: ${targetURL}`) this.open(targetURL) return process.exit(0) } static createBucket (cb) { console.log(`${cst.PM2_IO_MSG} By default we allow you to trial PM2 Plus for 14 days without any credit card.`) this.io.bucket.create({ name: 'PM2 Plus Monitoring' }).then(res => { const bucket = res.data.bucket console.log(`${cst.PM2_IO_MSG} Successfully created the bucket`) this.pm2.link({ public_key: bucket.public_id, secret_key: bucket.secret_id, pm2_version: pkg.version }, (err) => { if (err) { return cb(new Error('Failed to connect your local PM2 to your bucket'), bucket) } else { return cb(null, bucket) } }) }).catch(err => { return cb(new Error(`Failed to create a bucket: ${err.message}`)) }) } /** * Connect the local agent to a specific bucket * @param {Function} cb */ static connectToBucket (cb) { this.io.bucket.retrieveAll().then(res => { const buckets = res.data if (buckets.length === 0) { return this.createBucket(cb) } var table = new Table({ style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true}, head : ['Bucket name', 'Plan type'] }) buckets.forEach(function(bucket) { table.push([bucket.name, bucket.payment.offer_type]) }) console.log(table.toString()) console.log(`${cst.PM2_IO_MSG} If you don't want to connect to a bucket, type 'none'`) const choices = buckets.map(bucket => bucket.name) choices.push('none') promptly.choose(`${cst.PM2_IO_MSG} Type the name of the bucket you want to connect to :`, choices, (err, value) => { if (value === 'none') return cb() const bucket = buckets.find(bucket => bucket.name === value) if (bucket === undefined) return cb() this.pm2.link({ public_key: bucket.public_id, secret_key: bucket.secret_id, pm2_version: pkg.version }, (err) => { return err ? cb(err) : cb(null, bucket) }) }) }) } /** * Authenticate the user with either of the strategy * @param {Function} cb */ static authenticate () { this._strategy._retrieveTokens((err, tokens) => { if (err) { const msg = err.data ? err.data.error_description || err.data.msg : err.message console.log(`${cst.PM2_IO_MSG_ERR} Unexpected error : ${msg}`) return process.exit(1) } console.log(`${cst.PM2_IO_MSG} Successfully authenticated`) this.io.user.retrieve().then(res => { const user = res.data this.io.user.retrieve().then(res => { const tmpUser = res.data console.log(`${cst.PM2_IO_MSG} Successfully validated`) this.connectToBucket(this.createBucketHandler.bind(this)) }) }) }) } static open (target, appName, callback) { let opener const escape = function (s) { return s.replace(/"/g, '\\"') } if (typeof (appName) === 'function') { callback = appName appName = null } switch (process.platform) { case 'darwin': { opener = appName ? `open -a "${escape(appName)}"` : `open` break } case 'win32': { opener = appName ? `start "" ${escape(appName)}"` : `start ""` break } default: { opener = appName ? escape(appName) : `xdg-open` break } } if (process.env.SUDO_USER) { opener = 'sudo -u ' + process.env.SUDO_USER + ' ' + opener } return exec(`${opener} "${escape(target)}"`, callback) } } ================================================ FILE: lib/API/pm2-plus/auth-strategies/CliAuth.js ================================================ 'use strict' const AuthStrategy = require('@pm2/js-api/src/auth_strategies/strategy') const querystring = require('querystring'); const http = require('http') const fs = require('fs') const url = require('url') const exec = require('child_process').exec const tryEach = require('async/tryEach') const path = require('path') const os = require('os') const needle = require('needle') const chalk = require('ansis') const cst = require('../../../../constants.js') const promptly = require('promptly') module.exports = class CliStrategy extends AuthStrategy { // the client will try to call this but we handle this part ourselves retrieveTokens (km, cb) { this.authenticated = false this.callback = cb this.km = km this.BASE_URI = 'https://id.keymetrics.io'; } // so the cli know if we need to tell user to login/register isAuthenticated () { return new Promise((resolve, reject) => { if (this.authenticated) return resolve(true) let tokensPath = cst.PM2_IO_ACCESS_TOKEN fs.readFile(tokensPath, (err, tokens) => { if (err && err.code === 'ENOENT') return resolve(false) if (err) return reject(err) // verify that the token is valid try { tokens = JSON.parse(tokens || '{}') } catch (err) { fs.unlinkSync(tokensPath) return resolve(false) } // if the refresh tokens is here, the user could be automatically authenticated return resolve(typeof tokens.refresh_token === 'string') }) }) } verifyToken (refresh) { return this.km.auth.retrieveToken({ client_id: this.client_id, refresh_token: refresh }) } // called when we are sure the user asked to be logged in _retrieveTokens (optionalCallback) { const km = this.km const cb = this.callback tryEach([ // try to find the token via the environment (next) => { if (!process.env.PM2_IO_TOKEN) { return next(new Error('No token in env')) } this.verifyToken(process.env.PM2_IO_TOKEN) .then((res) => { return next(null, res.data) }).catch(next) }, // try to find it in the file system (next) => { fs.readFile(cst.PM2_IO_ACCESS_TOKEN, (err, tokens) => { if (err) return next(err) // verify that the token is valid tokens = JSON.parse(tokens || '{}') if (new Date(tokens.expire_at) > new Date(new Date().toISOString())) { return next(null, tokens) } this.verifyToken(tokens.refresh_token) .then((res) => { return next(null, res.data) }).catch(next) }) }, // otherwise make the whole flow (next) => { return this.authenticate((err, data) => { if (err instanceof Error) return next(err) // verify that the token is valid this.verifyToken(data.refresh_token) .then((res) => { return next(null, res.data) }).catch(next) }) } ], (err, result) => { // if present run the optional callback if (typeof optionalCallback === 'function') { optionalCallback(err, result) } if (result.refresh_token) { this.authenticated = true let file = cst.PM2_IO_ACCESS_TOKEN fs.writeFile(file, JSON.stringify(result), () => { return cb(err, result) }) } else { return cb(err, result) } }) } authenticate (cb) { console.log(`${cst.PM2_IO_MSG} Using non-browser authentication.`) promptly.confirm(`${cst.PM2_IO_MSG} Do you have a pm2.io account? (y/n)`, (err, answer) => { // Either login or register return answer === true ? this.login(cb) : this.register(cb) }) } login (cb) { let retry = () => { promptly.prompt(`${cst.PM2_IO_MSG} Your username or email: `, (err, username) => { if (err) return retry(); promptly.password(`${cst.PM2_IO_MSG} Your password: `, { replace : '*' }, (err, password) => { if (err) return retry(); console.log(`${cst.PM2_IO_MSG} Authenticating ...`) this._loginUser({ username: username, password: password }, (err, data) => { if (err) { console.error(`${cst.PM2_IO_MSG_ERR} Failed to authenticate: ${err.message}`) return retry() } return cb(null, data) }) }) }) } retry() } register (cb) { console.log(`${cst.PM2_IO_MSG} No problem ! We just need few informations to create your account`) var retry = () => { promptly.prompt(`${cst.PM2_IO_MSG} Please choose an username :`, { validator : this._validateUsername, retry : true }, (err, username) => { promptly.prompt(`${cst.PM2_IO_MSG} Please choose an email :`, { validator : this._validateEmail, retry : true },(err, email) => { promptly.password(`${cst.PM2_IO_MSG} Please choose a password :`, { replace : '*' }, (err, password) => { promptly.confirm(`${cst.PM2_IO_MSG} Do you accept the terms and privacy policy (https://pm2.io/legals/terms_conditions.pdf) ? (y/n)`, (err, answer) => { if (err) { console.error(chalk.bold.red(err)); return retry() } else if (answer === false) { console.error(`${cst.PM2_IO_MSG_ERR} You must accept the terms and privacy policy to contiue.`) return retry() } this._registerUser({ email : email, password : password, username : username }, (err, data) => { console.log('\n') if (err) { console.error(`${cst.PM2_IO_MSG_ERR} Unexpect error: ${err.message}`) console.error(`${cst.PM2_IO_MSG_ERR} You can also contact us to get help: contact@pm2.io`) return process.exit(1) } return cb(undefined, data) }) }) }) }) }) } retry() } /** * Register function * @param opts.username * @param opts.password * @param opts.email */ _registerUser (opts, cb) { const data = Object.assign(opts, { password_confirmation: opts.password, accept_terms: true }) needle.post(this.BASE_URI + '/api/oauth/register', data, { json: true, headers: { 'X-Register-Provider': 'pm2-register', 'x-client-id': this.client_id } }, function (err, res, body) { if (err) return cb(err) if (body.email && body.email.message) return cb(new Error(body.email.message)) if (body.username && body.username.message) return cb(new Error(body.username.message)) if (!body.access_token) return cb(new Error(body.msg)) return cb(null, { refresh_token : body.refresh_token.token, access_token : body.access_token.token }) }); } _loginUser (user_info, cb) { const URL_AUTH = '/api/oauth/authorize?response_type=token&scope=all&client_id=' + this.client_id + '&redirect_uri=http://localhost:43532'; needle.get(this.BASE_URI + URL_AUTH, (err, res) => { if (err) return cb(err); var cookie = res.cookies; needle.post(this.BASE_URI + '/api/oauth/login', user_info, { cookies : cookie }, (err, resp, body) => { if (err) return cb(err) if (resp.statusCode != 200) return cb('Wrong credentials') var location = resp.headers['x-redirect'] needle.get(this.BASE_URI + location, { cookies : cookie }, (err, res) => { if (err) return cb(err); var refresh_token = querystring.parse(url.parse(res.headers.location).query).access_token; needle.post(this.BASE_URI + '/api/oauth/token', { client_id : this.client_id, grant_type : 'refresh_token', refresh_token : refresh_token, scope : 'all' }, (err, res, body) => { if (err) return cb(err) return cb(null, body) }) }) }) }) } _validateEmail (email) { var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; if (re.test(email) == false) throw new Error('Not an email'); return email; } _validateUsername (value) { if (value.length < 6) { throw new Error('Min length of 6'); } return value; }; deleteTokens (km) { return new Promise((resolve, reject) => { // revoke the refreshToken km.auth.revoke() .then(res => { // remove the token from the filesystem let file = cst.PM2_IO_ACCESS_TOKEN fs.unlinkSync(file) return resolve(res) }).catch(reject) }) } } ================================================ FILE: lib/API/pm2-plus/auth-strategies/WebAuth.js ================================================ 'use strict' const cst = require('../../../../constants.js'); const AuthStrategy = require('@pm2/js-api/src/auth_strategies/strategy') const http = require('http') const fs = require('fs') const url = require('url') const exec = require('child_process').exec const tryEach = require('async/tryEach'); module.exports = class WebStrategy extends AuthStrategy { // the client will try to call this but we handle this part ourselves retrieveTokens (km, cb) { this.authenticated = false this.callback = cb this.km = km } // so the cli know if we need to tell user to login/register isAuthenticated () { return new Promise((resolve, reject) => { if (this.authenticated) return resolve(true) let tokensPath = cst.PM2_IO_ACCESS_TOKEN fs.readFile(tokensPath, (err, tokens) => { if (err && err.code === 'ENOENT') return resolve(false) if (err) return reject(err) // verify that the token is valid try { tokens = JSON.parse(tokens || '{}') } catch (err) { fs.unlinkSync(tokensPath) return resolve(false) } // if the refresh tokens is here, the user could be automatically authenticated return resolve(typeof tokens.refresh_token === 'string') }) }) } // called when we are sure the user asked to be logged in _retrieveTokens (optionalCallback) { const km = this.km const cb = this.callback let verifyToken = (refresh) => { return km.auth.retrieveToken({ client_id: this.client_id, refresh_token: refresh }) } tryEach([ // try to find the token via the environment (next) => { if (!process.env.PM2_IO_TOKEN) { return next(new Error('No token in env')) } verifyToken(process.env.PM2_IO_TOKEN) .then((res) => { return next(null, res.data) }).catch(next) }, // try to find it in the file system (next) => { fs.readFile(cst.PM2_IO_ACCESS_TOKEN, (err, tokens) => { if (err) return next(err) // verify that the token is valid tokens = JSON.parse(tokens || '{}') if (new Date(tokens.expire_at) > new Date(new Date().toISOString())) { return next(null, tokens) } verifyToken(tokens.refresh_token) .then((res) => { return next(null, res.data) }).catch(next) }) }, // otherwise make the whole flow (next) => { return this.loginViaWeb((data) => { // verify that the token is valid verifyToken(data.access_token) .then((res) => { return next(null, res.data) }).catch(err => next(err)) }) } ], (err, result) => { // if present run the optional callback if (typeof optionalCallback === 'function') { optionalCallback(err, result) } if (result.refresh_token) { this.authenticated = true let file = cst.PM2_IO_ACCESS_TOKEN fs.writeFile(file, JSON.stringify(result), () => { return cb(err, result) }) } else { return cb(err, result) } }) } loginViaWeb (cb) { const redirectURL = `${this.oauth_endpoint}${this.oauth_query}` console.log(`${cst.PM2_IO_MSG} Please follow the popup or go to this URL :`, '\n', ' ', redirectURL) let shutdown = false let server = http.createServer((req, res) => { // only handle one request if (shutdown === true) return res.end() shutdown = true let query = url.parse(req.url, true).query res.write(`

You can go back to your terminal now :)

`) res.end() server.close() return cb(query) }) server.listen(43532, () => { this.open(redirectURL) }) } deleteTokens (km) { return new Promise((resolve, reject) => { // revoke the refreshToken km.auth.revoke() .then(res => { // remove the token from the filesystem let file = cst.PM2_IO_ACCESS_TOKEN fs.unlinkSync(file) return resolve(res) }).catch(reject) }) } open (target, appName, callback) { let opener const escape = function (s) { return s.replace(/"/g, '\\"') } if (typeof (appName) === 'function') { callback = appName appName = null } switch (process.platform) { case 'darwin': { opener = appName ? `open -a "${escape(appName)}"` : `open` break } case 'win32': { opener = appName ? `start "" ${escape(appName)}"` : `start ""` break } default: { opener = appName ? escape(appName) : `xdg-open` break } } if (process.env.SUDO_USER) { opener = 'sudo -u ' + process.env.SUDO_USER + ' ' + opener } return exec(`${opener} "${escape(target)}"`, callback) } } ================================================ FILE: lib/API/pm2-plus/helpers.js ================================================ var cst = require('../../../constants.js'); var Common = require('../../Common.js'); const chalk = require('ansis'); const forEach = require('async/forEach'); const open = require('../../tools/open.js'); const Modules = require('../Modules'); function processesAreAlreadyMonitored(CLI, cb) { CLI.Client.executeRemote('getMonitorData', {}, function(err, list) { if (err) return cb(false); var l = list.filter(l => l.pm2_env.km_link == true) var l2 = list.filter(l => l.name == 'pm2-server-monit') return cb(l.length > 0 && l2.length > 0 ? true : false) }) } module.exports = function(CLI) { CLI.prototype.openDashboard = function() { if (!this.gl_interact_infos) { Common.printError(chalk.bold.white('Agent if offline, type `$ pm2 plus` to log in')); return this.exitCli(cst.ERROR_EXIT); } var uri = `https://app.pm2.io/#/r/${this.gl_interact_infos.public_key}` console.log(cst.PM2_IO_MSG + ` Opening ${uri}`) open(uri); setTimeout(_ => { this.exitCli(); }, 200); }; CLI.prototype.clearSetup = function (opts, cb) { const modules = ['event-loop-inspector'] this.gl_is_km_linked = false forEach(modules, (_module, next) => { Modules.uninstall(this, _module, () => { next() }); }, (err) => { this.reload('all', () => { return cb() }) }) } /** * Install required package and enable flags for current running processes */ CLI.prototype.minimumSetup = function (opts, cb) { var self = this; this.gl_is_km_linked = true function install(cb) { var modules = [] if (opts.type === 'enterprise' || opts.type === 'plus') { modules = ['pm2-logrotate', 'pm2-server-monit'] if (opts.type === 'enterprise') { modules.push('deep-metrics') } } forEach(modules, (_module, next) => { Modules.install(self, _module, {}, () => { next() }); }, (err) => { self.reload('all', () => { return cb() }) }) } processesAreAlreadyMonitored(self, (already_monitored) => { if (already_monitored) { console.log(cst.PM2_IO_MSG + ` PM2 ${opts.type || ''} bundle already installed`); return cb() } if (opts.installAll) return install(cb) // promptly.confirm(chalk.bold('Install all pm2 plus dependencies ? (y/n)'), (err, answer) => { // if (!err && answer === true) return install(cb) // self.reload('all', () => { // return cb() // }) // }); }) } } ================================================ FILE: lib/API/pm2-plus/link.js ================================================ var cst = require('../../../constants.js'); var Common = require('../../Common.js'); var chalk = require('ansis'); var fs = require('fs'); var KMDaemon = require('@pm2/agent/src/InteractorClient'); var pkg = require('../../../package.json') module.exports = function(CLI) { CLI.prototype.linkManagement = function(cmd, public_key, machine, opts, cb) { var that = this; // pm2 link stop || kill if (cmd == 'stop' || cmd == 'kill') { that.gl_is_km_linked = false console.log(cst.PM2_IO_MSG + ' Stopping agent...'); return that.killAgent(function(err) { if (err) { Common.printError(err); return process.exit(cst.ERROR_EXIT); } console.log(cst.PM2_IO_MSG + ' Stopped'); that.reload('all', () => { return process.exit(cst.SUCCESS_EXIT); }) }); } // pm2 link info if (cmd == 'info') { console.log(cst.PM2_IO_MSG + ' Getting agent information...'); that.agentInfos(function(err, infos) { if (err) { console.error(cst.PM2_IO_MSG_ERR + ' ' + err.message); return that.exitCli(cst.ERROR_EXIT); } console.log(infos); return that.exitCli(cst.SUCCESS_EXIT); }); return false; } // pm2 link delete if (cmd == 'delete') { that.gl_is_km_linked = false console.log(cst.PM2_IO_MSG + ' Permanently disable agent...'); that.killAgent(function(err) { try { fs.unlinkSync(cst.INTERACTION_CONF); } catch(e) { console.log(cst.PM2_IO_MSG + ' No interaction config file found'); return process.exit(cst.SUCCESS_EXIT); } console.log(cst.PM2_IO_MSG + ' Agent interaction ended'); if (!cb) return process.exit(cst.SUCCESS_EXIT); return cb() }); return false; } if (cmd && !public_key) { console.error(cst.PM2_IO_MSG + ' Command [%s] unknown or missing public key', cmd); return process.exit(cst.ERROR_EXIT); } // pm2 link xxx yyy var infos; if (!cmd) { infos = null; } else infos = { public_key : public_key, secret_key : cmd, machine_name : machine, info_node : opts.infoNode || null, pm2_version: pkg.version } that.link(infos, cb) }; CLI.prototype.link = function(infos, cb) { var that = this; process.env.WS_JSON_PATCH = true KMDaemon.launchAndInteract(cst, infos, function(err, dt) { if (err) { Common.printError(cst.PM2_IO_MSG + ' Run `$ pm2 plus` to connect') return that.exitCli(cst.ERROR_EXIT); } console.log(chalk.bold.green('[+] PM2+ activated!')) if (!cb) { return that.exitCli(cst.SUCCESS_EXIT); } return cb(null, dt) }); }; CLI.prototype.agentInfos = function(cb) { KMDaemon.getInteractInfo(this._conf, function(err, data) { if (err) return cb(Common.retErr(err)); return cb(null, data); }); }; CLI.prototype.killAgent = function(cb) { var that = this; KMDaemon.killInteractorDaemon(that._conf, function(err) { if (err) return cb ? cb(Common.retErr(err)) : that.exitCli(cst.SUCCESS_EXIT); return cb ? cb(null) : that.exitCli(cst.SUCCESS_EXIT); }); }; CLI.prototype.unlink = function(cb) { this.linkManagement('delete', cb); }; }; ================================================ FILE: lib/API/pm2-plus/pres/motd ================================================ ██████╗ ███╗ ███╗██████╗ ██╗ ██╗ ██████╗ ██╔══██╗████╗ ████║╚════██╗ ██║ ██╔╝██╔═══██╗ ██████╔╝██╔████╔██║ █████╔╝ ██║ ██╔╝ ██║ ██║ ██╔═══╝ ██║╚██╔╝██║██╔═══╝ ██║ ██╔╝ ██║ ██║ ██║ ██║ ╚═╝ ██║███████╗ ██║██╔╝ ╚██████╔╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝╚═╝ ╚═════╝ https://pm2.io/ Harden your Node.js Production Environment - Real-time Monitoring Web Interface - Pro Active Alerting System - Production Profiling for Memory and CPU - PM2 Runtime High Availability Fallback ================================================ FILE: lib/API/pm2-plus/pres/motd.update ================================================ ------------- ██████╗ ███╗ ███╗██████╗ ██╗ ██╗ ██████╗ ██╔══██╗████╗ ████║╚════██╗ ██║ ██╔╝██╔═══██╗ ██████╔╝██╔████╔██║ █████╔╝ ██║ ██╔╝ ██║ ██║ ██╔═══╝ ██║╚██╔╝██║██╔═══╝ ██║ ██╔╝ ██║ ██║ ██║ ██║ ╚═╝ ██║███████╗ ██║██╔╝ ╚██████╔╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝╚═╝ ╚═════╝ https://pm2.io/ Harden your Node.js Production Environment - Real-time Monitoring Web Interface - Pro Active Alerting System - Production Profiling for Memory and CPU - PM2 Runtime High Availability Fallback Start using it by typing: $ pm2 plus ------------- ================================================ FILE: lib/API/pm2-plus/pres/welcome ================================================ ------------- PM2 Plus Edition PM2 Plus is a monitoring dashboard specialized for Node.js Apps. Create an account: $ pm2 plus register Connect your local PM2 to the PM2 Plus servers: $ pm2 plus connect See our UI with your own realtime dashboard: $ pm2 plus web More details available there: http://pm2.io/plus ------------- Having complex or specific needs? You can also checkout our enterprise offer at: http://pm2.io/enterprise ------------- ================================================ FILE: lib/API/pm2-plus/process-selector.js ================================================ const fs = require('fs'); const forEachLimit = require('async/forEachLimit'); var cst = require('../../../constants.js'); var Common = require('../../Common.js'); module.exports = function(CLI) { /** * Monitor Selectively Processes (auto filter in interaction) * @param String state 'monitor' or 'unmonitor' * @param String target * @param Function cb callback */ CLI.prototype.monitorState = function(state, target, cb) { var that = this; if (!target) { Common.printError(cst.PREFIX_MSG_ERR + 'Please specify an '); return cb ? cb(new Error('argument missing')) : that.exitCli(cst.ERROR_EXIT); } function monitor (pm_id, cb) { // State can be monitor or unmonitor that.Client.executeRemote(state, pm_id, cb); } if (target === 'all') { that.Client.getAllProcessId(function (err, procs) { if (err) { Common.printError(err); return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT); } forEachLimit(procs, 1, monitor, function (err, res) { return typeof cb === 'function' ? cb(err, res) : that.speedList(); }); }); } else if (!Number.isInteger(parseInt(target))) { this.Client.getProcessIdByName(target, true, function (err, procs) { if (err) { Common.printError(err); return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT); } forEachLimit(procs, 1, monitor, function (err, res) { return typeof cb === 'function' ? cb(err, res) : that.speedList(); }); }); } else { monitor(parseInt(target), function (err, res) { return typeof cb === 'function' ? cb(err, res) : that.speedList(); }); } }; } ================================================ FILE: lib/API/schema.json ================================================ { "script": { "type": "string", "require": true, "alias" : "exec", "docDescription": "Path of the script to launch, required field" }, "name": { "type": "string", "docDefault": "Script filename without the extension (app for app.js)", "docDescription": "Process name in the process list" }, "name_prefix": { "type": "string" }, "filter_env": { "type": [ "boolean", "array", "string" ], "docDefault": false, "docDescription": "Enable filtering global environments" }, "namespace": { "type": "string", "docDefault": "default", "docDescription": "Process namespace" }, "install_url": { "type": "string" }, "cwd": { "type": "string", "docDefault": "CWD of the current environment (from your shell)", "docDescription": "Current working directory to start the process with" }, "args": { "type": [ "array", "string" ], "docDescription": "Arguments to pass to the script" }, "exec_interpreter": { "type": "string", "alias": "interpreter", "docDefault": "node", "docDescription": "Interpreter absolute path" }, "node_args": { "type": [ "array", "string" ], "alias": ["interpreterArgs", "interpreter_args"], "docDescription": "Arguments to pass to the interpreter" }, "out_file": { "type": "string", "alias": ["out", "output", "out_log"], "docDefault": "~/.pm2/logs/-out.log", "docDescription": "File path for stdout (each line is appended to this file)" }, "error_file": { "type": "string", "alias": ["error", "err", "err_file", "err_log"], "docDefault": "~/.pm2/logs/-error.err", "docDescription": "File path for stderr (each line is appended to this file)" }, "log_file": { "type": [ "boolean", "string" ], "alias": "log", "docDefault": "/dev/null", "docDescription": "File path for combined stdout and stderr (each line is appended to this file)" }, "disable_logs": { "type": "boolean", "docDefault": false, "docDescription": "Disable all logs storage" }, "log_type": { "type": "string", "docDescription": "Define a specific log output type, possible value: json" }, "log_date_format": { "type": "string", "docDescription": "Format for log timestamps in day.js format (eg YYYY-MM-DD HH:mm Z)" }, "time": { "type": "boolean" }, "env": { "type": [ "object", "string" ], "docDescription": "Specify environment variables to be injected" }, "^env_\\S*$": { "type": [ "object", "string" ], "docDescription": "Specify environment variables to be injected when using --env " }, "max_memory_restart": { "type": [ "string", "number" ], "regex": "^\\d+(G|M|K)?$", "ext_type": "sbyte", "desc": "it should be a NUMBER - byte, \"[NUMBER]G\"(Gigabyte), \"[NUMBER]M\"(Megabyte) or \"[NUMBER]K\"(Kilobyte)", "docDescription": "Restart the app if an amount of memory is exceeded (format: /[0-9](K|M|G)?/ K for KB, 'M' for MB, 'G' for GB, default to B)" }, "pid_file": { "type": "string", "alias": "pid", "docDefault": "~/.pm2/pids/app_name-id.pid", "docDescription": "File path where the pid of the started process is written by pm2" }, "restart_delay": { "type" : "number", "docDefault": 0, "docDescription": "Time in ms to wait before restarting a crashing app" }, "exp_backoff_restart_delay": { "type": "number", "docDefault": 0, "docDescription": "Restart Time in ms to wait before restarting a crashing app" }, "source_map_support": { "type": "boolean", "docDefault": true, "docDescription": "Enable or disable the source map support" }, "disable_source_map_support": { "type": "boolean", "docDefault": false, "docDescription": "Enable or disable the source map support" }, "wait_ready": { "type": "boolean", "docDefault": false, "docDescription": "Make the process wait for a process.send('ready')" }, "instances": { "type": "number", "docDefault": 1, "docDescription": "Number of instances to be started in cluster mode" }, "kill_timeout": { "type": "number", "docDefault": 1600, "docDescription": "Time in ms before sending the final SIGKILL signal after SIGINT" }, "shutdown_with_message": { "type": "boolean", "docDefault": false, "docDescription": "Shutdown an application with process.send('shutdown') instead of process.kill(pid, SIGINT)" }, "listen_timeout": { "type": "number", "docDescription": "Time in ms before forcing a reload if app is still not listening/has still note sent ready" }, "cron_restart": { "type": [ "string", "number" ], "alias": "cron", "docDescription": "A cron pattern to restart your app" }, "merge_logs": { "type": "boolean", "alias" : "combine_logs", "docDefault": false, "docDescription": "In cluster mode, merge each type of logs into a single file (instead of having one for each cluster)" }, "vizion": { "type": "boolean", "default" : true, "docDefault" : "True", "docDescription": "Enable or disable the versioning metadatas (vizion library)" }, "autostart": { "type": "boolean", "default": true, "docDefault": "True", "docDescription": "Enable or disable auto start when adding process" }, "autorestart": { "type": "boolean", "default": true, "docDefault": "True", "docDescription": "Enable or disable auto restart after process failure" }, "stop_exit_codes": { "type": [ "array", "number" ], "docDescription": "List of exit codes that should allow the process to stop (skip autorestart)." }, "watch_delay": { "type": "number", "docDefault": "True", "docDescription": "Restart delay on file change detected" }, "watch": { "type": [ "boolean", "array", "string" ], "docDefault": false, "docDescription": "Enable or disable the watch mode" }, "ignore_watch": { "type": [ "array", "string" ], "docDescription": "List of paths to ignore (regex)" }, "watch_options": { "type": "object", "docDescription": "Object that will be used as an options with chokidar (refer to chokidar documentation)" }, "min_uptime": { "type": [ "number", "string" ], "regex": "^\\d+(h|m|s)?$", "desc": "it should be a NUMBER - milliseconds, \"[NUMBER]h\"(hours), \"[NUMBER]m\"(minutes) or \"[NUMBER]s\"(seconds)", "min": 100, "ext_type": "stime", "docDefault": 1000, "docDescription": "Minimum uptime of the app to be considered started (format is /[0-9]+(h|m|s)?/, for hours, minutes, seconds, docDefault to ms)" }, "max_restarts": { "type": "number", "min": 0, "docDefault": 16, "docDescription": "Number of times a script is restarted when it exits in less than min_uptime" }, "execute_command": { "type": "boolean" }, "exec_mode": { "type": "string", "regex": "^(cluster|fork)(_mode)?$", "desc": "it should be \"cluster\"(\"cluster_mode\") or \"fork\"(\"fork_mode\") only", "docDefault": "fork", "docDescription": "Set the execution mode, possible values: fork|cluster" }, "force": { "type": "boolean", "docDefault": false, "docDescription": "Start a script even if it is already running (only the script path is considered)" }, "append_env_to_name": { "type": "boolean", "docDefault": false, "docDescription": "Append the environment name to the app name" }, "post_update": { "type": "array", "docDescription": "List of commands executed after a pull/upgrade operation performed from Keymetrics dashboard" }, "trace": { "type": [ "boolean" ], "docDefault": false, "docDescription": "Enable or disable the transaction tracing" }, "disable_trace": { "type": [ "boolean" ], "docDefault": true, "docDescription": "Enable or disable the transaction tracing" }, "v8": { "type": [ "boolean" ] }, "event_loop_inspector": { "type": [ "boolean" ] }, "deep_monitoring": { "type": [ "boolean" ] }, "increment_var": { "type": "string", "docDescription": "Specify the name of an environment variable to inject which increments for each cluster" }, "instance_var": { "type": "string", "default": "NODE_APP_INSTANCE", "docDefault": "NODE_APP_INSTANCE", "docDescription": "Rename the NODE_APP_INSTANCE environment variable" }, "pmx": { "type": ["boolean", "string"], "default": true, "docDefault": "True", "docDescription": "Enable or disable pmx wrapping" }, "automation": { "type": "boolean", "default": true, "docDefault": "True", "docDescription": "Enable or disable pmx wrapping" }, "treekill": { "type": "boolean", "default": true, "docDefault": "True", "docDescription": "Only kill the main process, not detached children" }, "port": { "type": "number", "docDescription": "Shortcut to inject a PORT environment variable" }, "username" : { "type": "string", "docDescription": "Current user that started the process" }, "uid": { "type" : [ "number", "string" ], "alias": "user", "docDefault": "Current user uid", "docDescription": "Set user id" }, "gid": { "type" : [ "number", "string" ], "docDefault": "Current user gid", "docDescription": "Set group id" }, "windowsHide": { "type": "boolean", "docDefault": "True", "docDescription": "Enable or disable the Windows popup when starting an app", "default": true }, "kill_retry_time": { "type": "number", "default" : 100 }, "write": { "type": "boolean" }, "io": { "type": "object", "docDescription": "Specify apm values and configuration" } } ================================================ FILE: lib/API.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ 'use strict'; const commander = require('commander'); const fs = require('fs'); const path = require('path'); const eachLimit = require('async/eachLimit'); const series = require('async/series'); const debug = require('debug')('pm2:cli'); const util = require('util'); const chalk = require('ansis'); const fclone = require('fclone'); var DockerMgmt = require('./API/ExtraMgmt/Docker.js') var conf = require('../constants.js'); var Client = require('./Client'); var Common = require('./Common'); var KMDaemon = require('@pm2/agent/src/InteractorClient'); var Config = require('./tools/Config'); var Modularizer = require('./API/Modules/Modularizer.js'); var path_structure = require('../paths.js'); var UX = require('./API/UX'); var pkg = require('../package.json'); var hf = require('./API/Modules/flagExt.js'); var Configuration = require('./Configuration.js'); const sexec = require('./tools/sexec.js') var IMMUTABLE_MSG = chalk.bold.blue('Use --update-env to update environment variables'); /** * Main Function to be imported * can be aliased to PM2 * * To use it when PM2 is installed as a module: * * var PM2 = require('pm2'); * * var pm2 = PM2(); * * * @param {Object} opts * @param {String} [opts.cwd=] override pm2 cwd for starting scripts * @param {String} [opts.pm2_home=[]] pm2 directory for log, pids, socket files * @param {Boolean} [opts.independent=false] unique PM2 instance (random pm2_home) * @param {Boolean} [opts.daemon_mode=true] should be called in the same process or not * @param {String} [opts.public_key=null] pm2 plus bucket public key * @param {String} [opts.secret_key=null] pm2 plus bucket secret key * @param {String} [opts.machine_name=null] pm2 plus instance name */ class API { constructor (opts) { if (!opts) opts = {}; var that = this; this.daemon_mode = typeof(opts.daemon_mode) == 'undefined' ? true : opts.daemon_mode; this.pm2_home = conf.PM2_ROOT_PATH; this.public_key = conf.PUBLIC_KEY || opts.public_key || null; this.secret_key = conf.SECRET_KEY || opts.secret_key || null; this.machine_name = conf.MACHINE_NAME || opts.machine_name || null /** * CWD resolution */ this.cwd = process.cwd(); if (opts.cwd) { this.cwd = path.resolve(opts.cwd); } /** * PM2 HOME resolution */ if (opts.pm2_home && opts.independent == true) throw new Error('You cannot set a pm2_home and independent instance in same time'); if (opts.pm2_home) { // Override default conf file this.pm2_home = opts.pm2_home; conf = Object.assign(conf, path_structure(this.pm2_home)); } else if (opts.independent == true && conf.IS_WINDOWS === false) { // Create an unique pm2 instance const crypto = require('crypto'); var random_file = crypto.randomBytes(8).toString('hex'); this.pm2_home = path.join('/tmp', random_file); // If we dont explicitly tell to have a daemon // It will go as in proc if (typeof(opts.daemon_mode) == 'undefined') this.daemon_mode = false; conf = Object.assign(conf, path_structure(this.pm2_home)); } this._conf = conf; if (conf.IS_WINDOWS) { // Weird fix, may need to be dropped // @todo windows connoisseur double check if (process.stdout._handle && process.stdout._handle.setBlocking) process.stdout._handle.setBlocking(true); } this.Client = new Client({ pm2_home: that.pm2_home, conf: this._conf, secret_key: this.secret_key, public_key: this.public_key, daemon_mode: this.daemon_mode, machine_name: this.machine_name }); this.pm2_configuration = Configuration.getSync('pm2') || {} this.gl_interact_infos = null; this.gl_is_km_linked = false; try { var pid = fs.readFileSync(conf.INTERACTOR_PID_PATH); pid = parseInt(pid.toString().trim()); process.kill(pid, 0); that.gl_is_km_linked = true; } catch (e) { that.gl_is_km_linked = false; } // For testing purposes if (this.secret_key && process.env.NODE_ENV == 'local_test') that.gl_is_km_linked = true; KMDaemon.ping(this._conf, function(err, result) { if (!err && result === true) { fs.readFile(conf.INTERACTION_CONF, (err, _conf) => { if (!err) { try { that.gl_interact_infos = JSON.parse(_conf.toString()) } catch(e) { var json5 = require('./tools/json5.js') try { that.gl_interact_infos = json5.parse(_conf.toString()) } catch(e) { console.error(e) that.gl_interact_infos = null } } } }) } }) this.gl_retry = 0; } /** * Connect to PM2 * Calling this command is now optional * * @param {Function} cb callback once pm2 is ready for commands */ connect (noDaemon, cb) { var that = this; this.start_timer = new Date(); if (typeof(cb) == 'undefined') { cb = noDaemon; noDaemon = false; } else if (noDaemon === true) { // Backward compatibility with PM2 1.x this.Client.daemon_mode = false; this.daemon_mode = false; } this.Client.start(function(err, meta) { if (err) return cb(err); if (meta.new_pm2_instance == false && that.daemon_mode === true) return cb(err, meta); that.launchSysMonitoring(() => {}) // If new pm2 instance has been popped // Launch all modules that.launchAll(that, function(err_mod) { return cb(err, meta); }); }); } /** * Usefull when custom PM2 created with independent flag set to true * This will cleanup the newly created instance * by removing folder, killing PM2 and so on * * @param {Function} cb callback once cleanup is successfull */ destroy (cb) { var that = this; debug('Killing and deleting current deamon'); this.killDaemon(function() { var cmd = 'rm -rf ' + that.pm2_home; var test_path = path.join(that.pm2_home, 'module_conf.json'); var test_path_2 = path.join(that.pm2_home, 'pm2.pid'); if (that.pm2_home.indexOf('.pm2') > -1) return cb(new Error('Destroy is not a allowed method on .pm2')); fs.access(test_path, fs.constants.R_OK, function(err) { if (err) return cb(err); debug('Deleting temporary folder %s', that.pm2_home); sexec(cmd, cb); }); }); } /** * Disconnect from PM2 instance * This will allow your software to exit by itself * * @param {Function} [cb] optional callback once connection closed */ disconnect (cb) { var that = this; if (!cb) cb = function() {}; this.Client.close(function(err, data) { debug('The session lasted %ds', (new Date() - that.start_timer) / 1000); return cb(err, data); }); }; /** * Alias on disconnect * @param cb */ close (cb) { this.disconnect(cb); } /** * Launch modules * * @param {Function} cb callback once pm2 has launched modules */ launchModules (cb) { this.launchAll(this, cb); } /** * Enable bus allowing to retrieve various process event * like logs, restarts, reloads * * @param {Function} cb callback called with 1st param err and 2nb param the bus */ launchBus (cb) { this.Client.launchBus(cb); } /** * Exit methods for API * @param {Integer} code exit code for terminal */ exitCli (code) { var that = this; // Do nothing if PM2 called programmatically (also in speedlist) if (conf.PM2_PROGRAMMATIC && process.env.PM2_USAGE != 'CLI') return false; KMDaemon.disconnectRPC(function() { that.Client.close(function() { code = code || 0; // Safe exits process after all streams are drained. // file descriptor flag. var fds = 0; // exits process when stdout (1) and sdterr(2) are both drained. function tryToExit() { if ((fds & 1) && (fds & 2)) { debug('This command took %ds to execute', (new Date() - that.start_timer) / 1000); process.exit(code); } } [process.stdout, process.stderr].forEach(function(std) { var fd = std.fd; if (!std.bufferSize) { // bufferSize equals 0 means current stream is drained. fds = fds | fd; } else { // Appends nothing to the std queue, but will trigger `tryToExit` event on `drain`. std.write && std.write('', function() { fds = fds | fd; tryToExit(); }); } // Does not write anything more. delete std.write; }); tryToExit(); }); }); } //////////////////////////// // Application management // //////////////////////////// /** * Start a file or json with configuration * @param {Object||String} cmd script to start or json * @param {Function} cb called when application has been started */ start (cmd, opts, cb) { if (typeof(opts) == "function") { cb = opts; opts = {}; } if (!opts) opts = {}; var that = this; if (Array.isArray(opts.watch) && opts.watch.length === 0) opts.watch = (opts.rawArgs ? !!~opts.rawArgs.indexOf('--watch') : !!~process.argv.indexOf('--watch')) || false; if (Common.isConfigFile(cmd) || (typeof(cmd) === 'object')) { that._startJson(cmd, opts, 'restartProcessId', (err, procs) => { return cb ? cb(err, procs) : this.speedList() }) } else { that._startScript(cmd, opts, (err, procs) => { return cb ? cb(err, procs) : this.speedList(0) }) } } /** * Reset process counters * * @method resetMetaProcess */ reset (process_name, cb) { var that = this; function processIds(ids, cb) { eachLimit(ids, conf.CONCURRENT_ACTIONS, function(id, next) { that.Client.executeRemote('resetMetaProcessId', id, function(err, res) { if (err) console.error(err); Common.printOut(conf.PREFIX_MSG + 'Resetting meta for process id %d', id); return next(); }); }, function(err) { if (err) return cb(Common.retErr(err)); return cb ? cb(null, {success:true}) : that.speedList(); }); } if (process_name == 'all') { that.Client.getAllProcessId(function(err, ids) { if (err) { Common.printError(err); return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT); } return processIds(ids, cb); }); } else if (isNaN(process_name)) { that.Client.getProcessIdByName(process_name, function(err, ids) { if (err) { Common.printError(err); return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT); } if (ids.length === 0) { Common.printError('Unknown process name'); return cb ? cb(new Error('Unknown process name')) : that.exitCli(conf.ERROR_EXIT); } return processIds(ids, cb); }); } else { processIds([process_name], cb); } } /** * Update daemonized PM2 Daemon * * @param {Function} cb callback when pm2 has been upgraded */ update (cb) { var that = this; Common.printOut('Be sure to have the latest version by doing `npm install pm2@latest -g` before doing this procedure.'); // Dump PM2 processes that.Client.executeRemote('notifyKillPM2', {}, function() {}); that.getVersion(function(err, new_version) { // If not linked to PM2 plus, and update PM2 to latest, display motd.update if (!that.gl_is_km_linked && !err && (pkg.version != new_version)) { var dt = fs.readFileSync(path.join(__dirname, that._conf.PM2_UPDATE)); console.log(dt.toString()); } that.dump(function(err) { that.killDaemon(function() { that.Client.launchDaemon({interactor:false}, function(err, child) { that.Client.launchRPC(function() { that.resurrect(function() { Common.printOut(chalk.blue.bold('>>>>>>>>>> PM2 updated')); that.launchSysMonitoring(() => {}) that.launchAll(that, function() { KMDaemon.launchAndInteract(that._conf, { pm2_version: pkg.version }, function(err, data, interactor_proc) { }) setTimeout(() => { return cb ? cb(null, {success:true}) : that.speedList(); }, 250) }); }); }); }); }); }); }); return false; } /** * Reload an application * * @param {String} process_name Application Name or All * @param {Object} opts Options * @param {Function} cb Callback */ reload (process_name, opts, cb) { var that = this; if (typeof(opts) == "function") { cb = opts; opts = {}; } var delay = Common.lockReload(); if (delay > 0 && opts.force != true) { Common.printError(conf.PREFIX_MSG_ERR + 'Reload already in progress, please try again in ' + Math.floor((conf.RELOAD_LOCK_TIMEOUT - delay) / 1000) + ' seconds or use --force'); return cb ? cb(new Error('Reload in progress')) : that.exitCli(conf.ERROR_EXIT); } if (Common.isConfigFile(process_name)) that._startJson(process_name, opts, 'reloadProcessId', function(err, apps) { Common.unlockReload(); if (err) return cb ? cb(err) : that.exitCli(conf.ERROR_EXIT); return cb ? cb(null, apps) : that.exitCli(conf.SUCCESS_EXIT); }); else { if (opts && opts.env) { var err = 'Using --env [env] without passing the ecosystem.config.js does not work' Common.err(err); Common.unlockReload(); return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT); } if (opts && !opts.updateEnv) Common.printOut(IMMUTABLE_MSG); that._operate('reloadProcessId', process_name, opts, function(err, apps) { Common.unlockReload(); if (err) return cb ? cb(err) : that.exitCli(conf.ERROR_EXIT); return cb ? cb(null, apps) : that.exitCli(conf.SUCCESS_EXIT); }); } } /** * Restart process * * @param {String} cmd Application Name / Process id / JSON application file / 'all' * @param {Object} opts Extra options to be updated * @param {Function} cb Callback */ restart (cmd, opts, cb) { if (typeof(opts) == "function") { cb = opts; opts = {}; } var that = this; if (typeof(cmd) === 'number') cmd = cmd.toString(); if (cmd == "-") { // Restart from PIPED JSON process.stdin.resume(); process.stdin.setEncoding('utf8'); process.stdin.on('data', function (param) { process.stdin.pause(); that.actionFromJson('restartProcessId', param, opts, 'pipe', cb); }); } else if (Common.isConfigFile(cmd) || typeof(cmd) === 'object') that._startJson(cmd, opts, 'restartProcessId', cb); else { if (opts && opts.env) { var err = 'Using --env [env] without passing the ecosystem.config.js does not work' Common.err(err); return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT); } if (opts && !opts.updateEnv) Common.printOut(IMMUTABLE_MSG); that._operate('restartProcessId', cmd, opts, cb); } } /** * Delete process * * @param {String} process_name Application Name / Process id / Application file / 'all' * @param {Function} cb Callback */ delete (process_name, jsonVia, cb) { var that = this; if (typeof(jsonVia) === "function") { cb = jsonVia; jsonVia = null; } if (typeof(process_name) === "number") { process_name = process_name.toString(); } if (jsonVia == 'pipe') return that.actionFromJson('deleteProcessId', process_name, commander, 'pipe', (err, procs) => { return cb ? cb(err, procs) : this.speedList() }); if (Common.isConfigFile(process_name)) return that.actionFromJson('deleteProcessId', process_name, commander, 'file', (err, procs) => { return cb ? cb(err, procs) : this.speedList() }); else { that._operate('deleteProcessId', process_name, (err, procs) => { return cb ? cb(err, procs) : this.speedList() }); } } /** * Stop process * * @param {String} process_name Application Name / Process id / Application file / 'all' * @param {Function} cb Callback */ stop (process_name, cb) { var that = this; if (typeof(process_name) === 'number') process_name = process_name.toString(); if (process_name == "-") { process.stdin.resume(); process.stdin.setEncoding('utf8'); process.stdin.on('data', function (param) { process.stdin.pause(); that.actionFromJson('stopProcessId', param, commander, 'pipe', (err, procs) => { return cb ? cb(err, procs) : this.speedList() }) }); } else if (Common.isConfigFile(process_name)) that.actionFromJson('stopProcessId', process_name, commander, 'file', (err, procs) => { return cb ? cb(err, procs) : this.speedList() }); else that._operate('stopProcessId', process_name, (err, procs) => { return cb ? cb(err, procs) : this.speedList() }); } /** * Get list of all processes managed * * @param {Function} cb Callback */ list (opts, cb) { var that = this; if (typeof(opts) == 'function') { cb = opts; opts = null; } that.Client.executeRemote('getMonitorData', {}, function(err, list) { if (err) { Common.printError(err); return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT); } if (opts && opts.rawArgs && opts.rawArgs.indexOf('--watch') > -1) { var dayjs = require('dayjs'); function show() { process.stdout.write('\x1b[2J'); process.stdout.write('\x1b[0f'); console.log('Last refresh: ', dayjs().format()); that.Client.executeRemote('getMonitorData', {}, function(err, list) { UX.list(list, null); }); } show(); setInterval(show, 900); return false; } return cb ? cb(null, list) : that.speedList(null); }); } /** * Kill Daemon * * @param {Function} cb Callback */ killDaemon (cb) { process.env.PM2_STATUS = 'stopping' var that = this; that.Client.executeRemote('notifyKillPM2', {}, function() {}); that._operate('deleteProcessId', 'all', function(err, list) { Common.printOut(conf.PREFIX_MSG + '[v] All Applications Stopped'); process.env.PM2_SILENT = 'false'; that.killAgent(function(err, data) { if (!err) { Common.printOut(conf.PREFIX_MSG + '[v] Agent Stopped'); } that.Client.killDaemon(function(err, res) { if (err) Common.printError(err); Common.printOut(conf.PREFIX_MSG + '[v] PM2 Daemon Stopped'); return cb ? cb(err, res) : that.exitCli(conf.SUCCESS_EXIT); }); }); }) } kill (cb) { this.killDaemon(cb); } ///////////////////// // Private methods // ///////////////////// /** * Method to START / RESTART a script * * @private * @param {string} script script name (will be resolved according to location) */ _startScript (script, opts, cb) { if (typeof opts == "function") { cb = opts; opts = {}; } var that = this; /** * Commander.js tricks */ var app_conf = Config.filterOptions(opts); var appConf = {}; if (typeof app_conf.name == 'function') delete app_conf.name; delete app_conf.args; // Retrieve arguments via -- var argsIndex; if (opts.rawArgs && (argsIndex = opts.rawArgs.indexOf('--')) >= 0) app_conf.args = opts.rawArgs.slice(argsIndex + 1); else if (opts.scriptArgs) app_conf.args = opts.scriptArgs; app_conf.script = script; if(!app_conf.namespace) app_conf.namespace = 'default'; if ((appConf = Common.verifyConfs(app_conf)) instanceof Error) { Common.err(appConf) return cb ? cb(Common.retErr(appConf)) : that.exitCli(conf.ERROR_EXIT); } app_conf = appConf[0]; if (opts.watchDelay) { if (typeof opts.watchDelay === "string" && opts.watchDelay.indexOf("ms") !== -1) app_conf.watch_delay = parseInt(opts.watchDelay); else { app_conf.watch_delay = parseFloat(opts.watchDelay) * 1000; } } var mas = []; if(typeof opts.ext != 'undefined') hf.make_available_extension(opts, mas); // for -e flag mas.length > 0 ? app_conf.ignore_watch = mas : 0; /** * If -w option, write configuration to configuration.json file */ if (app_conf.write) { var dst_path = path.join(process.env.PWD || process.cwd(), app_conf.name + '-pm2.json'); Common.printOut(conf.PREFIX_MSG + 'Writing configuration to', chalk.blue(dst_path)); // pretty JSON try { fs.writeFileSync(dst_path, JSON.stringify(app_conf, null, 2)); } catch (e) { console.error(e.stack || e); } } series([ restartExistingProcessName, restartExistingNameSpace, restartExistingProcessId, restartExistingProcessPathOrStartNew ], function(err, data) { if (err instanceof Error) return cb ? cb(err) : that.exitCli(conf.ERROR_EXIT); var ret = {}; data.forEach(function(_dt) { if (_dt !== undefined) ret = _dt; }); return cb ? cb(null, ret) : that.speedList(); }); /** * If start start/restart application */ function restartExistingProcessName(cb) { if (!isNaN(script) || (typeof script === 'string' && script.indexOf('/') != -1) || (typeof script === 'string' && path.extname(script) !== '')) return cb(null); that.Client.getProcessIdByName(script, function(err, ids) { if (err && cb) return cb(err); if (ids.length > 0) { that._operate('restartProcessId', script, opts, function(err, list) { if (err) return cb(err); Common.printOut(conf.PREFIX_MSG + 'Process successfully started'); return cb(true, list); }); } else return cb(null); }); } /** * If start start/restart namespace */ function restartExistingNameSpace(cb) { if (!isNaN(script) || (typeof script === 'string' && script.indexOf('/') != -1) || (typeof script === 'string' && path.extname(script) !== '')) return cb(null); if (script !== 'all') { that.Client.getProcessIdsByNamespace(script, function (err, ids) { if (err && cb) return cb(err); if (ids.length > 0) { that._operate('restartProcessId', script, opts, function (err, list) { if (err) return cb(err); Common.printOut(conf.PREFIX_MSG + 'Process successfully started'); return cb(true, list); }); } else return cb(null); }); } else { that._operate('restartProcessId', 'all', function(err, list) { if (err) return cb(err); Common.printOut(conf.PREFIX_MSG + 'Process successfully started'); return cb(true, list); }); } } function restartExistingProcessId(cb) { if (isNaN(script)) return cb(null); that._operate('restartProcessId', script, opts, function(err, list) { if (err) return cb(err); Common.printOut(conf.PREFIX_MSG + 'Process successfully started'); return cb(true, list); }); } /** * Restart a process with the same full path * Or start it */ function restartExistingProcessPathOrStartNew(cb) { that.Client.executeRemote('getMonitorData', {}, function(err, procs) { if (err) return cb ? cb(new Error(err)) : that.exitCli(conf.ERROR_EXIT); var full_path = path.resolve(that.cwd, script); var managed_script = null; procs.forEach(function(proc) { if (proc.pm2_env.pm_exec_path == full_path && proc.pm2_env.name == app_conf.name) managed_script = proc; }); if (managed_script && (managed_script.pm2_env.status == conf.STOPPED_STATUS || managed_script.pm2_env.status == conf.STOPPING_STATUS || managed_script.pm2_env.status == conf.ERRORED_STATUS)) { // Restart process if stopped var app_name = managed_script.pm2_env.name; that._operate('restartProcessId', app_name, opts, function(err, list) { if (err) return cb ? cb(new Error(err)) : that.exitCli(conf.ERROR_EXIT); Common.printOut(conf.PREFIX_MSG + 'Process successfully started'); return cb(true, list); }); return false; } else if (managed_script && !opts.force) { Common.err('Script already launched, add -f option to force re-execution'); return cb(new Error('Script already launched')); } var resolved_paths = null; try { resolved_paths = Common.resolveAppAttributes({ cwd : that.cwd, pm2_home : that.pm2_home }, app_conf); } catch(e) { Common.err(e.message); return cb(Common.retErr(e)); } Common.printOut(conf.PREFIX_MSG + 'Starting %s in %s (%d instance' + (resolved_paths.instances > 1 ? 's' : '') + ')', resolved_paths.pm_exec_path, resolved_paths.exec_mode, resolved_paths.instances); if (!resolved_paths.env) resolved_paths.env = {}; // Set PM2 HOME in case of child process using PM2 API resolved_paths.env['PM2_HOME'] = that.pm2_home; var additional_env = Modularizer.getAdditionalConf(resolved_paths.name); Object.assign(resolved_paths.env, additional_env); // Is KM linked? resolved_paths.km_link = that.gl_is_km_linked; that.Client.executeRemote('prepare', resolved_paths, function(err, data) { if (err) { Common.printError(conf.PREFIX_MSG_ERR + 'Error while launching application', err.stack || err); return cb(Common.retErr(err)); } Common.printOut(conf.PREFIX_MSG + 'Done.'); return cb(true, data); }); return false; }); } } /** * Method to start/restart/reload processes from a JSON file * It will start app not started * Can receive only option to skip applications * * @private */ _startJson (file, opts, action, pipe, cb) { var config = {}; var appConf = {}; var staticConf = []; var deployConf = {}; var apps_info = []; var that = this; /** * Get File configuration */ if (typeof(cb) === 'undefined' && typeof(pipe) === 'function') { cb = pipe; } if (typeof(file) === 'object') { config = file; } else if (pipe === 'pipe') { config = Common.parseConfig(file, 'pipe'); } else { var data = null; var isAbsolute = path.isAbsolute(file) var file_path = isAbsolute ? file : path.join(that.cwd, file); debug('Resolved filepath %s', file_path); try { data = fs.readFileSync(file_path); } catch(e) { Common.printError(conf.PREFIX_MSG_ERR + 'File ' + file +' not found'); return cb ? cb(Common.retErr(e)) : that.exitCli(conf.ERROR_EXIT); } try { config = Common.parseConfig(data, file); } catch(e) { Common.printError(conf.PREFIX_MSG_ERR + 'File ' + file + ' malformated'); console.error(e); return cb ? cb(Common.retErr(e)) : that.exitCli(conf.ERROR_EXIT); } } /** * Alias some optional fields */ if (config.deploy) deployConf = config.deploy; if (config.static) staticConf = config.static; if (config.apps) appConf = config.apps; else if (config.pm2) appConf = config.pm2; else appConf = config; if (!Array.isArray(appConf)) appConf = [appConf]; if ((appConf = Common.verifyConfs(appConf)) instanceof Error) return cb ? cb(appConf) : that.exitCli(conf.ERROR_EXIT); process.env.PM2_JSON_PROCESSING = true; // Get App list var apps_name = []; var proc_list = {}; // Add statics to apps staticConf.forEach(function(serve) { appConf.push({ name: serve.name ? serve.name : `static-page-server-${serve.port}`, script: path.resolve(__dirname, 'API', 'Serve.js'), env: { PM2_SERVE_PORT: serve.port, PM2_SERVE_HOST: serve.host, PM2_SERVE_PATH: serve.path, PM2_SERVE_SPA: serve.spa, PM2_SERVE_DIRECTORY: serve.directory, PM2_SERVE_BASIC_AUTH: serve.basic_auth !== undefined, PM2_SERVE_BASIC_AUTH_USERNAME: serve.basic_auth ? serve.basic_auth.username : null, PM2_SERVE_BASIC_AUTH_PASSWORD: serve.basic_auth ? serve.basic_auth.password : null, PM2_SERVE_MONITOR: serve.monitor } }); }); // Here we pick only the field we want from the CLI when starting a JSON appConf.forEach(function(app) { if (!app.env) { app.env = {}; } app.env.io = app.io; // --only if (opts.only) { var apps = opts.only.split(/,| /) if (apps.indexOf(app.name) == -1) return false } // Namespace if (!app.namespace) { if (opts.namespace) app.namespace = opts.namespace; else app.namespace = 'default'; } // --watch if (!app.watch && opts.watch && opts.watch === true) app.watch = true; // --ignore-watch if (!app.ignore_watch && opts.ignore_watch) app.ignore_watch = opts.ignore_watch; if (opts.install_url) app.install_url = opts.install_url; // --instances if (opts.instances && typeof(opts.instances) === 'number') app.instances = opts.instances; // --uid if (opts.uid) app.uid = opts.uid; // --gid if (opts.gid) app.gid = opts.gid; // Specific if (app.append_env_to_name && opts.env) app.name += ('-' + opts.env); if (opts.name_prefix && app.name.indexOf(opts.name_prefix) == -1) app.name = `${opts.name_prefix}:${app.name}` app.username = Common.getCurrentUsername(); apps_name.push(app.name); }); that.Client.executeRemote('getMonitorData', {}, function(err, raw_proc_list) { if (err) { Common.printError(err); return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT); } /** * Uniquify in memory process list */ raw_proc_list.forEach(function(proc) { proc_list[proc.name] = proc; }); /** * Auto detect application already started * and act on them depending on action */ eachLimit(Object.keys(proc_list), conf.CONCURRENT_ACTIONS, function(proc_name, next) { // Skip app name (--only option) if (apps_name.indexOf(proc_name) == -1) return next(); if (!(action == 'reloadProcessId' || action == 'softReloadProcessId' || action == 'restartProcessId')) throw new Error('Wrong action called'); var apps = appConf.filter(function(app) { return app.name == proc_name; }); var envs = apps.map(function(app){ // Binds env_diff to env and returns it. return Common.mergeEnvironmentVariables(app, opts.env, deployConf); }); // Assigns own enumerable properties of all // Notice: if people use the same name in different apps, // duplicated envs will be overrode by the last one var env = envs.reduce(function(e1, e2){ return Object.assign(e1, e2); }); // When we are processing JSON, allow to keep the new env by default env.updateEnv = true; // Pass `env` option that._operate(action, proc_name, env, function(err, ret) { if (err) Common.printError(err); // For return apps_info = apps_info.concat(ret); that.Client.notifyGod(action, proc_name); // And Remove from array to spy apps_name.splice(apps_name.indexOf(proc_name), 1); return next(); }); }, function(err) { if (err) return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT); if (apps_name.length > 0 && action != 'start') Common.printOut(conf.PREFIX_MSG_WARNING + 'Applications %s not running, starting...', apps_name.join(', ')); // Start missing apps return startApps(apps_name, function(err, apps) { apps_info = apps_info.concat(apps); return cb ? cb(err, apps_info) : that.speedList(err ? 1 : 0); }); }); return false; }); function startApps(app_name_to_start, cb) { var apps_to_start = []; var apps_started = []; var apps_errored = []; appConf.forEach(function(app, i) { if (app_name_to_start.indexOf(app.name) != -1) { apps_to_start.push(appConf[i]); } }); eachLimit(apps_to_start, conf.CONCURRENT_ACTIONS, function(app, next) { if (opts.cwd) app.cwd = opts.cwd; if (opts.force_name) app.name = opts.force_name; if (opts.started_as_module) app.pmx_module = true; var resolved_paths = null; // hardcode script name to use `serve` feature inside a process file if (app.script === 'serve') { app.script = path.resolve(__dirname, 'API', 'Serve.js') } try { resolved_paths = Common.resolveAppAttributes({ cwd : that.cwd, pm2_home : that.pm2_home }, app); } catch (e) { apps_errored.push(e) Common.err(`Error: ${e.message}`) return next(); } if (!resolved_paths.env) resolved_paths.env = {}; // Set PM2 HOME in case of child process using PM2 API resolved_paths.env['PM2_HOME'] = that.pm2_home; var additional_env = Modularizer.getAdditionalConf(resolved_paths.name); Object.assign(resolved_paths.env, additional_env); resolved_paths.env = Common.mergeEnvironmentVariables(resolved_paths, opts.env, deployConf); delete resolved_paths.env.current_conf; // Is KM linked? resolved_paths.km_link = that.gl_is_km_linked; if (resolved_paths.wait_ready) { Common.warn(`App ${resolved_paths.name} has option 'wait_ready' set, waiting for app to be ready...`) } that.Client.executeRemote('prepare', resolved_paths, function(err, data) { if (err) { Common.printError(conf.PREFIX_MSG_ERR + 'Process failed to launch %s', err.message ? err.message : err); return next(); } if (data.length === 0) { Common.printError(conf.PREFIX_MSG_ERR + 'Process config loading failed', data); return next(); } Common.printOut(conf.PREFIX_MSG + 'App [%s] launched (%d instances)', data[0].pm2_env.name, data.length); apps_started = apps_started.concat(data); next(); }); }, function(err) { var final_error = err || apps_errored.length > 0 ? apps_errored : null return cb ? cb(final_error, apps_started) : that.speedList(); }); return false; } } /** * Apply a RPC method on the json file * @private * @method actionFromJson * @param {string} action RPC Method * @param {object} options * @param {string|object} file file * @param {string} jsonVia action type (=only 'pipe' ?) * @param {Function} */ actionFromJson (action, file, opts, jsonVia, cb) { var appConf = {}; var ret_processes = []; var that = this; //accept programmatic calls if (typeof file == 'object') { cb = typeof jsonVia == 'function' ? jsonVia : cb; appConf = file; } else if (jsonVia == 'file') { var data = null; try { data = fs.readFileSync(file); } catch(e) { Common.printError(conf.PREFIX_MSG_ERR + 'File ' + file +' not found'); return cb ? cb(Common.retErr(e)) : that.exitCli(conf.ERROR_EXIT); } try { appConf = Common.parseConfig(data, file); } catch(e) { Common.printError(conf.PREFIX_MSG_ERR + 'File ' + file + ' malformated'); console.error(e); return cb ? cb(Common.retErr(e)) : that.exitCli(conf.ERROR_EXIT); } } else if (jsonVia == 'pipe') { appConf = Common.parseConfig(file, 'pipe'); } else { Common.printError('Bad call to actionFromJson, jsonVia should be one of file, pipe'); return that.exitCli(conf.ERROR_EXIT); } // Backward compatibility if (appConf.apps) appConf = appConf.apps; if (!Array.isArray(appConf)) appConf = [appConf]; if ((appConf = Common.verifyConfs(appConf)) instanceof Error) return cb ? cb(appConf) : that.exitCli(conf.ERROR_EXIT); eachLimit(appConf, conf.CONCURRENT_ACTIONS, function(proc, next1) { var name = ''; var new_env; if (!proc.name) name = path.basename(proc.script); else name = proc.name; if (opts.only && opts.only != name) return process.nextTick(next1); if (opts && opts.env) new_env = Common.mergeEnvironmentVariables(proc, opts.env); else new_env = Common.mergeEnvironmentVariables(proc); that.Client.getProcessIdByName(name, function(err, ids) { if (err) { Common.printError(err); return next1(); } if (!ids) return next1(); eachLimit(ids, conf.CONCURRENT_ACTIONS, function(id, next2) { var opts = {}; //stopProcessId could accept options to? if (action == 'restartProcessId') { opts = {id : id, env : new_env}; } else { opts = id; } that.Client.executeRemote(action, opts, function(err, res) { ret_processes.push(res); if (err) { Common.printError(err); return next2(); } if (action == 'restartProcessId') { that.Client.notifyGod('restart', id); } else if (action == 'deleteProcessId') { that.Client.notifyGod('delete', id); } else if (action == 'stopProcessId') { that.Client.notifyGod('stop', id); } Common.printOut(conf.PREFIX_MSG + '[%s](%d) \u2713', name, id); return next2(); }); }, function(err) { return next1(null, ret_processes); }); }); }, function(err) { if (cb) return cb(null, ret_processes); else return that.speedList(); }); } /** * Main function to operate with PM2 daemon * * @param {String} action_name Name of action (restartProcessId, deleteProcessId, stopProcessId) * @param {String} process_name can be 'all', a id integer or process name * @param {Object} envs object with CLI options / environment */ _operate (action_name, process_name, envs, cb) { var that = this; var update_env = false; var ret = []; // Make sure all options exist if (!envs) envs = {}; if (typeof(envs) == 'function'){ cb = envs; envs = {}; } // Set via env.update (JSON processing) if (envs.updateEnv === true) update_env = true; var concurrent_actions = envs.parallel || conf.CONCURRENT_ACTIONS; if (!process.env.PM2_JSON_PROCESSING || envs.commands) { envs = that._handleAttributeUpdate(envs); } /** * Set current updated configuration if not passed */ if (!envs.current_conf) { var _conf = fclone(envs); envs = { current_conf : _conf } // Is KM linked? envs.current_conf.km_link = that.gl_is_km_linked; } /** * Operate action on specific process id */ function processIds(ids, cb) { Common.printOut(conf.PREFIX_MSG + 'Applying action %s on app [%s](ids: %s)', action_name, process_name, ids); if (ids.length <= 2) concurrent_actions = 1; if (action_name == 'deleteProcessId') concurrent_actions = 10; eachLimit(ids, concurrent_actions, function(id, next) { var opts; // These functions need extra param to be passed if (action_name == 'restartProcessId' || action_name == 'reloadProcessId' || action_name == 'softReloadProcessId') { var new_env = {}; if (update_env === true) { if (conf.PM2_PROGRAMMATIC == true) new_env = Common.safeExtend({}, process.env); else new_env = Object.assign({}, process.env); Object.keys(envs).forEach(function(k) { new_env[k] = envs[k]; }); } else { new_env = envs; } opts = { id : id, env : new_env }; } else { opts = id; } that.Client.executeRemote(action_name, opts, function(err, res) { if (err) { Common.printError(conf.PREFIX_MSG_ERR + 'Process %s not found', id); return next(`Process ${id} not found`); } if (action_name == 'restartProcessId') { that.Client.notifyGod('restart', id); } else if (action_name == 'deleteProcessId') { that.Client.notifyGod('delete', id); } else if (action_name == 'stopProcessId') { that.Client.notifyGod('stop', id); } else if (action_name == 'reloadProcessId') { that.Client.notifyGod('reload', id); } else if (action_name == 'softReloadProcessId') { that.Client.notifyGod('graceful reload', id); } if (!Array.isArray(res)) res = [res]; // Filter return res.forEach(function(proc) { Common.printOut(conf.PREFIX_MSG + '[%s](%d) \u2713', proc.pm2_env ? proc.pm2_env.name : process_name, id); if (action_name == 'stopProcessId' && proc.pm2_env && proc.pm2_env.cron_restart) { Common.warn(`App ${chalk.bold(proc.pm2_env.name)} stopped but CRON RESTART is still UP ${proc.pm2_env.cron_restart}`) } if (!proc.pm2_env) return false; ret.push({ name : proc.pm2_env.name, namespace: proc.pm2_env.namespace, pm_id : proc.pm2_env.pm_id, status : proc.pm2_env.status, restart_time : proc.pm2_env.restart_time, pm2_env : { name : proc.pm2_env.name, namespace: proc.pm2_env.namespace, pm_id : proc.pm2_env.pm_id, status : proc.pm2_env.status, restart_time : proc.pm2_env.restart_time, env : proc.pm2_env.env } }); }); return next(); }); }, function(err) { if (err) return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT); return cb ? cb(null, ret) : that.speedList(); }); } if (process_name == 'all') { // When using shortcuts like 'all', do not delete modules var fn if (process.env.PM2_STATUS == 'stopping') that.Client.getAllProcessId(function(err, ids) { reoperate(err, ids) }); else that.Client.getAllProcessIdWithoutModules(function(err, ids) { reoperate(err, ids) }); function reoperate(err, ids) { if (err) { Common.printError(err); return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT); } if (!ids || ids.length === 0) { Common.printError(conf.PREFIX_MSG_WARNING + 'No process found'); return cb ? cb(new Error('process name not found')) : that.exitCli(conf.ERROR_EXIT); } return processIds(ids, cb); } } // operate using regex else if (isNaN(process_name) && process_name[0] === '/' && process_name[process_name.length - 1] === '/') { var regex = new RegExp(process_name.replace(/\//g, '')); that.Client.executeRemote('getMonitorData', {}, function(err, list) { if (err) { Common.printError('Error retrieving process list: ' + err); return cb(err); } var found_proc = []; list.forEach(function(proc) { if (regex.test(proc.pm2_env.name)) { found_proc.push(proc.pm_id); } }); if (found_proc.length === 0) { Common.printError(conf.PREFIX_MSG_WARNING + 'No process found'); return cb ? cb(new Error('process name not found')) : that.exitCli(conf.ERROR_EXIT); } return processIds(found_proc, cb); }); } else if (isNaN(process_name)) { /** * We can not stop or delete a module but we can restart it * to refresh configuration variable */ var allow_module_restart = action_name == 'restartProcessId' ? true : false; that.Client.getProcessIdByName(process_name, allow_module_restart, function (err, ids) { if (err) { Common.printError(err); return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT); } if (ids && ids.length > 0) { /** * Determine if the process to restart is a module * if yes load configuration variables and merge with the current environment */ var additional_env = Modularizer.getAdditionalConf(process_name); Object.assign(envs, additional_env); return processIds(ids, cb); } that.Client.getProcessIdsByNamespace(process_name, allow_module_restart, function (err, ns_process_ids) { if (err) { Common.printError(err); return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT); } if (!ns_process_ids || ns_process_ids.length === 0) { Common.printError(conf.PREFIX_MSG_ERR + 'Process or Namespace %s not found', process_name); return cb ? cb(new Error('process or namespace not found')) : that.exitCli(conf.ERROR_EXIT); } /** * Determine if the process to restart is a module * if yes load configuration variables and merge with the current environment */ var ns_additional_env = Modularizer.getAdditionalConf(process_name); Object.assign(envs, ns_additional_env); return processIds(ns_process_ids, cb); }); }); } else { if (that.pm2_configuration.docker == "true" || that.pm2_configuration.docker == true) { // Docker/Systemd process interaction detection that.Client.executeRemote('getMonitorData', {}, (err, proc_list) => { var higher_id = 0 proc_list.forEach(p => { p.pm_id > higher_id ? higher_id = p.pm_id : null }) // Is Docker/Systemd if (process_name > higher_id) return DockerMgmt.processCommand(that, higher_id, process_name, action_name, (err) => { if (err) { Common.printError(conf.PREFIX_MSG_ERR + (err.message ? err.message : err)); return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT); } return cb ? cb(null, ret) : that.speedList(); }) // Check if application name as number is an app name that.Client.getProcessIdByName(process_name, function(err, ids) { if (ids.length > 0) return processIds(ids, cb); // Check if application name as number is an namespace that.Client.getProcessIdsByNamespace(process_name, function(err, ns_process_ids) { if (ns_process_ids.length > 0) return processIds(ns_process_ids, cb); // Else operate on pm id return processIds([process_name], cb); }); }); }) } else { // Check if application name as number is an app name that.Client.getProcessIdByName(process_name, function(err, ids) { if (ids.length > 0) return processIds(ids, cb); // Check if application name as number is an namespace that.Client.getProcessIdsByNamespace(process_name, function(err, ns_process_ids) { if (ns_process_ids.length > 0) return processIds(ns_process_ids, cb); // Else operate on pm id return processIds([process_name], cb); }); }); } } } /** * Converts CamelCase Commander.js arguments * to Underscore * (nodeArgs -> node_args) */ _handleAttributeUpdate (opts) { var conf = Config.filterOptions(opts); var that = this; if (typeof(conf.name) != 'string') delete conf.name; var argsIndex = 0; if (opts.rawArgs && (argsIndex = opts.rawArgs.indexOf('--')) >= 0) { conf.args = opts.rawArgs.slice(argsIndex + 1); } var appConf = Common.verifyConfs(conf)[0]; if (appConf instanceof Error) { Common.printError('Error while transforming CamelCase args to underscore'); return appConf; } if (argsIndex == -1) delete appConf.args; if (appConf.name == 'undefined') delete appConf.name; delete appConf.exec_mode; if (Array.isArray(appConf.watch) && appConf.watch.length === 0) { if (!~opts.rawArgs.indexOf('--watch')) delete appConf.watch } // Options set via environment variables if (process.env.PM2_DEEP_MONITORING) appConf.deep_monitoring = true; // Force deletion of defaults values set by commander // to avoid overriding specified configuration by user if (appConf.treekill === true) delete appConf.treekill; if (appConf.pmx === true) delete appConf.pmx; if (appConf.vizion === true) delete appConf.vizion; if (appConf.automation === true) delete appConf.automation; if (appConf.autostart === true) delete appConf.autostart; if (appConf.autorestart === true) delete appConf.autorestart; return appConf; } getProcessIdByName (name, cb) { var that = this; this.Client.getProcessIdByName(name, function(err, id) { if (err) { Common.printError(err); return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT); } console.log(id); return cb ? cb(null, id) : that.exitCli(conf.SUCCESS_EXIT); }); } /** * Description * @method jlist * @param {} debug * @return */ jlist (debug) { var that = this; that.Client.executeRemote('getMonitorData', {}, function(err, list) { if (err) { Common.printError(err); return that.exitCli(conf.ERROR_EXIT); } if (debug) { process.stdout.write(util.inspect(list, false, null, false)); } else { process.stdout.write(JSON.stringify(list)); } that.exitCli(conf.SUCCESS_EXIT); }); } /** * Display system information * @method slist * @return */ slist (tree) { this.Client.executeRemote('getSystemData', {}, (err, sys_infos) => { if (err) { Common.err(err) return this.exitCli(conf.ERROR_EXIT) } if (tree === true) { var treeify = require('./tools/treeify.js') console.log(treeify.asTree(sys_infos, true)) } else process.stdout.write(util.inspect(sys_infos, false, null, false)) this.exitCli(conf.SUCCESS_EXIT) }) } /** * Description * @method speedList * @return */ speedList (code, apps_acted) { var that = this; var systemdata = null var acted = [] if ((code != 0 && code != null)) { return that.exitCli(code ? code : conf.SUCCESS_EXIT); } if (apps_acted && apps_acted.length > 0) { apps_acted.forEach(proc => { acted.push(proc.pm2_env ? proc.pm2_env.pm_id : proc.pm_id) }) } // Do nothing if PM2 called programmatically and not called from CLI (also in exitCli) if ((conf.PM2_PROGRAMMATIC && process.env.PM2_USAGE != 'CLI')) return false; return that.Client.executeRemote('getMonitorData', {}, (err, proc_list) => { doList(err, proc_list) }) function doList(err, list) { if (err) { if (that.gl_retry == 0) { that.gl_retry += 1; return setTimeout(that.speedList.bind(that), 1400); } console.error('Error retrieving process list: %s.\nA process seems to be on infinite loop, retry in 5 seconds',err); return that.exitCli(conf.ERROR_EXIT); } if (process.stdout.isTTY === false) { UX.list_min(list); } else if (commander.miniList && !commander.silent) UX.list_min(list); else if (!commander.silent) { if (that.gl_interact_infos) { var dashboard_url = `https://app.pm2.io/#/r/${that.gl_interact_infos.public_key}` if (that.gl_interact_infos.info_node != 'https://root.keymetrics.io') { dashboard_url = `${that.gl_interact_infos.info_node}/#/r/${that.gl_interact_infos.public_key}` } Common.printOut('%s PM2+ activated | Instance Name: %s | Dash: %s', chalk.green.bold('⇆'), chalk.bold(that.gl_interact_infos.machine_name), chalk.bold(dashboard_url)) } UX.list(list, commander); //Common.printOut(chalk.white.italic(' Use `pm2 show ` to get more details about an app')); } if (that.Client.daemon_mode == false) { Common.printOut('[--no-daemon] Continue to stream logs'); Common.printOut('[--no-daemon] Exit on target PM2 exit pid=' + fs.readFileSync(conf.PM2_PID_FILE_PATH).toString()); global._auto_exit = true; return that.streamLogs('all', 0, false, 'HH:mm:ss', false); } // if (process.stdout.isTTY) if looking for start logs else if (!process.env.TRAVIS && process.env.NODE_ENV != 'test' && acted.length > 0 && (commander.attach === true)) { Common.info(`Log streaming apps id: ${chalk.cyan(acted.join(' '))}, exit with Ctrl-C or will exit in 10secs`) // setTimeout(() => { // Common.info(`Log streaming exited automatically, run 'pm2 logs' to continue watching logs`) // return that.exitCli(code ? code : conf.SUCCESS_EXIT); // }, 10000) return acted.forEach((proc_name) => { that.streamLogs(proc_name, 0, false, null, false); }) } else { return that.exitCli(code ? code : conf.SUCCESS_EXIT); } } } /** * Scale up/down a process * @method scale */ scale (app_name, number, cb) { var that = this; function addProcs(proc, value, cb) { (function ex(proc, number) { if (number-- === 0) return cb(); Common.printOut(conf.PREFIX_MSG + 'Scaling up application'); that.Client.executeRemote('duplicateProcessId', proc.pm2_env.pm_id, ex.bind(this, proc, number)); })(proc, number); } function rmProcs(procs, value, cb) { var i = 0; (function ex(procs, number) { if (number++ === 0) return cb(); that._operate('deleteProcessId', procs[i++].pm2_env.pm_id, ex.bind(this, procs, number)); })(procs, number); } function end() { return cb ? cb(null, {success:true}) : that.speedList(); } this.Client.getProcessByName(app_name, function(err, procs) { if (err) { Common.printError(err); return cb ? cb(Common.retErr(err)) : that.exitCli(conf.ERROR_EXIT); } if (!procs || procs.length === 0) { Common.printError(conf.PREFIX_MSG_ERR + 'Application %s not found', app_name); return cb ? cb(new Error('App not found')) : that.exitCli(conf.ERROR_EXIT); } var proc_number = procs.length; if (typeof(number) === 'string' && number.indexOf('+') >= 0) { number = parseInt(number, 10); return addProcs(procs[0], number, end); } else if (typeof(number) === 'string' && number.indexOf('-') >= 0) { number = parseInt(number, 10); return rmProcs(procs[0], number, end); } else { number = parseInt(number, 10); number = number - proc_number; if (number < 0) return rmProcs(procs, number, end); else if (number > 0) return addProcs(procs[0], number, end); else { Common.printError(conf.PREFIX_MSG_ERR + 'Nothing to do'); return cb ? cb(new Error('Same process number')) : that.exitCli(conf.ERROR_EXIT); } } }); } /** * Description * @method describeProcess * @param {} pm2_id * @return */ describe (pm2_id, cb) { var that = this; var found_proc = []; that.Client.executeRemote('getMonitorData', {}, function(err, list) { if (err) { Common.printError('Error retrieving process list: ' + err); that.exitCli(conf.ERROR_EXIT); } list.forEach(function(proc) { if ((!isNaN(pm2_id) && proc.pm_id == pm2_id) || (typeof(pm2_id) === 'string' && proc.name == pm2_id)) { found_proc.push(proc); } }); if (found_proc.length === 0) { Common.printError(conf.PREFIX_MSG_WARNING + '%s doesn\'t exist', pm2_id); return cb ? cb(null, []) : that.exitCli(conf.ERROR_EXIT); } if (!cb) { found_proc.forEach(function(proc) { UX.describe(proc); }); } return cb ? cb(null, found_proc) : that.exitCli(conf.SUCCESS_EXIT); }); } /** * API method to perform a deep update of PM2 * @method deepUpdate */ deepUpdate (cb) { var that = this; Common.printOut(conf.PREFIX_MSG + 'Updating PM2...'); var child = sexec("npm i -g pm2@latest; pm2 update"); child.stdout.on('end', function() { Common.printOut(conf.PREFIX_MSG + 'PM2 successfully updated'); cb ? cb(null, {success:true}) : that.exitCli(conf.SUCCESS_EXIT); }); } }; ////////////////////////// // Load all API methods // ////////////////////////// require('./API/Extra.js')(API); require('./API/Deploy.js')(API); require('./API/Modules/index.js')(API); require('./API/pm2-plus/link.js')(API); require('./API/pm2-plus/process-selector.js')(API); require('./API/pm2-plus/helpers.js')(API); require('./API/Configuration.js')(API); require('./API/Version.js')(API); require('./API/Startup.js')(API); require('./API/LogManagement.js')(API); require('./API/Containerizer.js')(API); module.exports = API; ================================================ FILE: lib/Client.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ var debug = require('debug')('pm2:client'); var Common = require('./Common.js'); var KMDaemon = require('@pm2/agent/src/InteractorClient'); var rpc = require('pm2-axon-rpc'); var forEach = require('async/forEach'); var axon = require('pm2-axon'); var util = require('util'); var fs = require('fs'); var path = require('path'); var pkg = require('../package.json'); var which = require('./tools/which.js'); function noop() {} var Client = module.exports = function(opts) { if (!opts) opts = {}; if (!opts.conf) this.conf = require('../constants.js'); else { this.conf = opts.conf; } this.daemon_mode = typeof(opts.daemon_mode) === 'undefined' ? true : opts.daemon_mode; this.pm2_home = this.conf.PM2_ROOT_PATH; this.secret_key = opts.secret_key; this.public_key = opts.public_key; this.machine_name = opts.machine_name; // Create all folders and files needed // Client depends to that to interact with PM2 properly this.initFileStructure(this.conf); debug('Using RPC file %s', this.conf.DAEMON_RPC_PORT); debug('Using PUB file %s', this.conf.DAEMON_PUB_PORT); this.rpc_socket_file = this.conf.DAEMON_RPC_PORT; this.pub_socket_file = this.conf.DAEMON_PUB_PORT; }; // @breaking change (noDaemonMode has been drop) // @todo ret err Client.prototype.start = function(cb) { var that = this; this.pingDaemon(function(daemonAlive) { if (daemonAlive === true) return that.launchRPC(function(err, meta) { return cb(null, { daemon_mode : that.conf.daemon_mode, new_pm2_instance : false, rpc_socket_file : that.rpc_socket_file, pub_socket_file : that.pub_socket_file, pm2_home : that.pm2_home }); }); /** * No Daemon mode */ if (that.daemon_mode === false) { var Daemon = require('./Daemon.js'); var daemon = new Daemon({ pub_socket_file : that.conf.DAEMON_PUB_PORT, rpc_socket_file : that.conf.DAEMON_RPC_PORT, pid_file : that.conf.PM2_PID_FILE_PATH, ignore_signals : true }); console.log('Launching in no daemon mode'); daemon.innerStart(function() { KMDaemon.launchAndInteract(that.conf, { machine_name : that.machine_name, public_key : that.public_key, secret_key : that.secret_key, pm2_version : pkg.version }, function(err, data, interactor_proc) { that.interactor_process = interactor_proc; }); that.launchRPC(function(err, meta) { return cb(null, { daemon_mode : that.conf.daemon_mode, new_pm2_instance : true, rpc_socket_file : that.rpc_socket_file, pub_socket_file : that.pub_socket_file, pm2_home : that.pm2_home }); }); }); return false; } /** * Daemon mode */ that.launchDaemon(function(err, child) { if (err) { Common.printError(err); return cb ? cb(err) : process.exit(that.conf.ERROR_EXIT); } if (!process.env.PM2_DISCRETE_MODE) Common.printOut(that.conf.PREFIX_MSG + 'PM2 Successfully daemonized'); that.launchRPC(function(err, meta) { return cb(null, { daemon_mode : that.conf.daemon_mode, new_pm2_instance : true, rpc_socket_file : that.rpc_socket_file, pub_socket_file : that.pub_socket_file, pm2_home : that.pm2_home }); }); }); }); }; // Init file structure of pm2_home // This includes // - pm2 pid and log path // - rpc and pub socket for command execution Client.prototype.initFileStructure = function (opts) { if (!fs.existsSync(opts.DEFAULT_LOG_PATH)) { try { require('mkdirp').sync(opts.DEFAULT_LOG_PATH); } catch (e) { console.error(e.stack || e); } } if (!fs.existsSync(opts.DEFAULT_PID_PATH)) { try { require('mkdirp').sync(opts.DEFAULT_PID_PATH); } catch (e) { console.error(e.stack || e); } } if (!fs.existsSync(opts.PM2_MODULE_CONF_FILE)) { try { fs.writeFileSync(opts.PM2_MODULE_CONF_FILE, "{}"); } catch (e) { console.error(e.stack || e); } } if (!fs.existsSync(opts.DEFAULT_MODULE_PATH)) { try { require('mkdirp').sync(opts.DEFAULT_MODULE_PATH); } catch (e) { console.error(e.stack || e); } } if (process.env.PM2_DISCRETE_MODE) { try { fs.writeFileSync(path.join(opts.PM2_HOME, 'touch'), Date.now().toString()); } catch(e) { debug(e.stack || e); } } if (!process.env.PM2_PROGRAMMATIC && !fs.existsSync(path.join(opts.PM2_HOME, 'touch'))) { var vCheck = require('./VersionCheck.js') vCheck({ state: 'install', version: pkg.version }) var dt = fs.readFileSync(path.join(__dirname, opts.PM2_BANNER)); console.log(dt.toString()); try { fs.writeFileSync(path.join(opts.PM2_HOME, 'touch'), Date.now().toString()); } catch(e) { debug(e.stack || e); } } }; Client.prototype.close = function(cb) { var that = this; forEach([ that.disconnectRPC.bind(that), that.disconnectBus.bind(that) ], function(fn, next) { fn(next) }, cb); }; /** * Launch the Daemon by forking this same file * The method Client.remoteWrapper will be called * * @method launchDaemon * @param {Object} opts * @param {Object} [opts.interactor=true] allow to disable interaction on launch */ Client.prototype.launchDaemon = function(opts, cb) { if (typeof(opts) == 'function') { cb = opts; opts = { interactor : true }; } var that = this var ClientJS = path.resolve(path.dirname(module.filename), 'Daemon.js'); var node_args = []; var out, err; // if (process.env.TRAVIS) { // // Redirect PM2 internal err and out to STDERR STDOUT when running with Travis // out = 1; // err = 2; // } // else { out = fs.openSync(that.conf.PM2_LOG_FILE_PATH, 'a'), err = fs.openSync(that.conf.PM2_LOG_FILE_PATH, 'a'); //} if (this.conf.LOW_MEMORY_ENVIRONMENT) { var os = require('os'); node_args.push('--gc-global'); // Does full GC (smaller memory footprint) node_args.push('--max-old-space-size=' + Math.floor(os.totalmem() / 1024 / 1024)); } // Node.js tuning for better performance //node_args.push('--expose-gc'); // Allows manual GC in the code /** * Add node [arguments] depending on PM2_NODE_OPTIONS env variable */ if (process.env.PM2_NODE_OPTIONS) node_args = node_args.concat(process.env.PM2_NODE_OPTIONS.split(' ')); node_args.push(ClientJS); if (!process.env.PM2_DISCRETE_MODE) Common.printOut(that.conf.PREFIX_MSG + 'Spawning PM2 daemon with pm2_home=' + this.pm2_home); var interpreter = process.execPath; var child = require('child_process').spawn(interpreter, node_args, { detached : true, cwd : that.conf.cwd || process.cwd(), windowsHide: true, env : Object.assign({ 'SILENT' : that.conf.DEBUG ? !that.conf.DEBUG : true, 'PM2_HOME' : that.pm2_home }, process.env), stdio : [null, out, err, 'ipc'] }); function onError(e) { console.error(e.message || e); return cb ? cb(e.message || e) : false; } child.once('error', onError); if (this.conf.IS_BUN === false) child.unref(); child.once('message', function(msg) { debug('PM2 daemon launched with return message: ', msg); child.removeListener('error', onError); child.disconnect(); if (opts && opts.interactor == false) return cb(null, child); if (process.env.PM2_NO_INTERACTION == 'true') return cb(null, child); /** * Here the Keymetrics agent is launched automaticcaly if * it has been already configured before (via pm2 link) */ KMDaemon.launchAndInteract(that.conf, { machine_name : that.machine_name, public_key : that.public_key, secret_key : that.secret_key, pm2_version : pkg.version }, function(err, data, interactor_proc) { that.interactor_process = interactor_proc; return cb(null, child); }); }); }; /** * Ping the daemon to know if it alive or not * @api public * @method pingDaemon * @param {} cb * @return */ Client.prototype.pingDaemon = function pingDaemon(cb) { var req = axon.socket('req'); var client = new rpc.Client(req); var that = this; debug('[PING PM2] Trying to connect to server'); client.sock.once('reconnect attempt', function() { client.sock.close(); debug('Daemon not launched'); process.nextTick(function() { return cb(false); }); }); client.sock.once('error', function(e) { if (e.code === 'EACCES') { fs.stat(that.conf.DAEMON_RPC_PORT, function(e, stats) { if (stats.uid === 0) { console.error(that.conf.PREFIX_MSG_ERR + 'Permission denied, to give access to current user:'); console.log('$ sudo chown ' + process.env.USER + ':' + process.env.USER + ' ' + that.conf.DAEMON_RPC_PORT + ' ' + that.conf.DAEMON_PUB_PORT); } else console.error(that.conf.PREFIX_MSG_ERR + 'Permission denied, check permissions on ' + that.conf.DAEMON_RPC_PORT); process.exit(1); }); } else console.error(e.message || e); }); client.sock.once('connect', function() { client.sock.once('close', function() { return cb(true); }); client.sock.close(); debug('Daemon alive'); }); req.connect(this.rpc_socket_file); }; /** * Methods to interact with the Daemon via RPC * This method wait to be connected to the Daemon * Once he's connected it trigger the command parsing (on ./bin/pm2 file, at the end) * @method launchRPC * @params {function} [cb] * @return */ Client.prototype.launchRPC = function launchRPC(cb) { var self = this; debug('Launching RPC client on socket file %s', this.rpc_socket_file); var req = axon.socket('req'); this.client = new rpc.Client(req); var connectHandler = function() { self.client.sock.removeListener('error', errorHandler); debug('RPC Connected to Daemon'); if (cb) { setTimeout(function() { cb(null); }, 4); } }; var errorHandler = function(e) { self.client.sock.removeListener('connect', connectHandler); if (cb) { return cb(e); } }; this.client.sock.once('connect', connectHandler); this.client.sock.once('error', errorHandler); this.client_sock = req.connect(this.rpc_socket_file); }; /** * Methods to close the RPC connection * @callback cb */ Client.prototype.disconnectRPC = function disconnectRPC(cb) { var that = this; if (!cb) cb = noop; if (!this.client_sock || !this.client_sock.close) { this.client = null; return process.nextTick(function() { cb(new Error('SUB connection to PM2 is not launched')); }); } if (this.client_sock.connected === false || this.client_sock.closing === true) { this.client = null; return process.nextTick(function() { cb(new Error('RPC already being closed')); }); } try { var timer; that.client_sock.once('close', function() { clearTimeout(timer); that.client = null; debug('PM2 RPC cleanly closed'); return cb(null, { msg : 'RPC Successfully closed' }); }); timer = setTimeout(function() { if (that.client_sock.destroy) that.client_sock.destroy(); that.client = null; return cb(null, { msg : 'RPC Successfully closed via timeout' }); }, 200); that.client_sock.close(); } catch(e) { debug('Error while disconnecting RPC PM2', e.stack || e); return cb(e); } return false; }; Client.prototype.launchBus = function launchEventSystem(cb) { var self = this; this.sub = axon.socket('sub-emitter'); this.sub_sock = this.sub.connect(this.pub_socket_file); this.sub_sock.once('connect', function() { return cb(null, self.sub, self.sub_sock); }); }; Client.prototype.disconnectBus = function disconnectBus(cb) { if (!cb) cb = noop; var that = this; if (!this.sub_sock || !this.sub_sock.close) { that.sub = null; return process.nextTick(function() { cb(null, { msg : 'bus was not connected'}); }); } if (this.sub_sock.connected === false || this.sub_sock.closing === true) { that.sub = null; return process.nextTick(function() { cb(new Error('SUB connection is already being closed')); }); } try { var timer; that.sub_sock.once('close', function() { that.sub = null; clearTimeout(timer); debug('PM2 PUB cleanly closed'); return cb(); }); timer = setTimeout(function() { if (Client.sub_sock.destroy) that.sub_sock.destroy(); return cb(); }, 200); this.sub_sock.close(); } catch(e) { return cb(e); } }; /** * Description * @method gestExposedMethods * @param {} cb * @return */ Client.prototype.getExposedMethods = function getExposedMethods(cb) { this.client.methods(cb); }; /** * Description * @method executeRemote * @param {} method * @param {} env * @param {} fn * @return */ Client.prototype.executeRemote = function executeRemote(method, app_conf, fn) { var self = this; // stop watch on stop | env is the process id if (method.indexOf('stop') !== -1) { this.stopWatch(method, app_conf); } // stop watching when process is deleted else if (method.indexOf('delete') !== -1) { this.stopWatch(method, app_conf); } // stop everything on kill else if (method.indexOf('kill') !== -1) { this.stopWatch('deleteAll', app_conf); } else if (method.indexOf('restartProcessId') !== -1 && process.argv.indexOf('--watch') > -1) { delete app_conf.env.current_conf.watch; this.toggleWatch(method, app_conf); } if (!this.client || !this.client.call) { this.start(function(error) { if (error) { if (fn) return fn(error); console.error(error); return process.exit(0); } if (self.client) { return self.client.call(method, app_conf, fn); } }); return false; } debug('Calling daemon method pm2:%s on rpc socket:%s', method, this.rpc_socket_file); return this.client.call(method, app_conf, fn); }; Client.prototype.notifyGod = function(action_name, id, cb) { this.executeRemote('notifyByProcessId', { id : id, action_name : action_name, manually : true }, function() { debug('God notified'); return cb ? cb() : false; }); }; Client.prototype.killDaemon = function killDaemon(fn) { var timeout; var that = this; function quit() { that.close(function() { return fn ? fn(null, {success:true}) : false; }); } // under unix, we listen for signal (that is send by daemon to notify us that its shuting down) if (process.platform !== 'win32' && process.platform !== 'win64') { process.once('SIGQUIT', function() { debug('Received SIGQUIT from pm2 daemon'); clearTimeout(timeout); quit(); }); } else { // if under windows, try to ping the daemon to see if it still here setTimeout(function() { that.pingDaemon(function(alive) { if (!alive) { clearTimeout(timeout); return quit(); } }); }, 250) } timeout = setTimeout(function() { quit(); }, 3000); // Kill daemon this.executeRemote('killMe', {pid : process.pid}); }; /** * Description * @method toggleWatch * @param {String} pm2 method name * @param {Object} application environment, should include id * @param {Function} callback */ Client.prototype.toggleWatch = function toggleWatch(method, env, fn) { debug('Calling toggleWatch'); this.client.call('toggleWatch', method, env, function() { return fn ? fn() : false; }); }; /** * Description * @method startWatch * @param {String} pm2 method name * @param {Object} application environment, should include id * @param {Function} callback */ Client.prototype.startWatch = function restartWatch(method, env, fn) { debug('Calling startWatch'); this.client.call('startWatch', method, env, function() { return fn ? fn() : false; }); }; /** * Description * @method stopWatch * @param {String} pm2 method name * @param {Object} application environment, should include id * @param {Function} callback */ Client.prototype.stopWatch = function stopWatch(method, env, fn) { debug('Calling stopWatch'); this.client.call('stopWatch', method, env, function() { return fn ? fn() : false; }); }; Client.prototype.getAllProcess = function(cb) { var found_proc = []; this.executeRemote('getMonitorData', {}, function(err, procs) { if (err) { Common.printError('Error retrieving process list: ' + err); return cb(err); } return cb(null, procs); }); }; Client.prototype.getAllProcessId = function(cb) { var found_proc = []; this.executeRemote('getMonitorData', {}, function(err, procs) { if (err) { Common.printError('Error retrieving process list: ' + err); return cb(err); } return cb(null, procs.map(proc => proc.pm_id)); }); }; Client.prototype.getAllProcessIdWithoutModules = function(cb) { var found_proc = []; this.executeRemote('getMonitorData', {}, function(err, procs) { if (err) { Common.printError('Error retrieving process list: ' + err); return cb(err); } var proc_ids = procs .filter(proc => !proc.pm2_env.pmx_module) .map(proc => proc.pm_id) return cb(null, proc_ids); }); }; Client.prototype.getProcessIdByName = function(name, force_all, cb) { var found_proc = []; var full_details = {}; if (typeof(cb) === 'undefined') { cb = force_all; force_all = false; } if (typeof(name) == 'number') name = name.toString(); this.executeRemote('getMonitorData', {}, function(err, list) { if (err) { Common.printError('Error retrieving process list: ' + err); return cb(err); } list.forEach(function(proc) { if (proc.pm2_env.name == name || proc.pm2_env.pm_exec_path == path.resolve(name)) { found_proc.push(proc.pm_id); full_details[proc.pm_id] = proc; } }); return cb(null, found_proc, full_details); }); }; Client.prototype.getProcessIdsByNamespace = function(namespace, force_all, cb) { var found_proc = []; var full_details = {}; if (typeof(cb) === 'undefined') { cb = force_all; force_all = false; } if (typeof(namespace) == 'number') namespace = namespace.toString(); this.executeRemote('getMonitorData', {}, function(err, list) { if (err) { Common.printError('Error retrieving process list: ' + err); return cb(err); } list.forEach(function(proc) { if (proc.pm2_env.namespace == namespace) { found_proc.push(proc.pm_id); full_details[proc.pm_id] = proc; } }); return cb(null, found_proc, full_details); }); }; Client.prototype.getProcessByName = function(name, cb) { var found_proc = []; this.executeRemote('getMonitorData', {}, function(err, list) { if (err) { Common.printError('Error retrieving process list: ' + err); return cb(err); } list.forEach(function(proc) { if (proc.pm2_env.name == name || proc.pm2_env.pm_exec_path == path.resolve(name)) { found_proc.push(proc); } }); return cb(null, found_proc); }); }; Client.prototype.getProcessByNameOrId = function (nameOrId, cb) { var foundProc = []; this.executeRemote('getMonitorData', {}, function (err, list) { if (err) { Common.printError('Error retrieving process list: ' + err); return cb(err); } list.forEach(function (proc) { if (proc.pm2_env.name === nameOrId || proc.pm2_env.pm_exec_path === path.resolve(nameOrId) || proc.pid === parseInt(nameOrId) || proc.pm2_env.pm_id === parseInt(nameOrId)) { foundProc.push(proc); } }); return cb(null, foundProc); }); }; ================================================ FILE: lib/Common.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ /** * Common Utilities ONLY USED IN ->CLI<- */ var fs = require('fs'); var path = require('path'); var os = require('os'); var util = require('util'); var chalk = require('ansis'); var fclone = require('fclone'); var semver = require('semver'); var dayjs = require('dayjs'); var execSync = require('child_process').execSync; var isBinary = require('./tools/isbinaryfile.js'); var cst = require('../constants.js'); var extItps = require('./API/interpreter.json'); var Config = require('./tools/Config'); var pkg = require('../package.json'); var which = require('./tools/which.js'); var Common = module.exports; function homedir() { var env = process.env; var home = env.HOME; var user = env.LOGNAME || env.USER || env.LNAME || env.USERNAME; if (process.platform === 'win32') { return env.USERPROFILE || env.HOMEDRIVE + env.HOMEPATH || home || null; } if (process.platform === 'darwin') { return home || (user ? '/Users/' + user : null); } if (process.platform === 'linux') { return home || (process.getuid() === 0 ? '/root' : (user ? '/home/' + user : null)); } return home || null; } function resolveHome(filepath) { if (filepath[0] === '~') { return path.join(homedir(), filepath.slice(1)); } return filepath; } Common.determineSilentCLI = function() { // pm2 should ignore -s --silent -v if they are after '--' var variadicArgsDashesPos = process.argv.indexOf('--'); var s1opt = process.argv.indexOf('--silent') var s2opt = process.argv.indexOf('-s') if (process.env.PM2_SILENT || (variadicArgsDashesPos > -1 && (s1opt != -1 && s1opt < variadicArgsDashesPos) && (s2opt != -1 != s2opt < variadicArgsDashesPos)) || (variadicArgsDashesPos == -1 && (s1opt > -1 || s2opt > -1))) { for (var key in console){ var code = key.charCodeAt(0); if (code >= 97 && code <= 122){ console[key] = function(){}; } } process.env.PM2_DISCRETE_MODE = true; } } Common.printVersion = function() { var variadicArgsDashesPos = process.argv.indexOf('--'); if (process.argv.indexOf('-v') > -1 && process.argv.indexOf('-v') < variadicArgsDashesPos) { console.log(pkg.version); process.exit(0); } } Common.lockReload = function() { try { var t1 = fs.readFileSync(cst.PM2_RELOAD_LOCKFILE).toString(); // Check if content and if time < 30 return locked // Else if content detected (lock file staled), allow and rewritte if (t1 && t1 != '') { var diff = dayjs().diff(parseInt(t1)); if (diff < cst.RELOAD_LOCK_TIMEOUT) return diff; } } catch(e) {} try { // Write latest timestamp fs.writeFileSync(cst.PM2_RELOAD_LOCKFILE, dayjs().valueOf().toString()); return 0; } catch(e) { console.error(e.message || e); } }; Common.unlockReload = function() { try { fs.writeFileSync(cst.PM2_RELOAD_LOCKFILE, ''); } catch(e) { console.error(e.message || e); } }; /** * Resolve app paths and replace missing values with defaults. * @method prepareAppConf * @param app {Object} * @param {} cwd * @param {} outputter * @return app */ Common.prepareAppConf = function(opts, app) { /** * Minimum validation */ if (!app.script) return new Error('No script path - aborting'); var cwd = null; if (app.cwd) { cwd = path.resolve(app.cwd); process.env.PWD = app.cwd; } if (!app.node_args) { app.node_args = []; } if (app.port && app.env) { app.env.PORT = app.port; } // CWD option resolving cwd && (cwd[0] != '/') && (cwd = path.resolve(process.cwd(), cwd)); cwd = cwd || opts.cwd; // Full path script resolution app.pm_exec_path = path.resolve(cwd, app.script); // If script does not exist after resolution if (!fs.existsSync(app.pm_exec_path)) { var ckd; // Try resolve command available in $PATH if ((ckd = which(app.script))) { if (typeof(ckd) !== 'string') ckd = ckd.toString(); app.pm_exec_path = ckd; } else // Throw critical error return new Error(`Script not found: ${app.pm_exec_path}`); } /** * Auto detect .map file and enable source map support automatically */ if (app.disable_source_map_support != true) { try { fs.accessSync(app.pm_exec_path + '.map', fs.constants.R_OK); app.source_map_support = true; } catch(e) {} delete app.disable_source_map_support; } delete app.script; // Set current env by first adding the process environment and then extending/replacing it // with env specified on command-line or JSON file. var env = {}; /** * Do not copy internal pm2 environment variables if acting on process * is made from a programmatic script started by PM2 or if a pm_id is present in env */ if (cst.PM2_PROGRAMMATIC || process.env.pm_id) Common.safeExtend(env, process.env); else env = process.env; function filterEnv (envObj) { if (app.filter_env == true) return {} if (typeof app.filter_env === 'string') { delete envObj[app.filter_env] return envObj } var new_env = {}; var allowedKeys = app.filter_env.reduce((acc, current) => acc.filter( item => !item.includes(current)), Object.keys(envObj)) allowedKeys.forEach( key => new_env[key] = envObj[key]); return new_env } app.env = [ {}, (app.filter_env && app.filter_env.length > 0) ? filterEnv(process.env) : env, app.env || {} ].reduce(function(e1, e2){ return Object.assign(e1, e2); }); app.pm_cwd = cwd; // Interpreter try { Common.sink.resolveInterpreter(app); } catch(e) { return e } // Exec mode and cluster stuff Common.sink.determineExecMode(app); /** * Scary */ var formated_app_name = app.name.replace(/[^a-zA-Z0-9\\.\\-]/g, '-'); ['log', 'out', 'error', 'pid'].forEach(function(f){ var af = app[f + '_file'], ps, ext = (f == 'pid' ? 'pid':'log'), isStd = !~['log', 'pid'].indexOf(f); if (af) af = resolveHome(af); if ((f == 'log' && typeof af == 'boolean' && af) || (f != 'log' && !af)) { ps = [cst['DEFAULT_' + ext.toUpperCase() + '_PATH'], formated_app_name + (isStd ? '-' + f : '') + '.' + ext]; } else if ((f != 'log' || (f == 'log' && af)) && af !== 'NULL' && af !== '/dev/null') { ps = [cwd, af]; var dir = path.dirname(path.resolve(cwd, af)); if (!fs.existsSync(dir)) { Common.printError(cst.PREFIX_MSG_WARNING + 'Folder does not exist: ' + dir); Common.printOut(cst.PREFIX_MSG + 'Creating folder: ' + dir); try { require('mkdirp').sync(dir); } catch (err) { Common.printError(cst.PREFIX_MSG_ERR + 'Could not create folder: ' + path.dirname(af)); throw new Error('Could not create folder'); } } } // PM2 paths if (af !== 'NULL' && af !== '/dev/null') { ps && (app['pm_' + (isStd ? f.substr(0, 3) + '_' : '') + ext + '_path'] = path.resolve.apply(null, ps)); } else if (path.sep === '\\') { app['pm_' + (isStd ? f.substr(0, 3) + '_' : '') + ext + '_path'] = '\\\\.\\NUL'; } else { app['pm_' + (isStd ? f.substr(0, 3) + '_' : '') + ext + '_path'] = '/dev/null'; } delete app[f + '_file']; }); return app; }; /** * Definition of known config file extensions with their type */ Common.knonwConfigFileExtensions = { '.json': 'json', '.yml': 'yaml', '.yaml': 'yaml', '.config.js': 'js', '.config.cjs': 'js', '.config.mjs': 'mjs' } /** * Check if filename is a configuration file * @param {string} filename * @return {mixed} null if not conf file, json or yaml if conf */ Common.isConfigFile = function (filename) { if (typeof (filename) !== 'string') return null; for (let extension in Common.knonwConfigFileExtensions) { if (filename.indexOf(extension) !== -1) { return Common.knonwConfigFileExtensions[extension]; } } return null; }; Common.getConfigFileCandidates = function (name) { return Object.keys(Common.knonwConfigFileExtensions).map((extension) => name + extension); } /** * Parses a config file like ecosystem.config.js. Supported formats: JS, JSON, JSON5, YAML. * @param {string} confString contents of the config file * @param {string} filename path to the config file * @return {Object} config object */ Common.parseConfig = function(confObj, filename) { var yamljs = require('js-yaml'); var vm = require('vm'); var isConfigFile = Common.isConfigFile(filename); if (!filename || filename == 'pipe' || filename == 'none' || isConfigFile == 'json') { var code = '(' + confObj + ')'; var sandbox = {}; return vm.runInThisContext(code, sandbox, { filename: path.resolve(filename), displayErrors: false, timeout: 1000 }); } else if (isConfigFile == 'yaml') { return yamljs.load(confObj.toString()); } else if (isConfigFile == 'js' || isConfigFile == 'mjs') { var confPath = require.resolve(path.resolve(filename)); delete require.cache[confPath]; return require(confPath); } }; Common.retErr = function(e) { if (!e) return new Error('Unidentified error'); if (e instanceof Error) return e; return new Error(e); } Common.sink = {}; Common.sink.determineCron = function(app) { if (app.cron_restart == 0 || app.cron_restart == '0') { Common.printOut(cst.PREFIX_MSG + 'disabling cron restart'); return } if (app.cron_restart) { const Croner = require('croner'); try { Common.printOut(cst.PREFIX_MSG + 'cron restart at ' + app.cron_restart); Croner(app.cron_restart); } catch(ex) { return new Error(`Cron pattern error: ${ex.message}`); } } }; /** * Handle alias (fork <=> fork_mode, cluster <=> cluster_mode) */ Common.sink.determineExecMode = function(app) { if (app.exec_mode) app.exec_mode = app.exec_mode.replace(/^(fork|cluster)$/, '$1_mode'); /** * Here we put the default exec mode */ if (!app.exec_mode && (app.instances >= 1 || app.instances === 0 || app.instances === -1) && (app.exec_interpreter.includes('node') === true || app.exec_interpreter.includes('bun') === true)) { app.exec_mode = 'cluster_mode'; } else if (!app.exec_mode) { app.exec_mode = 'fork_mode'; } if (typeof app.instances == 'undefined') app.instances = 1; }; var resolveNodeInterpreter = function(app) { if (app.exec_mode && app.exec_mode.indexOf('cluster') > -1) { Common.printError(cst.PREFIX_MSG_WARNING + chalk.bold.yellow('Choosing the Node.js version in cluster mode is not supported')); return false; } var nvm_path = cst.IS_WINDOWS ? process.env.NVM_HOME : process.env.NVM_DIR; if (!nvm_path) { Common.printError(cst.PREFIX_MSG_ERR + chalk.red('NVM is not available in PATH')); Common.printError(cst.PREFIX_MSG_ERR + chalk.red('Fallback to node in PATH')); var msg = cst.IS_WINDOWS ? 'https://github.com/coreybutler/nvm-windows/releases/' : '$ curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash'; Common.printOut(cst.PREFIX_MSG_ERR + chalk.bold('Install NVM:\n' + msg)); } else { var node_version = app.exec_interpreter.split('@')[1]; var path_to_node = cst.IS_WINDOWS ? '/v' + node_version + '/node.exe' : semver.satisfies(node_version, '>= 0.12.0') ? '/versions/node/v' + node_version + '/bin/node' : '/v' + node_version + '/bin/node'; var nvm_node_path = path.join(nvm_path, path_to_node); try { fs.accessSync(nvm_node_path); } catch(e) { Common.printOut(cst.PREFIX_MSG + 'Installing Node v%s', node_version); var nvm_bin = path.join(nvm_path, 'nvm.' + (cst.IS_WINDOWS ? 'exe' : 'sh')); var nvm_cmd = cst.IS_WINDOWS ? nvm_bin + ' install ' + node_version : '. ' + nvm_bin + ' ; nvm install ' + node_version; Common.printOut(cst.PREFIX_MSG + 'Executing: %s', nvm_cmd); execSync(nvm_cmd, { cwd: path.resolve(process.cwd()), env: process.env, maxBuffer: 20 * 1024 * 1024 }); // in order to support both arch, nvm for Windows renames 'node.exe' to: // 'node32.exe' for x32 arch // 'node64.exe' for x64 arch if (cst.IS_WINDOWS) nvm_node_path = nvm_node_path.replace(/node/, 'node' + process.arch.slice(1)) } Common.printOut(cst.PREFIX_MSG + chalk.green.bold('Setting Node to v%s (path=%s)'), node_version, nvm_node_path); app.exec_interpreter = nvm_node_path; } }; /** * Resolve interpreter */ Common.sink.resolveInterpreter = function(app) { var noInterpreter = !app.exec_interpreter; var extName = path.extname(app.pm_exec_path); var betterInterpreter = extItps[extName]; // Bun support if (noInterpreter && (extName == '.js' || extName == '.ts') && cst.IS_BUN === true) { noInterpreter = false app.exec_interpreter = process.execPath; } // No interpreter defined and correspondance in schema hashmap if (noInterpreter && betterInterpreter) { app.exec_interpreter = betterInterpreter; if (betterInterpreter == "python") { if (which('python') == null) { if (which('python3') == null) Common.printError(cst.PREFIX_MSG_WARNING + chalk.bold.yellow('python and python3 binaries not available in PATH')); else app.exec_interpreter = 'python3'; } } } // Else if no Interpreter detect if process is binary else if (noInterpreter) { app.exec_interpreter = isBinary(app.pm_exec_path) ? 'none' : process.execPath; } else if (app.exec_interpreter.indexOf('node@') > -1) resolveNodeInterpreter(app); if (app.exec_interpreter.indexOf('python') > -1) app.env.PYTHONUNBUFFERED = '1' if (app.exec_interpreter == 'lsc') { app.exec_interpreter = path.resolve(__dirname, '../node_modules/.bin/lsc'); } if (app.exec_interpreter == 'coffee') { app.exec_interpreter = path.resolve(__dirname, '../node_modules/.bin/coffee'); } if (app.exec_interpreter != 'none' && which(app.exec_interpreter) == null) { // If node is not present if (app.exec_interpreter == 'node') { Common.warn(`Using builtin node.js version on version ${process.version}`) app.exec_interpreter = cst.BUILTIN_NODE_PATH } else throw new Error(`Interpreter ${app.exec_interpreter} is NOT AVAILABLE in PATH. (type 'which ${app.exec_interpreter}' to double check.)`) } return app; }; Common.deepCopy = Common.serialize = Common.clone = function(obj) { if (obj === null || obj === undefined) return {}; return fclone(obj); }; Common.errMod = function(msg) { if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false; if (msg instanceof Error) return console.error(msg.message); return console.error(`${cst.PREFIX_MSG_MOD_ERR}${msg}`); } Common.err = function(msg) { if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false; if (msg instanceof Error) return console.error(`${cst.PREFIX_MSG_ERR}${msg.message}`); return console.error(`${cst.PREFIX_MSG_ERR}${msg}`); } Common.printError = function(msg) { if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false; if (msg instanceof Error) return console.error(msg.message); return console.error.apply(console, arguments); }; Common.log = function(msg) { if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false; return console.log(`${cst.PREFIX_MSG}${msg}`); } Common.info = function(msg) { if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false; return console.log(`${cst.PREFIX_MSG_INFO}${msg}`); } Common.warn = function(msg) { if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false; return console.log(`${cst.PREFIX_MSG_WARNING}${msg}`); } Common.logMod = function(msg) { if (process.env.PM2_SILENT || process.env.PM2_PROGRAMMATIC === 'true') return false; return console.log(`${cst.PREFIX_MSG_MOD}${msg}`); } Common.printOut = function() { if (process.env.PM2_SILENT === 'true' || process.env.PM2_PROGRAMMATIC === 'true') return false; return console.log.apply(console, arguments); }; /** * Raw extend */ Common.extend = function(destination, source) { if (typeof destination !== 'object') { destination = {}; } if (!source || typeof source !== 'object') { return destination; } Object.keys(source).forEach(function(new_key) { if (source[new_key] != '[object Object]') destination[new_key] = source[new_key]; }); return destination; }; /** * This is useful when starting script programmatically */ Common.safeExtend = function(origin, add){ if (!add || typeof add != 'object') return origin; //Ignore PM2's set environment variables from the nested env var keysToIgnore = ['name', 'exec_mode', 'env', 'args', 'pm_cwd', 'exec_interpreter', 'pm_exec_path', 'node_args', 'pm_out_log_path', 'pm_err_log_path', 'pm_pid_path', 'pm_id', 'status', 'pm_uptime', 'created_at', 'windowsHide', 'username', 'merge_logs', 'kill_retry_time', 'prev_restart_delay', 'instance_var', 'unstable_restarts', 'restart_time', 'axm_actions', 'pmx_module', 'command', 'watch', 'filter_env', 'versioning', 'vizion_runing', 'MODULE_DEBUG', 'pmx', 'axm_options', 'created_at', 'watch', 'vizion', 'axm_dynamic', 'axm_monitor', 'instances', 'automation', 'autostart', 'autorestart', 'stop_exit_codes', 'unstable_restart', 'treekill', 'exit_code', 'vizion']; var keys = Object.keys(add); var i = keys.length; while (i--) { //Only copy stuff into the env that we don't have already. if(keysToIgnore.indexOf(keys[i]) == -1 && add[keys[i]] != '[object Object]') origin[keys[i]] = add[keys[i]]; } return origin; }; /** * Extend the app.env object of with the properties taken from the * app.env_[envName] and deploy configuration. * Also update current json attributes * * Used only for Configuration file processing * * @param {Object} app The app object. * @param {string} envName The given environment name. * @param {Object} deployConf Deployment configuration object (from JSON file or whatever). * @returns {Object} The app.env variables object. */ Common.mergeEnvironmentVariables = function(app_env, env_name, deploy_conf) { var app = fclone(app_env); var new_conf = { env : {} } // Stringify possible object for (var key in app.env) { if (typeof app.env[key] == 'object') { app.env[key] = JSON.stringify(app.env[key]); } } /** * Extra configuration update */ Object.assign(new_conf, app); if (env_name) { // First merge variables from deploy.production.env object as least priority. if (deploy_conf && deploy_conf[env_name] && deploy_conf[env_name]['env']) { Object.assign(new_conf.env, deploy_conf[env_name]['env']); } Object.assign(new_conf.env, app.env); // Then, last and highest priority, merge the app.env_production object. if ('env_' + env_name in app) { Object.assign(new_conf.env, app['env_' + env_name]); } else { Common.printOut(cst.PREFIX_MSG_WARNING + chalk.bold('Environment [%s] is not defined in process file'), env_name); } } delete new_conf.exec_mode var res = { current_conf: {} } Object.assign(res, new_conf.env); Object.assign(res.current_conf, new_conf); // #2541 force resolution of node interpreter if (app.exec_interpreter && app.exec_interpreter.indexOf('@') > -1) { resolveNodeInterpreter(app); res.current_conf.exec_interpreter = app.exec_interpreter } return res } /** * This function will resolve paths, option and environment * CALLED before 'prepare' God call (=> PROCESS INITIALIZATION) * @method resolveAppAttributes * @param {Object} opts * @param {Object} opts.cwd * @param {Object} opts.pm2_home * @param {Object} appConf application configuration * @return app */ Common.resolveAppAttributes = function(opts, conf) { var conf_copy = fclone(conf); var app = Common.prepareAppConf(opts, conf_copy); if (app instanceof Error) { throw new Error(app.message); } return app; } /** * Verify configurations * Called on EVERY Operation (start/restart/reload/stop...) * @param {Array} appConfs * @returns {Array} */ Common.verifyConfs = function(appConfs) { if (!appConfs || appConfs.length == 0) { return []; } // Make sure it is an Array. appConfs = [].concat(appConfs); var verifiedConf = []; for (var i = 0; i < appConfs.length; i++) { var app = appConfs[i]; if (app.exec_mode) app.exec_mode = app.exec_mode.replace(/^(fork|cluster)$/, '$1_mode'); // JSON conf: alias cmd to script if (app.cmd && !app.script) { app.script = app.cmd delete app.cmd } // JSON conf: alias command to script if (app.command && !app.script) { app.script = app.command delete app.command } if (!app.env) { app.env = {} } // Render an app name if not existing. Common.renderApplicationName(app); if (app.execute_command == true) { app.exec_mode = 'fork' delete app.execute_command } app.username = Common.getCurrentUsername(); /** * If command is like pm2 start "python xx.py --ok" * Then automatically start the script with bash -c and set a name eq to command */ if (app.script && app.script.indexOf(' ') > -1 && cst.IS_WINDOWS === false) { var _script = app.script; if (which('bash')) { app.script = 'bash'; app.args = ['-c', _script]; if (!app.name) { app.name = _script } } else if (which('sh')) { app.script = 'sh'; app.args = ['-c', _script]; if (!app.name) { app.name = _script } } else { warn('bash or sh not available in $PATH, keeping script as is') } } /** * Add log_date_format by default */ if (app.time || process.env.ASZ_MODE) { app.log_date_format = 'YYYY-MM-DDTHH:mm:ss' } /** * Checks + Resolve UID/GID * comes from pm2 --uid <> --gid <> or --user */ if (app.uid || app.gid || app.user) { // 1/ Check if windows if (cst.IS_WINDOWS === true) { Common.printError(cst.PREFIX_MSG_ERR + '--uid and --git does not works on windows'); return new Error('--uid and --git does not works on windows'); } // 2/ Verify that user is root (todo: verify if other has right) if (process.env.NODE_ENV != 'test' && process.getuid && process.getuid() !== 0) { Common.printError(cst.PREFIX_MSG_ERR + 'To use --uid and --gid please run pm2 as root'); return new Error('To use UID and GID please run PM2 as root'); } // 3/ Resolve user info via /etc/password var passwd = require('./tools/passwd.js') var users try { users = passwd.getUsers() } catch(e) { Common.printError(e); return new Error(e); } var user_info = users[app.uid || app.user] if (!user_info) { Common.printError(`${cst.PREFIX_MSG_ERR} User ${app.uid || app.user} cannot be found`); return new Error(`${cst.PREFIX_MSG_ERR} User ${app.uid || app.user} cannot be found`); } app.env.HOME = user_info.homedir app.uid = parseInt(user_info.userId) // 4/ Resolve group id if gid is specified if (app.gid) { var groups try { groups = passwd.getGroups() } catch(e) { Common.printError(e); return new Error(e); } var group_info = groups[app.gid] if (!group_info) { Common.printError(`${cst.PREFIX_MSG_ERR} Group ${app.gid} cannot be found`); return new Error(`${cst.PREFIX_MSG_ERR} Group ${app.gid} cannot be found`); } app.gid = parseInt(group_info.id) } else { app.gid = parseInt(user_info.groupId) } } /** * Specific options of PM2.io */ if (process.env.PM2_DEEP_MONITORING) { app.deep_monitoring = true; } if (app.automation == false) { app.pmx = false; } if (app.disable_trace) { app.trace = false delete app.disable_trace; } /** * Instances params */ if (app.instances == 'max') { app.instances = 0; } if (typeof(app.instances) === 'string') { app.instances = parseInt(app.instances) || 0; } if (app.exec_mode != 'cluster_mode' && !app.instances && typeof(app.merge_logs) == 'undefined') { app.merge_logs = true; } var ret; if (app.cron_restart) { if ((ret = Common.sink.determineCron(app)) instanceof Error) return ret; } /** * Now validation configuration */ var ret = Config.validateJSON(app); if (ret.errors && ret.errors.length > 0){ ret.errors.forEach(function(err) { warn(err) }); return new Error(ret.errors); } verifiedConf.push(ret.config); } return verifiedConf; } /** * Get current username * Called on EVERY starting app * * @returns {String} */ Common.getCurrentUsername = function(){ var current_user = ''; if (os.userInfo) { try { current_user = os.userInfo().username; } catch (err) { // For the case of unhandled error for uv_os_get_passwd // https://github.com/Unitech/pm2/issues/3184 } } if(current_user === '') { current_user = process.env.USER || process.env.LNAME || process.env.USERNAME || process.env.SUDO_USER || process.env.C9_USER || process.env.LOGNAME; } return current_user; } /** * Render an app name if not existing. * @param {Object} conf */ Common.renderApplicationName = function(conf){ if (!conf.name && conf.script){ conf.name = conf.script !== undefined ? path.basename(conf.script) : 'undefined'; var lastDot = conf.name.lastIndexOf('.'); if (lastDot > 0){ conf.name = conf.name.slice(0, lastDot); } } } /** * Show warnings * @param {String} warning */ function warn(warning){ Common.printOut(cst.PREFIX_MSG_WARNING + warning); } ================================================ FILE: lib/Configuration.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ var Configuration = module.exports = {}; var fs = require('fs'); var Common = require('./Common'); var eachSeries = require('async/eachSeries'); var cst = require('../constants.js'); function splitKey(key) { var values = [key]; if (key.indexOf('.') > -1) values = key.match(/(?:[^."]+|"[^"]*")+/g).map(function(dt) { return dt.replace(/"/g, '') }); else if (key.indexOf(':') > -1) values = key.match(/(?:[^:"]+|"[^"]*")+/g).map(function(dt) { return dt.replace(/"/g, '') }); return values; } function serializeConfiguration(json_conf) { return JSON.stringify(json_conf, null, 4) } Configuration.set = function(key, value, cb) { fs.readFile(cst.PM2_MODULE_CONF_FILE, function(err, data) { if (err) return cb(err); var json_conf = JSON.parse(data); var values = splitKey(key); if (values.length > 0) { var levels = values; var tmp = json_conf; levels.forEach(function(key, index) { if (index == levels.length -1) tmp[key] = value; else if (!tmp[key]) { tmp[key] = {}; tmp = tmp[key]; } else { if (typeof(tmp[key]) != 'object') tmp[key] = {}; tmp = tmp[key]; } }); } else { if (json_conf[key] && typeof(json_conf[key]) === 'string') Common.printOut(cst.PREFIX_MSG + 'Replacing current value key %s by %s', key, value); json_conf[key] = value; } fs.writeFile(cst.PM2_MODULE_CONF_FILE, serializeConfiguration(json_conf), function(err, data) { if (err) return cb(err); return cb(null, json_conf); }); return false; }); }; Configuration.unset = function(key, cb) { fs.readFile(cst.PM2_MODULE_CONF_FILE, function(err, data) { if (err) return cb(err); var json_conf = JSON.parse(data); var values = splitKey(key); if (values.length > 0) { var levels = values; var tmp = json_conf; levels.forEach(function(key, index) { if (index == levels.length -1) delete tmp[key]; else if (!tmp[key]) { tmp[key] = {}; tmp = tmp[key]; } else { if (typeof(tmp[key]) != 'object') tmp[key] = {}; tmp = tmp[key]; } }); } else delete json_conf[key]; if (err) return cb(err); if (key === 'all') json_conf = {}; fs.writeFile(cst.PM2_MODULE_CONF_FILE, serializeConfiguration(json_conf), function(err, data) { if (err) return cb(err); return cb(null, json_conf); }); return false; }); } Configuration.setSyncIfNotExist = function(key, value) { try { var conf = JSON.parse(fs.readFileSync(cst.PM2_MODULE_CONF_FILE)); } catch(e) { return null; } var values = splitKey(key); var exists = false; if (values.length > 1 && conf && conf[values[0]]) { exists = Object.keys(conf[values[0]]).some(function(key) { if (key == values[1]) return true; return false; }); } if (exists === false) return Configuration.setSync(key, value); return null; }; Configuration.setSync = function(key, value) { try { var data = fs.readFileSync(cst.PM2_MODULE_CONF_FILE); } catch(e) { return null; } var json_conf = JSON.parse(data); var values = splitKey(key); if (values.length > 0) { var levels = values; var tmp = json_conf; levels.forEach(function(key, index) { if (index == levels.length -1) tmp[key] = value; else if (!tmp[key]) { tmp[key] = {}; tmp = tmp[key]; } else { if (typeof(tmp[key]) != 'object') tmp[key] = {}; tmp = tmp[key]; } }); } else { if (json_conf[key] && typeof(json_conf[key]) === 'string') Common.printOut(cst.PREFIX_MSG + 'Replacing current value key %s by %s', key, value); json_conf[key] = value; } if (key === 'all') json_conf = {}; try { fs.writeFileSync(cst.PM2_MODULE_CONF_FILE, serializeConfiguration(json_conf)); return json_conf; } catch(e) { console.error(e.message); return null; } }; Configuration.unsetSync = function(key) { try { var data = fs.readFileSync(cst.PM2_MODULE_CONF_FILE); } catch(e) { return null; } var json_conf = JSON.parse(data); var values = splitKey(key); if (values.length > 0) { var levels = values; var tmp = json_conf; levels.forEach(function(key, index) { if (index == levels.length -1) delete tmp[key]; else if (!tmp[key]) { tmp[key] = {}; tmp = tmp[key]; } else { if (typeof(tmp[key]) != 'object') tmp[key] = {}; tmp = tmp[key]; } }); } else delete json_conf[key]; if (key === 'all') json_conf = {}; try { fs.writeFileSync(cst.PM2_MODULE_CONF_FILE, serializeConfiguration(json_conf)); } catch(e) { console.error(e.message); return null; } }; Configuration.multiset = function(serial, cb) { var arrays = []; serial = serial.match(/(?:[^ "]+|"[^"]*")+/g); while (serial.length > 0) arrays.push(serial.splice(0, 2)); eachSeries(arrays, function(el, next) { Configuration.set(el[0], el[1], next); }, cb); }; Configuration.get = function(key, cb) { Configuration.getAll(function(err, data) { var climb = splitKey(key); climb.some(function(val) { if (!data[val]) { data = null; return true; } data = data[val]; return false; }); if (!data) return cb({err : 'Unknown key'}, null); return cb(null, data); }); }; Configuration.getSync = function(key) { try { var data = Configuration.getAllSync(); } catch(e) { return null; } var climb = splitKey(key); climb.some(function(val) { if (!data[val]) { data = null; return true; } data = data[val]; return false; }); if (!data) return null; return data; }; Configuration.getAll = function(cb) { fs.readFile(cst.PM2_MODULE_CONF_FILE, function(err, data) { if (err) return cb(err); return cb(null, JSON.parse(data)); }); }; Configuration.getAllSync = function() { try { return JSON.parse(fs.readFileSync(cst.PM2_MODULE_CONF_FILE)); } catch(e) { console.error(e.stack || e); return {}; } }; ================================================ FILE: lib/Daemon.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ var debug = require('debug')('pm2:daemon'); var pkg = require('../package.json'); var cst = require('../constants.js'); var rpc = require('pm2-axon-rpc'); var axon = require('pm2-axon'); var domain = require('domain'); var Utility = require('./Utility.js'); var util = require('util'); var fs = require('fs'); var God = require('./God'); var eachLimit = require('async/eachLimit'); var fmt = require('./tools/fmt.js'); var semver = require('semver'); var Daemon = module.exports = function(opts) { if (!opts) opts = {}; this.ignore_signals = opts.ignore_signals || false; this.rpc_socket_ready = false; this.pub_socket_ready = false; this.pub_socket_file = opts.pub_socket_file || cst.DAEMON_PUB_PORT; this.rpc_socket_file = opts.rpc_socket_file || cst.DAEMON_RPC_PORT; this.pid_path = opts.pid_file || cst.PM2_PID_FILE_PATH; }; Daemon.prototype.start = function() { var that = this; var d = domain.create(); d.once('error', function(err) { fmt.sep(); fmt.title('PM2 global error caught'); fmt.field('Time', new Date()); console.error(err.message); console.error(err.stack); fmt.sep(); console.error('[PM2] Resurrecting PM2'); var path = cst.IS_WINDOWS ? __dirname + '/../bin/pm2' : process.env['_']; var fork_new_pm2 = require('child_process').spawn('node', [path, 'update'], { detached: true, windowsHide: true, stdio: 'inherit' }); fork_new_pm2.on('close', function() { console.log('PM2 successfully forked'); process.exit(0); }) }); d.run(function() { that.innerStart(); }); } Daemon.prototype.innerStart = function(cb) { var that = this; if (!cb) cb = function() { fmt.sep(); fmt.title('New PM2 Daemon started'); fmt.field('Time', new Date()); fmt.field('PM2 version', pkg.version); fmt.field('Node.js version', process.versions.node); fmt.field('Current arch', process.arch); fmt.field('PM2 home', cst.PM2_HOME); fmt.field('PM2 PID file', that.pid_path); fmt.field('RPC socket file', that.rpc_socket_file); fmt.field('BUS socket file', that.pub_socket_file); fmt.field('Application log path', cst.DEFAULT_LOG_PATH); fmt.field('Worker Interval', cst.WORKER_INTERVAL); fmt.field('Process dump file', cst.DUMP_FILE_PATH); fmt.field('Concurrent actions', cst.CONCURRENT_ACTIONS); fmt.field('SIGTERM timeout', cst.KILL_TIMEOUT); fmt.field('Runtime Binary', process.execPath); fmt.sep(); }; // Write Daemon PID into file try { fs.writeFileSync(that.pid_path, process.pid.toString()); } catch (e) { console.error(e.stack || e); } if (this.ignore_signals != true) this.handleSignals(); /** * Pub system for real time notifications */ this.pub = axon.socket('pub-emitter'); this.pub_socket = this.pub.bind(this.pub_socket_file); this.pub_socket.once('bind', function() { fs.chmod(that.pub_socket_file, '775', function(e) { if (e) console.error(e); try { if (process.env.PM2_SOCKET_USER && process.env.PM2_SOCKET_GROUP) fs.chown(that.pub_socket_file, parseInt(process.env.PM2_SOCKET_USER), parseInt(process.env.PM2_SOCKET_GROUP), function(e) { if (e) console.error(e); }); } catch(e) { console.error(e); } }); that.pub_socket_ready = true; that.sendReady(cb); }); /** * Rep/Req - RPC system to interact with God */ this.rep = axon.socket('rep'); var server = new rpc.Server(this.rep); this.rpc_socket = this.rep.bind(this.rpc_socket_file); this.rpc_socket.once('bind', function() { fs.chmod(that.rpc_socket_file, '775', function(e) { if (e) console.error(e); try { if (process.env.PM2_SOCKET_USER && process.env.PM2_SOCKET_GROUP) fs.chown(that.rpc_socket_file, parseInt(process.env.PM2_SOCKET_USER), parseInt(process.env.PM2_SOCKET_GROUP), function(e) { if (e) console.error(e); }); } catch(e) { console.error(e); } }); that.rpc_socket_ready = true; that.sendReady(cb); }); /** * Memory Snapshot */ function profile(type, msg, cb) { if (semver.satisfies(process.version, '< 8')) return cb(null, { error: 'Node.js is not on right version' }) var cmd if (type === 'cpu') { cmd = { enable: 'Profiler.enable', start: 'Profiler.start', stop: 'Profiler.stop', disable: 'Profiler.disable' } } if (type == 'mem') { cmd = { enable: 'HeapProfiler.enable', start: 'HeapProfiler.startSampling', stop: 'HeapProfiler.stopSampling', disable: 'HeapProfiler.disable' } } const inspector = require('inspector') var session = new inspector.Session() session.connect() var timeout = msg.timeout || 5000 session.post(cmd.enable, (err, data) => { if (err) return cb(null, { error: err.message || err }) console.log(`Starting ${cmd.start}`) session.post(cmd.start, (err, data) => { if (err) return cb(null, { error: err.message || err }) setTimeout(() => { session.post(cmd.stop, (err, data) => { if (err) return cb(null, { error: err.message || err }) const profile = data.profile console.log(`Stopping ${cmd.stop}`) session.post(cmd.disable) fs.writeFile(msg.pwd, JSON.stringify(profile), (err) => { if (err) return cb(null, { error: err.message || err }) return cb(null, { file : msg.pwd }) }) }) }, timeout) }) }) } server.expose({ killMe : that.close.bind(this), profileCPU : profile.bind(this, 'cpu'), profileMEM : profile.bind(this, 'mem'), prepare : God.prepare, getMonitorData : God.getMonitorData, startProcessId : God.startProcessId, stopProcessId : God.stopProcessId, restartProcessId : God.restartProcessId, deleteProcessId : God.deleteProcessId, sendLineToStdin : God.sendLineToStdin, softReloadProcessId : God.softReloadProcessId, reloadProcessId : God.reloadProcessId, duplicateProcessId : God.duplicateProcessId, resetMetaProcessId : God.resetMetaProcessId, stopWatch : God.stopWatch, startWatch : God.startWatch, toggleWatch : God.toggleWatch, notifyByProcessId : God.notifyByProcessId, notifyKillPM2 : God.notifyKillPM2, monitor : God.monitor, unmonitor : God.unmonitor, msgProcess : God.msgProcess, sendDataToProcessId : God.sendDataToProcessId, sendSignalToProcessId : God.sendSignalToProcessId, sendSignalToProcessName : God.sendSignalToProcessName, ping : God.ping, getVersion : God.getVersion, getReport : God.getReport, reloadLogs : God.reloadLogs }); this.startLogic(); } Daemon.prototype.close = function(opts, cb) { var that = this; God.bus.emit('pm2:kill', { status : 'killed', msg : 'pm2 has been killed via CLI' }); if (God.system_infos_proc !== null) God.system_infos_proc.kill() /** * Cleanly kill pm2 */ that.rpc_socket.close(function() { that.pub_socket.close(function() { // notify cli that the daemon is shuting down (only under unix since windows doesnt handle signals) if (cst.IS_WINDOWS === false) { try { process.kill(parseInt(opts.pid), 'SIGQUIT'); } catch(e) { console.error('Could not send SIGQUIT to CLI'); } } try { fs.unlinkSync(that.pid_path); } catch(e) {} console.log('PM2 successfully stopped'); setTimeout(function() { process.exit(cst.SUCCESS_EXIT); }, 2); }); }); } Daemon.prototype.handleSignals = function() { var that = this; process.on('SIGTERM', that.gracefullExit.bind(this)); process.on('SIGINT', that.gracefullExit.bind(this)); process.on('SIGHUP', function() {}); process.on('SIGQUIT', that.gracefullExit.bind(this)); process.on('SIGUSR2', function() { God.reloadLogs({}, function() {}); }); } Daemon.prototype.sendReady = function(cb) { // Send ready message to Client if (this.rpc_socket_ready == true && this.pub_socket_ready == true) { cb(null, { pid : process.pid, pm2_version : pkg.version }); if (typeof(process.send) != 'function') return false; process.send({ online : true, success : true, pid : process.pid, pm2_version : pkg.version }); }; } Daemon.prototype.gracefullExit = function() { var that = this; // never execute multiple gracefullExit simultaneously // this can lead to loss of some apps in dump file if (this.isExiting) return this.isExiting = true God.bus.emit('pm2:kill', { status : 'killed', msg : 'pm2 has been killed by SIGNAL' }); console.log('pm2 has been killed by signal, dumping process list before exit...'); if (God.system_infos_proc !== null) God.system_infos_proc.kill() God.dumpProcessList(function() { var processes = God.getFormatedProcesses(); eachLimit(processes, 1, function(proc, next) { console.log('Deleting process %s', proc.pm2_env.pm_id); God.deleteProcessId(proc.pm2_env.pm_id, function() { return next(); }); }, function(err) { try { fs.unlinkSync(that.pid_path); } catch(e) {} setTimeout(function() { that.isExiting = false console.log('Exited peacefully'); process.exit(cst.SUCCESS_EXIT); }, 2); }); }); } Daemon.prototype.startLogic = function() { var that = this; /** * Action treatment specifics * Attach actions to pm2_env.axm_actions variables (name + options) */ God.bus.on('axm:action', function axmActions(msg) { var pm2_env = msg.process; var exists = false; var axm_action = msg.data; if (!pm2_env || !God.clusters_db[pm2_env.pm_id]) return console.error('AXM ACTION Unknown id %s', pm2_env.pm_id); if (!God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions) God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions = []; God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions.forEach(function(actions) { if (actions.action_name == axm_action.action_name) exists = true; }); if (exists === false) { debug('Adding action', axm_action); God.clusters_db[pm2_env.pm_id].pm2_env.axm_actions.push(axm_action); } msg = null; }); /** * Configure module */ God.bus.on('axm:option:configuration', function axmMonitor(msg) { if (!msg.process) return console.error('[axm:option:configuration] no process defined'); if (!God.clusters_db[msg.process.pm_id]) return console.error('[axm:option:configuration] Unknown id %s', msg.process.pm_id); try { // Application Name nverride if (msg.data.name) God.clusters_db[msg.process.pm_id].pm2_env.name = msg.data.name; Object.keys(msg.data).forEach(function(conf_key) { God.clusters_db[msg.process.pm_id].pm2_env.axm_options[conf_key] = Utility.clone(msg.data[conf_key]); }); } catch(e) { console.error(e.stack || e); } msg = null; }); /** * Process monitoring data (probes) */ God.bus.on('axm:monitor', function axmMonitor(msg) { if (!msg.process) return console.error('[axm:monitor] no process defined'); if (!msg.process || !God.clusters_db[msg.process.pm_id]) return console.error('AXM MONITOR Unknown id %s', msg.process.pm_id); Object.assign(God.clusters_db[msg.process.pm_id].pm2_env.axm_monitor, Utility.clone(msg.data)); msg = null; }); /** * Broadcast messages */ God.bus.onAny(function(event, data_v) { if (['axm:action', 'axm:monitor', 'axm:option:setPID', 'axm:option:configuration'].indexOf(event) > -1) { data_v = null; return false; } that.pub.emit(event, Utility.clone(data_v)); data_v = null; }); }; if (require.main === module) { process.title = process.env.PM2_DAEMON_TITLE || 'PM2 v' + pkg.version + ': God Daemon (' + process.env.PM2_HOME + ')'; var daemon = new Daemon(); daemon.start(); } ================================================ FILE: lib/Event.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ var Utility = require('./Utility.js'); module.exports = function(God) { God.notify = function(action_name, data, manually) { God.bus.emit('process:event', { event : action_name, manually : typeof(manually) == 'undefined' ? false : true, process : Utility.formatCLU(data), at : Utility.getDate() }); }; God.notifyByProcessId = function(opts, cb) { if (typeof(opts.id) === 'undefined') { return cb(new Error('process id missing')); } var proc = God.clusters_db[opts.id]; if (!proc) { return cb(new Error('process id doesnt exists')); } God.bus.emit('process:event', { event : opts.action_name, manually : typeof(opts.manually) == 'undefined' ? false : true, process : Utility.formatCLU(proc), at : Utility.getDate() }); process.nextTick(function() { return cb ? cb(null) : false; }); return false; }; }; ================================================ FILE: lib/God/ActionMethods.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ 'use strict'; /** * @file ActionMethod like restart, stop, monitor... are here * @author Alexandre Strzelewicz * @project PM2 */ var fs = require('fs'); var path = require('path'); var eachLimit = require('async/eachLimit'); var os = require('os'); var p = path; var cst = require('../../constants.js'); var pkg = require('../../package.json'); var pidusage = require('pidusage'); var util = require('util'); var debug = require('debug')('pm2:ActionMethod'); var Utility = require('../Utility'); /** * Description * @method exports * @param {} God * @return */ module.exports = function(God) { /** * Description * @method getMonitorData * @param {} env * @param {} cb * @return */ God.getMonitorData = function getMonitorData(env, cb) { var processes = God.getFormatedProcesses(); var pids = processes.filter(filterBadProcess) .map(function(pro, i) { var pid = getProcessId(pro) return pid; }) // No pids, return empty statistics if (pids.length === 0) { return cb(null, processes.map(function(pro) { pro['monit'] = { memory : 0, cpu : 0 }; return pro })) } pidusage(pids, function retPidUsage(err, statistics) { // Just log, we'll set empty statistics if (err) { console.error('Error caught while calling pidusage'); console.error(err); return cb(null, processes.map(function(pro) { pro['monit'] = { memory : 0, cpu : 0 }; return pro })) } if (!statistics) { console.error('Statistics is not defined!') return cb(null, processes.map(function(pro) { pro['monit'] = { memory : 0, cpu : 0 }; return pro })) } processes = processes.map(function(pro) { if (filterBadProcess(pro) === false) { pro['monit'] = { memory : 0, cpu : 0 }; return pro; } var pid = getProcessId(pro); var stat = statistics[pid]; if (!stat) { pro['monit'] = { memory : 0, cpu : 0 }; return pro; } pro['monit'] = { memory: stat.memory, cpu: Math.round(stat.cpu * 10) / 10 }; return pro; }); cb(null, processes); }); }; /** * Description * @method dumpProcessList * @param {} cb * @return */ God.dumpProcessList = function(cb) { var process_list = []; var apps = Utility.clone(God.getFormatedProcesses()); var that = this; // Don't override the actual dump file if process list is empty // unless user explicitely did `pm2 dump`. // This often happens when PM2 crashed, we don't want to override // the dump file with an empty list of process. if (!apps[0]) { debug('[PM2] Did not override dump file because list of processes is empty'); return cb(null, {success:true, process_list: process_list}); } function fin(err) { // try to fix issues with empty dump file // like #3485 if (process_list.length === 0) { // fix : if no dump file, no process, only module and after pm2 update if (!fs.existsSync(cst.DUMP_FILE_PATH) && typeof that.clearDump === 'function') { that.clearDump(function(){}); } // if no process in list don't modify dump file // process list should not be empty return cb(null, {success:true, process_list: process_list}); } // Back up dump file try { if (fs.existsSync(cst.DUMP_FILE_PATH)) { fs.writeFileSync(cst.DUMP_BACKUP_FILE_PATH, fs.readFileSync(cst.DUMP_FILE_PATH)); } } catch (e) { console.error(e.stack || e); } // Overwrite dump file, delete if broken try { fs.writeFileSync(cst.DUMP_FILE_PATH, JSON.stringify(process_list)); } catch (e) { console.error(e.stack || e); try { // try to backup file if (fs.existsSync(cst.DUMP_BACKUP_FILE_PATH)) { fs.writeFileSync(cst.DUMP_FILE_PATH, fs.readFileSync(cst.DUMP_BACKUP_FILE_PATH)); } } catch (e) { // don't keep broken file fs.unlinkSync(cst.DUMP_FILE_PATH); console.error(e.stack || e); } } return cb(null, {success:true, process_list: process_list}); } function saveProc(apps) { if (!apps[0]) return fin(null); delete apps[0].pm2_env.instances; delete apps[0].pm2_env.pm_id; // Do not dump modules if (!apps[0].pm2_env.pmx_module) process_list.push(apps[0].pm2_env); apps.shift(); return saveProc(apps); } saveProc(apps); }; /** * Description * @method ping * @param {} env * @param {} cb * @return CallExpression */ God.ping = function(env, cb) { return cb(null, {msg : 'pong'}); }; /** * Description * @method notifyKillPM2 */ God.notifyKillPM2 = function() { God.pm2_being_killed = true; }; /** * Duplicate a process * @method duplicateProcessId * @param {} id * @param {} cb * @return CallExpression */ God.duplicateProcessId = function(id, cb) { if (!(id in God.clusters_db)) return cb(God.logAndGenerateError(id + ' id unknown'), {}); if (!God.clusters_db[id] || !God.clusters_db[id].pm2_env) return cb(God.logAndGenerateError('Error when getting proc || proc.pm2_env'), {}); var proc = Utility.clone(God.clusters_db[id].pm2_env); delete proc.created_at; delete proc.pm_id; delete proc.unique_id; // generate a new unique id for new process proc.unique_id = Utility.generateUUID() God.injectVariables(proc, function inject (_err, proc) { return God.executeApp(Utility.clone(proc), function (err, clu) { if (err) return cb(err); God.notify('start', clu, true); return cb(err, Utility.clone(clu)); }); }); }; /** * Start a stopped process by ID * @method startProcessId * @param {} id * @param {} cb * @return CallExpression */ God.startProcessId = function(id, cb) { if (!(id in God.clusters_db)) return cb(God.logAndGenerateError(id + ' id unknown'), {}); var proc = God.clusters_db[id]; if (proc.pm2_env.status == cst.ONLINE_STATUS) return cb(God.logAndGenerateError('process already online'), {}); if (proc.pm2_env.status == cst.LAUNCHING_STATUS) return cb(God.logAndGenerateError('process already started'), {}); if (proc.process && proc.process.pid) return cb(God.logAndGenerateError('Process with pid ' + proc.process.pid + ' already exists'), {}); return God.executeApp(God.clusters_db[id].pm2_env, function(err, proc) { return cb(err, Utility.clone(proc)); }); }; /** * Stop a process and set it on state 'stopped' * @method stopProcessId * @param {} id * @param {} cb * @return Literal */ God.stopProcessId = function(id, cb) { if (typeof id == 'object' && 'id' in id) id = id.id; if (!(id in God.clusters_db)) return cb(God.logAndGenerateError(id + ' : id unknown'), {}); var proc = God.clusters_db[id]; //clear time-out restart task clearTimeout(proc.pm2_env.restart_task); if (proc.pm2_env.status == cst.STOPPED_STATUS) { proc.process.pid = 0; return cb(null, God.getFormatedProcess(id)); } // state == 'none' means that the process is not online yet if (proc.state && proc.state === 'none') return setTimeout(function() { God.stopProcessId(id, cb); }, 250); console.log('Stopping app:%s id:%s', proc.pm2_env.name, proc.pm2_env.pm_id); proc.pm2_env.status = cst.STOPPING_STATUS; if (!proc.process.pid) { console.error('app=%s id=%d does not have a pid', proc.pm2_env.name, proc.pm2_env.pm_id); proc.pm2_env.status = cst.STOPPED_STATUS; return cb(null, { error : true, message : 'could not kill process w/o pid'}); } God.killProcess(proc.process.pid, proc.pm2_env, function(err) { proc.pm2_env.status = cst.STOPPED_STATUS; God.notify('exit', proc); if (err && err.type && err.type === 'timeout') { console.error('app=%s id=%d pid=%s could not be stopped', proc.pm2_env.name, proc.pm2_env.pm_id, proc.process.pid); proc.pm2_env.status = cst.ERRORED_STATUS; return cb(null, God.getFormatedProcess(id)); } if (proc.pm2_env.pm_id.toString().indexOf('_old_') !== 0) { try { fs.unlinkSync(proc.pm2_env.pm_pid_path); } catch (e) {} } if (proc.pm2_env.axm_actions) proc.pm2_env.axm_actions = []; if (proc.pm2_env.axm_monitor) proc.pm2_env.axm_monitor = {}; proc.process.pid = 0; return cb(null, God.getFormatedProcess(id)); }); }; God.resetMetaProcessId = function(id, cb) { if (!(id in God.clusters_db)) return cb(God.logAndGenerateError(id + ' id unknown'), {}); if (!God.clusters_db[id] || !God.clusters_db[id].pm2_env) return cb(God.logAndGenerateError('Error when getting proc || proc.pm2_env'), {}); God.clusters_db[id].pm2_env.created_at = Utility.getDate(); God.clusters_db[id].pm2_env.unstable_restarts = 0; God.clusters_db[id].pm2_env.restart_time = 0; return cb(null, God.getFormatedProcesses()); }; /** * Delete a process by id * It will stop it and remove it from the database * @method deleteProcessId * @param {} id * @param {} cb * @return Literal */ God.deleteProcessId = function(id, cb) { God.deleteCron(id); God.stopProcessId(id, function(err, proc) { if (err) return cb(God.logAndGenerateError(err), {}); // ! transform to slow object delete God.clusters_db[id]; if (Object.keys(God.clusters_db).length == 0) God.next_id = 0; return cb(null, proc); }); return false; }; /** * Restart a process ID * If the process is online it will not put it on state stopped * but directly kill it and let God restart it * @method restartProcessId * @param {} id * @param {} cb * @return Literal */ God.restartProcessId = function(opts, cb) { var id = opts.id; var env = opts.env || {}; if (typeof(id) === 'undefined') return cb(God.logAndGenerateError('opts.id not passed to restartProcessId', opts)); if (!(id in God.clusters_db)) return cb(God.logAndGenerateError('God db process id unknown'), {}); var proc = God.clusters_db[id]; God.resetState(proc.pm2_env); God.deleteCron(id); /** * Merge new application configuration on restart * Same system in reloadProcessId and softReloadProcessId */ Utility.extend(proc.pm2_env.env, env); Utility.extendExtraConfig(proc, opts); if (God.pm2_being_killed) { return cb(God.logAndGenerateError('[RestartProcessId] PM2 is being killed, stopping restart procedure...')); } if (proc.pm2_env.status === cst.ONLINE_STATUS || proc.pm2_env.status === cst.LAUNCHING_STATUS) { God.stopProcessId(id, function(err) { if (God.pm2_being_killed) return cb(God.logAndGenerateError('[RestartProcessId] PM2 is being killed, stopping restart procedure...')); proc.pm2_env.restart_time += 1; return God.startProcessId(id, cb); }); return false; } else { debug('[restart] process not online, starting it'); return God.startProcessId(id, cb); } }; /** * Restart all process by name * @method restartProcessName * @param {} name * @param {} cb * @return Literal */ God.restartProcessName = function(name, cb) { var processes = God.findByName(name); if (processes && processes.length === 0) return cb(God.logAndGenerateError('Unknown process'), {}); eachLimit(processes, cst.CONCURRENT_ACTIONS, function(proc, next) { if (God.pm2_being_killed) return next('[Watch] PM2 is being killed, stopping restart procedure...'); if (proc.pm2_env.status === cst.ONLINE_STATUS) return God.restartProcessId({id:proc.pm2_env.pm_id}, next); else if (proc.pm2_env.status !== cst.STOPPING_STATUS && proc.pm2_env.status !== cst.LAUNCHING_STATUS) return God.startProcessId(proc.pm2_env.pm_id, next); else return next(util.format('[Watch] Process name %s is being stopped so I won\'t restart it', name)); }, function(err) { if (err) return cb(God.logAndGenerateError(err)); return cb(null, God.getFormatedProcesses()); }); return false; }; /** * Send system signal to process id * @method sendSignalToProcessId * @param {} opts * @param {} cb * @return CallExpression */ God.sendSignalToProcessId = function(opts, cb) { var id = opts.process_id; var signal = opts.signal; if (!(id in God.clusters_db)) return cb(God.logAndGenerateError(id + ' id unknown'), {}); var proc = God.clusters_db[id]; //God.notify('send signal ' + signal, proc, true); try { process.kill(God.clusters_db[id].process.pid, signal); } catch(e) { return cb(God.logAndGenerateError('Error when sending signal (signal unknown)'), {}); } return cb(null, God.getFormatedProcesses()); }; /** * Send system signal to all processes by name * @method sendSignalToProcessName * @param {} opts * @param {} cb * @return */ God.sendSignalToProcessName = function(opts, cb) { var processes = God.findByName(opts.process_name); var signal = opts.signal; if (processes && processes.length === 0) return cb(God.logAndGenerateError('Unknown process name'), {}); eachLimit(processes, cst.CONCURRENT_ACTIONS, function(proc, next) { if (proc.pm2_env.status == cst.ONLINE_STATUS || proc.pm2_env.status == cst.LAUNCHING_STATUS) { try { process.kill(proc.process.pid, signal); } catch(e) { return next(e); } } return setTimeout(next, 200); }, function(err) { if (err) return cb(God.logAndGenerateError(err), {}); return cb(null, God.getFormatedProcesses()); }); }; /** * Stop watching daemon * @method stopWatch * @param {} method * @param {} value * @param {} fn * @return */ God.stopWatch = function(method, value, fn) { var env = null; if (method == 'stopAll' || method == 'deleteAll') { var processes = God.getFormatedProcesses(); processes.forEach(function(proc) { God.clusters_db[proc.pm_id].pm2_env.watch = false; God.watch.disable(proc.pm2_env); }); } else { if (method.indexOf('ProcessId') !== -1) { env = God.clusters_db[value]; } else if (method.indexOf('ProcessName') !== -1) { env = God.clusters_db[God.findByName(value)]; } if (env) { God.watch.disable(env.pm2_env); env.pm2_env.watch = false; } } return fn(null, {success:true}); }; /** * Toggle watching daemon * @method toggleWatch * @param {String} method * @param {Object} application environment, should include id * @param {Function} callback */ God.toggleWatch = function(method, value, fn) { var env = null; if (method == 'restartProcessId') { env = God.clusters_db[value.id]; } else if(method == 'restartProcessName') { env = God.clusters_db[God.findByName(value)]; } if (env) { env.pm2_env.watch = !env.pm2_env.watch; if (env.pm2_env.watch) God.watch.enable(env.pm2_env); else God.watch.disable(env.pm2_env); } return fn(null, {success:true}); }; /** * Start Watch * @method startWatch * @param {String} method * @param {Object} application environment, should include id * @param {Function} callback */ God.startWatch = function(method, value, fn) { var env = null; if (method == 'restartProcessId') { env = God.clusters_db[value.id]; } else if(method == 'restartProcessName') { env = God.clusters_db[God.findByName(value)]; } if (env) { if (env.pm2_env.watch) return fn(null, {success:true, notrestarted:true}); God.watch.enable(env.pm2_env); //env.pm2_env.env.watch = true; env.pm2_env.watch = true; } return fn(null, {success:true}); }; /** * Description * @method reloadLogs * @param {} opts * @param {} cb * @return CallExpression */ God.reloadLogs = function(opts, cb) { console.log('Reloading logs...'); var processIds = Object.keys(God.clusters_db); processIds.forEach(function (id) { var cluster = God.clusters_db[id]; console.log('Reloading logs for process id %d', id); if (cluster && cluster.pm2_env) { // Cluster mode if (cluster.send && cluster.pm2_env.exec_mode == 'cluster_mode') { try { cluster.send({ type:'log:reload' }); } catch(e) { console.error(e.message || e); } } // Fork mode else if (cluster._reloadLogs) { cluster._reloadLogs(function(err) { if (err) God.logAndGenerateError(err); }); } } }); return cb(null, {}); }; /** * Send Line To Stdin * @method sendLineToStdin * @param Object packet * @param String pm_id Process ID * @param String line Line to send to process stdin */ God.sendLineToStdin = function(packet, cb) { if (typeof(packet.pm_id) == 'undefined' || !packet.line) return cb(God.logAndGenerateError('pm_id or line field missing'), {}); var pm_id = packet.pm_id; var line = packet.line; var proc = God.clusters_db[pm_id]; if (!proc) return cb(God.logAndGenerateError('Process with ID <' + pm_id + '> unknown.'), {}); if (proc.pm2_env.exec_mode == 'cluster_mode') return cb(God.logAndGenerateError('Cannot send line to processes in cluster mode'), {}); if (proc.pm2_env.status != cst.ONLINE_STATUS && proc.pm2_env.status != cst.LAUNCHING_STATUS) return cb(God.logAndGenerateError('Process with ID <' + pm_id + '> offline.'), {}); try { proc.stdin.write(line, function() { return cb(null, { pm_id : pm_id, line : line }); }); } catch(e) { return cb(God.logAndGenerateError(e), {}); } } /** * @param {object} packet * @param {function} cb */ God.sendDataToProcessId = function(packet, cb) { if (typeof(packet.id) == 'undefined' || typeof(packet.data) == 'undefined' || !packet.topic) return cb(God.logAndGenerateError('ID, DATA or TOPIC field is missing'), {}); var pm_id = packet.id; var data = packet.data; var proc = God.clusters_db[pm_id]; if (!proc) return cb(God.logAndGenerateError('Process with ID <' + pm_id + '> unknown.'), {}); if (proc.pm2_env.status != cst.ONLINE_STATUS && proc.pm2_env.status != cst.LAUNCHING_STATUS) return cb(God.logAndGenerateError('Process with ID <' + pm_id + '> offline.'), {}); try { proc.send(packet); } catch(e) { return cb(God.logAndGenerateError(e), {}); } return cb(null, { success: true, data : packet }); }; /** * Send Message to Process by id or name * @method msgProcess * @param {} cmd * @param {} cb * @return Literal */ God.msgProcess = function(cmd, cb) { if ('id' in cmd) { var id = cmd.id; if (!(id in God.clusters_db)) return cb(God.logAndGenerateError(id + ' id unknown'), {}); var proc = God.clusters_db[id]; var action_exist = false; proc.pm2_env.axm_actions.forEach(function(action) { if (action.action_name == cmd.msg) { action_exist = true; // Reset output buffer action.output = []; } }); if (action_exist == false) { return cb(God.logAndGenerateError('Action doesn\'t exist ' + cmd.msg + ' for ' + proc.pm2_env.name), {}); } if (proc.pm2_env.status == cst.ONLINE_STATUS || proc.pm2_env.status == cst.LAUNCHING_STATUS) { /* * Send message */ if (cmd.opts == null && !cmd.uuid) proc.send(cmd.msg); else proc.send(cmd); return cb(null, { process_count : 1, success : true }); } else return cb(God.logAndGenerateError(id + ' : id offline'), {}); } else if ('name' in cmd) { /* * As names are not unique in case of cluster, this * will send msg to all process matching 'name' */ var name = cmd.name; var arr = Object.keys(God.clusters_db); var sent = 0; (function ex(arr) { if (arr[0] == null || !arr) { return cb(null, { process_count : sent, success : true }); } var id = arr[0]; if (!God.clusters_db[id] || !God.clusters_db[id].pm2_env) { arr.shift(); return ex(arr); } var proc_env = God.clusters_db[id].pm2_env; const isActionAvailable = proc_env.axm_actions.find(action => action.action_name === cmd.msg) !== undefined // if action doesn't exist for this app // try with the next one if (isActionAvailable === false) { arr.shift(); return ex(arr); } if ((p.basename(proc_env.pm_exec_path) == name || proc_env.name == name || proc_env.namespace == name || name == 'all') && (proc_env.status == cst.ONLINE_STATUS || proc_env.status == cst.LAUNCHING_STATUS)) { proc_env.axm_actions.forEach(function(action) { if (action.action_name == cmd.msg) { action_exist = true; } }); if (action_exist == false || proc_env.axm_actions.length == 0) { arr.shift(); return ex(arr); } if (cmd.opts == null) God.clusters_db[id].send(cmd.msg); else God.clusters_db[id].send(cmd); sent++; arr.shift(); return ex(arr); } else { arr.shift(); return ex(arr); } return false; })(arr); } else return cb(God.logAndGenerateError('method requires name or id field'), {}); return false; }; /** * Description * @method getVersion * @param {} env * @param {} cb * @return CallExpression */ God.getVersion = function(env, cb) { process.nextTick(function() { return cb(null, pkg.version); }); }; God.monitor = function Monitor(pm_id, cb) { if (!God.clusters_db[pm_id] || !God.clusters_db[pm_id].pm2_env) return cb(new Error('Unknown pm_id')); God.clusters_db[pm_id].pm2_env._km_monitored = true; return cb(null, { success : true, pm_id : pm_id }); } God.unmonitor = function Monitor(pm_id, cb) { if (!God.clusters_db[pm_id] || !God.clusters_db[pm_id].pm2_env) return cb(new Error('Unknown pm_id')); God.clusters_db[pm_id].pm2_env._km_monitored = false; return cb(null, { success : true, pm_id : pm_id }); } God.getReport = function(arg, cb) { var report = { pm2_version : pkg.version, node_version : 'N/A', node_path : process.env['_'] || 'not found', argv0 : process.argv0, argv : process.argv, user : process.env.USER, uid : (cst.IS_WINDOWS === false && process.geteuid) ? process.geteuid() : 'N/A', gid : (cst.IS_WINDOWS === false && process.getegid) ? process.getegid() : 'N/A', env : process.env, managed_apps : Object.keys(God.clusters_db).length, started_at : God.started_at }; if (process.versions && process.versions.node) { report.node_version = process.versions.node; } process.nextTick(function() { return cb(null, report); }); }; }; function filterBadProcess(pro) { if (pro.pm2_env.status !== cst.ONLINE_STATUS) { return false; } if (pro.pm2_env.axm_options && pro.pm2_env.axm_options.pid) { if (isNaN(pro.pm2_env.axm_options.pid)) { return false; } } return true; } function getProcessId(pro) { var pid = pro.pid if (pro.pm2_env.axm_options && pro.pm2_env.axm_options.pid) { pid = pro.pm2_env.axm_options.pid; } return pid } ================================================ FILE: lib/God/ClusterMode.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ 'use strict'; /** * @file Cluster execution functions related * @author Alexandre Strzelewicz * @project PM2 */ var cluster = require('cluster'); var Utility = require('../Utility.js'); var pkg = require('../../package.json'); /** * Description * @method exports * @param {} God * @return */ module.exports = function ClusterMode(God) { /** * For Node apps - Cluster mode * It will wrap the code and enable load-balancing mode * @method nodeApp * @param {} env_copy * @param {} cb * @return Literal */ God.nodeApp = function nodeApp(env_copy, cb){ var clu = null; console.log(`App [${env_copy.name}:${env_copy.pm_id}] starting in -cluster mode-`) if (env_copy.node_args && Array.isArray(env_copy.node_args)) { cluster.settings.execArgv = env_copy.node_args; } env_copy._pm2_version = pkg.version; try { // node.js cluster clients can not receive deep-level objects or arrays in the forked process, e.g.: // { "args": ["foo", "bar"], "env": { "foo1": "bar1" }} will be parsed to // { "args": "foo, bar", "env": "[object Object]"} // So we passing a stringified JSON here. clu = cluster.fork({pm2_env: JSON.stringify(env_copy), windowsHide: true}); } catch(e) { God.logAndGenerateError(e); return cb(e); } clu.pm2_env = env_copy; /** * Broadcast message to God */ clu.on('message', function cluMessage(msg) { /********************************* * If you edit this function * Do the same in ForkMode.js ! *********************************/ if (msg.data && msg.type) { return God.bus.emit(msg.type ? msg.type : 'process:msg', { at : Utility.getDate(), data : msg.data, process : { pm_id : clu.pm2_env.pm_id, name : clu.pm2_env.name, rev : (clu.pm2_env.versioning && clu.pm2_env.versioning.revision) ? clu.pm2_env.versioning.revision : null, namespace : clu.pm2_env.namespace } }); } else { if (typeof msg == 'object' && 'node_version' in msg) { clu.pm2_env.node_version = msg.node_version; return false; } return God.bus.emit('process:msg', { at : Utility.getDate(), raw : msg, process : { pm_id : clu.pm2_env.pm_id, name : clu.pm2_env.name, namespace : clu.pm2_env.namespace } }); } }); return cb(null, clu); }; }; ================================================ FILE: lib/God/ForkMode.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ 'use strict'; /** * @file Fork execution related functions * @author Alexandre Strzelewicz * @project PM2 */ var log = require('debug')('pm2:fork_mode'); var fs = require('fs'); var Utility = require('../Utility.js'); var path = require('path'); var dayjs = require('dayjs'); var semver = require('semver') var cst = require('../../constants.js'); /** * Description * @method exports * @param {} God * @return */ module.exports = function ForkMode(God) { /** * For all apps - FORK MODE * fork the app * @method forkMode * @param {} pm2_env * @param {} cb * @return */ God.forkMode = function forkMode(pm2_env, cb) { var command = ''; var args = []; console.log(`App [${pm2_env.name}:${pm2_env.pm_id}] starting in -fork mode-`) var spawn = require('child_process').spawn; var interpreter = pm2_env.exec_interpreter || process.execPath; var pidFile = pm2_env.pm_pid_path; if (interpreter !== 'none') { command = interpreter; if (pm2_env.node_args && Array.isArray(pm2_env.node_args)) { args = args.concat(pm2_env.node_args); } // Deprecated - to remove at some point if (process.env.PM2_NODE_OPTIONS) { args = args.concat(process.env.PM2_NODE_OPTIONS.split(' ')); } if (interpreter === 'node' || RegExp('node$').test(interpreter)) { args.push(path.resolve(path.dirname(module.filename), '..', 'ProcessContainerFork.js')); } else if (interpreter.includes('bun') === true) { args.push(path.resolve(path.dirname(module.filename), '..', 'ProcessContainerForkBun.js')); } else args.push(pm2_env.pm_exec_path); } else { command = pm2_env.pm_exec_path; args = [ ]; } if (pm2_env.args) { args = args.concat(pm2_env.args); } // piping stream o file var stds = { out: pm2_env.pm_out_log_path, err: pm2_env.pm_err_log_path }; // entire log std if necessary. if ('pm_log_path' in pm2_env){ stds.std = pm2_env.pm_log_path; } log("stds: %j", stds); Utility.startLogging(stds, function(err, result) { if (err) { God.logAndGenerateError(err); return cb(err); }; try { var options = { env : pm2_env, detached : true, cwd : pm2_env.pm_cwd || process.cwd(), stdio : ['pipe', 'pipe', 'pipe', 'ipc'] //Same as fork() in node core } if (typeof(pm2_env.windowsHide) === "boolean") { options.windowsHide = pm2_env.windowsHide; } else { options.windowsHide = true; } if (pm2_env.uid) { options.uid = pm2_env.uid } if (pm2_env.gid) { options.gid = pm2_env.gid } var cspr = spawn(command, args, options); } catch(e) { God.logAndGenerateError(e); return cb(e); } if (!cspr || !cspr.stderr || !cspr.stdout) { var fatalError = new Error('Process could not be forked properly, check your system health') God.logAndGenerateError(fatalError); return cb(fatalError); } cspr.process = {}; cspr.process.pid = cspr.pid; cspr.pm2_env = pm2_env; function transformLogToJson(pm2_env, type, data) { return JSON.stringify({ message : data.toString(), timestamp : pm2_env.log_date_format ? dayjs().format(pm2_env.log_date_format) : new Date().toISOString(), type : type, process_id : cspr.pm2_env.pm_id, app_name : cspr.pm2_env.name }) + '\n' } function prefixLogWithDate(pm2_env, data) { var log_data = [] log_data = data.toString().split('\n') if (log_data.length > 1) log_data.pop() log_data = log_data.map(line => `${dayjs().format(pm2_env.log_date_format)}: ${line}\n`) log_data = log_data.join('') return log_data } cspr.stderr.on('data', function forkErrData(data) { var log_data = null; // via --out /dev/null --err /dev/null if (pm2_env.disable_logs === true) return false; if (pm2_env.log_type && pm2_env.log_type === 'json') log_data = transformLogToJson(pm2_env, 'err', data) else if (pm2_env.log_date_format) log_data = prefixLogWithDate(pm2_env, data) else log_data = data.toString(); God.bus.emit('log:err', { process : { pm_id : cspr.pm2_env.pm_id, name : cspr.pm2_env.name, rev : (cspr.pm2_env.versioning && cspr.pm2_env.versioning.revision) ? cspr.pm2_env.versioning.revision : null, namespace : cspr.pm2_env.namespace }, at : Utility.getDate(), data : log_data }); if (Utility.checkPathIsNull(pm2_env.pm_err_log_path) && (!pm2_env.pm_log_path || Utility.checkPathIsNull(pm2_env.pm_log_path))) { return false; } stds.std && stds.std.write && stds.std.write(log_data); stds.err && stds.err.write && stds.err.write(log_data); }); cspr.stdout.on('data', function forkOutData(data) { var log_data = null; if (pm2_env.disable_logs === true) return false; if (pm2_env.log_type && pm2_env.log_type === 'json') log_data = transformLogToJson(pm2_env, 'out', data) else if (pm2_env.log_date_format) log_data = prefixLogWithDate(pm2_env, data) else log_data = data.toString() God.bus.emit('log:out', { process : { pm_id : cspr.pm2_env.pm_id, name : cspr.pm2_env.name, rev : (cspr.pm2_env.versioning && cspr.pm2_env.versioning.revision) ? cspr.pm2_env.versioning.revision : null, namespace : cspr.pm2_env.namespace }, at : Utility.getDate(), data : log_data }); if (Utility.checkPathIsNull(pm2_env.pm_out_log_path) && (!pm2_env.pm_log_path || Utility.checkPathIsNull(pm2_env.pm_log_path))) return false; stds.std && stds.std.write && stds.std.write(log_data); stds.out && stds.out.write && stds.out.write(log_data); }); /** * Broadcast message to God */ cspr.on('message', function forkMessage(msg) { /********************************* * If you edit this function * Do the same in ClusterMode.js ! *********************************/ if (msg.data && msg.type) { process.nextTick(function() { return God.bus.emit(msg.type ? msg.type : 'process:msg', { at : Utility.getDate(), data : msg.data, process : { pm_id : cspr.pm2_env.pm_id, name : cspr.pm2_env.name, versioning : cspr.pm2_env.versioning, namespace : cspr.pm2_env.namespace } }); }); } else { if (typeof msg == 'object' && 'node_version' in msg) { cspr.pm2_env.node_version = msg.node_version; return false; } return God.bus.emit('process:msg', { at : Utility.getDate(), raw : msg, process : { pm_id : cspr.pm2_env.pm_id, name : cspr.pm2_env.name, namespace : cspr.pm2_env.namespace } }); } }); try { var pid = cspr.pid if (typeof(pid) !== 'undefined') fs.writeFileSync(pidFile, pid.toString()); } catch (e) { console.error(e.stack || e); } cspr.once('exit', function forkClose(status) { try { for(var k in stds){ if (stds[k] && stds[k].destroy) stds[k].destroy(); else if (stds[k] && stds[k].end) stds[k].end(); else if (stds[k] && stds[k].close) stds[k].close(); stds[k] = stds[k]._file; } } catch(e) { God.logAndGenerateError(e);} }); cspr._reloadLogs = function(cb) { try { for (var k in stds){ if (stds[k] && stds[k].destroy) stds[k].destroy(); else if (stds[k] && stds[k].end) stds[k].end(); else if (stds[k] && stds[k].close) stds[k].close(); stds[k] = stds[k]._file; } } catch(e) { God.logAndGenerateError(e);} //cspr.removeAllListeners(); Utility.startLogging(stds, cb); }; cspr.unref(); return cb(null, cspr); }); }; }; ================================================ FILE: lib/God/Methods.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ 'use strict'; /** * @file Utilities for PM2 * @author Alexandre Strzelewicz * @project PM2 */ var p = require('path'); var treekill = require('../TreeKill'); var cst = require('../../constants.js'); /** * Description * @method exports * @param {} God * @return */ module.exports = function(God) { /** * Description * @method logAndGenerateError * @param {} err * @return NewExpression */ God.logAndGenerateError = function(err) { // Is an Error object if (err instanceof Error) { console.trace(err); return err; } // Is a JSON or simple string console.error(err); return new Error(err); }; /** * Utility functions * @method getProcesses * @return MemberExpression */ God.getProcesses = function() { return God.clusters_db; }; God.getFormatedProcess = function getFormatedProcesses(id) { if (God.clusters_db[id]) return { pid : God.clusters_db[id].process.pid, name : God.clusters_db[id].pm2_env.name, pm2_env : God.clusters_db[id].pm2_env, pm_id : God.clusters_db[id].pm2_env.pm_id }; return {}; }; /** * Get formated processes * @method getFormatedProcesses * @return {Array} formated processes */ God.getFormatedProcesses = function getFormatedProcesses() { var keys = Object.keys(God.clusters_db); var arr = new Array(); var kl = keys.length; for (var i = 0; i < kl; i++) { var key = keys[i]; if (!God.clusters_db[key]) continue; // Avoid _old type pm_ids if (isNaN(God.clusters_db[key].pm2_env.pm_id)) continue; arr.push({ pid : God.clusters_db[key].process.pid, name : God.clusters_db[key].pm2_env.name, pm2_env : God.clusters_db[key].pm2_env, pm_id : God.clusters_db[key].pm2_env.pm_id }) } return arr; }; /** * Description * @method findProcessById * @param {} id * @return ConditionalExpression */ God.findProcessById = function findProcessById(id) { return God.clusters_db[id] ? God.clusters_db[id] : null; }; /** * Description * @method findByName * @param {} name * @return arr */ God.findByName = function(name) { var db = God.clusters_db; var arr = []; if (name == 'all') { for (var key in db) { // Avoid _old_proc process style if (typeof(God.clusters_db[key].pm2_env.pm_id) === 'number') arr.push(db[key]); } return arr; } for (var key in db) { if (God.clusters_db[key].pm2_env.name == name || God.clusters_db[key].pm2_env.pm_exec_path == p.resolve(name)) { arr.push(db[key]); } } return arr; }; /** * Check if a process is alive in system processes * Return TRUE if process online * @method checkProcess * @param {} pid * @return */ God.checkProcess = function(pid) { if (!pid) return false; try { // Sending 0 signal do not kill the process process.kill(pid, 0); return true; } catch (err) { return false; } }; /** * Description * @method processIsDead * @param {} pid * @param {} cb * @return Literal */ God.processIsDead = function(pid, pm2_env, cb, sigkill) { if (!pid) return cb({type : 'param:missing', msg : 'no pid passed'}); var timeout = null; var kill_timeout = (pm2_env && pm2_env.kill_timeout) ? pm2_env.kill_timeout : cst.KILL_TIMEOUT; var mode = pm2_env.exec_mode; var timer = setInterval(function() { if (God.checkProcess(pid) === false) { console.log('pid=%d msg=process killed', pid); clearTimeout(timeout); clearInterval(timer); return cb(null, true); } console.log('pid=%d msg=failed to kill - retrying in %dms', pid, pm2_env.kill_retry_time); return false; }, pm2_env.kill_retry_time); timeout = setTimeout(function() { clearInterval(timer); if (sigkill) { console.log('Process with pid %d could not be killed', pid); return cb({type : 'timeout', msg : 'timeout'}); } else { console.log('Process with pid %d still alive after %sms, sending it SIGKILL now...', pid, kill_timeout); if (pm2_env.treekill !== true) { try { process.kill(parseInt(pid), 'SIGKILL'); } catch(e) { console.error('[SimpleKill][SIGKILL] %s pid can not be killed', pid, e.stack, e.message); } return God.processIsDead(pid, pm2_env, cb, true); } else { treekill(parseInt(pid), 'SIGKILL', function(err) { return God.processIsDead(pid, pm2_env, cb, true); }); } } }, kill_timeout); return false; }; /** * Description * @method killProcess * @param int pid * @param Object pm2_env * @param function cb * @return CallExpression */ God.killProcess = function(pid, pm2_env, cb) { if (!pid) return cb({msg : 'no pid passed or null'}); if (typeof(pm2_env.pm_id) === 'number' && (cst.KILL_USE_MESSAGE || pm2_env.shutdown_with_message == true)) { var proc = God.clusters_db[pm2_env.pm_id]; if (proc && proc.send) { try { proc.send('shutdown'); } catch (e) { console.error(`[AppKill] Cannot send "shutdown" message to ${pid}`) console.error(e.stack, e.message) } return God.processIsDead(pid, pm2_env, cb); } else { console.log(`[AppKill] ${pid} pid cannot be notified with send()`) } } if (pm2_env.treekill !== true) { try { process.kill(parseInt(pid), cst.KILL_SIGNAL); } catch(e) { console.error('[SimpleKill] %s pid can not be killed', pid, e.stack, e.message); } return God.processIsDead(pid, pm2_env, cb); } else { treekill(parseInt(pid), cst.KILL_SIGNAL, function(err) { return God.processIsDead(pid, pm2_env, cb); }); } }; /** * Description * @method getNewId * @return UpdateExpression */ God.getNewId = function() { return God.next_id++; }; /** * When a process is restarted or reloaded reset fields * to monitor unstable starts * @method resetState * @param {} pm2_env * @return */ God.resetState = function(pm2_env) { pm2_env.created_at = Date.now(); pm2_env.unstable_restarts = 0; pm2_env.prev_restart_delay = 0; }; }; ================================================ FILE: lib/God/Reload.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ 'use strict'; /** * @file Reload functions related * @author Alexandre Strzelewicz * @project PM2 */ var cst = require('../../constants.js'); var Utility = require('../Utility.js'); /** * softReload will wait permission from process to exit * @method softReload * @param {} God * @param {} id * @param {} cb * @return Literal */ function softReload(God, id, cb) { var t_key = '_old_' + id; // Move old worker to tmp id God.clusters_db[t_key] = God.clusters_db[id]; delete God.clusters_db[id]; var old_worker = God.clusters_db[t_key]; // Deep copy var new_env = Utility.clone(old_worker.pm2_env); // Reset created_at and unstable_restarts God.resetState(new_env); new_env.restart_time += 1; old_worker.pm2_env.pm_id = t_key; old_worker.pm_id = t_key; God.executeApp(new_env, function(err, new_worker) { if (err) return cb(err); var timer = null; var onListen = function () { clearTimeout(timer); softCleanDeleteProcess(); console.log('-softReload- New worker listening'); }; // Bind to know when the new process is up new_worker.once('listening', onListen); timer = setTimeout(function() { new_worker.removeListener('listening', onListen); softCleanDeleteProcess(); }, new_env.listen_timeout || cst.GRACEFUL_LISTEN_TIMEOUT); // Remove old worker properly var softCleanDeleteProcess = function () { var cleanUp = function () { clearTimeout(timer); console.log('-softReload- Old worker disconnected'); return God.deleteProcessId(t_key, cb); }; old_worker.once('disconnect', cleanUp); try { if (old_worker.state != 'dead' && old_worker.state != 'disconnected') old_worker.send && old_worker.send('shutdown'); else { clearTimeout(timer); console.error('Worker %d is already disconnected', old_worker.pm2_env.pm_id); return God.deleteProcessId(t_key, cb); } } catch(e) { clearTimeout(timer); console.error('Worker %d is already disconnected', old_worker.pm2_env.pm_id); return God.deleteProcessId(t_key, cb); } timer = setTimeout(function () { old_worker.removeListener('disconnect', cleanUp); return God.deleteProcessId(t_key, cb); }, cst.GRACEFUL_TIMEOUT); return false; }; return false; }); return false; }; /** * hardReload will reload without waiting permission from process * @method hardReload * @param {} God * @param {} id * @param {} cb * @return Literal */ function hardReload(God, id, wait_msg, cb) { var t_key = '_old_' + id; // Move old worker to tmp id God.clusters_db[t_key] = God.clusters_db[id]; delete God.clusters_db[id]; var old_worker = God.clusters_db[t_key]; // Deep copy var new_env = Utility.clone(old_worker.pm2_env); new_env.restart_time += 1; // Reset created_at and unstable_restarts God.resetState(new_env); old_worker.pm2_env.pm_id = t_key; old_worker.pm_id = t_key; var timer = null; var readySignalSent = false; var onListen = function () { clearTimeout(timer); readySignalSent = true; console.log('-reload- New worker listening'); return God.deleteProcessId(t_key, cb); }; var listener = function (packet) { if (packet.raw === 'ready' && packet.process.name === old_worker.pm2_env.name && packet.process.pm_id === id) { God.bus.removeListener('process:msg', listener); return onListen(); } }; if (wait_msg !== 'listening') { God.bus.on('process:msg', listener); } God.executeApp(new_env, function(err, new_worker) { if (err) return cb(err); // Bind to know when the new process is up if (wait_msg === 'listening') { new_worker.once('listening', onListen); } timer = setTimeout(function() { if (readySignalSent) { return; } if (wait_msg === 'listening') new_worker.removeListener(wait_msg, onListen); else God.bus.removeListener('process:msg', listener); return God.deleteProcessId(t_key, cb); }, new_env.listen_timeout || cst.GRACEFUL_LISTEN_TIMEOUT); return false; }); return false; }; /** * Description * @method exports * @param {} God * @return */ module.exports = function(God) { /** * Reload * @method softReloadProcessId * @param {} id * @param {} cb * @return CallExpression */ God.softReloadProcessId = function(opts, cb) { var id = opts.id; var env = opts.env || {}; if (!(id in God.clusters_db)) return cb(new Error(`pm_id ${id} not available in ${id}`)); if (God.clusters_db[id].pm2_env.status == cst.ONLINE_STATUS && God.clusters_db[id].pm2_env.exec_mode == 'cluster_mode' && !God.clusters_db[id].pm2_env.wait_ready) { Utility.extend(God.clusters_db[id].pm2_env.env, opts.env); Utility.extendExtraConfig(God.clusters_db[id], opts); return softReload(God, id, cb); } else { console.log('Process %s in a stopped status, starting it', id); return God.restartProcessId(opts, cb); } }; /** * Reload * @method reloadProcessId * @param {} id * @param {} cb * @return CallExpression */ God.reloadProcessId = function(opts, cb) { var id = opts.id; var env = opts.env || {}; if (!(id in God.clusters_db)) return cb(new Error('PM2 ID unknown')); if (God.clusters_db[id].pm2_env.status == cst.ONLINE_STATUS && God.clusters_db[id].pm2_env.exec_mode == 'cluster_mode') { Utility.extend(God.clusters_db[id].pm2_env.env, opts.env); Utility.extendExtraConfig(God.clusters_db[id], opts); var wait_msg = God.clusters_db[id].pm2_env.wait_ready ? 'ready' : 'listening'; return hardReload(God, id, wait_msg, cb); } else { console.log('Process %s in a stopped status, starting it', id); return God.restartProcessId(opts, cb); } }; }; ================================================ FILE: lib/God.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ /****************************** * ______ _______ ______ * | __ \ | |__ | * | __/ | __| * |___| |__|_|__|______| * * Main Daemon side file * ******************************/ var cluster = require('cluster'); var numCPUs = require('os').cpus() ? require('os').cpus().length : 1; var path = require('path'); var EventEmitter2 = require('eventemitter2').EventEmitter2; var fs = require('fs'); var vizion = require('vizion'); var debug = require('debug')('pm2:god'); var Utility = require('./Utility'); var cst = require('../constants.js'); var timesLimit = require('async/timesLimit'); var Configuration = require('./Configuration.js'); /** * Override cluster module configuration */ if (cst.IS_BUN == true) { cluster.setupMaster({ windowsHide: true, exec : path.resolve(path.dirname(module.filename), 'ProcessContainerBun.js') }); } else { cluster.setupMaster({ windowsHide: true, exec : path.resolve(path.dirname(module.filename), 'ProcessContainer.js') }); } /** * Expose God */ var God = module.exports = { next_id : 0, clusters_db : {}, configuration: {}, started_at : Date.now(), system_infos_proc: null, system_infos: null, bus : new EventEmitter2({ wildcard: true, delimiter: ':', maxListeners: 1000 }) }; Utility.overrideConsole(God.bus); /** * Populate God namespace */ require('./Event.js')(God); require('./God/Methods.js')(God); require('./God/ForkMode.js')(God); require('./God/ClusterMode.js')(God); require('./God/Reload')(God); require('./God/ActionMethods')(God); require('./Watcher')(God); God.init = function() { require('./Worker.js')(this) God.system_infos_proc = null this.configuration = Configuration.getSync('pm2') setTimeout(function() { God.Worker.start() }, 500) } God.writeExitSeparator = function(pm2_env, code, signal) { try { var exit_sep = `[PM2][${new Date().toISOString()}] app exited` if (code) exit_sep += `itself with exit code: ${code}` if (signal) exit_sep += `by an external signal: ${signal}` exit_sep += '\n' if (pm2_env.pm_out_log_path) fs.writeFileSync(pm2_env.pm_out_log_path, exit_sep) if (pm2_env.pm_err_log_path) fs.writeFileSync(pm2_env.pm_err_log_path, exit_sep) if (pm2_env.pm_log_path) fs.writeFileSync(pm2_env.pm_log_path, exit_sep) } catch(e) { } } /** * Init new process */ God.prepare = function prepare (env, cb) { // generate a new unique id for each processes env.env.unique_id = Utility.generateUUID() // if the app is standalone, no multiple instance if (typeof env.instances === 'undefined') { env.vizion_running = false; if (env.env && env.env.vizion_running) env.env.vizion_running = false; if (env.status == cst.STOPPED_STATUS) { env.pm_id = God.getNewId() var clu = { pm2_env : env, process: { } } God.clusters_db[env.pm_id] = clu God.registerCron(env) return cb(null, [ God.clusters_db[env.pm_id] ]) } return God.executeApp(env, function (err, clu) { if (err) return cb(err); God.notify('start', clu, true); return cb(null, [ Utility.clone(clu) ]); }); } // find how many replicate the user want env.instances = parseInt(env.instances); if (env.instances === 0) { env.instances = numCPUs; } else if (env.instances < 0) { env.instances += numCPUs; } if (env.instances <= 0) { env.instances = 1; } timesLimit(env.instances, 1, function (n, next) { env.vizion_running = false; if (env.env && env.env.vizion_running) { env.env.vizion_running = false; } God.injectVariables(env, function inject (err, _env) { if (err) return next(err); return God.executeApp(Utility.clone(_env), function (err, clu) { if (err) return next(err); God.notify('start', clu, true); // here call next wihtout an array because // async.times aggregate the result into an array return next(null, Utility.clone(clu)); }); }); }, cb); }; /** * Launch the specified script (present in env) * @api private * @method executeApp * @param {Mixed} env * @param {Function} cb * @return Literal */ God.executeApp = function executeApp(env, cb) { var env_copy = Utility.clone(env); Utility.extend(env_copy, env_copy.env); env_copy['status'] = env.autostart ? cst.LAUNCHING_STATUS : cst.STOPPED_STATUS; env_copy['pm_uptime'] = Date.now(); env_copy['axm_actions'] = []; env_copy['axm_monitor'] = {}; env_copy['axm_options'] = {}; env_copy['axm_dynamic'] = {}; env_copy['vizion_running'] = env_copy['vizion_running'] !== undefined ? env_copy['vizion_running'] : false; if (!env_copy.created_at) env_copy['created_at'] = Date.now(); /** * Enter here when it's the first time that the process is created * 1 - Assign a new id * 2 - Reset restart time and unstable_restarts * 3 - Assign a log file name depending on the id * 4 - If watch option is set, look for changes */ if (env_copy['pm_id'] === undefined) { env_copy['pm_id'] = God.getNewId(); env_copy['restart_time'] = 0; env_copy['unstable_restarts'] = 0; // add -pm_id to pid file env_copy.pm_pid_path = env_copy.pm_pid_path.replace(/-[0-9]+\.pid$|\.pid$/g, '-' + env_copy['pm_id'] + '.pid'); // If merge option, dont separate the logs if (!env_copy['merge_logs']) { ['', '_out', '_err'].forEach(function(k){ var key = 'pm' + k + '_log_path'; env_copy[key] && (env_copy[key] = env_copy[key].replace(/-[0-9]+\.log$|\.log$/g, '-' + env_copy['pm_id'] + '.log')); }); } // Initiate watch file if (env_copy['watch']) { God.watch.enable(env_copy); } } God.registerCron(env_copy) if (env_copy['autostart'] === false) { var clu = {pm2_env: env_copy, process: {pid: 0}}; God.clusters_db[env_copy.pm_id] = clu; return cb(null, clu); } var cb_called = false /** Callback when application is launched */ var readyCb = function ready(proc) { cb_called = true proc.pm2_env.version = Utility.findPackageVersion(proc.pm2_env.pm_exec_path || proc.pm2_env.cwd); // If vizion enabled run versioning retrieval system if (cst.ENABLE_GIT_PARSING === true && proc.pm2_env.vizion !== false && proc.pm2_env.vizion !== "false") { God.finalizeProcedure(proc); } else God.notify('online', proc); if (proc.pm2_env.status !== cst.ERRORED_STATUS) proc.pm2_env.status = cst.ONLINE_STATUS console.log(`App [${proc.pm2_env.name}:${proc.pm2_env.pm_id}] online`); if (cb) cb(null, proc); } if (env_copy.exec_mode === 'cluster_mode') { /** * Cluster mode logic (for NodeJS apps) */ God.nodeApp(env_copy, function nodeApp(err, clu) { if (cb && err) return cb(err); if (err) return false; var old_env = God.clusters_db[clu.pm2_env.pm_id]; if (old_env) { old_env = null; God.clusters_db[clu.pm2_env.pm_id] = null; } God.clusters_db[clu.pm2_env.pm_id] = clu; if (cst.IS_BUN) { // When starting an app that does not listen on a port // Bun do not call 'online' event // This is a temporary workaround var a = setTimeout(() => { if (clu.pm2_env) God.clusters_db[clu.pm2_env.pm_id].state = 'online' return readyCb(clu) }, 500) } clu.once('error', function(err) { if (cst.IS_BUN) clearTimeout(a) console.error(err.stack || err); try { clu.destroy && clu.destroy(); } catch (e) { console.error(e.stack || e); God.handleExit(clu, cst.ERROR_EXIT); } }); clu.once('disconnect', function() { if (cst.IS_BUN) clearTimeout(a) console.log('App name:%s id:%s disconnected', clu.pm2_env.name, clu.pm2_env.pm_id); }); clu.once('exit', function cluExit(code, signal) { if (cst.IS_BUN) { clearTimeout(a) if (cb_called == false) readyCb(clu); } //God.writeExitSeparator(clu.pm2_env, code, signal) God.handleExit(clu, code || 0, signal || 'SIGINT'); }); return clu.once('online', function () { if (cst.IS_BUN) { clearTimeout(a); } if (!clu.pm2_env.wait_ready) return readyCb(clu); // Timeout if the ready message has not been sent before listen_timeout var ready_timeout = setTimeout(function() { God.bus.removeListener('process:msg', listener) return readyCb(clu) }, clu.pm2_env.listen_timeout || cst.GRACEFUL_LISTEN_TIMEOUT); var listener = function (packet) { if (packet.raw === 'ready' && packet.process.name === clu.pm2_env.name && packet.process.pm_id === clu.pm2_env.pm_id) { clearTimeout(ready_timeout); God.bus.removeListener('process:msg', listener) return readyCb(clu) } } God.bus.on('process:msg', listener); }); }); } else { /** * Fork mode logic */ God.forkMode(env_copy, function forkMode(err, clu) { if (cb && err) return cb(err); if (err) return false; var old_env = God.clusters_db[clu.pm2_env.pm_id]; if (old_env) old_env = null; God.clusters_db[env_copy.pm_id] = clu; clu.once('error', function cluError(err) { console.error(err.stack || err); try { clu.kill && clu.kill(); } catch (e) { console.error(e.stack || e); God.handleExit(clu, cst.ERROR_EXIT); } }); clu.once('exit', function cluClose(code, signal) { //God.writeExitSeparator(clu.pm2_env, code, signal) if (clu.connected === true) clu.disconnect && clu.disconnect(); clu._reloadLogs = null; return God.handleExit(clu, code || 0, signal); }); if (!clu.pm2_env.wait_ready) return readyCb(clu); // Timeout if the ready message has not been sent before listen_timeout var ready_timeout = setTimeout(function() { God.bus.removeListener('process:msg', listener) return readyCb(clu) }, clu.pm2_env.listen_timeout || cst.GRACEFUL_LISTEN_TIMEOUT); var listener = function (packet) { if (packet.raw === 'ready' && packet.process.name === clu.pm2_env.name && packet.process.pm_id === clu.pm2_env.pm_id) { clearTimeout(ready_timeout); God.bus.removeListener('process:msg', listener) return readyCb(clu) } } God.bus.on('process:msg', listener); }); } return false; }; /** * Handle logic when a process exit (Node or Fork) * @method handleExit * @param {} clu * @param {} exit_code * @return */ God.handleExit = function handleExit(clu, exit_code, kill_signal) { console.log(`App [${clu.pm2_env.name}:${clu.pm2_env.pm_id}] exited with code [${exit_code}] via signal [${kill_signal || 'SIGINT'}]`) var proc = this.clusters_db[clu.pm2_env.pm_id]; if (!proc) { console.error('Process undefined ? with process id ', clu.pm2_env.pm_id); return false; } var stopExitCodes = proc.pm2_env.stop_exit_codes !== undefined && proc.pm2_env.stop_exit_codes !== null ? proc.pm2_env.stop_exit_codes : []; if (!Array.isArray(stopExitCodes)) { stopExitCodes = [stopExitCodes]; } var stopping = (proc.pm2_env.status == cst.STOPPING_STATUS || proc.pm2_env.status == cst.STOPPED_STATUS || proc.pm2_env.status == cst.ERRORED_STATUS) || (proc.pm2_env.autorestart === false || proc.pm2_env.autorestart === "false") || (stopExitCodes.map((strOrNum) => typeof strOrNum === 'string' ? parseInt(strOrNum, 10) : strOrNum) .includes(exit_code)); var overlimit = false; if (stopping) proc.process.pid = 0; // Reset probes and actions if (proc.pm2_env.axm_actions) proc.pm2_env.axm_actions = []; if (proc.pm2_env.axm_monitor) proc.pm2_env.axm_monitor = {}; if (proc.pm2_env.status != cst.ERRORED_STATUS && proc.pm2_env.status != cst.STOPPING_STATUS) proc.pm2_env.status = cst.STOPPED_STATUS; if (proc.pm2_env.pm_id.toString().indexOf('_old_') !== 0) { try { fs.unlinkSync(proc.pm2_env.pm_pid_path); } catch (e) { debug('Error when unlinking pid file', e); } } /** * Avoid infinite reloop if an error is present */ // If the process has been created less than 15seconds ago // And if the process has an uptime less than a second var min_uptime = typeof(proc.pm2_env.min_uptime) !== 'undefined' ? proc.pm2_env.min_uptime : 1000; var max_restarts = typeof(proc.pm2_env.max_restarts) !== 'undefined' ? proc.pm2_env.max_restarts : 16; if ((Date.now() - proc.pm2_env.created_at) < (min_uptime * max_restarts)) { if ((Date.now() - proc.pm2_env.pm_uptime) < min_uptime) { // Increment unstable restart proc.pm2_env.unstable_restarts += 1; } } if (proc.pm2_env.unstable_restarts >= max_restarts) { // Too many unstable restart in less than 15 seconds // Set the process as 'ERRORED' // And stop restarting it proc.pm2_env.status = cst.ERRORED_STATUS; proc.process.pid = 0; console.log('Script %s had too many unstable restarts (%d). Stopped. %j', proc.pm2_env.pm_exec_path, proc.pm2_env.unstable_restarts, proc.pm2_env.status); God.notify('restart overlimit', proc); proc.pm2_env.unstable_restarts = 0; proc.pm2_env.created_at = null; overlimit = true; } if (typeof(exit_code) !== 'undefined') proc.pm2_env.exit_code = exit_code; God.notify('exit', proc); if (God.pm2_being_killed) { //console.log('[HandleExit] PM2 is being killed, stopping restart procedure...'); return false; } var restart_delay = 0; if (proc.pm2_env.restart_delay !== undefined && !isNaN(parseInt(proc.pm2_env.restart_delay))) { proc.pm2_env.status = cst.WAITING_RESTART; restart_delay = parseInt(proc.pm2_env.restart_delay); } if (proc.pm2_env.exp_backoff_restart_delay !== undefined && !isNaN(parseInt(proc.pm2_env.exp_backoff_restart_delay))) { proc.pm2_env.status = cst.WAITING_RESTART; if (!proc.pm2_env.prev_restart_delay) { proc.pm2_env.prev_restart_delay = proc.pm2_env.exp_backoff_restart_delay restart_delay = proc.pm2_env.exp_backoff_restart_delay } else { proc.pm2_env.prev_restart_delay = Math.floor(Math.min(15000, proc.pm2_env.prev_restart_delay * 1.5)) restart_delay = proc.pm2_env.prev_restart_delay } console.log(`App [${clu.pm2_env.name}:${clu.pm2_env.pm_id}] will restart in ${restart_delay}ms`) } if (!stopping && !overlimit) { //make this property unenumerable Object.defineProperty(proc.pm2_env, 'restart_task', {configurable: true, writable: true}); proc.pm2_env.restart_task = setTimeout(function() { proc.pm2_env.restart_time += 1; God.executeApp(proc.pm2_env); }, restart_delay); } return false; }; /** * @method finalizeProcedure * @param proc {Object} * @return */ God.finalizeProcedure = function finalizeProcedure(proc) { var last_path = ''; var current_path = proc.pm2_env.cwd || path.dirname(proc.pm2_env.pm_exec_path); var proc_id = proc.pm2_env.pm_id; proc.pm2_env.version = Utility.findPackageVersion(proc.pm2_env.pm_exec_path || proc.pm2_env.cwd); if (proc.pm2_env.vizion_running === true) { debug('Vizion is already running for proc id: %d, skipping this round', proc_id); return God.notify('online', proc); } proc.pm2_env.vizion_running = true; vizion.analyze({folder : current_path}, function recur_path(err, meta){ var proc = God.clusters_db[proc_id]; if (err) debug(err.stack || err); if (!proc || !proc.pm2_env || proc.pm2_env.status == cst.STOPPED_STATUS || proc.pm2_env.status == cst.STOPPING_STATUS || proc.pm2_env.status == cst.ERRORED_STATUS) { return console.error('Cancelling versioning data parsing'); } proc.pm2_env.vizion_running = false; if (!err) { proc.pm2_env.versioning = meta; proc.pm2_env.versioning.repo_path = current_path; God.notify('online', proc); } else if (err && current_path === last_path) { proc.pm2_env.versioning = null; God.notify('online', proc); } else { last_path = current_path; current_path = path.dirname(current_path); proc.pm2_env.vizion_running = true; vizion.analyze({folder : current_path}, recur_path); } return false; }); }; /** * Inject variables into processes * @param {Object} env environnement to be passed to the process * @param {Function} cb invoked with */ God.injectVariables = function injectVariables (env, cb) { // allow to override the key of NODE_APP_INSTANCE if wanted var instanceKey = process.env.PM2_PROCESS_INSTANCE_VAR || env.instance_var; // we need to find the last NODE_APP_INSTANCE used var instances = Object.keys(God.clusters_db) .map(function (procId) { return God.clusters_db[procId]; }).filter(function (proc) { return proc.pm2_env.name === env.name && typeof proc.pm2_env[instanceKey] !== 'undefined'; }).map(function (proc) { return proc.pm2_env[instanceKey]; }).sort(function (a, b) { return b - a; }); // default to last one + 1 var instanceNumber = typeof instances[0] === 'undefined' ? 0 : instances[0] + 1; // but try to find a one available for (var i = 0; i < instances.length; i++) { if (instances.indexOf(i) === -1) { instanceNumber = i; break; } } env[instanceKey] = instanceNumber; // if using increment_var, we need to increment it if (env.increment_var) { var lastIncrement = Object.keys(God.clusters_db) .map(function (procId) { return God.clusters_db[procId]; }).filter(function (proc) { return proc.pm2_env.name === env.name && typeof proc.pm2_env[env.increment_var] !== 'undefined'; }).map(function (proc) { return Number(proc.pm2_env[env.increment_var]); }).sort(function (a, b) { return b - a; })[0]; // inject a incremental variable var defaut = Number(env.env[env.increment_var]) || 0; env[env.increment_var] = typeof lastIncrement === 'undefined' ? defaut : lastIncrement + 1; env.env[env.increment_var] = env[env.increment_var]; } return cb(null, env); }; God.init() ================================================ FILE: lib/HttpInterface.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ var http = require('http'); var os = require('os'); var pm2 = require('../index.js'); var urlT = require('url'); var cst = require('../constants.js'); // Default, attach to default local PM2 pm2.connect(function() { startWebServer(pm2); }); function startWebServer(pm2) { http.createServer(function (req, res) { // Add CORS headers to allow browsers to fetch data directly res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Headers', 'Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With'); res.setHeader('Access-Control-Allow-Methods', 'GET'); // We always send json res.setHeader('Content-Type','application/json'); var path = urlT.parse(req.url).pathname; if (path == '/') { // Main monit route pm2.list(function(err, list) { if (err) { return res.send(err); } var data = { system_info: { hostname: os.hostname(), uptime: os.uptime() }, monit: { loadavg: os.loadavg(), total_mem: os.totalmem(), free_mem: os.freemem(), cpu: os.cpus(), interfaces: os.networkInterfaces() }, processes: list }; if (cst.WEB_STRIP_ENV_VARS === true) { for (var i = data.processes.length - 1; i >= 0; i--) { var proc = data.processes[i]; // Strip important environment variables if (typeof proc.pm2_env === 'undefined' && typeof proc.pm2_env.env === 'undefined') return; delete proc.pm2_env.env; } } res.statusCode = 200; res.write(JSON.stringify(data)); return res.end(); }) } else { // 404 res.statusCode = 404; res.write(JSON.stringify({err : '404'})); return res.end(); } }).listen(process.env.PM2_WEB_PORT || cst.WEB_PORT, cst.WEB_IPADDR, function() { console.log('Web interface listening on %s:%s', cst.WEB_IPADDR, cst.WEB_PORT); }); } ================================================ FILE: lib/ProcessContainer.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. * * This file wrap target application * - redirect stdin, stderr to bus + log files * - rename process * - pid */ var p = require('path'); var cst = require('../constants'); var Utility = require('./Utility.js'); var ProcessUtils = require('./ProcessUtils'); var Url = require('url'); // Load all env-vars from master. var pm2_env = JSON.parse(process.env.pm2_env); for(var k in pm2_env) { process.env[k] = pm2_env[k]; } // Rename process process.title = process.env.PROCESS_TITLE || 'node ' + pm2_env.pm_exec_path; delete process.env.pm2_env; /** * Main entrance to wrap the desired code */ (function ProcessContainer() { var fs = require('fs'); ProcessUtils.injectModules() var stdFile = pm2_env.pm_log_path; var outFile = pm2_env.pm_out_log_path; var errFile = pm2_env.pm_err_log_path; var pidFile = pm2_env.pm_pid_path; var script = pm2_env.pm_exec_path; var original_send = process.send; if (typeof(process.env.source_map_support) != 'undefined' && process.env.source_map_support !== 'false') { require('source-map-support').install(); } process.send = function() { if (process.connected) original_send.apply(this, arguments); }; //send node version if (process.versions && process.versions.node) { process.send({ 'node_version': process.versions.node }); } if (cst.MODIFY_REQUIRE) require.main.filename = pm2_env.pm_exec_path; // Resets global paths for require() require('module')._initPaths(); try { var pid = process.pid if (typeof(pid) !== 'undefined') fs.writeFileSync(pidFile, process.pid.toString()); } catch (e) { console.error(e.stack || e); } // Add args to process if args specified on start if (process.env.args != null) process.argv = process.argv.concat(pm2_env.args); // stdio, including: out, err and entire (both out and err if necessary). var stds = { out: outFile, err: errFile }; stdFile && (stds.std = stdFile); // uid/gid management if (pm2_env.uid || pm2_env.gid) { try { if (process.env.gid) process.setgid(pm2_env.gid); if (pm2_env.uid) process.setuid(pm2_env.uid); } catch(e) { setTimeout(function() { console.error('%s on call %s', e.message, e.syscall); console.error('%s is not accessible', pm2_env.uid); return process.exit(1); }, 100); } } exec(script, stds); })(); /** * Description * @method exec * @param {} script * @param {} stds * @return */ function exec(script, stds) { if (p.extname(script) == '.ts' || p.extname(script) == '.tsx') { try { require('ts-node/register'); } catch (e) { console.error('Failed to load Typescript interpreter:', e.message || e); } } process.on('message', function (msg) { if (msg.type === 'log:reload') { for (var k in stds){ if (typeof stds[k] == 'object' && !isNaN(stds[k].fd)){ if (stds[k].destroy) stds[k].destroy(); else if (stds[k].end) stds[k].end(); else if (stds[k].close) stds[k].close(); stds[k] = stds[k]._file; } } Utility.startLogging(stds, function (err) { if (err) return console.error('Failed to reload logs:', err.stack); console.log('Reloading log...'); }); } }); var dayjs = null; if (pm2_env.log_date_format) dayjs = require('dayjs'); Utility.startLogging(stds, function (err) { if (err) { process.send({ type : 'process:exception', data : { message: err.message, syscall: 'ProcessContainer.startLogging' } }); throw err; return; } process.stderr.write = (function(write) { return function(string, encoding, cb) { var log_data = null; // Disable logs if specified if (pm2_env.disable_logs === true) { return cb ? cb() : false; } if (pm2_env.log_type && pm2_env.log_type === 'json') { log_data = JSON.stringify({ message : string.toString(), timestamp : pm2_env.log_date_format && dayjs ? dayjs().format(pm2_env.log_date_format) : new Date().toISOString(), type : 'err', process_id : pm2_env.pm_id, app_name : pm2_env.name }) + '\n'; } else if (pm2_env.log_date_format && dayjs) log_data = `${dayjs().format(pm2_env.log_date_format)}: ${string.toString()}`; else log_data = string.toString(); process.send({ type : 'log:err', topic : 'log:err', data : log_data }); if (Utility.checkPathIsNull(pm2_env.pm_err_log_path) && (!pm2_env.pm_log_path || Utility.checkPathIsNull(pm2_env.pm_log_path))) return cb ? cb() : false; stds.std && stds.std.write && stds.std.write(log_data, encoding); stds.err && stds.err.write && stds.err.write(log_data, encoding, cb); }; })(process.stderr.write); process.stdout.write = (function(write) { return function(string, encoding, cb) { var log_data = null; // Disable logs if specified if (pm2_env.disable_logs === true) { return cb ? cb() : false; } if (pm2_env.log_type && pm2_env.log_type === 'json') { log_data = JSON.stringify({ message : string.toString(), timestamp : pm2_env.log_date_format && dayjs ? dayjs().format(pm2_env.log_date_format) : new Date().toISOString(), type : 'out', process_id : pm2_env.pm_id, app_name : pm2_env.name }) + '\n'; } else if (pm2_env.log_date_format && dayjs) log_data = `${dayjs().format(pm2_env.log_date_format)}: ${string.toString()}`; else log_data = string.toString(); process.send({ type : 'log:out', data : log_data }); if (Utility.checkPathIsNull(pm2_env.pm_out_log_path) && (!pm2_env.pm_log_path || Utility.checkPathIsNull(pm2_env.pm_log_path))) return cb ? cb() : null; stds.std && stds.std.write && stds.std.write(log_data, encoding); stds.out && stds.out.write && stds.out.write(log_data, encoding, cb); }; })(process.stdout.write); function getUncaughtExceptionListener(listener) { return function uncaughtListener(err) { var error = err && err.stack ? err.stack : err; if (listener === 'unhandledRejection') { error = 'You have triggered an unhandledRejection, you may have forgotten to catch a Promise rejection:\n' + error; } logError(['std', 'err'], error); // Notify master that an uncaughtException has been catched try { if (err) { var errObj = {}; Object.getOwnPropertyNames(err).forEach(function(key) { errObj[key] = err[key]; }); } process.send({ type : 'log:err', topic : 'log:err', data : '\n' + error + '\n' }); process.send({ type : 'process:exception', data : errObj !== undefined ? errObj : {message: 'No error but ' + listener + ' was caught!'} }); } catch(e) { logError(['std', 'err'], 'Channel is already closed can\'t broadcast error:\n' + e.stack); } if (!process.listeners(listener).filter(function (listener) { return listener !== uncaughtListener; }).length) { if (listener == 'uncaughtException') { process.emit('disconnect'); process.exit(cst.CODE_UNCAUGHTEXCEPTION); } } } } process.on('uncaughtException', getUncaughtExceptionListener('uncaughtException')); process.on('unhandledRejection', getUncaughtExceptionListener('unhandledRejection')); // Change dir to fix process.cwd process.chdir(pm2_env.pm_cwd || process.env.PWD || p.dirname(script)); if (ProcessUtils.isESModule(script) === true) import(Url.pathToFileURL(process.env.pm_exec_path)); else { if (cst.IS_BUN) { require(script); } else { require('module')._load(script, null, true); } } function logError(types, error){ try { types.forEach(function(type){ stds[type] && typeof stds[type].write == 'function' && stds[type].write(error + '\n'); }); } catch(e) { } } }); } ================================================ FILE: lib/ProcessContainerBun.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ var p = require('path'); var cst = require('../constants'); var Utility = require('./Utility.js'); var Url = require('url'); var util = require('util') // Load all env-vars from master. var pm2_env = JSON.parse(process.env.pm2_env); for(var k in pm2_env) { process.env[k] = pm2_env[k]; } // Rename process process.title = process.env.PROCESS_TITLE || 'bun ' + pm2_env.pm_exec_path; delete process.env.pm2_env; /** * Main entrance to wrap the desired code */ (function ProcessContainer() { var fs = require('fs'); var stdFile = pm2_env.pm_log_path; var outFile = pm2_env.pm_out_log_path; var errFile = pm2_env.pm_err_log_path; var pidFile = pm2_env.pm_pid_path; var script = pm2_env.pm_exec_path; var original_send = process.send; if (typeof(process.env.source_map_support) != 'undefined' && process.env.source_map_support !== 'false') { require('source-map-support').install(); } process.send = function() { if (process.connected) original_send.apply(this, arguments); }; //send node version if (process.versions && process.versions.node) { process.send({ 'node_version': process.versions.node }); } if (cst.MODIFY_REQUIRE) require.main.filename = pm2_env.pm_exec_path; // Resets global paths for require() require('module')._initPaths(); try { var pid = process.pid if (typeof(pid) !== 'undefined') fs.writeFileSync(pidFile, process.pid.toString()); } catch (e) { console.error(e.stack || e); } // Add args to process if args specified on start if (process.env.args != null) process.argv = process.argv.concat(pm2_env.args); // stdio, including: out, err and entire (both out and err if necessary). var stds = { out: outFile, err: errFile }; stdFile && (stds.std = stdFile); // uid/gid management if (pm2_env.uid || pm2_env.gid) { try { if (process.env.gid) process.setgid(pm2_env.gid); if (pm2_env.uid) process.setuid(pm2_env.uid); } catch(e) { setTimeout(function() { console.error('%s on call %s', e.message, e.syscall); console.error('%s is not accessible', pm2_env.uid); return process.exit(1); }, 100); } } exec(script, stds); })(); /** * Description * @method exec * @param {} script * @param {} stds * @return */ function exec(script, stds) { process.on('message', function (msg) { if (msg.type === 'log:reload') { for (var k in stds){ if (typeof stds[k] == 'object' && !isNaN(stds[k].fd)){ if (stds[k].destroy) stds[k].destroy(); else if (stds[k].end) stds[k].end(); else if (stds[k].close) stds[k].close(); stds[k] = stds[k]._file; } } Utility.startLogging(stds, function (err) { if (err) return console.error('Failed to reload logs:', err.stack); console.log('Reloading log...'); }); } }); var dayjs = null; if (pm2_env.log_date_format) dayjs = require('dayjs'); Utility.startLogging(stds, function (err) { if (err) { process.send({ type : 'process:exception', data : { message: err.message, syscall: 'ProcessContainer.startLogging' } }); throw err; return; } const originalConsole = { ...console }; ['warn', 'error'].forEach((method) => { console[method] = (...args) => { let log_data = null; const msg = util.format(...args); //const msg = args.map(arg => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg))).join(' '); // Disable logs if specified if (pm2_env.disable_logs === true) { return cb ? cb() : false; } if (pm2_env.log_type && pm2_env.log_type === 'json') { log_data = JSON.stringify({ message : msg, timestamp : pm2_env.log_date_format && dayjs ? dayjs().format(pm2_env.log_date_format) : new Date().toISOString(), type : 'err', process_id : pm2_env.pm_id, app_name : pm2_env.name }) + '\n'; } else if (pm2_env.log_date_format && dayjs) log_data = `${dayjs().format(pm2_env.log_date_format)}: ${msg}`; else log_data = msg.endsWith('\n') ? msg : msg + '\n'; // Send the log message to the master process process.send({ type: 'log:err', data: log_data, }); stds.std && stds.std.write && stds.std.write(log_data); stds.err && stds.err.write && stds.err.write(log_data); }; }); ['log', 'info'].forEach((method) => { console[method] = (...args) => { let log_data = null; const msg = util.format(...args); //const msg = args.map(arg => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg))).join(' '); // Disable logs if specified if (pm2_env.disable_logs === true) { return cb ? cb() : false; } if (pm2_env.log_type && pm2_env.log_type === 'json') { log_data = JSON.stringify({ message : msg, timestamp : pm2_env.log_date_format && dayjs ? dayjs().format(pm2_env.log_date_format) : new Date().toISOString(), type : 'out', process_id : pm2_env.pm_id, app_name : pm2_env.name }) + '\n'; } else if (pm2_env.log_date_format && dayjs) log_data = `${dayjs().format(pm2_env.log_date_format)}: ${msg}`; else log_data = msg.endsWith('\n') ? msg : msg + '\n'; // Send the log message to the master process process.send({ type: 'log:out', data: log_data, }); stds.std && stds.std.write && stds.std.write(log_data); stds.out && stds.out.write && stds.out.write(log_data); }; }); process.stderr.write = (function(write) { return function(string, encoding, cb) { var log_data = null; // Disable logs if specified if (pm2_env.disable_logs === true) { return cb ? cb() : false; } if (pm2_env.log_type && pm2_env.log_type === 'json') { log_data = JSON.stringify({ message : string.toString(), timestamp : pm2_env.log_date_format && dayjs ? dayjs().format(pm2_env.log_date_format) : new Date().toISOString(), type : 'err', process_id : pm2_env.pm_id, app_name : pm2_env.name }) + '\n'; } else if (pm2_env.log_date_format && dayjs) log_data = `${dayjs().format(pm2_env.log_date_format)}: ${string.toString()}`; else log_data = string.toString(); process.send({ type : 'log:err', topic : 'log:err', data : log_data }); if (Utility.checkPathIsNull(pm2_env.pm_err_log_path) && (!pm2_env.pm_log_path || Utility.checkPathIsNull(pm2_env.pm_log_path))) return cb ? cb() : false; stds.std && stds.std.write && stds.std.write(log_data, encoding); stds.err && stds.err.write && stds.err.write(log_data, encoding, cb); }; })(process.stderr.write); process.stdout.write = (function(write) { return function(string, encoding, cb) { var log_data = null; // Disable logs if specified if (pm2_env.disable_logs === true) { return cb ? cb() : false; } if (pm2_env.log_type && pm2_env.log_type === 'json') { log_data = JSON.stringify({ message : string.toString(), timestamp : pm2_env.log_date_format && dayjs ? dayjs().format(pm2_env.log_date_format) : new Date().toISOString(), type : 'out', process_id : pm2_env.pm_id, app_name : pm2_env.name }) + '\n'; } else if (pm2_env.log_date_format && dayjs) log_data = `${dayjs().format(pm2_env.log_date_format)}: ${string.toString()}`; else log_data = string.toString(); process.send({ type : 'log:out', data : log_data }); if (Utility.checkPathIsNull(pm2_env.pm_out_log_path) && (!pm2_env.pm_log_path || Utility.checkPathIsNull(pm2_env.pm_log_path))) return cb ? cb() : null; stds.std && stds.std.write && stds.std.write(log_data, encoding); stds.out && stds.out.write && stds.out.write(log_data, encoding, cb); }; })(process.stdout.write); function getUncaughtExceptionListener(listener) { return function uncaughtListener(err) { var error = err && err.stack ? err.stack : err; if (listener === 'unhandledRejection') { error = 'You have triggered an unhandledRejection, you may have forgotten to catch a Promise rejection:\n' + error; } logError(['std', 'err'], error); // Notify master that an uncaughtException has been catched try { if (err) { var errObj = {}; Object.getOwnPropertyNames(err).forEach(function(key) { errObj[key] = err[key]; }); } process.send({ type : 'log:err', topic : 'log:err', data : '\n' + error + '\n' }); process.send({ type : 'process:exception', data : errObj !== undefined ? errObj : {message: 'No error but ' + listener + ' was caught!'} }); } catch(e) { logError(['std', 'err'], 'Channel is already closed can\'t broadcast error:\n' + e.stack); } if (!process.listeners(listener).filter(function (listener) { return listener !== uncaughtListener; }).length) { if (listener == 'uncaughtException') { process.emit('disconnect'); process.exit(cst.CODE_UNCAUGHTEXCEPTION); } } } } process.on('uncaughtException', getUncaughtExceptionListener('uncaughtException')); process.on('unhandledRejection', getUncaughtExceptionListener('unhandledRejection')); // Change dir to fix process.cwd process.chdir(pm2_env.pm_cwd || process.env.PWD || p.dirname(script)); require(script); function logError(types, error){ try { types.forEach(function(type){ stds[type] && typeof stds[type].write == 'function' && stds[type].write(error + '\n'); }); } catch(e) { } } }); } ================================================ FILE: lib/ProcessContainerFork.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ var url = require('url'); // Inject custom modules var ProcessUtils = require('./ProcessUtils') ProcessUtils.injectModules() if (typeof(process.env.source_map_support) != "undefined" && process.env.source_map_support !== "false") { require('source-map-support').install(); } // Rename the process process.title = process.env.PROCESS_TITLE || 'node ' + process.env.pm_exec_path; if (process.connected && process.send && process.versions && process.versions.node) process.send({ 'node_version': process.versions.node }); // Require the real application if (process.env.pm_exec_path) { if (ProcessUtils.isESModule(process.env.pm_exec_path) === true) { import(url.pathToFileURL(process.env.pm_exec_path)); } else require('module')._load(process.env.pm_exec_path, null, true); } else throw new Error('Could not _load() the script'); // Change some values to make node think that the user's application // was started directly such as `node app.js` process.mainModule = process.mainModule || {}; process.mainModule.loaded = false; require.main = process.mainModule; ================================================ FILE: lib/ProcessContainerForkBun.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ var url = require('url'); // Inject custom modules var ProcessUtils = require('./ProcessUtils') ProcessUtils.injectModules() if (typeof(process.env.source_map_support) != "undefined" && process.env.source_map_support !== "false") { require('source-map-support').install(); } // Rename the process process.title = process.env.PROCESS_TITLE || 'bun ' + process.env.pm_exec_path; if (process.connected && process.send && process.versions && process.versions.node) process.send({ 'node_version': process.versions.node }); require(process.env.pm_exec_path); // Change some values to make node think that the user's application // was started directly such as `node app.js` process.mainModule = process.mainModule || {}; process.mainModule.loaded = false; require.main = process.mainModule; ================================================ FILE: lib/ProcessUtils.js ================================================ 'use strict' module.exports = { injectModules: function() { if (process.env.pmx !== 'false') { const pmx = require('@pm2/io') let conf = {} const hasSpecificConfig = typeof process.env.io === 'string' || process.env.trace === 'true' // pmx is already init, no need to do it twice if (hasSpecificConfig === false) return if (process.env.io) { const io = JSON.parse(process.env.io) conf = io.conf ? io.conf : conf } pmx.init(Object.assign({ tracing: process.env.trace === 'true' || false }, conf)) } }, isESModule(exec_path) { var fs = require('fs') var path = require('path') var semver = require('semver') var data var findPackageJson = function(directory) { var file = path.join(directory, 'package.json') if (fs.existsSync(file) && fs.statSync(file).isFile()) { return file; } var parent = path.resolve(directory, '..') if (parent === directory) { return null; } return findPackageJson(parent) } if (semver.satisfies(process.version, '< 13.3.0')) return false if (path.extname(exec_path) === '.mjs') return true try { data = JSON.parse(fs.readFileSync(findPackageJson(path.dirname(exec_path)))) if (data.type === 'module') return true else return false } catch(e) { } } } ================================================ FILE: lib/TreeKill.js ================================================ 'use strict'; // From https://raw.githubusercontent.com/pkrumins/node-tree-kill/master/index.js var childProcess = require('child_process'); var spawn = childProcess.spawn; var exec = childProcess.exec; module.exports = function (pid, signal, callback) { var tree = {}; var pidsToProcess = {}; tree[pid] = []; pidsToProcess[pid] = 1; switch (process.platform) { case 'win32': exec('taskkill /pid ' + pid + ' /T /F', { windowsHide: true }, callback); break; case 'freebsd': case 'darwin': buildProcessTree(pid, tree, pidsToProcess, function (parentPid) { return spawn('pgrep', ['-P', parentPid]); }, function () { killAll(tree, signal, callback); }); break; // case 'sunos': // buildProcessTreeSunOS(pid, tree, pidsToProcess, function () { // killAll(tree, signal, callback); // }); // break; default: // Linux buildProcessTree(pid, tree, pidsToProcess, function (parentPid) { return spawn('ps', ['-o', 'pid', '--no-headers', '--ppid', parentPid]); }, function () { killAll(tree, signal, callback); }); break; } }; function killAll (tree, signal, callback) { var killed = {}; try { Object.keys(tree).forEach(function (pid) { tree[pid].forEach(function (pidpid) { if (!killed[pidpid]) { killPid(pidpid, signal); killed[pidpid] = 1; } }); if (!killed[pid]) { killPid(pid, signal); killed[pid] = 1; } }); } catch (err) { if (callback) { return callback(err); } else { console.error(err); } } if (callback) { return callback(); } } function killPid(pid, signal) { try { process.kill(parseInt(pid, 10), signal); } catch (err) { if (err.code !== 'ESRCH') console.error(err); } } function buildProcessTree (parentPid, tree, pidsToProcess, spawnChildProcessesList, cb) { var ps = spawnChildProcessesList(parentPid); var allData = ''; ps.on('error', function(err) { console.error(err); }); if (ps.stdout) { ps.stdout.on('data', function (data) { data = data.toString('ascii'); allData += data; }); } var onClose = function (code) { delete pidsToProcess[parentPid]; if (code !== 0) { // no more parent processes if (Object.keys(pidsToProcess).length == 0) { cb(); } return; } var pids = allData.match(/\d+/g) || []; if (pids.length === 0) return cb(); pids.forEach(function (pid) { pid = parseInt(pid, 10); tree[parentPid].push(pid); tree[pid] = []; pidsToProcess[pid] = 1; buildProcessTree(pid, tree, pidsToProcess, spawnChildProcessesList, cb); }); }; ps.on('close', onClose); } ================================================ FILE: lib/Utility.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ /** * Common Utilities ONLY USED IN ->DAEMON<- */ var fclone = require('fclone'); var fs = require('fs'); var cst = require('../constants.js'); var waterfall = require('async/waterfall'); var util = require('util'); var url = require('url'); var dayjs = require('dayjs'); var findPackageJson = require('./tools/find-package-json') var Utility = module.exports = { findPackageVersion : function(fullpath) { var version try { version = findPackageJson(fullpath).next().value.version } catch(e) { version = 'N/A' } return version }, getDate : function() { return Date.now(); }, extendExtraConfig : function(proc, opts) { if (opts.env && opts.env.current_conf) { if (opts.env.current_conf.env && typeof(opts.env.current_conf.env) === 'object' && Object.keys(opts.env.current_conf.env).length === 0) delete opts.env.current_conf.env Utility.extendMix(proc.pm2_env, opts.env.current_conf); delete opts.env.current_conf; } }, formatCLU : function(process) { if (!process.pm2_env) { return process; } var obj = Utility.clone(process.pm2_env); delete obj.env; return obj; }, extend : function(destination, source){ if (!source || typeof source != 'object') return destination; Object.keys(source).forEach(function(new_key) { if (source[new_key] != '[object Object]') destination[new_key] = source[new_key]; }); return destination; }, // Same as extend but drop value with 'null' extendMix : function(destination, source){ if (!source || typeof source != 'object') return destination; Object.keys(source).forEach(function(new_key) { if (source[new_key] == 'null') delete destination[new_key]; else destination[new_key] = source[new_key] }); return destination; }, whichFileExists : function(file_arr) { var f = null; file_arr.some(function(file) { try { fs.statSync(file); } catch(e) { return false; } f = file; return true; }); return f; }, clone : function(obj) { if (obj === null || obj === undefined) return {}; return fclone(obj); }, overrideConsole : function(bus) { if (cst.PM2_LOG_DATE_FORMAT && typeof cst.PM2_LOG_DATE_FORMAT == 'string') { // Generate timestamp prefix function timestamp(){ return `${dayjs(Date.now()).format(cst.PM2_LOG_DATE_FORMAT)}:`; } var hacks = ['info', 'log', 'error', 'warn'], consoled = {}; // store console functions. hacks.forEach(function(method){ consoled[method] = console[method]; }); hacks.forEach(function(k){ console[k] = function(){ if (bus) { bus.emit('log:PM2', { process : { pm_id : 'PM2', name : 'PM2', rev : null }, at : Utility.getDate(), data : util.format.apply(this, arguments) + '\n' }); } // do not destroy variable insertion arguments[0] && (arguments[0] = timestamp() + ' PM2 ' + k + ': ' + arguments[0]); consoled[k].apply(console, arguments); }; }); } }, startLogging : function(stds, callback) { /** * Start log outgoing messages * @method startLogging * @param {} callback * @return */ // Make sure directories of `logs` and `pids` exist. // try { // ['logs', 'pids'].forEach(function(n){ // console.log(n); // (function(_path){ // !fs.existsSync(_path) && fs.mkdirSync(_path, '0755'); // })(path.resolve(cst.PM2_ROOT_PATH, n)); // }); // } catch(err) { // return callback(new Error('can not create directories (logs/pids):' + err.message)); // } // waterfall. var flows = []; // types of stdio, should be sorted as `std(entire log)`, `out`, `err`. var types = Object.keys(stds).sort(function(x, y){ return -x.charCodeAt(0) + y.charCodeAt(0); }); // Create write streams. (function createWS(io){ if(io.length != 1){ return false; } io = io[0]; // If `std` is a Stream type, try next `std`. // compatible with `pm2 reloadLogs` if(typeof stds[io] == 'object' && !isNaN(stds[io].fd)){ return createWS(types.splice(0, 1)); } flows.push(function(next){ var file = stds[io]; // if file contains ERR or /dev/null, dont try to create stream since he dont want logs if (!file || file.indexOf('NULL') > -1 || file.indexOf('/dev/null') > -1) return next(); stds[io] = fs.createWriteStream(file, {flags: 'a'}) .once('error', next) .on('open', function(){ stds[io].removeListener('error', next); stds[io].on('error', function(err) { console.error(err); }); next(); }); stds[io]._file = file; }); return createWS(types.splice(0, 1)); })(types.splice(0, 1)); waterfall(flows, callback); }, /** * Function parse the module name and returns it as canonic: * - Makes the name based on installation filename. * - Removes the Github author, module version and git branch from original name. * * @param {string} module_name * @returns {string} Canonic module name (without trimed parts). * @example Always returns 'pm2-slack' for inputs 'ma-zal/pm2-slack', 'ma-zal/pm2-slack#own-branch', * 'pm2-slack-1.0.0.tgz' or 'pm2-slack@1.0.0'. */ getCanonicModuleName: function(module_name) { if (typeof module_name !== 'string') return null; var canonic_module_name = module_name; // Returns the module name from a .tgz package name (or the original name if it is not a valid pkg). // Input: The package name (e.g. "foo.tgz", "foo-1.0.0.tgz", "folder/foo.tgz") // Output: The module name if (canonic_module_name.match(/\.tgz($|\?)/)) { if (canonic_module_name.match(/^(.+\/)?([^\/]+)\.tgz($|\?)/)) { canonic_module_name = canonic_module_name.match(/^(.+\/)?([^\/]+)\.tgz($|\?)/)[2]; if (canonic_module_name.match(/^(.+)-[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9_]+\.[0-9]+)?$/)) { canonic_module_name = canonic_module_name.match(/^(.+)-[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9_]+\.[0-9]+)?$/)[1]; } } } //pm2 install git+https://github.com/user/module if(canonic_module_name.indexOf('git+') !== -1) { canonic_module_name = canonic_module_name.split('/').pop(); } //pm2 install https://github.com/user/module if(canonic_module_name.indexOf('http') !== -1) { var uri = url.parse(canonic_module_name); canonic_module_name = uri.pathname.split('/').pop(); } //pm2 install file:///home/user/module else if(canonic_module_name.indexOf('file://') === 0) { canonic_module_name = canonic_module_name.replace(/\/$/, '').split('/').pop(); } //pm2 install username/module else if(canonic_module_name.indexOf('/') !== -1) { if (canonic_module_name.charAt(0) !== "@"){ canonic_module_name = canonic_module_name.split('/')[1]; } } //pm2 install @somescope/module@2.1.0-beta if(canonic_module_name.lastIndexOf('@') > 0) { canonic_module_name = canonic_module_name.substr(0,canonic_module_name.lastIndexOf("@")); } //pm2 install module#some-branch if(canonic_module_name.indexOf('#') !== -1) { canonic_module_name = canonic_module_name.split('#')[0]; } if (canonic_module_name.indexOf('.git') !== -1) { canonic_module_name = canonic_module_name.replace('.git', ''); } return canonic_module_name; }, checkPathIsNull: function(path) { return path === 'NULL' || path === '/dev/null' || path === '\\\\.\\NUL'; }, generateUUID: function () { var s = []; var hexDigits = "0123456789abcdef"; for (var i = 0; i < 36; i++) { s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); } s[14] = "4"; s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); s[8] = s[13] = s[18] = s[23] = "-"; return s.join(""); } }; ================================================ FILE: lib/VersionCheck.js ================================================ var vCheck = require('@pm2/pm2-version-check') var semver = require('semver') var fs = require('fs') var os = require('os') function hasDockerEnv() { try { fs.statSync('/.dockerenv'); return true; } catch (_) { return false; } } function hasDockerCGroup() { try { return fs.readFileSync('/proc/self/cgroup', 'utf8').includes('docker'); } catch (_) { return false; } } module.exports = function (opts) { var params = { state: opts.state, version: opts.version } try { params.os = os.type() params.uptime = Math.floor(process.uptime()) params.nodev = process.versions.node params.docker = hasDockerEnv() || hasDockerCGroup() } catch(e) { } vCheck.runCheck(params, (err, pkg) => { if (err) return false if (!pkg.current_version) return false if (opts.version && semver.lt(opts.version, pkg.current_version)) { console.log('[PM2] This PM2 is not UP TO DATE') console.log('[PM2] Upgrade to version %s', pkg.current_version) } }) } ================================================ FILE: lib/Watcher.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ var chokidar = require('chokidar'); var util = require('util'); var log = require('debug')('pm2:watch'); module.exports = function ClusterMode(God) { /** * Watch folder for changes and restart * @method watch * @param {Object} pm2_env pm2 app environnement * @return MemberExpression */ God.watch = {}; God.watch._watchers = {}; God.watch.enable = function(pm2_env) { if (God.watch._watchers[pm2_env.pm_id]) { God.watch._watchers[pm2_env.pm_id].close(); God.watch._watchers[pm2_env.pm_id] = null; delete God.watch._watchers[pm2_env.pm_id]; } log('Initial watch ', pm2_env.watch) var watch = pm2_env.watch if(typeof watch == 'boolean' || Array.isArray(watch) && watch.length === 0) watch = pm2_env.pm_cwd; log('Watching %s', watch); var watch_options = { ignored : pm2_env.ignore_watch || /[\/\\]\.|node_modules/, persistent : true, ignoreInitial : true, cwd: pm2_env.pm_cwd }; if (pm2_env.watch_options) { watch_options = Object.assign(watch_options, pm2_env.watch_options); } log('Watch opts', watch_options); var watcher = chokidar.watch(watch, watch_options); console.log('[Watch] Start watching', pm2_env.name); watcher.on('all', function(event, path) { var self = this; if (self.restarting === true) { log('Already restarting, skipping'); return false; } self.restarting = true; console.log('Change detected on path %s for app %s - restarting', path, pm2_env.name); setTimeout(function() { God.restartProcessName(pm2_env.name, function(err, list) { self.restarting = false; if (err) { log('Error while restarting', err); return false; } return log('Process restarted'); }); }, (pm2_env.watch_delay || 0)); return false; }); watcher.on('error', function(e) { console.error(e.stack || e); }); God.watch._watchers[pm2_env.pm_id] = watcher; //return God.watch._watchers[pm2_env.name]; }, /** * Description * @method close * @param {} id * @return */ God.watch.disableAll = function() { var watchers = God.watch._watchers; console.log('[Watch] PM2 is being killed. Watch is disabled to avoid conflicts'); for (var i in watchers) { watchers[i].close && watchers[i].close(); watchers.splice(i, 1); } }, God.watch.disable = function(pm2_env) { var watcher = God.watch._watchers[pm2_env.pm_id] if (watcher) { console.log('[Watch] Stop watching', pm2_env.name); watcher.close(); delete God.watch._watchers[pm2_env.pm_id]; return true; } else { return false; } } }; ================================================ FILE: lib/Worker.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ const eachLimit = require('async/eachLimit'); const debug = require('debug')('pm2:worker'); const domain = require('domain'); const Cron = require('croner'); const pkg = require('../package.json'); var cst = require('../constants.js'); var vCheck = require('./VersionCheck.js') module.exports = function(God) { var timer = null; God.CronJobs = new Map(); God.Worker = {}; God.Worker.is_running = false; God.getCronID = function(pm_id) { return `cron-${pm_id}` } God.registerCron = function(pm2_env) { if (!pm2_env || pm2_env.pm_id === undefined || !pm2_env.cron_restart || pm2_env.cron_restart == '0' || God.CronJobs.has(God.getCronID(pm2_env.pm_id))) return; var pm_id = pm2_env.pm_id console.log('[PM2][WORKER] Registering a cron job on:', pm_id); var job = Cron(pm2_env.cron_restart, function() { God.restartProcessId({id: pm_id}, function(err, data) { if (err) console.error(err.stack || err); return; }); }); God.CronJobs.set(God.getCronID(pm_id), job); } /** * Deletes the cron job on deletion of process */ God.deleteCron = function(id) { if (typeof(id) !== 'undefined' && God.CronJobs.has(God.getCronID(id)) === false) return; console.log('[PM2] Deregistering a cron job on:', id); var job = God.CronJobs.get(God.getCronID(id)); if (job) job.stop(); God.CronJobs.delete(God.getCronID(id)); }; var _getProcessById = function(pm_id) { var proc = God.clusters_db[pm_id]; return proc ? proc : null; }; var maxMemoryRestart = function(proc_key, cb) { var proc = _getProcessById(proc_key.pm2_env.pm_id); if (!(proc && proc.pm2_env && proc_key.monit)) return cb(); if (proc_key.monit.memory !== undefined && proc.pm2_env.max_memory_restart !== undefined && proc.pm2_env.max_memory_restart < proc_key.monit.memory && proc.pm2_env.axm_options && proc.pm2_env.axm_options.pid === undefined) { console.log('[PM2][WORKER] Process %s restarted because it exceeds --max-memory-restart value (current_memory=%s max_memory_limit=%s [octets])', proc.pm2_env.pm_id, proc_key.monit.memory, proc.pm2_env.max_memory_restart); God.reloadProcessId({ id : proc.pm2_env.pm_id }, function(err, data) { if (err) console.error(err.stack || err); return cb(); }); } else { return cb(); } }; var tasks = function() { if (God.Worker.is_running === true) { debug('[PM2][WORKER] Worker is already running, skipping this round'); return false; } God.Worker.is_running = true; God.getMonitorData(null, function(err, data) { if (err || !data || typeof(data) !== 'object') { God.Worker.is_running = false; return console.error(err); } eachLimit(data, 1, function(proc, next) { if (!proc || !proc.pm2_env || proc.pm2_env.pm_id === undefined) return next(); debug('[PM2][WORKER] Processing proc id:', proc.pm2_env.pm_id); // Reset restart delay if application has an uptime of more > 30secs if (proc.pm2_env.exp_backoff_restart_delay !== undefined && proc.pm2_env.prev_restart_delay && proc.pm2_env.prev_restart_delay > 0) { var app_uptime = Date.now() - proc.pm2_env.pm_uptime if (app_uptime > cst.EXP_BACKOFF_RESET_TIMER) { var ref_proc = _getProcessById(proc.pm2_env.pm_id); ref_proc.pm2_env.prev_restart_delay = 0 console.log(`[PM2][WORKER] Reset the restart delay, as app ${proc.name} has been up for more than ${cst.EXP_BACKOFF_RESET_TIMER}ms`) } } // Check if application has reached memory threshold maxMemoryRestart(proc, function() { return next(); }); }, function(err) { God.Worker.is_running = false; debug('[PM2][WORKER] My job here is done, next job in %d seconds', parseInt(cst.WORKER_INTERVAL / 1000)); }); }); }; var wrappedTasks = function() { var d = domain.create(); d.once('error', function(err) { console.error('[PM2][WORKER] Error caught by domain:\n' + (err.stack || err)); God.Worker.is_running = false; }); d.run(function() { tasks(); }); }; God.Worker.start = function() { timer = setInterval(wrappedTasks, cst.WORKER_INTERVAL); if (!process.env.PM2_DISABLE_VERSION_CHECK) { setInterval(() => { vCheck({ state: 'check', version: pkg.version, }); }, 1000 * 60 * 60 * 24); } }; God.Worker.stop = function() { if (timer !== null) clearInterval(timer); }; }; ================================================ FILE: lib/binaries/CLI.js ================================================ 'use strict'; process.env.PM2_USAGE = 'CLI'; var cst = require('../../constants.js'); var commander = require('commander'); var chalk = require('ansis'); var forEachLimit = require('async/forEachLimit'); var debug = require('debug')('pm2:cli'); var PM2 = require('../API.js'); var pkg = require('../../package.json'); var tabtab = require('../completion.js'); var Common = require('../Common.js'); var PM2ioHandler = require('../API/pm2-plus/PM2IO'); var semver = require('semver') if (cst.IS_BUN === true && semver.lt(process.versions.bun, '1.1.25')) { throw new Error('PM2 cannot run on Bun version < 1.1.25 (cluster support)') } Common.determineSilentCLI(); Common.printVersion(); var pm2 = new PM2(); PM2ioHandler.usePM2Client(pm2) commander.version(pkg.version) .option('-v --version', 'print pm2 version') .option('-s --silent', 'hide all messages', false) .option('--ext ', 'watch only this file extensions') .option('-n --name ', 'set a name for the process in the process list') .option('-m --mini-list', 'display a compacted list without formatting') .option('--interpreter ', 'set a specific interpreter to use for executing app, default: node') .option('--interpreter-args ', 'set arguments to pass to the interpreter (alias of --node-args)') .option('--node-args ', 'space delimited arguments to pass to node') .option('-o --output ', 'specify log file for stdout') .option('-e --error ', 'specify log file for stderr') .option('-l --log [path]', 'specify log file which gathers both stdout and stderr') .option('--filter-env [envs]', 'filter out outgoing global values that contain provided strings', function(v, m) { m.push(v); return m;}, []) .option('--log-type ', 'specify log output style (raw by default, json optional)') .option('--log-date-format ', 'add custom prefix timestamp to logs') .option('--time', 'enable time logging') .option('--disable-logs', 'disable all logs storage') .option('--env ', 'specify which set of environment variables from ecosystem file must be injected') .option('-a --update-env', 'force an update of the environment with restart/reload (-a <=> apply)') .option('-f --force', 'force actions') .option('-i --instances ', 'launch [number] instances (for networked app)(load balanced)') .option('--parallel ', 'number of parallel actions (for restart/reload)') .option('--shutdown-with-message', 'shutdown an application with process.send(\'shutdown\') instead of process.kill(pid, SIGINT)') .option('-p --pid ', 'specify pid file') .option('-k --kill-timeout ', 'delay before sending final SIGKILL signal to process') .option('--listen-timeout ', 'listen timeout on application reload') .option('--max-memory-restart ', 'Restart the app if an amount of memory is exceeded (in bytes)') .option('--restart-delay ', 'specify a delay between restarts (in milliseconds)') .option('--exp-backoff-restart-delay ', 'specify a delay between restarts (in milliseconds)') .option('-x --execute-command', 'execute a program using fork system') .option('--max-restarts [count]', 'only restart the script COUNT times') .option('-u --user ', 'define user when generating startup script') .option('--uid ', 'run target script with rights') .option('--gid ', 'run target script with rights') .option('--namespace ', 'start application within specified namespace') .option('--cwd ', 'run target script from path ') .option('--hp ', 'define home path when generating startup script') .option('--wait-ip', 'override systemd script to wait for full internet connectivity to launch pm2') .option('--service-name ', 'define service name when generating startup script') .option('-c --cron ', 'restart a running process based on a cron pattern') .option('-c --cron-restart ', '(alias) restart a running process based on a cron pattern') .option('-w --write', 'write configuration in local folder') .option('--no-daemon', 'run pm2 daemon in the foreground if it doesn\'t exist already') .option('--source-map-support', 'force source map support') .option('--only ', 'with json declaration, allow to only act on one application') .option('--disable-source-map-support', 'force source map support') .option('--wait-ready', 'ask pm2 to wait for ready event from your app') .option('--merge-logs', 'merge logs from different instances but keep error and out separated') .option('--watch [paths]', 'watch application folder for changes', function(v, m) { m.push(v); return m;}, []) .option('--ignore-watch ', 'List of paths to ignore (name or regex)') .option('--watch-delay ', 'specify a restart delay after changing files (--watch-delay 4 (in sec) or 4000ms)') .option('--no-color', 'skip colors') .option('--no-vizion', 'start an app without vizion feature (versioning control)') .option('--no-autostart', 'add an app without automatic start') .option('--no-autorestart', 'start an app without automatic restart') .option('--stop-exit-codes ', 'specify a list of exit codes that should skip automatic restart') .option('--no-treekill', 'Only kill the main process, not detached children') .option('--no-pmx', 'start an app without pmx') .option('--no-automation', 'start an app without pmx') .option('--trace', 'enable transaction tracing with km') .option('--disable-trace', 'disable transaction tracing with km') .option('--sort ', 'sort process according to field\'s name') .option('--attach', 'attach logging after your start/restart/stop/reload') .option('--v8', 'enable v8 data collecting') .option('--event-loop-inspector', 'enable event-loop-inspector dump in pmx') .option('--deep-monitoring', 'enable all monitoring tools (equivalent to --v8 --event-loop-inspector --trace)') .usage('[cmd] app'); function displayUsage() { console.log('usage: pm2 [options] ') console.log(''); console.log('pm2 -h, --help all available commands and options'); console.log('pm2 examples display pm2 usage examples'); console.log('pm2 -h help on a specific command'); console.log(''); console.log('Access pm2 files in ~/.pm2'); } function displayExamples() { console.log('- Start and add a process to the pm2 process list:') console.log(''); console.log(chalk.cyan(' $ pm2 start app.js --name app')); console.log(''); console.log('- Show the process list:'); console.log(''); console.log(chalk.cyan(' $ pm2 ls')); console.log(''); console.log('- Stop and delete a process from the pm2 process list:'); console.log(''); console.log(chalk.cyan(' $ pm2 delete app')); console.log(''); console.log('- Stop, start and restart a process from the process list:'); console.log(''); console.log(chalk.cyan(' $ pm2 stop app')); console.log(chalk.cyan(' $ pm2 start app')); console.log(chalk.cyan(' $ pm2 restart app')); console.log(''); console.log('- Clusterize an app to all CPU cores available:'); console.log(''); console.log(chalk.cyan(' $ pm2 start -i max')); console.log(''); console.log('- Update pm2 :'); console.log(''); console.log(chalk.cyan(' $ npm install pm2 -g && pm2 update')); console.log(''); console.log('- Install pm2 auto completion:') console.log(''); console.log(chalk.cyan(' $ pm2 completion install')) console.log(''); console.log('Check the full documentation on https://pm2.keymetrics.io/'); console.log(''); } function beginCommandProcessing() { pm2.getVersion(function(err, remote_version) { if (!err && (pkg.version != remote_version)) { console.log(''); console.log(chalk.red.bold('>>>> In-memory PM2 is out-of-date, do:\n>>>> $ pm2 update')); console.log('In memory PM2 version:', chalk.blue.bold(remote_version)); console.log('Local PM2 version:', chalk.blue.bold(pkg.version)); console.log(''); } }); commander.parse(process.argv); } function checkCompletion(){ return tabtab.complete('pm2', function(err, data) { if(err || !data) return; if(/^--\w?/.test(data.last)) return tabtab.log(commander.options.map(function (data) { return data.long; }), data); if(/^-\w?/.test(data.last)) return tabtab.log(commander.options.map(function (data) { return data.short; }), data); // array containing commands after which process name should be listed var cmdProcess = ['stop', 'restart', 'scale', 'reload', 'delete', 'reset', 'pull', 'forward', 'backward', 'logs', 'describe', 'desc', 'show']; if (cmdProcess.indexOf(data.prev) > -1) { pm2.list(function(err, list){ tabtab.log(list.map(function(el){ return el.name }), data); pm2.disconnect(); }); } else if (data.prev == 'pm2') { tabtab.log(commander.commands.map(function (data) { return data._name; }), data); pm2.disconnect(); } else pm2.disconnect(); }); }; var _arr = process.argv.indexOf('--') > -1 ? process.argv.slice(0, process.argv.indexOf('--')) : process.argv; if (_arr.indexOf('log') > -1) { process.argv[_arr.indexOf('log')] = 'logs'; } if (_arr.indexOf('--no-daemon') > -1) { // // Start daemon if it does not exist // // Function checks if --no-daemon option is present, // and starts daemon in the same process if it does not exist // console.log('pm2 launched in no-daemon mode (you can add DEBUG="*" env variable to get more messages)'); var pm2NoDaeamon = new PM2({ daemon_mode : false }); pm2NoDaeamon.connect(function() { pm2 = pm2NoDaeamon; beginCommandProcessing(); }); } else if (_arr.indexOf('startup') > -1 || _arr.indexOf('unstartup') > -1) { setTimeout(function() { commander.parse(process.argv); }, 100); } else { // HERE we instanciate the Client object pm2.connect(function() { debug('Now connected to daemon'); if (process.argv.slice(2)[0] === 'completion') { checkCompletion(); //Close client if completion related installation var third = process.argv.slice(3)[0]; if ( third == null || third === 'install' || third === 'uninstall') pm2.disconnect(); } else { beginCommandProcessing(); } }); } // // Helper function to fail when unknown command arguments are passed // function failOnUnknown(fn) { return function(arg) { if (arguments.length > 1) { console.log(cst.PREFIX_MSG + '\nUnknown command argument: ' + arg); commander.outputHelp(); process.exit(cst.ERROR_EXIT); } return fn.apply(this, arguments); }; } /** * @todo to remove at some point once it's fixed in official commander.js * https://github.com/tj/commander.js/issues/475 * * Patch Commander.js Variadic feature */ function patchCommanderArg(cmd) { var argsIndex; if ((argsIndex = commander.rawArgs.indexOf('--')) >= 0) { var optargs = commander.rawArgs.slice(argsIndex + 1); cmd = cmd.slice(0, cmd.indexOf(optargs[0])); } return cmd; } // // Start command // commander.command('start [name|namespace|file|ecosystem|id...]') .option('--watch', 'Watch folder for changes') .option('--fresh', 'Rebuild Dockerfile') .option('--daemon', 'Run container in Daemon mode (debug purposes)') .option('--container', 'Start application in container mode') .option('--dist', 'with --container; change local Dockerfile to containerize all files in current directory') .option('--image-name [name]', 'with --dist; set the exported image name') .option('--node-version [major]', 'with --container, set a specific major Node.js version') .option('--dockerdaemon', 'for debugging purpose') .description('start and daemonize an app') .action(function(cmd, opts) { if (opts.container == true && opts.dist == true) return pm2.dockerMode(cmd, opts, 'distribution'); else if (opts.container == true) return pm2.dockerMode(cmd, opts, 'development'); if (cmd == "-") { process.stdin.resume(); process.stdin.setEncoding('utf8'); process.stdin.on('data', function (cmd) { process.stdin.pause(); pm2._startJson(cmd, commander, 'restartProcessId', 'pipe'); }); } else { // Commander.js patch cmd = patchCommanderArg(cmd); if (cmd.length === 0) { cmd = [cst.APP_CONF_DEFAULT_FILE]; } let acc = [] forEachLimit(cmd, 1, function(script, next) { pm2.start(script, commander, (err, apps) => { acc = acc.concat(apps) next(err) }); }, function(err, dt) { if (err && err.message && (err.message.includes('Script not found') === true || err.message.includes('NOT AVAILABLE IN PATH') === true)) { pm2.exitCli(1) } else pm2.speedList(err ? 1 : 0, acc); }); } }); commander.command('trigger [params]') .description('trigger process action') .action(function(pm_id, action_name, params) { pm2.trigger(pm_id, action_name, params); }); commander.command('deploy ') .description('deploy your json') .action(function(cmd) { pm2.deploy(cmd, commander); }); commander.command('startOrRestart ') .description('start or restart JSON file') .action(function(file) { pm2._startJson(file, commander, 'restartProcessId'); }); commander.command('startOrReload ') .description('start or gracefully reload JSON file') .action(function(file) { pm2._startJson(file, commander, 'reloadProcessId'); }); commander.command('pid [app_name]') .description('return pid of [app_name] or all') .action(function(app) { pm2.getPID(app); }); commander.command('create') .description('return pid of [app_name] or all') .action(function() { pm2.boilerplate() }); commander.command('startOrGracefulReload ') .description('start or gracefully reload JSON file') .action(function(file) { pm2._startJson(file, commander, 'reloadProcessId'); }); // // Stop specific id // commander.command('stop ') .option('--watch', 'Stop watching folder for changes') .description('stop a process') .action(function(param) { forEachLimit(param, 1, function(script, next) { pm2.stop(script, next); }, function(err) { pm2.speedList(err ? 1 : 0); }); }); // // Stop All processes // commander.command('restart ') .option('--watch', 'Toggle watching folder for changes') .description('restart a process') .action(function(param) { // Commander.js patch param = patchCommanderArg(param); let acc = [] forEachLimit(param, 1, function(script, next) { pm2.restart(script, commander, (err, apps) => { acc = acc.concat(apps) next(err) }); }, function(err) { pm2.speedList(err ? 1 : 0, acc); }); }); // // Scale up/down a process in cluster mode // commander.command('scale ') .description('scale up/down a process in cluster mode depending on total_number param') .action(function(app_name, number) { pm2.scale(app_name, number); }); // // snapshot PM2 // commander.command('profile:mem [time]') .description('Sample PM2 heap memory') .action(function(time) { pm2.profile('mem', time); }); // // snapshot PM2 // commander.command('profile:cpu [time]') .description('Profile PM2 cpu') .action(function(time) { pm2.profile('cpu', time); }); // // Reload process(es) // commander.command('reload ') .description('reload processes (note that its for app using HTTP/HTTPS)') .action(function(pm2_id) { pm2.reload(pm2_id, commander); }); commander.command('id ') .description('get process id by name') .action(function(name) { pm2.getProcessIdByName(name); }); // Inspect a process commander.command('inspect ') .description('inspect a process') .action(function(cmd) { pm2.inspect(cmd, commander); }); // // Stop and delete a process by name from database // commander.command('delete ') .alias('del') .description('stop and delete a process from pm2 process list') .action(function(name) { if (name == "-") { process.stdin.resume(); process.stdin.setEncoding('utf8'); process.stdin.on('data', function (param) { process.stdin.pause(); pm2.delete(param, 'pipe'); }); } else forEachLimit(name, 1, function(script, next) { pm2.delete(script,'', next); }, function(err) { pm2.speedList(err ? 1 : 0); }); }); // // Send system signal to process // commander.command('sendSignal ') .description('send a system signal to the target process') .action(function(signal, pm2_id) { if (isNaN(parseInt(pm2_id))) { console.log(cst.PREFIX_MSG + 'Sending signal to process name ' + pm2_id); pm2.sendSignalToProcessName(signal, pm2_id); } else { console.log(cst.PREFIX_MSG + 'Sending signal to process id ' + pm2_id); pm2.sendSignalToProcessId(signal, pm2_id); } }); // // Stop and delete a process by name from database // commander.command('ping') .description('ping pm2 daemon - if not up it will launch it') .action(function() { pm2.ping(); }); commander.command('updatePM2') .description('update in-memory PM2 with local PM2') .action(function() { pm2.update(); }); commander.command('update') .description('(alias) update in-memory PM2 with local PM2') .action(function() { pm2.update(); }); /** * Module specifics */ commander.command('install ') .alias('module:install') .option('--tarball', 'is local tarball') .option('--install', 'run yarn install before starting module') .option('--docker', 'is docker container') .option('--v1', 'install module in v1 manner (do not use it)') .option('--safe [time]', 'keep module backup, if new module fail = restore with previous') .description('install or update a module and run it forever') .action(function(plugin_name, opts) { require('util')._extend(commander, opts); pm2.install(plugin_name, commander); }); commander.command('module:update ') .option('--tarball', 'is local tarball') .description('update a module and run it forever') .action(function(plugin_name, opts) { require('util')._extend(commander, opts); pm2.install(plugin_name, commander); }); commander.command('module:generate [app_name]') .description('Generate a sample module in current folder') .action(function(app_name) { pm2.generateModuleSample(app_name); }); commander.command('uninstall ') .alias('module:uninstall') .description('stop and uninstall a module') .action(function(plugin_name) { pm2.uninstall(plugin_name); }); commander.command('package [target]') .description('Check & Package TAR type module') .action(function(target) { pm2.package(target); }); commander.command('publish [folder]') .option('--npm', 'publish on npm') .alias('module:publish') .description('Publish the module you are currently on') .action(function(folder, opts) { pm2.publish(folder, opts); }); commander.command('set [key] [value]') .description('sets the specified config ') .action(function(key, value) { pm2.set(key, value); }); commander.command('multiset ') .description('multiset eg "key1 val1 key2 val2') .action(function(str) { pm2.multiset(str); }); commander.command('get [key]') .description('get value for ') .action(function(key) { pm2.get(key); }); commander.command('conf [key] [value]') .description('get / set module config values') .action(function(key, value) { pm2.get() }); commander.command('config [value]') .description('get / set module config values') .action(function(key, value) { pm2.conf(key, value); }); commander.command('unset ') .description('clears the specified config ') .action(function(key) { pm2.unset(key); }); commander.command('report') .description('give a full pm2 report for https://github.com/Unitech/pm2/issues') .action(function(key) { pm2.report(); }); // // PM2 I/O // commander.command('link [secret] [public] [name]') .option('--info-node [url]', 'set url info node') .description('link with the pm2 monitoring dashboard') .action(pm2.linkManagement.bind(pm2)); commander.command('unlink') .description('unlink with the pm2 monitoring dashboard') .action(function() { pm2.unlink(); }); commander.command('monitor [name]') .description('monitor target process') .action(function(name) { if (name === undefined) { return plusHandler() } pm2.monitorState('monitor', name); }); commander.command('unmonitor [name]') .description('unmonitor target process') .action(function(name) { pm2.monitorState('unmonitor', name); }); commander.command('open') .description('open the pm2 monitoring dashboard') .action(function(name) { pm2.openDashboard(); }); function plusHandler (command, opts) { if (opts && opts.infoNode) { process.env.KEYMETRICS_NODE = opts.infoNode } return PM2ioHandler.launch(command, opts) } commander.command('plus [command] [option]') .alias('register') .option('--info-node [url]', 'set url info node for on-premise pm2 plus') .option('-d --discrete', 'silent mode') .option('-a --install-all', 'install all modules (force yes)') .description('enable pm2 plus') .action(plusHandler); commander.command('login') .description('Login to pm2 plus') .action(function() { return plusHandler('login') }); commander.command('logout') .description('Logout from pm2 plus') .action(function() { return plusHandler('logout') }); // // Save processes to file // commander.command('dump') .alias('save') .option('--force', 'force deletion of dump file, even if empty') .description('dump all processes for resurrecting them later') .action(failOnUnknown(function(opts) { pm2.dump(commander.force) })); // // Delete dump file // commander.command('cleardump') .description('Create empty dump file') .action(failOnUnknown(function() { pm2.clearDump(); })); // // Save processes to file // commander.command('send ') .description('send stdin to ') .action(function(pm_id, line) { pm2.sendLineToStdin(pm_id, line); }); // // Attach to stdin/stdout // Not TTY ready // commander.command('attach [command separator]') .description('attach stdin/stdout to application identified by ') .action(function(pm_id, separator) { pm2.attach(pm_id, separator); }); // // Resurrect // commander.command('resurrect') .description('resurrect previously dumped processes') .action(failOnUnknown(function() { console.log(cst.PREFIX_MSG + 'Resurrecting'); pm2.resurrect(); })); // // Set pm2 to startup // commander.command('unstartup [platform]') .description('disable the pm2 startup hook') .action(function(platform) { pm2.uninstallStartup(platform, commander); }); // // Set pm2 to startup // commander.command('startup [platform]') .description('enable the pm2 startup hook') .action(function(platform) { pm2.startup(platform, commander); }); // // Logrotate // commander.command('logrotate') .description('copy default logrotate configuration') .action(function(cmd) { pm2.logrotate(commander); }); // // Sample generate // commander.command('ecosystem [mode]') .alias('init') .description('generate a process conf file. (mode = null or simple)') .action(function(mode) { pm2.generateSample(mode); }); commander.command('reset ') .description('reset counters for process') .action(function(proc_id) { pm2.reset(proc_id); }); commander.command('describe ') .description('describe all parameters of a process') .action(function(proc_id) { pm2.describe(proc_id); }); commander.command('desc ') .description('(alias) describe all parameters of a process') .action(function(proc_id) { pm2.describe(proc_id); }); commander.command('info ') .description('(alias) describe all parameters of a process') .action(function(proc_id) { pm2.describe(proc_id); }); commander.command('show ') .description('(alias) describe all parameters of a process') .action(function(proc_id) { pm2.describe(proc_id); }); commander.command('env ') .description('list all environment variables of a process id') .action(function(proc_id) { pm2.env(proc_id); }); // // List command // commander .command('list') .alias('ls') .description('list all processes') .action(function() { pm2.list(commander) }); commander.command('l') .description('(alias) list all processes') .action(function() { pm2.list() }); commander.command('ps') .description('(alias) list all processes') .action(function() { pm2.list() }); commander.command('status') .description('(alias) list all processes') .action(function() { pm2.list() }); // List in raw json commander.command('jlist') .description('list all processes in JSON format') .action(function() { pm2.jlist() }); commander.command('sysmonit') .description('start system monitoring daemon') .action(function() { pm2.launchSysMonitoring() }) commander.command('slist') .alias('sysinfos') .option('-t --tree', 'show as tree') .description('list system infos in JSON') .action(function(opts) { pm2.slist(opts.tree) }) // List in prettified Json commander.command('prettylist') .description('print json in a prettified JSON') .action(failOnUnknown(function() { pm2.jlist(true); })); // // Dashboard command // commander.command('monit') .description('launch termcaps monitoring') .action(function() { pm2.dashboard(); }); commander.command('imonit') .description('launch legacy termcaps monitoring') .action(function() { pm2.monit(); }); commander.command('dashboard') .alias('dash') .description('launch dashboard with monitoring and logs') .action(function() { pm2.dashboard(); }); // // Flushing command // commander.command('flush [api]') .description('flush logs') .action(function(api) { pm2.flush(api); }); /* old version commander.command('flush') .description('flush logs') .action(failOnUnknown(function() { pm2.flush(); })); */ // // Reload all logs // commander.command('reloadLogs') .description('reload all logs') .action(function() { pm2.reloadLogs(); }); // // Log streaming // commander.command('logs [id|name|namespace]') .option('--json', 'json log output') .option('--format', 'formated log output') .option('--raw', 'raw output') .option('--err', 'only shows error output') .option('--out', 'only shows standard output') .option('--lines ', 'output the last N lines, instead of the last 15 by default') .option('--timestamp [format]', 'add timestamps (default format YYYY-MM-DD-HH:mm:ss)') .option('--nostream', 'print logs without launching the log stream') .option('--highlight [value]', 'highlights the given value') .description('stream logs file. Default stream all logs') .action(function(id, cmd) { var Logs = require('../API/Log.js'); if (!id) id = 'all'; var line = 15; var raw = false; var exclusive = false; var timestamp = false; var highlight = false; if(!isNaN(parseInt(cmd.lines))) { line = parseInt(cmd.lines); } if (cmd.parent.rawArgs.indexOf('--raw') !== -1) raw = true; if (cmd.timestamp) timestamp = typeof cmd.timestamp === 'string' ? cmd.timestamp : 'YYYY-MM-DD-HH:mm:ss'; if (cmd.highlight) highlight = typeof cmd.highlight === 'string' ? cmd.highlight : false; if (cmd.out === true) exclusive = 'out'; if (cmd.err === true) exclusive = 'err'; if (cmd.nostream === true) pm2.printLogs(id, line, raw, timestamp, exclusive); else if (cmd.json === true) Logs.jsonStream(pm2.Client, id); else if (cmd.format === true) Logs.formatStream(pm2.Client, id, false, 'YYYY-MM-DD-HH:mm:ssZZ', exclusive, highlight); else pm2.streamLogs(id, line, raw, timestamp, exclusive, highlight); }); // // Kill // commander.command('kill') .description('kill daemon') .action(failOnUnknown(function(arg) { pm2.killDaemon(function() { process.exit(cst.SUCCESS_EXIT); }); })); // // Update repository for a given app // commander.command('pull [commit_id]') .description('updates repository for a given app') .action(function(pm2_name, commit_id) { if (commit_id !== undefined) { pm2._pullCommitId({ pm2_name: pm2_name, commit_id: commit_id }); } else pm2.pullAndRestart(pm2_name); }); // // Update repository to the next commit for a given app // commander.command('forward ') .description('updates repository to the next commit for a given app') .action(function(pm2_name) { pm2.forward(pm2_name); }); // // Downgrade repository to the previous commit for a given app // commander.command('backward ') .description('downgrades repository to the previous commit for a given app') .action(function(pm2_name) { pm2.backward(pm2_name); }); // // Perform a deep update of PM2 // commander.command('deepUpdate') .description('performs a deep update of PM2') .action(function() { pm2.deepUpdate(); }); // // Launch a http server that expose a given path on given port // commander.command('serve [path] [port]') .alias('expose') .option('--port [port]', 'specify port to listen to') .option('--spa', 'always serving index.html on inexistant sub path') .option('--basic-auth-username [username]', 'set basic auth username') .option('--basic-auth-password [password]', 'set basic auth password') .option('--monitor [frontend-app]', 'frontend app monitoring (auto integrate snippet on html files)') .description('serve a directory over http via port') .action(function (path, port, cmd) { pm2.serve(path, port || cmd.port, cmd, commander); }); commander.command('autoinstall') .action(function() { pm2.autoinstall() }) commander.command('examples') .description('display pm2 usage examples') .action(() => { console.log(cst.PREFIX_MSG + chalk.gray('pm2 usage examples:\n')); displayExamples(); process.exit(cst.SUCCESS_EXIT); }) // // Catch all // commander.command('*') .action(function() { console.log(cst.PREFIX_MSG_ERR + chalk.bold('Command not found\n')); displayUsage(); // Check if it does not forget to close fds from RPC process.exit(cst.ERROR_EXIT); }); // // Display help if 0 arguments passed to pm2 // if (process.argv.length == 2) { commander.parse(process.argv); displayUsage(); // Check if it does not forget to close fds from RPC process.exit(cst.ERROR_EXIT); } ================================================ FILE: lib/binaries/DevCLI.js ================================================ 'use strict'; process.env.PM2_NO_INTERACTION = 'true'; // Do not print banner process.env.PM2_DISCRETE_MODE = true; var commander = require('commander'); var PM2 = require('../..'); var Log = require('../API/Log'); var cst = require('../../constants.js'); var pkg = require('../../package.json'); var chalk = require('ansis'); var path = require('path'); var fmt = require('../tools/fmt.js'); var exec = require('child_process').exec; var os = require('os'); commander.version(pkg.version) .description('pm2-dev monitor for any file changes and automatically restart it') .option('--raw', 'raw log output') .option('--timestamp', 'print timestamp') .option('--node-args ', 'space delimited arguments to pass to node in cluster mode - e.g. --node-args="--debug=7001 --trace-deprecation"') .option('--ignore [files]', 'files to ignore while watching') .option('--post-exec [cmd]', 'execute extra command after change detected') .option('--silent-exec', 'do not output result of post command', false) .option('--test-mode', 'debug mode for test suit') .option('--interpreter ', 'the interpreter pm2 should use for executing app (bash, python...)') .option('--env [name]', 'select env_[name] env variables in process config file') .option('--auto-exit', 'exit if all processes are errored/stopped or 0 apps launched') .usage('pm2-dev app.js'); var pm2 = new PM2.custom({ pm2_home : path.join(os.homedir ? os.homedir() : (process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE), '.pm2-dev') }); pm2.connect(function() { commander.parse(process.argv); }); function postExecCmd(command, cb) { var exec_cmd = exec(command); if (commander.silentExec !== true) { exec_cmd.stdout.on('data', function(data) { process.stdout.write(data); }); exec_cmd.stderr.on('data', function(data) { process.stderr.write(data); }); } exec_cmd.on('close', function done() { if (cb) cb(null); }); exec_cmd.on('error', function (err) { console.error(err.stack || err); }); }; function run(cmd, opts) { var timestamp = opts.timestamp; opts.watch = true; opts.autostart = true; opts.autorestart = true; opts.restart_delay = 1000 if (opts.autoExit) autoExit(); if (opts.ignore) { opts.ignore_watch = opts.ignore.split(',') opts.ignore_watch.push('node_modules'); } if (timestamp === true) timestamp = 'YYYY-MM-DD-HH:mm:ss'; pm2.start(cmd, opts, function(err, procs) { if (err) { console.error(err); pm2.destroy(function() { process.exit(0); }); return false; } if (opts.testMode) { return pm2.disconnect(function() { console.log('disconnected succesfully from pm2-dev') }); } fmt.title('PM2 development mode'); fmt.field('Apps started', procs.map(function(p) { return p.pm2_env.name } )); fmt.field('Processes started', chalk.bold(procs.length)); fmt.field('Watch and Restart', chalk.green('Enabled')); fmt.field('Ignored folder', opts.ignore_watch || 'node_modules'); if (opts.postExec) fmt.field('Post restart cmd', opts.postExec); fmt.sep(); setTimeout(function() { pm2.Client.launchBus(function(err, bus) { bus.on('process:event', function(packet) { if (packet.event == 'online') { if (opts.postExec) postExecCmd(opts.postExec); } }); }); }, 1000); Log.devStream(pm2.Client, 'all', opts.raw, timestamp, false); process.on('SIGINT', function() { console.log('>>>>> [PM2 DEV] Stopping current development session'); pm2.delete('all', function() { pm2.destroy(function() { process.exit(0); }); }); }); }); } commander.command('*') .action(function(cmd, opts){ run(cmd, commander); }); commander.command('start ') .description('start target config file/script in development mode') .action(function(cmd, opts) { run(cmd, commander); }); function exitPM2() { if (pm2 && pm2.connected == true) { console.log(chalk.green.bold('>>> Exiting PM2')); pm2.kill(function() { process.exit(0); }); } else process.exit(0); } function autoExit(final) { setTimeout(function() { pm2.list(function(err, apps) { if (err) console.error(err.stack || err); var online_count = 0; apps.forEach(function(app) { if (app.pm2_env.status == cst.ONLINE_STATUS || app.pm2_env.status == cst.LAUNCHING_STATUS) online_count++; }); if (online_count == 0) { console.log('0 application online, exiting'); if (final == true) process.exit(1); else autoExit(true); return false; } autoExit(false); }); }, 3000); } if (process.argv.length == 2) { commander.outputHelp(); exitPM2(); } ================================================ FILE: lib/binaries/Runtime.js ================================================ 'use strict'; var commander = require('commander'); var PM2 = require('../..'); var Log = require('../../lib/API/Log'); var cst = require('../../constants.js'); var pkg = require('../../package.json'); var path = require('path'); var pm2; // Do not print banner process.env.PM2_DISCRETE_MODE = true; commander.version(pkg.version) .description('pm2-runtime is an automatic pmx injection that runs in simulated no-daemon environment') .option('--auto-manage', 'keep application online after command exit') .option('--fast-boot', 'boot app faster by keeping pm2 runtime online in background (effective at second exit/start)') .option('--web [port]', 'launch process web api on [port] default to 9615') .option('--secret [key]', 'PM2 plus secret key') .option('--public [key]', 'PM2 plus public key') .option('--machine-name [name]', 'PM2 plus machine name') .option('--env [name]', 'select env_[name] env variables in process config file') .option('--watch', 'Watch and Restart') .option('-i --instances ', 'launch [number] instances with load-balancer') .usage('pm2-runtime app.js'); commander.command('*') .action(function(cmd){ pm2 = new PM2.custom({ pm2_home : path.join(process.env.HOME, '.pm3'), secret_key : cst.SECRET_KEY || commander.secret, public_key : cst.PUBLIC_KEY || commander.public, machine_name : cst.MACHINE_NAME || commander.machineName }); pm2.connect(function() { if (commander.web) { var port = commander.web === true ? cst.WEB_PORT : commander.web; pm2.web(port); } pm2.start(cmd, commander, function(err, obj) { if (process.env.PM2_RUNTIME_DEBUG) { return pm2.disconnect(function() {}); } if (err) { console.error(err); return process.exit(1); } var pm_id = obj[0].pm2_env.pm_id; if (commander.instances == undefined) { return pm2.attach(pm_id, function() { exitPM2(); }); } if (commander.json === true) Log.jsonStream(pm2.Client, pm_id); else if (commander.format === true) Log.formatStream(pm2.Client, pm_id, false, 'YYYY-MM-DD-HH:mm:ssZZ'); else Log.stream(pm2.Client, 'all', true); }); }); }); if (process.argv.length == 2) { commander.outputHelp(); process.exit(1); } process.on('SIGINT', function() { exitPM2(); }); process.on('SIGTERM', function() { exitPM2(); }); commander.parse(process.argv); function exitPM2() { console.log('Exited at %s', new Date()); if (commander.autoManage) return process.exit(0); if (commander.fastBoot) { return pm2.delete('all', function() { process.exit(0); }); } pm2.kill(function() { process.exit(0); }); } ================================================ FILE: lib/binaries/Runtime4Docker.js ================================================ 'use strict'; /** * Specialized PM2 CLI for Containers */ var commander = require('commander'); var PM2 = require('../..'); var Log = require('../../lib/API/Log'); var cst = require('../../constants.js'); var pkg = require('../../package.json'); var path = require('path'); var DEFAULT_FAIL_COUNT = 3; process.env.PM2_DISCRETE_MODE = true; commander.version(pkg.version) .description('pm2-runtime is a drop-in replacement Node.js binary for containers') .option('-i --instances ', 'launch [number] of processes automatically load-balanced. Increase overall performances and performance stability.') .option('--secret [key]', '[MONITORING] PM2 plus secret key') .option('--no-autostart', 'add an app without automatic start') .option('--no-autorestart', 'start an app without automatic restart') .option('--stop-exit-codes ', 'specify a list of exit codes that should skip automatic restart') .option('--node-args ', 'space delimited arguments to pass to node in cluster mode - e.g. --node-args="--debug=7001 --trace-deprecation"') .option('-n --name ', 'set a for script') .option('--max-memory-restart ', 'specify max memory amount used to autorestart (in octet or use syntax like 100M)') .option('-c --cron ', 'restart a running process based on a cron pattern') .option('--interpreter ', 'the interpreter pm2 should use for executing app (bash, python...)') .option('--public [key]', '[MONITORING] PM2 plus public key') .option('--machine-name [name]', '[MONITORING] PM2 plus machine name') .option('--trace', 'enable transaction tracing with km') .option('--v8', 'enable v8 data collecting') .option('--format', 'output logs formated like key=val') .option('--raw', 'raw output (default mode)') .option('--formatted', 'formatted log output |id|app|log') .option('--json', 'output logs in json format') .option('--delay ', 'delay start of configuration file by ', 0) .option('--web [port]', 'launch process web api on [port] (default to 9615)') .option('--only ', 'only act on one application of configuration') .option('--no-auto-exit', 'do not exit if all processes are errored/stopped or 0 apps launched') .option('--env [name]', 'inject env_[name] env variables in process config file') .option('--watch', 'watch and restart application on file change') .option('--error ', 'error log file destination (default disabled)', '/dev/null') .option('--output ', 'output log file destination (default disabled)', '/dev/null') .option('--deep-monitoring', 'enable all monitoring tools (equivalent to --v8 --event-loop-inspector --trace)') .allowUnknownOption() .usage('app.js'); commander.command('*') .action(function(cmd){ Runtime.instanciate(cmd); }); commander.command('start ') .description('start an application or json ecosystem file') .action(function(cmd) { Runtime.instanciate(cmd); }); if (process.argv.length == 2) { commander.outputHelp(); process.exit(1); } var Runtime = { pm2 : null, instanciate : function(cmd) { this.pm2 = new PM2.custom({ pm2_home : process.env.PM2_HOME || path.join(process.env.HOME, '.pm2'), secret_key : cst.SECRET_KEY || commander.secret, public_key : cst.PUBLIC_KEY || commander.public, machine_name : cst.MACHINE_NAME || commander.machineName, daemon_mode : process.env.PM2_RUNTIME_DEBUG || false }); this.pm2.connect(function(err, pm2_meta) { process.on('SIGINT', function() { Runtime.exit(); }); process.on('SIGTERM', function() { Runtime.exit(); }); Runtime.startLogStreaming(); Runtime.startApp(cmd, function(err) { if (err) { console.error(err.message || err); return Runtime.exit(); } }); }); }, /** * Log Streaming Management */ startLogStreaming : function() { if (commander.json === true) Log.jsonStream(this.pm2.Client, 'all'); else if (commander.format === true) Log.formatStream(this.pm2.Client, 'all', false, 'YYYY-MM-DD-HH:mm:ssZZ'); else Log.stream(this.pm2.Client, 'all', !commander.formatted, commander.timestamp, true); }, /** * Application Startup */ startApp : function(cmd, cb) { function exec() { this.pm2.start(cmd, commander, function(err, obj) { if (err) return cb(err); if (obj && obj.length == 0) return cb(new Error(`0 application started (no apps to run on ${cmd})`)) if (commander.web) { var port = commander.web === true ? cst.WEB_PORT : commander.web; Runtime.pm2.web(port); } if (commander.autoExit) { setTimeout(function() { Runtime.autoExitWorker(); }, 4000); } // For Testing purpose (allow to auto exit CLI) if (process.env.PM2_RUNTIME_DEBUG) Runtime.pm2.disconnect(function() {}); return cb(null, obj); }); } // via --delay option setTimeout(exec.bind(this), commander.delay * 1000); }, /** * Exit runtime mgmt */ exit : function(code) { if (!this.pm2) return process.exit(1); this.pm2.kill(function() { process.exit(code || 0); }); }, /** * Exit current PM2 instance if 0 app is online * function activated via --auto-exit */ autoExitWorker : function(fail_count) { var interval = 2000; if (typeof(fail_count) =='undefined') fail_count = DEFAULT_FAIL_COUNT; var timer = setTimeout(function () { Runtime.pm2.list(function (err, apps) { if (err) { console.error('Could not run pm2 list'); return Runtime.autoExitWorker(); } var appOnline = 0; apps.forEach(function (app) { if (!app.pm2_env.pmx_module && (app.pm2_env.status === cst.ONLINE_STATUS || app.pm2_env.status === cst.LAUNCHING_STATUS)) { appOnline++; } }); if (appOnline === 0) { console.log('0 application online, retry =', fail_count); if (fail_count <= 0) return Runtime.exit(2); return Runtime.autoExitWorker(--fail_count); } Runtime.autoExitWorker(); }); }, interval); timer.unref(); } } commander.parse(process.argv); ================================================ FILE: lib/completion.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ var fs = require('fs'), pth = require('path'); // hacked from node-tabtab 0.0.4 https://github.com/mklabs/node-tabtab.git // Itself based on npm completion by @isaac exports.complete = function complete(name, completer, cb) { // cb not there, assume callback is completer and // the completer is the executable itself if(!cb) { cb = completer; completer = name; } var env = parseEnv(); // if not a complete command, return here. if(!env.complete) return cb(); // if install cmd, add complete script to either ~/.bashrc or ~/.zshrc if(env.install) return install(name, completer, function(err, state) { console.log(state || err.message); if(err) return cb(err); cb(null, null, state); }); // if install cmd, add complete script to either ~/.bashrc or ~/.zshrc if(env.uninstall) return uninstall(name, completer, function(err, state) { console.log(state || err.message); if(err) return cb(err); cb(null, null, state); }); // if the COMP_* are not in the env, then dump the install script. if(!env.words || !env.point || !env.line) return script(name, completer, function(err, content) { if(err) return cb(err); process.stdout.write(content, function (n) { cb(null, null, content); }); process.stdout.on("error", function (er) { // Darwin is a real dick sometimes. // // This is necessary because the "source" or "." program in // bash on OS X closes its file argument before reading // from it, meaning that you get exactly 1 write, which will // work most of the time, and will always raise an EPIPE. // // Really, one should not be tossing away EPIPE errors, or any // errors, so casually. But, without this, `. <(npm completion)` // can never ever work on OS X. // -- isaacs // https://github.com/isaacs/npm/blob/master/lib/completion.js#L162 if (er.errno === "EPIPE") er = null cb(er, null, content); }); cb(null, null, content); }); var partial = env.line.substr(0, env.point), last = env.line.split(' ').slice(-1).join(''), lastPartial = partial.split(' ').slice(-1).join(''), prev = env.line.split(' ').slice(0, -1).slice(-1)[0]; cb(null, { line: env.line, words: env.words, point: env.point, partial: partial, last: last, prev: prev, lastPartial: lastPartial }); }; // simple helper function to know if the script is run // in the context of a completion command. Also mapping the // special ` completion` cmd. exports.isComplete = function isComplete() { var env = parseEnv(); return env.complete || (env.words && env.point && env.line); }; exports.parseOut = function parseOut(str) { var shorts = str.match(/\s-\w+/g); var longs = str.match(/\s--\w+/g); return { shorts: shorts.map(trim).map(cleanPrefix), longs: longs.map(trim).map(cleanPrefix) }; }; // specific to cake case exports.parseTasks = function(str, prefix, reg) { var tasks = str.match(reg || new RegExp('^' + prefix + '\\s[^#]+', 'gm')) || []; return tasks.map(trim).map(function(s) { return s.replace(prefix + ' ', ''); }); }; exports.log = function log(arr, o, prefix) { prefix = prefix || ''; arr = Array.isArray(arr) ? arr : [arr]; arr.filter(abbrev(o)).forEach(function(v) { console.log(prefix + v); }); } function trim (s) { return s.trim(); } function cleanPrefix(s) { return s.replace(/-/g, ''); } function abbrev(o) { return function(it) { return new RegExp('^' + o.last.replace(/^--?/g, '')).test(it); }} // output the completion.sh script to the console for install instructions. // This is actually a 'template' where the package name is used to setup // the completion on the right command, and properly name the bash/zsh functions. function script(name, completer, cb) { var p = pth.join(__dirname, 'completion.sh'); fs.readFile(p, 'utf8', function (er, d) { if (er) return cb(er); cb(null, d); }); } function install(name, completer, cb) { var markerIn = '###-begin-' + name + '-completion-###', markerOut = '###-end-' + name + '-completion-###'; var rc, scriptOutput; readRc(completer, function(err, file) { if(err) return cb(err); var part = file.split(markerIn)[1]; if(part) { return cb(null, ' ✗ ' + completer + ' tab-completion has been already installed. Do nothing.'); } rc = file; next(); }); script(name, completer, function(err, file) { scriptOutput = file; next(); }); function next() { if(!rc || !scriptOutput) return; writeRc(rc + scriptOutput, function(err) { if(err) return cb(err); return cb(null, ' ✓ ' + completer + ' tab-completion installed.'); }); } } function uninstall(name, completer, cb) { var markerIn = '\n\n###-begin-' + name + '-completion-###', markerOut = '###-end-' + name + '-completion-###\n'; readRc(completer, function(err, file) { if(err) return cb(err); var part = file.split(markerIn)[1]; if(!part) { return cb(null, ' ✗ ' + completer + ' tab-completion has been already uninstalled. Do nothing.'); } part = markerIn + part.split(markerOut)[0] + markerOut; writeRc(file.replace(part, ''), function(err) { if(err) return cb(err); return cb(null, ' ✓ ' + completer + ' tab-completion uninstalled.'); }); }); } function readRc(completer, cb) { var file = '.' + process.env.SHELL.match(/\/bin\/(\w+)/)[1] + 'rc', filepath = pth.join(process.env.HOME, file); fs.lstat(filepath, function (err, stats) { if(err) return cb(new Error("No " + file + " file. You'll have to run instead: " + completer + " completion >> ~/" + file)); fs.readFile(filepath, 'utf8', cb); }); } function writeRc(content, cb) { var file = '.' + process.env.SHELL.match(/\/bin\/(\w+)/)[1] + 'rc', filepath = pth.join(process.env.HOME, file); fs.lstat(filepath, function (err, stats) { if(err) return cb(new Error("No " + file + " file. You'll have to run instead: " + completer + " completion >> ~/" + file)); fs.writeFile(filepath, content, cb); }); } function installed (marker, completer, cb) { readRc(completer, function(err, file) { if(err) return cb(err); var installed = file.match(marker); return cb(!!installed); }); } function parseEnv() { var args = process.argv.slice(2), complete = args[0] === 'completion'; return { args: args, complete: complete, install: complete && args[1] === 'install', uninstall: complete && args[1] === 'uninstall', words: +process.env.COMP_CWORD, point: +process.env.COMP_POINT, line: process.env.COMP_LINE } }; ================================================ FILE: lib/completion.sh ================================================ ###-begin-pm2-completion-### ### credits to npm for the completion file model # # Installation: pm2 completion >> ~/.bashrc (or ~/.zshrc) # COMP_WORDBREAKS=${COMP_WORDBREAKS/=/} COMP_WORDBREAKS=${COMP_WORDBREAKS/@/} export COMP_WORDBREAKS if type complete &>/dev/null; then _pm2_completion () { local si="$IFS" IFS=$'\n' COMPREPLY=($(COMP_CWORD="$COMP_CWORD" \ COMP_LINE="$COMP_LINE" \ COMP_POINT="$COMP_POINT" \ pm2 completion -- "${COMP_WORDS[@]}" \ 2>/dev/null)) || return $? IFS="$si" } complete -o default -F _pm2_completion pm2 elif type compctl &>/dev/null; then _pm2_completion () { local cword line point words si read -Ac words read -cn cword let cword-=1 read -l line read -ln point si="$IFS" IFS=$'\n' reply=($(COMP_CWORD="$cword" \ COMP_LINE="$line" \ COMP_POINT="$point" \ pm2 completion -- "${words[@]}" \ 2>/dev/null)) || return $? IFS="$si" } compctl -K _pm2_completion + -f + pm2 fi ###-end-pm2-completion-### ================================================ FILE: lib/motd ================================================ ------------- __/\\\\\\\\\\\\\____/\\\\____________/\\\\____/\\\\\\\\\_____ _\/\\\/////////\\\_\/\\\\\\________/\\\\\\__/\\\///////\\\___ _\/\\\_______\/\\\_\/\\\//\\\____/\\\//\\\_\///______\//\\\__ _\/\\\\\\\\\\\\\/__\/\\\\///\\\/\\\/_\/\\\___________/\\\/___ _\/\\\/////////____\/\\\__\///\\\/___\/\\\________/\\\//_____ _\/\\\_____________\/\\\____\///_____\/\\\_____/\\\//________ _\/\\\_____________\/\\\_____________\/\\\___/\\\/___________ _\/\\\_____________\/\\\_____________\/\\\__/\\\\\\\\\\\\\\\_ _\///______________\///______________\///__\///////////////__ Runtime Edition PM2 is a Production Process Manager for Node.js applications with a built-in Load Balancer. Start and Daemonize any application: $ pm2 start app.js Load Balance 4 instances of api.js: $ pm2 start api.js -i 4 Monitor in production: $ pm2 monitor Make pm2 auto-boot at server restart: $ pm2 startup To go further checkout: http://pm2.io/ ------------- ================================================ FILE: lib/templates/Dockerfiles/Dockerfile-java.tpl ================================================ FROM anapsix/alpine-java:latest RUN apk update && apk add git && rm -rf /var/cache/apk/* RUN npm install pm2@next -g RUN mkdir -p /var/app WORKDIR /var/app ================================================ FILE: lib/templates/Dockerfiles/Dockerfile-nodejs.tpl ================================================ FROM keymetrics/pm2:latest RUN mkdir -p /var/app WORKDIR /var/app COPY ./package.json /var/app RUN npm install ================================================ FILE: lib/templates/Dockerfiles/Dockerfile-ruby.tpl ================================================ FROM anapsix/alpine-ruby:latest RUN apk update && apk add git && rm -rf /var/cache/apk/* RUN npm install pm2@next -g RUN mkdir -p /var/app WORKDIR /var/app ================================================ FILE: lib/templates/ecosystem-es.tpl ================================================ const config = { apps : [{ script: 'index.js', watch: '.' }, { script: './service-worker/', watch: ['./service-worker'] }], deploy : { production : { user : 'SSH_USERNAME', host : 'SSH_HOSTMACHINE', ref : 'origin/master', repo : 'GIT_REPOSITORY', path : 'DESTINATION_PATH', 'pre-deploy-local': '', 'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env production', 'pre-setup': '' } } }; export default config; ================================================ FILE: lib/templates/ecosystem-simple-es.tpl ================================================ const config = { apps : [{ name : "app1", script : "./app.js" }] } export default config; ================================================ FILE: lib/templates/ecosystem-simple.tpl ================================================ module.exports = { apps : [{ name : "app1", script : "./app.js" }] } ================================================ FILE: lib/templates/ecosystem.tpl ================================================ module.exports = { apps : [{ script: 'index.js', watch: '.' }, { script: './service-worker/', watch: ['./service-worker'] }], deploy : { production : { user : 'SSH_USERNAME', host : 'SSH_HOSTMACHINE', ref : 'origin/master', repo : 'GIT_REPOSITORY', path : 'DESTINATION_PATH', 'pre-deploy-local': '', 'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env production', 'pre-setup': '' } } }; ================================================ FILE: lib/templates/init-scripts/launchd.tpl ================================================ Label com.PM2 UserName %USER% KeepAlive ProgramArguments /bin/sh -c %PM2_PATH% resurrect RunAtLoad OnDemand LaunchOnlyOnce EnvironmentVariables PATH %NODE_PATH% PM2_HOME %HOME_PATH% StandardErrorPath /tmp/com.PM2.err StandardOutPath /tmp/com.PM2.out ================================================ FILE: lib/templates/init-scripts/openrc.tpl ================================================ #!/sbin/openrc-run # Copyright 2013-2022 the PM2 project authors. All rights reserved. # Init script automatically generated by pm2 startup description="Production process manager for Node.js apps with a built-in load balancer." extra_started_commands="reload" PM2="%PM2_PATH%" user=${PM2_USER:-%USER%} export PM2_HOME=$(eval echo ~${user})"/.pm2/" # Options for start-stop-daemon (default start function) command=${PM2} command_user=${user} command_args="resurrect" pidfile=${PM2_HOME}/pm2.pid run_pm2_as_user() { einfo "${PM2} $@" eval su -l ${user} -c \'${PM2} $@\' } depend() { need net need localmount after bootmisc } start_post() { if [ "${user}" == "root" ]; then ewarn "PM2: Better run this daemon as a non root user. To set this user create" ewarn "PM2: /etc/conf.d/pm2 file and define 'PM2_USER=user' there." ewarn "PM2: Note user MUST have home directory for PM2 logs/state/etc..." fi einfo "PM2: Process Manager started. To start services run:" einfo "PM2: # su -l ${user} -c '$PM2 start /path/to/app'" } stop() { ebegin "Stopping PM2 process manager..." run_pm2_as_user dump run_pm2_as_user kill eend $? } reload() { ebegin "Reloading pm2" run_pm2_as_user reload all eend $? } # vim: ts=4 ================================================ FILE: lib/templates/init-scripts/pm2-init-amazon.sh ================================================ #!/bin/bash # # pm2 Process manager for NodeJS # # chkconfig: 345 80 20 # # description: PM2 next gen process manager for Node.js # processname: pm2 # ### BEGIN INIT INFO # Provides: pm2 # Required-Start: $local_fs $remote_fs # Required-Stop: $local_fs $remote_fs # Should-Start: $network # Should-Stop: $network # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: PM2 init script # Description: PM2 is the next gen process manager for Node.js ### END INIT INFO NAME=pm2 PM2=%PM2_PATH% USER=%USER% export PATH=%NODE_PATH%:$PATH export PM2_HOME="%HOME_PATH%" lockfile="/var/lock/subsys/pm2-init.sh" super() { su - $USER -c "PATH=$PATH; PM2_HOME=$PM2_HOME $*" } start() { echo "Starting $NAME" super $PM2 resurrect retval=$? [ $retval -eq 0 ] && touch $lockfile } stop() { echo "Stopping $NAME" super $PM2 kill rm -f $lockfile } restart() { echo "Restarting $NAME" stop start } reload() { echo "Reloading $NAME" super $PM2 reload all } status() { echo "Status for $NAME:" super $PM2 list RETVAL=$? } case "$1" in start) start ;; stop) stop ;; status) status ;; restart) restart ;; reload) reload ;; *) echo "Usage: {start|stop|status|restart|reload}" exit 1 ;; esac exit $RETVAL ================================================ FILE: lib/templates/init-scripts/rcd-openbsd.tpl ================================================ #!/bin/sh # # from /usr/ports/infrastructure/templates/rc.template daemon="/usr/local/bin/pm2" #daemon_flags= #daemon_rtable=0 #daemon_timeout="30" daemon_user="%USER%" . /etc/rc.d/rc.subr pexp="node: PM2.*God Daemon.*" #rc_bg= # (undefined) #rc_reload= # (undefined) #rc_usercheck=YES #rc_pre() { #} rc_start() { ${rcexec} "${daemon} ${daemon_flags} resurrect" } #rc_check() { # pgrep -T "${daemon_rtable}" -q -xf "${pexp}" #} rc_reload() { ${rcexec} "${daemon} reload all" #pkill -HUP -T "${daemon_rtable}" -xf "${pexp}" } #rc_stop() { # pkill -T "${daemon_rtable}" -xf "${pexp}" #} #rc_post() { #} rc_cmd $1 ================================================ FILE: lib/templates/init-scripts/rcd.tpl ================================================ #!/bin/sh # PROVIDE: pm2 # REQUIRE: LOGIN # KEYWORD: shutdown . /etc/rc.subr name="%SERVICE_NAME%" rcvar="%SERVICE_NAME%_enable" start_cmd="pm2_start" stop_cmd="pm2_stop" reload_cmd="pm2_reload" status_cmd="pm2_status" extra_commands="reload status" pm2() { env PATH="$PATH:%NODE_PATH%" PM2_HOME="%HOME_PATH%" su -m "%USER%" -c "%PM2_PATH% $*" } pm2_start() { pm2 resurrect } pm2_stop() { pm2 kill } pm2_reload() { pm2 reload all } pm2_status() { pm2 list } load_rc_config $name run_rc_command "$1" ================================================ FILE: lib/templates/init-scripts/smf.tpl ================================================ ================================================ FILE: lib/templates/init-scripts/systemd-online.tpl ================================================ [Unit] Description=PM2 process manager Documentation=https://pm2.keymetrics.io/ After=network-online.target Restart=on-failure [Service] Type=forking User=%USER% LimitNOFILE=infinity LimitNPROC=infinity LimitCORE=infinity Environment=PATH=%NODE_PATH%:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin Environment=PM2_HOME=%HOME_PATH% PIDFile=%HOME_PATH%/pm2.pid ExecStart=%PM2_PATH% resurrect ExecReload=%PM2_PATH% reload all ExecStop=%PM2_PATH% kill [Install] WantedBy=network-online.target ================================================ FILE: lib/templates/init-scripts/systemd.tpl ================================================ [Unit] Description=PM2 process manager Documentation=https://pm2.keymetrics.io/ After=network.target [Service] Type=forking User=%USER% LimitNOFILE=infinity LimitNPROC=infinity LimitCORE=infinity Environment=PATH=%NODE_PATH%:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin Environment=PM2_HOME=%HOME_PATH% PIDFile=%HOME_PATH%/pm2.pid Restart=on-failure ExecStart=%PM2_PATH% resurrect ExecReload=%PM2_PATH% reload all ExecStop=%PM2_PATH% kill [Install] WantedBy=multi-user.target ================================================ FILE: lib/templates/init-scripts/upstart.tpl ================================================ #!/bin/bash ### BEGIN INIT INFO # Provides: pm2 # Required-Start: $local_fs $remote_fs $network # Required-Stop: $local_fs $remote_fs $network # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: PM2 Init script # Description: PM2 process manager ### END INIT INFO NAME=pm2 PM2=%PM2_PATH% USER=%USER% DEFAULT=/etc/default/$NAME export PATH=%NODE_PATH%:$PATH export PM2_HOME="%HOME_PATH%" # The following variables can be overwritten in $DEFAULT # maximum number of open files MAX_OPEN_FILES= # overwrite settings from default file if [ -f "$DEFAULT" ]; then . "$DEFAULT" fi # set maximum open files if set if [ -n "$MAX_OPEN_FILES" ]; then ulimit -n $MAX_OPEN_FILES fi get_user_shell() { local shell shell=$(getent passwd "${1:-$(whoami)}" | cut -d: -f7 | sed -e 's/[[:space:]]*$//') if [[ $shell == *"/sbin/nologin" ]] || [[ $shell == "/bin/false" ]] || [[ -z "$shell" ]]; then shell="/bin/bash" fi echo "$shell" } super() { local shell shell=$(get_user_shell $USER) su - "$USER" -s "$shell" -c "PATH=$PATH; PM2_HOME=$PM2_HOME $*" } start() { echo "Starting $NAME" super $PM2 resurrect } stop() { super $PM2 kill } restart() { echo "Restarting $NAME" stop start } reload() { echo "Reloading $NAME" super $PM2 reload all } status() { echo "Status for $NAME:" super $PM2 list RETVAL=$? } case "$1" in start) start ;; stop) stop ;; status) status ;; restart) restart ;; reload) reload ;; force-reload) reload ;; *) echo "Usage: {start|stop|status|restart|reload|force-reload}" exit 1 ;; esac exit $RETVAL ================================================ FILE: lib/templates/logrotate.d/pm2 ================================================ %HOME_PATH%/pm2.log %HOME_PATH%/logs/*.log { rotate 12 weekly missingok notifempty compress delaycompress copytruncate create 0640 %USER% %USER% } ================================================ FILE: lib/templates/sample-apps/http-server/README.md ================================================ # Basic HTTP Server and Cluster mode In this boilerplate it will start an http server in cluster mode. You can check the content of the ecosystem.config.js on how to start mutliple instances of the same HTTP application in order to get the most from your working system. ## Via CLI Via CLI you can start any HTTP/TCP application in cluster mode with: ```bash $ pm2 start api.js -i max ``` ================================================ FILE: lib/templates/sample-apps/http-server/api.js ================================================ var http = require('http'); var server = http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(process.env.PORT || 8000, function() { console.log('App listening on port %d', server.address().port); }); ================================================ FILE: lib/templates/sample-apps/http-server/ecosystem.config.js ================================================ module.exports = { apps : [{ name: 'API', script: 'api.js', instances: 4, max_memory_restart: '1G', env: { NODE_ENV: 'development' }, env_production: { NODE_ENV: 'production' } }] }; ================================================ FILE: lib/templates/sample-apps/http-server/package.json ================================================ { "name": "simple-http-server", "version": "1.0.0", "description": "Simple HTTP server that can be used in cluster mode", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" } ================================================ FILE: lib/templates/sample-apps/pm2-plus-metrics-actions/README.md ================================================ # pm2 custom metrics boilerplate In this boilerplate you will discover a working example of custom metrics feature. Metrics covered are: - io.metric - io.counter - io.meter - io.histogram ## What is Custom Metrics? Custom metrics is a powerfull way to get more visibility from a running application. It will allow you to monitor in realtime the current value of variables, know the number of actions being processed, measure latency and much more. Once you have plugged in some custom metrics you will be able to monitor their value in realtime with `pm2 monit` Or `pm2 describe` Or on the PM2+ Web interface `pm2 open` ## Example ```javascript const io = require('@pm2/io') const currentReq = io.counter({ name: 'CM: Current Processing', type: 'counter' }) setInterval(() => { currentReq.inc() }, 1000) ``` ## Documentation https://doc.pm2.io/en/plus/guide/custom-metrics/ ================================================ FILE: lib/templates/sample-apps/pm2-plus-metrics-actions/custom-metrics.js ================================================ const io = require('@pm2/io') // Straight Metric var user_count = 10 const users = io.metric({ name: 'CM: Realtime user', value: () => { return user_count } }) // or users.set(user_count) // Counter (.inc() .dec()) const currentReq = io.counter({ name: 'CM: Current Processing', type: 'counter' }) setInterval(() => { currentReq.inc() }, 1000) // Meter const reqsec = io.meter({ name: 'CM: req/sec' }) setInterval(() => { reqsec.mark() }, 100) // Histogram const latency = io.histogram({ name: 'CM: latency' }); var latencyValue = 0; setInterval(() => { latencyValue = Math.round(Math.random() * 100); latency.update(latencyValue); }, 100) //////////////////// // Custom Actions // //////////////////// io.action('add user', (done) => { user_count++ done({success:true}) }) io.action('remove user', (done) => { user_count++ done({success:true}) }) io.action('with params', (arg, done) => { console.log(arg) done({success:arg}) }) ================================================ FILE: lib/templates/sample-apps/pm2-plus-metrics-actions/ecosystem.config.js ================================================ module.exports = { apps : [{ name: 'Custom Metrics', script: 'custom-metrics.js', env: { NODE_ENV: 'development' }, env_production: { NODE_ENV: 'production' } }] }; ================================================ FILE: lib/templates/sample-apps/pm2-plus-metrics-actions/package.json ================================================ { "name": "pm2-plus-custom-metrics", "version": "1.0.0", "description": "Example that shows how to use pm2+ custom metrics", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" } ================================================ FILE: lib/templates/sample-apps/python-app/README.md ================================================ # Managing a Python Application On this boilerlate you will see how you can start a Python application with PM2. ================================================ FILE: lib/templates/sample-apps/python-app/echo.py ================================================ #!/usr/bin/python import time while 1: print("Start : %s" % time.ctime()) print("second line") time.sleep(1) ================================================ FILE: lib/templates/sample-apps/python-app/ecosystem.config.js ================================================ module.exports = { apps : [{ name: 'API', script: 'echo.py', env: { NODE_ENV: 'development' }, env_production: { NODE_ENV: 'production' } }] }; ================================================ FILE: lib/templates/sample-apps/python-app/package.json ================================================ { "name": "python-app", "version": "1.0.0", "description": "Example on how to manage a Python application with PM2", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" } ================================================ FILE: lib/tools/Config.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ var util = require('util'); /** * Validator of configured file / commander options. */ var Config = module.exports = { _errMsgs: { 'require': '"%s" is required', 'type' : 'Expect "%s" to be a typeof %s, but now is %s', 'regex' : 'Verify "%s" with regex failed, %s', 'max' : 'The maximum of "%s" is %s, but now is %s', 'min' : 'The minimum of "%s" is %s, but now is %s' }, /** * Schema definition. * @returns {exports|*} */ get schema(){ // Cache. if (this._schema) { return this._schema; } // Render aliases. this._schema = require('../API/schema'); for (var k in this._schema) { if (k.indexOf('\\') > 0) { continue; } var aliases = [ k.split('_').map(function(n, i){ if (i != 0 && n && n.length > 1) { return n[0].toUpperCase() + n.slice(1); } return n; }).join('') ]; if (this._schema[k].alias && Array.isArray(this._schema[k].alias)) { // If multiple aliases, merge this._schema[k].alias.forEach(function(alias) { aliases.splice(0, 0, alias); }); } else if (this._schema[k].alias) aliases.splice(0, 0, this._schema[k].alias); this._schema[k].alias = aliases; } return this._schema; } }; /** * Filter / Alias options */ Config.filterOptions = function(cmd) { var conf = {}; var schema = this.schema; for (var key in schema) { var aliases = schema[key].alias; aliases && aliases.forEach(function(alias){ if (typeof(cmd[alias]) !== 'undefined') { conf[key] || (conf[key] = cmd[alias]); } }); } return conf; }; /** * Verify JSON configurations. * @param {Object} json * @returns {{errors: Array, config: {}}} */ Config.validateJSON = function(json){ // clone config var conf = Object.assign({}, json), res = {}; this._errors = []; var regexKeys = {}, defines = this.schema; for (var sk in defines) { // Pick up RegExp keys. if (sk.indexOf('\\') >= 0) { regexKeys[sk] = false; continue; } var aliases = defines[sk].alias; aliases && aliases.forEach(function(alias){ conf[sk] || (conf[sk] = json[alias]); }) var val = conf[sk]; delete conf[sk]; // Validate key-value pairs. if (val === undefined || val === null || ((val = this._valid(sk, val)) === null)) { // If value is not defined // Set default value (via schema.json) if (typeof(defines[sk].default) !== 'undefined') res[sk] = defines[sk].default; continue; } //console.log(sk, val, val === null, val === undefined); res[sk] = val; } // Validate RegExp values. var hasRegexKey = false; for (var k in regexKeys) { hasRegexKey = true; regexKeys[k] = new RegExp(k); } if (hasRegexKey) { for (var k in conf) { for (var rk in regexKeys) { if (regexKeys[rk].test(k)) if (this._valid(k, conf[k], defines[rk])) { res[k] = conf[k]; delete conf[k]; } } } } return {errors: this._errors, config: res}; }; /** * Validate key-value pairs by specific schema * @param {String} key * @param {Mixed} value * @param {Object} sch * @returns {*} * @private */ Config._valid = function(key, value, sch){ var sch = sch || this.schema[key], scht = typeof sch.type == 'string' ? [sch.type] : sch.type; // Required value. var undef = typeof value == 'undefined'; if(this._error(sch.require && undef, 'require', key)){ return null; } // If undefined, make a break. if (undef) { return null; } // Wrap schema types. scht = scht.map(function(t){ return '[object ' + t[0].toUpperCase() + t.slice(1) + ']' }); // Typeof value. var type = Object.prototype.toString.call(value), nt = '[object Number]'; // Auto parse Number if (type != '[object Boolean]' && scht.indexOf(nt) >= 0 && !isNaN(value)) { value = parseFloat(value); type = nt; } // Verify types. if (this._error(!~scht.indexOf(type), 'type', key, scht.join(' / '), type)) { return null; } // Verify RegExp if exists. if (this._error(type == '[object String]' && sch.regex && !(new RegExp(sch.regex)).test(value), 'regex', key, sch.desc || ('should match ' + sch.regex))) { return null; } // Verify maximum / minimum of Number value. if (type == '[object Number]') { if (this._error(typeof sch.max != 'undefined' && value > sch.max, 'max', key, sch.max, value)) { return null; } if (this._error(typeof sch.min != 'undefined' && value < sch.min, 'min', key, sch.min, value)) { return null; } } // If first type is Array, but current is String, try to split them. if(scht.length > 1 && type != scht[0] && type == '[object String]'){ if(scht[0] == '[object Array]') { value = value.split(/([\w\-]+\="[^"]*")|([\w\-]+\='[^']*')|"([^"]*)"|'([^']*)'|\s/) // unfortunately, js does not support lookahead RegExp (/(?= 2) { var seed = { 'sbyte': { 'G': 1024 * 1024 * 1024, 'M': 1024 * 1024, 'K': 1024 }, 'stime': { 'h': 60 * 60 * 1000, 'm': 60 * 1000, 's': 1000 } }[sch.ext_type]; if(seed){ value = parseFloat(value.slice(0, -1)) * (seed[value.slice(-1)]); } } return value; }; /** * Wrap errors. * @param {Boolean} possible A value indicates whether it is an error or not. * @param {String} type * @returns {*} * @private */ Config._error = function(possible, type){ if (possible) { var args = Array.prototype.slice.call(arguments); args.splice(0, 2, this._errMsgs[type]); this._errors && this._errors.push(util.format.apply(null, args)); } return possible; } ================================================ FILE: lib/tools/IsAbsolute.js ================================================ 'use strict'; function posix(path) { return path.charAt(0) === '/'; } function win32(path) { // https://github.com/nodejs/node/blob/b3fcc245fb25539909ef1d5eaa01dbf92e168633/lib/path.js#L56 var splitDeviceRe = /^([a-zA-Z]:|[\\/]{2}[^\\/]+[\\/]+[^\\/]+)?([\\/])?([\s\S]*?)$/; var result = splitDeviceRe.exec(path); var device = result[1] || ''; var isUnc = Boolean(device && device.charAt(1) !== ':'); // UNC paths are always absolute return Boolean(result[2] || isUnc); } module.exports = process.platform === 'win32' ? win32 : posix; module.exports.posix = posix; module.exports.win32 = win32; ================================================ FILE: lib/tools/copydirSync.js ================================================ var fs = require('fs'); var path = require('path'); /* options: { utimes: false, // Boolean | Object, keep utimes if true mode: false, // Boolean | Number, keep file mode if true cover: true, // Boolean, cover if file exists filter: true, // Boolean | Function, file filter } */ function copydirSync(from, to, options) { if (typeof options === 'function') { options = { filter: options }; } if(typeof options === 'undefined') options = {}; if(typeof options.cover === 'undefined') { options.cover = true; } options.filter = typeof options.filter === 'function' ? options.filter : function(state, filepath, filename) { return options.filter; }; var stats = fs.lstatSync(from); var statsname = stats.isDirectory() ? 'directory' : stats.isFile() ? 'file' : stats.isSymbolicLink() ? 'symbolicLink' : ''; var valid = options.filter(statsname, from, path.dirname(from), path.basename(from)); if (statsname === 'directory' || statsname === 'symbolicLink') { // Directory or SymbolicLink if(valid) { try { fs.statSync(to); } catch(err) { if(err.code === 'ENOENT') { fs.mkdirSync(to, { recursive: true }); options.debug && console.log('>> ' + to); } else { throw err; } } rewriteSync(to, options, stats); if (statsname != 'symbolicLink') listDirectorySync(from, to, options); } } else if(stats.isFile()) { // File if(valid) { if(options.cover) { writeFileSync(from, to, options, stats); } else { try { fs.statSync(to); } catch(err) { if(err.code === 'ENOENT') { writeFileSync(from, to, options, stats); } else { throw err; } } } } } else { throw new Error('stats invalid: '+ from); } }; function listDirectorySync(from, to, options) { var files = fs.readdirSync(from); copyFromArraySync(files, from, to, options); } function copyFromArraySync(files, from, to, options) { if(files.length === 0) return true; var f = files.shift(); copydirSync(path.join(from, f), path.join(to, f), options); copyFromArraySync(files, from, to, options); } function writeFileSync(from, to, options, stats) { fs.writeFileSync(to, fs.readFileSync(from, 'binary'), 'binary'); options.debug && console.log('>> ' + to); rewriteSync(to, options, stats); } function rewriteSync(f, options, stats, callback) { if(options.cover) { var mode = options.mode === true ? stats.mode : options.mode; var utimes = options.utimes === true ? { atime: stats.atime, mtime: stats.mtime } : options.utimes; mode && fs.chmodSync(f, mode); utimes && fs.utimesSync(f, utimes.atime, utimes.mtime); } return true; } module.exports = copydirSync; ================================================ FILE: lib/tools/deleteFolderRecursive.js ================================================ const fs = require('fs'); const Path = require('path'); const deleteFolderRecursive = function(path) { if (fs.existsSync(path)) { fs.readdirSync(path).forEach((file, index) => { const curPath = Path.join(path, file); if (fs.lstatSync(curPath).isDirectory()) { // recurse deleteFolderRecursive(curPath); } else { // delete file fs.unlinkSync(curPath); } }); fs.rmdirSync(path); } }; module.exports = deleteFolderRecursive ================================================ FILE: lib/tools/find-package-json.js ================================================ 'use strict'; var path = require('path') , fs = require('fs'); /** * Attempt to somewhat safely parse the JSON. * * @param {String} data JSON blob that needs to be parsed. * @returns {Object|false} Parsed JSON or false. * @api private */ function parse(data) { data = data.toString('utf-8'); // // Remove a possible UTF-8 BOM (byte order marker) as this can lead to parse // values when passed in to the JSON.parse. // if (data.charCodeAt(0) === 0xFEFF) data = data.slice(1); try { return JSON.parse(data); } catch (e) { return false; } } /** * Find package.json files. * * @param {String|Object} root The root directory we should start searching in. * @returns {Object} Iterator interface. * @api public */ module.exports = function find(root) { root = root || process.cwd(); if (typeof root !== "string") { if (typeof root === "object" && typeof root.filename === 'string') { root = root.filename; } else { throw new Error("Must pass a filename string or a module object to finder"); } } return { /** * Return the parsed package.json that we find in a parent folder. * * @returns {Object} Value, filename and indication if the iteration is done. * @api public */ next: function next() { if (root.match(/^(\w:\\|\/)$/)) return { value: undefined, filename: undefined, done: true }; var file = path.join(root, 'package.json') , data; root = path.resolve(root, '..'); if (fs.existsSync(file) && (data = parse(fs.readFileSync(file)))) { data.__path = file; return { value: data, filename: file, done: false }; } return next(); } }; }; ================================================ FILE: lib/tools/fmt.js ================================================ // -------------------------------------------------------------------------------------------------------------------- // // fmt.js - Command line output formatting. // // Copyright (c) 2012 Andrew Chilton - http://chilts.org/ // Written by Andrew Chilton // // License: http://opensource.org/licenses/MIT // // -------------------------------------------------------------------------------------------------------------------- var util = require('util'); // -------------------------------------------------------------------------------------------------------------------- var sep = '==============================================================================='; var line = '-------------------------------------------------------------------------------'; var field = ' '; // -------------------------------------------------------------------------------------------------------------------- // separator module.exports.separator = function() { console.log(sep); }; // alias the above module.exports.sep = module.exports.separator; // line module.exports.line = function() { console.log(line); }; // title module.exports.title = function(title) { var out = '--- ' + title + ' '; out += line.substr(out.length); console.log(out); }; // field module.exports.field = function(key, value) { console.log('' + key + field.substr(key.length) + ' : ' + value); }; // subfield module.exports.subfield = function(key, value) { console.log('- ' + key + field.substr(key.length + 2) + ' : ' + value); }; // list item module.exports.li = function(msg) { console.log('* ' + msg); }; // dump module.exports.dump = function(data, name) { if ( name ) { console.log(name + ' :', util.inspect(data, false, null, true)); } else { console.log(util.inspect(data, false, null, true)); } }; // msg module.exports.msg = function(msg) { console.log(msg); }; // -------------------------------------------------------------------------------------------------------------------- ================================================ FILE: lib/tools/isbinaryfile.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ var fs = require('fs'); var max_bytes = 512; module.exports = function(bytes, size) { // Read the file with no encoding for raw buffer access. if (size === undefined) { var file = bytes; try { if(!fs.statSync(file).isFile()) return false; } catch (err) { // otherwise continue on } var descriptor = fs.openSync(file, 'r'); try { bytes = Buffer.alloc(max_bytes); size = fs.readSync(descriptor, bytes, 0, bytes.length, 0); } finally { fs.closeSync(descriptor); } } // async version has a function instead of a `size` else if (typeof size === "function") { var file = bytes, callback = size; fs.stat(file, function(err, stat) { if (err || !stat.isFile()) return callback(null, false); fs.open(file, 'r', function(err, descriptor){ if (err) return callback(err); var bytes = Buffer.alloc(max_bytes); // Read the file with no encoding for raw buffer access. fs.read(descriptor, bytes, 0, bytes.length, 0, function(err, size, bytes){ fs.close(descriptor, function(err2){ if (err || err2) return callback(err || err2); return callback(null, isBinaryCheck(bytes, size)); }); }); }); }); } return isBinaryCheck(bytes, size); } function isBinaryCheck(bytes, size) { if (size === 0) return false; var suspicious_bytes = 0; var total_bytes = Math.min(size, max_bytes); if (size >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) { // UTF-8 BOM. This isn't binary. return false; } for (var i = 0; i < total_bytes; i++) { if (bytes[i] === 0) { // NULL byte--it's binary! return true; } else if ((bytes[i] < 7 || bytes[i] > 14) && (bytes[i] < 32 || bytes[i] > 127)) { // UTF-8 detection if (bytes[i] > 193 && bytes[i] < 224 && i + 1 < total_bytes) { i++; if (bytes[i] > 127 && bytes[i] < 192) { continue; } } else if (bytes[i] > 223 && bytes[i] < 240 && i + 2 < total_bytes) { i++; if (bytes[i] > 127 && bytes[i] < 192 && bytes[i + 1] > 127 && bytes[i + 1] < 192) { i++; continue; } } suspicious_bytes++; // Read at least 32 bytes before making a decision if (i > 32 && (suspicious_bytes * 100) / total_bytes > 10) { return true; } } } if ((suspicious_bytes * 100) / total_bytes > 10) { return true; } return false; } ================================================ FILE: lib/tools/json5.js ================================================ // json5.js // Modern JSON. See README.md for details. // // This file is based directly off of Douglas Crockford's json_parse.js: // https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js var JSON5 = (typeof exports === 'object' ? exports : {}); JSON5.parse = (function () { "use strict"; // This is a function that can parse a JSON5 text, producing a JavaScript // data structure. It is a simple, recursive descent parser. It does not use // eval or regular expressions, so it can be used as a model for implementing // a JSON5 parser in other languages. // We are defining the function inside of another function to avoid creating // global variables. var at, // The index of the current character ch, // The current character escapee = { "'": "'", '"': '"', '\\': '\\', '/': '/', '\n': '', // Replace escaped newlines in strings w/ empty string b: '\b', f: '\f', n: '\n', r: '\r', t: '\t' }, ws = [ ' ', '\t', '\r', '\n', '\v', '\f', '\xA0', '\uFEFF' ], text, error = function (m) { // Call error when something is wrong. var error = new SyntaxError(); error.message = m; error.at = at; error.text = text; throw error; }, next = function (c) { // If a c parameter is provided, verify that it matches the current character. if (c && c !== ch) { error("Expected '" + c + "' instead of '" + ch + "'"); } // Get the next character. When there are no more characters, // return the empty string. ch = text.charAt(at); at += 1; return ch; }, peek = function () { // Get the next character without consuming it or // assigning it to the ch varaible. return text.charAt(at); }, identifier = function () { // Parse an identifier. Normally, reserved words are disallowed here, but we // only use this for unquoted object keys, where reserved words are allowed, // so we don't check for those here. References: // - http://es5.github.com/#x7.6 // - https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Core_Language_Features#Variables // - http://docstore.mik.ua/orelly/webprog/jscript/ch02_07.htm var key = ch; // Identifiers must start with a letter, _ or $. if ((ch !== '_' && ch !== '$') && (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z')) { error("Bad identifier"); } // Subsequent characters can contain digits. while (next() && ( ch === '_' || ch === '$' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9'))) { key += ch; } return key; }, number = function () { // Parse a number value. var number, sign = '', string = '', base = 10; if (ch === '-' || ch === '+') { sign = ch; next(ch); } // support for Infinity (could tweak to allow other words): if (ch === 'I') { number = word(); if (typeof number !== 'number' || isNaN(number)) { error('Unexpected word for number'); } return (sign === '-') ? -number : number; } // support for NaN if (ch === 'N' ) { number = word(); if (!isNaN(number)) { error('expected word to be NaN'); } // ignore sign as -NaN also is NaN return number; } if (ch === '0') { string += ch; next(); if (ch === 'x' || ch === 'X') { string += ch; next(); base = 16; } else if (ch >= '0' && ch <= '9') { error('Octal literal'); } } switch (base) { case 10: while (ch >= '0' && ch <= '9' ) { string += ch; next(); } if (ch === '.') { string += '.'; while (next() && ch >= '0' && ch <= '9') { string += ch; } } if (ch === 'e' || ch === 'E') { string += ch; next(); if (ch === '-' || ch === '+') { string += ch; next(); } while (ch >= '0' && ch <= '9') { string += ch; next(); } } break; case 16: while (ch >= '0' && ch <= '9' || ch >= 'A' && ch <= 'F' || ch >= 'a' && ch <= 'f') { string += ch; next(); } break; } if(sign === '-') { number = -string; } else { number = +string; } if (!isFinite(number)) { error("Bad number"); } else { return number; } }, string = function () { // Parse a string value. var hex, i, string = '', delim, // double quote or single quote uffff; // When parsing for string values, we must look for ' or " and \ characters. if (ch === '"' || ch === "'") { delim = ch; while (next()) { if (ch === delim) { next(); return string; } else if (ch === '\\') { next(); if (ch === 'u') { uffff = 0; for (i = 0; i < 4; i += 1) { hex = parseInt(next(), 16); if (!isFinite(hex)) { break; } uffff = uffff * 16 + hex; } string += String.fromCharCode(uffff); } else if (ch === '\r') { if (peek() === '\n') { next(); } } else if (typeof escapee[ch] === 'string') { string += escapee[ch]; } else { break; } } else if (ch === '\n') { // unescaped newlines are invalid; see: // https://github.com/aseemk/json5/issues/24 // invalid unescaped chars? break; } else { string += ch; } } } error("Bad string"); }, inlineComment = function () { // Skip an inline comment, assuming this is one. The current character should // be the second / character in the // pair that begins this inline comment. // To finish the inline comment, we look for a newline or the end of the text. if (ch !== '/') { error("Not an inline comment"); } do { next(); if (ch === '\n' || ch === '\r') { next(); return; } } while (ch); }, blockComment = function () { // Skip a block comment, assuming this is one. The current character should be // the * character in the /* pair that begins this block comment. // To finish the block comment, we look for an ending */ pair of characters, // but we also watch for the end of text before the comment is terminated. if (ch !== '*') { error("Not a block comment"); } do { next(); while (ch === '*') { next('*'); if (ch === '/') { next('/'); return; } } } while (ch); error("Unterminated block comment"); }, comment = function () { // Skip a comment, whether inline or block-level, assuming this is one. // Comments always begin with a / character. if (ch !== '/') { error("Not a comment"); } next('/'); if (ch === '/') { inlineComment(); } else if (ch === '*') { blockComment(); } else { error("Unrecognized comment"); } }, white = function () { // Skip whitespace and comments. // Note that we're detecting comments by only a single / character. // This works since regular expressions are not valid JSON(5), but this will // break if there are other valid values that begin with a / character! while (ch) { if (ch === '/') { comment(); } else if (ws.indexOf(ch) >= 0) { next(); } else { return; } } }, word = function () { // true, false, or null. switch (ch) { case 't': next('t'); next('r'); next('u'); next('e'); return true; case 'f': next('f'); next('a'); next('l'); next('s'); next('e'); return false; case 'n': next('n'); next('u'); next('l'); next('l'); return null; case 'I': next('I'); next('n'); next('f'); next('i'); next('n'); next('i'); next('t'); next('y'); return Infinity; case 'N': next( 'N' ); next( 'a' ); next( 'N' ); return NaN; } error("Unexpected '" + ch + "'"); }, value, // Place holder for the value function. array = function () { // Parse an array value. var array = []; if (ch === '[') { next('['); white(); while (ch) { if (ch === ']') { next(']'); return array; // Potentially empty array } // ES5 allows omitting elements in arrays, e.g. [,] and // [,null]. We don't allow this in JSON5. if (ch === ',') { error("Missing array element"); } else { array.push(value()); } white(); // If there's no comma after this value, this needs to // be the end of the array. if (ch !== ',') { next(']'); return array; } next(','); white(); } } error("Bad array"); }, object = function () { // Parse an object value. var key, object = {}; if (ch === '{') { next('{'); white(); while (ch) { if (ch === '}') { next('}'); return object; // Potentially empty object } // Keys can be unquoted. If they are, they need to be // valid JS identifiers. if (ch === '"' || ch === "'") { key = string(); } else { key = identifier(); } white(); next(':'); object[key] = value(); white(); // If there's no comma after this pair, this needs to be // the end of the object. if (ch !== ',') { next('}'); return object; } next(','); white(); } } error("Bad object"); }; value = function () { // Parse a JSON value. It could be an object, an array, a string, a number, // or a word. white(); switch (ch) { case '{': return object(); case '[': return array(); case '"': case "'": return string(); case '-': case '+': case '.': return number(); default: return ch >= '0' && ch <= '9' ? number() : word(); } }; // Return the json_parse function. It will have access to all of the above // functions and variables. return function (source, reviver) { var result; text = String(source); at = 0; ch = ' '; result = value(); white(); if (ch) { error("Syntax error"); } // If there is a reviver function, we recursively walk the new structure, // passing each name/value pair to the reviver function for possible // transformation, starting with a temporary root object that holds the result // in an empty key. If there is not a reviver function, we simply return the // result. return typeof reviver === 'function' ? (function walk(holder, key) { var k, v, value = holder[key]; if (value && typeof value === 'object') { for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = walk(value, k); if (v !== undefined) { value[k] = v; } else { delete value[k]; } } } } return reviver.call(holder, key, value); }({'': result}, '')) : result; }; }()); // JSON5 stringify will not quote keys where appropriate JSON5.stringify = function (obj, replacer, space) { if (replacer && (typeof(replacer) !== "function" && !isArray(replacer))) { throw new Error('Replacer must be a function or an array'); } var getReplacedValueOrUndefined = function(holder, key, isTopLevel) { var value = holder[key]; // Replace the value with its toJSON value first, if possible if (value && value.toJSON && typeof value.toJSON === "function") { value = value.toJSON(); } // If the user-supplied replacer if a function, call it. If it's an array, check objects' string keys for // presence in the array (removing the key/value pair from the resulting JSON if the key is missing). if (typeof(replacer) === "function") { return replacer.call(holder, key, value); } else if(replacer) { if (isTopLevel || isArray(holder) || replacer.indexOf(key) >= 0) { return value; } else { return undefined; } } else { return value; } }; function isWordChar(char) { return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char === '_' || char === '$'; } function isWordStart(char) { return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || char === '_' || char === '$'; } function isWord(key) { if (typeof key !== 'string') { return false; } if (!isWordStart(key[0])) { return false; } var i = 1, length = key.length; while (i < length) { if (!isWordChar(key[i])) { return false; } i++; } return true; } // export for use in tests JSON5.isWord = isWord; // polyfills function isArray(obj) { if (Array.isArray) { return Array.isArray(obj); } else { return Object.prototype.toString.call(obj) === '[object Array]'; } } function isDate(obj) { return Object.prototype.toString.call(obj) === '[object Date]'; } isNaN = isNaN || function(val) { return typeof val === 'number' && val !== val; }; var objStack = []; function checkForCircular(obj) { for (var i = 0; i < objStack.length; i++) { if (objStack[i] === obj) { throw new TypeError("Converting circular structure to JSON"); } } } function makeIndent(str, num, noNewLine) { if (!str) { return ""; } // indentation no more than 10 chars if (str.length > 10) { str = str.substring(0, 10); } var indent = noNewLine ? "" : "\n"; for (var i = 0; i < num; i++) { indent += str; } return indent; } var indentStr; if (space) { if (typeof space === "string") { indentStr = space; } else if (typeof space === "number" && space >= 0) { indentStr = makeIndent(" ", space, true); } else { // ignore space parameter } } // Copied from Crokford's implementation of JSON // See https://github.com/douglascrockford/JSON-js/blob/e39db4b7e6249f04a195e7dd0840e610cc9e941e/json2.js#L195 // Begin var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, meta = { // table of character substitutions '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\' }; function escapeString(string) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. // Otherwise we must also replace the offending characters with safe escape // sequences. escapable.lastIndex = 0; return escapable.test(string) ? '"' + string.replace(escapable, function (a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; } // End function internalStringify(holder, key, isTopLevel) { var buffer, res; // Replace the value, if necessary var obj_part = getReplacedValueOrUndefined(holder, key, isTopLevel); if (obj_part && !isDate(obj_part)) { // unbox objects // don't unbox dates, since will turn it into number obj_part = obj_part.valueOf(); } switch(typeof obj_part) { case "boolean": return obj_part.toString(); case "number": if (isNaN(obj_part) || !isFinite(obj_part)) { return "null"; } return obj_part.toString(); case "string": return escapeString(obj_part.toString()); case "object": if (obj_part === null) { return "null"; } else if (isArray(obj_part)) { checkForCircular(obj_part); buffer = "["; objStack.push(obj_part); for (var i = 0; i < obj_part.length; i++) { res = internalStringify(obj_part, i, false); buffer += makeIndent(indentStr, objStack.length); if (res === null || typeof res === "undefined") { buffer += "null"; } else { buffer += res; } if (i < obj_part.length-1) { buffer += ","; } else if (indentStr) { buffer += "\n"; } } objStack.pop(); buffer += makeIndent(indentStr, objStack.length, true) + "]"; } else { checkForCircular(obj_part); buffer = "{"; var nonEmpty = false; objStack.push(obj_part); for (var prop in obj_part) { if (obj_part.hasOwnProperty(prop)) { var value = internalStringify(obj_part, prop, false); isTopLevel = false; if (typeof value !== "undefined" && value !== null) { buffer += makeIndent(indentStr, objStack.length); nonEmpty = true; var key = isWord(prop) ? prop : escapeString(prop); buffer += key + ":" + (indentStr ? ' ' : '') + value + ","; } } } objStack.pop(); if (nonEmpty) { buffer = buffer.substring(0, buffer.length-1) + makeIndent(indentStr, objStack.length) + "}"; } else { buffer = '{}'; } } return buffer; default: // functions and undefined should be ignored return undefined; } } // special case...when undefined is used inside of // a compound object/array, return null. // but when top-level, return undefined var topLevelHolder = {"":obj}; if (obj === undefined) { return getReplacedValueOrUndefined(topLevelHolder, '', true); } return internalStringify(topLevelHolder, '', true); }; ================================================ FILE: lib/tools/open.js ================================================ var exec = require('child_process').exec , path = require('path') ; /** * open a file or uri using the default application for the file type. * * @return {ChildProcess} - the child process object. * @param {string} target - the file/uri to open. * @param {string} appName - (optional) the application to be used to open the * file (for example, "chrome", "firefox") * @param {function(Error)} callback - called with null on success, or * an error object that contains a property 'code' with the exit * code of the process. */ module.exports = open; function open(target, appName, callback) { var opener; if (typeof(appName) === 'function') { callback = appName; appName = null; } switch (process.platform) { case 'darwin': if (appName) { opener = 'open -a "' + escape(appName) + '"'; } else { opener = 'open'; } break; case 'win32': // if the first parameter to start is quoted, it uses that as the title // so we pass a blank title so we can quote the file we are opening if (appName) { opener = 'start "" "' + escape(appName) + '"'; } else { opener = 'start ""'; } break; default: if (appName) { opener = escape(appName); } else { // use Portlands xdg-open everywhere else opener = path.join(__dirname, './xdg-open'); } break; } if (process.env.SUDO_USER) { opener = 'sudo -u ' + process.env.SUDO_USER + ' ' + opener; } return exec(opener + ' "' + escape(target) + '"', callback); } function escape(s) { return s.replace(/"/g, '\\\"'); } ================================================ FILE: lib/tools/passwd.js ================================================ var fs = require('fs') var getUsers = function() { return fs.readFileSync('/etc/passwd') .toString() .split('\n') .filter(function (user) { return user.length && user[0] != '#'; }) .reduce(function(map, user) { var fields = user.split(':'); map[fields[0]] = map[fields[2]] = { username : fields[0], password : fields[1], userId : fields[2], groupId : fields[3], name : fields[4].split(',')[0], homedir : fields[5], shell : fields[6] }; return map }, {}) } var getGroups = function(cb) { var groups try { groups = fs.readFileSync('/etc/group') } catch(e) { return e } return groups .toString() .split('\n') .filter(function (group) { return group.length && group[0] != '#'; }) .reduce(function(map, group) { var fields = group.split(':'); map[fields[0]] = map[fields[2]] = { name : fields[0], password : fields[1], id : fields[2], members : fields[3].split(',') }; return map; }, {}) } module.exports = { getUsers, getGroups } ================================================ FILE: lib/tools/sexec.js ================================================ var path = require('path'); var fs = require('fs'); var child = require('child_process'); var DEFAULT_MAXBUFFER_SIZE = 20 * 1024 * 1024; function _exec(command, options, callback) { options = options || {}; if (typeof options === 'function') { callback = options; } if (typeof options === 'object' && typeof callback === 'function') { options.async = true; } if (!command) { try { console.error('[sexec] must specify command'); } catch (e) { return; } } options = Object.assign({ silent: false, cwd: path.resolve(process.cwd()).toString(), env: process.env, maxBuffer: DEFAULT_MAXBUFFER_SIZE, encoding: 'utf8', }, options); var c = child.exec(command, options, function (err, stdout, stderr) { if (callback) { if (!err) { callback(0, stdout, stderr); } else if (err.code === undefined) { // See issue #536 /* istanbul ignore next */ callback(1, stdout, stderr); } else { callback(err.code, stdout, stderr); } } }); if (!options.silent) { c.stdout.pipe(process.stdout); c.stderr.pipe(process.stderr); } } module.exports = _exec; ================================================ FILE: lib/tools/treeify.js ================================================ // treeify.js // Luke Plaster // https://github.com/notatestuser/treeify.js // do the universal module definition dance (function (root, factory) { if (typeof exports === 'object') { module.exports = factory(); } else if (typeof define === 'function' && define.amd) { define(factory); } else { root.treeify = factory(); } }(this, function() { function makePrefix(key, last) { var str = (last ? '└' : '├'); if (key) { str += '─ '; } else { str += '──┐'; } return str; } function filterKeys(obj, hideFunctions) { var keys = []; for (var branch in obj) { // always exclude anything in the object's prototype if (!obj.hasOwnProperty(branch)) { continue; } // ... and hide any keys mapped to functions if we've been told to if (hideFunctions && ((typeof obj[branch])==="function")) { continue; } keys.push(branch); } return keys; } function growBranch(key, root, last, lastStates, showValues, hideFunctions, callback) { var line = '', index = 0, lastKey, circular, lastStatesCopy = lastStates.slice(0); if (lastStatesCopy.push([ root, last ]) && lastStates.length > 0) { // based on the "was last element" states of whatever we're nested within, // we need to append either blankness or a branch to our line lastStates.forEach(function(lastState, idx) { if (idx > 0) { line += (lastState[1] ? ' ' : '│') + ' '; } if ( ! circular && lastState[0] === root) { circular = true; } }); // the prefix varies based on whether the key contains something to show and // whether we're dealing with the last element in this collection line += makePrefix(key, last) + key; // append values and the circular reference indicator showValues && (typeof root !== 'object' || root instanceof Date) && (line += ': ' + root); circular && (line += ' (circular ref.)'); callback(line); } // can we descend into the next item? if ( ! circular && typeof root === 'object') { var keys = filterKeys(root, hideFunctions); keys.forEach(function(branch){ // the last key is always printed with a different prefix, so we'll need to know if we have it lastKey = ++index === keys.length; // hold your breath for recursive action growBranch(branch, root[branch], lastKey, lastStatesCopy, showValues, hideFunctions, callback); }); } }; // -------------------- var Treeify = {}; // Treeify.asLines // -------------------- // Outputs the tree line-by-line, calling the lineCallback when each one is available. Treeify.asLines = function(obj, showValues, hideFunctions, lineCallback) { /* hideFunctions and lineCallback are curried, which means we don't break apps using the older form */ var hideFunctionsArg = typeof hideFunctions !== 'function' ? hideFunctions : false; growBranch('.', obj, false, [], showValues, hideFunctionsArg, lineCallback || hideFunctions); }; // Treeify.asTree // -------------------- // Outputs the entire tree, returning it as a string with line breaks. Treeify.asTree = function(obj, showValues, hideFunctions) { var tree = ''; growBranch('.', obj, false, [], showValues, hideFunctions, function(line) { tree += line + '\n'; }); return tree; }; // -------------------- return Treeify; })); ================================================ FILE: lib/tools/which.js ================================================ var fs = require('fs'); var path = require('path'); var cst = require('../../constants.js') // XP's system default value for `PATHEXT` system variable, just in case it's not // set on Windows. var XP_DEFAULT_PATHEXT = '.com;.exe;.bat;.cmd;.vbs;.vbe;.js;.jse;.wsf;.wsh'; // For earlier versions of NodeJS that doesn't have a list of constants (< v6) var FILE_EXECUTABLE_MODE = 1; function statFollowLinks() { return fs.statSync.apply(fs, arguments); } function isWindowsPlatform() { return cst.IS_WINDOWS; } // Cross-platform method for splitting environment `PATH` variables function splitPath(p) { return p ? p.split(path.delimiter) : []; } // Tests are running all cases for this func but it stays uncovered by codecov due to unknown reason /* istanbul ignore next */ function isExecutable(pathName) { try { // TODO(node-support): replace with fs.constants.X_OK once remove support for node < v6 fs.accessSync(pathName, FILE_EXECUTABLE_MODE); } catch (err) { return false; } return true; } function checkPath(pathName) { return fs.existsSync(pathName) && !statFollowLinks(pathName).isDirectory() && (isWindowsPlatform() || isExecutable(pathName)); } //@ //@ ### which(command) //@ //@ Examples: //@ //@ ```javascript //@ var nodeExec = which('node'); //@ ``` //@ //@ Searches for `command` in the system's `PATH`. On Windows, this uses the //@ `PATHEXT` variable to append the extension if it's not already executable. //@ Returns a [ShellString](#shellstringstr) containing the absolute path to //@ `command`. function _which(cmd) { if (!cmd) console.error('must specify command'); var options = {} var isWindows = isWindowsPlatform(); var pathArray = splitPath(process.env.PATH); var queryMatches = []; // No relative/absolute paths provided? if (cmd.indexOf('/') === -1) { // Assume that there are no extensions to append to queries (this is the // case for unix) var pathExtArray = ['']; if (isWindows) { // In case the PATHEXT variable is somehow not set (e.g. // child_process.spawn with an empty environment), use the XP default. var pathExtEnv = process.env.PATHEXT || XP_DEFAULT_PATHEXT; pathExtArray = splitPath(pathExtEnv.toUpperCase()); } // Search for command in PATH for (var k = 0; k < pathArray.length; k++) { // already found it if (queryMatches.length > 0 && !options.all) break; var attempt = path.resolve(pathArray[k], cmd); if (isWindows) { attempt = attempt.toUpperCase(); } var match = attempt.match(/\.[^<>:"/|?*.]+$/); if (match && pathExtArray.indexOf(match[0]) >= 0) { // this is Windows-only // The user typed a query with the file extension, like // `which('node.exe')` if (checkPath(attempt)) { queryMatches.push(attempt); break; } } else { // All-platforms // Cycle through the PATHEXT array, and check each extension // Note: the array is always [''] on Unix for (var i = 0; i < pathExtArray.length; i++) { var ext = pathExtArray[i]; var newAttempt = attempt + ext; if (checkPath(newAttempt)) { queryMatches.push(newAttempt); break; } } } } } else if (checkPath(cmd)) { // a valid absolute or relative path queryMatches.push(path.resolve(cmd)); } if (queryMatches.length > 0) { return options.all ? queryMatches : queryMatches[0]; } return options.all ? [] : null; } module.exports = _which; ================================================ FILE: lib/tools/xdg-open ================================================ #!/bin/sh #--------------------------------------------- # xdg-open # # Utility script to open a URL in the registered default application. # # Refer to the usage() function below for usage. # # Copyright 2009-2010, Fathi Boudra # Copyright 2009-2010, Rex Dieter # Copyright 2006, Kevin Krammer # Copyright 2006, Jeremy White # # LICENSE: # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # #--------------------------------------------- manualpage() { cat << _MANUALPAGE Name xdg-open - opens a file or URL in the user's preferred application Synopsis xdg-open { file | URL } xdg-open { --help | --manual | --version } Description xdg-open opens a file or URL in the user's preferred application. If a URL is provided the URL will be opened in the user's preferred web browser. If a file is provided the file will be opened in the preferred application for files of that type. xdg-open supports file, ftp, http and https URLs. xdg-open is for use inside a desktop session only. It is not recommended to use xdg-open as root. Options --help Show command synopsis. --manual Show this manual page. --version Show the xdg-utils version information. Exit Codes An exit code of 0 indicates success while a non-zero exit code indicates failure. The following failure codes can be returned: 1 Error in command line syntax. 2 One of the files passed on the command line did not exist. 3 A required tool could not be found. 4 The action failed. Examples xdg-open 'http://www.freedesktop.org/' Opens the freedesktop.org website in the user's default browser. xdg-open /tmp/foobar.png Opens the PNG image file /tmp/foobar.png in the user's default image viewing application. _MANUALPAGE } usage() { cat << _USAGE xdg-open - opens a file or URL in the user's preferred application Synopsis xdg-open { file | URL } xdg-open { --help | --manual | --version } _USAGE } #@xdg-utils-common@ #---------------------------------------------------------------------------- # Common utility functions included in all XDG wrapper scripts #---------------------------------------------------------------------------- DEBUG() { [ -z "${XDG_UTILS_DEBUG_LEVEL}" ] && return 0; [ ${XDG_UTILS_DEBUG_LEVEL} -lt $1 ] && return 0; shift echo "$@" >&2 } # This handles backslashes but not quote marks. first_word() { read first rest echo "$first" } #------------------------------------------------------------- # map a binary to a .desktop file binary_to_desktop_file() { search="${XDG_DATA_HOME:-$HOME/.local/share}:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" binary="`which "$1"`" binary="`readlink -f "$binary"`" base="`basename "$binary"`" IFS=: for dir in $search; do unset IFS [ "$dir" ] || continue [ -d "$dir/applications" ] || [ -d "$dir/applnk" ] || continue for file in "$dir"/applications/*.desktop "$dir"/applications/*/*.desktop "$dir"/applnk/*.desktop "$dir"/applnk/*/*.desktop; do [ -r "$file" ] || continue # Check to make sure it's worth the processing. grep -q "^Exec.*$base" "$file" || continue # Make sure it's a visible desktop file (e.g. not "preferred-web-browser.desktop"). grep -Eq "^(NoDisplay|Hidden)=true" "$file" && continue command="`grep -E "^Exec(\[[^]=]*])?=" "$file" | cut -d= -f 2- | first_word`" command="`which "$command"`" if [ x"`readlink -f "$command"`" = x"$binary" ]; then # Fix any double slashes that got added path composition echo "$file" | sed -e 's,//*,/,g' return fi done done } #------------------------------------------------------------- # map a .desktop file to a binary ## FIXME: handle vendor dir case desktop_file_to_binary() { search="${XDG_DATA_HOME:-$HOME/.local/share}:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" desktop="`basename "$1"`" IFS=: for dir in $search; do unset IFS [ "$dir" ] && [ -d "$dir/applications" ] || continue file="$dir/applications/$desktop" [ -r "$file" ] || continue # Remove any arguments (%F, %f, %U, %u, etc.). command="`grep -E "^Exec(\[[^]=]*])?=" "$file" | cut -d= -f 2- | first_word`" command="`which "$command"`" readlink -f "$command" return done } #------------------------------------------------------------- # Exit script on successfully completing the desired operation exit_success() { if [ $# -gt 0 ]; then echo "$@" echo fi exit 0 } #----------------------------------------- # Exit script on malformed arguments, not enough arguments # or missing required option. # prints usage information exit_failure_syntax() { if [ $# -gt 0 ]; then echo "xdg-open: $@" >&2 echo "Try 'xdg-open --help' for more information." >&2 else usage echo "Use 'man xdg-open' or 'xdg-open --manual' for additional info." fi exit 1 } #------------------------------------------------------------- # Exit script on missing file specified on command line exit_failure_file_missing() { if [ $# -gt 0 ]; then echo "xdg-open: $@" >&2 fi exit 2 } #------------------------------------------------------------- # Exit script on failure to locate necessary tool applications exit_failure_operation_impossible() { if [ $# -gt 0 ]; then echo "xdg-open: $@" >&2 fi exit 3 } #------------------------------------------------------------- # Exit script on failure returned by a tool application exit_failure_operation_failed() { if [ $# -gt 0 ]; then echo "xdg-open: $@" >&2 fi exit 4 } #------------------------------------------------------------ # Exit script on insufficient permission to read a specified file exit_failure_file_permission_read() { if [ $# -gt 0 ]; then echo "xdg-open: $@" >&2 fi exit 5 } #------------------------------------------------------------ # Exit script on insufficient permission to write a specified file exit_failure_file_permission_write() { if [ $# -gt 0 ]; then echo "xdg-open: $@" >&2 fi exit 6 } check_input_file() { if [ ! -e "$1" ]; then exit_failure_file_missing "file '$1' does not exist" fi if [ ! -r "$1" ]; then exit_failure_file_permission_read "no permission to read file '$1'" fi } check_vendor_prefix() { file_label="$2" [ -n "$file_label" ] || file_label="filename" file=`basename "$1"` case "$file" in [[:alpha:]]*-*) return ;; esac echo "xdg-open: $file_label '$file' does not have a proper vendor prefix" >&2 echo 'A vendor prefix consists of alpha characters ([a-zA-Z]) and is terminated' >&2 echo 'with a dash ("-"). An example '"$file_label"' is '"'example-$file'" >&2 echo "Use --novendor to override or 'xdg-open --manual' for additional info." >&2 exit 1 } check_output_file() { # if the file exists, check if it is writeable # if it does not exist, check if we are allowed to write on the directory if [ -e "$1" ]; then if [ ! -w "$1" ]; then exit_failure_file_permission_write "no permission to write to file '$1'" fi else DIR=`dirname "$1"` if [ ! -w "$DIR" ] || [ ! -x "$DIR" ]; then exit_failure_file_permission_write "no permission to create file '$1'" fi fi } #---------------------------------------- # Checks for shared commands, e.g. --help check_common_commands() { while [ $# -gt 0 ] ; do parm="$1" shift case "$parm" in --help) usage echo "Use 'man xdg-open' or 'xdg-open --manual' for additional info." exit_success ;; --manual) manualpage exit_success ;; --version) echo "xdg-open 1.1.0 rc3" exit_success ;; esac done } check_common_commands "$@" [ -z "${XDG_UTILS_DEBUG_LEVEL}" ] && unset XDG_UTILS_DEBUG_LEVEL; if [ ${XDG_UTILS_DEBUG_LEVEL-0} -lt 1 ]; then # Be silent xdg_redirect_output=" > /dev/null 2> /dev/null" else # All output to stderr xdg_redirect_output=" >&2" fi #-------------------------------------- # Checks for known desktop environments # set variable DE to the desktop environments name, lowercase detectDE() { # see https://bugs.freedesktop.org/show_bug.cgi?id=34164 unset GREP_OPTIONS if [ -n "${XDG_CURRENT_DESKTOP}" ]; then case "${XDG_CURRENT_DESKTOP}" in ENLIGHTENMENT) DE=enlightenment; ;; GNOME) DE=gnome; ;; KDE) DE=kde; ;; LXDE) DE=lxde; ;; MATE) DE=mate; ;; XFCE) DE=xfce ;; esac fi if [ x"$DE" = x"" ]; then # classic fallbacks if [ x"$KDE_FULL_SESSION" != x"" ]; then DE=kde; elif [ x"$GNOME_DESKTOP_SESSION_ID" != x"" ]; then DE=gnome; elif [ x"$MATE_DESKTOP_SESSION_ID" != x"" ]; then DE=mate; elif `dbus-send --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.GetNameOwner string:org.gnome.SessionManager > /dev/null 2>&1` ; then DE=gnome; elif xprop -root _DT_SAVE_MODE 2> /dev/null | grep ' = \"xfce4\"$' >/dev/null 2>&1; then DE=xfce; elif xprop -root 2> /dev/null | grep -i '^xfce_desktop_window' >/dev/null 2>&1; then DE=xfce elif echo $DESKTOP | grep -q '^Enlightenment'; then DE=enlightenment; fi fi if [ x"$DE" = x"" ]; then # fallback to checking $DESKTOP_SESSION case "$DESKTOP_SESSION" in gnome) DE=gnome; ;; LXDE|Lubuntu) DE=lxde; ;; MATE) DE=mate; ;; xfce|xfce4|'Xfce Session') DE=xfce; ;; esac fi if [ x"$DE" = x"" ]; then # fallback to uname output for other platforms case "$(uname 2>/dev/null)" in Darwin) DE=darwin; ;; esac fi if [ x"$DE" = x"gnome" ]; then # gnome-default-applications-properties is only available in GNOME 2.x # but not in GNOME 3.x which gnome-default-applications-properties > /dev/null 2>&1 || DE="gnome3" fi } #---------------------------------------------------------------------------- # kfmclient exec/openURL can give bogus exit value in KDE <= 3.5.4 # It also always returns 1 in KDE 3.4 and earlier # Simply return 0 in such case kfmclient_fix_exit_code() { version=`LC_ALL=C.UTF-8 kde-config --version 2>/dev/null | grep '^KDE'` major=`echo $version | sed 's/KDE.*: \([0-9]\).*/\1/'` minor=`echo $version | sed 's/KDE.*: [0-9]*\.\([0-9]\).*/\1/'` release=`echo $version | sed 's/KDE.*: [0-9]*\.[0-9]*\.\([0-9]\).*/\1/'` test "$major" -gt 3 && return $1 test "$minor" -gt 5 && return $1 test "$release" -gt 4 && return $1 return 0 } # This handles backslashes but not quote marks. last_word() { read first rest echo "$rest" } # Get the value of a key in a desktop file's Desktop Entry group. # Example: Use get_key foo.desktop Exec # to get the values of the Exec= key for the Desktop Entry group. get_key() { local file="${1}" local key="${2}" local desktop_entry="" IFS_="${IFS}" IFS="" while read line do case "$line" in "[Desktop Entry]") desktop_entry="y" ;; # Reset match flag for other groups "["*) desktop_entry="" ;; "${key}="*) # Only match Desktop Entry group if [ -n "${desktop_entry}" ] then echo "${line}" | cut -d= -f 2- fi esac done < "${file}" IFS="${IFS_}" } open_darwin() { open "$1" if [ $? -eq 0 ]; then exit_success else exit_failure_operation_failed fi } open_kde() { if [ -n "${KDE_SESSION_VERSION}" ]; then case "${KDE_SESSION_VERSION}" in 4) kde-open "$1" ;; 5) kde-open${KDE_SESSION_VERSION} "$1" ;; esac else kfmclient exec "$1" kfmclient_fix_exit_code $? fi if [ $? -eq 0 ]; then exit_success else exit_failure_operation_failed fi } open_gnome() { if gvfs-open --help 2>/dev/null 1>&2; then gvfs-open "$1" else gnome-open "$1" fi if [ $? -eq 0 ]; then exit_success else exit_failure_operation_failed fi } open_mate() { if gvfs-open --help 2>/dev/null 1>&2; then gvfs-open "$1" else mate-open "$1" fi if [ $? -eq 0 ]; then exit_success else exit_failure_operation_failed fi } open_xfce() { exo-open "$1" if [ $? -eq 0 ]; then exit_success else exit_failure_operation_failed fi } open_enlightenment() { enlightenment_open "$1" if [ $? -eq 0 ]; then exit_success else exit_failure_operation_failed fi } #----------------------------------------- # Recursively search .desktop file search_desktop_file() { local default="$1" local dir="$2" local target="$3" local file="" # look for both vendor-app.desktop, vendor/app.desktop if [ -r "$dir/$default" ]; then file="$dir/$default" elif [ -r "$dir/`echo $default | sed -e 's|-|/|'`" ]; then file="$dir/`echo $default | sed -e 's|-|/|'`" fi if [ -r "$file" ] ; then command="$(get_key "${file}" "Exec" | first_word)" command_exec=`which $command 2>/dev/null` icon="$(get_key "${file}" "Icon")" # FIXME: Actually LC_MESSAGES should be used as described in # http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s04.html localised_name="$(get_key "${file}" "Name")" set -- $(get_key "${file}" "Exec" | last_word) # We need to replace any occurrence of "%f", "%F" and # the like by the target file. We examine each # argument and append the modified argument to the # end then shift. local args=$# local replaced=0 while [ $args -gt 0 ]; do case $1 in %[c]) replaced=1 arg="${localised_name}" shift set -- "$@" "$arg" ;; %[fFuU]) replaced=1 arg="$target" shift set -- "$@" "$arg" ;; %[i]) replaced=1 shift set -- "$@" "--icon" "$icon" ;; *) arg="$1" shift set -- "$@" "$arg" ;; esac args=$(( $args - 1 )) done [ $replaced -eq 1 ] || set -- "$@" "$target" "$command_exec" "$@" if [ $? -eq 0 ]; then exit_success fi fi for d in $dir/*/; do [ -d "$d" ] && search_desktop_file "$default" "$d" "$target" done } open_generic_xdg_mime() { filetype="$2" default=`xdg-mime query default "$filetype"` if [ -n "$default" ] ; then xdg_user_dir="$XDG_DATA_HOME" [ -n "$xdg_user_dir" ] || xdg_user_dir="$HOME/.local/share" xdg_system_dirs="$XDG_DATA_DIRS" [ -n "$xdg_system_dirs" ] || xdg_system_dirs=/usr/local/share/:/usr/share/ DEBUG 3 "$xdg_user_dir:$xdg_system_dirs" for x in `echo "$xdg_user_dir:$xdg_system_dirs" | sed 's/:/ /g'`; do search_desktop_file "$default" "$x/applications/" "$1" done fi } open_generic_xdg_file_mime() { filetype=`xdg-mime query filetype "$1" | sed "s/;.*//"` open_generic_xdg_mime "$1" "$filetype" } open_generic_xdg_x_scheme_handler() { scheme="`echo $1 | sed -n 's/\(^[[:alnum:]+\.-]*\):.*$/\1/p'`" if [ -n $scheme ]; then filetype="x-scheme-handler/$scheme" open_generic_xdg_mime "$1" "$filetype" fi } open_generic() { # Paths or file:// URLs if (echo "$1" | grep -q '^file://' || ! echo "$1" | egrep -q '^[[:alpha:]+\.\-]+:'); then local file="$1" # Decode URLs if echo "$file" | grep -q '^file:///'; then file=${file#file://} file="$(printf "$(echo "$file" | sed -e 's@%\([a-f0-9A-F]\{2\}\)@\\x\1@g')")" fi file_check=${file%%#*} file_check=${file_check%%\?*} check_input_file "$file_check" filetype=`xdg-mime query filetype "$file_check" | sed "s/;.*//"` open_generic_xdg_mime "$file" "$filetype" if which run-mailcap 2>/dev/null 1>&2; then run-mailcap --action=view "$file" if [ $? -eq 0 ]; then exit_success fi fi if mimeopen -v 2>/dev/null 1>&2; then mimeopen -L -n "$file" if [ $? -eq 0 ]; then exit_success fi fi fi open_generic_xdg_x_scheme_handler "$1" IFS=":" for browser in $BROWSER; do if [ x"$browser" != x"" ]; then browser_with_arg=`printf "$browser" "$1" 2>/dev/null` if [ $? -ne 0 ]; then browser_with_arg=$browser; fi if [ x"$browser_with_arg" = x"$browser" ]; then eval '$browser "$1"'$xdg_redirect_output; else eval '$browser_with_arg'$xdg_redirect_output; fi if [ $? -eq 0 ]; then exit_success; fi fi done exit_failure_operation_impossible "no method available for opening '$1'" } open_lxde() { # pcmanfm only knows how to handle file:// urls and filepaths, it seems. if (echo "$1" | grep -q '^file://' || ! echo "$1" | egrep -q '^[[:alpha:]+\.\-]+:') then local file="$1" # handle relative paths if ! echo "$file" | egrep -q '^(file://)?/'; then file="$(pwd)/$file" fi pcmanfm "$file" else open_generic "$1" fi if [ $? -eq 0 ]; then exit_success else exit_failure_operation_failed fi } [ x"$1" != x"" ] || exit_failure_syntax url= while [ $# -gt 0 ] ; do parm="$1" shift case "$parm" in -*) exit_failure_syntax "unexpected option '$parm'" ;; *) if [ -n "$url" ] ; then exit_failure_syntax "unexpected argument '$parm'" fi url="$parm" ;; esac done if [ -z "${url}" ] ; then exit_failure_syntax "file or URL argument missing" fi detectDE if [ x"$DE" = x"" ]; then DE=generic fi DEBUG 2 "Selected DE $DE" # sanitize BROWSER (avoid caling ourselves in particular) case "${BROWSER}" in *:"xdg-open"|"xdg-open":*) BROWSER=$(echo $BROWSER | sed -e 's|:xdg-open||g' -e 's|xdg-open:||g') ;; "xdg-open") BROWSER= ;; esac # if BROWSER variable is not set, check some well known browsers instead if [ x"$BROWSER" = x"" ]; then BROWSER=links2:elinks:links:lynx:w3m if [ -n "$DISPLAY" ]; then BROWSER=x-www-browser:firefox:seamonkey:mozilla:epiphany:konqueror:chromium-browser:google-chrome:$BROWSER fi fi case "$DE" in kde) open_kde "$url" ;; gnome*) open_gnome "$url" ;; mate) open_mate "$url" ;; xfce) open_xfce "$url" ;; lxde) open_lxde "$url" ;; enlightenment) open_enlightenment "$url" ;; generic) open_generic "$url" ;; *) exit_failure_operation_impossible "no method available for opening '$url'" ;; esac ================================================ FILE: package.json ================================================ { "name": "pm2", "preferGlobal": true, "version": "6.0.14", "engines": { "node": ">=16.0.0" }, "directories": { "bin": "./bin", "lib": "./lib", "example": "./examples" }, "author": { "name": "Strzelewicz Alexandre", "email": "alexandre@pm2.io", "url": "https://pm2.io" }, "maintainers": [ { "name": "Alexandre Strzelewicz", "email": "alexandre@pm2.io" }, { "name": "Antoine Bluchet", "email": "antoine@pm2.io" } ], "contributors": [ { "name": "Alex Kocharin", "email": "alex@kocharin.ru" }, { "name": "Antoine Bluchet", "email": "soyuka@gmail.com" }, { "name": "Subhash Burramsetty" }, { "name": "Valentin Marchaud", "email": "thisismac47@gmail.com" }, { "name": "Valentin Touffet", "email": "contact@eywek.fr" }, { "name": "Florian Hermouet-Joscht", "email": "florian@keymetrics.io" }, { "name": "Vincent Vallet", "email": "wallet77@gmail.com" }, { "name": "Joni Shkurti", "email": "jonishkurti90@gmail.com" }, { "name": "Jun Tjatse", "email": "thisnamemeansnothing@gmail.com" }, { "name": "Xu Jingxin", "email": "sailxjx@gmail.com" }, { "name": "Ben Postlethwaite", "email": "post.ben.here@gmail.com" }, { "name": "Devo.ps", "email": "contact@devo.ps" }, { "name": "Bret Copeland", "email": "bret@atlantisflight.org" }, { "name": "John Hurliman", "email": "jhurliman@jhurliman.org" }, { "name": "TruongSinh Tran-Nguyen", "email": "i@truongsinh.pro" }, { "name": "Michael Hueuberger", "email": "michael.heuberger@binarykitchen.com" }, { "name": "Chris Wiggins", "email": "chris@chriswiggins.co.nz" } ], "homepage": "http://pm2.keymetrics.io/", "description": "Production process manager for Node.JS applications with a built-in load balancer.", "main": "index.js", "types": "types/index.d.ts", "scripts": { "test:unit": "bash test/unit.sh", "test:e2e": "bash test/e2e.sh", "test": "bash test/unit.sh && bash test/e2e.sh" }, "keywords": [ "cli", "fault tolerant", "sysadmin", "tools", "pm2", "logs", "log", "json", "express", "hapi", "kraken", "reload", "load balancer", "lb", "load-balancer", "kubernetes", "k8s", "pm2-docker", "runtime", "source maps", "graceful", "microservice", "programmatic", "harmony", "node-pm2", "production", "keymetrics", "node.js monitoring", "strong-pm", "deploy", "deployment", "daemon", "supervisor", "supervisord", "nodemon", "pm2.io", "ghost", "ghost production", "monitoring", "keymetrics", "process manager", "forever", "profiling", "probes", "apm", "container", "forever-monitor", "keep process alive", "process configuration", "clustering", "cluster cli", "cluster", "docker", "cron", "devops", "dev ops" ], "bin": { "pm2": "bin/pm2", "pm2-dev": "bin/pm2-dev", "pm2-docker": "bin/pm2-docker", "pm2-runtime": "bin/pm2-runtime" }, "dependencies": { "@pm2/agent": "~2.1.1", "@pm2/js-api": "~0.8.0", "@pm2/io": "~6.1.0", "@pm2/pm2-version-check": "^1.0.4", "ansis": "4.0.0-node10", "async": "3.2.6", "@pm2/blessed": "0.1.81", "chokidar": "3.6.0", "cli-tableau": "2.0.1", "commander": "2.15.1", "croner": "4.1.97", "dayjs": "1.11.15", "debug": "4.4.3", "enquirer": "2.3.6", "eventemitter2": "5.0.1", "fclone": "1.0.11", "mkdirp": "1.0.4", "needle": "2.4.0", "pidusage": "3.0.2", "pm2-axon": "~4.0.1", "pm2-axon-rpc": "~0.7.1", "pm2-deploy": "~1.0.2", "pm2-multimeter": "^0.1.2", "promptly": "2.2.0", "semver": "7.7.2", "source-map-support": "0.5.21", "sprintf-js": "1.1.2", "vizion": "~2.2.1", "js-yaml": "4.1.1" }, "overrides": { "debug": "4.4.3" }, "optionalDependencies": { "pm2-sysmonit": "^1.2.8" }, "devDependencies": { "mocha": "^11.7.0", "should": "^13.2.3" }, "bugs": { "url": "https://github.com/Unitech/pm2/issues" }, "repository": { "type": "git", "url": "git://github.com/Unitech/pm2.git" }, "license": "AGPL-3.0" } ================================================ FILE: packager/alpine/pm2/APKBUILD ================================================ # Contributor: Paul LESELLIER # Maintainer: Paul LESELLIER pkgname=pm2 pkgver=master pkgrel=0 pkgdesc="PM2 CE: Production Process Manager for Node.js apps with a built-in Load Balancer." url="http://pm2.io" arch="noarch" license="GNU-AGPL-3.0" depends="nodejs" makedepends="make nodejs-npm" install="" # "$pkgname.pre-install $pkgname.post-install" subpackages="" # "$pkgname-dev $pkgname-doc" source=" https://github.com/Unitech/pm2/archive/$pkgver.zip " builddir="$srcdir/" build() { cd "$builddir" cd pm2-$pkgver npm install --production } package() { cd "$builddir" echo $pkgdir cd pm2-$pkgver for filename in constants.js paths.js index.js package.json do echo " [+] Installing: $filename to /usr/share/pm2/$filename" install -m 644 -D $filename $pkgdir/usr/share/pm2/$filename done for dirname in bin lib node_modules do echo "[~] Processing directory $dirname" CHMOD_VAL=644 if [ "$dirname" == "bin" ]; then CHMOD_VAL=755 fi for filename in $(find $dirname -type f) do echo " [+] Installing: $filename to /usr/share/pm2/$filename" install -m $CHMOD_VAL -D $filename $pkgdir/usr/share/pm2/$filename done done echo "[!] Linking pm2 binary as /usr/bin/pm2" mkdir $pkgdir/usr/bin/ cd $pkgdir/usr/bin/ ln -s ../share/pm2/bin/pm2 pm2 } sha512sums="f38040c3df19d610292fa9c28cab818e2d00360332e8bce627f6886e03601d52070084e57b4a4bbee52ca9bf960693a44eea28d28448767bc51d92fbee75757c 2.7.2.zip" ================================================ FILE: packager/alpine/pm2_io.rsa.pub ================================================ -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAugt3OP2U2VjgY1VvlxyC qP33UT96MMulFzjthMJD57VU4yBzjoboIDjc1AIVjEiCXDktxNezFiqG/MHMh/nl DVB/zvNI8CpNte250+iwSwADZZGh+Fy9V1vFARVOXZhHB7V/QXsuHoT0QXTyY4Ss 9RiS5AMHJzK1CRAZrfivAiGN7j6+G6dz2vnDIK6Q3OVpS5ssV3+44Hx7itBKXCls cvhjI4GGcZGf3K26oHfNwh1WjVT4L0Yjox5B8kN4tzif9fhKSCinnynfUZbyeSBA 4gU1cE4twQneXdc1NtPPrSOHz5THNmmQXqNNQ7sEv88LBRutcnph7IuuHEes1J/Q AwIDAQAB -----END PUBLIC KEY----- ================================================ FILE: packager/build-deb-rpm.sh ================================================ #!/bin/bash set -ex # Ensure all the tools we need are available ensureAvailable() { eval $1 --version >/dev/null || (echo "You need to install $1" && exit 2) } ensureAvailable dpkg-deb ensureAvailable fpm ensureAvailable fakeroot ensureAvailable lintian ensureAvailable rpmbuild PACKAGE_TMPDIR=tmp/debian_pkg echo "Cleaning PACKAGE_TMPDIR..." rm -rf $PACKAGE_TMPDIR PM2_VERSION=`node dist/bin/pm2 --version` VERSION=$PM2_VERSION TARBALL_NAME=dist/pm2-v$PM2_VERSION.tar.gz OUTPUT_DIR=artifacts if [ ! -e $TARBALL_NAME ]; then echo "Hey! Listen! You need to run build-dist.sh first." exit 1 fi; mkdir -p $OUTPUT_DIR # Remove old packages rm -f dist/*.deb $OUTPUT_DIR/*.deb $OUTPUT_DIR/*.rpm # Extract to a temporary directory rm -rf $PACKAGE_TMPDIR mkdir -p $PACKAGE_TMPDIR/ tar zxf $TARBALL_NAME -C $PACKAGE_TMPDIR/ # Create Linux package structure mkdir -p $PACKAGE_TMPDIR/usr/share/pm2/ mkdir -p $PACKAGE_TMPDIR/usr/share/doc/pm2/ mv $PACKAGE_TMPDIR/dist/bin $PACKAGE_TMPDIR/usr/share/pm2/ mv $PACKAGE_TMPDIR/dist/lib $PACKAGE_TMPDIR/usr/share/pm2/ mv $PACKAGE_TMPDIR/dist/constants.js $PACKAGE_TMPDIR/usr/share/pm2/ mv $PACKAGE_TMPDIR/dist/paths.js $PACKAGE_TMPDIR/usr/share/pm2/ mv $PACKAGE_TMPDIR/dist/index.js $PACKAGE_TMPDIR/usr/share/pm2/ mv $PACKAGE_TMPDIR/dist/node_modules $PACKAGE_TMPDIR/usr/share/pm2/ mv $PACKAGE_TMPDIR/dist/package.json $PACKAGE_TMPDIR/usr/share/pm2/ cp packager/debian/copyright $PACKAGE_TMPDIR/usr/share/doc/pm2/copyright INSTALLED_SIZE=`du -sk $PACKAGE_TMPDIR | cut -f 1` sed -i "s/__VERSION__/$VERSION/" packager/debian/control sed -i "s/__INSTALLED_SIZE__/$INSTALLED_SIZE/" packager/debian/control mkdir -p $PACKAGE_TMPDIR/etc/default echo "[+] Adding default configuration file for pm2 to package." cat < $PACKAGE_TMPDIR/etc/default/pm2 ## ## Default configuration var for pm2 ## # Path for PM2's home (configuration files, modules, sockets... etc) export PM2_HOME=/etc/pm2 # User that own files in PM2_HOME export PM2_SOCKET_USER=\`id -u pm2\` # Group that own files in PM2_HOME export PM2_SOCKET_GROUP=\`id -g pm2\` EOF mkdir -p $PACKAGE_TMPDIR/etc/systemd/system/ echo "[+] Adding systemd configuration for pm2 to package." cat < $PACKAGE_TMPDIR/etc/systemd/system/pm2.service [Unit] Description=PM2 process manager Documentation=https://pm2.keymetrics.io/ After=network.target [Service] Type=forking LimitNOFILE=infinity LimitNPROC=infinity LimitCORE=infinity PIDFile=/etc/pm2/pm2.pid Restart=on-failure ExecStart=/usr/bin/pm2 resurrect ExecReload=/usr/bin/pm2 reload all ExecStop=/usr/bin/pm2 kill [Install] WantedBy=multi-user.target EOF # These are unneeded and throw lintian lint errors rm -f $PACKAGE_TMPDIR/usr/share/pm2/node_modules/node-uuid/benchmark/bench.gnu find $PACKAGE_TMPDIR/usr/share/pm2 \( -name '*.md' -o -name '*.md~' -o -name '*.gitmodules' \) -delete # Assume everything else is junk we don't need rm -rf $PACKAGE_TMPDIR/dist # Currently the "binaries" are JavaScript files that expect to be in the same # directory as the libraries, so we can't just copy them directly to /usr/bin. # We set the path and pass the args in another script instead. mkdir -p $PACKAGE_TMPDIR/usr/bin/ cat < $PACKAGE_TMPDIR/usr/bin/pm2 #!/bin/bash . /etc/default/pm2 /usr/share/pm2/bin/pm2 \$@ EOF chmod a+x $PACKAGE_TMPDIR/usr/bin/pm2 #### Build RPM fpm --input-type dir --chdir $PACKAGE_TMPDIR \ --name pm2 \ --url https://pm2.io/ \ --category 'Development/Languages' \ --license AGPLv3 \ --description '$(cat packager/debian/description)' \ --vendor 'Keymetrics ' \ --maintainer 'Alexandre Strzelewicz ' \ --version $PM2_VERSION \ --after-install packager/rhel/postinst \ --before-remove packager/rhel/prerm \ --after-remove packager/rhel/postrm \ --architecture noarch \ --depends nodejs \ --output-type rpm . ##### Adapt files for Debian-like distro mkdir -p $PACKAGE_TMPDIR/DEBIAN mkdir -p $PACKAGE_TMPDIR/usr/share/lintian/overrides/ cp packager/debian/lintian-overrides $PACKAGE_TMPDIR/usr/share/lintian/overrides/pm2 # Debian/Ubuntu call the Node.js binary "nodejs", not "node". sed -i 's/env node/env nodejs/' $PACKAGE_TMPDIR/usr/share/pm2/bin/pm2 # Replace variables in Debian package control file cp packager/debian/* $PACKAGE_TMPDIR/DEBIAN/. ls $PACKAGE_TMPDIR/DEBIAN/ ##### Build DEB (Debian, Ubuntu) package fakeroot dpkg-deb -b $PACKAGE_TMPDIR "pm2_"$VERSION"_all.deb" ================================================ FILE: packager/build-dist.sh ================================================ #!/bin/sh set -ex #npm run build npm pack rm -rf dist mkdir dist mv pm2-*.tgz dist/pack.tgz cd dist tar -xzf pack.tgz --strip 1 rm -rf pack.tgz npm install --production cd .. # First run that print a banner node dist/bin/pm2 --version # cleanup find -name "*~" -delete # fix chmod chmod 755 `find -name LICENSE` chmod a+x `find -name "*.sh"` tar -cvzf dist/pm2-v`node dist/bin/pm2 --version`.tar.gz dist/* shasum -a 256 dist/pm2-*.tar.gz ================================================ FILE: packager/debian/control ================================================ Package: pm2 Version: __VERSION__ Depends: nodejs (>= 6.12.2) Conflicts: nodejs (<< 0.12.0) Section: devel Priority: optional Architecture: all Installed-Size: __INSTALLED_SIZE__ Maintainer: Alexandre Strzelewicz Homepage: http://pm2.io/ Description: PM2 - Process Manager for Node.js. PM2 is a Process Manager mainly for Node.js that allows to automatically increase performance and stability while enhancing the process management experience for developer and DevOps. ================================================ FILE: packager/debian/copyright ================================================ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: PM2 Upstream-Contact: Alexandre Strzelewicz Source: http://pm2.o/ Files: * Copyright: 2013-present, Keymetrics License: AGPLv3 ================================================ FILE: packager/debian/description ================================================ Advanced Process Manager PM2: Process Manager for Node.js PM2 is a Process Manager mainly for Node.js that allows to automatically increase performance and stability while enhancing the process management experience for developer and DevOps. ================================================ FILE: packager/debian/lintian-overrides ================================================ # No changelog file at the moment pm2: debian-changelog-file-missing # node-gyp throws this pm2: python-script-but-no-python-dep # Some .js files in node_modules have 0777 permissions instead of 0666 pm2: executable-not-elf-or-script # Some node_modules have node in shebang pm2: unusual-interpreter # Third-party licenses pm2: extra-license-file # lib/async.js in node_modules pm2: embedded-javascript-library # No manpage... yet! pm2: binary-without-manpage ================================================ FILE: packager/debian/postinst ================================================ #!/bin/bash set -e adduser --quiet --system \ --group --home /etc/pm2 \ --gecos "PM2 Process Manager" pm2 if hash systemctl 2> /dev/null; then { systemctl enable "pm2.service" && \ systemctl start "pm2.service" } || echo "pm2 could not be registered or started" elif hash service 2> /dev/null; then service "pm2" start || echo "pm2 could not be registered or started" else echo 'Ingnoring pm2 auto-startup.' echo 'You can run `pm2 startup` as root to do it manually.' fi ================================================ FILE: packager/debian/postrm ================================================ #!/bin/bash deluser --quiet pm2 || true ================================================ FILE: packager/debian/prerm ================================================ #!/bin/bash if hash systemctl 2> /dev/null; then systemctl disable "pm2.service" && \ systemctl stop "pm2.service" || \ echo "pm2 wasn't even running!" elif hash service 2> /dev/null; then service "pm2" stop || echo "pm2 wasn't even running!" else echo "Your system does not appear to use upstart, systemd or sysv, so pm2 could not be stopped" echo 'Unless these systems were removed since install, no processes have been left running' fi ================================================ FILE: packager/publish_deb_rpm.sh ================================================ #!/bin/bash REPOSITORY_NAME="keymetrics/pm2" for OSDIST in 'ubuntu/trusty' 'ubuntu/xenial' 'ubuntu/yakkety' 'ubuntu/zesty' 'ubuntu/artful' 'debian/wheezy' 'debian/jessie' 'debian/stretch' 'debian/buster' 'raspbian/wheezy' 'raspbian/jessie' 'raspbian/stretch' 'raspbian/buster' do package_cloud push $REPOSITORY_NAME/$OSDIST `find -name "*.deb"` done for OSDIST in 'el/5' 'el/6' 'el/7' 'poky/jethro' 'poky/krogoth' do package_cloud push $REPOSITORY_NAME/$OSDIST `find -name "*.rpm"` done ================================================ FILE: packager/rhel/postinst ================================================ #!/bin/bash set -e mkdir -p /etc/pm2 adduser --system \ --home-dir /etc/pm2 \ --comment "PM2 Process Manager" pm2 chown -R pm2:pm2 /etc/pm2 if hash systemctl 2> /dev/null; then { systemctl enable "pm2.service" && \ systemctl start "pm2.service" } || echo "pm2 could not be registered or started" elif hash service 2> /dev/null; then service "pm2" start || echo "pm2 could not be registered or started" else echo 'Ingnoring pm2 auto-startup.' echo 'You can run `pm2 startup` as root to do it manually.' fi ================================================ FILE: packager/rhel/postrm ================================================ #!/bin/bash userdel pm2 || true groupdel pm2 || true ================================================ FILE: packager/rhel/prerm ================================================ #!/bin/bash if hash systemctl 2> /dev/null; then systemctl disable "pm2.service" && \ systemctl stop "pm2.service" || \ echo "pm2 wasn't even running!" elif hash service 2> /dev/null; then service "pm2" stop || echo "pm2 wasn't even running!" else echo "Your system does not appear to use upstart, systemd or sysv, so pm2 could not be stopped" echo 'Unless these systems were removed since install, no processes have been left running' fi ================================================ FILE: packager/setup.deb.sh ================================================ #!/bin/bash REPOSITORY_OWNER="Keymetrics" show_banner () { echo echo "__/\\\\\\\\\\\\\\\\\\\\\\\\\\____/\\\\\\\\____________/\\\\\\\\____/\\\\\\\\\\\\\\\\\\_____" echo " _\\/\\\\\\/////////\\\\\\_\\/\\\\\\\\\\\\________/\\\\\\\\\\\\__/\\\\\\///////\\\\\\___" echo " _\\/\\\\\\_______\\/\\\\\\_\\/\\\\\\//\\\\\\____/\\\\\\//\\\\\\_\\///______\\//\\\\\\__" echo " _\\/\\\\\\\\\\\\\\\\\\\\\\\\\\/__\\/\\\\\\\\///\\\\\\/\\\\\\/_\\/\\\\\\___________/\\\\\\/___" echo " _\\/\\\\\\/////////____\\/\\\\\\__\\///\\\\\\/___\\/\\\\\\________/\\\\\\//_____" echo " _\\/\\\\\\_____________\\/\\\\\\____\\///_____\\/\\\\\\_____/\\\\\\//________" echo " _\\/\\\\\\_____________\\/\\\\\\_____________\\/\\\\\\___/\\\\\\/___________" echo " _\\/\\\\\\_____________\\/\\\\\\_____________\\/\\\\\\__/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\_" echo " _\\///______________\\///______________\\///__\\///////////////__" echo " Community Edition Setup" echo } unknown_os () { echo "Unfortunately, your operating system distribution and version might not be supported by this script." echo echo "You can override the OS detection by setting os= and dist= prior to running this script." echo "For example, to force Ubuntu Trusty: os=ubuntu dist=trusty ./script.sh" echo echo "For more informations, please read the documentation on http://pm2.io/" exit 1 } gpg_check () { echo "Checking for gpg..." if command -v gpg > /dev/null; then echo "Detected gpg..." else echo "Installing gnupg for GPG verification..." apt-get install -y gnupg if [ "$?" -ne "0" ]; then echo "Unable to install GPG! Your base system has a problem; please check your default OS's package repositories because GPG should work." echo "Repository installation aborted." exit 1 fi fi } curl_check () { echo "Checking for curl..." if command -v curl > /dev/null; then echo "Detected curl..." else echo "Installing curl..." apt-get install -q -y curl if [ "$?" -ne "0" ]; then echo "Unable to install curl! Your base system has a problem; please check your default OS's package repositories because curl should work." echo "Repository installation aborted." exit 1 fi fi } install_debian_keyring () { if [ "${os}" = "debian" ]; then echo "Installing debian-archive-keyring which is needed for installing " echo "apt-transport-https on many Debian systems." apt-get install -y debian-archive-keyring &> /dev/null fi } detect_os () { if [[ ( -z "${os}" ) && ( -z "${dist}" ) ]]; then # some systems dont have lsb-release yet have the lsb_release binary and # vice-versa if [ -e /etc/lsb-release ]; then . /etc/lsb-release if [ "${ID}" = "raspbian" ]; then os=${ID} dist=`cut --delimiter='.' -f1 /etc/debian_version` else os=${DISTRIB_ID} dist=${DISTRIB_CODENAME} if [ -z "$dist" ]; then dist=${DISTRIB_RELEASE} fi fi elif [ `which lsb_release 2>/dev/null` ]; then dist=`lsb_release -c | cut -f2` os=`lsb_release -i | cut -f2 | awk '{ print tolower($1) }'` elif [ -e /etc/debian_version ]; then # some Debians have jessie/sid in their /etc/debian_version # while others have '6.0.7' os=`cat /etc/issue | head -1 | awk '{ print tolower($1) }'` if grep -q '/' /etc/debian_version; then dist=`cut --delimiter='/' -f1 /etc/debian_version` else dist=`cut --delimiter='.' -f1 /etc/debian_version` fi else unknown_os fi fi if [ -z "$dist" ]; then unknown_os fi # remove whitespace from OS and dist name os="${os// /}" dist="${dist// /}" echo "Detected operating system as $os/$dist." } install_node () { # Official install method of # https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions # without using sudo. curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - || exit 1 } main () { show_banner detect_os curl_check gpg_check # Need to first run apt-get update so that apt-transport-https can be # installed echo -n "Running apt-get update... " apt-get update &> /dev/null echo "done." # Install the debian-archive-keyring package on debian systems so that # apt-transport-https can be installed next install_debian_keyring echo -n "Installing apt-transport-https... " apt-get install -y apt-transport-https &> /dev/null echo "done." install_node gpg_key_url="https://packagecloud.io/$REPOSITORY_OWNER/pm2/gpgkey" apt_config_url="https://packagecloud.io/install/repositories/$REPOSITORY_OWNER/pm2/config_file.list?os=${os}&dist=${dist}&source=script" apt_source_path="/etc/apt/sources.list.d/"$REPOSITORY_OWNER"_pm2.list" echo -n "Installing $apt_source_path..." # create an apt config file for this repository curl -sSf "${apt_config_url}" > $apt_source_path curl_exit_code=$? if [ "$curl_exit_code" = "22" ]; then echo "This script is unable to download the repository definition." echo [ -e $apt_source_path ] && rm $apt_source_path unknown_os elif [ "$curl_exit_code" = "35" -o "$curl_exit_code" = "60" ]; then echo "curl is unable to connect to packagecloud.io over TLS when running: " echo " curl ${apt_config_url}" echo "This is usually due to one of two things:" echo echo " 1.) Missing CA root certificates (make sure the ca-certificates package is installed)" echo " 2.) An old version of libssl. Try upgrading libssl on your system to a more recent version" echo echo "Contact support@packagecloud.io with information about your system for help." [ -e $apt_source_path ] && rm $apt_source_path exit 1 elif [ "$curl_exit_code" -gt "0" ]; then echo echo "Unable to run: " echo " curl ${apt_config_url}" echo echo "Double check your curl installation and try again." [ -e $apt_source_path ] && rm $apt_source_path exit 1 else echo "done." fi echo -n "Importing packagecloud gpg key... " # import the gpg key curl -L "${gpg_key_url}" 2> /dev/null | apt-key add - &>/dev/null echo "done." echo -n "Running apt-get update... " # update apt on this system apt-get update &> /dev/null echo "done." echo -n "Installing PM2..." apt-get install -y pm2 &> /dev/null echo "done." CURR_USER=$SUDO_USER if [ "$CURR_USER" == "" ]; then CURR_USER=$USER fi if [ "$CURR_USER" == "root" ] || [ "$CURR_USER" == "" ]; then echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "WARNING: You are either running this script as root or the" echo " \$USER variable is empty. In order to have a" echo " working PM2 installation, you need to add your" echo " user in the pm2 group using the following" echo " command: usermod -aG pm2 " echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" else echo -n "Adding $CURR_USER to group pm2..." usermod -aG pm2 $CURR_USER echo "done." fi echo echo "Installation done." echo "You now need to logout of your system and login again in order to be able to use the 'pm2' command." } main ================================================ FILE: packager/setup.rpm.sh ================================================ #!/bin/bash REPOSITORY_OWNER="Keymetrics" show_banner () { echo echo "__/\\\\\\\\\\\\\\\\\\\\\\\\\\____/\\\\\\\\____________/\\\\\\\\____/\\\\\\\\\\\\\\\\\\_____" echo " _\\/\\\\\\/////////\\\\\\_\\/\\\\\\\\\\\\________/\\\\\\\\\\\\__/\\\\\\///////\\\\\\___" echo " _\\/\\\\\\_______\\/\\\\\\_\\/\\\\\\//\\\\\\____/\\\\\\//\\\\\\_\\///______\\//\\\\\\__" echo " _\\/\\\\\\\\\\\\\\\\\\\\\\\\\\/__\\/\\\\\\\\///\\\\\\/\\\\\\/_\\/\\\\\\___________/\\\\\\/___" echo " _\\/\\\\\\/////////____\\/\\\\\\__\\///\\\\\\/___\\/\\\\\\________/\\\\\\//_____" echo " _\\/\\\\\\_____________\\/\\\\\\____\\///_____\\/\\\\\\_____/\\\\\\//________" echo " _\\/\\\\\\_____________\\/\\\\\\_____________\\/\\\\\\___/\\\\\\/___________" echo " _\\/\\\\\\_____________\\/\\\\\\_____________\\/\\\\\\__/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\_" echo " _\\///______________\\///______________\\///__\\///////////////__" echo " Community Edition Setup" echo } unknown_os () { echo "Unfortunately, your operating system distribution and version might not be supported by this script." echo echo "You can override the OS detection by setting os= and dist= prior to running this script." echo "For example, to force Ubuntu Trusty: os=ubuntu dist=trusty ./script.sh" echo echo "For more informations, please read the documentation on http://pm2.io/" exit 1 } curl_check () { echo "Checking for curl..." if command -v curl > /dev/null; then echo "Detected curl..." else echo "Installing curl..." yum install -d0 -e0 -y curl fi } detect_os () { if [[ ( -z "${os}" ) && ( -z "${dist}" ) ]]; then if [ -e /etc/os-release ]; then . /etc/os-release os=${ID} if [ "${os}" = "poky" ]; then dist=`echo ${VERSION_ID}` elif [ "${os}" = "sles" ]; then dist=`echo ${VERSION_ID}` elif [ "${os}" = "opensuse" ]; then dist=`echo ${VERSION_ID}` else dist=`echo ${VERSION_ID} | awk -F '.' '{ print $1 }'` fi elif [ `which lsb_release 2>/dev/null` ]; then # get major version (e.g. '5' or '6') dist=`lsb_release -r | cut -f2 | awk -F '.' '{ print $1 }'` # get os (e.g. 'centos', 'redhatenterpriseserver', etc) os=`lsb_release -i | cut -f2 | awk '{ print tolower($1) }'` elif [ -e /etc/oracle-release ]; then dist=`cut -f5 --delimiter=' ' /etc/oracle-release | awk -F '.' '{ print $1 }'` os='ol' elif [ -e /etc/fedora-release ]; then dist=`cut -f3 --delimiter=' ' /etc/fedora-release` os='fedora' elif [ -e /etc/redhat-release ]; then os_hint=`cat /etc/redhat-release | awk '{ print tolower($1) }'` if [ "${os_hint}" = "centos" ]; then dist=`cat /etc/redhat-release | awk '{ print $3 }' | awk -F '.' '{ print $1 }'` os='centos' elif [ "${os_hint}" = "scientific" ]; then dist=`cat /etc/redhat-release | awk '{ print $4 }' | awk -F '.' '{ print $1 }'` os='scientific' else dist=`cat /etc/redhat-release | awk '{ print tolower($7) }' | cut -f1 --delimiter='.'` os='redhatenterpriseserver' fi else aws=`grep -q Amazon /etc/issue` if [ "$?" = "0" ]; then dist='6' os='aws' else unknown_os fi fi fi if [[ ( -z "${os}" ) || ( -z "${dist}" ) ]]; then unknown_os fi # remove whitespace from OS and dist name os="${os// /}" dist="${dist// /}" echo "Detected operating system as ${os}/${dist}." } finalize_yum_repo () { echo "Installing pygpgme to verify GPG signatures..." yum install -y pygpgme --disablerepo='Keymetrics_pm2' pypgpme_check=`rpm -qa | grep -qw pygpgme` if [ "$?" != "0" ]; then echo echo "WARNING: " echo "The pygpgme package could not be installed. This means GPG verification is not possible for any RPM installed on your system. " echo "To fix this, add a repository with pygpgme. Usualy, the EPEL repository for your system will have this. " echo "More information: https://fedoraproject.org/wiki/EPEL#How_can_I_use_these_extra_packages.3F" echo # set the repo_gpgcheck option to 0 sed -i'' 's/repo_gpgcheck=1/repo_gpgcheck=0/' /etc/yum.repos.d/Keymetrics_pm2.repo fi echo "Installing yum-utils..." yum install -y yum-utils --disablerepo='Keymetrics_pm2' yum_utils_check=`rpm -qa | grep -qw yum-utils` if [ "$?" != "0" ]; then echo echo "WARNING: " echo "The yum-utils package could not be installed. This means you may not be able to install source RPMs or use other yum features." echo fi echo "Generating yum cache for Keymetrics_pm2..." yum -q makecache -y --disablerepo='*' --enablerepo='Keymetrics_pm2' } finalize_zypper_repo () { zypper --gpg-auto-import-keys refresh Keymetrics_pm2 } install_node () { curl --silent --location https://rpm.nodesource.com/setup_lts.x | bash - || exit 1 } install_pm2 () { PKG_MANAGER=$1 echo -n "Installing PM2 with $PKG_MANAGER..." $PKG_MANAGER install -y pm2 2> /dev/null CURR_USER=$SUDO_USER if [ "$CURR_USER" == "" ]; then CURR_USER=$USER fi if [ "$CURR_USER" == "root" ] || [ "$CURR_USER" == "" ]; then echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "WARNING: You are either running this script as root or the" echo " \$USER variable is empty. In order to have a" echo " working PM2 installation, you need to add your" echo " user in the pm2 group using the following" echo " command: usermod -aG pm2 " echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" else echo -n "Adding $CURR_USER to group pm2..." usermod -aG pm2 $CURR_USER echo "done." fi } main () { show_banner detect_os curl_check yum_repo_config_url="https://packagecloud.io/install/repositories/$REPOSITORY_OWNER/pm2/config_file.repo?os=${os}&dist=${dist}&source=script" if [ "${os}" = "sles" ] || [ "${os}" = "opensuse" ]; then yum_repo_path=/etc/zypp/repos.d/Keymetrics_pm2.repo else yum_repo_path=/etc/yum.repos.d/Keymetrics_pm2.repo install_node fi echo "Downloading repository file: ${yum_repo_config_url}" curl -sSf "${yum_repo_config_url}" > $yum_repo_path curl_exit_code=$? if [ "$curl_exit_code" = "22" ]; then echo echo echo -n "Unable to download repo config from: " echo "${yum_repo_config_url}" echo echo "This usually happens if your operating system is not supported by " echo "packagecloud.io, or this script's OS detection failed." echo echo "You can override the OS detection by setting os= and dist= prior to running this script." echo "You can find a list of supported OSes and distributions on our website: https://packagecloud.io/docs#os_distro_version" echo echo "For example, to force CentOS 6: os=el dist=6 ./script.sh" echo echo "If you are running a supported OS, please email support@packagecloud.io and report this." [ -e $yum_repo_path ] && rm $yum_repo_path exit 1 elif [ "$curl_exit_code" = "35" -o "$curl_exit_code" = "60" ]; then echo echo "curl is unable to connect to packagecloud.io over TLS when running: " echo " curl ${yum_repo_config_url}" echo echo "This is usually due to one of two things:" echo echo " 1.) Missing CA root certificates (make sure the ca-certificates package is installed)" echo " 2.) An old version of libssl. Try upgrading libssl on your system to a more recent version" echo echo "Contact support@packagecloud.io with information about your system for help." [ -e $yum_repo_path ] && rm $yum_repo_path exit 1 elif [ "$curl_exit_code" -gt "0" ]; then echo echo "Unable to run: " echo " curl ${yum_repo_config_url}" echo echo "Double check your curl installation and try again." [ -e $yum_repo_path ] && rm $yum_repo_path exit 1 else echo "done." fi if [ "${os}" = "sles" ] || [ "${os}" = "opensuse" ]; then finalize_zypper_repo install_pm2 zypper else finalize_yum_repo install_pm2 yum fi echo echo "Installation done." echo "You now need to logout of your system and login again in order to be able to use the 'pm2' command." } main ================================================ FILE: paths.js ================================================ /** * Copyright 2013-2022 the PM2 project authors. All rights reserved. * Use of this source code is governed by a license that * can be found in the LICENSE file. */ var debug = require('debug')('pm2:paths'); var p = require('path'); var fs = require('fs') function getDefaultPM2Home() { var PM2_ROOT_PATH; if (process.env.PM2_HOME) PM2_ROOT_PATH = process.env.PM2_HOME; else if (process.env.HOME && !process.env.HOMEPATH) PM2_ROOT_PATH = p.resolve(process.env.HOME, '.pm2'); else if (process.env.HOME || process.env.HOMEPATH) PM2_ROOT_PATH = p.resolve(process.env.HOMEDRIVE, process.env.HOME || process.env.HOMEPATH, '.pm2'); else { console.error('[PM2][Initialization] Environment variable HOME (Linux) or HOMEPATH (Windows) are not set!'); console.error('[PM2][Initialization] Defaulting to /etc/.pm2'); PM2_ROOT_PATH = p.resolve('/etc', '.pm2'); } debug('pm2 home resolved to %s', PM2_ROOT_PATH, process.env.HOME); return PM2_ROOT_PATH; } module.exports = function(PM2_HOME) { var has_node_embedded = false if (fs.existsSync(p.resolve(__dirname, './node')) === true) { has_node_embedded = true } if (!PM2_HOME) { PM2_HOME = getDefaultPM2Home() } var pm2_file_stucture = { PM2_HOME : PM2_HOME, PM2_ROOT_PATH : PM2_HOME, PM2_CONF_FILE : p.resolve(PM2_HOME, 'conf.js'), PM2_MODULE_CONF_FILE : p.resolve(PM2_HOME, 'module_conf.json'), PM2_LOG_FILE_PATH : p.resolve(PM2_HOME, 'pm2.log'), PM2_PID_FILE_PATH : p.resolve(PM2_HOME, 'pm2.pid'), PM2_RELOAD_LOCKFILE : p.resolve(PM2_HOME, 'reload.lock'), DEFAULT_PID_PATH : p.resolve(PM2_HOME, 'pids'), DEFAULT_LOG_PATH : p.resolve(PM2_HOME, 'logs'), DEFAULT_MODULE_PATH : p.resolve(PM2_HOME, 'modules'), PM2_IO_ACCESS_TOKEN : p.resolve(PM2_HOME, 'pm2-io-token'), DUMP_FILE_PATH : p.resolve(PM2_HOME, 'dump.pm2'), DUMP_BACKUP_FILE_PATH : p.resolve(PM2_HOME, 'dump.pm2.bak'), DAEMON_RPC_PORT : p.resolve(PM2_HOME, 'rpc.sock'), DAEMON_PUB_PORT : p.resolve(PM2_HOME, 'pub.sock'), INTERACTOR_RPC_PORT : p.resolve(PM2_HOME, 'interactor.sock'), INTERACTOR_LOG_FILE_PATH : p.resolve(PM2_HOME, 'agent.log'), INTERACTOR_PID_PATH : p.resolve(PM2_HOME, 'agent.pid'), INTERACTION_CONF : p.resolve(PM2_HOME, 'agent.json5'), HAS_NODE_EMBEDDED : has_node_embedded, BUILTIN_NODE_PATH : has_node_embedded === true ? p.resolve(__dirname, './node/bin/node') : null, BUILTIN_NPM_PATH : has_node_embedded === true ? p.resolve(__dirname, './node/bin/npm') : null, }; // allow overide of file paths via environnement var paths = Object.keys(pm2_file_stucture); paths.forEach(function (key) { var envKey = key.indexOf('PM2_') > -1 ? key : 'PM2_' + key; if (process.env[envKey] && key !== 'PM2_HOME' && key !== 'PM2_ROOT_PATH') { pm2_file_stucture[key] = process.env[envKey]; } }); if (process.platform === 'win32' || process.platform === 'win64') { //@todo instead of static unique rpc/pub file custom with PM2_HOME or UID pm2_file_stucture.DAEMON_RPC_PORT = '\\\\.\\pipe\\rpc.sock'; pm2_file_stucture.DAEMON_PUB_PORT = '\\\\.\\pipe\\pub.sock'; pm2_file_stucture.INTERACTOR_RPC_PORT = '\\\\.\\pipe\\interactor.sock'; } return pm2_file_stucture; }; ================================================ FILE: pm2 ================================================ #!/bin/bash SCRIPT_PATH="$(dirname "$0")/../lib/binaries/CLI.js" # Check if 'bun' is available, otherwise use 'node' if command -v bun &> /dev/null then bun "$SCRIPT_PATH" "$@" else node "$SCRIPT_PATH" "$@" fi ================================================ FILE: preinstall.js ================================================ const fs = require('fs'); const path = require('path'); // Determine platform const isWindows = process.platform === 'win32'; if (!isWindows) process.exit(0) const sourceFile = 'bin/pm2-windows'; const destinationFile = 'bin/pm2'; // Resolve file paths const sourcePath = path.resolve(__dirname, sourceFile); const destinationPath = path.resolve(__dirname, destinationFile); // Copy the appropriate file based on the platform fs.copyFile(sourcePath, destinationPath, (err) => { if (err) { console.error(`Error copying file from ${sourcePath} to ${destinationPath}:`, err); process.exit(1); } console.log(`Successfully copied ${sourceFile} to ${destinationFile}`); }); ================================================ FILE: pres/TMP.md ================================================ ### Commands Cheatsheet
Commands Cheatsheet ```bash # General $ npm install pm2 -g # Install PM2 $ pm2 start app.js # Start, Daemonize and auto-restart application (Node) $ pm2 start app.py # Start, Daemonize and auto-restart application (Python) $ pm2 start npm -- start # Start, Daemonize and auto-restart Node application # Cluster Mode (Node.js only) $ pm2 start app.js -i 4 # Start 4 instances of application in cluster mode # it will load balance network queries to each app $ pm2 reload all # Zero Second Downtime Reload $ pm2 scale [app-name] 10 # Scale Cluster app to 10 process # Process Monitoring $ pm2 list # List all processes started with PM2 $ pm2 list --sort= # Sort all processes started with PM2 $ pm2 monit # Display memory and cpu usage of each app $ pm2 show [app-name] # Show all information about application # Log management $ pm2 logs # Display logs of all apps $ pm2 logs [app-name] # Display logs for a specific app $ pm2 logs --json # Logs in JSON format $ pm2 flush $ pm2 reloadLogs # Process State Management $ pm2 start app.js --name="api" # Start application and name it "api" $ pm2 start app.js -- -a 34 # Start app and pass option "-a 34" as argument $ pm2 start app.js --watch # Restart application on file change $ pm2 start script.sh # Start bash script $ pm2 start app.json # Start all applications declared in app.json $ pm2 reset [app-name] # Reset all counters $ pm2 stop all # Stop all apps $ pm2 stop 0 # Stop process with id 0 $ pm2 restart all # Restart all apps $ pm2 delete all # Kill and delete all apps $ pm2 delete 0 # Delete app with id 0 # Startup/Boot management $ pm2 startup # Detect init system, generate and configure pm2 boot on startup $ pm2 save # Save current process list $ pm2 resurrect # Restore previously saved processes $ pm2 unstartup # Disable and remove startup system $ pm2 update # Save processes, kill PM2 and restore processes $ pm2 init # Generate a sample js configuration file # Deployment $ pm2 deploy app.json prod setup # Setup "prod" remote server $ pm2 deploy app.json prod # Update "prod" remote server $ pm2 deploy app.json prod revert 2 # Revert "prod" remote server by 2 # Module system $ pm2 module:generate [name] # Generate sample module with name [name] $ pm2 install pm2-logrotate # Install module (here a log rotation system) $ pm2 uninstall pm2-logrotate # Uninstall module $ pm2 publish # Increment version, git push and npm publish ```
Also check out the [example folder](https://github.com/Unitech/pm2/tree/master/examples) to discover all features. ================================================ FILE: run.sh ================================================ #!/bin/bash # Check if 'bun' is available, otherwise use 'node' if command -v bun &> /dev/null then bun "$@" else node "$@" fi ================================================ FILE: test/Dockerfile ================================================ FROM node:alpine RUN mkdir -p /var/pm2 WORKDIR /var/pm2 ENV NODE_ENV test ENV PM2_DISCRETE_MODE true RUN apk update && apk add bash git curl python python3 php5 && rm -rf /var/cache/apk/* RUN ln -s /usr/bin/php5 /usr/bin/php RUN npm install -g mocha@3.5 CMD ["mocha", "./test/programmatic/api.mocha.js"] ================================================ FILE: test/README.md ================================================ # Installing development version ```bash $ npm install git://github.com/Unitech/pm2.git#development -g ``` # Redhat ``` $ sudo yum install git wget emacs $ sudo yum groupinstall "Development Tools" $ wget -qO- https://raw.github.com/creationix/nvm/master/install.sh | sh $ # put .bash_profile content to .bashrc $ source .bashrc $ nvm install v0.11.10 $ nvm alias default 0.11.10 $ npm install pm2 -g $ # OR $ npm install git://github.com/Unitech/pm2.git#development -g ``` # CentOS ``` $ yum install git wget emacs $ wget -qO- https://raw.github.com/creationix/nvm/master/install.sh | sh $ ``` ## Remove init script sudo update-rc.d -f pm2-init.sh remove ``` $ chkconfig --del pm2-init.sh $ chkconfig --add pm2-init.sh ``` gyp WARN EACCES user "root" does not have permission to create dev dir : https://github.com/TooTallNate/node-gyp/issues/126 -> add --unsafe-perm # .pm2 Doesnt work ``` $ sudo sh -c 'echo "export PM2_HOME=/var/" >> /etc/profile' $ sudo mkdir /var/.pm2; chown -R tknew:tknew /var/.pm2 ``` ================================================ FILE: test/benchmarks/monit-daemon.sh ================================================ #!/bin/bash while [ true ] do PM2_PID=`pgrep "pm2: Daemon" -o` # Run garbage collector kill -SIGILL $PM2_PID sleep 5 FILE="/proc/$PM2_PID/smaps" Rss=`echo 0 $(cat $FILE | grep Rss | awk '{print $2}' | sed 's#^#+#') | bc;` echo `date +%H:%M:%S` $Rss >> $RESULT_FILE sleep 100 done ================================================ FILE: test/benchmarks/monit.sh ================================================ #!/bin/bash RESULT_FILE=result.monit export RESULT_FILE=$RESULT_FILE launch() { echo "========= `date`" >> $RESULT_FILE nohup ./monit-daemon.sh &> monit.log & } ppkill() { pkill -f monit-daemon.sh ; pkill -f sleep } case "$1" in start) launch ;; kill) ppkill ;; stop) ppkill ;; restart) ppkill launch ;; *) echo "Usage: {start|kill|stop|restart}" exit 1 ;; esac exit $RETVAL ================================================ FILE: test/benchmarks/result.monit ================================================ ========= Fri Aug 22 14:11:29 EDT 2014 14:11:35 61012 ========= Fri Aug 22 14:11:45 EDT 2014 14:11:50 59984 ========= Fri Aug 22 14:12:47 EDT 2014 14:12:52 59464 ================================================ FILE: test/docker_parallel_test.sh ================================================ set -e docker build -t pm2-test -f test/Dockerfile . JOBS=2 OPTS="--jobs $JOBS --joblog joblog-X docker run -v `pwd`:/var/pm2 pm2-test" ls test/e2e/cli/* | parallel $OPTS bash #ls test/e2e/binaries/* test/e2e/modules/* test/e2e/internal/* test/e2e/process-file/* test/e2e/cli/* test/e2e/logs/* | parallel $OPTS bash ================================================ FILE: test/e2e/binaries/pm2-dev.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" pm2_path=`pwd`/bin/pm2-dev if [ ! -f $pm2_path ]; then pm2_path=`pwd`/../bin/pm2-dev if [ ! -f $pm2_path ]; then pm2_path=`pwd`/../../bin/pm2-dev fi fi pm2dev="$pm2_path" export PM2_HOME=$HOME'/.pm2-dev' cd $file_path/pm2-dev # Test with js $pm2dev app.js & sleep 2 $pm2 ls should 'should have started 1 apps' 'online' 1 should 'should watch be true' 'watch: true' 1 pkill -f Daemon $pm2 kill echo "THEN" # Test with json and args $pm2dev start app.json --test-mode $pm2 ls should 'should have started 1 apps' 'online' 1 $pm2 prettylist | grep "watch: \[ 'server', 'client' \]" spec "Should application have two watch arguments" $pm2 prettylist | grep "ignore_watch: \[ 'node_modules', 'client/img' \]" spec "Should application have two ignore_watch arguments" $pm2 kill ================================================ FILE: test/e2e/binaries/pm2-runtime.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" pm2_path=`pwd`/bin/pm2-runtime if [ ! -f $pm2_path ]; then pm2_path=`pwd`/../bin/pm2-runtime if [ ! -f $pm2_path ]; then pm2_path=`pwd`/../../bin/pm2-runtime fi fi pm2_runtime="$pm2_path" export PM2_RUNTIME_DEBUG='true' cd $file_path/pm2-dev # # Simple start with 4 apps # $pm2 kill pkill -f PM2 $pm2_runtime app.js -i 4 should 'should have started 4 apps' 'online' 4 $pm2 kill # # Test with json and args # $pm2_runtime app.json should 'should have started 1 apps' 'online' 1 $pm2 prettylist | grep "watch: \[ 'server', 'client' \]" spec "Should application have two watch arguments" $pm2 prettylist | grep "ignore_watch: \[ 'node_modules', 'client/img' \]" spec "Should application have two ignore_watch arguments" $pm2 kill # Restore default behavior for exit checks unset PM2_RUNTIME_DEBUG # # --no-autorestart checks # # $pm2_runtime app.js --no-autorestart # PID_PM2=$! # $pm2 pid app # echo "OK" # PID=`cat /tmp/pid` # echo $PID # kill $PID # sleep 3 # pgrep "PM2" # ispec "PM2 runtime should be killed because no app is running" # # Auto Exit Worker # $pm2_runtime exited_app.js 2> /dev/null sleep 1 pgrep "PM2" ispec "PM2 runtime should be killed because no app is running" ================================================ FILE: test/e2e/cli/app-configuration.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" node -e "require('semver').lt(process.versions.node, '6.0.0') ? process.exit(0) : process.exit(1)" [ $? -eq 1 ] || exit 0 cd $file_path $pm2 unset echo spec "Should unset echo variables" $pm2 start echo.js --name "echo" should 'should app be online' 'online' 1 should 'should not have config variable' "config_var: 'false'" 0 $pm2 set echo.config_var false exists 'should NOW have config variable' "config_var: 'false'" $pm2 set echo.probes true exists 'should NOW have config variable' "probes: 'true'" should 'should have start 3 apps' 'restart_time: 2' 1 $pm2 multiset "echo.conf false" exists 'should NOW have config variable' "conf: 'false'" should 'should have start 3 apps' 'restart_time: 3' 1 # $pm2 get echo.config_var | grep "false" # spec "Should get method work" # $pm2 get echo | grep "false\|true" # spec "Should get method work" # $pm2 conf echo.config_var | grep "false" # spec "Should conf method work" # $pm2 conf echo | grep "false\|true" # spec "Should get method work" $pm2 delete all # # # # $pm2 unset "probe-test" $pm2 start probes.js --name "probe-test" echo "Wait for init..." sleep 3 exists 'probe test-probe exist' "test-probe" exists 'probe Event Loop Latency exist' "Event Loop Latency p95" # Set new value for alert probe # $pm2 set probe-test.probes.Event\ Loop\ Latency.value 25 # sleep 1 # exists 'probe Event Loop Latency alerted' "alert: { cmp: '>', value: 25, mode: 'threshold'" # Override value for test-probe # $pm2 set probe-test.probes.test-probe.value 30 # sleep 1 ================================================ FILE: test/e2e/cli/args.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/args rm args.log > args.log ## Test #1 $pm2 start -f params_check.js --merge-logs -o args.log --name 'some-project' -- \ --logger.level=info \ --koa.hostname=0.0.0.0 \ --ms.amqp.hostname=amqps://localhost \ --ms.amqp.port=5673 \ --ms.amqp.heartbeat=30 \ --ms.amqp.username=someuser \ --ms.amqp.password=12345 \ --ms.amqp.vhost=default \ --mailer.config.host=localhost \ --mailer.config.auth=null function hasArg { if [ "$2" ] then OCCURENCES=$2 else OCCURENCES=1 fi CMD=`grep -c -- $1 args.log` [ $CMD -eq $OCCURENCES ] || fail "Arg $1 not present" success "Arg $1 present" } sleep 2 hasArg "--logger.level=info" hasArg "--koa.hostname=0.0.0.0" hasArg "--ms.amqp.hostname=amqps://localhost" hasArg "--ms.amqp.port=5673" hasArg "--ms.amqp.heartbeat=30" hasArg "--ms.amqp.username=someuser" hasArg "--ms.amqp.password=12345" hasArg "--ms.amqp.vhost=default" hasArg "--mailer.config.host=localhost" hasArg "--mailer.config.auth=null" ## Test #2 with double params $pm2 delete all >args.log $pm2 start -f params_check.js echo.js --merge-logs -o args.log -- argv1 argv1 argv2 argv3 sleep 2 hasArg "argv1" 2 hasArg "argv2" hasArg "argv3" ================================================ FILE: test/e2e/cli/attach.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/stdin $pm2 start stdin.js -o out-rel.log --merge-logs >out-rel.log # Send LINE\n to stdin application $pm2 send 0 "LINE" cat out-rel.log grep "LINE" out-rel.log spec "Should have reveived line" $pm2 delete all ================================================ FILE: test/e2e/cli/binary.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path function getInterpreter() { echo `$pm2 prettylist | grep "exec_interpreter:" | awk -F"'" '{print $2}'` } # # Testing pm2 execution of binary files # $pm2 start `type -p watch` -- ls OUT=$(getInterpreter) [ $OUT="none" ] || fail "$1" success "$1" $pm2 kill $pm2 start binary-js-file OUT=$(getInterpreter) echo $OUT [ $OUT="node" ] || fail "$1" success "$1" $pm2 kill $pm2 start binary-js-file.js OUT=$(getInterpreter) [ $OUT="node" ] || fail "$1" success "$1" $pm2 kill $pm2 start binary-py-file.py OUT=$(getInterpreter) [ $OUT="python" ] || fail "$1" success "$1" $pm2 kill # # Should execute command in $PATH # $pm2 start ls spec "Should script started" OUT=$(getInterpreter) [ $OUT="none" ] || fail "$1" success "Right interpreter" should 'Have the right relative path' '/bin/ls' 1 ================================================ FILE: test/e2e/cli/bun.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/interpreter ########### typescript fork test $pm2 delete all >typescript.log $pm2 start echo.ts -o typescript.log --merge-logs sleep 1.5 grep "Hello Typescript!" typescript.log spec "Should work on Typescript files in fork mode" # ########### typescript cluster test $pm2 delete all >typescript.log $pm2 start echo.tsx -o typescript.log --merge-logs sleep 1.5 grep "Hello Typescript!" typescript.log spec "Should work on Typescript files in fork mode" $pm2 delete all ================================================ FILE: test/e2e/cli/cli-actions-1.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path # Determine wget / curl which wget > /dev/null if [ $? -eq 0 ] then http_get="wget" else echo -e "\033[31mYou need wget to run this test \033[0m"; exit 1; fi # # Different way to stop process # $pm2 start echo.js $pm2 start echo.js -f $pm2 start echo.js -f sleep 0.5 should 'should have started 3 apps' 'online' 3 $pm2 stop 12412 $pm2 stop 0 should 'should have stopped 1 apps' 'stopped' 1 $pm2 stop asdsdaecho.js $pm2 stop echo should 'should have stopped 3 apps' 'stopped' 3 # # Describe process # $pm2 describe 0 spec "should describe stopped process" $pm2 restart 1 $pm2 describe 1 spec "should describe online process" $pm2 describe asdsa ispec "should exit with right exit code when no process found" # # Update pm2 # $pm2 updatePM2 spec "should update pm2" # # Verify PID # $pm2 kill $pm2 start echo.js -p echo.pid sleep 0.5 ls echo-0.pid spec "should pid file exists" $pm2 stop all sleep 1 ls echo-0.pid ispec "should pid file be deleted once stopped" $pm2 kill $pm2 start echo.js -p echo.pid -i 1 sleep 1 ls echo-0.pid spec "should pid file exists" $pm2 stop all sleep 1 ls echo-0.pid ispec "should pid file be deleted once stopped" $pm2 kill # # Main tests # $pm2 kill spec "kill daemon" $pm2 start eyayimfake ispec "should fail if script doesnt exist" $pm2 ispec "No argument" $pm2 list $pm2 start cluster-pm2.json spec "Should start well formated json with name for file prefix" $pm2 list spec "Should list processes successfully" $pm2 start multi-echo.json spec "Should start multiple applications" $pm2 init echo spec "Should init echo sample json" $pm2 start echo-pm2.json -f spec "Should start echo service" $pm2 list # Not consistent on travis :( # OUT=`$pm2 logs --nostream --lines 10 PM2 | wc -l` # [ $OUT -gt 10 ] || fail "Error : pm2 logs ouput showed $OUT lines but min is 10" # success "should only print logs" # OUT=`$pm2 logs --nostream --lines 100 PM2 | wc -l` # [ $OUT -gt 100 ] || fail "Error : pm2 logs ouput showed $OUT lines but min is 100" # success "should only print logs: " # sleep 1 # kill $! # spec "Should kill logs" # $pm2 logs echo & # spec "Should display logs" # TMPPID=$! # sleep 1 # kill $! # spec "Should kill logs" # $pm2 web # spec "Should start web interface" # sleep 1 # JSON_FILE='/tmp/web-json' # $http_get -q http://localhost:9615/ -O $JSON_FILE # cat $JSON_FILE | grep "HttpInterface.js" > /dev/null # spec "Should get the right JSON with HttpInterface file launched" # $pm2 flush # spec "Should clean logs" # # cat ~/.pm2/logs/echo-out.log | wc -l # # spec "File Log should be cleaned" # sleep 1 # $http_get -q http://localhost:9615/ -O $JSON_FILE # cat $JSON_FILE | grep "restart_time\":0" > /dev/null # spec "Should get the right JSON with HttpInterface file launched" # # # # Restart only one process # # # $pm2 restart 1 # should 'should has restarted process' 'restart_time: 1' 1 # # # # Restart all processes # # # $pm2 restart all # spec "Should restart all processes" # sleep 1 # $http_get -q http://localhost:9615/ -O $JSON_FILE # OUT=`cat $JSON_FILE | grep -o "restart_time\":1" | wc -l` # [ $OUT -eq 7 ] || fail "Error while wgeting data via web interface" # success "Got data from interface" $pm2 start echo-env.js $pm2 list $pm2 dump spec "Should dump current processes" $pm2 save spec "Should save (dump alias) current processes" ls ~/.pm2/dump.pm2 spec "Dump file should be present" $pm2 stop all spec "Should stop all processes" sleep 0.5 should 'should have stopped 8 apps' 'stopped' 8 $pm2 kill # # Issue #71 # PROC_NAME='ECHONEST' # Launch a script with name option $pm2 start echo.js --name $PROC_NAME -f should 'should have started app with name' 'ECHONEST' 7 # Restart a process by name $pm2 restart $PROC_NAME should 'should have restarted app by name' 'restart_time: 1' 1 $pm2 kill $pm2 resurrect spec "Should resurrect all apps" sleep 0.5 should 'should have resurrected all processes' 'restart_time' 8 $pm2 delete all spec "Should delete all processes" sleep 0.5 should 'should have deleted process' 'restart_time' 0 $pm2 kill spec "Should kill daemon" ================================================ FILE: test/e2e/cli/cli-actions-2.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path ############# Start / Stop / Restart echo "---- Start an app, stop it, if state stopped and started, restart stopped app" $pm2 start echo.js spec "Should start an app by script.js" $pm2 stop echo.js spec "Should stop an app by script.js" $pm2 restart echo.js spec "Should restart an app by script.js (TRANSITIONAL STATE)" ############### Start edge case $pm2 delete all echo "Start application with filename starting with a numeric" $pm2 start 001-test.js should 'should app be online' 'online' 1 $pm2 stop 001-test should 'should app be stopped' 'stopped' 1 $pm2 restart 001-test should 'should app be online once restart called' 'online' 1 ############## PID $pm2 delete all $pm2 start 001-test.js --name "test" should 'should app be online' 'online' 1 $pm2 pid > /tmp/pid-tmp $pm2 pid test ############### $pm2 delete all echo "Start application with filename starting with a numeric" $pm2 start throw-string.js -l err-string.log --merge-logs --no-automation >err-string.log sleep 1 grep 'throw-string.js' err-string.log spec "Should have written raw stack when throwing a string" #### $pm2 delete all $pm2 start echo.js --name gege should 'should app be online' 'online' 1 $pm2 stop gege should 'should app be stopped' 'stopped' 1 $pm2 restart gege should 'should app be online once restart called' 'online' 1 ############### $pm2 delete all echo "---- BY_NAME Start an app, stop it, if state stopped and started, restart stopped app" $pm2 start echo.js --name gege should 'should app be online' 'online' 1 $pm2 stop gege should 'should app be stopped' 'stopped' 1 $pm2 restart gege should 'should app be online once restart called' 'online' 1 ############### $pm2 delete all echo "Start an app, start it one more time, if started, throw message" $pm2 start echo.js $pm2 start echo.js ispec "Should not re start app" ########### DELETED STUFF BY ID $pm2 delete all $pm2 start echo.js $pm2 delete 0 should 'should has been deleted process by id' "name: 'echo'" 0 ########### DELETED STUFF BY NAME $pm2 delete all $pm2 start echo.js --name test $pm2 delete test should 'should has been deleted process by name' "name: 'test'" 0 ########### DELETED STUFF BY SCRIPT $pm2 delete all $pm2 start echo.js $pm2 delete echo.js $pm2 list should 'should has been deleted process by script' "name: 'echo'" 0 ######## Actions on app name as number (#1937) $pm2 delete all $pm2 start echo.js --name "455" should 'should restart processes' 'restart_time: 0' 1 $pm2 restart 455 should 'should restart processes' 'restart_time: 1' 1 $pm2 restart 0 should 'should restart processes' 'restart_time: 2' 1 $pm2 stop 455 should 'should app be stopped' 'stopped' 1 $pm2 delete 455 should 'should has been deleted process by id' "name: '455'" 0 $pm2 kill ########### OPTIONS OUTPUT FILES $pm2 delete all $pm2 start echo.js -o outech.log -e errech.log --name gmail -i 2 sleep 2 cat outech-0.log > /dev/null spec "file outech-0.log exist" cat errech-0.log > /dev/null spec "file errech-0.log exist" ########### Stdout / Stderr rm stdout-stderr.log $pm2 start stdout-stderr.js -l stdout-stderr.log --merge-logs sleep 2 cat stdout-stderr.log | grep "outwrite" spec "stdout written" cat stdout-stderr.log | grep "outcb" spec "stdout cb written" cat stdout-stderr.log | grep "errwrite" spec "stderr written" cat stdout-stderr.log | grep "errcb" spec "stderr cb written" $pm2 delete all ## #2350 verify all script have been killed $pm2 start python-script.py $pm2 start echo.js should 'should app be online' 'online' 2 kill `cat ~/.pm2/pm2.pid` spec "should have killed pm2" sleep 3 # pgrep "python" # ispec "should python script be killed" ================================================ FILE: test/e2e/cli/dump.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path $pm2 start echo.js -i 4 spec "should start 4 processes" should 'should have 4 apps started' 'online' 4 rm -f ~/.pm2/dump.pm2 ~/.pm2/dump.pm2.bak $pm2 save spec "should save process list" ls ~/.pm2/dump.pm2 spec "dump file should exist" ls ~/.pm2/dump.pm2.bak ispec "dump backup file should not exist" $pm2 save spec "should save and backup process list" ls ~/.pm2/dump.pm2 spec "dump file should exist" ls ~/.pm2/dump.pm2.bak spec "dump backup file should exist" ================================================ FILE: test/e2e/cli/ecosystem.e2e.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/ecosystem $pm2 start ecosystem.config.js ================================================ FILE: test/e2e/cli/env-refresh.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path echo -e "\033[1mENV REFRESH\033[0m" # # REFRESH with Restart via CLI # TEST_VARIABLE='hello1' $pm2 start env.js -o out-env.log --merge-logs --name "env" >out-env.log sleep 0.5 grep "hello1" out-env.log &> /dev/null spec "should contain env variable" TEST_VARIABLE='89hello89' $pm2 restart env --update-env sleep 1.0 grep "89hello89" out-env.log &> /dev/null spec "should contain refreshed environment variable" >out-env.log TEST_VARIABLE="CLUNEWSTER" $pm2 restart env sleep 0.5 grep "89hello89" out-env.log &> /dev/null spec "should not change environment (--skip-env)" $pm2 delete all # # Cluster mode # >out-env.log $pm2 start env.js -o out-env.log --merge-logs sleep 1 grep "undefined" out-env.log &> /dev/null spec "should contain nothing" >out-env.log TEST_VARIABLE="CLUSTER" $pm2 reload env --update-env sleep 1 grep "CLUSTER" out-env.log &> /dev/null spec "should contain CLUSTER" >out-env.log TEST_VARIABLE="CLUNEWSTER" $pm2 reload env sleep 1 grep "CLUSTER" out-env.log &> /dev/null spec "should contain not change environment (--skip-env)" # # REFRESH with Restart via JSON # $pm2 start env.json >out-env.log sleep 0.5 grep "YES" out-env.log &> /dev/null spec "should contain env variable" $pm2 restart env-refreshed.json >out-env.log sleep 0.5 grep '{"HEYYYY":true}' out-env.log &> /dev/null spec "should contain refreshed env variable via json" $pm2 start env-ecosystem.json --env production >out-env.log sleep 0.5 grep "No worries!" out-env.log &> /dev/null spec "should use deploy.production.env.TEST_VARIABLE" $pm2 kill # Bun edit require('module').globalPaths does not return paths if [ "$IS_BUN" = false ]; then $pm2 l NODE_PATH='/test' $pm2 start local_require.js should 'should have loaded the right globalPaths' 'restart_time: 0' 1 $pm2 kill $pm2 l NODE_PATH='/test2' $pm2 start local_require.js -i 1 should 'should have loaded the right globalPaths' 'restart_time: 0' 1 fi ================================================ FILE: test/e2e/cli/extra-lang.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/extra-lang which php spec "should php cli be installed" which python3 spec "should python cli be installed" # # JSON # $pm2 start apps.json should 'should have started 2 apps' 'online' 2 >python-app.log >php-app-out.log >php-error.log sleep 1 grep "Python" python-app.log spec "Python script should have written data in log file" grep "PHP" php-app-out.log spec "PHP script should have written data in log file" grep "ERROR" php-error.log spec "PHP script should have written data in error log file" # Switch to production environment $pm2 restart apps.json --env production should 'should have started 2 apps' 'online' 2 >python-app.log >php-app-out.log >php-error.log sleep 1 grep "PythonProduction" python-app.log spec "Python script should have written data in log file (Production mode)" # # CLI # $pm2 delete all >cli-python.log $pm2 start echo.py --interpreter="/usr/bin/python3" --interpreter-args="-u" --log="cli-python.log" --merge-logs should 'should have started 1 app' 'onl\ine' 1 sleep 1 grep "RAWPython" cli-python.log spec "Python script should have written data in log file" ================================================ FILE: test/e2e/cli/fork.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path ########### Fork mode $pm2 start echo.js -x should 'should start app in fork mode' 'fork_mode' 1 $pm2 restart echo.js should 'should has restarted app' 'restart_time: 1' 1 ########### Fork mode $pm2 kill $pm2 start bashscript.sh should 'should start app in fork mode' 'fork_mode' 1 ########### Auto Detective Interpreter In Fork mode ### Dump resurrect should be ok $pm2 dump $pm2 kill #should 'should has forked app' 'fork' 0 $pm2 resurrect should 'should has forked app' 'fork_mode' 1 ## Delete $pm2 list $pm2 delete 0 should 'should has delete process' 'fork_mode' 0 ================================================ FILE: test/e2e/cli/mjs.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/mjs # Activate test only for Node > 9.0.0 node -e "require('semver').gte(process.versions.node, '9.0.0') ? process.exit(0) : process.exit(1)" [ $? -eq 0 ] || exit 0 $pm2 start --node-args="--experimental-modules" index.mjs -o outech.log -e errech.log >outech.log >errech.log sleep 1 should 'should app be online in fork mode with MJS support' 'online' 1 $pm2 delete all $pm2 start --node-args="--experimental-modules" -i 2 index.mjs sleep 1 should 'should app be online in cluster mode with MJS support' 'online' 2 ================================================ FILE: test/e2e/cli/monit.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path $pm2 link xxx aaa $pm2 start http.js -i 4 spec "should start 4 processes" $pm2 monitor 0 should 'should monitoring flag enabled (id)' '_km_monitored: true' 1 $pm2 unmonitor 0 should 'should monitoring flag disabled (id)' '_km_monitored: false' 1 $pm2 monitor http should 'should monitoring flag enabled (name)' '_km_monitored: true' 4 $pm2 unmonitor http should 'should monitoring flag disabled (name)' '_km_monitored: false' 4 $pm2 monitor all should 'should monitoring flag enabled ' '_km_monitored: true' 4 $pm2 unmonitor all should 'should monitoring flag disabled (name)' '_km_monitored: false' 4 ================================================ FILE: test/e2e/cli/multiparam.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path ## Start $pm2 start child.js echo.js server.js should 'should app be online' 'online' 3 ## Restart $pm2 restart child echo server should 'should app be online' 'online' 3 should 'should all script been restarted one time' 'restart_time: 1' 3 ## Stop $pm2 stop child echo server should 'should app be stopped' 'stopped' 3 ## Delete $pm2 delete child echo server shouldnot 'should app be deleted' 'stopped' 3 ================================================ FILE: test/e2e/cli/operate-regex.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path $pm2 start echo.js --name "echo-3" $pm2 start echo.js --name "echo-1" $pm2 start echo.js --name "echo-2" sleep 0.5 should 'should have started 3 apps' 'online' 3 $pm2 stop /echo-[1,2]/ should 'should have stopped 2 apps' 'stopped' 2 should 'only one app should still be online' 'online' 1 $pm2 stop /echo-3/ should 'should have stopped 1 apps' 'online' 0 $pm2 restart /echo-[1,2]/ should 'should have restarted 2 apps' 'online' 2 ================================================ FILE: test/e2e/cli/piped-config.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path ############# TEST cat all.json | $pm2 start - should 'should start processes' 'online' 6 cat all.json | $pm2 delete - should 'should delete all processes' 'name' 0 ================================================ FILE: test/e2e/cli/plus.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path $pm2 start echo.js $pm2 prettylist | grep "km_link: false" spec "should km_link not be enabled" $pm2 plus alcz82ewyhy2va6 litfrsovr52celr --install-all should 'have started 3 apps' 'online' 3 should 'all application be monitored' 'km_link: true' 3 $pm2 plus delete should 'have started 1 apps' 'online' 1 $pm2 prettylist | grep "km_link: false" spec "should km_link be disabled" ================================================ FILE: test/e2e/cli/python-support.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/extra-lang # # Config file # $pm2 start app-python.config.js --only 'echo-python-1' should 'should mode be fork' 'fork_mode' 1 should 'should have started 1 apps' 'online' 1 $pm2 delete all # Check with multi instances $pm2 start app-python.config.js --only 'echo-python-max' should 'should mode be fork' 'fork_mode' 4 should 'should have started 4 apps' 'online' 4 # Should keep same params on restart $pm2 restart all should 'should have restarted processes' 'restart_time: 1' 4 should 'should mode be fork' 'fork_mode' 4 $pm2 delete all # # CLI # $pm2 start echo.py should 'should mode be fork' 'fork_mode' 1 should 'should have started 1 apps' 'online' 1 $pm2 delete all $pm2 start echo.py -i 4 should 'should mode be fork' 'fork_mode' 4 should 'should have started 4 apps' 'online' 4 $pm2 restart all should 'should have restarted processes' 'restart_time: 1' 4 should 'should mode be fork' 'fork_mode' 4 ================================================ FILE: test/e2e/cli/reload.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path $pm2 start delayed_exit.js -i 2 should 'should start processes' 'online' 2 should 'should app be in cluster mode' "exec_mode: 'cluster_mode'" 2 OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"` > $OUT_LOG $pm2 reload delayed_exit sleep 1 OUT=`grep "SIGINT" "$OUT_LOG" | wc -l` [ $OUT -eq 1 ] || fail "Signal not received by the process name" success "Processes sucessfully receives the SIGINT signal" $pm2 kill $pm2 start delayed_exit.js should 'should start processes' 'online' 1 $pm2 stop delayed_exit.js sleep 3 should 'should stop processes' 'stopped' 1 $pm2 restart delayed_exit.js should 'should restart processes' 'restart_time: 0' 1 $pm2 restart delayed_exit.js sleep 3 should 'should restart processes' 'restart_time: 1' 1 $pm2 kill # $pm2 start delayed_exit.js -i 2 # should 'should start processes' 'online' 2 # $pm2 stop delayed_exit.js # sleep 3 # should 'should stop processes' 'stopped' 2 # $pm2 restart delayed_exit.js # should 'should restart processes' 'restart_time: 0' 2 # $pm2 restart delayed_exit.js # should 'should restart processes' 'restart_time: 1' 2 # $pm2 reload delayed_exit.js # should 'should restart processes' 'restart_time: 2' 2 # $pm2 gracefulReload delayed_exit.js # should 'should restart processes' 'restart_time: 3' 2 # $pm2 kill $pm2 start child.js -i 4 sleep 0.5 should 'should start processes' 'online' 4 $pm2 restart all should 'should restarted be one for all' 'restart_time' 4 $pm2 restart child.js should 'should restart a second time (BY SCRIPT NAME)' 'restart_time: 2' 4 $pm2 restart child should 'should restart a third time (BY NAME)' 'restart_time: 3' 4 sleep 0.5 $pm2 reload all sleep 0.5 should 'should RELOAD a fourth time' 'restart_time: 4' 4 ############### CLUSTER STUFF $pm2 kill $pm2 start child.js -i 4 should 'should start processes' 'online' 4 $pm2 start network.js -i 4 should 'should has 8 online apps' 'online' 8 should 'should has 4 api online' 'network.js' 4 should 'should has 4 child.js online' 'child.js' 4 $pm2 reload all should 'should reload all' 'restart_time' 8 $pm2 reload child.js should 'should reload only child.js' 'restart_time: 2' 4 $pm2 reload network.js should 'should reload network.js' 'restart_time: 2' 8 ############### BLOCKING STUFF # this is not a networked application $pm2 start echo.js should 'should has 8 online apps' 'online' 9 $pm2 reload echo should 'should not hang and fallback to restart behaviour' 'restart_time' 9 ############### NO-AUTORESTART $pm2 kill $pm2 start killtoofast.js --no-autorestart should 'should not restart' 'restart_time: 0' 1 $pm2 delete all $pm2 start no-restart.json should 'should not restart' 'restart_time: 0' 1 ############### STOP EXIT CODES $pm2 kill $pm2 start exitcode42.js --stop-exit-codes 42 sleep 2 should 'should not restart' 'restart_time: 0' 1 $pm2 delete all $pm2 start exitcode42.js --stop-exit-codes 34 sleep 1 shouldnot 'should restart' 'restart_time: 0' 1 $pm2 kill $pm2 start exitcode42.js --stop-exit-codes 3 sleep 1 shouldnot 'should restart processes' 'restart_time: 0' 1 $pm2 kill $pm2 delete all $pm2 start stop-exit-codes.json sleep 0.5 should 'should not restart' 'restart_time: 0' 1 ############### Via ENV: SEND() instead of KILL() $pm2 kill export PM2_KILL_USE_MESSAGE='true' $pm2 start signal-send.js should 'should start processes' 'online' 1 OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"` > $OUT_LOG $pm2 reload signal-send.js sleep 1 OUT=`grep "shutdown" "$OUT_LOG" | wc -l` [ $OUT -eq 1 ] || fail "Signal not received by the process name" success "Processes sucessfully receives the signal" unset PM2_KILL_USE_MESSAGE ############### VIA --shutdown-with-message $pm2 kill $pm2 start signal-send.js --shutdown-with-message should 'should start processes' 'online' 1 OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"` > $OUT_LOG $pm2 reload signal-send.js sleep 1 OUT=`grep "shutdown" "$OUT_LOG" | wc -l` [ $OUT -eq 1 ] || fail "Signal not received by the process name" success "Processes sucessfully receives the signal" ================================================ FILE: test/e2e/cli/reset.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path echo "################## RESET ###################" # # BY ID # $pm2 start echo.js should 'should restarted be one for all' 'restart_time: 0' 1 $pm2 restart 0 should 'should process restarted' 'restart_time: 1' 1 $pm2 reset 0 should 'should process reseted' 'restart_time: 0' 1 # # BY NAME # $pm2 start echo.js -i 4 -f should 'should restarted be one for all' 'restart_time: 0' 5 $pm2 restart echo should 'should process restarted' 'restart_time: 1' 5 $pm2 reset echo should 'should process reseted' 'restart_time: 0' 5 # # ALL # $pm2 restart all $pm2 restart all $pm2 restart all should 'should process restarted' 'restart_time: 3' 5 $pm2 reset all should 'should process reseted' 'restart_time: 0' 5 # # Restart delay test # $pm2 delete all $pm2 start killtoofast.js --restart-delay 5000 should 'should process not have been restarted yet' 'restart_time: 0' 1 $pm2 kill ================================================ FILE: test/e2e/cli/resurrect.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path $pm2 start echo.js -i 4 spec "should start 4 processes" should 'should have 4 apps started' 'online' 4 $pm2 save $pm2 resurrect spec "should resurrect from dump" should 'should have still 4 apps started' 'online' 4 $pm2 save $pm2 delete all echo "[{" > ~/.pm2/dump.pm2 $pm2 resurrect spec "should resurrect from backup if dump is broken" ls ~/.pm2/dump.pm2 ispec "should delete broken dump" should 'should have still 4 apps started' 'online' 4 $pm2 delete all $pm2 resurrect spec "should resurrect from backup if dump is missing" should 'should have still 4 apps started' 'online' 4 ================================================ FILE: test/e2e/cli/right-exit-code.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path $pm2 kill $pm2 restart BULLSHIT ispec "Unknown process = error exit" $pm2 restart 666 ispec "Unknown process = error exit" $pm2 restart all ispec "No process = error exit" $pm2 stop all ispec "No process = error exit" $pm2 delete 10 ispec "No process = error exit" $pm2 delete toto ispec "No process = error exit" ================================================ FILE: test/e2e/cli/serve.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/serve PORT=8081 PORT_2=8082 echo "################## PM2 SERVE ###################" $pm2 serve --port $PORT should 'should have started serving dir' 'online' 1 curl http://localhost:$PORT/ > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l` [ $OUT -eq 1 ] || fail "should have served index file under /" success "should have served index file under /" curl http://localhost:$PORT/index.html > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l` [ $OUT -eq 1 ] || fail "should have served index file under /index.html" success "should have served index file under /index.html" echo "Shutting down the server" $pm2 delete all curl http://localhost:$PORT/index.html > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l` [ $OUT -eq 0 ] || fail "should be offline" success "should be offline" echo "testing SPA" $pm2 serve . $PORT --spa should 'should have started serving dir' 'online' 1 curl http://localhost:$PORT/ > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l` [ $OUT -eq 1 ] || fail "should have served index file under /index.html" success "should have served index file under /index.html" curl http://localhost:$PORT/index.html > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l` [ $OUT -eq 1 ] || fail "should have served index file under /index.html" success "should have served index file under /index.html" curl http://localhost:$PORT/other.html > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | wc -l` [ $OUT -eq 2 ] || fail "should have served file under /other.html" success "should have served file under /other.html" curl http://localhost:$PORT/mangezdespommes/avecpepin/lebref > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l` [ $OUT -eq 1 ] || fail "should have served index file under /index.html" success "should have served index file under /index.html" curl http://localhost:$PORT/mangezdespommes/avecpepin/lebref/other.html > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | wc -l` [ $OUT -eq 2 ] || fail "should have served file under /other.html" success "should have served file under /other.html" echo "Shutting down the server" $pm2 delete all echo "testing basic auth" $pm2 serve . $PORT --basic-auth-username user --basic-auth-password pass should 'should have started serving dir' 'online' 1 curl http://user:pass@localhost:$PORT/index.html > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l` [ $OUT -eq 1 ] || fail "should have served index file under /index.html" success "should have served index file under /index.html" echo "Shutting down the server" $pm2 delete all echo "Testing with static ecosystem" $pm2 start ecosystem-serve.json should 'should have started serving dir' 'online' 1 curl http://user:pass@localhost:8081/index.html > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l` [ $OUT -eq 1 ] || fail "should be listening on port 8081" success "should be listening on port 8081" curl http://user:pass@localhost:8081/mangezdesmangues/aupakistan > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l` [ $OUT -eq 1 ] || fail "should be listening on port 8081" success "should be listening on port 8081" echo "Shutting down the server" $pm2 delete all $pm2 serve . $PORT_2 should 'should have started serving dir' 'online' 1 curl http://localhost:$PORT_2/index.html > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l` [ $OUT -eq 1 ] || fail "should be listening on port $PORT_2" success "should be listening on port $PORT_2" node -e "require('semver').lt(process.versions.node, '6.0.0') ? process.exit(0) : process.exit(1)" [ $? -eq 1 ] || exit 0 $pm2 delete all $pm2 serve . $PORT_2 --name frontend should 'should have started serving dir' 'online' 1 should 'should have custom name' 'frontend' 7 curl http://localhost:$PORT_2/index.html > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l` [ $OUT -eq 1 ] || fail "should be listening on port $PORT_2" success "should be listening on port $PORT_2" curl http://localhost:$PORT_2/yolo.html > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -o "your file doesnt exist" | wc -l` [ $OUT -eq 1 ] || fail "should have served custom 404 file" success "should have served custom 404 file" $pm2 delete all $pm2 start ecosystem.json should 'should have started serving dir' 'online' 1 curl http://localhost:8081/index.html > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l` [ $OUT -eq 1 ] || fail "should be listening on port 8081" success "should be listening on port 8081" $pm2 stop ecosystem.json curl http://localhost:8081/index.html > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -o "good shit" | wc -l` [ $OUT -eq 0 ] || fail "should be offline" success "should be offline" ================================================ FILE: test/e2e/cli/smart-start.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path # # Test for SMART start # $pm2 start echo.js should 'process should have been started' 'restart_time: 0' 1 should 'process should have been started' 'online' 1 $pm2 stop echo should 'process should have been started' 'stopped' 1 $pm2 start echo should 'process should have been started' 'online' 1 $pm2 start echo should 'process should have been started' 'restart_time: 1' 1 should 'process should have been started' 'online' 1 $pm2 start 0 should 'process should have been started' 'restart_time: 2' 1 should 'process should have been started' 'online' 1 # $pm2 stop echo # should 'process should have been started' 'stopped' 1 # $pm2 start all # should 'process should have been started' 'restart_time: 2' 1 # should 'process should have been started' 'online' 1 ================================================ FILE: test/e2e/cli/sort.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/sort $pm2 start http.js $pm2 start other.js $pm2 list --sort=name > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -v "npm" | grep -no "http" -m 1 | cut -f1 -d:` OUT2=`cat /tmp/tmp_out.txt | grep -v "npm" | grep -no "other" -m 1 | cut -f1 -d:` [ $OUT -lt $OUT2 ] || fail "should sort app by name (asc)" success "should sort app by name (asc)" $pm2 list --sort=name:desc > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -v "npm" | grep -no "http" -m 1 | cut -f1 -d:` OUT2=`cat /tmp/tmp_out.txt | grep -v "npm" | grep -no "other" -m 1 | cut -f1 -d:` [ $OUT -gt $OUT2 ] || fail "should sort app by name (desc)" success "should sort app by name (desc)" $pm2 list --sort=id > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -v "npm" | grep -no "http" -m 1 | cut -f1 -d:` OUT2=`cat /tmp/tmp_out.txt | grep -v "npm" | grep -no "other" -m 1 | cut -f1 -d:` [ $OUT -lt $OUT2 ] || fail "should sort app by id (asc)" success "should sort app by id (asc)" $pm2 list --sort=id:desc > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -v "npm" | grep -no "http" -m 1 | cut -f1 -d:` OUT2=`cat /tmp/tmp_out.txt | grep -v "npm" | grep -no "other" -m 1 | cut -f1 -d:` [ $OUT -gt $OUT2 ] || fail "should sort app by id (desc)" success "should sort app by id (desc)" ================================================ FILE: test/e2e/cli/start-app.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/start-app # # Direct command # $pm2 delete all if [ "$IS_BUN" = true ]; then $pm2 start "bun -e 'setTimeout(function() { }, 100000); console.log(process.env.TEST)'" -l test.log --merge-logs else $pm2 start "node -e 'setTimeout(function() { }, 100000); console.log(process.env.TEST)'" -l test.log --merge-logs fi should 'should have started command' 'online' 1 should 'should have not been restarted' 'restart_time: 0' 1 cat test.log | grep "undefined" &> /dev/null sleep 1 spec "should have printed undefined env var" TEST='ok' $pm2 restart 0 --update-env cat test.log | grep "ok" &> /dev/null sleep 1 should 'should have started command' 'online' 1 should 'should have not been restarted' 'restart_time: 1' 1 spec "should have printed undefined env var" # # Direct command via Conf file # $pm2 delete all if [ "$IS_BUN" = true ]; then $pm2 start ecosystem-bun.config.js else $pm2 start ecosystem.config.js fi should 'should have started command' 'online' 1 should 'should have not been restarted' 'restart_time: 0' 1 cat test-conf.log | grep "test_val" 2> /dev/null spec "should have printed the test_val" # # Compile C Program # cd $file_path/c-compile $pm2 start "cc hello.c; ./a.out" -l c-log.log --merge-logs sleep 2 cat c-log.log | grep "Hello World" &> /dev/null spec "should have printed compiled output" ================================================ FILE: test/e2e/cli/startOrX.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" echo -e "\033[1mStartOrX.sh:\033[0m" cd $file_path $pm2 startOrRestart all.json should 'should start processes' 'online' 6 $pm2 startOrRestart all.json should 'should has restarted app' 'restart_time: 1' 6 $pm2 startOrReload all.json should 'should has reloaded app' 'restart_time: 2' 6 # slow # $pm2 startOrGracefulReload all.json # should 'should has graceful reloaded app' 'restart_time: 3' 8 ================================================ FILE: test/e2e/cli/watch.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/watch $pm2 start http.js --watch should '1 should watch be false' 'watch: true' 1 $pm2 stop http should '2 should watch be false when stopping app' 'watch: false' 1 $pm2 start http should '3 should watch be false when starting app' 'watch: false' 1 $pm2 restart http.js should '4 should restart with watch and should be false' 'watch: false' 1 $pm2 delete http $pm2 start http.js should '5 should watch be false' 'watch: false' 1 $pm2 stop http.js should '5 should watch be false' 'watch: false' 1 $pm2 start http.js --watch should '7 should restart with watch and should be true' 'watch: true' 1 $pm2 delete all $pm2 start http.js should '8 should watch be false' 'watch: false' 1 $pm2 restart http.js should '6 should watch be false' 'watch: false' 1 $pm2 restart http.js --watch should '7 should restart with watch and should be true' 'watch: true' 1 $pm2 restart http.js --watch should '8 should restart with watch and should be false' 'watch: false' 1 $pm2 restart http.js --watch should '9 should restart with watch and should be true' 'watch: true' 1 $pm2 stop http.js should '10 should stop app and watch is stopped' 'watch: false' 1 $pm2 restart http.js --watch should '11 should restart stopped app with watch and should be true' 'watch: true' 1 $pm2 restart http.js --watch should '12 should restart with watch and should be false' 'watch: false' 1 $pm2 kill $pm2 start app.json should 'should have started 1 apps' 'online' 1 should 'should have restarted app by name' 'restart_time: 0' 1 should 'should watch be false' 'watch: false' 1 $pm2 restart app.json should 'should have started 1 apps' 'online' 1 should 'should have restarted app by name' 'restart_time: 1' 1 should 'should watch be false' 'watch: false' 1 $pm2 stop app.json should 'should have started 1 apps' 'stopped' 1 should 'should have restarted app by name' 'restart_time: 1' 1 should 'should watch be false' 'watch: false' 1 $pm2 kill $pm2 start app-watch.json should 'should have started 1 apps' 'online' 1 should 'should have restarted app by name' 'restart_time: 0' 1 should 'should watch exist' 'watch: true' 1 $pm2 stop app-watch.json should 'should have started 1 apps' 'stopped' 1 should 'should have restarted app by name' 'restart_time: 0' 1 should 'should watch exist' 'watch: false' 1 $pm2 restart app-watch.json should 'should have started 1 apps' 'online' 1 should 'should have restarted app by name' 'restart_time: 0' 1 should 'should watch exist' 'watch: true' 1 $pm2 restart all should 'should have started 1 apps' 'online' 1 should 'should have restarted app by name' 'restart_time: 1' 1 should 'should watch exist' 'watch: true' 1 $pm2 stop all should 'should have started 1 apps' 'stopped' 1 should 'should have restarted app by name' 'restart_time: 1' 1 should 'should watch exist' 'watch: false' 1 ================================================ FILE: test/e2e/docker.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" # Docker should function dshould { sleep 0.3 CID=`docker ps -lq` docker exec -it $CID /bin/sh -c "PM2_HOME=/root/.pm2-dev/ pm2 prettylist" > /tmp/dout OUT=`cat /tmp/dout | grep -o "$2" | wc -l` [ $OUT -eq $3 ] || fail "$1" success "$1" } function PRODshould { sleep 0.3 CID=`docker ps -lq` docker exec -it $CID /bin/sh -c "pm2 prettylist" > /tmp/dout OUT=`cat /tmp/dout | grep -o "$2" | wc -l` [ $OUT -eq $3 ] || fail "$1" success "$1" } # Bootstrap one app cd $file_path/docker/expressor # Kill all running container docker kill `docker ps -aq -f status=running` rm Dockerfile rm -rf node_modules ################### DEV MODE # # Wrap Application in Development mode # $pm2 start process.json --dockerdaemon --container CID=`docker ps -lq` # Check Dockerfile generation ls Dockerfile spec "Dockerfile have been generated" grep "process.json" Dockerfile spec "Right entry file" sleep 1 # Check running processes inside container dshould 'should have started 2 apps' 'online' 2 dshould 'should the 2 apps not being restarted' 'restart_time: 0' 2 docker kill $CID # # Edit Dockerfile (simulate user who needs a specific library) # sed -i '' '5i\ RUN pm2 install pm2-server-monit ' Dockerfile $pm2 start process.json --dockerdaemon --container sleep 1 grep "RUN pm2 install pm2-server-monit" Dockerfile spec "Custom line should still be present" dshould 'should have started 2 apps' 'online' 2 dshould 'should the 2 apps be stable' 'restart_time: 0' 2 docker kill `docker ps -lq` ################### DISTRIBUTION MODE # # Wrap Application in Distribution mode # $pm2 start process.json --container --dist --image-name pm2ns/test --dockerdaemon sleep 2 grep "RUN pm2 install pm2-server-monit" Dockerfile spec "Custom line should still be present" PRODshould 'should have started 3 apps (2 user app + one module)' 'online' 3 docker kill `docker ps -lq` rm Dockerfile # Re wrap with new generated Dockerfile $pm2 start process.json --container --dist --image-name pm2ns/test --dockerdaemon sleep 1 PRODshould 'should have started 2 apps' 'online' 2 PRODshould 'should the 2 apps not being restarted' 'restart_time: 0' 2 grep "pm2-docker" Dockerfile spec "pm2-docker runtime should be present" # Exit docker kill `docker ps -lq` rm Dockerfile ================================================ FILE: test/e2e/esmodule.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/include.sh" # Bootstrap one app cd $file_path/esmodules/mjs #### FORK MODE $pm2 delete all $pm2 start index.mjs sleep 2 should 'should have detected es module via .mjs file extension and started 1 app' 'online' 1 should 'should have application in stable state' 'restart_time: 0' 1 $pm2 restart index sleep 2 should 'should have detected es module via .mjs file extension and started 1 app' 'online' 1 should 'should have application in stable state' 'restart_time: 1' 1 $pm2 delete all cd $file_path/esmodules/packagemodule $pm2 start index.js sleep 2 should 'should have detected es module via .mjs file extension and started 1 app' 'online' 1 should 'should have application in stable state' 'restart_time: 0' 1 $pm2 restart index sleep 2 should 'should have detected es module via .mjs file extension and started 1 app' 'online' 1 should 'should have application in stable state' 'restart_time: 1' 1 $pm2 save $pm2 update sleep 2 should 'should have detected es module via .mjs file extension and started 1 app' 'online' 1 should 'should have application in stable state' 'restart_time: 0' 1 #### CLUSTER MODE cd $file_path/esmodules/mjs $pm2 delete all $pm2 start index.mjs -i 4 sleep 2 should 'should have detected es module via .mjs file extension and started 4 apps' 'online' 4 should 'should have application in stable state' 'restart_time: 0' 4 $pm2 restart index sleep 2 should 'should have detected es module via .mjs file extension and started 4 app' 'online' 4 should 'should have application in stable state' 'restart_time: 1' 4 $pm2 delete all cd $file_path/esmodules/packagemodule $pm2 start index.js -i 4 sleep 2 should 'should have detected es module via .mjs file extension and started 4 apps' 'online' 4 should 'should have application in stable state' 'restart_time: 0' 4 $pm2 restart index sleep 2 should 'should have detected es module via .mjs file extension and started 4 app' 'online' 4 should 'should have application in stable state' 'restart_time: 1' 4 $pm2 delete all ================================================ FILE: test/e2e/file-descriptor.sh ================================================ #!/usr/bin/env bash # # LSOF check # SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path echo "################## RELOAD ###################" # lsof -c PM2 > /tmp/no_pm2_out.dat # $pm2 list # sleep 1 # lsof -c PM2 > /tmp/empty_pm2_out.dat # $pm2 start echo.js -i 3 # $pm2 start killtoofast.js -i 3 # $pm2 delete all # sleep 3 # lsof -c PM2 > /tmp/empty_pm2_out2.dat # OUT1=`cat /tmp/empty_pm2_out.dat | wc -l` # OUT2=`cat /tmp/empty_pm2_out2.dat | wc -l` # if [ $OUT1 -eq $OUT2 ]; then # success "All file descriptors have been closed" # else # fail "Some file descriptors are still open" # fi # $pm2 start killtoofast.js -i 6 # $pm2 kill # rm /tmp/no_pm2_out.dat # rm /tmp/no_pm2_out2.dat # rm /tmp/empty_pm2_out.dat # rm /tmp/empty_pm2_out2.dat # sleep 6 > /tmp/no_pm_pm2_out.dat > /tmp/no_pm_pm2_out2.dat lsof -c PM2 > /tmp/no_pm2_out2.dat diff /tmp/no_pm2_out.dat /tmp/no_pm2_out2.dat if [ $? == "0" ]; then success "All file descriptors have been closed" else fail "Some file descriptors are still open" fi rm /tmp/no_pm2_out.dat rm /tmp/no_pm2_out2.dat rm /tmp/empty_pm2_out.dat rm /tmp/empty_pm2_out2.dat ================================================ FILE: test/e2e/include.sh ================================================ # # (C) 2013 Unitech.io Inc. # # export PM2_RPC_PORT=4242 # export PM2_PUB_PORT=4243 node="`type -P node`" if command -v bun >/dev/null 2>&1 then IS_BUN=true else IS_BUN=false fi pm2_path=`pwd`/bin/pm2 if [ ! -f $pm2_path ]; then pm2_path=`pwd`/../bin/pm2 if [ ! -f $pm2_path ]; then pm2_path=`pwd`/../../bin/pm2 fi fi pm2="$pm2_path" SRC=$(cd $(dirname "$0"); pwd) file_path="${SRC}/../fixtures" if [ ! -d $file_path ]; then file_path="${SRC}/../../fixtures" if [ ! -d $file_path ]; then file_path="${SRC}/../../../fixtures" fi fi $pm2 link delete $pm2 kill function fail { echo -e "######## ✘ $1" exit 1 } function success { echo -e "------------> ✔ $1" } function spec { RET=$? sleep 0.1 [ $RET -eq 0 ] || fail "$1" success "$1" } function runTest { echo "[~] Starting test $1" START=$(date +%s) bash $1 RET=$? if [ $RET -ne 0 ]; then STR="[RETRY] $1 failed and NOW is getting retried" echo $STR echo $STR >> e2e_time bash $1 RET=$? if [ $RET -ne 0 ]; then fail $1 fi fi END=$(date +%s) DIFF=$(echo "$END - $START" | bc) STR="[V] $1 succeeded and took $DIFF seconds" echo $STR echo $STR >> e2e_time } function ispec { RET=$? sleep 0.2 [ $RET -ne 0 ] || fail "$1" success "$1" } function should { sleep 0.3 $pm2 prettylist > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -v "npm" | grep -o "$2" | wc -l` [ $OUT -eq $3 ] || { [ -n "${4+x}" ] && [ $OUT -eq $4 ]; } || fail "$1" #[ $OUT -eq $3 ] || fail "$1" success "$1" } function shouldnot { sleep 0.3 $pm2 prettylist > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -v "npm" | grep -o "$2" | wc -l` [ $OUT -ne $3 ] || fail "$1" success "$1" } function exists { sleep 0.3 $pm2 prettylist > /tmp/tmp_out.txt OUT=`cat /tmp/tmp_out.txt | grep -v "npm" | grep -o "$2" | wc -l` [ $OUT -ge 1 ] || fail "$1" success "$1" } ================================================ FILE: test/e2e/internals/daemon-paths-override.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" $pm2 kill rm /tmp/.toto.pid ########### Override PM2 pid path PM2_PID_FILE_PATH=/tmp/.toto.pid $pm2 ls sleep 2 test -f /tmp/.toto.pid spec 'should have picked the pm2 pid path' ================================================ FILE: test/e2e/internals/increment-var.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/increment-var/ $pm2 delete all echo '-------- CLUSTER MODE TEST -------' $pm2 start ecosystem.json --only sample-normal should "start 2 processes" "online" 2 should "start one process with NODE_APP_INSTANCE at 0" "NODE_APP_INSTANCE: 0" 1 should "start one process with NODE_APP_INSTANCE at 1" "NODE_APP_INSTANCE: 1" 1 should "not start one process with NODE_APP_INSTANCE at 2" "NODE_APP_INSTANCE: 2" 0 $pm2 scale sample-normal 4 should "start 2 more processes" "online" 4 should "start one process with NODE_APP_INSTANCE at 0" "NODE_APP_INSTANCE: 0" 1 should "start one process with NODE_APP_INSTANCE at 1" "NODE_APP_INSTANCE: 1" 1 should "start one process with NODE_APP_INSTANCE at 2" "NODE_APP_INSTANCE: 2" 1 should "start one process with NODE_APP_INSTANCE at 3" "NODE_APP_INSTANCE: 3" 1 should "not start one process with NODE_APP_INSTANCE at 4" "NODE_APP_INSTANCE: 4" 0 $pm2 scale sample-normal 2 should "deleted 2 more processes" "online" 2 should "not have the process with NODE_APP_INSTANCE at 0" "NODE_APP_INSTANCE: 0" 0 should "not have the process with NODE_APP_INSTANCE at 1" "NODE_APP_INSTANCE: 1" 0 should "have one process with NODE_APP_INSTANCE at 2" "NODE_APP_INSTANCE: 2" 1 should "have one process with NODE_APP_INSTANCE at 3" "NODE_APP_INSTANCE: 3" 1 $pm2 scale sample-normal 4 should "start 2 more processes" "online" 4 should "should reuse old number with NODE_APP_INSTANCE at 0" "NODE_APP_INSTANCE: 0" 1 should "should reuse old number with NODE_APP_INSTANCE at 1" "NODE_APP_INSTANCE: 1" 1 should "have one process with NODE_APP_INSTANCE at 2" "NODE_APP_INSTANCE: 2" 1 should "have one process with NODE_APP_INSTANCE at 3" "NODE_APP_INSTANCE: 3" 1 should "not have the process with NODE_APP_INSTANCE at 4" "NODE_APP_INSTANCE: 4" 0 should "not have the process with NODE_APP_INSTANCE at 5" "NODE_APP_INSTANCE: 5" 0 $pm2 delete all $pm2 start ecosystem.json --only sample-other-instance should "start 2 processes" "online" 2 should "not have deleted the process with NODE_APP_INSTANCE at 0" "NODE_APP_INSTANCE: 0" 0 should "not have deleted the process with NODE_APP_INSTANCE at 1" "NODE_APP_INSTANCE: 1" 0 should "start one process with APP_ID at 0" "APP_ID: 0" 1 should "start one process with APP_ID at 1" "APP_ID: 1" 1 $pm2 delete all $pm2 start ecosystem.json --only sample-increment should "start 2 processes" "online" 2 should "start one process with NODE_APP_INSTANCE at 0" "NODE_APP_INSTANCE: 0" 1 should "start one process with NODE_APP_INSTANCE at 1" "NODE_APP_INSTANCE: 1" 1 should "start one process with PORT at 3000" "PORT: 3000" 2 should "start one process with PORT at 3001" "PORT: 3001" 2 $pm2 delete all echo '-------- FORK MODE TEST -------' $pm2 start ecosystem.json --only sample-normal-fork should "start 2 processes" "online" 2 should "start one process with NODE_APP_INSTANCE at 0" "NODE_APP_INSTANCE: 0" 1 should "start one process with NODE_APP_INSTANCE at 1" "NODE_APP_INSTANCE: 1" 1 should "not start one process with NODE_APP_INSTANCE at 2" "NODE_APP_INSTANCE: 2" 0 $pm2 scale sample-normal-fork 4 should "start 2 more processes" "online" 4 should "start one process with NODE_APP_INSTANCE at 0" "NODE_APP_INSTANCE: 0" 1 should "start one process with NODE_APP_INSTANCE at 1" "NODE_APP_INSTANCE: 1" 1 should "start one process with NODE_APP_INSTANCE at 2" "NODE_APP_INSTANCE: 2" 1 should "start one process with NODE_APP_INSTANCE at 3" "NODE_APP_INSTANCE: 3" 1 should "not start one process with NODE_APP_INSTANCE at 4" "NODE_APP_INSTANCE: 4" 0 $pm2 scale sample-normal-fork 2 should "deleted 2 more processes" "online" 2 should "not have the process with NODE_APP_INSTANCE at 0" "NODE_APP_INSTANCE: 0" 0 should "not have the process with NODE_APP_INSTANCE at 1" "NODE_APP_INSTANCE: 1" 0 should "have one process with NODE_APP_INSTANCE at 2" "NODE_APP_INSTANCE: 2" 1 should "have one process with NODE_APP_INSTANCE at 3" "NODE_APP_INSTANCE: 3" 1 $pm2 scale sample-normal-fork 4 should "start 2 more processes" "online" 4 should "should reuse old number with NODE_APP_INSTANCE at 0" "NODE_APP_INSTANCE: 0" 1 should "should reuse old number with NODE_APP_INSTANCE at 1" "NODE_APP_INSTANCE: 1" 1 should "have one process with NODE_APP_INSTANCE at 2" "NODE_APP_INSTANCE: 2" 1 should "have one process with NODE_APP_INSTANCE at 3" "NODE_APP_INSTANCE: 3" 1 should "not have the process with NODE_APP_INSTANCE at 4" "NODE_APP_INSTANCE: 4" 0 should "not have the process with NODE_APP_INSTANCE at 5" "NODE_APP_INSTANCE: 5" 0 $pm2 delete all $pm2 start ecosystem.json --only sample-other-instance-fork should "start 2 processes" "online" 2 should "not have deleted the process with NODE_APP_INSTANCE at 0" "NODE_APP_INSTANCE: 0" 0 should "not have deleted the process with NODE_APP_INSTANCE at 1" "NODE_APP_INSTANCE: 1" 0 should "start one process with APP_ID at 0" "APP_ID: 0" 1 should "start one process with APP_ID at 1" "APP_ID: 1" 1 $pm2 delete all $pm2 start ecosystem.json --only sample-increment-fork should "start 2 processes" "online" 2 should "start one process with NODE_APP_INSTANCE at 0" "NODE_APP_INSTANCE: 0" 1 should "start one process with NODE_APP_INSTANCE at 1" "NODE_APP_INSTANCE: 1" 1 should "start one process with PORT at 3000" "PORT: 3000" 2 should "start one process with PORT at 3001" "PORT: 3001" 2 ================================================ FILE: test/e2e/internals/infinite-loop.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path echo "Starting infinite loop tests" $pm2 start killtoofast.js --name unstable-process echo -n "Waiting for process to restart too many times and pm2 to stop it" for (( i = 0; i <= 100; i++ )); do sleep 0.1 echo -n "." done $pm2 list should 'should has stopped unstable process' 'errored' 1 $pm2 delete all echo "Start infinite loop tests for restart|reload" cp killnotsofast.js killthen.js $pm2 start killthen.js --name killthen $pm2 list should 'should killthen alive for a long time' 'online' 1 # Replace killthen file with the fast quit file sleep 15 cp killtoofast.js killthen.js echo "Restart with unstable process" $pm2 list $pm2 restart all # pm2 reload should also work here for (( i = 0; i <= 80; i++ )); do sleep 0.1 echo -n "." done $pm2 list should 'should has stoped unstable process' 'errored' 1 rm killthen.js $pm2 list $pm2 kill ================================================ FILE: test/e2e/internals/listen-timeout.sh ================================================ #!/usr/bin/env bash #export PM2_GRACEFUL_LISTEN_TIMEOUT=1000 SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/listen-timeout/ echo -e "\033[1mENV REFRESH\033[0m" $pm2 start wait-ready.js -i 1 --wait-ready --listen-timeout 5000 $pm2 reload all & sleep 2 should 'should have started 1 clustered app' 'online' 1 should 'should restart processes with new name' 'restart_time: 1' 1 ================================================ FILE: test/e2e/internals/options-via-env.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path # With start $pm2 start echo.js should 'should deep_monitoring' 'deep_monitoring' 0 $pm2 delete all PM2_DEEP_MONITORING=true $pm2 start echo.js should 'should deep_monitoring' 'deep_monitoring' 1 $pm2 delete all # With restart $pm2 start echo.js should 'should deep_monitoring' 'deep_monitoring' 0 PM2_DEEP_MONITORING=true $pm2 restart echo should 'should deep_monitoring' 'deep_monitoring' 1 ================================================ FILE: test/e2e/internals/promise.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/promise/ # Check for 0.10 & 0.12 support node -e "process.version.indexOf('v0') > -1 ? process.exit(1) : process.exit(0)" RET=$? [ $RET -eq 0 ] || exit 0 echo "###### Cluster mode" > rejection.log $pm2 start rejection.js -i 1 -l rejection.log --merge-logs sleep 1 should 'should has not restarted process' 'restart_time: 0' 1 cat rejection.log | grep "Errorla" spec "should have logged promise error" $pm2 delete all > empty-rejection.log $pm2 start empty-rejection.js -i 1 -l empty-rejection.log --merge-logs sleep 1 should 'should has not restarted process' 'restart_time: 0' 1 cat empty-rejection.log | grep "You have triggered an unhandledRejection, you may have forgotten to catch a Promise rejection" spec "should have logged promise error" $pm2 delete all echo "###### Fork mode" > rejection.log $pm2 start rejection.js -l rejection.log --merge-logs sleep 1 should 'should has not restarted process' 'restart_time: 0' 1 cat rejection.log | grep "You have triggered an unhandledRejection, you may have forgotten to catch a Promise rejection" spec "should have logged promise error" $pm2 delete all > empty-rejection.log $pm2 start empty-rejection.js -l empty-rejection.log --merge-logs sleep 1 should 'should has not restarted process' 'restart_time: 0' 1 cat empty-rejection.log | grep "You have triggered an unhandledRejection, you may have forgotten to catch a Promise rejection" spec "should have logged promise error" ================================================ FILE: test/e2e/internals/signal.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path # # Signal feature # $pm2 start signal.js -i 2 # get the log file and the id. OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"` cat /dev/null > $OUT_LOG $pm2 sendSignal SIGUSR2 signal sleep 1 OUT=`grep "SIGUSR2" "$OUT_LOG" | wc -l` [ $OUT -eq 1 ] || fail "Signal not received by the process name" success "Processes sucessfully receives the signal" $pm2 stop signal.js # Send a process by id $pm2 start signal.js sleep 1 # get the log file and the id. OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"` ID=`$pm2 prettylist | grep -E "pm_id:" | sed "s/.*pm_id: \([^,]*\),/\1/"` cat /dev/null > $OUT_LOG $pm2 sendSignal SIGUSR2 $ID OUT=`grep "SIGUSR2" "$OUT_LOG" | wc -l` [ $OUT -eq 1 ] || fail "Signal not received by the process name" success "Processes sucessfully receives the signal" ================================================ FILE: test/e2e/internals/source_map.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path # # Fork mode # rm sm.log $pm2 start source-map/main.js -e sm.log --merge-logs --disable-source-map-support sleep 2 cat sm.log | grep "main.js" spec "should not take source map into account" rm sm.log $pm2 delete all $pm2 start source-map/main.js -e sm.log --merge-logs sleep 2 cat sm.log | grep "main.ts" spec "should automatically activate source map support (detect main.ts)" rm sm.log $pm2 delete all $pm2 start source-map/main.js -e sm.log --merge-logs --source-map-support sleep 2 cat sm.log | grep "main.ts" spec "should force source map support" # # Cluster mode # rm sm.log $pm2 delete all $pm2 start source-map/main.js -e sm.log --merge-logs -i 1 sleep 2 cat sm.log | grep "main.ts" spec "should automatically activate source map support (detect main.ts)" rm sm.log $pm2 delete all $pm2 start source-map/main.js -e sm.log --merge-logs -i 1 --source-map-support sleep 2 cat sm.log | grep "main.ts" spec "should force source map support" ================================================ FILE: test/e2e/internals/start-consistency.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path ############# TEST cd start-consistency; $pm2 start child.js LINE_NB_CLI=`$pm2 prettylist | wc -l` $pm2 delete all $pm2 start child.json LINE_NB_JSON=`$pm2 prettylist | wc -l` $pm2 prettylist | grep "vizion: true" spec "Vizion" # if [ $LINE_NB_JSON -eq $LINE_NB_CLI ] # then # success "Starting a basic JSON is consistent with CLI start" # else # fail "Starting a basic JSON is NOT consistent with CLI start" # fi ================================================ FILE: test/e2e/internals/wait-ready-event.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/wait_ready_event ##### start with sending event and without waiting (fork mode) $pm2 start http-wait-start.js should 'should have started 1 forked app' 'online' 1 $pm2 delete all ##### start with sending event and ask to wait (fork mode) $pm2 start http-wait-start.js --wait-ready should 'should have started 1 forked app' 'online' 1 $pm2 delete all ##### start without sending event and without waiting (fork mode) $pm2 start http-wait-start.js should 'should have started 1 forked app ' 'online' 1 $pm2 delete all ##### start without sending event and ask to wait (fork mode) $pm2 start http-wait-start_nocb.js --wait-ready --listen-timeout=8000 & sleep 5 should 'should be 1 forked launching state app waiting for ready event' 'launching' 1 $pm2 delete all ##### start with sending event and without waiting (cluster mode) $pm2 start http-wait-start.js -i 1 should 'should have started 1 clustered app' 'online' 1 $pm2 delete all ##### start with sending event and ask to wait (cluster mode) $pm2 start http-wait-start.js -i 1 --wait-ready should 'should have started 1 clustered app' 'online' 1 $pm2 delete all ##### start without sending event and without waiting (cluster mode) $pm2 start http-wait-start.js -i 1 should 'should have started 1 clustered app' 'online' 1 $pm2 delete all ##### start without sending event and ask to wait (cluster mode) $pm2 start http-wait-start_nocb.js -i 1 --wait-ready --listen-timeout=8000 & sleep 5 should 'should be 1 clustered launching state app waiting for ready event' 'launching' 1 $pm2 delete all ================================================ FILE: test/e2e/internals/wrapped-fork.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path echo "################## Wrapped fork ###################" echo "Testing wrapped fork mode values" rm path-check1.txt rm path-check2.txt if command -v bun >/dev/null 2>&1 then bun path-check.js > path-check1.txt else node path-check.js > path-check1.txt fi $pm2 start path-check.js --no-autorestart -o path-check2.txt sleep 1 OUT=`diff path-check1.txt path-check2.txt` echo $OUT [ -z "$OUT" ] || fail "The outputs are not identical" success "The outputs are identical" $pm2 kill ================================================ FILE: test/e2e/logs/log-create-not-exist-dir.sh ================================================ #!/usr/bin/env bash SRC=$( cd $(dirname "$0") pwd ) source "${SRC}/../include.sh" cd $file_path/log-create-not-exist-dir/ LOG_PATH_PREFIX="${SRC}/__log-create-not-exist-dir__" # a long path directory, to make sure that creating the directory is using synchronous mode # if using asynchronous mode, creating the long path directory would fail. LOG_FILE="${LOG_PATH_PREFIX}/a-deep/path/which/should/cost/lots/of/time/to/create/the/directory/out-rel.log" rm -rf "${LOG_PATH_PREFIX}" $pm2 start echo.js -o ${LOG_FILE} spec "Should have the exit code 0"; sleep 2 grep "start" ${LOG_FILE} spec "Should have 'start' in the log file" cd ${SRC} rm -rf "${LOG_PATH_PREFIX}" $pm2 delete all ================================================ FILE: test/e2e/logs/log-custom.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path CURRENT_YEAR=`date +"%Y"` # CLUSTERMODE YYYY $pm2 start echo.js --log-date-format "YYYY" -o out-rel.log --merge-logs >out-rel.log sleep 2 grep $CURRENT_YEAR out-rel.log spec "Should have written year in log file according to format YYYY" rm out-rel.log $pm2 delete all # CLUSTERMODE Wrong format $pm2 start echo.js --log-date-format "YYYY asdsd asd asd sad asd " -o out-rel.log --merge-logs sleep 1 should 'should has not restarted' 'restart_time: 0' 1 spec "Should have not fail with random format" rm out-rel.log $pm2 delete all # CLUSTERMODE YYYY $pm2 start echo.js --log-date-format "YYYY" -o out-rel.log --merge-logs -x >out-rel.log sleep 2 grep $CURRENT_YEAR out-rel.log spec "Should have written year in log file according to format YYYY" rm out-rel.log $pm2 delete all ================================================ FILE: test/e2e/logs/log-entire.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" function head { echo -e "\x1B[1;35m$1\x1B[0m" } function test_dir { local result="" if [ -f "$1-0.log" ]; then result="$1-0.log" else result="$1.log" fi echo "$result" } function test { sleep 5 out_file=$(test_dir "out") err_file=$(test_dir "err") if [ ! -n "$1" ]; then entire_file=$(test_dir "entire") fi COUNT=$(grep "tick" $out_file | wc -l) echo "$COUNT" [ "$COUNT" -ne 0 ] || fail "Should have \"tick\" in out log." success "Should have \"tick\" in out log." COUNT=$(grep "Error" $err_file | wc -l) echo "$COUNT" [ "$COUNT" -ne 0 ] || fail "Should have \"Error\" in error log." success "Should have \"Error\" in error log." if [ ! -n "$1" ]; then COUNT1=$(grep "tick" $entire_file | wc -l) echo "$COUNT1" COUNT2=$(grep "Error" $entire_file | wc -l) echo "$COUN2" ([ "$COUNT1" -ne 0 ] && [ "$COUNT2" -ne 0 ]) || fail "Should have \"tick\" and \"Error\" in entire log." success "Should have \"tick\" and \"Error\" in entire log." fi $pm2 kill sleep 1 rm $out_file rm $err_file if [ ! -n "$1" ]; then rm $entire_file fi } cd $file_path $pm2 kill head ">> START CLUSTERMODE (ENTIRE EXISTS)" $pm2 start throw-later.js -i 1 -o out.log -e err.log -l entire.log test head ">> START CLUSTERMODE (ENTIRE DOES NOT EXIST)" $pm2 start throw-later.js -i 1 -o out.log -e err.log test "NE" head ">> START CLUSTERMODE WITH --merge-logs (ENTIRE EXISTS)" $pm2 start throw-later.js -i 1 -o out.log -e err.log -l entire.log --merge-logs test head ">> START CLUSTERMODE WITH --merge-logs (ENTIRE DOES NOT EXIST)" $pm2 start throw-later.js -i 1 -o out.log -e err.log --merge-logs test "NE" head ">> START FORKMODE (ENTIRE EXISTS)" $pm2 start throw-later.js -o out.log -e err.log -l entire.log test head ">> START FORKMODE (ENTIRE DOES NOT EXIST)" $pm2 start throw-later.js -o out.log -e err.log test "NE" head ">> START FORKMODE WITH --merge-logs (ENTIRE EXISTS)" $pm2 start throw-later.js -o out.log -e err.log -l entire.log --merge-logs test head ">> START FORKMODE WITH --merge-logs (ENTIRE DOES NOT EXIST)" $pm2 start throw-later.js -o out.log -e err.log --merge-logs test "NE" head ">> RELOAD LOGS (ENTIRE EXISTS)" $pm2 start throw-later.js -o out.log -e err.log -l entire.log --merge-logs $pm2 reloadLogs test head ">> RELOAD LOGS (ENTIRE DOES NOT EXIST)" $pm2 start throw-later.js -o out.log -e err.log --merge-logs $pm2 reloadLogs test "NE" head ">> RESTART (ENTIRE EXISTS)" $pm2 start throw-later.js -o out.log -e err.log -l entire.log --merge-logs $pm2 restart all test head ">> RESTART (ENTIRE DOES NOT EXIST)" $pm2 start throw-later.js -o out.log -e err.log --merge-logs $pm2 restart all test "NE" head ">> RELOAD (ENTIRE EXISTS)" $pm2 start throw-later.js -o out.log -e err.log -l entire.log --merge-logs $pm2 reload all test head ">> RELOAD (ENTIRE DOES NOT EXIST)" $pm2 start throw-later.js -o out.log -e err.log --merge-logs $pm2 reload all test "NE" head ">> DESCRIBE (ENTIRE EXISTS)" $pm2 start throw-later.js -o out.log -e err.log -l entire.log --merge-logs $pm2 jlist | grep -w "entire.log" 2> /dev/null spec "\"entire log path\" should exists." test head ">> DESCRIBE (ENTIRE DOES NOT EXIST)" $pm2 start throw-later.js -o out.log -e err.log --merge-logs $pm2 jlist | grep -w "pm_log_path" 2> /dev/null ispec "\"entire log path\" should not exist." test "NE" head ">> FLUSH (ENTIRE EXISTS)" $pm2 start throw-later.js -o out.log -e err.log -l entire.log --merge-logs $pm2 flush test head ">> FLUSH (ENTIRE DOES NOT EXIST)" $pm2 start throw-later.js -o out.log -e err.log --merge-logs $pm2 flush test "NE" head ">> JLIST (ENTIRE EXISTS)" $pm2 start throw-later.js -o out.log -e err.log -l entire.log --merge-logs $pm2 jlist | grep -w "pm_log_path" spec "\"entire log path\" should exists." test head ">> JLIST (ENTIRE DOES NOT EXIST)" $pm2 start throw-later.js -o out.log -e err.log --merge-logs $pm2 jlist | grep -w "pm_log_path" ispec "\"entire log path\" should not exist." test "NE" head ">> START JSON (ENTIRE EXISTS)" $pm2 start throw-later.json test head ">> START JSON (ENTIRE DOES NOT EXIST)" $pm2 start throw-later1.json test "NE" $pm2 kill ================================================ FILE: test/e2e/logs/log-json.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/log-json/ rm output.log # fork mode json logs $pm2 start ecosystem.json --only one-echo ! test -f output.log sleep 2 node -pe 'JSON.parse(process.argv[1])' `cat output.log` spec 'should have parsed valid json' $pm2 delete all rm output.log # cluster mode json logs $pm2 start ecosystem.json -i 2 --only one-echo-cluster ! test -f output.log sleep 2 node -pe 'JSON.parse(process.argv[1])' `cat output.log` spec 'should have parsed valid json' $pm2 delete all rm output.log CURRENT_YEAR=`date +"%Y"` # fork mode with date $pm2 start ecosystem.json --only one-echo-date ! test -f output.log sleep 2 node -pe 'JSON.parse(process.argv[1])' `cat output.log` spec 'should have parsed valid json' OUT=`cat output.log | grep -o "$CURRENT_YEAR" | wc -l` [ $OUT -ge 1 ] || fail "should contains custom timestamp" success "should contains custom timestamp" $pm2 delete all rm output.log # cluster mode with date $pm2 start ecosystem.json --only one-echo-cluster-date ! test -f output.log sleep 2 node -pe 'JSON.parse(process.argv[1])' `cat output.log` spec 'should have parsed valid json' OUT=`cat output.log | grep -o "$CURRENT_YEAR" | wc -l` [ $OUT -eq 1 ] || fail "should contains custom timestamp in cluster mode" success "should contains custom timestamp in cluster mode" $pm2 delete all rm output.log ================================================ FILE: test/e2e/logs/log-namespace.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/log-namespace/ LOG_PATH_PREFIX="${SRC}/__log-namespace__" rm -rf "${LOG_PATH_PREFIX}" mkdir "${LOG_PATH_PREFIX}" $pm2 start echo.js --namespace e2e-test-log-namespace LOG_FILE_BASELINE="${LOG_PATH_PREFIX}/baseline-out.log" $pm2 logs e2e-test-log-namespace > $LOG_FILE_BASELINE & # backgrounded - will be stopped by `$pm2 delete all` sleep 2 # should leave time for ~40 "tick" lines # Using -q to avoid spamming, since there will be a fair few "tick" matches grep -q "tick" ${LOG_FILE_BASELINE} spec "Should have 'tick' in the log file" LOG_FILE_LINES_ZERO="${LOG_PATH_PREFIX}/lines-zero-out.log" $pm2 logs e2e-test-log-namespace --lines 0 > $LOG_FILE_LINES_ZERO & sleep 2 # should leave time for ~40 "tick" lines # Using -q to avoid spamming, since there will be a fair few "tick" matches grep -q "tick" ${LOG_FILE_LINES_ZERO} spec "Should have 'tick' in the log file even if using --lines 0" cd ${SRC} rm -rf "${LOG_PATH_PREFIX}" $pm2 delete all ================================================ FILE: test/e2e/logs/log-null.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path rm ~/.pm2/logs/echo-out.log rm ~/.pm2/logs/echo-error.log echo ">>>>>>>>>>>>>>>>>>>> LOG PATH SET TO NULL" # set error log to null in fork $pm2 start echo.js -o out.log -e NULL --merge-logs sleep 1 test -f out.log spec "err log should exist with null in fork mode" ! test -f ~/.pm2/logs/echo-error.log spec "err log shouldnt exist with null in fork mode" $pm2 delete all rm out.log # set error log to null in cluster $pm2 start echo.js -i 1 -o out.log -e NULL --merge-logs sleep 1 test -f out.log spec "err log should exist with null in cluster mode" ! test -f ~/.pm2/logs/echo-error.log spec "err log shouldnt exist with null in cluster mode" $pm2 delete all rm out.log # set out log to null in fork $pm2 start echo.js -o NULL -e err.log --merge-logs sleep 1 test -f err.log spec "err log should exist with null in fork mode" ! test -f ~/.pm2/logs/echo-out.log spec "output log shouldnt exist with null in fork mode" $pm2 delete all rm err.log # set out log to null in cluster $pm2 start echo.js -i 1 -o NULL -e err.log --merge-logs sleep 1 test -f err.log spec "err log should exist with null in cluster mode" ! test -f ~/.pm2/logs/echo-out.log spec "output log shouldnt exis with null in cluster mode" $pm2 delete all rm err.log # set error AND out log to null in cluster $pm2 start echo.js -i 1 -o NULL -e NULL --merge-logs sleep 1 ! test -f ~/.pm2/logs/echo-out.log spec "out log shouldnt exist with null in cluster mode" ! test -f ~/.pm2/logs/echo-error.log spec "error log shouldnt exist with null in cluster mode" $pm2 delete all # set error AND out log to null in fork $pm2 start echo.js -o NULL -e NULL --merge-logs sleep 1 ! test -f ~/.pm2/logs/echo-out.log spec "out log shouldnt exist with null in fork mode" ! test -f ~/.pm2/logs/echo-error.log spec "error log shouldnt exist with null in fork mode" $pm2 delete all rm ~/.pm2/logs/echo-out.log rm ~/.pm2/logs/echo-error.log echo ">>>>>>>>>>>>>>>>>>>> LOG PATH SET TO /dev/null" # set error log to null in fork $pm2 start echo.js -o out.log -e /dev/null --merge-logs sleep 1 test -f out.log spec "err log should exist with /dev/null in fork mode" ! test -f ~/.pm2/logs/echo-error.log spec "err log shouldnt exist with /dev/null in fork mode" $pm2 delete all rm out.log # set error log to null in cluster $pm2 start echo.js -i 1 -o out.log -e /dev/null --merge-logs sleep 1 test -f out.log spec "err log should exist with /dev/null in cluster mode" ! test -f ~/.pm2/logs/echo-error.log spec "err log shouldnt exist with /dev/null in cluster mode" $pm2 delete all rm out.log # set out log to null in fork $pm2 start echo.js -o /dev/null -e err.log --merge-logs sleep 1 test -f err.log spec "err log should exist with /dev/null in fork mode" ! test -f ~/.pm2/logs/echo-out.log spec "output log shouldnt exist with /dev/null in fork mode" $pm2 delete all rm err.log # set out log to null in cluster $pm2 start echo.js -i 1 -o /dev/null -e err.log --merge-logs sleep 1 test -f err.log spec "err log should exist with /dev/null in cluster mode" ! test -f ~/.pm2/logs/echo-out.log spec "output log shouldnt exis with /dev/null in cluster mode" $pm2 delete all rm err.log # set error AND out log to null in cluster $pm2 start echo.js -i 1 -o /dev/null -e /dev/null --merge-logs sleep 1 ! test -f ~/.pm2/logs/echo-out.log spec "out log shouldnt exist with /dev/null in cluster mode" ! test -f ~/.pm2/logs/echo-error.log spec "error log shouldnt exist with /dev/null in cluster mode" $pm2 delete all # set error AND out log to null in fork $pm2 start echo.js -o /dev/null -e /dev/null --merge-logs sleep 1 ! test -f ~/.pm2/logs/echo-out.log spec "out log shouldnt exist with /dev/null in fork mode" ! test -f ~/.pm2/logs/echo-error.log spec "error log shouldnt exist with /dev/null in fork mode" $pm2 delete all ================================================ FILE: test/e2e/logs/log-reload.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path ## FORK MODE $pm2 kill $pm2 start echo.js -o out-rel.log -e err-rel.log -x --merge-logs sleep 2 grep "echo.js" out-rel.log spec "Should have written te right stuff in out log in fork mode" grep "echo.js-error" err-rel.log spec "Should have written te right stuff in err log in fork mode" rm out-rel.log rm err-rel.log $pm2 reloadLogs spec "Should have reloaded logs via CLI" sleep 1 grep "echo.js" out-rel.log spec "(RELOADED) Should have written the right stuff in out log in fork mode" grep "echo.js-error" err-rel.log spec "(RELOADED) Should have written the right stuff in err log in fork mode" rm out-rel.log rm err-rel.log ================================================ FILE: test/e2e/logs/log-timestamp.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" function head { echo -e "\x1B[1;35m$1\x1B[0m" } function rm_pm2log { if [ "$1" -ne 1 ]; then $pm2 kill rm -rf ~/.pm2/pm2.log fi } function grep_log { echo "travis" eval "$pm2 $1 >| pm2.log" sleep 0.3 OUT=`cat pm2.log | grep -n "[0-9]\{4\}\-[0-9]\{2\}\-[0-9]\{2\}" | wc -l` } function no_prefix { eval "grep_log \"$1\"" echo "line count: $OUT" [ $OUT -eq 0 ] || fail "expect no timestamp prefix in pm2.log, but currently existing." success "have no timestamp prefix" rm_pm2log "$2" } function prefix { eval "grep_log \"$1\"" echo "line count: $OUT" [ $OUT -ne 0 ] || fail "expect have timestamp prefix in pm2.log, but currently does not exist." success "have timestamp prefix" rm_pm2log "$2" } cd $file_path $pm2 kill sleep 0.5 $pm2 flush unset PM2_LOG_DATE_FORMAT export PM2_LOG_DATE_FORMAT="" head ">> LIST (NO PREFIX)" no_prefix "ls" 0 head ">> START (NO PREFIX)" no_prefix "start echo.js" 1 head ">> RESTART (NO PREFIX)" no_prefix "restart echo" 1 head ">> STOP (NO PREFIX)" no_prefix "stop echo" 0 head ">> START JSON (NO PREFIX)" no_prefix "start echo-pm2.json" 1 head ">> RESTART JSON (NO PREFIX)" no_prefix "restart echo-pm2.json" 1 head ">> STOP-JSON (NO PREFIX)" no_prefix "stop echo-pm2.json" 0 export PM2_LOG_DATE_FORMAT="YYYY-MM-DD HH:mm Z" head ">> LIST (PREFIX)" prefix "ls" 0 head ">> START (PREFIX)" prefix "start echo.js" 1 head ">> RESTART (PREFIX)" prefix "restart echo" 1 head ">> STOP (PREFIX)" prefix "stop echo" 0 head ">> START JSON (PREFIX)" prefix "start echo-pm2.json" 1 head ">> RESTART JSON (PREFIX)" prefix "restart echo-pm2.json" 1 head ">> STOP-JSON (PREFIX)" prefix "restart echo-pm2.json" 0 rm -rf pm2.log unset PM2_LOG_DATE_FORMAT touch ~/.pm2/pm2.log ================================================ FILE: test/e2e/misc/cron-system.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path PM2_WORKER_INTERVAL=1000 $pm2 update $pm2 delete all # # Cron wrong format detection # $pm2 start cron.js -c "* * * asdasd" ispec "Cron should throw error when pattern invalid" # # Cron restart in fork mode # $pm2 start cron.js -c "*/2 * * * * *" --no-vizion spec "Should cron restart echo.js" sleep 2 should 'should app been restarted' 'restart_time: 0' 0 $pm2 restart cron $pm2 reset all sleep 4 should 'should app been restarted after restart' 'restart_time: 0' 0 $pm2 reset cron $pm2 stop cron sleep 4 should 'should app be started again' 'online' 1 $pm2 delete cron sleep 4 should 'should app not be started again' 'stopped' 0 should 'should app not be started again' 'online' 0 $pm2 delete all # # Cron restart in cluster mode # $pm2 start cron.js -i 1 -c "*/2 * * * * *" spec "Should start app" sleep 2 should 'should app been restarted' 'restart_time: 0' 0 $pm2 reset all sleep 3 should 'should app been restarted a second time' 'restart_time: 0' 0 $pm2 delete all # # Cron after resurect # $pm2 start cron.js -i 1 -c "*/2 * * * * *" spec "Should start app" sleep 2 should 'should app been restarted' 'restart_time: 0' 0 $pm2 update $pm2 reset all sleep 4 should 'should app been restarted' 'restart_time: 0' 0 $pm2 delete all # # Cron every sec # $pm2 start cron.js -c "* * * * * *" sleep 4 should 'should app been restarted' 'restart_time: 0' 0 # # Delete cron # $pm2 restart cron --cron-restart 0 $pm2 reset all sleep 2 should 'app stop be restarted' 'restart_time: 0' 1 $pm2 delete all ================================================ FILE: test/e2e/misc/inside-pm2.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path #################################################################### # Check that we can start a process from inside a PM2 watched app. # #################################################################### TEST_VARIABLE='hello1' $pm2 start startProcessInsidePm2.json >inside-out.log # sleep 1 # should 'start master process' 'pm_id: 0' 2 sleep 1 $pm2 list should 'child process should be started' 'pm_id: 1' 2 should 'restarted status should be zero' "restart_time: 0" 2 grep "hello1" inside-out.log &> /dev/null spec "Child should have hello1 variable" TEST_VARIABLE='hello2' $pm2 restart "insideProcess" --update-env sleep 1 grep "hello2" inside-out.log &> /dev/null spec "Child should have hello2 variable after restart" # Call bash script that restarts app $pm2 delete all $pm2 start echo.js sleep 1 export PM2_PATH=$pm2 $pm2 start inside/inner_restart.sh --no-autorestart sleep 2 should 'restarted status should be one' "restart_time: 3" 1 ================================================ FILE: test/e2e/misc/instance-number.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path $pm2 start server.js -i -100 should 'should have started 1 processes' 'online' 1 $pm2 delete all ================================================ FILE: test/e2e/misc/misc.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path ## Try to launch an app with `push` as name $pm2 kill $pm2 start push.json $pm2 stop push.json $pm2 list # # Max memory auto restart option # # --max-memory-restart option && maxMemoryRestart (via JSON file) # $pm2 kill PM2_WORKER_INTERVAL=1000 $pm2 start big-array.js --max-memory-restart="20M" sleep 3 $pm2 list should 'process should have been restarted' 'restart_time: 0' 0 $pm2 delete all # # Via JSON # $pm2 start json-reload/max-mem.json sleep 3 $pm2 list should 'process should been restarted' 'restart_time: 0' 0 $pm2 delete all $pm2 start env.js OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"` cat /dev/null > $OUT_LOG sleep 1 OUT=`cat $OUT_LOG | head -n 1` if [ $OUT="undefined" ] then success "environment variable not defined" else fail "environment defined ? wtf ?" fi $pm2 delete all $pm2 start env.json cat /dev/null > $OUT_LOG sleep 1 OUT=`cat $OUT_LOG | head -n 1` if [ "$OUT" = "undefined" ] then fail "environment variable hasnt been defined" else success "environment variable successfully defined" fi ##################### # Merge logs option # ##################### $pm2 kill rm outmerge* $pm2 start echo.js -i 4 -o outmerge.log cat outmerge.log > /dev/null ispec 'file outmerge.log should not exist' cat outmerge-0.log > /dev/null spec 'file outmerge-0.log should exist' rm outmerge* ############ Now with --merge option $pm2 kill rm outmerge* $pm2 start echo.js -i 4 -o outmerge.log --merge-logs sleep 0.2 cat outmerge.log > /dev/null spec 'file outmerge.log should exist' cat outmerge-0.log > /dev/null ispec 'file outmerge-0.log should not exist' rm outmerge* ================================================ FILE: test/e2e/misc/nvm-node-version.sh ================================================ #!/usr/bin/env bash echo "feature in deprecation" exit 0 SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/nvm-node-version function getInterpreter() { echo `$pm2 prettylist | grep "exec_interpreter:" | awk -F"'" '{print $2}'` } $pm2 start ecosystem.json sleep 1 OCC=$($pm2 prettylist | grep "exec_interpreter" | grep 'v4.6.0\|v6.7.0' | wc -l) [ $OCC -eq 2 ] || fail "Errors in setting interpreters" success "Success" $pm2 restart ecosystem.json should 'should have 2 apps online' 'online' 2 OCC=$($pm2 prettylist | grep "exec_interpreter" | grep 'v4.6.0\|v6.7.0' | wc -l) [ $OCC -eq 2 ] || fail "Errors in setting interpreters" success "Success" $pm2 restart all sleep 0.5 should 'should have 2 apps online' 'online' 2 OCC=$($pm2 prettylist | grep "exec_interpreter" | grep 'v4.6.0\|v6.7.0' | wc -l) [ $OCC -eq 2 ] || fail "Errors in setting interpreters" success "Success" # Update node.js version $pm2 restart ecosystem-change.json OCC=$($pm2 prettylist | grep "exec_interpreter" | grep 'v4.5.0\|v6.7.0' | wc -l) [ $OCC -eq 2 ] || fail "Errors in setting interpreters" success "Success" $pm2 restart all sleep 0.5 should 'should have 2 apps online' 'online' 2 OCC=$($pm2 prettylist | grep "exec_interpreter" | grep 'v4.5.0\|v6.7.0' | wc -l) [ $OCC -eq 2 ] || fail "Errors in setting interpreters" success "Success" ================================================ FILE: test/e2e/misc/port-release.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path $pm2 start cluster-pm2.json should 'should have started 4 processes' 'online' 4 $pm2 reload cluster-pm2.json should 'should have started 4 processes' 'online' 4 ================================================ FILE: test/e2e/misc/startup.sh ================================================ #!/usr/bin/env bash if [ "$EUID" -ne 0 ] then echo "Please run as root" exit fi SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path $pm2 startup upstart -u $USER --hp $HOME --service-name abcdef spec "should startup command generation exited succesfully with custom service-name" test -e /etc/init.d/abcdef spec "should have generated upstart file with custom service-name" $pm2 unstartup upstart --service-name abcdef spec "should have disabled startup with custom service-name" ! test -e /etc/init.d/abcdef spec "should have deleted upstart file with custom service-name" $pm2 startup upstart -u $USER --hp $HOME spec "should startup command generation exited succesfully" test -e /etc/init.d/pm2-$USER spec "should have generated upstart file" $pm2 unstartup upstart spec "should have disabled startup" ! test -e /etc/init.d/pm2-$USER spec "should have deleted upstart file" ================================================ FILE: test/e2e/misc/versioning-cmd.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path rm -rf app-playground git clone https://github.com/keymetrics/app-playground.git cd app-playground $pm2 start package.json CUR_HASH=`$pm2 prettylist | grep "revision" | cut -d: -f2 | tr -d " ,'"` HEAD_HASH=$CUR_HASH echo "CURRENT GIT HASH= " $CUR_HASH GIT_HASH=`git rev-parse HEAD` [ $CUR_HASH == $GIT_HASH ] || fail "Wrong commit" spec "Right commit after start" # # Backward # $pm2 backward "keymetrics tuto" sleep 1 CUR_HASH=`$pm2 prettylist | grep "revision" | cut -d: -f2 | tr -d " ,'"` echo "CURRENT GIT HASH= " $CUR_HASH GIT_HASH=`git rev-parse HEAD` [ $CUR_HASH == $GIT_HASH ] || fail "Wrong commit" spec "Right commit after backward action" # # Backward # $pm2 backward "keymetrics tuto" sleep 1 CUR_HASH=`$pm2 prettylist | grep "revision" | cut -d: -f2 | tr -d " ,'"` DEEP_HASH=$CUR_HASH echo "CURRENT GIT HASH= " $CUR_HASH GIT_HASH=`git rev-parse HEAD` [ $CUR_HASH == $GIT_HASH ] || fail "Wrong commit" spec "Right commit after backward action" # # Forward # $pm2 forward "keymetrics tuto" sleep 1 CUR_HASH=`$pm2 prettylist | grep "revision" | cut -d: -f2 | tr -d " ,'"` echo "CURRENT GIT HASH= " $CUR_HASH GIT_HASH=`git rev-parse HEAD` [ $CUR_HASH == $GIT_HASH ] || fail "Wrong commit" spec "Right commit after backward action" # # Pull to HEAD # $pm2 pull "keymetrics tuto" sleep 1 CUR_HASH=`$pm2 prettylist | grep "revision" | cut -d: -f2 | tr -d " ,'"` echo "CURRENT GIT HASH= " $CUR_HASH GIT_HASH=`git rev-parse HEAD` [ $CUR_HASH == $GIT_HASH ] || fail "Wrong commit" spec "Right commit after pullAndReload" [ $CUR_HASH == $HEAD_HASH ] || fail "Wrong commit" spec "Is updated with right hash" # # Pull to commit id # $pm2 pull "keymetrics tuto" $DEEP_HASH sleep 1 CUR_HASH=`$pm2 prettylist | grep "revision" | cut -d: -f2 | tr -d " ,'"` echo "CURRENT GIT HASH= " $CUR_HASH GIT_HASH=`git rev-parse HEAD` [ $CUR_HASH == $GIT_HASH ] || fail "Wrong commit" spec "Right commit after pullAndReload" [ $CUR_HASH == $DEEP_HASH ] || fail "Wrong commit" spec "Is updated with old hash" cd .. rm -rf app-playground ================================================ FILE: test/e2e/misc/vizion.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path echo "################## VIZION ###################" mv git .git ############### $pm2 kill $pm2 start echo.js sleep 1 should 'should have versioning metadata' 'jshkurti/pm2_travis' 1 $pm2 delete all $pm2 start echo.js --no-vizion sleep 1 should 'should not have versioning metadata' 'jshkurti/pm2_travis' 0 $pm2 delete all $pm2 start no-vizion.json sleep 1 should 'should not have versioning metadata' 'jshkurti/pm2_travis' 0 mv .git git ================================================ FILE: test/e2e/modules/get-set.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path $pm2 unset all spec "Should unset all variables" ls ~/.pm2/module_conf.json spec "Should file exists" $pm2 get $pm2 set key1 val1 cat ~/.pm2/module_conf.json | grep "key1" spec "Should key exists" $pm2 unset key1 cat ~/.pm2/module_conf.json | grep "key1" ispec "Should key does not exist" rm -rf ~/.pm2 ================================================ FILE: test/e2e/modules/module-safeguard.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path # # Re init module system # $pm2 kill rm -rf ~/.pm2 # # # $pm2 ls $pm2 install pm2-sample-module@2.3.5 spec "Should have installed module" sleep 1 should 'should have started module' 'online' 1 should 'should module be in stable state' 'restart_time: 0' 1 #should 'should module be on the right version' "module_version: '2.3.5'" 1 $pm2 install pm2-sample-module@2.2.5 --safe ispec "Should installation of unstable module fail (npm installation has failed)" should 'should have restored module to previous version and online' 'online' 1 should 'should module be in stable state' 'restart_time: 0' 1 #should 'should module be on the right version' "module_version: '2.3.5'" 1 $pm2 install pm2-sample-module@2.3.5 --safe spec "Should installation of unstable module fail (module bad behavior (restart))" should 'should have restored module to previous version and online' 'online' 1 should 'should module be in stable state' 'restart_time: 0' 1 #should 'should module be on the right version' "module_version: '2.3.5'" 1 # # Test edge cases # $pm2 uninstall all ================================================ FILE: test/e2e/modules/module.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path # # Re init module system # rm -rf ~/.pm2/node_modules $pm2 kill # # # $pm2 unset pm2-probe $pm2 set 'pm2-probe:config1xxx' true $pm2 install pm2-probe@latest spec "Should install a module" should 'should app be online' 'online' 1 $pm2 install pm2-probe@latest spec "Should update a module" should 'should app be online' 'online' 1 ls ~/.pm2/modules/pm2-probe spec "Module should be installed" # Default configuration variable in package.json (under "config" attribute) should 'should have default config variable via package.json' "var2: false" 4 3 # # Should configuration variable be present two times # one time in the raw env, and a second time prefixed with the module name # exists '1# should have config variable' "config1xxx: 'true'" 6 # # Change variable value # $pm2 set 'pm2-probe:config1xxx' false sleep 1 exists '2# should have config variable' "config1xxx: 'false'" 4 $pm2 update spec "Should update successfully" should 'and module still online' 'online' 1 $pm2 kill spec "Should kill pm2" $pm2 list spec "Should resurrect pm2" should 'and module still online' 'online' 1 $pm2 delete all should 'should module status not be modified' 'online' 1 $pm2 stop all should 'should module status not be modified' 'online' 1 $pm2 stop pm2-probe should 'should module be possible to stop' 'stopped' 1 $pm2 uninstall pm2-probe spec "Should uninstall a module" should 'should module not be online' 'online' 0 ls ~/.pm2/modules/pm2-probe ispec "Module should be deleted" $pm2 update should 'should module not be online' 'online' 0 # # Module test # cd module-fixture $pm2 kill # Unset all possible variables for module $pm2 unset example-module # Install local module in development mode $pm2 install . sleep 0.5 spec 'Should have installed module' # # Override environment variable # $pm2 set example-module:var2 true # sleep 0.5 # should 'should module been restarted after setting variable' 'restart_time: 1' 1 # # 4 occurences because of a restart # should 'should have config variable modified' "var2: 'true'" 4 # $pm2 set example-module:newvar true # sleep 0.5 # should 'should module been restarted after setting variable' 'restart_time: 2' 1 # # 4 occurences because of a restart # should 'should have config variable modified' "newvar: 'true'" 4 ================================================ FILE: test/e2e/process-file/app-config-update.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" export PM2_GRACEFUL_TIMEOUT=1000 export PM2_GRACEFUL_LISTEN_TIMEOUT=1000 cd $file_path $pm2 kill $pm2 start app-config-update/args1.json $pm2 prettylist | grep "node_args: \[\]" spec "1 Should application have empty node argument list" $pm2 restart app-config-update/args2.json $pm2 prettylist | grep "node_args: \[ '--harmony' \]" spec "2 Should application have one node argument" $pm2 delete all $pm2 start app-config-update/echo.js $pm2 prettylist | grep "node_args: \[\]" spec "3 Should application have empty node argument list" $pm2 restart app-config-update/echo.js --node-args="--harmony" $pm2 prettylist | grep "node_args: \[ '--harmony' \]" spec "4 Should application have one node argument" # Variation with pm2 start that restarts an app $pm2 start echo --node-args="--harmony" $pm2 prettylist | grep "node_args: \[ '--harmony' \]" spec "5 Should application have one node argument" # # Rename # $pm2 restart 0 --name="new-name" $pm2 reset all $pm2 restart new-name should '6 should restart processes with new name' 'restart_time: 1' 1 $pm2 start 0 --name="new-name-2" $pm2 reset all $pm2 restart new-name-2 should '7 should restart processes with new name' 'restart_time: 1' 1 $pm2 delete all ########## RELOAD/CLUSTER MODE ######### $pm2 start app-config-update/echo.js -i 1 $pm2 prettylist | grep "node_args: \[\]" spec "Should application have empty node argument list" $pm2 reload app-config-update/echo.js --node-args="--harmony" $pm2 prettylist | grep "node_args: \[ '--harmony' \]" spec "Should application have one node argument" $pm2 prettylist | grep "node_args" spec "Should have found parameter" # Now set node-args to null $pm2 reload app-config-update/echo.js --node-args=null # Should not find node_args anymore $pm2 prettylist | grep "node_args" ispec "Should have deleted cli parameter when passing null" $pm2 reload echo --name="new-name" $pm2 reset all $pm2 restart new-name should 'should reload processes with new name' 'restart_time: 1' 1 ================================================ FILE: test/e2e/process-file/append-env-to-name.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path ## Test #1 $pm2 start append-env-to-name.json --env dev should 'have started app with name web-dev' "name: 'web-dev'" 3 $pm2 start append-env-to-name.json --env prod should 'have started same app with name : web-prod' "name: 'web-prod'" 3 ================================================ FILE: test/e2e/process-file/homogen-json-action.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path $pm2 kill cd homogen-json-action $pm2 start all.json should 'should start process' 'online' 6 should 'should all script been restarted 0 time' 'restart_time: 0' 6 $pm2 start all.json should 'should smart restart processes' 'online' 6 should 'should all script been restarted one time' 'restart_time: 1' 6 $pm2 restart all.json should 'should all script been restarted one time' 'restart_time: 2' 6 $pm2 reload all.json should 'should all script been restarted one time' 'restart_time: 3' 6 # With restart should equal a start $pm2 delete all $pm2 restart all.json should 'should start process' 'online' 6 should 'should all script been restarted 0 time' 'restart_time: 0' 6 # With reload should equal a start $pm2 delete all $pm2 reload all.json should 'should start process' 'online' 6 should 'should all script been restarted 0 time' 'restart_time: 0' 6 ================================================ FILE: test/e2e/process-file/js-configuration.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/js-configuration $pm2 start ecosystem.config.js should 'should have started 1 processes' 'online' 1 ================================================ FILE: test/e2e/process-file/json-file.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path echo -e "\033[1mRunning tests for json files :\033[0m" ## alias "apps" to "pm2" = nicer for package.json $pm2 start pm2-ecosystem.json should 'should start processes' 'online' 6 $pm2 delete all.json should 'should delete all processes' 'name' 0 $pm2 kill PM2_WORKER_INTERVAL=90000 $pm2 start all.json should 'should start processes' 'online' 6 $pm2 stop all.json should 'should stop processes' 'stopped' 6 $pm2 delete all.json should 'should delete all processes' 'name' 0 $pm2 start all.json should 'should start processes' 'online' 6 $pm2 restart all.json should 'should stop processes' 'online' 6 should 'should all script been restarted one time' 'restart_time: 1' 6 $pm2 reload all.json sleep 1 should 'should reload processes' 'online' 6 should 'should all script been restarted one time' 'restart_time: 2' 6 ## ## Smart restart ## $pm2 start all.json sleep 1 should 'should smart restart processes' 'online' 6 should 'should all script been restarted one time' 'restart_time: 3' 6 $pm2 stop all.json sleep 1 should 'should stop processes' 'stopped' 6 $pm2 start all.json should 'should smart restart processes' 'online' 6 # $pm2 stop all.json # sleep 1 # should 'should stop processes' 'stopped' 6 # $pm2 start all # should 'should smart restart processes' 'online' 6 $pm2 kill ########## JS style PM2_WORKER_INTERVAL=90000 $pm2 start configuration.json should 'should start processes' 'online' 6 $pm2 stop configuration.json should 'should stop processes' 'stopped' 6 $pm2 delete configuration.json should 'should start processes' 'online' 0 $pm2 start configuration.json should 'should start processes' 'online' 6 $pm2 restart configuration.json should 'should stop processes' 'online' 6 should 'should all script been restarted one time' 'restart_time: 1' 6 $pm2 delete configuration.json should 'should delete processes' 'online' 0 ########## PIPE command $pm2 kill cat all.json | $pm2 start - should 'should start processes' 'online' 6 $pm2 kill ######### --only option $pm2 start all.json --only echo should 'should start processes' 'online' 1 $pm2 start all.json --only child should 'should start processes' 'online' 5 $pm2 restart all.json --only child should 'should start processes' 'online' 5 should 'should all script been restarted one time' 'restart_time: 1' 4 $pm2 delete all.json --only echo should 'should start processes' 'online' 4 $pm2 reload all.json --only child should 'should all script been restarted one time' 'restart_time: 2' 4 ######## multu only $pm2 start all.json --only "echo,child" should 'should start processes' 'online' 5 $pm2 kill ================================================ FILE: test/e2e/process-file/json-reload.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path cd json-reload # # Max memory auto restart option # # --max-memory-restart option && maxMemoryRestart (via JSON file) # $pm2 kill PM2_WORKER_INTERVAL=1000 $pm2 start max-mem-0.json sleep 2 $pm2 list should 'process should has not been restarted' 'restart_time: 0' 1 $pm2 restart max-mem.json sleep 2 $pm2 list should 'process should has been restarted' 'restart_time: 0' 0 # # Date format change # $pm2 delete all CURRENT_YEAR=`date +"%Y"` >echo-test.log $pm2 start echo-pre.json sleep 1 grep $CURRENT_YEAR echo-test.log spec "Should have written year in log file according to format YYYY" grep "ok" echo-test.log spec "Should have written new string depending on ECHO_MSG" $pm2 restart echo-post.json >echo-test.log sleep 1 grep $CURRENT_YEAR echo-test.log ispec "Should have written year in log file according to format" grep "YAY" echo-test.log spec "Should have written new string depending on ECHO_MSG" # Switch to production environment $pm2 restart echo-post.json --env production >echo-test.log sleep 1 grep "WOW" echo-test.log spec "Should have written new string depending on ECHO_MSG" # # Switch to production environment # $pm2 reload echo-post.json --env production >echo-test.log sleep 1 grep "WOW" echo-test.log spec "Should have written new string depending on ECHO_MSG" # # Go back to original environment # $pm2 restart echo-post.json sleep 1 grep "YAY" echo-test.log spec "Should have written new string depending on ECHO_MSG" ================================================ FILE: test/e2e/process-file/yaml-configuration.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path/yaml-configuration $pm2 start non-existent.yaml should 'should have started 0 processes because file unknown' 'online' 0 $pm2 start malformated.yml should 'should have started 0 processes because file malformated' 'online' 0 $pm2 start apps.yaml should 'should have started 6 processes' 'online' 6 $pm2 restart all should 'should have restarted 6 processes' 'restart_time: 1' 6 $pm2 restart apps.yaml should 'should have restarted 6 processes' 'restart_time: 2' 6 $pm2 reload all should 'should have reloaded 6 processes' 'restart_time: 3' 6 $pm2 reload apps.yaml should 'should have reloaded 6 processes' 'restart_time: 4' 6 $pm2 stop all should 'should have reloaded 6 processes' 'stopped' 6 $pm2 start apps.yaml $pm2 delete all should 'should have deleted 6 processes' 'online' 0 ================================================ FILE: test/e2e/pull.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/../include.sh" cd $file_path $pm2 interact kill # # Testing pull,forward,backward methods # if [ $TRAVIS ] then git config --global user.email "jshkurti@student.42.fr" git config --global user.name "jshkurti" fi rm -rf ./app-playground git clone https://github.com/keymetrics/app-playground.git cd app-playground git checkout hotfix # set max_memory_restart to 160M by default. MAC does not support `sed -i` echo `sed -e 's/"160"/"160M"/' process.json` >| process.json $pm2 start ./process.json --name app sleep 5 OUT=`$pm2 ls | grep errored | wc -l` [ $OUT -eq 1 ] || fail "Process should be errored because node_modules are missing" success "Process should be errored because node_modules are missing" OUT=`$pm2 info 0 | grep remote | egrep -oh 'https://([^ ]+)'` [ $OUT = "https://github.com/keymetrics/app-playground.git" ] || fail "Remote URL should be right" success "Remote URL should be right" OUT=`$pm2 backward app | wc -l` [ $OUT -eq 13 ] || fail "Backward method should work properly and print adequate output" success "Backward method should work properly and print adequate output" OUT=`$pm2 forward app | wc -l` [ $OUT -eq 13 ] || fail "Forward method should work properly and print adequate output" success "Forward method should work properly and print adequate output" OUT=`$pm2 forward app | wc -l` [ $OUT -eq 2 ] || fail "Forward method should fail and thus print 2-lined output" success "Forward method should fail and thus print 2-lined output" OUT=`$pm2 pull app | wc -l` [ $OUT -eq 2 ] || fail "Pull method should 'fail' because it is already up-to-date" success "Pull method should 'fail' because it is already up-to-date" export PM2_WORKER_INTERVAL=1000 $pm2 kill $pm2 start ./process.json --name app sleep 5 # # Testing refresh-versioning worker # OUT=`$pm2 jlist | egrep -oh '"unstaged":true' | wc -c` [ $OUT -eq 16 ] || fail "Worker: unstaged flag should be true" success "Worker: unstaged flag should be true" git add --all git commit -m 'staged now' sleep 5 OUT=`$pm2 jlist | egrep -oh '"unstaged":false' | wc -c` [ $OUT -eq 17 ] || fail "Worker: unstaged flag should be false this time" success "Worker: unstaged flag should be false this time" OUT=`$pm2 jlist | egrep -oh '"ahead":true' | wc -c` [ $OUT -eq 13 ] || fail "Worker: ahead flag should be true" success "Worker: ahead flag should be true" OUT=`$pm2 pull app 83dfc32383a84e146005d8981bcae2c52a5b123b | egrep -oh 'Current commit 83dfc32383a84e146005d8981bcae2c52a5b123b' | wc -c` [ $OUT -eq 56 ] || fail "Commit ID should be correct" success "Commit ID should be correct" $pm2 kill cd .. rm -rf ./app-playground ================================================ FILE: test/e2e.sh ================================================ #!/usr/bin/env bash export PM2_SILENT="true" SRC=$(cd $(dirname "$0"); pwd) source "${SRC}/e2e/include.sh" # Abort script at first error set -e touch e2e_time > e2e_time # CLI runTest ./test/e2e/cli/reload.sh runTest ./test/e2e/cli/start-app.sh runTest ./test/e2e/cli/operate-regex.sh #runTest ./test/e2e/cli/bun.sh runTest ./test/e2e/cli/app-configuration.sh runTest ./test/e2e/cli/binary.sh runTest ./test/e2e/cli/startOrX.sh runTest ./test/e2e/cli/reset.sh runTest ./test/e2e/cli/env-refresh.sh runTest ./test/e2e/cli/extra-lang.sh runTest ./test/e2e/cli/python-support.sh runTest ./test/e2e/cli/multiparam.sh runTest ./test/e2e/cli/smart-start.sh runTest ./test/e2e/cli/args.sh runTest ./test/e2e/cli/attach.sh runTest ./test/e2e/cli/serve.sh runTest ./test/e2e/esmodule.sh runTest ./test/e2e/cli/monit.sh runTest ./test/e2e/cli/cli-actions-1.sh runTest ./test/e2e/cli/cli-actions-2.sh runTest ./test/e2e/cli/dump.sh runTest ./test/e2e/cli/resurrect.sh runTest ./test/e2e/cli/watch.sh runTest ./test/e2e/cli/right-exit-code.sh runTest ./test/e2e/cli/fork.sh runTest ./test/e2e/cli/piped-config.sh # PROCESS FILES runTest ./test/e2e/process-file/json-file.sh runTest ./test/e2e/process-file/yaml-configuration.sh runTest ./test/e2e/process-file/json-reload.sh runTest ./test/e2e/process-file/app-config-update.sh runTest ./test/e2e/process-file/js-configuration.sh # BINARIES # INTERNALS runTest ./test/e2e/internals/wait-ready-event.sh runTest ./test/e2e/internals/daemon-paths-override.sh if [ "$IS_BUN" = false ]; then # runTest ./test/e2e/binaries/pm2-dev.sh # runTest ./test/e2e/binaries/pm2-runtime.sh runTest ./test/e2e/process-file/homogen-json-action.sh runTest ./test/e2e/internals/source_map.sh runTest ./test/e2e/internals/wrapped-fork.sh runTest ./test/e2e/logs/log-json.sh runTest ./test/e2e/misc/inside-pm2.sh #runTest ./test/e2e/misc/versioning-cmd.sh fi runTest ./test/e2e/internals/infinite-loop.sh runTest ./test/e2e/internals/options-via-env.sh #runTest ./test/e2e/internals/promise.sh runTest ./test/e2e/internals/increment-var.sh runTest ./test/e2e/internals/start-consistency.sh # MISC #runTest ./test/e2e/misc/vizion.sh runTest ./test/e2e/misc/misc.sh runTest ./test/e2e/misc/instance-number.sh runTest ./test/e2e/misc/startup.sh runTest ./test/e2e/misc/nvm-node-version.sh runTest ./test/e2e/misc/port-release.sh ## TMP DISABLE #runTest ./test/e2e/misc/cron-system.sh # LOGS runTest ./test/e2e/logs/log-custom.sh runTest ./test/e2e/logs/log-reload.sh runTest ./test/e2e/logs/log-entire.sh runTest ./test/e2e/logs/log-null.sh runTest ./test/e2e/logs/log-create-not-exist-dir.sh runTest ./test/e2e/logs/log-namespace.sh # MODULES runTest ./test/e2e/modules/get-set.sh runTest ./test/e2e/modules/module.sh runTest ./test/e2e/modules/module-safeguard.sh $pm2 kill echo "============== e2e test finished ==============" cat e2e_time # cat ~/.pm2/pm2.log | grep "PM2 global error caught" # spec "PM2 Daemon should not have thrown any global error" ================================================ FILE: test/fixtures/001-test.js ================================================ setInterval(function() { console.log('log message from echo.js'); console.error('err msg from echo.js'); }, 50); ================================================ FILE: test/fixtures/all.json ================================================ [{ "name" : "echo", "script" : "./echo.js" },{ "name" : "child", "script" : "./child.js", "instances" : "4", "error_file" : "./child-err.log", "out_file" : "./child-out.log" },{ "name" : "api-2", "script" : "./server.js" }] ================================================ FILE: test/fixtures/all2.json ================================================ [{ "name" : "echo", "script" : "./echo.js", "instances" : "1", "env_production" : { "NODE_ENV" : "production", "TOTO" : "heymoto" }, "env_test" : { "NODE_ENV" : "test", "TOTO" : "heyamota" } },{ "name" : "child", "script" : "./child.js", "instances" : "1", "error_file" : "./child-err.log", "out_file" : "./child-out.log" },{ "name" : "api-2", "script" : "./server.js", "instances" : "2" }] ================================================ FILE: test/fixtures/app-config-update/args1.json ================================================ { "script" : "echo.js", "node_args" : "" } ================================================ FILE: test/fixtures/app-config-update/args2.json ================================================ { "script" : "echo.js", "node_args" : "--harmony" } ================================================ FILE: test/fixtures/app-config-update/echo.js ================================================ var i = 0; setInterval(function() { console.log('ok', i++); }, 2000); console.log('ok'); ================================================ FILE: test/fixtures/append-env-to-name.json ================================================ [ { "name": "web", "script": "echo.js", "append_env_to_name": true, "env_dev": { "NODE_ENV": "dev", "PORT": 4000 }, "env_prod": { "NODE_ENV": "prod", "PORT": 3000 } } ] ================================================ FILE: test/fixtures/args/echo.js ================================================ setInterval(function() { console.log('echo.js'); }, 5000); setInterval(function() { console.error('echo.js-error'); }, 5000); ================================================ FILE: test/fixtures/args/params_check.js ================================================ process.argv.shift(); process.argv.shift(); process.argv.forEach(function(val, index) { console.log(val); }); setInterval(function() { }, 1000); ================================================ FILE: test/fixtures/args.js ================================================ if (process.argv.indexOf('-d') == -1 || process.argv.indexOf('-a') == -1) { process.exit(); } else { setInterval(function() { console.log('ok'); }, 500); } ================================================ FILE: test/fixtures/bashscript.sh ================================================ while true; do ls -l sleep 5 done ================================================ FILE: test/fixtures/big-array-es6.js ================================================ var obj = {}; var i = 0; setInterval(function() { obj[i] = Array.apply(null, new Array(99999)).map(String.prototype.valueOf,"hi"); i++; }, 2); (function testHarmony() { // // Harmony test // try { var assert = require('assert') , s = new Set(); s.add('a'); assert.ok(s.has('a')); console.log('● ES6 mode'.green); } catch(e) {} })(); ================================================ FILE: test/fixtures/big-array-listen.js ================================================ var obj = {}; var i = 0; var http = require('http'); http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(8000); process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 100); } }); setInterval(function() { obj[i] = Array.apply(null, new Array(99999)).map(String.prototype.valueOf,"hi"); i++; }, 2); ================================================ FILE: test/fixtures/big-array.js ================================================ var obj = {}; var i = 0; setInterval(function() { obj[i] = Array.apply(null, new Array(99999)).map(String.prototype.valueOf,"hi"); i++; }, 2); ================================================ FILE: test/fixtures/binary-js-file ================================================ setInterval(function () {console.log("test")}, 5000) ================================================ FILE: test/fixtures/binary-js-file.js ================================================ setInterval(function () {console.log("test")}, 5000) ================================================ FILE: test/fixtures/binary-py-file.py ================================================ import time while True: print 'hello' time.sleep(1) ================================================ FILE: test/fixtures/c-compile/hello.c ================================================ #include int main() { // printf() displays the string inside quotation printf("Hello World\n"); return 0; } ================================================ FILE: test/fixtures/change_cwd.json ================================================ [{ "name" : "iminpath1", "script" : "iminpath1.js", "cwd" : "path1", "out_file" : "./iminpath1.log" },{ "name" : "iminpath2", "script" : "iminpath2.js", "cwd" : "path1/path2", "out_file" : "./iminpath2.log" }] ================================================ FILE: test/fixtures/child.js ================================================ var http = require('http'); http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(0); process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 100); } }); ================================================ FILE: test/fixtures/child_no_http.js ================================================ var pmx = require('@pm2/io').init({ http: false }); var http = require('http'); http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(8000); process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 100); } }); ================================================ FILE: test/fixtures/cluster/sigint_catcher.js ================================================ setInterval(function() { // Do nothing to keep process alive }, 1000); process.on('SIGINT', function () { console.log('SIGINT cb called'); }); ================================================ FILE: test/fixtures/cluster-alias.json ================================================ { "apps": [{ "name" : "serv-clust", "script" : "server.js", "instances" : "4", "exec_mode" : "cluster", "env_test" : { "NODE_ENV" : "TEST" } }] } ================================================ FILE: test/fixtures/cluster-pm2.json ================================================ { "name" : "serv-clust", "script" : "server.js", "max" : "10", "instances" : "4", "env_test" : { "NODE_ENV" : "TEST" } } ================================================ FILE: test/fixtures/conf.json ================================================ // TG! [{ // Application 1 name : "echo", script : 'toto.js', env : { "TOTO" : process.env.NODE_ENV || 'vazytg' } }] ================================================ FILE: test/fixtures/configuration.js ================================================ [{ name : "echo", script : [".", "/", "e", "cho.js"].join('') },{ name : "child", script : "./child.js", instances : "4", error_file : "./child-err.log", out_file : "./child-out.log" },{ name : "api-2", script : "./server.js" }] ================================================ FILE: test/fixtures/configuration.json ================================================ // TG! [{ // Application 1 name : "echo", script : [".", "/", "e", "cho.js"].join('') },{ // APplication 2 name : "child", script : "./child.js", instances : "4", error_file : "./child-err.log", out_file : "./child-out.log" },{ name : "api-2", script : "./server.js" }] ================================================ FILE: test/fixtures/containerizer/Dockerfile.dev ================================================ FROM mhart/alpine-node:latest RUN apk update && apk add git && rm -rf /var/cache/apk/* RUN npm install pm2@next -g RUN mkdir -p /var/app WORKDIR /var/app COPY ./package.json /var/app RUN npm install ## DEVELOPMENT MODE ENV NODE_ENV=development CMD ["pm2-docker", "start", "index.js"] ================================================ FILE: test/fixtures/containerizer/Dockerfile.prod ================================================ FROM mhart/alpine-node:latest RUN apk update && apk add git && rm -rf /var/cache/apk/* RUN npm install pm2@next -g RUN mkdir -p /var/app WORKDIR /var/app COPY ./package.json /var/app RUN npm install ## PRODUCTION MODE ENV NODE_ENV=production COPY . /var/app/ CMD ["pm2-docker", "start", "index.js"] ================================================ FILE: test/fixtures/cron-stop.js ================================================ setTimeout(() => { process.exit(0) }, 1000) ================================================ FILE: test/fixtures/cron.js ================================================ setInterval(function() { console.log('ok'); }, 500); ================================================ FILE: test/fixtures/cron_no_autorestart.js ================================================ console.log('ok') ================================================ FILE: test/fixtures/custom_actions/index.js ================================================ var pmx = require('@pm2/io'); pmx.action('ping', function(reply) { return reply({ 'pong' : 'hehe' }) }); pmx.action('param', function(data, reply) { return reply(data) }); setInterval(function() { }, 1000); ================================================ FILE: test/fixtures/delayed_exit.js ================================================ var exit = function() { console.log("Delay exit in 2 secs"); setTimeout(function(){ process.exit(); }, 2000); }; process.on('SIGTERM', function() { console.log('Got SIGTERM signal.'); exit(); }); process.on('SIGINT', function() { console.log('Got SIGINT signal'); exit(); }); setInterval(function keepMeAlive() {}, 1000); ================================================ FILE: test/fixtures/docker/expressor/Dockerfile.bak ================================================ FROM keymetrics/pm2:latest RUN mkdir -p /var/app WORKDIR /var/app COPY ./package.json /var/app RUN npm install ## DEVELOPMENT MODE ENV NODE_ENV=development CMD ["pm2-dev", "process.json", "--env", "development"] ================================================ FILE: test/fixtures/docker/expressor/app.js ================================================ var express = require('express'); var app = express(); app.get('/', function (req, res) { res.send('Hello Bro! yes'); }); app.listen(3000, function () { console.log('Example app listening on port 3000!'); }); ================================================ FILE: test/fixtures/docker/expressor/package.json ================================================ { "name": "@keymetrics/expressor", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "express": "^4.14.0", "passport" : "*", "redis" : "*" } } ================================================ FILE: test/fixtures/docker/expressor/process.json ================================================ { apps : [{ script : 'app.js', name : 'Express', instances : '1' }, { script : 'worker.js' }] } ================================================ FILE: test/fixtures/docker/expressor/worker.js ================================================ setInterval(function() { console.log('Worker finished his job'); }, 1000); ================================================ FILE: test/fixtures/echo-env.js ================================================ setInterval(function() { console.log(process.env.ECHO_MSG || 'ok'); }, 100); ================================================ FILE: test/fixtures/echo-pm2.json ================================================ { "name" : "echo", "script" : "echo.js", "options": [""] } ================================================ FILE: test/fixtures/echo-post.json ================================================ { "name" : "echo-json", "script" : "echo-env.js", "log_date_format" : "DD", "out_file" : "echo-test.log", "merge_logs" : true, "env" : { "ECHO_MSG" : "YAY" }, "env_production" : { "ECHO_MSG" : "WOW" }, "env_testing" : { "ECHO_MSG" : "TEST" } } ================================================ FILE: test/fixtures/echo-to-pm2.json ================================================ { "name" : "echo-to", "script" : "echo-to.js", "options": [""] } ================================================ FILE: test/fixtures/echo.coffee ================================================ #!/usr/bin/env coffee setInterval (-> console.log 'ok'), 500 ================================================ FILE: test/fixtures/echo.js ================================================ setInterval(function() { console.log('echo.js'); }, 50); setInterval(function() { console.error('echo.js-error'); }, 50); ================================================ FILE: test/fixtures/echo2.js ================================================ var http = require('http'); http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(0); process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 100); } }); ================================================ FILE: test/fixtures/echo3.js ================================================ var http = require('http'); http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(0); process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 100); } }); ================================================ FILE: test/fixtures/echoto-pm2.json ================================================ { "name" : "echoto", "script" : "echoto.js", "options": [""] } ================================================ FILE: test/fixtures/ecosystem/ecosystem.config.js ================================================ module.exports = { script: 'unknown.js' } ================================================ FILE: test/fixtures/ecosystem.config.js ================================================ module.exports = { apps : [{ script: 'index.js', watch: '.' }, { script: './service-worker/', watch: ['./service-worker'] }], deploy : { production : { user : 'SSH_USERNAME', host : 'SSH_HOSTMACHINE', ref : 'origin/master', repo : 'GIT_REPOSITORY', path : 'DESTINATION_PATH', 'pre-deploy-local': '', 'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env production', 'pre-setup': '' } } }; ================================================ FILE: test/fixtures/ecosystem.json ================================================ { /** * Application configuration section * http://pm2.keymetrics.io/docs/usage/application-declaration/ */ apps : [ // First application { name : "API", script : "app.js", env: { COMMON_VARIABLE: "true" }, env_production : { NODE_ENV: "production" } }, // Second application { name : "WEB", script : "web.js" } ], /** * Deployment section * http://pm2.keymetrics.io/docs/usage/deployment/ */ deploy : { production : { user : "node", host : "212.83.163.1", ref : "origin/master", repo : "git@github.com:repo.git", path : "/var/www/production", "post-deploy" : "npm install && pm2 startOrRestart ecosystem.json --env production" }, dev : { user : "node", host : "212.83.163.1", ref : "origin/master", repo : "git@github.com:repo.git", path : "/var/www/development", "post-deploy" : "npm install && pm2 startOrRestart ecosystem.json --env dev", env : { NODE_ENV: "dev" } } } } ================================================ FILE: test/fixtures/ecosystem.json5 ================================================ { /** * This is a sample configuration file for PM2 */ /** * Here we declare the apps that must be managed by PM2 * All options are listed here: * https://github.com/Unitech/PM2/blob/master/ADVANCED_README.md#json-app-declaration * */ apps : [ // First application { name : "API", script : "app.js", env: { COMMON_VARIABLE: "true" }, env_production : { NODE_ENV: "production" } }, // Second application { name : "WEB", script : "web.js" } ], /** * PM2 help you to deploy apps over your servers * For more help go to : * https://github.com/Unitech/PM2/blob/master/ADVANCED_README.md#deployment-pm2--090 */ deploy : { production : { user : "node", host : "212.83.163.1", ref : "origin/master", repo : "git@github.com:repo.git", path : "/var/www/production", "post-deploy" : "pm2 startOrRestart ecosystem.json5 --env production" }, dev : { user : "node", host : "212.83.163.1", ref : "origin/master", repo : "git@github.com:repo.git", path : "/var/www/development", "post-deploy" : "pm2 startOrRestart ecosystem.json5 --env dev", env : { NODE_ENV: "dev" } } } } ================================================ FILE: test/fixtures/empty.js ================================================ setInterval(function () {}, 1000); ================================================ FILE: test/fixtures/env-ecosystem.json ================================================ { "apps" : [{ "name" : "env2", "script" : "./env.js", "out_file" : "out-env.log", "merge_logs" : true, "env": { "NODE_ENV": "production" } }], "deploy" : { "production" : { "user" : "node", "host" : "212.83.163.1", "ref" : "origin/master", "repo" : "git@github.com:repo.git", "path" : "/var/www/production", "env" : { "TEST_VARIABLE": "No worries!" } } } } ================================================ FILE: test/fixtures/env-refreshed.json ================================================ [{ "name" : "appname", "script" : "./env.js", "out_file" : "out-env.log", "merge_logs" : true, "env": { "NODE_ENV": "production", "TEST_VARIABLE": { "HEYYYY": true } } }] ================================================ FILE: test/fixtures/env-switching/app.json ================================================ { script : './child.js', // Default environment env : { NODE_ENV : 'normal' }, // Prod env_production : { NODE_ENV : 'production' }, // Test env_test : { NODE_ENV : 'test' } } ================================================ FILE: test/fixtures/env-switching/child.js ================================================ setInterval(function() { console.log(process.env.NODE_ENV); }, 1000); ================================================ FILE: test/fixtures/env.js ================================================ setInterval(function() { console.log(process.env.TEST_VARIABLE); }, 100); ================================================ FILE: test/fixtures/env.json ================================================ [{ "name" : "appname", "script" : "./env.js", "out_file" : "out-env.log", "merge_logs" : true, "env": { "NODE_ENV": "production", "TEST_VARIABLE": "YES" } }] ================================================ FILE: test/fixtures/esmodules/mjs/circle.mjs ================================================ const PI = 3.14159265359; export function area(radius) { return (radius ** 2) * PI; } export function circumference(radius) { return 2 * radius * PI; } ================================================ FILE: test/fixtures/esmodules/mjs/index.mjs ================================================ import { area, circumference } from './circle.mjs'; const r = 3; console.log(`Circle with radius ${r} has area: ${area(r)}; circunference: ${circumference(r)}`); setInterval(() => { }, 1000) ================================================ FILE: test/fixtures/esmodules/packagemodule/circle.js ================================================ const PI = 3.14159265359; export function area(radius) { return (radius ** 2) * PI; } export function circumference(radius) { return 2 * radius * PI; } ================================================ FILE: test/fixtures/esmodules/packagemodule/index.js ================================================ import { area, circumference } from './circle.js'; const r = 3; console.log(`Circle with radius ${r} has area: ${area(r)}; circunference: ${circumference(r)}`); setInterval(() => { }, 1000) ================================================ FILE: test/fixtures/esmodules/packagemodule/package.json ================================================ { "name": "import", "version": "1.0.0", "description": "", "main": "index.js", "type":"module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" } ================================================ FILE: test/fixtures/events/custom_action.js ================================================ var axm = require('@pm2/io'); axm.action('refresh:db', function(reply) { console.log('Refreshing'); reply({success : true, subobj : { a : 'b'}}); }); ================================================ FILE: test/fixtures/events/custom_action_with_params.js ================================================ var axm = require('@pm2/io'); axm.action('refresh:db', { comment : 'Refresh the database' }, function(reply) { console.log('Refreshing'); reply({success : true}); }); axm.action('chanme:ladb', { comment : 'Refresh la BIG database' }, function(reply) { console.log('Refreshing BIG DB'); reply({success : true}); }); axm.action('rm:rf', { comment : 'Delete moi ca plus vite que ca !' }, function(reply) { console.log('RMING RFING'); reply({success : true}); }); axm.action('rm:roff', function(reply) { console.log('RMING RFING'); reply({success : true}); }); ================================================ FILE: test/fixtures/events/own_event.js ================================================ var axm = require('@pm2/io'); setInterval(function() { axm.emit('user:register', { user : 'toto@gmail.com', mail : 'hey@gmail.com' }); }, 200); ================================================ FILE: test/fixtures/exitcode42.js ================================================ setInterval(function() { console.log('BYE'); process.exit(42); }, 500); ================================================ FILE: test/fixtures/extra-lang/app-python.config.js ================================================ module.exports = { apps : [{ name: 'echo-python-1', cmd: 'echo.py', max_memory_restart: '1G', env: { NODE_ENV: 'development' }, env_production : { NODE_ENV: 'production' } },{ name: 'echo-python-max', cmd: 'echo.py', instances: 4, env: { NODE_ENV: 'development' }, env_production : { NODE_ENV: 'production' } }] }; ================================================ FILE: test/fixtures/extra-lang/apps.json ================================================ { apps: [{ "interpreter" : "/usr/bin/python3", "name" : "echo-python", "script" : "echo.py", "log" : "python-app.log", "combine_logs" : true, "env" : { DEFAULT_STR : "Python" }, "env_production" : { DEFAULT_STR : "PythonProduction" } }, { "exec_interpreter" : "/usr/bin/php", "name" : "echo-php", "script" : "echo.php", "output" : "php-app-out.log", "error" : "php-error.log", "combine_logs" : true }] } ================================================ FILE: test/fixtures/extra-lang/echo.php ================================================ ================================================ FILE: test/fixtures/extra-lang/echo.py ================================================ #!/usr/bin/python import os import time while 1: try: print(os.environ['DEFAULT_STR']) except KeyError: print('RAWPython') time.sleep(1) ================================================ FILE: test/fixtures/git/COMMIT_EDITMSG ================================================ first commit ================================================ FILE: test/fixtures/git/HEAD ================================================ ref: refs/heads/master ================================================ FILE: test/fixtures/git/config ================================================ [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true [remote "origin"] url = https://github.com/jshkurti/pm2_travis.git fetch = +refs/heads/*:refs/remotes/origin/* [branch "master"] remote = origin merge = refs/heads/master ================================================ FILE: test/fixtures/git/description ================================================ Unnamed repository; edit this file 'description' to name the repository. ================================================ FILE: test/fixtures/git/hooks/applypatch-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup test -x "$GIT_DIR/hooks/commit-msg" && exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} : ================================================ FILE: test/fixtures/git/hooks/commit-msg.sample ================================================ #!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. exit 1 } ================================================ FILE: test/fixtures/git/hooks/post-update.sample ================================================ #!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info ================================================ FILE: test/fixtures/git/hooks/pre-applypatch.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed # by applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. # # To enable this hook, rename this file to "pre-applypatch". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} : ================================================ FILE: test/fixtures/git/hooks/pre-commit.sample ================================================ #!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # If you want to allow non-ASCII filenames set this variable to true. allownonascii=$(git config --bool hooks.allownonascii) # Redirect output to stderr. exec 1>&2 # Cross platform projects tend to avoid non-ASCII filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test $(git diff --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 then cat <<\EOF Error: Attempt to add a non-ASCII file name. This can cause problems if you want to work with people on other platforms. To be portable it is advisable to rename the file. If you know what you are doing you can disable this check using: git config hooks.allownonascii true EOF exit 1 fi # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against -- ================================================ FILE: test/fixtures/git/hooks/pre-push.sample ================================================ #!/bin/sh # An example hook script to verify what is about to be pushed. Called by "git # push" after it has checked the remote status, but before anything has been # pushed. If this script exits with a non-zero status nothing will be pushed. # # This hook is called with the following parameters: # # $1 -- Name of the remote to which the push is being done # $2 -- URL to which the push is being done # # If pushing without using a named remote those arguments will be equal. # # Information about the commits which are being pushed is supplied as lines to # the standard input in the form: # # # # This sample shows how to prevent push of commits where the log message starts # with "WIP" (work in progress). remote="$1" url="$2" z40=0000000000000000000000000000000000000000 IFS=' ' while read local_ref local_sha remote_ref remote_sha do if [ "$local_sha" = $z40 ] then # Handle delete : else if [ "$remote_sha" = $z40 ] then # New branch, examine all commits range="$local_sha" else # Update to existing branch, examine new commits range="$remote_sha..$local_sha" fi # Check for WIP commit commit=`git rev-list -n 1 --grep '^WIP' "$range"` if [ -n "$commit" ] then echo "Found WIP commit in $local_ref, not pushing" exit 1 fi fi done exit 0 ================================================ FILE: test/fixtures/git/hooks/pre-rebase.sample ================================================ #!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up-to-date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /usr/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi <<\DOC_END This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". DOC_END ================================================ FILE: test/fixtures/git/hooks/prepare-commit-msg.sample ================================================ #!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first comments out the # "Conflicts:" part of a merge commit. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. case "$2,$3" in merge,) /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; # ,|template,) # /usr/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$1" ;; *) ;; esac # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" ================================================ FILE: test/fixtures/git/hooks/update.sample ================================================ #!/bin/sh # # An example hook script to blocks unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --bool hooks.allowunannotated) allowdeletebranch=$(git config --bool hooks.allowdeletebranch) denycreatebranch=$(git config --bool hooks.denycreatebranch) allowdeletetag=$(git config --bool hooks.allowdeletetag) allowmodifytag=$(git config --bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero="0000000000000000000000000000000000000000" if [ "$newrev" = "$zero" ]; then newrev_type=delete else newrev_type=$(git cat-file -t $newrev) fi case "$refname","$newrev_type" in refs/tags/*,commit) # un-annotated tag short_refname=${refname##refs/tags/} if [ "$allowunannotated" != "true" ]; then echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 ================================================ FILE: test/fixtures/git/info/exclude ================================================ # git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ ================================================ FILE: test/fixtures/git/logs/HEAD ================================================ 0000000000000000000000000000000000000000 7ae6d7c64d39da2167c33993e1f4f0233e8eb6f0 jshkurti 1429209718 +0200 commit (initial): first commit ================================================ FILE: test/fixtures/git/logs/refs/heads/master ================================================ 0000000000000000000000000000000000000000 7ae6d7c64d39da2167c33993e1f4f0233e8eb6f0 jshkurti 1429209718 +0200 commit (initial): first commit ================================================ FILE: test/fixtures/git/logs/refs/remotes/origin/master ================================================ 0000000000000000000000000000000000000000 7ae6d7c64d39da2167c33993e1f4f0233e8eb6f0 jshkurti 1429209819 +0200 update by push ================================================ FILE: test/fixtures/git/objects/7a/e6d7c64d39da2167c33993e1f4f0233e8eb6f0 ================================================ xK @/?PBbt$_h{^> /ҙJBڜȔ*":* dc*T {[Uc. n?_]j_VGo\ZR/Yև2;/ ================================================ FILE: test/fixtures/git/refs/heads/master ================================================ 7ae6d7c64d39da2167c33993e1f4f0233e8eb6f0 ================================================ FILE: test/fixtures/git/refs/remotes/origin/master ================================================ 7ae6d7c64d39da2167c33993e1f4f0233e8eb6f0 ================================================ FILE: test/fixtures/graceful-exit-no-listen.js ================================================ /* * Example of graceful exit that does not listen * * $ pm2 reload all */ process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 1500); } }); setInterval(function () { console.log('tick'); }, 4000); ================================================ FILE: test/fixtures/graceful-exit-send.js ================================================ /* * Example of graceful exit that does not listen but sends 'online' * * $ pm2 reload all */ process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 1500); } }); setInterval(function () { console.log('tick'); }, 4000); setTimeout(function () { process.send('online'); }, 2000); ================================================ FILE: test/fixtures/graceful-exit.js ================================================ /* * Example of graceful exit * * $ pm2 reload all */ process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 1500); } }); var http = require('http'); http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(8000, function() { console.log('listening'); }); ================================================ FILE: test/fixtures/homogen-json-action/all.json ================================================ { "apps" : [{ "script" : "http.js", "name" : "http", "instances" : 4 }, { "script" : "http.js", "name" : "http2", "instances" : 2 }] } ================================================ FILE: test/fixtures/homogen-json-action/http.js ================================================ var pmx = require('@pm2/io').init({ http : true }); var http = require('http'); var server = http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(8000, function() { console.log('App listening on port 8000'); }); ================================================ FILE: test/fixtures/http.js ================================================ var http = require('http'); var app = http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }) var listener = app.listen(0, function() { console.log('Listening on port ' + listener.address().port); }); process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 100); } }); ================================================ FILE: test/fixtures/increment-var/ecosystem.json ================================================ [ { "name" : "sample-normal", "instances": "2", "exec_mode": "cluster", "script" : "sample.js", "out_file": "NULL", "error_file": "NULL" }, { "name" : "sample-other-instance", "instances": "2", "instance_var": "APP_ID", "exec_mode": "cluster", "script" : "sample.js", "out_file": "NULL", "error_file": "NULL" }, { "name" : "sample-increment", "instances": "2", "increment_var": "PORT", "exec_mode": "cluster", "script" : "sample.js", "out_file": "NULL", "error_file": "NULL", "env": { "PORT": 3000 } }, { "name" : "sample-normal-fork", "instances": "2", "exec_mode": "fork", "script" : "sample.js", "out_file": "NULL", "error_file": "NULL" }, { "name" : "sample-other-instance-fork", "instances": "2", "instance_var": "APP_ID", "exec_mode": "fork", "script" : "sample.js", "out_file": "NULL", "error_file": "NULL" }, { "name" : "sample-increment-fork", "instances": "2", "increment_var": "PORT", "exec_mode": "fork", "script" : "sample.js", "out_file": "NULL", "error_file": "NULL", "env": { "PORT": 3000 } } ] ================================================ FILE: test/fixtures/increment-var/sample.js ================================================ setInterval(function () {}, 1000); ================================================ FILE: test/fixtures/inside/echo.js ================================================ setInterval(function() { console.log('echo.js'); }, 500); setInterval(function() { console.error('echo.js-error'); }, 500); ================================================ FILE: test/fixtures/inside/inner_restart.sh ================================================ #!/bin/bash $PM2_PATH restart echo $PM2_PATH restart echo $PM2_PATH restart echo ================================================ FILE: test/fixtures/inside/reload_inside.js ================================================ var PM2 = require('../../..'); var pm2 = new PM2.custom({ cwd : __dirname }); PM2.reload('echo', function(err, app) { if (err) throw err; }); ================================================ FILE: test/fixtures/inside/restart_inside.js ================================================ var PM2 = require('../../..'); var pm2 = new PM2.custom({ cwd : __dirname }); PM2.restart('echo', function(err, app) { if (err) throw err; }); ================================================ FILE: test/fixtures/inside/start_inside.js ================================================ var PM2 = require('../../..'); var pm2 = new PM2.custom({ cwd : __dirname }); PM2.start('./echo.js', function(err, app) { if (err) throw err; }); ================================================ FILE: test/fixtures/insidePm2Process.js ================================================ setInterval(function(){ console.log(process.env.TEST_VARIABLE); }, 500); ================================================ FILE: test/fixtures/interface/child.js ================================================ var http = require('http'); http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(8000); process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 100); } }); ================================================ FILE: test/fixtures/interface/http_transaction.js ================================================ var axm = require('@pm2/io'); axm.init({ http: true }) var http = require('http'); http.createServer(function(req, res) { res.writeHead(200); setTimeout(function() { res.end('transaction'); }, 1000); }).listen(9010); setInterval(function() { request(['/user', '/bla', '/user/lol/delete', '/POST/POST'][Math.floor((Math.random() * 4))]); }, 100); function makeid() { var text = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for( var i=0; i < 5; i++ ) text += possible.charAt(Math.floor(Math.random() * possible.length)); return text; } function request(path) { var options = { hostname: '127.0.0.1' ,port: 9010 ,path: path || '/users' ,method: 'GET' ,headers: { 'Content-Type': 'application/json' } }; var req = http.request(options, function(res) { res.setEncoding('utf8'); res.on('data', function (data) { console.log(data); // I can't parse it because, it's a string. why? }); }); req.on('error', function(e) { console.log('problem with request: ' + e.message); }); req.end(); } ================================================ FILE: test/fixtures/interface/human_event.js ================================================ var axm = require('@pm2/io'); setInterval(function() { axm.emit('content:page:created', { msg : 'A CMS page has been created', user : 'Francois Debiole' }); }, 200); ================================================ FILE: test/fixtures/interface/log_out.js ================================================ console.log('outmsg'); console.error('errmsg'); setInterval(function() {}, 100); ================================================ FILE: test/fixtures/interface/process_exception.js ================================================ var axm = require('@pm2/io'); //axm.catchAll(); setTimeout(function() { throw new Error('Exit'); }, 200); ================================================ FILE: test/fixtures/interface/process_exception_with_logs.js ================================================ var pmx = require('@pm2/io'); pmx.action('exception', function(reply) { console.log('Im going to crash'); console.log('I will crash muhahah'); throw new Error('CRASHED'); return reply({ sucess: true}); }); setInterval(function() { }, 1000); ================================================ FILE: test/fixtures/interface/promise_rejection.js ================================================ setTimeout(() => { var p = new Promise(function(resolve, reject) { //setTimeout(function() { //throw new Error('fail') abc = asdsad; return resolve('ok') //}, 200) }) p.then(function(e) { }) }, 100) ================================================ FILE: test/fixtures/interpreter/echo.coffee ================================================ #!/usr/bin/env coffee setInterval (-> console.log 'ok'), 500 ================================================ FILE: test/fixtures/interpreter/echo.ls ================================================ #!/usr/bin/env lsc setInterval (-> console.log 'Hello Livescript!'), 500 ================================================ FILE: test/fixtures/interpreter/echo.ts ================================================ class Greeter { constructor(public greeting: string) { } greet() { return this.greeting; } }; var greeter = new Greeter("Hello Typescript!"); console.log(greeter.greet()); ================================================ FILE: test/fixtures/interpreter/echo.tsx ================================================ class Greeter { constructor(public greeting: string) { } greet() { return this.greeting; } }; var greeter = new Greeter("Hello Typescript!"); console.log(greeter.greet()); ================================================ FILE: test/fixtures/js-configuration/app.js ================================================ setInterval(function() { console.log('ok'); }, 1000); ================================================ FILE: test/fixtures/js-configuration/ecosystem.config.js ================================================ module.exports = { /** * Application configuration section * http://pm2.keymetrics.io/docs/usage/application-declaration/ */ apps : [ // First application { name : "API", script : "app.js", env: { COMMON_VARIABLE: "true" }, env_production : { NODE_ENV: "production" } } ], /** * Deployment section * http://pm2.keymetrics.io/docs/usage/deployment/ */ deploy : { production : { user : "node", host : "212.83.163.1", ref : "origin/master", repo : "git@github.com:repo.git", path : "/var/www/production", "post-deploy" : "npm install && pm2 startOrRestart ecosystem.config.js --env production" }, dev : { user : "node", host : "212.83.163.1", ref : "origin/master", repo : "git@github.com:repo.git", path : "/var/www/development", "post-deploy" : "npm install && pm2 startOrRestart ecosystem.config.js --env dev", env : { NODE_ENV: "dev" } } } }; ================================================ FILE: test/fixtures/json-reload/big-array.js ================================================ var obj = {}; var i = 0; setInterval(function() { obj[i] = Array.apply(null, new Array(9999)).map(String.prototype.valueOf,"hi"); i++; }, 40); ================================================ FILE: test/fixtures/json-reload/echo-env.js ================================================ setInterval(function() { console.log(process.env.ECHO_MSG || 'ok'); }, 100); ================================================ FILE: test/fixtures/json-reload/echo-post.json ================================================ { "name" : "echo-json", "script" : "echo-env.js", "log_date_format" : "DD", "out_file" : "echo-test.log", "merge_logs" : true, "env" : { "ECHO_MSG" : "YAY" }, "env_production" : { "ECHO_MSG" : "WOW" } } ================================================ FILE: test/fixtures/json-reload/echo-pre.json ================================================ { "name" : "echo-json", "script" : "echo-env.js", "log_date_format" : "YYYY-MM-DD HH:mm Z", "out_file" : "echo-test.log", "merge_logs" : true } ================================================ FILE: test/fixtures/json-reload/max-mem-0.json ================================================ { "name" : "max_mem", "script" : "big-array.js", "max_memory_restart" : "300M" } ================================================ FILE: test/fixtures/json-reload/max-mem.json ================================================ { "name" : "max_mem", "script" : "big-array.js", "max_memory_restart" : "19M" } ================================================ FILE: test/fixtures/killnotsofast.js ================================================ setInterval(function() { console.log('ALIVE'); }, 500); ================================================ FILE: test/fixtures/killtoofast.js ================================================ setInterval(function() { console.log('BOUM'); process.exit(1); }, 30); ================================================ FILE: test/fixtures/listen-timeout/wait-ready.js ================================================ var http = require('http'); var server = http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(process.env.PORT || 8000, function() { console.log('App listening on port %d in env %s', process.env.PORT || 8000, process.env.NODE_ENV); // 1# Notify application ready setTimeout(function() { process.send('ready'); }, 1000); }); // // 2# Handle on Exit process.on('SIGINT', function() { console.log('Cleanup on exit'); server.on('close', function() { console.log('Connections closed'); process.exit(0); }); server.close(); }); ================================================ FILE: test/fixtures/local_require.js ================================================ var paths = require('module').globalPaths; if (Array.isArray(paths)) { var found = false; paths.forEach(function(elem) { if (elem === process.env.NODE_PATH) { found = true; } }); if (!found) process.exit(1); else setInterval(function keepAlive() {}, 10000); } else { process.exit(1); } ================================================ FILE: test/fixtures/log-create-not-exist-dir/echo.js ================================================ console.log("start"); setInterval(function () { console.log("tick"); }, 50); ================================================ FILE: test/fixtures/log-json/ecosystem.json ================================================ [{ "name" : "one-echo", "script" : "one-echo.js", "log_type": "json", "log_file": "output.log", "merge_logs": true, "out_file": "NULL", "error_file": "NULL", "autorestart": false }, { "name" : "one-echo-cluster", "script" : "one-echo.js", "exec_mode": "cluster_mode", "log_type": "json", "log_file": "output.log", "merge_logs": true, "out_file": "NULL", "error_file": "NULL", "autorestart": false }, { "name" : "one-echo-date", "script" : "one-echo.js", "log_type": "json", "log_file": "output.log", "log_date_format": "YYYY", "merge_logs": true, "out_file": "NULL", "error_file": "NULL", "autorestart": false }, { "name" : "one-echo-cluster-date", "script" : "one-echo.js", "exec_mode": "cluster_mode", "log_type": "json", "log_file": "output.log", "log_date_format": "YYYY", "merge_logs": true, "out_file": "NULL", "error_file": "NULL", "autorestart": false }] ================================================ FILE: test/fixtures/log-json/one-echo.js ================================================ console.log('echo') ================================================ FILE: test/fixtures/log-namespace/echo.js ================================================ console.log("start"); setInterval(function () { console.log("tick"); }, 50); ================================================ FILE: test/fixtures/mjs/ecosystem.config.js ================================================ module.exports = { apps : [ { name : 'Test es6 modules', script : 'index.mjs', node_args : '--experimental-modules' } ], }; ================================================ FILE: test/fixtures/mjs/index.mjs ================================================ import {test} from './test' test(); ================================================ FILE: test/fixtures/mjs/package.json ================================================ { "name": "test-es6-pm2", "version": "1.0.0", "description": "A simple project to test pm2 es6-modules pm2 support", "main": "index.mjs", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node --experimental-modules index.mjs" }, "keywords": [ "es6-modules", "import", "export", "pm2" ], "author": "vpotseluyko", "license": "ISC" } ================================================ FILE: test/fixtures/mjs/test.mjs ================================================ export function test() { setInterval(() => console.log(`Hello es6 from pm2`), 1000); } ================================================ FILE: test/fixtures/module-fixture/package.json ================================================ { "name": "example-module", "version": "0.3.21", "description": "Keymetrics++ and PM2 adapter", "main": "scoped-action.js", "dependencies": { }, "scripts": { "test": "DEBUG='axm:*' mocha test/*.mocha.js" }, "repository": { "type": "git", "url": "https://github.com/keymetrics/pmx.git" }, "config" : { "aconfig-var" : true, "var2" : false }, "author": "Keymetrics I/O", "license": "MIT" } ================================================ FILE: test/fixtures/module-fixture/scoped-action.js ================================================ var pmx = require('@pm2/io'); var conf = pmx.initModule({ widget : { type : 'generic', logo : 'https://app.keymetrics.io/img/logo/keymetrics-300.png', // 0 = main element // 1 = secondary // 2 = main border // 3 = secondary border theme : ['#141A1F', '#222222', '#3ff', '#3ff'], el : { probes : true, actions : true }, block : { actions : true, issues : true, meta : true } // Status // Green / Yellow / Red } }); pmx.scopedAction('testo', function(data, emitter) { var i = setInterval(function() { emitter.send('datard'); }, 100); setTimeout(function() { emitter.end('end'); clearInterval(i); }, 3000); }); var spawn = require('child_process').spawn; pmx.scopedAction('long running lsof', function(data, res) { var child = spawn('lsof', []); child.stdout.on('data', function(chunk) { chunk.toString().split('\n').forEach(function(line) { res.send(line); }); }); child.stdout.on('end', function(chunk) { res.end('end'); }); }); pmx.action('simple action', function(reply) { return reply({success:true}); }); pmx.action('simple with arg', function(opts,reply) { return reply(opts); }); ================================================ FILE: test/fixtures/multi-echo.json ================================================ [{ "name" : "echo2", "script" : "echo.js", "max" : "1" }, { "name" : "echo3", "script" : "echo.js", "max" : "1" }] ================================================ FILE: test/fixtures/network.js ================================================ var http = require('http'); var i = 0; http.createServer(function(req, res) { res.writeHead(200); res.end("hello world\n" + i++); }).listen(8004); ================================================ FILE: test/fixtures/no-restart.json ================================================ [ { "name" : "no_restart_app", "script" : "./killtoofast.js", "autorestart" : false } ] ================================================ FILE: test/fixtures/no-vizion.json ================================================ [ { "name" : "no_vizion", "script" : "./echo.js", "vizion" : false } ] ================================================ FILE: test/fixtures/no_cwd_change.json ================================================ [{ "name" : "iminpath1", "script" : "iminpath1.js", "out_file" : "./iminpath1.log" },{ "name" : "iminpath2", "script" : "iminpath2.js", "out_file" : "./iminpath2.log" }] ================================================ FILE: test/fixtures/nvm-node-version/ecosystem-change.json ================================================ { /** * Application configuration section * http://pm2.keymetrics.io/docs/usage/application-declaration/ */ apps : [ // First application { name : "http-4.6", script : "http.js", interpreter : "node@4.5.0" }, { name : "http-6.7", script : "http.js", force : true, env : { PORT : 8002 }, interpreter : "node@6.7.0" }, ] } ================================================ FILE: test/fixtures/nvm-node-version/ecosystem.json ================================================ { /** * Application configuration section * http://pm2.keymetrics.io/docs/usage/application-declaration/ */ apps : [ // First application { name : "http-4.6", script : "http.js", interpreter : "node@4.6.0" }, { name : "http-6.7", script : "http.js", force : true, env : { PORT : 8002 }, interpreter : "node@6.7.0" }, ] } ================================================ FILE: test/fixtures/nvm-node-version/http.js ================================================ var http = require('http'); var server = http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(process.env.PORT || 8000, function() { console.log('App listening on port %d in env %s', process.env.PORT || 8000, process.env.NODE_ENV); }); ================================================ FILE: test/fixtures/path-check.js ================================================ console.log(__filename); console.log(module.filename); console.log(module.parent); console.log(module.children); console.log(__dirname); console.log(module); console.log(process.env.PWD); console.log(require.main.filename); console.log(require.main === module); ================================================ FILE: test/fixtures/path-resolution/echo.js ================================================ setInterval(function() { console.log('echo.js'); }, 50); setInterval(function() { console.error('echo.js-error'); }, 50); ================================================ FILE: test/fixtures/path-resolution/ecosystem.config.js ================================================ module.exports = { /** * Application configuration section * http://pm2.keymetrics.io/docs/usage/application-declaration/ */ apps : [ { name : "test", script : "./echo.js", out_file : '~/echo-out.log', error_file : '~/echo-err.log', pid_file : '~/echo-pid.log' } ] } ================================================ FILE: test/fixtures/path-resolution/ecosystem2.config.js ================================================ module.exports = { /** * Application configuration section * http://pm2.keymetrics.io/docs/usage/application-declaration/ */ apps : [ { name : "test", script : "./echo.js", out_file : 'echo-out.log', error_file : 'echo-err.log', pid_file : 'echo-pid.log' } ] } ================================================ FILE: test/fixtures/path-resolution/ecosystem3.config.js ================================================ module.exports = { /** * Application configuration section * http://pm2.keymetrics.io/docs/usage/application-declaration/ */ apps : [ { name : "test-cluster", script : "./echo.js", out_file : 'echo-out.log', error_file : 'echo-err.log', instances: 4 } ] } ================================================ FILE: test/fixtures/path1/iminpath1.js ================================================ console.log(process.cwd()); ================================================ FILE: test/fixtures/path1/path2/iminpath2.js ================================================ console.log(process.cwd()); ================================================ FILE: test/fixtures/pm2-dev/app.js ================================================ var http = require('http'); http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(0); process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 100); } }); ================================================ FILE: test/fixtures/pm2-dev/app.json ================================================ { "script": "app.js", "watch": ["server", "client"], "ignore_watch" : ["node_modules", "client/img"] } ================================================ FILE: test/fixtures/pm2-dev/exited_app.js ================================================ throw new Error('exit') ================================================ FILE: test/fixtures/pm2-ecosystem.json ================================================ { "pm2" : [{ "name" : "echo", "script" : "./echo.js" },{ "name" : "child", "script" : "./child.js", "instances" : "4", "error_file" : "./child-err.log", "out_file" : "./child-out.log" },{ "name" : "api-2", "script" : "./server.js" }] } ================================================ FILE: test/fixtures/probes.js ================================================ var pmx = require('@pm2/io'); var conf = pmx.init(); var http = require('http'); http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(8000); var value_to_inspect = 0; /** * .metric, .counter, .meter, .histogram are also available (cf doc) */ var val = pmx.metric({ name : 'test-probe', value : function() { return value_to_inspect; }, /** * Here we set a default value threshold, to receive a notification * These options can be overriden via Keymetrics or via pm2 * More: http://bit.ly/1O02aap */ alert : { mode : 'threshold', value : 20, msg : 'test-probe alert!', action : function(val) { // Besides the automatic alert sent via Keymetrics // You can also configure your own logic to do something console.log('Value has reached %d', val); } } }); setInterval(function() { // Then we can see that this value increase over the time in Keymetrics value_to_inspect++; }, 30); process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 500); } }); ================================================ FILE: test/fixtures/process.json ================================================ ================================================ FILE: test/fixtures/promise/empty-rejection.js ================================================ setTimeout(function() { Promise.reject(); }, 1000); setInterval(function() { }, 1000); ================================================ FILE: test/fixtures/promise/rejection.js ================================================ setTimeout(function() { Promise.reject(new Error('Errorla')); }, 1000); setInterval(function() { }, 1000); ================================================ FILE: test/fixtures/push.json ================================================ [{ "name" : "push", "script" : "echo.js", "max" : "1" }] ================================================ FILE: test/fixtures/python-script.py ================================================ import time if __name__ == "__main__": while 1: print('Script.py: alive') time.sleep(1) ================================================ FILE: test/fixtures/quit.js ================================================ console.log('HEY!'); setTimeout(function() { process.exit(1); }, 3000); ================================================ FILE: test/fixtures/send-data-process/return-data.js ================================================ process.on('message', function(packet) { if (packet.topic == 'process:msg') { process.send({ topic : 'process:msg', data : { success : true } }); } }); ================================================ FILE: test/fixtures/serve/404.html ================================================ your file doesnt exist believe me ================================================ FILE: test/fixtures/serve/ecosystem-serve.json ================================================ { "static": [ { "path": ".", "spa": true, "basic_auth": { "username": "user", "password": "pass" }, "port": 8081 } ] } ================================================ FILE: test/fixtures/serve/ecosystem.json ================================================ [{ "name" : "frontend", "script" : "serve", "env": { "PM2_SERVE_PORT": 8081, "PM2_SERVE_PATH": "." } }] ================================================ FILE: test/fixtures/serve/index.html ================================================ good shit right there ================================================ FILE: test/fixtures/serve/other.html ================================================ iam another file with 2 lines ================================================ FILE: test/fixtures/server.js ================================================ var http = require('http'); http.createServer(function(req, res) { res.writeHead(200); res.end("hello world\n"); }).listen(8020); ================================================ FILE: test/fixtures/signal-send.js ================================================ setInterval(function() { }, 1000); process.on('message', function (msg) { console.log(msg); }); ================================================ FILE: test/fixtures/signal.js ================================================ setInterval(function() { console.log('ok'); }, 1000); process.on('SIGUSR2', function () { console.log('SIGUSR2'); }); ================================================ FILE: test/fixtures/signals/delayed_send.js ================================================ var http = require('http'); setInterval(function() { // Do nothing to keep process alive }, 1000); http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(0); process.on('message', function (msg) { if (msg === 'shutdown') { console.log('shutdown message received but forbid exit'); } }); ================================================ FILE: test/fixtures/signals/delayed_sigint.js ================================================ var http = require('http'); setInterval(function() { // Do nothing to keep process alive }, 1000); http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(0); process.on('SIGINT', function () { console.log('SIGINT cb called but forbid exit'); }); ================================================ FILE: test/fixtures/sort/http.js ================================================ var http = require('http'); var app = http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }) var listener = app.listen(0, function() { console.log('Listening on port ' + listener.address().port); }); process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 100); } }); ================================================ FILE: test/fixtures/sort/other.js ================================================ var http = require('http'); var app = http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }) var listener = app.listen(0, function() { console.log('Listening on port ' + listener.address().port); }); process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 100); } }); ================================================ FILE: test/fixtures/source-map/main.js ================================================ "use strict"; var models_1 = require('./models'); console.log(models_1.UserMessage); setTimeout(function () { throw new Error('errr'); }, 1000); //# sourceMappingURL=main.js.map ================================================ FILE: test/fixtures/source-map/models.js ================================================ 'use strict'; var UserMessage = (function () { function UserMessage(payload) { var data = JSON.parse(payload); if (!data.name || !data.message) { throw new Error('Invalid message payload received: ' + payload); } this.data = data; } Object.defineProperty(UserMessage.prototype, "name", { get: function () { return this.data.name; }, enumerable: true, configurable: true }); Object.defineProperty(UserMessage.prototype, "message", { get: function () { return this.data.message; }, enumerable: true, configurable: true }); return UserMessage; }()); exports.UserMessage = UserMessage; //# sourceMappingURL=models.js.map ================================================ FILE: test/fixtures/start-app/ecosystem-bun.config.js ================================================ module.exports = { apps : [{ cmd: "bun -e 'setTimeout(function() { }, 100000); console.log(process.env.TEST)'", log: 'test-conf.log', merge_logs: true, env: { TEST: 'test_val' } }] }; ================================================ FILE: test/fixtures/start-app/ecosystem.config.js ================================================ module.exports = { apps : [{ cmd: "node -e 'setTimeout(function() { }, 100000); console.log(process.env.TEST)'", log: 'test-conf.log', merge_logs: true, env: { TEST: 'test_val' } }] }; ================================================ FILE: test/fixtures/start-app/http.js ================================================ var http = require('http'); var app = http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }) var listener = app.listen(0, function() { console.log('Listening on port ' + listener.address().port); }); process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 100); } }); ================================================ FILE: test/fixtures/start-consistency/child.js ================================================ require('@pm2/io').init({ http : true }); var http = require('http'); var server = http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(8000); ================================================ FILE: test/fixtures/start-consistency/child.json ================================================ { script:"./child.js", name :"child" } ================================================ FILE: test/fixtures/startProcessInsidePm2.js ================================================ var PM2 = require('../..'); /** * New gen API */ var pm2 = new PM2.custom(); //console.log(process.env); pm2.connect(function(err) { console.error(' ----------------------' ); pm2.start('./insidePm2Process.js', { name: 'insideProcess', 'output': './inside-out.log', merge_logs: true }, function(err, proc){ if(err){ console.log(err); return process.exit(1); } }); }); ================================================ FILE: test/fixtures/startProcessInsidePm2.json ================================================ [{ "name" : "masterProcess", "script" : "./startProcessInsidePm2.js", "exec_mode" : "fork_mode", "min_uptime" : "100", "max_restarts" : "50" }] ================================================ FILE: test/fixtures/stdin/stdin.js ================================================ const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.prompt(); rl.on('line', function(line) { console.log('Line %s received', line); }); setInterval(function() { }, 100); ================================================ FILE: test/fixtures/stdout-stderr.js ================================================ process.stdout.write('outwrite', 'utf8', function() { console.log('outcb'); }); process.stderr.write('errwrite', 'utf8', function() { console.log('errcb'); }); setInterval(function() { process.stdout.write('outwrite', 'utf8', function() { console.log('outcb'); }); process.stderr.write('errwrite', 'utf8', function() { console.log('errcb'); }); }, 1000) ================================================ FILE: test/fixtures/stop-exit-codes.json ================================================ [ { "name" : "stop_exit_codes_app", "script" : "./exitcode42.js", "stop_exit_codes" : [12, 42] } ] ================================================ FILE: test/fixtures/throw-later.js ================================================ var timer = setInterval(function(){ console.log('tick', Date.now()); }, 100); setTimeout(function(){ clearInterval(timer); throw new Error('error has been caught') }, 350); ================================================ FILE: test/fixtures/throw-later.json ================================================ { "apps" : [{ "name" : "throw-later", "script" : "throw-later.js", "error_file" : "err.log", "out_file" : "out.log", "log_file" : "entire.log", "merge_logs" : true }] } ================================================ FILE: test/fixtures/throw-later1.json ================================================ { "apps" : [{ "name" : "throw-later", "script" : "throw-later.js", "error_file" : "err.log", "out_file" : "out.log", "merge_logs" : true }] } ================================================ FILE: test/fixtures/throw-string.js ================================================ function crash() { throw new Error('crashed'); } crash(); ================================================ FILE: test/fixtures/throw.js ================================================ throw new Error('Exit error message'); ================================================ FILE: test/fixtures/toto.js ================================================ console.log(process.env.TOTO); ================================================ FILE: test/fixtures/wait_ready_event/http-wait-start.js ================================================ var http = require('http'); var app = http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }) var listener = app.listen(0, function() { console.log('Listening on port ' + listener.address().port); process.send('ready'); }); process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 100); } }); ================================================ FILE: test/fixtures/wait_ready_event/http-wait-start_nocb.js ================================================ var http = require('http'); var app = http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }) var listener = app.listen(0, function() { console.log('Listening on port ' + listener.address().port); }); process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 100); } }); ================================================ FILE: test/fixtures/watch/app-watch-rename.json ================================================ { apps: [{ script : 'http.js', name : 'http', watch : true }] } ================================================ FILE: test/fixtures/watch/app-watch.json ================================================ { apps: [{ script : 'http.js', name : 'http', watch : true }] } ================================================ FILE: test/fixtures/watch/app.json ================================================ { apps: [{ script : 'http.js', name : 'http', watch : false, env : { SHOULDBETHERE : 'toto' }, env_production : { SHOULDBETHERE : 'TYPEENV PRODUCTION' } }] } ================================================ FILE: test/fixtures/watch/http.js ================================================ var http = require('http'); var app = http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }) console.log(process.env.TOTO_ENV); var listener = app.listen(0, function() { console.log('Listening on port ' + listener.address().port); }); process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 100); } }); ================================================ FILE: test/fixtures/watcher/donotwatchme.dir/.gitkeep ================================================ ================================================ FILE: test/fixtures/watcher/server-watch.bak.js ================================================ var http = require('http'); http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(8010); ================================================ FILE: test/fixtures/watcher/server-watch.json ================================================ [{ "name" : "server-watch", "script" : "./server-watch.bak.js", "watch" : ["**"], "ignore_watch" : ["*.log"], "watch_options" : { "followSymlinks": true } }] ================================================ FILE: test/fixtures/yaml-configuration/apps.yaml ================================================ apps: - name: test script: echo.js - name: HTTP script: child.js instances: 4 - name: PythonApp script: echo.py interpreter: /usr/bin/python3 interpreter_args: -u env: DEFAULT_STR: 'PYTHONENV' ================================================ FILE: test/fixtures/yaml-configuration/apps.yml ================================================ apps: - name: test script: echo.js - name: HTTP script: child.js instances: 4 - name: PythonApp script: echo.py interpreter: /usr/bin/python3 interpreter_args: -u ================================================ FILE: test/fixtures/yaml-configuration/child.js ================================================ var http = require('http'); http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(8000); process.on('message', function(msg) { if (msg == 'shutdown') { console.log('Closing all connections...'); setTimeout(function() { console.log('Finished closing connections'); process.exit(0); }, 100); } }); ================================================ FILE: test/fixtures/yaml-configuration/echo.js ================================================ setInterval(function() { console.log('ok'); }, 100); setInterval(function() { console.error('thisnok'); }, 100); ================================================ FILE: test/fixtures/yaml-configuration/echo.py ================================================ #!/usr/bin/python import os import time while 1: try: print(os.environ['DEFAULT_STR']) except KeyError: print('RAWPython') time.sleep(1) ================================================ FILE: test/fixtures/yaml-configuration/malformated.yml ================================================ { "ASDSDASD": "ASDSADSAD" } ================================================ FILE: test/helpers/apps.js ================================================ var path = require('path'); var CLI = require('../..'); var APPS = {}; /** * Description * @method forkPM2 * @return pm2 */ APPS.forkPM2 = function(cb) { var pm2 = require('child_process').fork('lib/Satan.js', [], { env : process.env, silent : process.env.DEBUG ? false : true }); pm2.unref(); pm2.on('message', function() { return cb(null, pm2); }); }; APPS.startSomeApps = function(pm2, cb) { pm2.start({ script : './events/custom_action.js', name : 'custom-action' }, cb); }; /** * Description * @method launchApp * @param {} ipm2 * @param {} script * @param {} name * @param {} cb * @return */ APPS.launchApp = function(ipm2, script, name, cb) { ipm2.rpc.prepare({ pm_exec_path : path.resolve(process.cwd(), 'test/fixtures/' + script), pm_err_log_path : path.resolve(process.cwd(), 'test/' + name + 'err.log'), pm_out_log_path : path.resolve(process.cwd(), 'test/' + name + '.log'), pm_pid_path : path.resolve(process.cwd(), 'test/child'), exec_mode : 'cluster_mode', name : name }, cb); }; /** * Description * @method launchAppFork * @param {} ipm2 * @param {} script * @param {} name * @param {} cb * @return */ APPS.launchAppFork = function(ipm2, script, name, cb) { ipm2.rpc.prepare({ pm_exec_path : path.resolve(process.cwd(), 'test/fixtures/' + script), pm_err_log_path : path.resolve(process.cwd(), 'test/errLogasdasd.log'), pm_out_log_path : path.resolve(process.cwd(), 'test/outLogasdasd.log'), pm_pid_path : path.resolve(process.cwd(), 'test/child'), exec_mode : 'fork_mode', name : name }, cb); }; module.exports = APPS; ================================================ FILE: test/helpers/plan.js ================================================ var assert = require('assert'); /** * Description * @method Plan * @param {} count * @param {} done * @return */ function Plan(count, done) { this.done = done; this.count = count; } /** * Description * @method ok * @param {} expression * @return */ Plan.prototype.ok = function(expression) { assert(expression); if (this.count === 0) { assert(false, 'Too many assertions called'); } else { this.count--; } if (this.count === 0) { this.done(); } }; module.exports = Plan; ================================================ FILE: test/interface/README.md ================================================ ## Structure . ├── bus.fork.spec.mocha.js ├── bus.spec.mocha.js ├── custom-actions.mocha.js ├── fixtures │   ├── http_transaction.js │   ├── human_event.js │   ├── log_out.js │   └── process_exception.js ├── interactor.connect.mocha.js ├── interactor.connect.two.mocha.js ├── interactor.daemonizer.mocha.js ├── password.mocha.js ├── README.md ├── remote.mocha.js └── scoped_pm2_actions.mocha.js ### Test about pmx.notify not working - bus.fork.spec.mocha.js - bus.spec.mocha.js - bus.spec.mocha.js: event system sent (wo agent) - custom-actions.mocha.js: connect and try custom actions - interactor.daemonizer.mocha.js: basic methods testing for automatic re read of agent conf (wo agent) - interactor.connect.mocha.js: ================================================ FILE: test/interface/bus.fork.spec.mocha.js ================================================ var should = require('should'); var PM2 = require('../..'); var Plan = require('../helpers/plan.js'); var PROCESS_ARCH = Object.keys({ pm_id : 0, name : 'app' // server: 'server name' - attached in interactor }); var PROCESS_EVENT = Object.keys({ event : 'process event name', manually: true, process : PROCESS_ARCH, at : new Date() }); var LOG_EVENT = Object.keys({ data : 'string', process : PROCESS_ARCH, at : new Date() }); var ERROR_EVENT = Object.keys({ at : new Date(), data : { stack : '\n', message : 'error' }, process : PROCESS_ARCH }); var HUMAN_EVENT = Object.keys({ at : new Date(), process : PROCESS_ARCH, data : { __name : 'event:name' } }); var TRANSACTION_HTTP_EVENT = Object.keys({ data : { url : '/user/root', method : 'POST', time : 234, code : 200 }, at : new Date(), process : PROCESS_ARCH }); process.on('uncaughtException', function(e) { console.log(e.stack); process.exit(1); }); describe('PM2 BUS / RPC', function() { var pm2 = new PM2.custom({ cwd : __dirname + '/../fixtures/interface' }); var pm2_bus; after(function(done) { pm2.delete('all', () => done()); }); before(function(done) { pm2.connect(function() { pm2.launchBus(function(err, bus) { pm2_bus = bus; setTimeout(done, 1000); }); }); }); describe('Events', function() { afterEach(function(done) { pm2_bus.off('*'); pm2.delete('all', function(err, ret) { done(); }); }); it('should (process:event) when start process get online event and start event with right properties', function(done) { var plan = new Plan(2, done); pm2_bus.on('*', function(event, data) { if (event == 'process:event') { event.should.eql('process:event'); data.should.have.properties(PROCESS_EVENT); data.process.should.have.properties(PROCESS_ARCH); plan.ok(true); } }); pm2.start('./child.js', {}, function(err, data) { should(err).be.null(); }); }); it('should (log:out log:err)', function(done) { var plan = new Plan(2, done); pm2_bus.on('*', function(event, data) { if (event == 'log:out') { event.should.eql('log:out'); data.should.have.properties(LOG_EVENT); plan.ok(true); } if (event == 'log:err') { event.should.eql('log:err'); data.should.have.properties(LOG_EVENT); plan.ok(true); } }); pm2.start('./log_out.js', {}, function(err, data) { should(err).be.null(); }); }); it('should (process:exception)', function(done) { var plan = new Plan(1, done); pm2_bus.on('*', function(event, data) { if (event == 'process:exception') { data.should.have.properties(ERROR_EVENT); data.process.should.have.properties(PROCESS_ARCH); plan.ok('true'); } }); pm2.start('./process_exception.js', {}, function(err, data) { should(err).be.null(); }); }); it('should (human:event)', function(done) { pm2_bus.on('*', function(event, data) { if (event == 'human:event') { data.should.have.properties(HUMAN_EVENT); data.process.should.have.properties(PROCESS_ARCH); return done(); } }); pm2.start('./human_event.js', {}, function(err, data) { should(err).be.null(); }); }); it('should (process:exception) with promise', function(done) { var plan = new Plan(1, done); pm2_bus.on('*', function(event, data) { console.log(event) if (event == 'process:exception') { data.should.have.properties(ERROR_EVENT); data.process.should.have.properties(PROCESS_ARCH); plan.ok(true); } }); pm2.start('./promise_rejection.js', {}, function(err, data) { should(err).be.null(); }); }); }); }); ================================================ FILE: test/interface/bus.spec.mocha.js ================================================ var should = require('should'); var PM2 = require('../..'); var Plan = require('../helpers/plan.js'); const PATH_FIXTURES = process.cwd() + '/test/interface/fixtures/'; var PROCESS_ARCH = Object.keys({ pm_id : 0, name : 'app' // server: 'server name' - attached in interactor }); var PROCESS_EVENT = Object.keys({ event : 'process event name', manually: true, process : PROCESS_ARCH, at : new Date() }); var LOG_EVENT = Object.keys({ data : 'string', process : PROCESS_ARCH, at : new Date() }); var ERROR_EVENT = Object.keys({ at : new Date(), data : { stack : '\n', message : 'error' }, process : PROCESS_ARCH }); var HUMAN_EVENT = Object.keys({ at : new Date(), process : PROCESS_ARCH, data : { __name : 'event:name' } }); var TRANSACTION_HTTP_EVENT = Object.keys({ data : { url : '/user/root', method : 'POST', time : 234, code : 200 }, at : new Date(), process : PROCESS_ARCH }); process.on('uncaughtException', function(e) { console.log(e.stack); process.exit(1); }); describe('PM2 BUS / RPC', function() { var pm2 = new PM2.custom({ cwd : __dirname + '/../fixtures/interface' }); var pm2_bus; after(function(done) { pm2.delete('all', () => done()); }); before(function(done) { pm2.connect(function() { pm2.launchBus(function(err, bus) { pm2_bus = bus; }); done(); }); }); describe('Events', function() { afterEach(function(done) { pm2_bus.off('*'); pm2.delete('all', function(err, ret) { done(); }); }); it('should (process:event) when start process get online event and start event with right properties', function(done) { var plan = new Plan(2, done); pm2_bus.on('*', function(event, data) { if (event == 'process:event') { event.should.eql('process:event'); data.should.have.properties(PROCESS_EVENT); data.process.should.have.properties(PROCESS_ARCH); plan.ok(true); } }); pm2.start('./child.js', {instances : 1}, function(err, data) { should(err).be.null(); }); }); it('should (log:out log:err)', function(done) { var plan = new Plan(2, done); pm2_bus.on('*', function(event, data) { if (event == 'log:out') { event.should.eql('log:out'); data.should.have.properties(LOG_EVENT); plan.ok(true); } if (event == 'log:err') { event.should.eql('log:err'); data.should.have.properties(LOG_EVENT); plan.ok(true); } }); pm2.start('./log_out.js', {instances : 1}, function(err, data) { should(err).be.null(); }); }); it('should (process:exception)', function(done) { var plan = new Plan(1, done); var called = false pm2_bus.on('*', function(event, data) { if (event == 'process:exception') { if (called) return called = true data.should.have.properties(ERROR_EVENT); data.process.should.have.properties(PROCESS_ARCH); plan.ok(true); } }); pm2.start('./process_exception.js', {instances : 1}, function(err, data) { should(err).be.null(); }); }); it('should (process:exception) with promise', function(done) { var called = false pm2_bus.on('*', function(event, data) { if (event == 'process:exception') { if (called) return called = true data.should.have.properties(ERROR_EVENT); data.process.should.have.properties(PROCESS_ARCH); return done() } }); pm2.start('./promise_rejection.js', {instances: 1}, function(err, data) { should(err).be.null(); }); }); it('should (human:event)', function(done) { var called = false pm2_bus.on('*', function(event, data) { if (event == 'human:event') { if (called) return called = true data.should.have.properties(HUMAN_EVENT); data.process.should.have.properties(PROCESS_ARCH); return done(); } }); pm2.start('./human_event.js', {instances : 1}, function(err, data) { should(err).be.null(); }); }); }); }); ================================================ FILE: test/interface/mocha.opts ================================================ --timeout 25000 ================================================ FILE: test/interface/utility.mocha.js ================================================ var assert = require('assert'); var Utility = require('../../lib/Utility.js'); describe('Utility', function() { describe('.getCanonicModuleName', function () { it('should get null without invalid parameters', function() { assert(Utility.getCanonicModuleName() === null); assert(Utility.getCanonicModuleName(/aa/) === null); assert(Utility.getCanonicModuleName(111) === null); assert(Utility.getCanonicModuleName({}) === null); }); it('should works with all notation', function() { assert(Utility.getCanonicModuleName('ma-zal/pm2-slack') === 'pm2-slack'); assert(Utility.getCanonicModuleName('pm2-slack@1.0.0') === 'pm2-slack'); assert(Utility.getCanonicModuleName('pm2-slack-1.0.0.tgz') === 'pm2-slack'); assert(Utility.getCanonicModuleName('ma-zal/pm2-slack') === 'pm2-slack'); assert(Utility.getCanonicModuleName('ma-zal/pm2-slack#own-branch') === 'pm2-slack'); assert(Utility.getCanonicModuleName('pm2-slack') === 'pm2-slack'); assert(Utility.getCanonicModuleName('@org/pm2-slack') === '@org/pm2-slack'); assert(Utility.getCanonicModuleName('@org/pm2-slack@latest') === '@org/pm2-slack'); assert(Utility.getCanonicModuleName('git+https://github.com/user/pm2-slack') === 'pm2-slack'); assert(Utility.getCanonicModuleName('git+https://github.com/user/pm2-slack.git') === 'pm2-slack'); assert(Utility.getCanonicModuleName('file:///home/user/pm2-slack') === 'pm2-slack'); assert(Utility.getCanonicModuleName('file://./pm2-slack') === 'pm2-slack'); assert(Utility.getCanonicModuleName('file:///home/user/pm2-slack/') === 'pm2-slack'); assert(Utility.getCanonicModuleName('http-server') === 'http-server'); assert(Utility.getCanonicModuleName('http://registry.com:12/modules/my-module?test=true') === 'my-module'); assert(Utility.getCanonicModuleName('http://registry.com:12/modules/http-my-module?test=true') === 'http-my-module'); }); }); }); ================================================ FILE: test/parallel.js ================================================ const forEachLimit = require('async/forEachLimit') const fs = require('fs') const exec = require('child_process').exec const path = require('path') const chalk = require('ansis') const Table = require('cli-table-redemption'); const testFolder = './test/e2e/' const CONCURRENT_TEST = 3 const DOCKER_IMAGE_NAME = 'pm2-test' var timings = {}; function run(cmd, cb) { exec(cmd, function(err, stdout, stderr) { if (err) { console.log(`Retrying ${cmd}`) return exec(cmd, function(err, stdout, stderr) { if (err) return cb(stdout.split('\n')); return cb(null); }) } return cb(null) }) } function buildContainer(cb) { exec(`docker build -t ${DOCKER_IMAGE_NAME} -f test/Dockerfile .`, cb) } function listAllTest(cb) { var test_suite = [] fs.readdir(testFolder, (err, folders) => { forEachLimit(folders, 4, (folder, next) => { var fold = path.join(testFolder, folder) fs.readdir(fold, (err, files) => { if (err) return next() files.forEach((file) => { test_suite.push(path.join(fold, file)) }) next() }) }, function() { launchTestSuite(test_suite, cb) }) }) } function launchTestSuite(files, cb) { forEachLimit(files, CONCURRENT_TEST, function(file, next) { var cmd = `docker run -v ${path.resolve(__dirname, '..')}:/var/pm2 ${DOCKER_IMAGE_NAME} bash ${file}` console.log(chalk.bold(`Running test ${file}`)) timings[file] = new Date().getTime() run(cmd, function(err) { if (err) { // Display Error console.error(chalk.bold.red(`${'='.repeat(25)} Test File ${file} has failed ${'='.repeat(25)}`)) console.error(chalk.bold('Output (stderr):')) err.forEach(function(line) { console.error(line) }) console.error(chalk.bold.red(`${'='.repeat(80)}`)) return next(err) } timings[file] = new Date().getTime() - timings[file] console.log(chalk.bold.green(`✓ Test ${file} success`)) return next(); }) }, (err) => { if (err) { console.log('Test Suite has failed') cb(err) } console.log('Test Suite passed succesfully') cb() }) } buildContainer(function(err) { if (err) { console.error(err) process.exit(1) } console.log(`Container ${DOCKER_IMAGE_NAME} has been built`) return listAllTest(function(err) { var table = new Table({ head: ['Test', 'Duration'], style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true} }) Object.keys(timings).forEach(function(test) { table.push([test, timings[test]]) }) console.log(table.toString()); if (err) { return console.error(chalk.bold.red('Test suite failed')) } console.log(chalk.bold.blue('Test suite succeeded')) }) }) ================================================ FILE: test/pm2_check_dependencies.sh ================================================ #!/usr/bin/env bash SRC=$(cd $(dirname "$0"); pwd) # Abort script at first error set -e echo "Checking dependencies ...."; dependencies=("php" "nvm" "node" "python"); declare -A depCmd; depCmd=([nvm]="echo $NVM_DIR >/dev/null"); error=false; for i in "${dependencies[@]}"; do currentDepInstalled=true; if [ ${depCmd[$i]+_} ]; then eval ${depCmd[$i]} || { error=true; currentDepInstalled=false;} else command -v $i >/dev/null 2>&1 || { error=true; currentDepInstalled=false;} fi if [ "$currentDepInstalled" = true ]; then echo -e '\E[32m'"\033[1m[OK]\033[0m" $i; else echo -e '\E[31m'"\033[1m[KO]\033[0m" $i; fi done if [ "$error" = true ]; then echo "Aborting."; exit 1; fi ================================================ FILE: test/programmatic/api.backward.compatibility.mocha.js ================================================ process.chdir(__dirname); var PM2 = require('../..'); var should = require('should'); describe('API backward compatibility checks', function() { describe('Backward compatibility', function() { it('should start pm2 in no daemon mode', function(done) { PM2.connect(true, function(err) { should(PM2.daemon_mode).be.false(); should(PM2.Client.daemon_mode).be.false(); done(); }); }); it('should be able to start a script', function(done) { PM2.start('./../fixtures/child.js', function(err) { should(err).be.null(); done(); }); }); it('should list one process', function(done) { PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(1); done(); }); }); it('should kill PM2 in no daemon', function(done) { PM2.kill(done); }); }); }); ================================================ FILE: test/programmatic/api.mocha.js ================================================ process.chdir(__dirname); var PM2 = require('../..'); var should = require('should'); describe('API checks', function() { describe('PM2 API case#1', function() { before(function(done) { PM2.delete('all', function() { done() }); }); after(function(done) { PM2.kill(done); }); it('should instanciate a new pm2 with old api', function() { should.exists(PM2.pm2_home); should(PM2.daemon_mode).be.true(); PM2.cwd.should.eql(process.cwd()); should.exists(PM2.Client); }); it('should connect to PM2', function(done) { PM2.connect(done); }); it('should start a script', function(done) { PM2.start('./../fixtures/child.js', function(err) { should(err).be.null(); PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(1); done(); }); }); }); it('should stop app by id', function(done) { PM2.stop(0, done); }); it('should start app by id', function(done) { PM2.restart(0, done); }); it('should fail if starting same script again', function(done) { PM2.start('./../fixtures/child.js', function(err) { should(err).not.be.null(); PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(1); done(); }); }); }); it('should FORCE starting same script again', function(done) { PM2.start('./../fixtures/child.js', {force :true }, function(err) { should(err).be.null(); PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(2); done(); }); }); }); it('should delete ALL', function(done) { PM2.delete('all', function(err) { should(err).be.null(); PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(0); done(); }); }); }); }); describe('PM2 API case#2 (JSON style)', function() { before(function(done) { PM2.delete('all', function() { done() }); }); after(function(done) { PM2.kill(done); }); it('should start script in cluster mode, 4 instances', function(done) { PM2.start({ script : './../fixtures/child.js', instances : 4, name : 'http-test' }, function(err) { should(err).be.null(); PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(4); done(); }); }); }); it('should stop app', function(done) { PM2.stop('http-test', function(err, procs) { should(err).be.null(); procs.length.should.eql(4); PM2.list(function(err, list) { should(list.length).eql(4); list.forEach(function(proc) { proc.pm2_env.status.should.eql('stopped'); }); done(); }); }); }); it('should restart all apps', function(done) { PM2.restart('http-test', function(err, procs) { should(err).be.null(); PM2.list(function(err, list) { should(list.length).eql(4); list.forEach(function(proc) { proc.pm2_env.status.should.eql('online'); }); done(); }); }); }); }); describe('Should keep environment variables', function() { it('should start app with treekill', function(done) { PM2.start({ script : './../fixtures/child.js', instances : 1, treekill : false, name : 'http-test' }, function(err) { should(err).be.null(); PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(1); should(list[0].pm2_env.treekill).be.false; done(); }); }); }); it('should restart app and treekill still at false', function(done) { PM2.restart('http-test', function() { PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(1); should(list[0].pm2_env.treekill).be.false; done(); }); }); }); }); describe('Issue #2337', function() { before(function(done) { PM2.delete('all', function() { done() }); }); after(function(done) { PM2.kill(done); }); it('should start two app with same name', function(done) { PM2.start({ script : './../fixtures/child.js', instances : 2, exec_mode : 'fork', name : 'http-test' }, function(err) { should(err).be.null(); PM2.list(function(err, list) { should(err).be.null(); list.forEach(function(proc) { proc.pm2_env.exec_mode.should.eql('fork_mode'); }); should(list.length).eql(2); done(); }); }); }); it('should stop first app', function(done) { PM2.stop(0, done); }); it('should force start a 3rd script', function(done) { PM2.start('./../fixtures/child.js', { force : true, name : 'toto' }, function() { PM2.list(function(err, list) { list.length.should.eql(3); done(); }); }); }); }); describe('PM2 auto connect feature', function() { after(function(done) { PM2.kill(function() { done(); }); }); it('should instanciate a new pm2 with old api', function() { should.exists(PM2.pm2_home); should(PM2.daemon_mode).be.true(); PM2.cwd.should.eql(process.cwd()); should.exists(PM2.Client); }); it('should be able to start a script without connect', function(done) { PM2.start('./../fixtures/child.js', function(err) { should(err).be.null(); done(); }); }); it('should do random commands', function(done) { PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(1); PM2.delete('all', function(err) { should(err).be.null(); PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(0); done(); }); }); }); }); }); describe('Custom PM2 instance', function() { var pm2; after(function(done) { pm2.kill(done); }); it('should create new custom PM2 instance', function() { pm2 = new PM2.custom({ daemon_mode : true }); should.exists(pm2.pm2_home); should(pm2.daemon_mode).be.true(); pm2.cwd.should.eql(process.cwd()); should.exists(pm2.Client); }); it('should be able to start a script without connect', function(done) { pm2.start('./../fixtures/child.js', function(err) { should(err).be.null(); done(); }); }); it('should do random commands', function(done) { pm2.list(function(err, list) { should(err).be.null(); should(list.length).eql(1); pm2.delete('all', function(err) { should(err).be.null(); pm2.list(function(err, list) { should(err).be.null(); should(list.length).eql(0); done(); }); }); }); }); }); describe('Should start pm2 in do daemon mode', function() { var pm2; after(function(done) { pm2.kill(done); }); it('should create new custom PM2 instance', function() { pm2 = new PM2.custom({ daemon_mode : false }); should.exists(pm2.pm2_home); should(pm2.daemon_mode).be.false(); pm2.cwd.should.eql(process.cwd()); should.exists(pm2.Client); }); }); describe('Launch modules', function() { var Modularizer = require('../../lib/API/Modules/Modularizer'); var module = 'pm2-server-monit'; after(function(done) { Modularizer.uninstall(PM2, module, done); }); it('Should start up modules', function(done) { PM2.connect(true, function(err) { should(err).be.null(); Modularizer.install(PM2, module, function() { PM2.stop(module, function() { should(err).be.null(); PM2.launchModules(function(err) { should(err).be.null(); PM2.list(function(err, list) { should(err).be.null(); should(list[0].name).eql(module); should(list[0].pm2_env.status).eql('online'); done(); }); }); }); }); }); }); }); }); ================================================ FILE: test/programmatic/auto_restart.mocha.js ================================================ const pm2 = require('../..'); const should = require('should'); const path = require('path') describe('PM2 auto restart on uncaughtexception', function() { var test_path = path.join(__dirname, 'fixtures', 'auto-restart') after((done) => { pm2.delete('all', () => { done() }) }) before((done) => { pm2.uninstall('all', () => { pm2.delete('all', () => { done() }) }) }) it('should start a failing app in fork mode', function(done) { pm2.start({ script: path.join(test_path, 'throw.js'), }, (err, apps) => { setTimeout(function() { pm2.list((err, list) => { should(list[0].pm2_env.restart_time).aboveOrEqual(0) pm2.delete('throw', () => { done() }) }) }, 200) }) }) it('should start a failing app in cluster mode', function(done) { pm2.start({ script: path.join(test_path, 'throw.js'), instances: 2 }, (err, apps) => { setTimeout(function() { pm2.list((err, list) => { should(list[0].pm2_env.restart_time).aboveOrEqual(0) pm2.delete('throw', () => { done() }) }) }, 200) }) }) }) ================================================ FILE: test/programmatic/client.mocha.js ================================================ var should = require('should'); var Client = require('../../lib/Client'); describe('Client Daemon', function() { var client; it('should instanciate a new client', function() { client = new Client({ independent : true }); should.exist(client.rpc_socket_file); should.exist(client.pub_socket_file); should.exist(client.pm2_home); should.exist(client.daemon_mode); }); it('should start a deamon', function(done) { client.start(done); }); it('should launch bus system', function(done) { client.launchBus(done); }); it('should get exposed methods', function(done) { client.getExposedMethods(done); }); it('should execute a daemon function', function(done) { client.executeRemote('ping', {}, function(err, res) { res.msg.should.eql('pong'); done(err); }); }); it('should disconnwct bus', function(done) { client.disconnectBus(done); }); it('should kill daemon', function(done) { client.killDaemon(done); }); // It is the job of the CLI describe.skip('Custom PM2 Home location', function() { it('should instanciate a PM2 on another folder', function(done) { client = new Client({ pm2_home : '/tmp/test' }); should(client.pm2_home).eql('/tmp/test') client.start(done); }); it('should kill daemon', function(done) { client.killDaemon(done); }); }); }); ================================================ FILE: test/programmatic/cluster.mocha.js ================================================ process.env.NODE_ENV = 'test'; var PM2 = require('../..'); var should = require('should'); var path = require('path'); var Plan = require('../helpers/plan.js'); process.chdir(__dirname); describe('Cluster programmatic tests', function() { var pm2 = new PM2.custom({ cwd : '../fixtures' }); after(function(done) { pm2.kill(done) }); describe('Start with different instances number parameter', function() { afterEach(function(done) { pm2.delete('all', done); }); it('should start 4 processes', function(done) { pm2.start({ script : './child.js', instances : 4 }, function(err, data) { should(err).be.null(); pm2.list(function(err, ret) { should(err).be.null(); ret.length.should.eql(4); done(); }); }); }); }); describe('Action methods', function() { before(function(done) { pm2.start({ script : '../fixtures/child.js', instances : 4 }, done); }); it('should RESTART all apps', function(done) { pm2.restart('all', function(err, data) { should(err).be.null(); pm2.list(function(err, procs) { should(err).be.null(); procs.length.should.eql(4); procs.forEach(function(proc) { proc.pm2_env.restart_time.should.eql(1); }); done(); }); }); }); it('should RELOAD all apps', function(done) { pm2.reload('all', function(err, data) { should(err).be.null(); pm2.list(function(err, procs) { should(err).be.null(); procs.length.should.eql(4); procs.forEach(function(proc) { proc.pm2_env.restart_time.should.eql(2); }); done(); }); }); }); it('should GRACEFUL RELOAD all apps', function(done) { pm2.reload('all', function(err, data) { should(err).be.null(); pm2.list(function(err, procs) { should(err).be.null(); procs.length.should.eql(4); procs.forEach(function(proc) { proc.pm2_env.restart_time.should.eql(3); }); done(); }); }); }); }); describe('Scaling feature', function() { after(function(done) { pm2.delete('all', done); }); before(function(done) { pm2.delete('all', function() { pm2.start({ script : '../fixtures/child.js', instances : 4, name : 'child' }, done); }); }); it('should scale up application to 8', function(done) { pm2.scale('child', 8, function(err, procs) { should(err).be.null(); pm2.list(function(err, procs) { should(err).be.null(); procs.length.should.eql(8); done(); }); }); }); it('should scale down application to 2', function(done) { pm2.scale('child', 2, function(err, procs) { should(err).be.null(); pm2.list(function(err, procs) { should(err).be.null(); procs.length.should.eql(2); done(); }); }); }); it('should do nothing', function(done) { pm2.scale('child', 2, function(err, procs) { should(err).not.be.null(); done(); }); }); }); // Skip Becoz Bun describe.skip('Listen timeout feature', function() { after(function(done) { pm2.delete('all', done); }); it('should start script with 1000ms listen timeout', function(done) { pm2.start({ script : './child.js', listen_timeout : 1000, exec_mode: 'cluster', instances : 1, name : 'child' }, done); }); it('should have listen timeout updated', function(done) { pm2.list(function(err, list) { should(list[0].pm2_env.listen_timeout).eql(1000); should(list.length).eql(1); done(); }); }); it('should take listen_timeout into account', function(done) { var called = false; var plan = new Plan(3, done); setTimeout(function() { should(called).be.false(); plan.ok(true); }, 800); setTimeout(function() { should(called).be.true(); plan.ok(true); }, 2500); pm2.reload('all', function(err, data) { called = true; plan.ok(true); }); }); it('should restart script with different listen timeout', function(done) { pm2.restart({ script : './child.js', listen_timeout : 100, instances : 1, name : 'child' }, done); }); it('should have listen timeout updated', function(done) { pm2.list(function(err, list) { should(list[0].pm2_env.listen_timeout).eql(100); should(list.length).eql(1); done(); }); }); it('should be reloaded after 100ms', function(done) { var called = false; setTimeout(function() { should(called).be.true(); done(); }, 500); pm2.reload('all', function(err, data) { called = true; }); }); }); describe('Kill timeout feature', function() { after(function(done) { pm2.delete('all', done); }); it('should start script with 1000ms listen timeout', function(done) { pm2.start({ script : './cluster/sigint_catcher.js', kill_timeout : 1000, instances : 1, name : 'sigint' }, done); }); it('should have listen timeout updated', function(done) { pm2.list(function(err, list) { should(list[0].pm2_env.kill_timeout).eql(1000); should(list.length).eql(1); done(); }); }); it('should script not be killed before kill timeout', function(done) { var called = false; setTimeout(function() { should(called).be.false(); }, 800); pm2.reload('sigint', function() { called = true; done(); }); }); }); }); ================================================ FILE: test/programmatic/common.mocha.js ================================================ var Common = require('../../lib/Common'); var should = require('should'); process.chdir(__dirname); describe('Common utilities', function () { describe('Config file detection', function () { var tests = [ { arg: "ecosystem.json", expected: "json" }, { arg: "ecosystem.yml", expected: "yaml" }, { arg: "ecosystem.yaml", expected: "yaml" }, { arg: "ecosystem.config.js", expected: "js" }, { arg: "ecosystem.config.cjs", expected: "js" }, { arg: "ecosystem.config.mjs", expected: "mjs" }, ] tests.forEach(function (test) { it('should accept configuration file ' + test.arg , function () { var result = Common.isConfigFile(test.arg); should(result).eql(test.expected); }) }); it('should not accept unknown filename', function () { should(Common.isConfigFile('lorem-ipsum.js')).be.null(); }) }) describe('Config file candidates', function () { it('should return an array with well-known file extensions', function () { var result = Common.getConfigFileCandidates('ecosystem'); should(result).eql([ 'ecosystem.json', 'ecosystem.yml', 'ecosystem.yaml', 'ecosystem.config.js', 'ecosystem.config.cjs', 'ecosystem.config.mjs' ]); }); }); }) ================================================ FILE: test/programmatic/conf_update.mocha.js ================================================ const PM2 = require('../..'); const should = require('should'); process.chdir(__dirname); describe('Modules programmatic testing', function() { var pm2; after(function(done) { pm2.kill(done); }); it('should instanciate PM2', function() { pm2 = new PM2.custom({ cwd : '../fixtures' }); }); it('should start 4 processes', function(done) { pm2.start({ script : './echo.js', instances : 4, uid : process.env.USER, force : true }, function(err, procs) { should(err).eql(null); should(procs.length).eql(4); should(procs[0].pm2_env.uid).eql(process.env.USER); done(); }); }); it('should start 4 processes', function(done) { pm2.restart('echo', { uid : process.env.USER }, function(err, procs) { console.log(JSON.stringify(procs[0].pm2_env, '', 2)); should(err).eql(null); should(procs.length).eql(4); should(procs[0].pm2_env.uid).eql(process.env.USER); done(); }); }); }); ================================================ FILE: test/programmatic/configuration.mocha.js ================================================ var should = require('should'); var PM2 = require('../..'); var Configuration = require('../../lib/Configuration.js'); describe('Configuration via SET / GET tests', function() { before(function(done) { PM2.list(done); }); it('should set a value', function(done) { Configuration.set('key1', 'val1', function(err, data) { should.not.exists(err); done(); }); }); it('should get all values', function(done) { Configuration.getAll(function(err, data) { should.not.exists(err); data.key1.should.eql('val1'); done(); }); }); it('should set another value', function(done) { Configuration.set('key2', 'val2', function(err, data) { should.not.exists(err); done(); }); }); it('should get all values', function(done) { Configuration.getAll(function(err, data) { should.not.exists(err); data.key1.should.eql('val1'); data.key2.should.eql('val2'); done(); }); }); it('should unset first value', function(done) { Configuration.unset('key1', function(err, data) { should.not.exists(err); done(); }); }); it('should get all values', function(done) { Configuration.getAll(function(err, data) { should.not.exists(err); should.not.exists(data.key1); data.key2.should.eql('val2'); done(); }); }); it('should get all values SYNCHRONOUSLY', function() { var data = Configuration.getAllSync(); should.not.exists(data.key1); data.key2.should.eql('val2'); }); describe('Sub value system', function() { it('should set a sub key', function(done) { Configuration.set('module-name.var1', 'val1', function(err, data) { should.not.exists(err); done(); }); }); it('should set a second sub key', function(done) { Configuration.set('module-name.var2', 'val2', function(err, data) { should.not.exists(err); done(); }); }); it('should get the val', function(done) { Configuration.getAll(function(err, data) { should.not.exists(err); data['module-name']['var1'].should.eql('val1'); data['module-name']['var2'].should.eql('val2'); done(); }); }); it('should get the val with .get', function(done) { Configuration.get('module-name.var1', function(err, data) { should.not.exists(err); data.should.eql('val1'); done(); }); }); it('should get the val with .get', function(done) { Configuration.get('module-name.var2', function(err, data) { should.not.exists(err); data.should.eql('val2'); done(); }); }); it('should NOT get the val with .get', function(done) { Configuration.get('moduleasd-name.var2', function(err, data) { should.exists(err); should(data).be.null(); done(); }); }); it('should NOT get the val with .get', function(done) { Configuration.get('module-name.var3', function(err, data) { should.exists(err); should(data).be.null(); done(); }); }); }); describe('Sub value system with :', function() { it('should set a sub key', function(done) { Configuration.set('module-name2:var1', 'val1', function(err, data) { should.not.exists(err); done(); }); }); it('should set a second sub key', function(done) { Configuration.set('module-name2:var2', 'val2', function(err, data) { should.not.exists(err); done(); }); }); it('should get the val', function(done) { Configuration.getAll(function(err, data) { should.not.exists(err); data['module-name2']['var1'].should.eql('val1'); data['module-name2']['var2'].should.eql('val2'); done(); }); }); it('should unset the val', function(done) { Configuration.unset('module-name2:var2', function(err, data) { should.not.exists(err); data['module-name2']['var1'].should.eql('val1'); should.not.exists(data['module-name2']['var2']); done(); }); }); }); describe('Sync', function() { before(function() { Configuration.unsetSync('module-name2'); }); it('should have 0 modules listed', function(done) { var data = Configuration.getSync('module-name2'); should(data).be.null(); done(); }); it('should set a sub key', function(done) { var ret = Configuration.setSync('module-name2:var1', 'val1'); done(); }); it('should have one key', function(done) { var data = Configuration.getSync('module-name2'); data['var1'].should.eql('val1'); done(); }); it('should set a second sub key', function(done) { var ret = Configuration.setSync('module-name2:var2', 'val2'); done(); }); it('should get the val', function() { var data = Configuration.getSync('module-name2:var2'); data.should.eql('val2'); }); it('should get null for unknown val', function() { var data = Configuration.getSync('module-name2:var23333'); should(data).be.null(); }); }); describe('Not split what is inside double quotes', function() { it('should do it', function(done) { Configuration.set('module-name2:"var2:toto"', 'val2', function(err, data) { should.not.exists(err); done(); }); }); it('should get the val', function() { var data = Configuration.getSync('module-name2:"var2:toto"'); data.should.eql('val2'); }); it('should do it', function(done) { Configuration.set('module-name3."var45.toto"', 'val2', function(err, data) { should.not.exists(err); done(); }); }); it('should get the val', function() { var data = Configuration.getSync('module-name3."var45.toto"'); data.should.eql('val2'); }); }); describe('Multiset', function() { it('should mutliset configuration', function(done) { Configuration.multiset('module-name3."var45.toto" val2 k2 v2 k3 v3', function(err, data) { should.not.exists(err); done(); }); }); it('should get values', function(done) { var data = Configuration.getSync('module-name3."var45.toto"'); data.should.eql('val2'); data = Configuration.getSync('k2'); data.should.eql('v2'); data = Configuration.getSync('k3'); data.should.eql('v3'); done(); }); }); }); ================================================ FILE: test/programmatic/containerizer.mocha.js ================================================ var Containerizer = require('../../lib/API/Containerizer.js'); var path = require('path'); var fs = require('fs'); var should = require('should'); var Plan = require('../helpers/plan.js'); describe('Containerizer unit tests', function() { var fixture_path = path.join(__dirname, '../fixtures/containerizer'); var Dockerfile = path.join(fixture_path, 'Dockerfile'); var res_lines_dev = ['## DEVELOPMENT MODE', 'ENV NODE_ENV=development', 'CMD ["pm2-dev", "index.js", "--env", "development"]']; var res_lines_prod = ['## DISTRIBUTION MODE', 'ENV NODE_ENV=production', 'COPY . /var/app', 'CMD ["pm2-docker", "index.js", "--env", "production"]']; after(function(done) { fs.unlink(Dockerfile, done); }); it('should generate a dockerfile', function() { var has_meta = false; return Containerizer.generateDockerfile(Dockerfile, 'index.js', { mode : 'development' }) .then(function(meta) { meta.Dockerfile_path.should.eql(Dockerfile); fs.statSync(Dockerfile); var lines = meta.Dockerfile.split('\n'); lines.forEach(function(line, i) { if (line == '## DEVELOPMENT MODE') { has_meta = true; should(lines[i]).eql(res_lines_dev[0]); should(lines[i + 1]).eql(res_lines_dev[1]); should(lines[i + 2]).eql(res_lines_dev[2]); } }); should(has_meta).be.true(); }); }); it('should switch dockerfile to distribution', function() { return Containerizer.switchDockerFile(Dockerfile, 'index.js', { mode : 'distribution' }) .then(function(meta) { meta.Dockerfile_path.should.eql(Dockerfile); fs.statSync(Dockerfile); var lines = meta.Dockerfile.split('\n') lines.forEach(function(line, i) { if (line == '## DISTRIBUTION MODE') { should(lines[i]).eql(res_lines_prod[0]); should(lines[i + 1]).eql(res_lines_prod[1]); should(lines[i + 2]).eql(res_lines_prod[2]); should(lines[i + 3]).eql(res_lines_prod[3]); } }); }); }); it('should switch dockerfile to distribution (no touching it)', function() { return Containerizer.switchDockerFile(Dockerfile, 'index.js', { mode : 'distribution' }) .then(function(meta) { meta.Dockerfile_path.should.eql(Dockerfile); fs.statSync(Dockerfile); var lines = meta.Dockerfile.split('\n'); lines.forEach(function(line, i) { if (line == '## DISTRIBUTION MODE') { should(lines[i]).eql(res_lines_prod[0]); should(lines[i + 1]).eql(res_lines_prod[1]); should(lines[i + 2]).eql(res_lines_prod[2]); should(lines[i + 3]).eql(res_lines_prod[3]); } }); }); }); it('should switch dockerfile to development', function() { return Containerizer.switchDockerFile(Dockerfile, 'index.js', { mode : 'development' }) .then(function(meta) { meta.Dockerfile_path.should.eql(Dockerfile); fs.statSync(Dockerfile); var lines = meta.Dockerfile.split('\n'); lines.forEach(function(line, i) { if (line == '## DEVELOPMENT MODE') { should(lines[i]).eql(res_lines_dev[0]); should(lines[i + 1]).eql(res_lines_dev[1]); should(lines[i + 2]).eql(res_lines_dev[2]); } }); }); }); it('should switch dockerfile to development (no touching it)', function() { return Containerizer.switchDockerFile(Dockerfile, 'index.js', { mode : 'development' }) .then(function(meta) { meta.Dockerfile_path.should.eql(Dockerfile); fs.statSync(Dockerfile); var lines = meta.Dockerfile.split('\n'); lines.forEach(function(line, i) { if (line == '## DEVELOPMENT MODE') { should(lines[i]).eql(res_lines_dev[0]); should(lines[i + 1]).eql(res_lines_dev[1]); should(lines[i + 2]).eql(res_lines_dev[2]); } }); }); }); it('should switch dockerfile to distribution', function() { return Containerizer.switchDockerFile(Dockerfile, 'index.js', { mode : 'distribution' }) .then(function(meta) { meta.Dockerfile_path.should.eql(Dockerfile); fs.statSync(Dockerfile); var lines = meta.Dockerfile.split('\n') lines.forEach(function(line, i) { if (line == '## DISTRIBUTION MODE') { should(lines[i]).eql(res_lines_prod[0]); should(lines[i + 1]).eql(res_lines_prod[1]); should(lines[i + 2]).eql(res_lines_prod[2]); should(lines[i + 3]).eql(res_lines_prod[3]); } }); }); }); }); ================================================ FILE: test/programmatic/custom_action.mocha.js ================================================ process.chdir(__dirname); var pm2 = require('../..'); var should = require('should'); describe('Custom actions via CLI/API', function() { before(function(done) { pm2.delete('all', function() { done() }); }); after(function(done) { pm2.delete('all', function() { done() }); }); it('should start custom action script', function(done) { pm2.start('./../fixtures/custom_actions/index.js', function() { setTimeout(done, 1200); }); }); it('should trigger message by id', function(done) { pm2.trigger(0, 'ping', function(err, ret) { should(err).be.null(); should(ret.length).eql(1); should(ret[0].data.return.pong).eql('hehe'); done(); }); }); it('should trigger message by name', function(done) { pm2.trigger('index', 'ping', function(err, ret) { should(err).be.null(); should(ret.length).eql(1); should(ret[0].data.return.pong).eql('hehe'); done(); }); }); it('should handle unknown application', function(done) { pm2.trigger('indexxo', 'ping', function(err, ret) { should(err).not.be.null(); done(); }); }); it('should cannot trigger message if unknow id', function(done) { pm2.trigger(10, 'ping', function(err, ret) { should(err).not.be.null(); done(); }); }); it('should cannot trigger message if unknow action name', function(done) { pm2.trigger(0, 'XXXXXXXXXx', function(err, ret) { should(err).not.be.null(); done(); }); }); it('should delete all processes', function(done) { pm2.delete('all', done); }); it('should start app in cluster mode', function(done) { pm2.start({ script: './../fixtures/custom_actions/index.js', instances : '4' }, function() { setTimeout(done, 800); }); }); it('should trigger message by id', function(done) { pm2.trigger(0, 'ping', function(err, ret) { should(err).be.null(); should(ret.length).eql(1); should(ret[0].data.return.pong).eql('hehe'); done(); }); }); it('should trigger message by name', function(done) { pm2.trigger('index', 'ping', function(err, ret) { should(err).be.null(); should(ret.length).eql(4); should(ret[0].data.return.pong).eql('hehe'); done(); }); }); it('should trigger message with params by name', function(done) { pm2.trigger('index', 'param', 'shouldret', function(err, ret) { should(err).be.null(); should(ret.length).eql(4); should(ret[0].data.return).eql('shouldret'); should(ret[3].data.return).eql('shouldret'); done(); }); }); }); ================================================ FILE: test/programmatic/dump.mocha.js ================================================ //process.env.NODE_ENV ='test'; var PM2 = require('../..'); var should = require('should'); var path = require('path'); describe('PM2 programmatic calls', function() { var proc1 = null; var procs = []; var bus = null; var pm2 = new PM2.custom({ cwd : __dirname + '/../fixtures' }); after(function(done) { pm2.delete('all', function(err, ret) { // clean dump file pm2.clearDump(function(err) { pm2.kill(done); }); }); }); before(function(done) { pm2.connect(function() { pm2.launchBus(function(err, _bus) { bus = _bus; pm2.delete('all', function(err, ret) { done(); }); }); }); }); it('should start a script', function(done) { pm2.start({ script : './child.js', name : 'child', instances : 1 }, function(err, data) { proc1 = data[0]; should(err).be.null() done(); }); }); it('should save/dump all processes', function(done) { pm2.dump(function(err, ret) { should(err).be.null() done(); }); }); it('should delete processes', function(done) { pm2.delete('all', function(err, ret) { should(err).be.null() pm2.list(function(err, ret) { should(err).be.null() ret.length.should.eql(0); done(); }); }); }); it('should not save/dump if 0 processes', function(done) { pm2.dump(function(err, ret) { should(err).not.be.null() done(); }); }); it('should save/dump if 0 processes AND --FORCE', function(done) { pm2.dump(true, function(err, ret) { should(err).be.null() done(); }); }); it('should resurrect 0 processes', function(done) { pm2.resurrect(function(err, ret) { should(err).be.null() pm2.list(function(err, ret) { should(err).be.null() ret.length.should.eql(0); done(); }); }); }); }) ================================================ FILE: test/programmatic/env_switching.js ================================================ /** * PM2 programmatic API tests */ var PM2 = require('../..'); var should = require('should'); var path = require('path'); // Change to current folder process.chdir(__dirname); var json_declaration_simple = { script : './../fixtures/env-switching/child.js', name : 'child', // Default environment env : { NODE_ENV : 'normal' }, // Prod env_production : { NODE_ENV : 'production' }, // Test env_test : { NODE_ENV : 'test' } }; var json_declaration = { script : './../fixtures/env-switching/child.js', instances: '8', // Default environment env : { NODE_ENV : 'normal' }, // Prod env_production : { NODE_ENV : 'production' }, // Test env_test : { NODE_ENV : 'test' } }; describe('PM2 programmatic calls', function() { var proc1 = null; var procs = []; var bus = null; var pm2 = new PM2.custom({ }); after(function(done) { pm2.kill(done); }); before(function(done) { pm2.connect(function() { pm2.launchBus(function(err, _bus) { bus = _bus; pm2.delete('all', function(err, ret) { done(); }); }); }); }); it('should start a script in production env and NODE_ENV have right value', function(done) { pm2.start(json_declaration, { env : 'production' }, function(err, data) { proc1 = data[0]; should(err).be.null(); proc1.pm2_env['NODE_ENV'].should.eql('production'); done(); }); }); it('should start a script in production env and NODE_ENV have right value', function(done) { pm2.restart(json_declaration, { env : 'production' }, function(err, data) { proc1 = data[0]; should(err).be.null(); proc1.pm2_env.env['NODE_ENV'].should.eql('production'); done(); }); }); it('should restarted process stay stable', function(done) { setTimeout(function() { pm2.list(function(err, ret) { should(ret[0].pm2_env.restart_time).eql(1) done(); }); }, 1000) }); it('should delete all processes', function(done) { pm2.delete('all', function(err, ret) { done(); }); }); it('should start a script and NODE_ENV have right value', function(done) { pm2.start(json_declaration, function(err, data) { proc1 = data[0]; should(err).be.null(); proc1.pm2_env['NODE_ENV'].should.eql(json_declaration.env.NODE_ENV); done(); }); }); it('should on restart keep previous NODE_ENV value', function(done) { pm2.restart(json_declaration, { env : 'test' }, function(err, data) { should(err).be.null(); data[0].pm2_env.env['NODE_ENV'].should.eql(json_declaration.env_test.NODE_ENV); done(); }); }); it('should delete all processes', function(done) { pm2.delete('all', function(err, ret) { done(); }); }); // it('should start a script and NODE_ENV have right value', function(done) { // pm2.start(json_declaration_simple, function(err, data) { // proc1 = data[0]; // should(err).be.null; // proc1.pm2_env['NODE_ENV'].should.eql(json_declaration.env.NODE_ENV); // done(); // }); // }); }); ================================================ FILE: test/programmatic/exp_backoff_restart_delay.mocha.js ================================================ process.env.EXP_BACKOFF_RESET_TIMER = 500 process.env.PM2_WORKER_INTERVAL = 100 const PM2 = require('../..'); const should = require('should'); const exec = require('child_process').exec const path = require('path') describe('Exponential backoff feature', function() { var pm2 var test_path = path.join(__dirname, 'fixtures', 'exp-backoff') after(function(done) { pm2.delete('all', function() { pm2.kill(done); }) }); before(function(done) { pm2 = new PM2.custom({ cwd : test_path }); pm2.delete('all', () => done()) }) it('should set exponential backoff restart', (done) => { pm2.start({ script: path.join(test_path, 'throw-stable.js'), exp_backoff_restart_delay: 100 }, (err, apps) => { should(err).be.null() should(apps[0].pm2_env.exp_backoff_restart_delay).eql(100) done() }) }) it('should have set the prev_restart delay', (done) => { setTimeout(() => { pm2.list((err, procs) => { should(procs[0].pm2_env.prev_restart_delay).be.aboveOrEqual(100) done() }) }, 800) }) it('should have incremented the prev_restart delay', (done) => { setTimeout(() => { pm2.list((err, procs) => { should(procs[0].pm2_env.prev_restart_delay).be.above(100) done() }) }, 500) }) it('should reset prev_restart_delay if application has reach stable uptime', (done) => { setTimeout(() => { pm2.list((err, procs) => { should(procs[0].pm2_env.prev_restart_delay).be.eql(0) done() }) }, 3000) }) }) ================================================ FILE: test/programmatic/filter_env.mocha.js ================================================ //#4596 process.chdir(__dirname) process.env.SHOULD_NOT_BE_THERE = 'true' var PM2 = require('../..') var should = require('should') describe('API checks', function() { before(function(done) { PM2.delete('all', function() { done() }) }) after(function(done) { PM2.kill(done) }) afterEach(function(done) { PM2.delete('all', done) }) it('should start app and validate presence of env var', function(done) { PM2.start({ script: './../fixtures/echo.js' }, (err) => { should(err).be.null() PM2.list(function(err, list) { should(err).be.null() should(list.length).eql(1) should.exists(list[0].pm2_env.SHOULD_NOT_BE_THERE) done() }) }) }) it('should start app with filtered env wth array of env to be ignored', function(done) { PM2.start({ script: './../fixtures/echo.js', filter_env: ['SHOULD_NOT_BE_THERE'] }, (err) => { should(err).be.null() PM2.list(function(err, list) { should(err).be.null() should(list.length).eql(1) should.not.exists(list[0].pm2_env.SHOULD_NOT_BE_THERE) done() }) }) }) it('should start app with filtered env with string env name to be ignored', function(done) { PM2.start({ script: './../fixtures/echo.js', filter_env: 'SHOULD_NOT_BE_THERE' }, (err) => { should(err).be.null() PM2.list(function(err, list) { should(err).be.null() should(list.length).eql(1) should.not.exists(list[0].pm2_env.SHOULD_NOT_BE_THERE) done() }) }) }) it('should start app with filtered env at true to drop all local env', function(done) { PM2.start({ script: './../fixtures/echo.js', filter_env: true }, (err) => { should(err).be.null() PM2.list(function(err, list) { should(err).be.null() should(list.length).eql(1) should.not.exists(list[0].pm2_env.SHOULD_NOT_BE_THERE) done() }) }) }) }) ================================================ FILE: test/programmatic/fixtures/auto-restart/throw.js ================================================ setTimeout(() => { throw new Error('err') }, 50) ================================================ FILE: test/programmatic/fixtures/exp-backoff/throw-stable.js ================================================ if (parseInt(process.env.restart_time) === 5) { setInterval(function() { console.log('Im stable mamen') }, 1000) } else { throw new Error('Ugly error') } ================================================ FILE: test/programmatic/fixtures/exp-backoff/throw.js ================================================ throw new Error('Ugly error') ================================================ FILE: test/programmatic/fixtures/instances/echo.js ================================================ setInterval(function() { console.log('OK') }, 1000) ================================================ FILE: test/programmatic/fixtures/instances/http.js ================================================ var http = require('http'); var server = http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(8008, function() { console.log('App listening on port %d', server.address().port); }); ================================================ FILE: test/programmatic/fixtures/json-env-passing/echo.js ================================================ setInterval(function() { console.log(process.env.JSONTEST); }, 50); ================================================ FILE: test/programmatic/fixtures/json-env-passing/ecosystem.config.js ================================================ module.exports = { apps : [{ script: 'echo.js', env: { JSONTEST: { si: 'si' } }, env_production: { NODE_ENV: 'production' } }], deploy : { production : { user : 'node', host : '212.83.163.1', ref : 'origin/master', repo : 'git@github.com:repo.git', path : '/var/www/production', 'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env production' } } }; ================================================ FILE: test/programmatic/fixtures/tar-module/mono-app-module/README.md ================================================ To start http application in cluster mode: ```bash $ pm2 start ecosystem.config.js # OR $ pm2 start http.js -i max ``` ================================================ FILE: test/programmatic/fixtures/tar-module/mono-app-module/ecosystem.config.js ================================================ module.exports = { name: 'envision', apps : [{ name : 'clustered_http', script : './http.js', instances : 'max', exec_mode : 'cluster', env : { PORT : 8002 } }, { name : 'forked_app', script : './http.js', env : { PORT : 8001 } }] } ================================================ FILE: test/programmatic/fixtures/tar-module/mono-app-module/http.js ================================================ var http = require('http'); var server = http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(process.env.PORT || 8000, function() { console.log('App listening on port %d', server.address().port); }); ================================================ FILE: test/programmatic/fixtures/tar-module/mono-app-module/package.json ================================================ { "name": "mono-app-module", "version": "0.23.0", "pm2": [{ "name" : "mono_app", "script": "http.js", "env" : { "PORT" : "8008" } }] } ================================================ FILE: test/programmatic/fixtures/tar-module/multi-app-module/README.md ================================================ To start http application in cluster mode: ```bash $ pm2 start ecosystem.config.js # OR $ pm2 start http.js -i max ``` ================================================ FILE: test/programmatic/fixtures/tar-module/multi-app-module/ecosystem.config.js ================================================ module.exports = { name: 'envision', apps : [{ name : 'clustered_http', script : './http.js', instances : 'max', exec_mode : 'cluster', env : { PORT : 8002 } }, { name : 'forked_app', script : './http.js', env : { PORT : 8001 } }] } ================================================ FILE: test/programmatic/fixtures/tar-module/multi-app-module/http.js ================================================ var http = require('http'); var server = http.createServer(function(req, res) { res.writeHead(200); res.end('hey'); }).listen(process.env.PORT || 8000, function() { console.log('App listening on port %d', server.address().port); }); ================================================ FILE: test/programmatic/fixtures/tar-module/multi-app-module/package.json ================================================ { "name": "multi-app-module", "version": "0.1", "pm2": [{ "name" : "first_app", "script": "http.js", "env" : { "PORT" : "8005" } }, { "name" : "second_app", "script" : "./http.js", "env" : { "PORT" : "8002" } }] } ================================================ FILE: test/programmatic/fixtures/version-test/index.js ================================================ setInterval(function() { console.log('HEY') }, 1000) ================================================ FILE: test/programmatic/fixtures/version-test/package.json ================================================ {"name":"version-module","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"author":"","license":"ISC"} ================================================ FILE: test/programmatic/flagExt.mocha.js ================================================ var should = require('should'); var f_e = require('../../lib/API/Modules/flagExt.js'); var fs = require('fs'); describe('Flag -ext', function() { var opts = {}; var res = []; opts.ext = 'js,json'; it('should return not empty result', function() { f_e.make_available_extension(opts, res); should(res).be.not.empty(); }); it('should not crash', function() { f_e.make_available_extension(); f_e.make_available_extension(res); f_e.make_available_extension(opts); }); it('should give different results', function() { var tmp_res = []; f_e.make_available_extension(opts, res); opts.ext = 'sh,py'; f_e.make_available_extension(opts, tmp_res); should(res).not.equal(tmp_res); }); it('should not crash in case, when no access for file or directory by permissions', function() { var dir = fs.mkdirSync("noAccessDir", 0777); opts.ext = 'txt' var fileStream = fs.createWriteStream("noAccessDir/checkPermissions.txt"); fileStream.write("It's a temporary file for testing flag --ext in PM2"); fileStream.end(); fs.chmodSync('noAccessDir/checkPermissions.txt', 0000); fs.chmodSync('noAccessDir', 0000); f_e.make_available_extension(opts, []); f_e.make_available_extension(opts, []); fs.chmodSync('noAccessDir', 0777); fs.chmodSync('noAccessDir/checkPermissions.txt', 0777); fs.unlinkSync('noAccessDir/checkPermissions.txt'); fs.rmdirSync('noAccessDir/'); }); }); ================================================ FILE: test/programmatic/flush.mocha.js ================================================ process.env.NODE_ENV = 'test'; var PM2 = require('../..'); var should = require('should'); var fs = require('fs'); var path = require('path'); describe('Programmatic flush feature test', function() { var proc1 = null; var procs = []; var pm2 = new PM2.custom({ cwd : __dirname + '/../fixtures' }); before(function(done) { pm2.delete('all', function() { done(); }); }); after(function(done) { pm2.disconnect(done); }); afterEach(function(done) { pm2.delete('all', done); }); describe('Flush test', function() { it('flush all logs', function(done) { pm2.start({ script: './echo.js', error_file : 'error-echo.log', out_file : 'out-echo.log', merge_logs: false }, function(err, procs) { should(err).be.null(); var out_file = procs[0].pm2_env.pm_out_log_path; var err_file = procs[0].pm2_env.pm_err_log_path; out_file.should.containEql('out-echo-0.log'); err_file.should.containEql('error-echo-0.log'); pm2.flush(undefined, function(){ fs.readFileSync(out_file, "utf8").should.be.empty(); fs.readFileSync(err_file, "utf8").should.be.empty(); done(); }); }); }); it('flush only echo logs', function(done) { pm2.start({ script: './echo.js', error_file : 'error-echo.log', out_file : 'out-echo.log', merge_logs: false }, function(err, procs) { should(err).be.null(); var out_file = procs[0].pm2_env.pm_out_log_path; var err_file = procs[0].pm2_env.pm_err_log_path; pm2.start({ script: './001-test.js', error_file : 'error-001-test.log', out_file : 'out-001-test.log', merge_logs: false }, function(err, procs, $out_file, $err_file) { should(err).be.null(); var out_file1 = procs[0].pm2_env.pm_out_log_path; var err_file1 = procs[0].pm2_env.pm_err_log_path; pm2.flush('echo', function(){ fs.readFileSync(out_file, "utf8").toString().should.be.empty(); fs.readFileSync(err_file, "utf8").toString().should.be.empty(); fs.readFileSync(out_file1, "utf8").toString().should.not.be.empty(); fs.readFileSync(err_file1, "utf8").toString().should.not.be.empty(); done(); }); }); }); }); }); }); ================================================ FILE: test/programmatic/god.mocha.js ================================================ var PM2 = require('../..'); var God = require('../../lib/God'); var numCPUs = require('os').cpus().length; var fs = require('fs'); var path = require('path'); var should = require('should'); var Common = require('../../lib/Common'); var eachLimit = require('async/eachLimit'); var cst = require('../../constants.js'); // Change to current folder process.chdir(__dirname); var pm2= new PM2.custom(); /** * Description * @method getConf * @return AssignmentExpression */ function getConf() { var a = Common.prepareAppConf({ cwd : process.cwd() }, { script : '../fixtures/echo.js', name : 'echo', instances : 2 }); return a; } function getConf2() { return Common.prepareAppConf({ cwd : process.cwd() }, { script : '../fixtures/echo2.js', instances : 4, exec_mode : 'cluster_mode', name : 'child' }); } function getConf3() { return Common.prepareAppConf({ cwd : process.cwd() }, { script : '../fixtures/echo3.js', instances : 10, exec_mode : 'cluster_mode', name : 'child' }); } function getConf4() { return Common.prepareAppConf({ cwd : process.cwd() }, { script : '../fixtures/args.js', args : ['-d', '-a'], instances : '1', name : 'child' }); } function deleteAll(data, cb) { var processes = God.getFormatedProcesses(); eachLimit(processes, cst.CONCURRENT_ACTIONS, function(proc, next) { console.log('Deleting process %s', proc.pm2_env.pm_id); God.deleteProcessId(proc.pm2_env.pm_id, function() { return next(); }); return false; }, function(err) { if (err) return cb(God.logAndGenerateError(err), {}); God.clusters_db = null; God.clusters_db = {}; return cb(null, []); }); } describe('God', function() { before(function(done) { pm2.connect(function() { deleteAll({}, function(err, dt) { done() }); }); }); it('should have right properties', function() { God.should.have.property('configuration'); God.should.have.property('prepare'); God.should.have.property('ping'); God.should.have.property('dumpProcessList'); God.should.have.property('getProcesses'); God.should.have.property('getMonitorData'); God.should.have.property('getFormatedProcesses'); God.should.have.property('checkProcess'); God.should.have.property('reloadLogs'); God.should.have.property('stopProcessId'); God.should.have.property('sendSignalToProcessId'); God.should.have.property('sendSignalToProcessName'); }); describe('One process', function() { var proc, pid; it('should fork one process', function(done) { God.prepare(getConf(), function(err, procs) { should(err).be.null(); procs[0].pm2_env.status.should.be.equal('online'); var a = God.getFormatedProcesses() God.getFormatedProcesses().length.should.equal(2); done(); }); }); }); describe('Process State Machine', function() { var clu, pid; before(function(done) { deleteAll({}, function(err, dt) { done(); }); }); it('should start a process', function(done) { God.prepare(getConf(), function(err, procs) { clu = procs[0]; pid = clu.pid; procs[0].pm2_env.status.should.be.equal('online'); done(); }); }); it('should stop a process and keep in database on state stopped', function(done) { God.stopProcessId(clu.pm2_env.pm_id, function(err, proc) { proc.pm2_env.status.should.be.equal('stopped'); God.checkProcess(proc.pid).should.be.equal(false); done(); }); }); it('should restart the same process and set it as state online and be up', function(done) { God.restartProcessId({id:clu.pm2_env.pm_id}, function(err, dt) { var proc = God.findProcessById(clu.pm2_env.pm_id); proc.pm2_env.status.should.be.equal('online'); God.checkProcess(proc.process.pid).should.be.equal(true); done(); }); }); it('should stop and delete a process id', function(done) { var old_pid = clu.pid; God.deleteProcessId(clu.pm2_env.pm_id, function(err, dt) { var proc = God.findProcessById(clu.pm2_env.pm_id); God.checkProcess(old_pid).should.be.equal(false); God.getFormatedProcesses().length.should.be.equal(1); done(); }); }); }); describe('Reload - cluster', function() { before(function(done) { deleteAll({}, function(err, dt) { done(); }); }); it('should launch app', function(done) { God.prepare(getConf2(), function(err, procs) { var processes = God.getFormatedProcesses(); setTimeout(function() { processes.length.should.equal(4); processes.forEach(function(proc) { proc.pm2_env.restart_time.should.eql(0); }); done(); }, 100); }); }); }); describe('Multi launching', function() { before(function(done) { deleteAll({}, function(err, dt) { done(); }); }); afterEach(function(done) { deleteAll({}, function(err, dt) { done(); }); }); it('should launch multiple processes depending on CPUs available', function(done) { God.prepare(Common.prepareAppConf({cwd : process.cwd() }, { script : '../fixtures/echo.js', name : 'child', instances:3 }), function(err, procs) { God.getFormatedProcesses().length.should.equal(3); procs.length.should.equal(3); done(); }); }); it('should start maximum processes depending on CPU numbers', function(done) { God.prepare(getConf3(), function(err, procs) { God.getFormatedProcesses().length.should.equal(10); procs.length.should.equal(10); done(); }); }); it('should dump process list', function(done) { God.prepare(Common.prepareAppConf({cwd : process.cwd() }, { script : '../fixtures/echo.js', name : 'child', instances : 3 }), function(err, procs) { God.getFormatedProcesses().length.should.equal(3); procs.length.should.equal(3); God.dumpProcessList(function(err) { should(err).be.null(); var apps = fs.readFileSync(cst.DUMP_FILE_PATH); apps = JSON.parse(apps); apps.length.should.equal(3); done(); }); }); }); it('should handle arguments', function(done) { God.prepare(getConf4(), function(err, procs) { setTimeout(function() { God.getFormatedProcesses()[0].pm2_env.restart_time.should.eql(0); done(); }, 500); }); }); }); it('should report pm2 version', function(done) { God.getVersion({}, function(err, version) { version.should.not.be.null(); done(); }); }); it('should get monitor data', function(done) { var f = require('child_process').fork('../fixtures/echo.js') var processes = [ // stopped status { pm2_env: {status: cst.STOPPED_STATUS} }, // axm pid { pm2_env: { status: cst.ONLINE_STATUS, axm_options: {pid: process.pid} } }, // axm pid is NaN { pm2_env: { status: cst.ONLINE_STATUS, axm_options: {pid: 'notanumber'} } }, { pm2_env: { status: cst.ONLINE_STATUS }, pid: f.pid } ] // mock var g = { getFormatedProcesses: function() { return processes } } require('../../lib/God/ActionMethods.js')(g) g.getMonitorData({}, function(err, procs) { should(err).be.null(); procs.length.should.be.equal(processes.length); procs[0].monit.should.be.deepEqual({memory: 0, cpu: 0}); procs[1].monit.memory.should.be.greaterThan(0); procs[2].monit.should.be.deepEqual({memory: 0, cpu: 0}); procs[3].monit.memory.should.be.greaterThan(0); f.kill() done() }) }); }); ================================================ FILE: test/programmatic/graceful.mocha.js ================================================ process.env.NODE_ENV = 'test'; var PM2 = require('../..'); var should = require('should'); var path = require('path'); var Plan = require('../helpers/plan.js'); var sexec = require('../../lib/tools/sexec.js') process.chdir(__dirname); describe('Wait ready / Graceful start / restart', function() { this.retries(2) var pm2 = new PM2.custom({ cwd : '../fixtures/listen-timeout/', }); before(function(done) { pm2.delete('all', function() { done() }); }); describe('(FORK) Listen timeout feature', function() { this.timeout(10000); after(function(done) { pm2.delete('all', done); }); it('should force script to set as ready after forced listen_timeout', function(done) { pm2.start({ script : './wait-ready.js', listen_timeout : 1000, wait_ready : true, name : 'echo' }); setTimeout(function() { pm2.list(function(err, apps) { should(apps[0].pm2_env.status).eql('launching'); }); }, 800); setTimeout(function() { pm2.list(function(err, apps) { should(apps[0].pm2_env.status).eql('online'); done(); }) }, 1500); }); it('should have listen timeout updated', function(done) { pm2.list(function(err, list) { should(list[0].pm2_env.wait_ready).eql(true); done(); }); }); it('should take listen timeout into account', function(done) { var called = false; var plan = new Plan(4, done); setTimeout(function() { should(called).be.false(); plan.ok(true); }, 300); setTimeout(function() { should(called).be.true(); plan.ok(true); pm2.list((err, apps) => { should(apps[0].pm2_env.wait_ready).eql(true) plan.ok(true) }) }, 1500); pm2.reload('all', function(err, data) { called = true; plan.ok(true); }); }); it('should restart script with different listen timeout', function(done) { pm2.restart({ script : './echo.js', listen_timeout : 100, instances : 1, name : 'echo' }, done); }); it('should have listen timeout updated', function(done) { pm2.list(function(err, list) { should(list[0].pm2_env.listen_timeout).eql(100); should(list.length).eql(1); done(); }); }); it('should be reloaded after 100ms', function(done) { var called = false; setTimeout(function() { should(called).be.true(); done(); }, 500); pm2.reload('all', function(err, data) { called = true; }); }); }); describe('(CLUSTER) Listen timeout feature', function() { this.timeout(10000); after(function(done) { pm2.delete('all', done); }); it('should force script to set as ready after forced listen_timeout', function(done) { pm2.start({ script : './wait-ready.js', listen_timeout : 1000, wait_ready : true, instances : 1, exec_mode: 'cluster', name : 'http' }); setTimeout(function() { pm2.list(function(err, apps) { should(apps[0].pm2_env.status).eql('launching'); }); }, 500); setTimeout(function() { pm2.list(function(err, apps) { should(apps[0].pm2_env.status).eql('online'); done(); }) }, 1500); }); it('should take listen timeout into account', function(done) { var called = false; var plan = new Plan(4, done); setTimeout(function() { should(called).be.false(); plan.ok(true); }, 500); setTimeout(function() { should(called).be.true(); plan.ok(true); pm2.list((err, apps) => { should(apps[0].pm2_env.wait_ready).eql(true) plan.ok(true) }) }, 1500); pm2.reload('all', function(err, data) { called = true; plan.ok(true); }); }); }); describe('(Cluster): Wait ready feature', function () { this.timeout(10000); after(function(done) { pm2.delete('all', done); }); it('Should send SIGINT right after ready and not wait for listen timeout', function(done) { const plan = new Plan(2, done); pm2.start({ script : './wait-ready.js', listen_timeout : 5000, wait_ready : true, instances : 1, exec_mode : 'cluster', name : 'echo' }, (error, result) => { if (error) { return done(error); } const oldPid = result[0].process.pid; plan.ok(typeof oldPid !== 'undefined'); pm2.reload('echo', {}, done); setTimeout(function() { sexec(`ps -eo pid | grep -w ${oldPid}`, (err, res) => { plan.ok(err === 1); }) }, 2000); }); }); }); }); ================================================ FILE: test/programmatic/id.mocha.js ================================================ process.chdir(__dirname); var PM2 = require('../..'); var should = require('should'); var assert = require('assert') describe('Unique ID verification', function() { describe('when starting', function() { var _id = null before(function(done) { PM2.delete('all', function() { done() }); }); after(function(done) { PM2.delete('all', function() { done() }); }); it('should start a script', function(done) { PM2.start('../fixtures/child.js', function(err) { should(err).be.null(); PM2.list(function(err, list) { should(err).be.null(); assert(list.length > 0) assert(typeof list[0].pm2_env.unique_id === 'string') _id = list[0].pm2_env.unique_id done(); }); }); }); it('should stop app by id', function(done) { PM2.stop(0, done); }); it('should restart and not changed unique id', function(done) { PM2.restart(0, (err) => { should(err).be.null(); PM2.list(function(err, list) { should(err).be.null(); assert(list.length > 0) assert(typeof list[0].pm2_env.unique_id === 'string') assert( list[0].pm2_env.unique_id === _id) done(); }); }); }); it('should generate another unique id for new process', function(done) { PM2.start('./../fixtures/child.js', { name: 'toto' }, function(err) { assert(!err); PM2.list(function(err, list) { should(err).be.null(); assert(list.length === 2) assert(typeof list[0].pm2_env.unique_id === 'string') assert(typeof list[1].pm2_env.unique_id === 'string') assert(list[0].pm2_env.unique_id !== typeof list[1].pm2_env.unique_id) done(); }); }); }); it('should duplicate a process and have a new id', function(done) { PM2.scale('child', 2, function(err) { assert(!err); PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(3); assert(typeof list[0].pm2_env.unique_id === 'string') assert(typeof list[1].pm2_env.unique_id === 'string') assert(typeof list[2].pm2_env.unique_id === 'string') assert(list[0].pm2_env.unique_id !== typeof list[1].pm2_env.unique_id) assert(list[1].pm2_env.unique_id !== typeof list[2].pm2_env.unique_id) assert(list[0].pm2_env.unique_id !== typeof list[2].pm2_env.unique_id) done(); }); }); }); }); }); ================================================ FILE: test/programmatic/inside.mocha.js ================================================ var PM2 = require('../..'); var should = require('should'); describe('Call PM2 inside PM2', function() { var pm2 = new PM2.custom({ cwd : __dirname + '/../fixtures/inside' }); after(function(done) { pm2.kill(function(){ done(); }); }); it('should start script that starts a script', function(done) { pm2.start('start_inside.js', function(err) { should(err).be.null(); setTimeout(done, 1500); }); }); it('should list 2 processes and apps stabilized', function(done) { pm2.list(function(err, list) { should(err).be.null(); should(list.length).eql(2); list.forEach(function(proc) { should(proc.pm2_env.restart_time).eql(0); should(proc.pm2_env.status).eql('online'); }); done(); }); }); it('should start script that restart script', function(done) { pm2.start('restart_inside.js', function(err) { should(err).be.null(); setTimeout(done, 1500); }); }); it('should list 3 processes and apps stabilized', function(done) { pm2.list(function(err, list) { should(err).be.null(); should(list.length).eql(3); list.forEach(function(proc) { if (proc.name == 'echo') { should(proc.pm2_env.restart_time).eql(1); should(proc.pm2_env.status).eql('online'); } else { should(proc.pm2_env.restart_time).eql(0); should(proc.pm2_env.status).eql('online'); } }); done(); }); }); it('should start script that reload script', function(done) { pm2.start('reload_inside.js', function(err) { should(err).be.null(); setTimeout(done, 1500); }); }); it('should list 4 processes and apps stabilized', function(done) { pm2.list(function(err, list) { should(err).be.null(); should(list.length).eql(4); list.forEach(function(proc) { if (proc.name == 'echo') { should(proc.pm2_env.restart_time).eql(2); should(proc.pm2_env.status).eql('online'); } else { should(proc.pm2_env.restart_time).eql(0); should(proc.pm2_env.status).eql('online'); } }); done(); }); }); }); ================================================ FILE: test/programmatic/instances.mocha.js ================================================ const pm2 = require('../..'); const should = require('should'); const path = require('path') const os = require('os') describe('PM2 instances max bound test', function() { var test_path = path.join(__dirname, 'fixtures', 'instances') after((done) => { pm2.delete('all', () => { done() }) }) before((done) => { pm2.uninstall('all', () => { pm2.delete('all', () => { done() }) }) }) it('should start maximum number of instances in cluster mode', (done) => { pm2.start({ script: path.join(test_path, 'http.js'), instances: 'max' }, function(err, apps) { should(apps.length).eql(os.cpus().length) should(apps[0].pm2_env.exec_mode).eql('cluster_mode') if (apps.length > 1) should(apps[1].pm2_env.exec_mode).eql('cluster_mode') done() }) }) it('should app be in stable mode', (done) => { setTimeout(function() { pm2.list(function(err, apps) { should(apps[0].pm2_env.restart_time).eql(0) if (apps.length > 1) should(apps[1].pm2_env.restart_time).eql(0) done() }) }, 1000) }) it('should delete all', (done) => { pm2.delete('all', function() { done() }) }) it('should start maximum number of instances in cluster mode', (done) => { pm2.start({ script: path.join(test_path, 'http.js'), instances: 0 }, function(err, apps) { should(apps.length).eql(os.cpus().length) should(apps[0].pm2_env.exec_mode).eql('cluster_mode') if (apps.length > 1) should(apps[1].pm2_env.exec_mode).eql('cluster_mode') done() }) }) it('should delete all', (done) => { pm2.delete('all', function() { done() }) }) it('should start 4 instances in cluster mode', (done) => { pm2.start({ script: path.join(test_path, 'http.js'), instances: 4 }, function(err, apps) { should(apps.length).eql(4) should(apps[0].pm2_env.exec_mode).eql('cluster_mode') should(apps[1].pm2_env.exec_mode).eql('cluster_mode') done() }) }) it('should start maximum number of instances in fork mode', (done) => { pm2.start({ script: path.join(test_path, 'echo.js'), exec_mode: 'fork', instances: 'max' }, function(err, apps) { should(apps.length).eql(os.cpus().length) should(apps[0].pm2_env.exec_mode).eql('fork_mode') if (apps.length > 1) should(apps[1].pm2_env.exec_mode).eql('fork_mode') done() }) }) it('should app be in stable mode', (done) => { setTimeout(function() { pm2.list(function(err, apps) { should(apps[0].pm2_env.restart_time).eql(0) if (apps.length > 1) should(apps[1].pm2_env.restart_time).eql(0) done() }) }, 1000) }) }) ================================================ FILE: test/programmatic/internal_config.mocha.js ================================================ const PM2 = require('../..'); const should = require('should'); describe('Internal PM2 configuration', function() { var pm2 before(function() { pm2 = new PM2.custom(); }) it('should set pm2:registry', function(done) { pm2.set('pm2:registry', 'http://thing.com', done) }) it('should new instance have the configuration', function() { var pm3 = new PM2.custom(); pm3.connect(() => { should(pm2.user_conf.registry).eql('http://thing.com') }) }) }) ================================================ FILE: test/programmatic/issues/json_env_passing_4080.mocha.js ================================================ var PM2 = require('../../..'); var should = require('should'); describe('Programmatic log feature test', function() { var proc1 = null; var procs = []; var pm2 = new PM2.custom({ cwd : __dirname + '/../fixtures/json-env-passing' }); before(function(done) { pm2.delete('all', function() { done(); }); }); after(function(done) { pm2.delete('all', function() { pm2.disconnect(done); }); }); it('should start a process with object as environment variable', function(done) { pm2.start({ script: 'echo.js', env: { NORMAL: 'STR', JSONTEST: { si: 'si' } }, env_production: { NODE_ENV: 'production' } }, function(err, procs) { should(err).be.null() should(procs.length).eql(1) done() }) }) it('should retrieve environment variable stringified', function(done) { pm2.list((err, procs) => { should(procs[0].pm2_env.JSONTEST).eql('{"si":"si"}') should(procs[0].pm2_env.NORMAL).eql('STR') done() }) }) }) ================================================ FILE: test/programmatic/json_validation.mocha.js ================================================ var should = require('should'); var Config = require('../../lib/tools/Config'); var Schema = require('../../lib/API/schema.json'); // Change to current folder process.chdir(__dirname); describe('JSON validation tests', function() { it('should fail when passing wrong json', function() { var ret = Config.validateJSON({ "exec_interpreter" : "node", "exec_mode" : "clusasdter_mode", "instances" : "max", "log_date_format" : "YYYY-MM-DD HH:mm Z", "max_memory_restart" : "160", "merge_logs" : true, "name" : "hapi_playground", "script" : "chidld.js", "cwd" : "examadsples", "node_args" : "--harmoasdny", "ignore_watch" : ["[\\/\\\\]\\./", "log"], "watch" : "true" }); /** * Error about instances to not be an integer * Error about watch to not be a boolean */ ret.errors.length.should.eql(2); }); it('should succeed while passing right json', function() { var ret = Config.validateJSON({ "exec_interpreter" : "node", "exec_mode" : "cluster_mode", "instances" : 0, "log_date_format" : "YYYY-MM-DD HH:mm Z", "max_memory_restart" : "160", "merge_logs" : true, "error_file" : "err.file", "out_file" : "out.file", "pid_file" : "pid.file", "log_file" : "my-merged-log-file.log", "name" : "hapi_playground", "script" : "child.js", "cwd" : "examples", "node_args" : "--harmony", "max_memory_restart" : "10M", "ignore_watch" : ["[\\/\\\\]\\./", "log"], "watch" : true, "node_args" : ["hey","hay"], "env" : {} }); ret.errors.length.should.eql(0); }); it('should set default values if some attributes not defined', function(done) { var default_values = Object.keys(Schema).filter(function(attr) { if (Schema[attr].default) return Schema[attr].default; return false; }); var ret = Config.validateJSON({ script : 'test.js', name : 'toto' }); // Returned array should contain also default values Object.keys(ret.config).should.containDeep(default_values); done(); }); }); ================================================ FILE: test/programmatic/lazy_api.mocha.js ================================================ process.chdir(__dirname); process.env.PM2_RELOAD_LOCK_TIMEOUT = 1000; var PM2 = require('../..'); var should = require('should'); describe('Lazy API usage', function() { it('should start a script without passing any args', function(done) { PM2.start('./../fixtures/child.js', done); }); it('should list one process', function(done) { PM2.list(function(err, procs) { procs.length.should.eql(1); done(); }); }); it('should fail to start script', function(done) { PM2.start('./../fixtures/child.js', function(err) { should.exists(err); done(); }); }); it('should list one process', function(done) { PM2.list(function(err, procs) { procs.length.should.eql(1); done(); }); }); it('should reload', function(done) { PM2.reload('child', function(err) { should.not.exists(err); done(); }); }); it('should process been restarted', function(done) { PM2.list(function(err, procs) { procs.length.should.eql(1); procs[0].pm2_env.restart_time.should.eql(1); done(); }); }); it('should restart', function(done) { PM2.restart('./../fixtures/child.js'); setTimeout(function() { done(); }, 300); }); it('should process been restarted', function(done) { PM2.list(function(err, procs) { procs.length.should.eql(1); procs[0].pm2_env.restart_time.should.eql(2); done(); }); }); it('should stop', function(done) { PM2.stop('./../fixtures/child.js'); setTimeout(function() { done(); }, 300); }); it('should process been stopped', function(done) { PM2.list(function(err, procs) { procs.length.should.eql(1); procs[0].pm2_env.status.should.eql('stopped'); done(); }); }); it('should delete', function(done) { PM2.delete('./../fixtures/child.js'); setTimeout(function() { done(); }, 300); }); it('should list 0 procs', function(done) { PM2.list(function(err, procs) { procs.length.should.eql(0); done(); }); }); }); ================================================ FILE: test/programmatic/logs.js ================================================ process.env.NODE_ENV = 'test'; var PM2 = require('../..'); var should = require('should'); var fs = require('fs'); var path = require('path'); describe('Programmatic log feature test', function() { var proc1 = null; var procs = []; var pm2 = new PM2.custom({ cwd : __dirname + '/../fixtures' }); before(function(done) { pm2.delete('all', function() { done(); }); }); after(function(done) { pm2.disconnect(done); }); afterEach(function(done) { pm2.delete('all', done); }); describe('Log merging', function() { it('should process HAS post fixed logs with id (merge_logs: false)', function(done) { pm2.start({ script: './echo.js', error_file : 'error-echo.log', out_file : 'out-echo.log', merge_logs: false, name: 'toto' }, function(err, procs) { should(err).be.null(); var out_file = procs[0].pm2_env.pm_out_log_path; var err_file = procs[0].pm2_env.pm_err_log_path; out_file.should.containEql('out-echo-0.log'); err_file.should.containEql('error-echo-0.log'); setTimeout(function() { fs.readFileSync(out_file).toString().should.containEql('echo.js'); fs.readFileSync(err_file).toString().should.containEql('echo.js-error'); done(); }, 500); }); }); it('should process HAS NOT post fixed logs with id (merge_logs: true)', function(done) { pm2.start({ script: './echo.js', error_file : 'error-echo.log', out_file : 'out-echo.log', merge_logs : true }, function(err, procs) { should(err).be.null(); var out_file = procs[0].pm2_env.pm_out_log_path; var err_file = procs[0].pm2_env.pm_err_log_path; out_file.should.containEql('out-echo.log'); err_file.should.containEql('error-echo.log'); setTimeout(function() { fs.readFileSync(out_file).toString().should.containEql('echo.js'); fs.readFileSync(err_file).toString().should.containEql('echo.js-error'); done(); }, 500); }); }); it('should process HAS NOT post fixed logs with id and MERGED FILE (merge_logs: true)', function(done) { pm2.start({ script: './echo.js', error_file : 'error-echo.log', out_file : 'out-echo.log', log_file : 'merged.log', merge_logs : true }, function(err, procs) { should(err).be.null(); var out_file = procs[0].pm2_env.pm_out_log_path; var err_file = procs[0].pm2_env.pm_err_log_path; var log_file = procs[0].pm2_env.pm_log_path; out_file.should.containEql('out-echo.log'); err_file.should.containEql('error-echo.log'); log_file.should.containEql('merged.log'); setTimeout(function() { fs.readFileSync(out_file).toString().should.containEql('echo.js'); fs.readFileSync(err_file).toString().should.containEql('echo.js-error'); fs.readFileSync(log_file).toString().should.containEql('echo.js-error'); fs.readFileSync(log_file).toString().should.containEql('echo.js'); done(); }, 500); }); }); }); describe('Log timestamp', function() { it('should every file be timestamped', function(done) { pm2.start({ script : './echo.js', error_file : 'error-echo.log', out_file : 'out-echo.log', log_file : 'merged.log', merge_logs : true, log_date_format : 'YYYY-MM-DD HH:mm Z' }, function(err, procs) { should(err).be.null(); var out_file = procs[0].pm2_env.pm_out_log_path; var err_file = procs[0].pm2_env.pm_err_log_path; var log_file = procs[0].pm2_env.pm_log_path; out_file.should.containEql('out-echo.log'); err_file.should.containEql('error-echo.log'); log_file.should.containEql('merged.log'); setTimeout(function() { fs.readFileSync(out_file).toString().should.containEql('20'); fs.readFileSync(err_file).toString().should.containEql('20'); fs.readFileSync(log_file).toString().should.containEql('20'); done(); }, 500); }); }); }); describe('Disable logs', function() { it('should start app with log disabled', function(done) { try { fs.writeFileSync(path.resolve('./test/fixtures/error-echo-disabled.log'), ''); fs.writeFileSync(path.resolve('./test/fixtures/out-echo-disabled.log'), ''); fs.writeFileSync(path.resolve('./test/fixtures/merged.log'), ''); } catch(e) {} pm2.start({ script : './echo.js', error_file : 'error-echo-disabled.log', out_file : 'out-echo-disabled.log', log_file : 'merged.log', disable_logs : true, merge_logs : true }, function(err, procs) { should(err).be.null(); var out_file = procs[0].pm2_env.pm_out_log_path; var err_file = procs[0].pm2_env.pm_err_log_path; var log_file = procs[0].pm2_env.pm_log_path; setTimeout(function() { fs.readFileSync(err_file).toString().should.eql(''); fs.readFileSync(out_file).toString().should.eql(''); done(); }, 500); }); }); it('should start app with log disabled', function(done) { try { fs.writeFileSync(path.resolve('./test/fixtures/error-echo-disabled.log'), ''); fs.writeFileSync(path.resolve('./test/fixtures/out-echo-disabled.log'), ''); fs.writeFileSync(path.resolve('./test/fixtures/merged.log'), ''); } catch(e) { } pm2.start({ script : './echo.js', error_file : '/dev/null', out_file : '/dev/null', log_file : '/dev/null', disable_logs : true, merge_logs : true }, function(err, procs) { should(err).be.null(); var out_file = procs[0].pm2_env.pm_out_log_path; var err_file = procs[0].pm2_env.pm_err_log_path; setTimeout(function() { fs.readFileSync(err_file).toString().should.eql(''); fs.readFileSync(out_file).toString().should.eql(''); done(); }, 500); }); }); }); describe('Logs set to /dev/null', function() { it('should not write to logs', function(done) { pm2.start({ script: './echo.js', error_file : '/dev/null', out_file : '/dev/null' }, function(err, procs) { should(err).be.null(); var out_file = procs[0].pm2_env.pm_out_log_path; var err_file = procs[0].pm2_env.pm_err_log_path; out_file.should.containEql('/dev/null'); err_file.should.containEql('/dev/null'); setTimeout(function() { fs.readFileSync(out_file).toString().should.containEql(''); fs.readFileSync(err_file).toString().should.containEql(''); done(); }, 500); }); }); it('should write to log_file and not error_file or out_file', function(done) { pm2.start({ script: './echo.js', error_file : '/dev/null', out_file : '/dev/null', log_file : 'merged.log', merge_logs : true }, function(err, procs) { should(err).be.null(); var out_file = procs[0].pm2_env.pm_out_log_path; var err_file = procs[0].pm2_env.pm_err_log_path; var log_file = procs[0].pm2_env.pm_log_path; out_file.should.containEql('/dev/null'); err_file.should.containEql('/dev/null'); log_file.should.containEql('merged.log'); setTimeout(function() { fs.readFileSync(out_file).toString().should.containEql(''); fs.readFileSync(err_file).toString().should.containEql(''); fs.readFileSync(log_file).toString().should.containEql('echo.js-error'); fs.readFileSync(log_file).toString().should.containEql('echo.js'); done(); }, 500); }); }); it('should write to log_file and error_file but not out_file', function(done) { pm2.start({ script: './echo.js', error_file : 'error-echo.log', out_file : '/dev/null', log_file : 'merged.log', merge_logs : true }, function(err, procs) { should(err).be.null(); var out_file = procs[0].pm2_env.pm_out_log_path; var err_file = procs[0].pm2_env.pm_err_log_path; var log_file = procs[0].pm2_env.pm_log_path; out_file.should.containEql('/dev/null'); err_file.should.containEql('error-echo.log'); log_file.should.containEql('merged.log'); setTimeout(function() { fs.readFileSync(out_file).toString().should.containEql(''); fs.readFileSync(err_file).toString().should.containEql('echo.js-error'); fs.readFileSync(log_file).toString().should.containEql('echo.js-error'); fs.readFileSync(log_file).toString().should.containEql('echo.js'); done(); }, 500); }); }); }); }); ================================================ FILE: test/programmatic/max_memory_limit.js ================================================ process.env.NODE_ENV = 'test'; process.env.PM2_WORKER_INTERVAL = 1000; var PM2 = require('../..'); var should = require('should'); var path = require('path'); // Change to current folder describe('Max memory restart programmatic', function() { var proc1 = null; var procs = []; var pm2 = new PM2.custom({ cwd : __dirname + '/../fixtures/json-reload/' }); after(function(done) { pm2.kill(done) }); afterEach(function(done) { pm2.delete('all', function() { setTimeout(done, 300); }); }); before(function(done) { pm2.connect(function() { done(); }); }); describe('Max memory limit', function() { it('should restart process based on memory limit (UGLY WAY)', function(done) { pm2.start('./big-array.js', { maxMemoryRestart : '10M' }, function(err, data) { should(err).be.null(); setTimeout(function() { pm2.list(function(err, ret) { should(err).be.null(); ret[0].pm2_env.restart_time.should.not.eql(0); done(); }); }, 3000); }); }); it('should restart process based on memory limit (JSON WAY)', function(done) { pm2.start({ script : './big-array.js', max_memory_restart : '10M' }, function(err, data) { should(err).be.null(); setTimeout(function() { pm2.list(function(err, ret) { should(err).be.null(); ret[0].pm2_env.restart_time.should.not.eql(0); done(); }); }, 3000); }); }); it('should restart CLUSTER process based on memory limit (JSON WAY)', function(done) { pm2.start({ script : './../big-array-listen.js', max_memory_restart : '10M', exec_mode : 'cluster' }, function(err, data) { should(err).be.null(); setTimeout(function() { pm2.list(function(err, ret) { should(err).be.null(); ret[0].pm2_env.restart_time.should.not.eql(0); done(); }); }, 3000); }); }); }); }); ================================================ FILE: test/programmatic/misc_commands.js ================================================ var PM2 = require('../..'); var should = require('should'); var path = require('path'); var fs = require('fs'); var cst = require('../../constants.js'); describe('Misc commands', function() { var pm2 = new PM2.custom({ cwd : __dirname + '/../fixtures' }); after(function(done) { pm2.kill(done); }); before(function(done) { pm2.connect(function() { pm2.delete('all', function() { done(); }); }); }); it('should start 4 processes', function(done) { pm2.start({ script : './echo.js', instances : 4, name : 'echo' }, function(err, data) { should(err).be.null(); done(); }); }); it('should restart them', function(done) { pm2.restart('all', function(err, data) { should(err).be.null(); pm2.list(function(err, procs) { should(err).be.null(); procs.length.should.eql(4); procs.forEach(function(proc) { proc.pm2_env.restart_time.should.eql(1); }); done(); }); }); }); it('should fail when trying to reset metadatas of unknown process', function(done) { pm2.reset('allasd', function(err, data) { should(err).not.be.null(); done(); }); }); it('should reset their metadatas', function(done) { pm2.reset('all', function(err, data) { should(err).be.null(); pm2.list(function(err, procs) { should(err).be.null(); procs.length.should.eql(4); procs.forEach(function(proc) { proc.pm2_env.restart_time.should.eql(0); }); done(); }); }); }); it('should save process list to dump', function(done) { if (fs.existsSync(cst.DUMP_FILE_PATH)) { fs.unlinkSync(cst.DUMP_FILE_PATH); } if (fs.existsSync(cst.DUMP_BACKUP_FILE_PATH)) { fs.unlinkSync(cst.DUMP_BACKUP_FILE_PATH); } pm2.dump(function(err, data) { should(fs.existsSync(cst.DUMP_FILE_PATH)).be.true(); should(fs.existsSync(cst.DUMP_BACKUP_FILE_PATH)).be.false(); should(err).be.null(); done(); }); }); it('should back up dump and re-save process list', function(done) { var origDump = fs.readFileSync(cst.DUMP_FILE_PATH).toString(); pm2.dump(function(err, data) { should(fs.existsSync(cst.DUMP_FILE_PATH)).be.true(); should(fs.existsSync(cst.DUMP_BACKUP_FILE_PATH)).be.true(); should(err).be.null(); var dumpBackup = fs.readFileSync(cst.DUMP_BACKUP_FILE_PATH).toString(); should(origDump).be.equal(dumpBackup); done(); }); }); it('should delete child processes', function(done) { pm2.delete('echo', function(err, data) { should(err).be.null(); pm2.list(function(err, procs) { should(err).be.null(); procs.length.should.eql(0); done(); }); }); }); it('should resurrect previous processes from dump', function(done) { pm2.resurrect(function(err, data) { should(err).be.null(); pm2.list(function(err, procs) { should(err).be.null(); procs.length.should.eql(4); done(); }); }); }); it('should resurrect previous processes from backup if dump is broken', function(done) { fs.writeFileSync(cst.DUMP_FILE_PATH, '[{'); pm2.resurrect(function(err, data) { should(err).be.null(); pm2.list(function(err, procs) { should(err).be.null(); procs.length.should.eql(4); done(); }); }); }); it('should delete broken dump', function() { should(fs.existsSync(cst.DUMP_FILE_PATH)).be.false(); }); it('should resurrect previous processes from backup if dump is missing', function(done) { if (fs.existsSync(cst.DUMP_FILE_PATH)) { fs.unlinkSync(cst.DUMP_FILE_PATH); } pm2.resurrect(function(err, data) { should(err).be.null(); pm2.list(function(err, procs) { should(err).be.null(); procs.length.should.eql(4); done(); }); }); }); it('should resurrect no processes if dump and backup are broken', function() { fs.writeFileSync(cst.DUMP_FILE_PATH, '[{'); fs.writeFileSync(cst.DUMP_BACKUP_FILE_PATH, '[{'); should(pm2.resurrect()).be.false(); }); it('should delete broken dump and backup', function() { should(fs.existsSync(cst.DUMP_FILE_PATH)).be.false(); should(fs.existsSync(cst.DUMP_BACKUP_FILE_PATH)).be.false(); }); it('should resurrect no processes if dump and backup are missing', function() { if (fs.existsSync(cst.DUMP_FILE_PATH)) { fs.unlinkSync(cst.DUMP_FILE_PATH); } if (fs.existsSync(cst.DUMP_BACKUP_FILE_PATH)) { fs.unlinkSync(cst.DUMP_BACKUP_FILE_PATH); } should(pm2.resurrect()).be.false(); }); }); ================================================ FILE: test/programmatic/module_configuration.mocha.js ================================================ process.env.NODE_ENV = 'test'; var PM2 = require('../..'); var should = require('should'); var path = require('path'); var Plan = require('../helpers/plan.js'); var fs = require('fs'); process.chdir(__dirname); describe('Module default flush configuration', function() { this.timeout(30000); before(function(done) { PM2.unset('pm2-logrotate', done); }); it('should install a module', function(done) { PM2.install('pm2-logrotate', function() { setTimeout(done, 1000); }); }); it('should module configuration have module options', function(done) { var conf = require(process.env.HOME + '/.pm2/module_conf.json'); should(conf['pm2-logrotate'].max_size).eql('10M'); should(conf['pm2-logrotate'].retain).eql('all'); should(conf['pm2-logrotate'].rotateModule).eql(true); done(); }); it('should change configuration', function(done) { PM2.set('pm2-logrotate.max_size', '20M', done); }); it('should have right value', function() { var conf = JSON.parse(fs.readFileSync(process.env.HOME + '/.pm2/module_conf.json')); should(conf['pm2-logrotate'].max_size).eql('20M'); }); it('should re install a module and not override previous set value', function() { var conf = JSON.parse(fs.readFileSync(process.env.HOME + '/.pm2/module_conf.json')); should(conf['pm2-logrotate'].max_size).eql('20M'); }); it('should uninstall module', function(done) { PM2.uninstall('pm2-logrotate', done); }); }); ================================================ FILE: test/programmatic/module_tar.mocha.js ================================================ const PM2 = require('../..'); const should = require('should'); const exec = require('child_process').exec const path = require('path') const fs = require('fs') describe('Modules programmatic testing', function() { var pm2; var MODULE_FOLDER_MONO = path.join(__dirname, './fixtures/tar-module/mono-app-module') var MODULE_FOLDER_MULTI = path.join(__dirname, './fixtures/tar-module/multi-app-module') var PACKAGE_MONO = path.join(process.cwd(), 'mono-app-module-v0-23-0.tar.gz') var PACKAGE_MULTI = path.join(process.cwd(), 'multi-app-module-v0-1.tar.gz') after(function(done) { pm2.kill(done); }); before(function(done) { pm2 = new PM2.custom({ cwd : './fixtures' }); pm2.uninstall('all', () => done()) }) describe('Package', function() { before((done) => { fs.unlink(PACKAGE_MONO, () => { fs.unlink(PACKAGE_MULTI, () => { done() }) }) }) it('should package tarball for mono app', function(done) { pm2.package(MODULE_FOLDER_MONO, (err) => { should(err).be.null() should(fs.existsSync(PACKAGE_MONO)).eql(true) done() }) }) it('should package tarball for multi app', function(done) { pm2.package(MODULE_FOLDER_MULTI, (err) => { should(err).be.null() should(fs.existsSync(PACKAGE_MULTI)).eql(true) done() }) }) }) describe('MULTI Install', function() { it('should install module', function(done) { pm2.install(PACKAGE_MULTI, { tarball: true }, function(err, apps) { should(err).eql(null); done(); }); }); it('should have file decompressed in the right folder', function() { var target_path = path.join(PM2._conf.DEFAULT_MODULE_PATH, 'multi-app-module') fs.readFileSync(path.join(target_path, 'package.json')) }) it('should have boot key present', function(done) { var conf = JSON.parse(fs.readFileSync(process.env.HOME + '/.pm2/module_conf.json')) should.exist(conf['tar-modules']['multi-app-module']); done() }) it('should have started 2 apps', function(done) { pm2.list(function(err, list) { should(err).be.null(); should(list.length).eql(2) should(list[0].pm2_env.version).eql('0.1') should(list[0].name).eql('multi-app-module:first_app') should(list[1].name).eql('multi-app-module:second_app') should(list[1].pm2_env.version).eql('0.1') should(list[0].pm2_env.status).eql('online') should(list[1].pm2_env.status).eql('online') done() }) }) }) describe('Reinstall', () => { it('should install module', function(done) { pm2.install(PACKAGE_MULTI, { tarball: true }, function(err, apps) { should(err).eql(null); done(); }); }); it('should have only 2 apps', function(done) { pm2.list(function(err, list) { should(err).be.null(); should(list.length).eql(2) should(list[0].pm2_env.status).eql('online') should(list[1].pm2_env.status).eql('online') done() }) }) }) describe('Re spawn PM2', () => { it('should kill/resurect pm2', (done) => { pm2.update(function(err) { should(err).be.null(); done() }) }) it('should have boot key present', function(done) { var conf = JSON.parse(fs.readFileSync(process.env.HOME + '/.pm2/module_conf.json')) should.exist(conf['tar-modules']['multi-app-module']); done() }) it('should have started 2 apps', function(done) { pm2.list(function(err, list) { should(err).be.null(); should(list.length).eql(2) should(list[0].pm2_env.status).eql('online') should(list[0].pm2_env.version).eql('0.1') should(list[1].pm2_env.version).eql('0.1') should(list[1].pm2_env.status).eql('online') done() }) }) }) describe('CLI UX', () => { it('should not delete modules when calling pm2 delete all', (done) => { pm2.delete('all', (err, apps) => { should(apps.length).eql(2) done() }) }) }) describe('Uninstall', () => { it('should uninstall multi app module', (done) => { pm2.uninstall('multi-app-module', (err, data) => { should(err).be.null(); done() }) }) it('should have boot key deleted', function(done) { var conf = JSON.parse(fs.readFileSync(process.env.HOME + '/.pm2/module_conf.json')) should.not.exist(conf['tar-modules']['multi-app-module']); done() }) it('should have no running apps', function(done) { pm2.list(function(err, list) { should(err).be.null(); should(list.length).eql(0) done() }) }) }) describe('MONO APP', () => { it('should install module', function(done) { pm2.install(PACKAGE_MONO, { tarball: true }, function(err, apps) { should(err).eql(null); done(); }); }); it('should have file decompressed in the right folder', function() { var target_path = path.join(PM2._conf.DEFAULT_MODULE_PATH, 'mono-app-module') var pkg_path = path.join(target_path, 'package.json') fs.readFileSync(pkg_path) }) it('should have boot key present', function(done) { var conf = JSON.parse(fs.readFileSync(process.env.HOME + '/.pm2/module_conf.json')) should.exist(conf['tar-modules']['mono-app-module']); done() }) it('should have started 1 app', function(done) { pm2.list(function(err, list) { should(err).be.null(); should(list.length).eql(1) should(list[0].name).eql('mono_app') should(list[0].pm2_env.version).eql('0.23.0') should(list[0].pm2_env.status).eql('online') done() }) }) it('should uninstall multi app module', (done) => { pm2.uninstall('mono-app-module', (err, data) => { should(err).be.null(); done() }) }) it('should have boot key deleted', function(done) { var conf = JSON.parse(fs.readFileSync(process.env.HOME + '/.pm2/module_conf.json')) should.not.exist(conf['tar-modules']['mono-app-module']); done() }) it('should have no running apps', function(done) { pm2.list(function(err, list) { should(err).be.null(); should(list.length).eql(0) done() }) }) }) }) ================================================ FILE: test/programmatic/modules.mocha.js ================================================ const PM2 = require('../..'); const should = require('should'); describe('Modules programmatic testing', function() { var pm2; after(function(done) { pm2.kill(done); }); it('should instanciate PM2', function() { pm2 = new PM2.custom({ daemon_mode : true }); }); it('should install a module', function(done) { pm2.install('pm2-server-monit', function(err, apps) { should(err).eql(null); should(apps.length).eql(1); var pm2_env = apps[0].pm2_env; should.exist(pm2_env); done(); }); }); it.skip('should run post install command', function(done) { var fs = require('fs'); var ec = {}; ec.dependencies = new Array(); ec.dependencies.push('pm2-server-monit'); ec.post_install = {}; ec.post_install['pm2-server-monit'] = 'echo "test passed!"'; fs.appendFileSync('test.json', JSON.stringify(ec)); pm2.install('test.json', function() { fs.unlinkSync('test.json'); done(); }); }); it('should list one module', function(done) { pm2.list(function(err, apps) { should(err).eql(null); should(apps.length).eql(1); var pm2_env = apps[0].pm2_env; should(pm2_env.status).eql('online'); done(); }); }); it('should uninstall all modules', function(done) { pm2.uninstall('all', function(err, apps) { done(); }); }); }); ================================================ FILE: test/programmatic/namespace.mocha.js ================================================ process.chdir(__dirname); var PM2 = require('../..'); var should = require('should'); describe('NAMESPACE app management', function() { var pm2 = new PM2.custom({ cwd : __dirname + '/../fixtures' }); before(function(done) { pm2.delete('all', function() { done() }); }); after(function(done) { pm2.kill(done); }); it('should start 2 app in NS1', (done) => { pm2.start({ script: './echo.js', name: 'echo1-ns1', namespace: 'NS1' }, (err, procs) => { should(err).be.null() procs[0].pm2_env.namespace.should.eql('NS1') pm2.start({ script: './echo.js', namespace: 'NS1', name: 'echo2-ns1' }, (err, procs) => { should(err).be.null() procs[0].pm2_env.namespace.should.eql('NS1') done() }) }) }) it('should start 2 app in NS2', (done) => { pm2.start({ script: './echo.js', name: 'echo1-ns2', namespace: 'NS2' }, (err, procs) => { should(err).be.null() procs[0].pm2_env.namespace.should.eql('NS2') pm2.start({ script: './echo.js', name: 'echo2-ns2', namespace: 'NS2' }, (err, procs) => { should(err).be.null() procs[0].pm2_env.namespace.should.eql('NS2') done() }) }) }) it('should restart only app in NS1', function(done) { pm2.restart('NS1', () => { PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(4); list.forEach(l => { if (l.name == 'echo1-ns1') should(l.pm2_env.restart_time).eql(1) if (l.name == 'echo2-ns1') should(l.pm2_env.restart_time).eql(1) if (l.name == 'echo1-ns2') should(l.pm2_env.restart_time).eql(0) if (l.name == 'echo2-ns2') should(l.pm2_env.restart_time).eql(0) }) done(); }); }) }) it('should restart all', function(done) { pm2.restart('all', () => { PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(4); list.forEach(l => { if (l.name == 'echo1-ns1') should(l.pm2_env.restart_time).eql(2) if (l.name == 'echo2-ns1') should(l.pm2_env.restart_time).eql(2) if (l.name == 'echo1-ns2') should(l.pm2_env.restart_time).eql(1) if (l.name == 'echo2-ns2') should(l.pm2_env.restart_time).eql(1) }) done(); }); }) }) it('should restart NS2', function(done) { pm2.restart('NS2', () => { PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(4); list.forEach(l => { if (l.name == 'echo1-ns1') should(l.pm2_env.restart_time).eql(2) if (l.name == 'echo2-ns1') should(l.pm2_env.restart_time).eql(2) if (l.name == 'echo1-ns2') should(l.pm2_env.restart_time).eql(2) if (l.name == 'echo2-ns2') should(l.pm2_env.restart_time).eql(2) }) done(); }); }) }) it('should stop NS2', function(done) { pm2.stop('NS2', () => { PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(4); list.forEach(l => { if (l.name == 'echo1-ns1') should(l.pm2_env.restart_time).eql(2) if (l.name == 'echo2-ns1') should(l.pm2_env.restart_time).eql(2) if (l.name == 'echo1-ns2') should(l.pm2_env.status).eql('stopped') if (l.name == 'echo2-ns2') should(l.pm2_env.status).eql('stopped') }) done(); }); }) }) it('should delete NS2', function(done) { pm2.delete('NS2', () => { PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(2); done(); }); }) }) }) ================================================ FILE: test/programmatic/path_resolution.mocha.js ================================================ var path = require('path'); process.chdir(path.join(__dirname, '../fixtures')); var PM2 = require('../..'); var should = require('should'); describe('Path resolution in configuration file', function() { this.timeout(4000) before(function(done) { PM2.delete('all', function() { done() } ); }); afterEach(function(done) { PM2.delete('all', function() { done() } ); }); it('should resolve paths (home)', function(done) { PM2.start('./path-resolution/ecosystem.config.js', function(err, proc) { should(proc[0].pm2_env.pm_err_log_path).eql(path.join(process.env.HOME, 'echo-err.log')); should(proc[0].pm2_env.pm_out_log_path).eql(path.join(process.env.HOME, 'echo-out.log')); should(proc[0].pm2_env.pm_pid_path).eql(path.join(process.env.HOME, 'echo-pid.log')); done(); }); }); it('should resolve paths (local)', function(done) { PM2.start('./path-resolution/ecosystem2.config.js', function(err, proc) { should(proc[0].pm2_env.pm_err_log_path).eql(path.join(process.cwd(), 'echo-err.log')); should(proc[0].pm2_env.pm_out_log_path).eql(path.join(process.cwd(), 'echo-out.log')); should(proc[0].pm2_env.pm_pid_path).eql(path.join(process.cwd(), 'echo-pid.log')); done(); }); }); it('should auto prefix log path on cluster mode', function(done) { PM2.start('./path-resolution/ecosystem3.config.js', function(err, proc) { should(proc[0].pm2_env.pm_err_log_path).eql(path.join(process.cwd(), 'echo-err-0.log')); should(proc[0].pm2_env.pm_out_log_path).eql(path.join(process.cwd(), 'echo-out-0.log')); should(proc[1].pm2_env.pm_err_log_path).eql(path.join(process.cwd(), 'echo-err-1.log')); should(proc[1].pm2_env.pm_out_log_path).eql(path.join(process.cwd(), 'echo-out-1.log')); done(); }); }); }); ================================================ FILE: test/programmatic/programmatic.js ================================================ /** * PM2 programmatic API tests */ //process.env.NODE_ENV ='test'; var PM2 = require('../..'); var should = require('should'); var path = require('path'); describe('PM2 programmatic calls', function() { var proc1 = null; var procs = []; var bus = null; var pm2 = new PM2.custom({ cwd : __dirname + '/../fixtures' }); after(function(done) { pm2.delete('all', function(err, ret) { // clean dump file pm2.clearDump(function(err) { pm2.kill(done); }); }); }); before(function(done) { pm2.connect(function() { pm2.launchBus(function(err, _bus) { bus = _bus; pm2.delete('all', function(err, ret) { done(); }); }); }); }); describe('General methods', function() { it('should start a script', function(done) { pm2.start({ script : './child.js', name : 'child', instances : 1 }, function(err, data) { proc1 = data[0]; should(err).be.null() done(); }); }); it('should get id', function(done) { pm2.getProcessIdByName('child', function(err, data) { data[0].should.eql(0); should(err).be.null() setTimeout(done, 300); }); }); it('should get node.js version application running', function(done) { pm2.describe('child', function(err, data) { should(err).be.null() data[0].pm2_env.node_version.should.eql(process.versions.node); done(); }); }); it('should start a script and force to launch it', function(done) { pm2.start({ script : './child.js', force : true, name : 'toto', instances : 1 }, function(err, data) { should(err).be.null() data.length.should.eql(1); done(); }); }); it('should start a script in a specified cwd', function(done) { var target_cwd = path.join(__dirname, '/../fixtures/'); pm2.start({ script : './cron.js', cwd: target_cwd, instances : 1 }, function(err, data) { should(err).be.null(); proc1 = data[0]; proc1.pm2_env.cwd.should.eql(target_cwd); should(err).be.null() done(); }); }); it('should notice error if wrong file passed', function(done) { pm2.start('./UNKNOWN_SCRIPT.js', { force : true, name : 'tota', instances : 3 }, function(err, data) { should.exists(err); done(); }); }); it('should start a script and force to launch it', function(done) { pm2.start('./child.js', { force : true, name : 'tota', instances : 6 }, function(err, data) { should(err).be.null() data.length.should.eql(6); done(); }); }); it('should get pm2 version', function(done) { pm2.getVersion(function(err, data) { should(err).be.null() should.exists(data); done(); }); }); it('should list processes', function(done) { pm2.list(function(err, ret) { should(err).be.null() ret.length.should.eql(9); done(); }); }); it('should delete one process', function(done) { pm2.delete(proc1.pm2_env.pm_id, function(err, ret) { should(err).be.null() pm2.list(function(err, ret) { should(err).be.null() ret.length.should.eql(8); done(); }); }); }); it('should save/dump all processes', function(done) { pm2.dump(function(err, ret) { should(err).be.null() done(); }); }); it('should delete processes', function(done) { pm2.delete('all', function(err, ret) { should(err).be.null() pm2.list(function(err, ret) { should(err).be.null() ret.length.should.eql(0); done(); }); }); }); it('should resurrect processes', function(done) { pm2.resurrect(function(err, ret) { should(err).be.null() pm2.list(function(err, ret) { should(err).be.null() ret.length.should.eql(8); done(); }); }); }); it('should ping pm2', function(done) { pm2.ping(function(err, ret) { should(err).be.null() done(); }); }); it('should reload all', function(done) { pm2.reload('all', function(err, ret) { should(err).be.null() done(); }); }); it('should reload only one application', function(done) { pm2.reload('tota', function(err, ret) { should(err).be.null() pm2.describe('tota', function(err, proc) { should(err).be.null() procs = proc; proc[0].pm2_env.restart_time.should.eql(2); done(); }); }); }); it('should describe all process with name', function(done) { pm2.describe('tota', function(err, proc) { should(err).be.null() proc.length.should.eql(6); done(); }); }); }); describe('Restart methods', function() { it('should restart all', function(done) { pm2.restart('all', function(err, ret) { should(err).be.null() pm2.describe('tota', function(err, proc) { should(err).be.null() proc.length.should.eql(6); proc[0].pm2_env.restart_time.should.eql(3); done(); }); }); }); it('should restart process by name', function(done) { pm2.restart('tota', function(err, ret) { should(err).be.null() pm2.describe('tota', function(err, proc) { should(err).be.null() proc.length.should.eql(6); proc[0].pm2_env.restart_time.should.eql(4); done(); }); }); }); it('should restart process by id', function(done) { pm2.restart(procs[0].pm2_env.pm_id, function(err, ret) { should(err).be.null() pm2.describe(procs[0].pm2_env.pm_id, function(err, proc) { should(err).be.null() proc.length.should.eql(1); proc[0].pm2_env.restart_time.should.eql(5); done(); }); }); }); }); describe('Stop methods', function() { it('should stop process name', function(done) { pm2.stop(procs[0].pm2_env.name, function(err, ret) { should(err).be.null() pm2.describe(procs[0].pm2_env.name, function(err, procs) { should(err).be.null() procs[0].pm2_env.status.should.eql('stopped'); done(); }); }); }); it('should stop process id', function(done) { pm2.stop(procs[1].pm2_env.pm_id, function(err, ret) { should(err).be.null() pm2.describe(procs[1].pm2_env.pm_id, function(err, procs) { should(err).be.null() procs[0].pm2_env.status.should.eql('stopped'); done(); }); }); }); it('should stop process all', function(done) { pm2.stop('all', function(err, ret) { should(err).be.null() pm2.describe(procs[0].pm2_env.pm_id, function(err, procs) { should(err).be.null() procs[0].pm2_env.status.should.eql('stopped'); done(); }); }); }); }); describe('start OR restart', function() { beforeEach(function(done) { pm2.delete('all', function(err, ret) { done(); }); }); it('should start a JSON object in cluster mode', function(done) { pm2.start({ script : './echo.js', instances : 4, exec_mode : 'cluster' }, function(err, dt) { should(err).be.null() pm2.list(function(err, ret) { should(err).be.null() ret.length.should.eql(4); ret[0].pm2_env.exec_mode.should.eql('cluster_mode'); done(); }); }); }); it('should start a JSON object in fork mode', function(done) { pm2.start({ script : './echo.js', instances : 4, exec_mode : 'fork' }, function(err, dt) { should(err).be.null() pm2.list(function(err, ret) { should(err).be.null() ret.length.should.eql(4); ret[0].pm2_env.exec_mode.should.eql('fork_mode'); done(); }); }); }); it('should start a JSON file', function(done) { pm2.start('./all2.json', function(err, dt) { should(err).be.null() pm2.list(function(err, ret) { should(err).be.null() ret.length.should.eql(4); done(); }); }); }); }); describe('start OR restart', function() { before(function(done) { pm2.delete('all', function(err, ret) { pm2.list(function(err, ret) { should(err).be.null() ret.length.should.eql(0); done(); }); }); }); it('should start', function(done) { pm2._startJson('./all2.json', {}, 'restartProcessId', function(err, data) { should(err).be.null() pm2.list(function(err, ret) { should(err).be.null() ret.length.should.eql(4); done(); }); }); }); it('should NOW restart action', function(done) { pm2._startJson('./all2.json', {}, 'restartProcessId', function(err, data) { should(err).be.null() pm2.list(function(err, ret) { should(err).be.null() ret.forEach(function(app) { app.pm2_env.restart_time.should.eql(1); }); done(); }); }); }); it('should reset status', function(done) { pm2.delete('all', function(err, ret) { done(); }); }); it('should start with specific environment variables depending on the env type', function(done) { pm2._startJson('../fixtures/all2.json', { env : 'production' }, 'restartProcessId', function(err, data) { should(err).be.null() pm2.list(function(err, ret) { should(err).be.null(); ret[0].pm2_env['NODE_ENV'].should.eql('production'); ret[0].pm2_env['TOTO'].should.eql('heymoto'); done(); }); }); }); }); describe('Connect / Disconnect', function() { it('should disconnect', function() { pm2.disconnect(); }); it('should connect', function(done) { pm2.connect(function() { done(); }); }); it('should disconnect with callback', function(done) { pm2.disconnect(function() { done(); }); }); it('should connect', function(done) { pm2.connect(function() { done(); }); }); }); }); ================================================ FILE: test/programmatic/reload-locker.mocha.js ================================================ process.env.NODE_ENV = 'test'; process.env.PM2_RELOAD_LOCK_TIMEOUT = 2000; var PM2 = require('../..'); var should = require('should'); var path = require('path'); var Plan = require('../helpers/plan.js'); var fs = require('fs'); var cst = require('../../constants.js'); process.chdir(__dirname); describe('Reload locker system', function() { var pm2 = new PM2.custom({ cwd : '../fixtures' }); before(function(done) { pm2.list(done); }); after(function(done) { pm2.kill(done) }); it('should start app', function(done) { pm2.start({ script : './http.js', instances : 2 }, function(err, data) { should(err).be.null(); pm2.list(function(err, ret) { should(err).be.null(); ret.length.should.eql(2); done(); }); }); }); it('should trigger one reload and forbid the second', function(done) { pm2.reload('all'); setTimeout(function() { fs.statSync(cst.PM2_RELOAD_LOCKFILE); var dt = parseInt(fs.readFileSync(cst.PM2_RELOAD_LOCKFILE).toString()); should(dt).above(0); pm2.reload('all', function(err) { should.exists(err); if (err) done() else done(new Error('should trigger error')); }); }, 100); }); it('should re allow reload when reload finished', function(done) { setTimeout(function() { pm2.reload('all', done); }, 2000); }); it('should lock file be empty', function() { var dt = fs.readFileSync(cst.PM2_RELOAD_LOCKFILE).toString(); should(dt).eql(''); }); }); ================================================ FILE: test/programmatic/resurect_state.mocha.js ================================================ var PM2 = require('../..'); var should = require('should'); var path = require('path'); var fs = require('fs'); var cst = require('../../constants.js'); var Configuration = require('../../lib/Configuration.js'); describe.skip('Keep state on pm2 update', function() { var pm2 before((done) => { Configuration.set('pm2:autodump', 'true', function(err, data) { pm2 = new PM2.custom({ cwd : __dirname + '/../fixtures' }); should.not.exists(err); done(); }); }) after((done) => { Configuration.set('pm2:autodump', 'false', function(err, data) { should.not.exists(err); done(); }); }) describe('Should autosave edits on stop/start/delete', function() { after(function(done) { pm2.kill(done); }); before(function(done) { pm2.connect(function() { pm2.delete('all', function() { done(); }); }); }); it('should set autodump to true', function(done) { pm2.set('pm2:autodump', 'true', function(err, data) { should.not.exists(err); done(); }); }) it('should start 4 processes', function(done) { pm2.start({ script : './echo.js', instances : 4, name : 'echo' }, function(err, data) { should(err).be.null(); done(); }); }); it('should kill pm2', function(done) { pm2.kill(done) }) it('should resurect with one process stopped', function(done) { pm2.resurrect(() => { pm2.list((err, dt) => { if (dt.length == 4) return done() return done(new Error('Did not kept process status')) }) }) }) it('should stop 1 process', function(done) { pm2.stop(0, done) }) it('should kill pm2', function(done) { pm2.kill(done) }) it('should resurect with one process stopped', function(done) { pm2.resurrect(() => { pm2.list((err, dt) => { if (dt.length == 4 && dt.filter(proc => proc.pm2_env.status == 'stopped').length == 1) return done() return done(new Error('Did not kept process status')) }) }) }) it('should delete and save', function(done) { pm2.delete(0, done) }) it('should kill pm2', function(done) { pm2.kill(done) }) it('should resurect with one process stopped', function(done) { pm2.resurrect(() => { pm2.list((err, dt) => { if (dt.length == 3) return done() return done(new Error('Did not kept process status')) }) }) }) }) }) ================================================ FILE: test/programmatic/send_data_process.mocha.js ================================================ /** * PM2 programmatic API tests */ var PM2 = require('../..'); var should = require('should'); var path = require('path'); describe('PM2 programmatic calls', function() { var pm2 = new PM2.custom({ cwd : __dirname + '/../fixtures' }); var pm2_bus = null; var proc1 = null; var procs = []; after(function(done) { pm2.delete('all', function(err, ret) { pm2.kill(done); }); }); before(function(done) { pm2.connect(function() { pm2.launchBus(function(err, bus) { pm2_bus = bus; pm2.delete('all', function(err, ret) { done(); }); }); }); }); /** * process.on('message', function(packet) { * process.send({ * topic : 'process:msg', * data : { * success : true * } * }); * }); */ it('should start a script', function(done) { pm2.start({ script : './send-data-process/return-data.js' }, function(err, data) { proc1 = data[0]; should(err).be.null(); done(); }); }); it('should receive data packet', function(done) { pm2_bus.on('process:msg', function(packet) { pm2_bus.off('process:msg'); packet.raw.data.success.should.eql(true); packet.raw.topic.should.eql('process:msg'); packet.process.pm_id.should.eql(proc1.pm2_env.pm_id); packet.process.name.should.eql(proc1.pm2_env.name); done(); }); pm2.sendDataToProcessId(proc1.pm2_env.pm_id, { topic : 'process:msg', data : { some : 'data', hello : true } }, function(err, res) { should(err).be.null(); }); }); it('should receive data packet (other input)', function(done) { pm2_bus.on('process:msg', function(packet) { pm2_bus.off('process:msg'); packet.raw.data.success.should.eql(true); packet.raw.topic.should.eql('process:msg'); packet.process.pm_id.should.eql(proc1.pm2_env.pm_id); packet.process.name.should.eql(proc1.pm2_env.name); done(); }); pm2.sendDataToProcessId({ id: proc1.pm2_env.pm_id, topic : 'process:msg', data : { some : 'data', hello : true } }, function(err, res) { should(err).be.null(); }); }); }); ================================================ FILE: test/programmatic/signals.js ================================================ var PM2 = require('../..'); var should = require('should'); var path = require('path'); var fs = require('fs'); describe('Signal kill (+delayed)', function() { var proc1 = null; var pm2 = new PM2.custom({ cwd : __dirname + '/../fixtures' }); after(function(done) { pm2.delete('all', function(err, ret) { pm2.kill(done); }); }); before(function(done) { pm2.connect(function() { pm2.delete('all', function(err, ret) { done(); }); }); }); describe('with 3000ms PM2_KILL_TIMEOUT (environment variable)', function() { it('should set 3000ms to PM2_KILL_TIMEOUT', function(done) { process.env.PM2_KILL_TIMEOUT = 3000; pm2.update(function() { done(); }); }); it('should start a script', function(done) { pm2.start({ script : './signals/delayed_sigint.js', name : 'delayed-sigint' }, function(err, data) { proc1 = data[0]; should(err).be.null(); setTimeout(done, 1000); }); }); it('should stop script after 3000ms', function(done) { setTimeout(function() { pm2.list(function(err, list) { list[0].pm2_env.status.should.eql('stopping'); }); }, 2500); setTimeout(function() { pm2.list(function(err, list) { list[0].pm2_env.status.should.eql('stopped'); done(); }); }, 3500); pm2.stop('delayed-sigint', function(err, app) { //done(err); }); }); }); describe('with 1000ms PM2_KILL_TIMEOUT (environment variable)', function() { it('should set 1000ms to PM2_KILL_TIMEOUT', function(done) { process.env.PM2_KILL_TIMEOUT = 1000; pm2.update(function() { done(); }); }); it('should start a script', function(done) { pm2.start({ script : './signals/delayed_sigint.js', name : 'delayed-sigint' }, function(err, data) { proc1 = data[0]; should(err).be.null(); setTimeout(done, 1000); }); }); it('should stop script after 1000ms', function(done) { setTimeout(function() { pm2.list(function(err, list) { list[0].pm2_env.status.should.eql('stopping'); }); }, 500); setTimeout(function() { pm2.list(function(err, list) { list[0].pm2_env.status.should.eql('stopped'); done(); }); }, 1500); pm2.stop('delayed-sigint', function(err, app) { //done(err); }); }); }); describe('[CLUSTER MODE] with 1000ms PM2_KILL_TIMEOUT (environment variable)', function() { it('should set 1000ms to PM2_KILL_TIMEOUT', function(done) { process.env.PM2_KILL_TIMEOUT = 1000; pm2.update(function() { done(); }); }); it('should start a script', function(done) { pm2.start({ script : './signals/delayed_sigint.js', name : 'delayed-sigint', exec_mode : 'cluster' }, function(err, data) { proc1 = data[0]; should(err).be.null(); setTimeout(done, 1000); }); }); it('should stop script after 1000ms', function(done) { setTimeout(function() { pm2.list(function(err, list) { list[0].pm2_env.status.should.eql('stopping'); }); }, 500); setTimeout(function() { pm2.list(function(err, list) { list[0].pm2_env.status.should.eql('stopped'); done(); }); }, 1500); pm2.stop('delayed-sigint', function(err, app) { //done(err); }); }); it('should reload script', function(done) { setTimeout(function() { pm2.list(function(err, list) { list[0].pm2_env.status.should.eql('online'); done(); }); }, 1500); pm2.reload('delayed-sigint', function(err, app) { //done(err); }); }); }); describe('with 4000ms via kill_timeout (json/cli option)', function() { it('should set 1000ms to PM2_KILL_TIMEOUT', function(done) { process.env.PM2_KILL_TIMEOUT = 1000; pm2.update(function() { done(); }); }); it('should start a script with flag kill timeout to 4000ms', function(done) { pm2.start({ script : './signals/delayed_sigint.js', name : 'delayed-sigint', exec_mode : 'cluster', kill_timeout : 4000 }, function(err, data) { proc1 = data[0]; should(err).be.null(); setTimeout(done, 1000); }); }); it('should stop script after 4000ms (and not 1000ms)', function(done) { setTimeout(function() { pm2.list(function(err, list) { list[0].pm2_env.status.should.eql('stopping'); }); }, 1500); setTimeout(function() { pm2.list(function(err, list) { list[0].pm2_env.status.should.eql('stopped'); done(); }); }, 4500); pm2.stop('delayed-sigint', function(err, app) { //done(err); }); }); it('should delete all', function(done) { pm2.delete('all', done) }) }); }); describe('Message kill (signal behavior override via PM2_KILL_USE_MESSAGE, +delayed)', function() { var proc1 = null; var appName = 'delayedsend'; process.env.PM2_KILL_USE_MESSAGE = true; var pm2 = new PM2.custom({ cwd : __dirname + '/../fixtures', }); after(function(done) { pm2.delete('all', function(err, ret) { pm2.kill(done); }); }); before(function(done) { pm2.connect(function() { pm2.delete('all', function(err, ret) { done(); }); }); }); describe.only('with 1000ms PM2_KILL_TIMEOUT (environment variable)', function() { it('should set 1000ms to PM2_KILL_TIMEOUT', function(done) { process.env.PM2_KILL_TIMEOUT = 1000; pm2.update(function() { done(); }); }); it('should start a script', function(done) { pm2.start({ script : './signals/delayed_send.js', error_file : 'error-echo.log', out_file : 'out-echo.log', name : appName, }, function(err, data) { proc1 = data[0]; should(err).be.null(); setTimeout(done, 1000); }); }); it('should stop script after 1000ms', function(done) { setTimeout(function() { console.log('CALLED1') pm2.describe(appName, function(err, list) { console.log('CALLED1FINI') should(err).be.null(); list[0].pm2_env.status.should.eql('stopping'); }); }, 500); setTimeout(function() { console.log('CALLED2') pm2.describe(appName, function(err, list) { console.log('CALLED2FINI') should(err).be.null(); list[0].pm2_env.status.should.eql('stopped'); done(); }); }, 1500); pm2.stop(appName, function(err, app) { //done(err); }); }); it('should read shutdown message', function(done) { var out_file = proc1.pm2_env.pm_out_log_path; fs.readFileSync(out_file).toString().should.containEql('shutdown message'); done(); }) it('should delete all', function(done) { pm2.delete('all', done) }) }); describe('[CLUSTER MODE] with 1000ms PM2_KILL_TIMEOUT (environment variable)', function() { it('should set 1000ms to PM2_KILL_TIMEOUT', function(done) { process.env.PM2_KILL_TIMEOUT = 1000; pm2.update(function() { done(); }); }); it('should start a script', function(done) { pm2.start({ script : './signals/delayed_send.js', name : appName, exec_mode : 'cluster' }, function(err, data) { proc1 = data[0]; should(err).be.null(); setTimeout(done, 1000); }); }); it('should stop script after 1000ms', function(done) { setTimeout(function() { pm2.describe(appName, function(err, list) { should(err).be.null(); list[0].pm2_env.status.should.eql('stopping'); }); }, 500); setTimeout(function() { pm2.describe(appName, function(err, list) { should(err).be.null(); list[0].pm2_env.status.should.eql('stopped'); done(); }); }, 1500); pm2.stop(appName, function(err, app) { //done(err); }); }); it('should reload script', function(done) { setTimeout(function() { pm2.describe(appName, function(err, list) { should(err).be.null(); list[0].pm2_env.status.should.eql('online'); done(); }); }, 1500); pm2.reload(appName, function(err, app) { //done(err); }); }); it('should read shutdown message', function(done) { var out_file = proc1.pm2_env.pm_out_log_path; fs.readFileSync(out_file).toString().should.containEql('shutdown message'); done(); }) }); }); ================================================ FILE: test/programmatic/sys_infos.mocha.js ================================================ const Sysinfos = require('../../lib/Sysinfo/SystemInfo.js') const should = require('should'); const path = require('path') describe('Sysinfos', function() { var sysinfo after(() => { sysinfo.kill() }) it('should start a failing app in fork mode', function(done) { sysinfo = new Sysinfos() sysinfo.fork() done() }) it('should query procs', function(done) { setTimeout(() => { sysinfo.query((err, data) => { console.log(data) done() }) }, 500) }) }) ================================================ FILE: test/programmatic/user_management.mocha.js ================================================ process.env.NODE_ENV = 'test' process.chdir(__dirname); var PM2 = require('../..'); var should = require('should'); describe('User management', function() { before(function(done) { PM2.delete('all', function() { done() }); }); after(function(done) { PM2.kill(done); }); it('should fail with unknown user', function(done) { PM2.start('./../fixtures/child.js', { user: 'toto' },function(err) { should(err.message).match(/cannot be found/) PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(0); done(); }); }); }) it('should succeed with known user', function(done) { PM2.start('./../fixtures/child.js', { user: process.env.USER },function(err) { should(err).be.null(); PM2.list(function(err, list) { should(err).be.null(); should(list.length).eql(1); should.exist(list[0].pm2_env.uid) should.exist(list[0].pm2_env.gid) PM2.delete('all', done) }); }); }) it('should succeed with known user via uid field', function(done) { PM2.start('./../fixtures/child.js', { uid: process.env.USER },function(err) { should(err).be.null(); PM2.list(function(err, list) { should(err).be.null(); should.exist(list[0].pm2_env.uid) should.exist(list[0].pm2_env.gid) should(list.length).eql(1); PM2.delete('all', done) }); }); }) }) ================================================ FILE: test/programmatic/version.mocha.js ================================================ const PM2 = require('../..'); const should = require('should'); const exec = require('child_process').exec const path = require('path') const fs = require('fs') describe('Modules programmatic testing', function() { var pm2 var pkg_path = path.join(__dirname, 'fixtures/version-test/package.json') after(function(done) { pm2.delete('all', function() { pm2.kill(done); }) }); before(function(done) { pm2 = new PM2.custom({ cwd : path.join(__dirname, 'fixtures') }); var pkg = JSON.parse(fs.readFileSync(pkg_path)) pkg.version = '1.0.0' fs.writeFileSync(pkg_path, JSON.stringify(pkg)) pm2.delete('all', () => done()) }) it('should start app and find version', function(done) { pm2.start('./version-test/index.js', (err) => { pm2.list(function(err, apps) { should(err).be.null() var real_version = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures/version-test/package.json'))).version should(apps[0].pm2_env.version).equal(real_version) done() }) }) }) var origin_version it('should update version', function(done) { var old = JSON.parse(fs.readFileSync(pkg_path)) origin_version = old.version old.version = '2.0.0' fs.writeFileSync(pkg_path, JSON.stringify(old)) pm2.restart('all', function() { setTimeout(() => { pm2.list((err, list) => { should(list[0].pm2_env.version).equal('2.0.0') done() }) }, 400) }) }) it('should restore version', function(done) { var old = JSON.parse(fs.readFileSync(pkg_path)) old.version = origin_version fs.writeFileSync(pkg_path, JSON.stringify(old)) pm2.restart('all', function() { pm2.list((err, list) => { should(list[0].pm2_env.version).equal(origin_version) done() }) }) }) }) ================================================ FILE: test/programmatic/watcher.js ================================================ var should = require('should'); var p = require('path'); var fs = require('fs') var EventEmitter = require('events').EventEmitter var PM2 = require('../..'); var extend = require('util')._extend var cwd = __dirname + '/../fixtures/watcher'; var paths = { server : p.join(cwd, 'server-watch.js'), bak : p.join(cwd, 'server-watch.bak.js'), json : p.join(cwd, 'server-watch.json') }; var ee = new EventEmitter() var json = { name : 'server-watch', script: './server-watch.js', cwd : cwd } function testPM2Env(event) { return function(obj, cb) { ee.once(event, function(e) { if(typeof obj == 'function') { return obj(e) } var value for(var key in obj) { value = obj[key] console.log('Testing %s for value %s', key, value) should(e[key]).eql(value) } return cb() }) } } function errShouldBeNull(err) { should(err).be.null(); } describe('Watcher', function() { var pm2 = new PM2.custom({ cwd : __dirname + '/../fixtures/watcher' }); after(function(cb) { pm2.destroy(function() { fs.unlink(paths.server, cb) }); }); before(function(cb) { //copy server-watch.bak, we'll add some lines in it fs.readFile(paths.bak, function(err, data) { if(err) { return cb(err) } return fs.writeFile(paths.server, data, cb) }) }) before(function(done) { pm2.connect(function() { done(); }); }); before(function(done) { pm2.launchBus(function(err, bus) { should(err).be.null bus.on('process:event', function(e) { var name = e.process.name + ':' + e.event console.log('Bus receiving: ' + name) delete e.process.ENV ee.emit(name, e.process) }) return done() }) }) it('should be watching', function(cb) { testPM2Env('server-watch:online')({watch: true}, cb) var json_app = extend(json, {watch: true}); pm2.start(json_app, errShouldBeNull) }) it('should be watching after restart', function(cb) { testPM2Env('server-watch:online')({watch: true}, cb) pm2.restart('server-watch', errShouldBeNull) }) it('should restart because of file edit', function(cb) { testPM2Env('server-watch:online')({restart_time: 2}, cb) fs.appendFileSync(paths.server, 'console.log("edit")') }) it('should stop watching', function(cb) { process.argv.push('--watch') testPM2Env('server-watch:stop')({watch: false}, function() { process.argv.splice(process.argv.indexOf('--watch'), 1) cb() }) pm2.stop('server-watch', errShouldBeNull) // this would be better: // pm2.actionFromJson('stopProcessId', extend(json, {watch: false}), errShouldBeNull) // or : // pm2.stop('server-watch', {watch: false}, errShouldBeNull) }) it('should not watch', function(cb) { testPM2Env('server-watch:online')({watch: false}, cb) pm2.restart(extend(json, {watch: false}), errShouldBeNull) }) it('should watch', function(cb) { testPM2Env('server-watch:online')({restart_time: 3, watch: true}, cb) pm2.restart(extend(json, {watch: true}), errShouldBeNull) }) it('should delete process', function(cb) { pm2.delete('server-watch', cb) }) it('should watch json', function(cb) { testPM2Env('server-watch:online')(function() { cb() }) var json_app = paths.json; pm2.start(json_app, errShouldBeNull) }) it('should restart json from file touch', function(cb) { testPM2Env('server-watch:online')({restart_time: 1}, cb) var path = p.join(cwd, 'donotwatchme.dir', 'test') fs.writeFile(path, 'Test', {flag: 'a+'}, function(err) { errShouldBeNull(err) }) }) it('should restart json from file deletion', function(cb) { testPM2Env('server-watch:online')({restart_time: 2}, cb) var path = p.join(cwd, 'donotwatchme.dir', 'test') fs.unlink(path, function(err) { errShouldBeNull(err) }) }) it('should not restart from ignore_watch', function(cb) { var path = p.join(cwd, 'pm2.log') fs.writeFile(path, 'Log', {flag: 'a+'}, function(err) { errShouldBeNull(err) pm2.describe('server-watch', function(err, d) { should(d[0].pm2_env.restart_time).eql(2) fs.unlinkSync(path) return cb() }) }) }) it('should work with watch_delay', function(cb) { testPM2Env('server-watch:online')({watch: true, watch_delay: 4000}, cb); pm2.start(extend(json, {watch: true, watch_delay: 4000}), errShouldBeNull); }) it('should not crash with watch_delay without watch', function(cb) { testPM2Env('server-watch:online')({watch_delay: 4000}, cb); pm2.start(extend(json, {watch_delay: 4000}), errShouldBeNull); }) /** * Test #1668 */ it('should delete from json', function(cb) { testPM2Env('server-watch:exit')(function() { cb() }) pm2.delete(paths.json, errShouldBeNull) }) }) ================================================ FILE: test/unit.sh ================================================ #!/usr/bin/env bash if command -v bun >/dev/null 2>&1 then mocha="bunx mocha" else mocha="npx mocha" fi pm2="`pwd`/bin/pm2" function reset { $pm2 uninstall all -s $pm2 link delete -s $pm2 kill -s } function runUnitTest { echo "[~] Starting test $1" START=$(date +%s) $mocha --exit --bail $1 RET=$? if [ $RET -ne 0 ]; then STR="[RETRY] $1 failed and NOW is getting retried" echo $STR echo $STR >> unit_time reset $mocha --bail --exit $1 RET=$? if [ $RET -ne 0 ]; then echo -e "######## TEST ✘ $1 FAILED TWICE!!" exit 1 fi fi reset END=$(date +%s) DIFF=$(echo "$END - $START" | bc) STR="[V] $1 succeeded and took $DIFF seconds" echo $STR echo $STR >> unit_time } reset touch unit_time > unit_time D=test/programmatic # Abort script at first error #set -e runUnitTest $D/path_resolution.mocha.js runUnitTest $D/modules.mocha.js runUnitTest $D/instances.mocha.js runUnitTest $D/reload-locker.mocha.js runUnitTest $D/filter_env.mocha.js runUnitTest $D/resurect_state.mocha.js runUnitTest $D/programmatic.js runUnitTest $D/namespace.mocha.js runUnitTest $D/auto_restart.mocha.js runUnitTest $D/containerizer.mocha.js runUnitTest $D/api.mocha.js runUnitTest $D/lazy_api.mocha.js #runUnitTest $D/version.mocha.js runUnitTest $D/exp_backoff_restart_delay.mocha.js runUnitTest $D/api.backward.compatibility.mocha.js runUnitTest $D/custom_action.mocha.js runUnitTest $D/logs.js runUnitTest $D/watcher.js runUnitTest $D/max_memory_limit.js runUnitTest $D/cluster.mocha.js runUnitTest $D/graceful.mocha.js runUnitTest $D/inside.mocha.js runUnitTest $D/misc_commands.js runUnitTest $D/signals.js runUnitTest $D/send_data_process.mocha.js runUnitTest $D/json_validation.mocha.js runUnitTest $D/env_switching.js runUnitTest $D/configuration.mocha.js runUnitTest $D/id.mocha.js runUnitTest $D/god.mocha.js runUnitTest $D/dump.mocha.js runUnitTest $D/common.mocha.js runUnitTest $D/issues/json_env_passing_4080.mocha.js D=test/interface runUnitTest $D/bus.spec.mocha.js runUnitTest $D/bus.fork.spec.mocha.js runUnitTest $D/utility.mocha.js echo "============== unit test finished ==============" cat unit_time ================================================ FILE: types/index.d.ts ================================================ // Type definitions for pm2 6.0.8 // Definitions by: João Portela https://www.github.com/jportela // Exported Methods /** * Either connects to a running pm2 daemon (“God”) or launches and daemonizes one. * Once launched, the pm2 process will keep running after the script exits. * @param errback - Called when finished connecting to or launching the pm2 daemon process. */ export function connect(errback: ErrCallback): void; /** * Either connects to a running pm2 daemon (“God”) or launches and daemonizes one. * Once launched, the pm2 process will keep running after the script exits. * @param noDaemonMode - (Default: false) If true is passed for the first argument * pm2 will not be run as a daemon and will die when the related script exits. * By default, pm2 stays alive after your script exits. * If pm2 is already running, your script will link to the existing daemon but will die once your process exits. * @param errback - Called when finished connecting to or launching the pm2 daemon process. */ export function connect(noDaemonMode:boolean, errback: ErrCallback): void; /** * Starts a script that will be managed by pm2. * @param options - Options * @param errback - An errback called when the script has been started. * The proc parameter will be a pm2 process object. */ export function start(options: StartOptions, errback: ErrProcCallback): void; /** * Starts a script that will be managed by pm2. * @param jsonConfigFile - The path to a JSON file that can contain the same options as the options parameter. * @param errback - An errback called when the script has been started. * The proc parameter will be a pm2 process object. */ export function start(jsonConfigFile: string, errback: ErrProcCallback): void; /** * Starts a script that will be managed by pm2. * @param script - The path of the script to run. * @param errback - An errback called when the script has been started. * The proc parameter will be a pm2 process object. */ export function start(script: string , errback: ErrProcCallback): void; /** * Starts a script that will be managed by pm2. * @param script - The path of the script to run. * @param options - Options * @param errback - An errback called when the script has been started. * The proc parameter will be a pm2 process object. */ export function start(script: string, options: StartOptions, errback: ErrProcCallback): void; /** * Starts a script that will be managed by pm2. * @param script - The path of the script to run. * @param jsonConfigFile - The path to a JSON file that can contain the same options as the options parameter. * @param errback - An errback called when the script has been started. * The proc parameter will be a pm2 process object. */ export function start(script: string, jsonConfigFile: string, errback: ErrProcCallback): void; /** * Disconnects from the pm2 daemon. */ export function disconnect(): void; /** * Stops a process but leaves the process meta-data in pm2’s list * @param process - Can either be the name as given in the pm2.start options, * a process id, or the string “all” to indicate that all scripts should be restarted. * @param errback - called when the process is stopped */ export function stop(process: string|number, errback: ErrProcCallback): void; /** * Stops and restarts the process. * @param process - Can either be the name as given in the pm2.start options, * a process id, or the string “all” to indicate that all scripts should be restarted. * @param errback - called when the process is restarted */ export function restart(process: string|number, errback: ErrProcCallback): void; /** * Stops the process and removes it from pm2’s list. * The process will no longer be accessible by its name * @param process - Can either be the name as given in the pm2.start options, * a process id, or the string “all” to indicate that all scripts should be restarted. * @param errback - called when the process is deleted */ declare function del(process: string|number, errback: ErrProcCallback): void; // have to use this construct because `delete` is a reserved word export {del as delete}; /** * Zero-downtime rolling restart. At least one process will be kept running at * all times as each instance is restarted individually. * Only works for scripts started in cluster mode. * @param process - Can either be the name as given in the pm2.start options, * a process id, or the string “all” to indicate that all scripts should be restarted. * @param errback - called when the process is reloaded */ export function reload(process: string|number, errback: ErrProcCallback): void; /** * Zero-downtime rolling restart. At least one process will be kept running at * all times as each instance is restarted individually. * Only works for scripts started in cluster mode. * @param process - Can either be the name as given in the pm2.start options, * a process id, or the string “all” to indicate that all scripts should be restarted. * @param options - An object containing configuration * @param options.updateEnv - (Default: false) If true is passed in, pm2 will reload it’s * environment from process.env before reloading your process. * @param errback - called when the process is reloaded */ export function reload(process: string|number, options: ReloadOptions, errback: ErrProcCallback): void; /** * Kills the pm2 daemon (same as pm2 kill). Note that when the daemon is killed, all its * processes are also killed. Also note that you still have to explicitly disconnect * from the daemon even after you kill it. * @param errback */ export function killDaemon(errback: ErrProcDescCallback): void; /** * Returns various information about a process: eg what stdout/stderr and pid files are used. * @param process - Can either be the name as given in the pm2.start options, * a process id, or the string “all” to indicate that all scripts should be restarted. * @param errback */ export function describe(process: string|number, errback: ErrProcDescsCallback): void; /** * Gets the list of running processes being managed by pm2. * @param errback */ export function list(errback: ErrProcDescsCallback): void; /** * Writes the process list to a json file at the path in the DUMP_FILE_PATH environment variable * (“~/.pm2/dump.pm2” by default). * @param errback */ export function dump(errback: ErrResultCallback): void; /** * Flushes the logs. * @param process - Can either be the name as given in the pm2.start options, * a process id, or the string “all” to indicate that all scripts should be restarted. * @param errback */ export function flush(process: number|string, errback: ErrResultCallback): void; /** * @param errback */ export function dump(errback: ErrResultCallback): void; /** * Rotates the log files. The new log file will have a higher number * in it (the default format being ${process.name}-${out|err}-${number}.log). * @param errback */ export function reloadLogs(errback: ErrResultCallback): void; /** * Opens a message bus. * @param errback The bus will be an Axon Sub Emitter object used to listen to and send events. */ export function launchBus(errback: ErrBusCallback): void; /** * @param signal * @param process - Can either be the name as given in the pm2.start options, * a process id, or the string “all” to indicate that all scripts should be restarted. * @param errback */ export function sendSignalToProcessName(signal:string|number, process: number|string, errback: ErrResultCallback): void; /** * - Registers the script as a process that will start on machine boot. The current process list will be dumped and saved for resurrection on reboot. * @param platform * @param errback */ export function startup(platform: Platform, errback: ErrResultCallback): void; /** * - Send an set of data as object to a specific process * @param proc_id * @param packet * @param cb */ export function sendDataToProcessId(proc_id: number, packet: object, cb: ErrResultCallback): void; /** * - Send an set of data as object to a specific process * @param packet {id: number, type: 'process:msg', topic: true, data: object} */ export function sendDataToProcessId(packet: {id: number, type: 'process:msg', topic: true, data: object}): void; /** * Launch system monitoring (CPU, Memory usage) * @param errback - Called when monitoring is launched */ export function launchSysMonitoring(errback?: ErrCallback): void; /** * Profile CPU or Memory usage * @param type - 'cpu' for CPU profiling, 'mem' for memory profiling * @param time - Duration in seconds (default: 10) * @param errback - Called when profiling is complete */ export function profile(type: 'cpu' | 'mem', time?: number, errback?: ErrCallback): void; /** * Get process environment variables * @param app_id - Process name or id * @param errback - Called with environment variables */ export function env(app_id: string | number, errback?: ErrCallback): void; /** * Get process PID * @param app_name - Process name (optional, returns all PIDs if not provided) * @param errback - Called with PID information */ export function getPID(app_name?: string, errback?: ErrProcCallback): void; /** * Trigger a custom action on a process * @param pm_id - Process id * @param action_name - Name of the action to trigger * @param params - Parameters to pass to the action * @param errback - Called when action completes */ export function trigger(pm_id: string | number, action_name: string, params?: any, errback?: ErrCallback): void; /** * Inspect a process (debugging) * @param app_name - Process name * @param errback - Called with inspect information */ export function inspect(app_name: string, errback?: ErrCallback): void; /** * Serve static files * @param path - Path to serve files from * @param port - Port number (default: 8080) * @param options - Serve options * @param errback - Called when server starts */ export function serve(path?: string, port?: number, options?: ServeOptions, errback?: ErrCallback): void; /** * Install a PM2 module * @param module_name - Name of the module to install * @param options - Installation options * @param errback - Called when installation completes */ export function install(module_name: string, options?: InstallOptions, errback?: ErrCallback): void; /** * Uninstall a PM2 module * @param module_name - Name of the module to uninstall * @param errback - Called when uninstallation completes */ export function uninstall(module_name: string, errback?: ErrCallback): void; /** * Send line to process stdin * @param pm_id - Process id * @param line - Line to send * @param separator - Line separator (default: '\n') * @param errback - Called when line is sent */ export function sendLineToStdin(pm_id: string | number, line: string, separator?: string, errback?: ErrCallback): void; /** * Attach to process logs * @param pm_id - Process id * @param separator - Log separator * @param errback - Called when attached */ export function attach(pm_id: string | number, separator?: string, errback?: ErrCallback): void; /** * Get PM2 configuration value * @param key - Configuration key (optional, returns all config if not provided) * @param errback - Called with configuration value */ export function get(key?: string, errback?: ErrCallback): void; /** * Set PM2 configuration value * @param key - Configuration key * @param value - Configuration value * @param errback - Called when value is set */ export function set(key: string, value: any, errback?: ErrCallback): void; /** * Set multiple PM2 configuration values * @param values - Configuration values as string * @param errback - Called when values are set */ export function multiset(values: string, errback?: ErrCallback): void; /** * Unset PM2 configuration value * @param key - Configuration key to unset * @param errback - Called when value is unset */ export function unset(key: string, errback?: ErrCallback): void; // Interfaces export interface Proc { name?: string; vizion?: boolean; autorestart?: boolean; exec_mode?: string; exec_interpreter?: string; pm_exec_path?: string; pm_cwd?: string; instances?: number; node_args?: string[]; pm_out_log_path?: string; pm_err_log_path?: string; pm_pid_path?: string; status?: string; pm_uptime?: number; axm_actions?: any[]; axm_monitor?: any; axm_dynamic?: any; vizion_running?: boolean; created_at?: number; pm_id?: number; restart_time?: number; unstable_restarts?: number; started_inside?: boolean; command?: Command; versioning?: any; exit_code?: number; } export interface Command { locked?: boolean; metadata?: any; started_at?: any; finished_at?: any; error?: any; } /** * An object with information about the process. */ export interface ProcessDescription { /** * The name given in the original start command. */ name?: string; /** * The pid of the process. */ pid?: number; /** * The pid for the pm2 God daemon process. */ pm_id?: number; monit?: Monit; /** * The list of path variables in the process’s environment */ pm2_env?: Pm2Env; } interface Monit { /** * The number of bytes the process is using. */ memory?: number; /** * The percent of CPU being used by the process at the moment. */ cpu?: number; } /** * The list of path variables in the process’s environment */ interface Pm2Env { /** * The working directory of the process. */ pm_cwd?: string; /** * The stdout log file path. */ pm_out_log_path?: string; /** * The stderr log file path. */ pm_err_log_path?: string; /** * The interpreter used. */ exec_interpreter?: string; /** * The uptime of the process. */ pm_uptime?: number; /** * The number of unstable restarts the process has been through. */ unstable_restarts?: number; restart_time?: number; status?: ProcessStatus; /** * The number of running instances. */ instances?: number | 'max'; /** * The path of the script being run in this process. */ pm_exec_path?: string; } export interface StartOptions { /** * Enable or disable auto start after process added (default: true). */ autostart?: boolean; /** * Enable or disable auto restart after process failure (default: true). */ autorestart?: boolean; /** * List of exit codes that should allow the process to stop (skip autorestart). */ stop_exit_codes?: number[]; /** * An arbitrary name that can be used to interact with (e.g. restart) the process * later in other commands. Defaults to the script name without its extension * (eg “testScript” for “testScript.js”) */ name?: string; /** * The path of the script to run */ script?: string; /** * A string or array of strings composed of arguments to pass to the script. */ args?: string | string[]; /** * A string or array of strings composed of arguments to call the interpreter process with. * Eg “–harmony” or [”–harmony”,”–debug”]. Only applies if interpreter is something other * than “none” (its “node” by default). */ interpreter_args?: string | string[]; /** * The working directory to start the process with. */ cwd?: string; /** * (Default: “~/.pm2/logs/app_name-out.log”) The path to a file to append stdout output to. * Can be the same file as error. */ output?: string; /** * (Default: “~/.pm2/logs/app_name-error.err”) The path to a file to append stderr output to. Can be the same file as output. */ error?: string; /** * The display format for log timestamps (eg “YYYY-MM-DD HH:mm Z”). The format is a moment display format. */ log_date_format?: string; /** * Default: “~/.pm2/logs/~/.pm2/pids/app_name-id.pid”) * The path to a file to write the pid of the started process. The file will be overwritten. * Note that the file is not used in any way by pm2 and so the user is free to manipulate or * remove that file at any time. The file will be deleted when the process is stopped or the daemon killed. */ pid?: string; /** * The minimum uptime of the script before it’s considered successfully started. */ min_uptime?: number; /** * The maximum number of times in a row a script will be restarted if it exits in less than min_uptime. */ max_restarts?: number; /** * If sets and script’s memory usage goes about the configured number, pm2 restarts the script. * Uses human-friendly suffixes: ‘K’ for kilobytes, ‘M’ for megabytes, ‘G’ for gigabytes’, etc. Eg “150M”. */ max_memory_restart?: number | string; /** * Arguments to pass to the interpreter */ node_args?: string | string[]; /** * Prefix logs with time */ time?: boolean; /** * This will make PM2 listen for that event. In your application you will need to add process.send('ready'); * when you want your application to be considered as ready. */ wait_ready?: boolean; /** * (Default: 1600) * The number of milliseconds to wait after a stop or restart command issues a SIGINT signal to kill the * script forceably with a SIGKILL signal. */ kill_timeout?: number; /** * (Default: 0) Number of millseconds to wait before restarting a script that has exited. */ restart_delay?: number; /** * (Default: “node”) The interpreter for your script (eg “python”, “ruby”, “bash”, etc). * The value “none” will execute the ‘script’ as a binary executable. */ interpreter?: string; /** * (Default: ‘fork’) If sets to ‘cluster’, will enable clustering * (running multiple instances of the script). */ exec_mode?: string; /** * (Default: 1) How many instances of script to create. Only relevant in exec_mode ‘cluster’. */ instances?: number; /** * (Default: false) If true, merges the log files for all instances of script into one stderr log * and one stdout log. Only applies in ‘cluster’ mode. For example, if you have 4 instances of * ‘test.js’ started via pm2, normally you would have 4 stdout log files and 4 stderr log files, * but with this option set to true you would only have one stdout file and one stderr file. */ merge_logs?: boolean; /** * If set to true, the application will be restarted on change of the script file. */ watch?: boolean|string[]; /** * (Default: false) By default, pm2 will only start a script if that script isn’t * already running (a script is a path to an application, not the name of an application * already running). If force is set to true, pm2 will start a new instance of that script. */ force?: boolean; ignore_watch?: string[]; cron?: any; execute_command?: any; write?: any; source_map_support?: any; disable_source_map_support?: any; /** * The environment variables to pass on to the process. */ env?: { [key: string]: string; }; /** * NameSpace for the process * @default 'default' * @example 'production' * @example 'development' * @example 'staging' */ namespace?: string; /** * (Default: false) Exponential backoff restart delay in milliseconds. * When enabled, PM2 will progressively increase restart delays after failures. */ exp_backoff_restart_delay?: number; /** * Timeout for application to be ready after reload (in milliseconds). */ listen_timeout?: number; /** * (Default: false) If true, shutdown the process using process.send('shutdown') instead of process.kill(). */ shutdown_with_message?: boolean; /** * Environment variable name that gets incremented for each cluster instance. */ increment_var?: string; /** * Name of the environment variable holding the instance ID. * @default 'NODE_APP_INSTANCE' */ instance_var?: string; /** * Filter out specific environment variables from the process. * Can be true to filter all, or array/string of specific variables. */ filter_env?: boolean | string | string[]; /** * (Default: false) Disable logs output. */ disable_logs?: boolean; /** * Log output type. */ log_type?: string; /** * (Default: false) Enable container mode. */ container?: boolean; /** * (Default: false) Distribution mode for Docker. */ dist?: boolean; /** * Docker image name. */ image_name?: string; /** * Node.js version for Docker container. */ node_version?: string; /** * (Default: false) Fresh install for Docker. */ fresh?: boolean; /** * (Default: false) Docker daemon mode. */ dockerdaemon?: boolean; } interface ReloadOptions { /** * (Default: false) If true is passed in, pm2 will reload it's environment from process.env * before reloading your process. */ updateEnv?: boolean; } /** * Options for serving static files */ export interface ServeOptions { /** * (Default: false) Single Page Application mode */ spa?: boolean; /** * Basic authentication username */ basic_auth_username?: string; /** * Basic authentication password */ basic_auth_password?: string; /** * Monitor URL path */ monitor?: string; } /** * Options for Docker operations */ export interface DockerOptions { /** * Docker image name */ imageName?: string; /** * Node.js version to use */ nodeVersion?: string; /** * (Default: false) Fresh installation */ fresh?: boolean; /** * (Default: false) Force operation */ force?: boolean; /** * (Default: false) Docker daemon mode */ dockerdaemon?: boolean; } /** * Options for module installation */ export interface InstallOptions { /** * (Default: false) Install from tarball */ tarball?: boolean; /** * (Default: true) Perform installation */ install?: boolean; /** * (Default: false) Docker mode */ docker?: boolean; /** * (Default: false) Use v1 API */ v1?: boolean; /** * (Default: false) Safe mode installation */ safe?: boolean | number; } // Types type ProcessStatus = 'online' | 'stopping' | 'stopped' | 'launching' | 'errored' | 'one-launch-status' | 'waiting_restart'; type Platform = 'ubuntu' | 'centos' | 'redhat' | 'gentoo' | 'systemd' | 'darwin' | 'amazon'; type ErrCallback = (err: Error) => void; type ErrProcCallback = (err: Error, proc: Proc) => void; type ErrProcDescCallback = (err: Error, processDescription: ProcessDescription) => void; type ErrProcDescsCallback = (err: Error, processDescriptionList: ProcessDescription[]) => void; type ErrResultCallback = (err: Error, result: any) => void; type ErrBusCallback = (err: Error, bus: any) => void; ================================================ FILE: types/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "lib": ["es6"], "noImplicitAny": true, "noImplicitThis": true, "strictNullChecks": true, // If the library is an external module (uses `export`), this allows your test file to import "mylib" instead of "./index". // If the library is global (cannot be imported via `import` or `require`), leave this out. "baseUrl": ".", "paths": { "mylib": ["."] } } }