modified: cpi.pl
[GalaxyCodeBases.git] / etc / whichpm / Makefile
blob36cf27c4883cca20b030c3c8a1c8837d978be7a3
1 # Since we rely on paths relative to the Makefile location, abort if make isn't being run from there.
2 $(if $(findstring /,$(MAKEFILE_LIST)),$(error Please only invoke this makefile from the directory it resides in))
3 # Run all shell commands with bash.
4 SHELL := bash
5 # Add the local npm packages' bin folder to the PATH, so that `make` can find them even when invoked directly (not via npm).
6 # !! Note that this extended path only takes effect in (a) recipe commands that are (b) true shell commands (not optimized away) - when in doubt, simply append ';'
7 # !! To also use the extended path in $(shell ...) function calls, use $(shell PATH="$(PATH)" ...),
8 export PATH := $(PWD)/node_modules/.bin:$(PATH)
9 # Sanity check: git repo must exist.
10 $(if $(shell [[ -d .git ]] && echo ok),,$(error No git repo found in current dir. Please at least initialize one with 'git init'))
11 # Sanity check: make sure dev dependencies (and npm) are installed - skip this check only for certain generic targets (':' is the pseudo target used by the `list` target's recipe.)
12 $(if $(or $(shell [[ '$(MAKECMDGOALS)' =~ list|: ]] && echo ok), $(shell [[ -d ./node_modules/semver ]] && echo 'ok')),,$(error Did you forget to run `npm install` after cloning the repo (Node.js must be installed)? At least one of the required dev dependencies not found))
13 # Determine the editor to use for modal editing. Use the same as for git, if configured; otherwise $EDITOR, then fall back to vi (which may be vim).
14 EDITOR := $(shell git config --global --get core.editor || echo "$${EDITOR:-vi}")
16 # Default target (by virtue of being the first non '.'-prefixed target in the file).
17 .PHONY: _no-target-specified
18 _no-target-specified:
19 $(error Please specify the target to make - `make list` shows targets. Alternatively, use `npm test` to run the default tests; `npm run` shows all commands)
21 # Lists all targets defined in this makefile.
22 .PHONY: list
23 list:
24 @$(MAKE) -pRrn -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | grep -Ev -e '^[^[:alnum:]]' -e '^$@$$' | sort
26 # Open this package's online repository URL (typically, on GitHub) in the default browser.
27 # Note: Supported on OSX and Freedesktop-compliant systems, which includes many Linux and BSD variants.
28 .PHONY: browse
29 browse:
30 @exe=; url=`json -f package.json repository.url` || exit; \
31 [[ `uname` == 'Darwin' ]] && exe='open'; \
32 [[ -n `command -v xdg-open` ]] && exe='xdg-open'; \
33 [[ -n $$exe ]] || { echo "Don't know how to open $$url in the default browser on this platform." >&2; exit 1; }; \
34 "$$exe" "$$url"
36 # Open this package's page in the npm registry.
37 # Note: Supported on OSX and Freedesktop-compliant systems, which includes many Linux and BSD variants.
38 .PHONY: browse-npm
39 browse-npm:
40 @exe=; [[ `json -f package.json private` == 'true' ]] && { echo "This package is marked private (not for publication in the npm registry)." >&2; exit 1; }; \
41 url="https://www.npmjs.com/package/`json -f package.json name`" || exit; \
42 [[ `uname` == 'Darwin' ]] && exe='open'; \
43 [[ -n `command -v xdg-open` ]] && exe='xdg-open'; \
44 [[ -n $$exe ]] || { echo "Don't know how to open $$url in the default browser on this platform." >&2; exit 1; }; \
45 "$$exe" "$$url"
47 .PHONY: test
48 # To optionally skip tests in the context of target 'release', for instance, invoke with NOTEST=1; e.g.: make release NOTEST=1
49 test:
50 ifeq ($(NOTEST),1)
51 @echo Note: Skipping tests, as requested. >&2
52 else
53 @exists() { [ -e "$$1" ]; }; exists ./test/* || { echo "(No tests defined.)" >&2; exit 0; }; \
54 if [[ -n $$(json -f package.json main) ]]; then tap ./test; else urchin ./test; fi
55 endif
57 # Commits (with prompt for message) and pushes to the branch of the same name in remote repo 'origin',
58 # but *without* tags, so as to allow quick pushing of changes without running into problems with tag redefinitions.
59 # (Tags are only pushed - forcefully - with `make release`.)
60 .PHONY: push
61 push: _need-clean-ws-or-no-untracked-files
62 @[[ -z $$(git status --porcelain || echo no) ]] && echo "-- (Nothing to commit.)" || { git commit || exit; echo "-- Committed."; }; \
63 targetBranch=`git symbolic-ref --short HEAD` || exit; \
64 git push origin "$$targetBranch" || exit; \
65 echo "-- Pushed."
68 # Reports the current version number - both from package.json and as defined by the latest git tag
69 # Implementation note: simply uses 'version' as a prerequisite, which queries $(MAKECMDGOALS) to adjust its behavior based on the caller.
70 .PHONY: verinfo
71 verinfo: version
73 # Increments the package's version number:
74 # Unless called via 'make verinfo', the workspace must be clean or at least have no untracked files.
75 # If VER is *not* specified in the environment:
76 # Reports the current version number - both from package.json and as defined by the latest git tag.
77 # If 'make version' was called directly, then prompts to change the version number.
78 # If called via 'make release', only prompts to change the version number if the git tag version number is the same as the package's.
79 # VER is set to the value entered and processing continues below.
80 # If VER *is* specified or continuing from above:
81 # Validates the new version number:
82 # If an increment specifier was given, increments from the latest package.json version number (as the version numbers stored in source files are assumed to be in sync with package.json).
83 # Implementation note: semver, as of v4.3.6, does not validate increment specifiers and simply defaults to 'patch' in case of an valid specifier; thus, we roll our own validation here.
84 # An increment specifier starting with 'pre' increments [to] a prerelease version number. By default, this simply appends or increments '-<num>', whereas '--preid <id>' can be used
85 # to append '-<id><num>' instead; however, we don't expose that, at least for now, though the user may specify an explicit, full pre-release version number.
86 # We use tag 'pre' with npm publish --tag, so as to have the latest prerelease be installable with <pkg>@pre, analogous to the (implicit) 'latest' tag that tracks production releases.
87 # An explicitly specified version number must be *higher* than the current one; pass variable FORCE=1 to override this in exceptional situations.
88 # Updates the version number in package.json and in source files in ./bin and ./lib.
89 .PHONY: version
90 version:
91 @[[ '$(MAKECMDGOALS)' == *verinfo* ]] && infoOnly=1 || infoOnly=0; \
92 gitTagVer=`git describe --abbrev=0 --match 'v[0-9]*.[0-9]*.[0-9]*' 2>/dev/null || echo '(none)'` || exit; gitTagVer=$${gitTagVer#v}; \
93 pkgVer=`json -f package.json version` || exit; \
94 if [[ -z $$VER ]]; then \
95 printf 'CURRENT version:\n\t%s (package.json)\n\t%s (git tag)\n' "$$pkgVer" "$$gitTagVer"; \
96 (( infoOnly )) && exit; \
97 [[ $$pkgVer != "$$gitTagVer" && $$pkgVer != '0.0.0' ]] && { alreadyBumped=1 || alreadyBumped=0; }; \
98 if [[ '$(MAKECMDGOALS)' == 'release' && $$alreadyBumped -eq 1 ]]; then \
99 printf "=== `[[ $$pkgVer == *-* ]] && printf 'PRE-'`RELEASING:\n\t%s -> **%s** \n===\n" "$$gitTagVer" "$$pkgVer"; \
100 read -p '(Y)es or (c)hange (y/c/N)?: ' -re response && [[ "$$response" == [yYcC] ]] || { echo 'Aborted.' >&2; exit 2; }; \
101 [[ $$response =~ [yY] ]] && exit 0; \
102 alreadyBumped=0; \
103 fi; \
104 if [[ '$(MAKECMDGOALS)' == 'version' || $$alreadyBumped -eq 0 ]]; then \
105 echo "==="; \
106 echo "Enter new version number in full or as one of: 'patch', 'minor', 'major', optionally prefixed with 'pre', or 'prerelease'."; \
107 echo "(Alternatively, pass a value from the command line with 'VER=<new-ver>'.)"; \
108 read -p "NEW VERSION number (just Enter to abort)?: " -re VER && { [[ -z $$VER ]] && echo 'Aborted.' >&2 && exit 2; }; \
109 fi; \
110 fi; \
111 oldVer=$$pkgVer; \
112 newVer=$${VER#v}; \
113 if printf "$$newVer" | grep -q '^[0-9]'; then \
114 semver "$$newVer" >/dev/null || { echo "Invalid semver version number specified: $$VER" >&2; exit 2; }; \
115 [[ "$(FORCE)" != '1' ]] && { semver -r "> $$oldVer" "$$newVer" >/dev/null || { echo "Invalid version number specified: $$VER - must be HIGHER than $$oldVer. To force this change, use FORCE=1 on the command line." >&2; exit 2; }; } \
116 else \
117 [[ $$newVer =~ ^(patch|minor|major|prepatch|preminor|premajor|prerelease)$$ ]] && newVer=`semver -i "$$newVer" "$$oldVer"` || { echo "Invalid version-increment specifier: $$VER" >&2; exit 2; } \
118 fi; \
119 printf "=== About to BUMP VERSION:\n\t$$oldVer -> **$$newVer**\n===\nProceed (y/N)?: " && read -re response && [[ "$$response" = [yY] ]] || { echo 'Aborted.' >&2; exit 2; }; \
120 for dir in ./bin ./lib; do [[ -d $$dir ]] && { replace --quiet --recursive "v$${oldVer//./\\.}" "v$${newVer}" "$$dir" || exit; }; done; \
121 [[ `json -f package.json version` == "$$newVer" ]] || { npm version $$newVer --no-git-tag-version >/dev/null && printf $$'\e[0;33m%s\e[0m\n' 'package.json' || exit; }; \
122 [[ $$gitTagVer == '(none)' ]] && newVerMdSnippet="**v$$newVer**" || newVerMdSnippet="**[v$$newVer](`json -f package.json repository.url | sed 's/.git$$//'`/compare/v$$gitTagVer...v$$newVer)**"; \
123 grep -Eq "\bv$${newVer//./\.}[^[:digit:]-]" CHANGELOG.md || { { sed -n '1,/^<!--/p' CHANGELOG.md && printf %s $$'\n* '"$$newVerMdSnippet"$$' ('"`date +'%Y-%m-%d'`"$$'):\n * ???\n' && sed -n '1,/^<!--/d; p' CHANGELOG.md; } > CHANGELOG.tmp.md && mv CHANGELOG.tmp.md CHANGELOG.md; }; \
124 printf -- "-- Version bumped to v$$newVer in source files and package.json (only just-now updated files were printed above, if any).\n Describe changes in CHANGELOG.md ('make release' will prompt for it).\n To update the read-me file, run 'make update-readme' (also happens during 'make release').\n"
126 # make release [VER=<newVerSpec>] [NOTEST=1]
127 # Increments the version number, runs tests, then commits and tags, pushes to origin, prompts to publish to the npm-registry; NOTEST=1 skips tests.
128 # VER=<newVerSpec> is mandatory, unless the version number in package.json is ahead of the latest Git version tag.
129 .PHONY: release
130 release: _need-origin _need-npm-credentials _need-master-branch _need-clean-ws-or-no-untracked-files version test
131 @newVer=`json -f package.json version` || exit; [[ $$newVer == *-* ]] && isPreRelease=1 || isPreRelease=0; \
132 echo '-- Opening changelog...'; \
133 $(EDITOR) CHANGELOG.md; \
134 changelogEntries=`sed -En -e '/\*\*\[?'"v$$newVer"'(\*|\])/,/^\* / { s///; t' -e 'p; }' CHANGELOG.md`; \
135 [[ -n $$changelogEntries ]] || { echo "ABORTED: No changelog entries provided for new version v$$newVer." >&2; exit 2; }; \
136 commitMsg="v$$newVer"$$'\n'"$$changelogEntries"; \
137 echo "-- Updating documentation, if applicable..."; \
138 $(MAKE) -f $(lastword $(MAKEFILE_LIST)) update-doc || exit; \
139 echo "-- Updating README.md..."; \
140 $(MAKE) -f $(lastword $(MAKEFILE_LIST)) update-license-year update-readme || exit; \
141 echo '-- Opening README.md for final inspection...'; \
142 $(EDITOR) README.md; \
143 grep -E '(^|[[:blank:]])\?\?\?([[:blank:]]|$$)' README.md && { echo "ABORTED: README.md still contains '???', the placeholder for missing information." >&2; exit 2; }; \
144 read -re -p "Ready to COMMIT, TAG, PUSH$$([[ `json -f package.json private` != 'true' ]] && echo ", and PUBLISH (prompted for separately)") (y/N)?: " response && [[ "$$response" =~ [yY] ]] || { echo 'Aborted.' >&2; exit 2; }; \
145 echo '-- Committing...'; \
146 git add --update . || exit; \
147 [[ -z $$(git status --porcelain || echo no) ]] && echo "-- (Nothing to commit.)" || { git commit -m "$$commitMsg" || exit; echo "-- v$$newVer committed."; }; \
148 git tag -f -a -m "$$commitMsg" "v$$newVer" || exit; { git tag -f "`(( isPreRelease )) && printf 'pre' || printf 'stable'`" || exit; }; \
149 echo "-- Tag v$$newVer created."; \
150 git push origin master || exit; git push -f origin master --tags; \
151 echo "-- v$$newVer pushed to origin."; \
152 if [[ `json -f package.json private` != 'true' ]]; then \
153 latestPreReleaseTag='pre'; \
154 printf "=== About to PUBLISH TO npm REGISTRY as `(( isPreRelease )) && printf 'PRE-RELEASE' || printf 'LATEST'` version:\n\t**`json -f package.json name`@$$newVer**\n===\nType 'publish' to proceed; anything else to abort: " && read -er response; \
155 [[ "$$response" == 'publish' ]] || { echo 'Aborted. Run `npm publish` on demand.' >&2; exit 2; }; \
156 { (( isPreRelease )) && npm publish --tag "$$latestPreReleaseTag" || npm publish; } || exit; \
157 echo "-- Published to npm`(( isPreRelease )) && printf " and tagged with '$$latestPreReleaseTag' to mark the latest pre-release"`."; \
158 else \
159 echo "-- (Package marked as private; not publishing to npm registry.)"; \
160 fi; \
161 echo "-- Done."
163 # Updates README.md as follows:
164 # - Replaces the '## Usage' chapter with the command-line help output by this package's CLI, if applicable.
165 # - Replaces the '### License' chapter with the contents of LICENSE.md
166 # - Replaces the '### npm Dependencies' chapter with the current list of dependencies.
167 # - Replaces the '## Changelog' chapter with the contents of CHANGELOG.md
168 # - Finally, places an auto-generated TOC at the top, if configured.
169 .PHONY: update-readme
170 update-readme: _update-readme-usage _update-readme-license _update-readme-dependencies _update-readme-changelog update-toc
171 @[[ '$(MAKECMDGOALS)' == 'update-readme' ]] && grep -E '(^|[[:blank:]])\?\?\?([[:blank:]]|$$)' README.md && echo "WARNING: README.md still contains '???', the placeholder for missing information." >&2; \
172 echo "-- README.md updated."
174 # If turned on: Updates the TOC in README.md - there is *generally* no need to call this *directly*, because the TOC is updated as part of the 'update-readme' target and, indirectly, the 'release' target.
175 # If this feature is turned off, this is a no-op.
176 # !! Note that a \n is prepended to the title to work around a npmjs.com rendering bug: without it, doctoc's comments would directly abut the title, which unexepctedly disables Markdown rendering (as of 31 May 2015).
177 .PHONY: update-toc
178 update-toc:
179 @[[ `json -f package.json net_same2u.make_pkg.tocOn` == 'true' ]] || { [[ '$(MAKECMDGOALS)' == 'update-toc' ]] && echo "WARNING: TOC generation is currently turned OFF. Use 'make toggle-toc' to activate." >&2; exit 0; }; \
180 doctoc --title $$'\n'"`json -f package.json net_same2u.make_pkg.tocTitle`" README.md >/dev/null || exit; \
181 [[ '$(MAKECMDGOALS)' == 'update-toc' ]] && echo "-- TOC in README.md updated." || :; \
183 # Note: For now, generating documentation is only supported for CLIs.
184 .PHONY: update-doc
185 update-doc: update-man
187 # If turned on: Extracts the Markdown-formatted man-page source assumed to be output by this package's CLI
188 # with --man-source and:
189 # - creates a man page (in ROFF format) in ./man/<cli>.1 with marked-man
190 # - extracts the Markdown source to ./doc/<cli>.md.
191 # If this package has no CLI or the feature is turned off, this is a no-op.
192 .PHONY: update-man
193 update-man:
194 @read -r cliName cliPath < <(json -f package.json bin | json -Ma key value | head -n 1) || { [[ '$(MAKECMDGOALS)' == 'update-man' ]] && echo "WARNING: Nothing to do; no CLI is defined for this package." >&2; exit 0; }; \
195 [[ `json -f package.json net_same2u.make_pkg.manOn` == 'true' ]] || { [[ '$(MAKECMDGOALS)' == 'update-man' ]] && echo "WARNING: man-page creation is currently turned OFF. Use 'make toggle-man' to activate." >&2; exit 0; }; \
196 ver='v'$$(json -f package.json version) || exit; \
197 mkdir -p doc man; \
198 printf '<!-- DO NOT EDIT THIS FILE: It is auto-generated by `make update-man` -->\n\n' > doc/"$$cliName".md; \
199 "$$cliPath" --man-source >> doc/"$$cliName".md || { printf "ERROR: Failed to extract man-page source.\nPlease ensure that '$$cliName --man-source' outputs the Markdown-formatted man-page source.\n" | fold -s >&2; exit 1; }; \
200 "$$cliPath" --man-source | marked-man --version "$$ver" > man/"$$cliName".1 || { echo "Do you need to install marked-man (npm install marked-man --save-dev)?" | fold -s >&2; exit 1; }; \
201 [[ '$(MAKECMDGOALS)' == 'update-man' ]] && echo "-- 'doc/$$cliName.md' and 'man/$$cliName.1' updated."$$'\n'"To view the latter as a man page, run: man man/$$cliName.1"$$'\n'"To update and view in one step, run: make view-man" || :
203 # If man-page creation is turned on: recreate the man page and view it with `man`.
204 .PHONY: view-man
205 view-man: update-man
206 @manfile=`json -f package.json man`; [[ -n $$manfile ]] || { echo "ERROR: No 'man' property found in 'package.json'." >&2; exit 2; }; \
207 man "$$manfile"
209 # Toggles inclusion of an auto-updating TOC in README.md via doctoc.
210 .PHONY: toggle-toc
211 toggle-toc:
212 @isOn=$$([[ `json -f package.json net_same2u.make_pkg.tocOn` == 'true' ]] && printf 1 || printf 0); \
213 nowState=`(( isOn )) && printf 'ON' || printf 'OFF'`; otherState=`(( isOn )) && printf 'OFF' || printf 'ON'`; \
214 echo "Inclusion of an auto-updating TOC for README.md is currently $$nowState."; \
215 read -re -p "Turn it $$otherState (y/N)?: " response && [[ "$$response" =~ [yY] ]] || { exit 0; }; \
216 json -I -f package.json -e 'this.net_same2u || (this.net_same2u = {}); this.net_same2u.make_pkg || (this.net_same2u.make_pkg = {}); this.net_same2u.make_pkg.tocOn = '`(( isOn )) && printf 'false' || printf 'true'`'; this.net_same2u.make_pkg.tocTitle || (this.net_same2u.make_pkg.tocTitle = "**Contents**")' || exit; \
217 if (( isOn )); then \
218 echo "NOTE: To be safe, no attempt was made to remove any existing TOC from README.md, if present." | fold -s >&2; \
219 else \
220 echo "-- Automatic TOC generation for README.md activated."; \
221 printf "Run 'make update-toc' to insert a TOC now.\n'make update-readme' and 'make release' will now update it automatically.\n" | fold -s; \
224 # Toggles generation of a man page via marked-man, based on a Markdown-formatted document
225 # that the package's CLI must output with --man-source.
226 .PHONY: toggle-man
227 toggle-man:
228 @isOn=$$([[ `json -f package.json net_same2u.make_pkg.manOn` == 'true' ]] && printf 1 || printf 0); \
229 nowState=`(( isOn )) && printf 'ON' || printf 'OFF'`; otherState=`(( isOn )) && printf 'OFF' || printf 'ON'`; \
230 echo "Generating a man page for this package's CLI is currently $$nowState."; \
231 read -re -p "Turn it $$otherState (y/N)?: " response && [[ "$$response" =~ [yY] ]] || { exit 0; }; \
232 if (( ! isOn )); then \
233 read -r cliName cliPath < <(json -f package.json bin | json -Ma key value | head -n 1); \
234 [[ -n $$cliName ]] || { echo "ERROR: No CLI declared in 'package.json'; please declare a CLI via the 'bin' property and try again." | fold -s >&2; exit 1; }; \
235 fi; \
236 json -I -f package.json -e 'this.net_same2u || (this.net_same2u = {}); this.net_same2u.make_pkg || (this.net_same2u.make_pkg = {}); this.net_same2u.make_pkg.manOn = '`(( isOn )) && printf 'false' || printf 'true'` || exit; \
237 if (( isOn )); then \
238 echo "-- Man-page creation is now OFF."; \
239 echo "NOTE: To be safe, a 'man' property, if present, was not removed from 'package.json', and no attempt was made to uninstall the 'marked-man' package, if present. Please make required changes manually." | fold -s >&2; \
240 else \
241 [[ -n `json -f package.json devDependencies.marked-man` ]] || { echo "-- Installing marked-man as a dev. dependency..."; npm install --save-dev marked-man || exit; }; \
242 [[ -n `json -f package.json man` ]] && { echo "NOTE: Retaining existing 'man' property in 'package.json'." >&2; } || \
243 { json -I -f package.json -e "this.man = \"./man/$$cliName.1\"" || exit; }; \
244 echo "-- Man-page creation is now ON."; echo "Run 'make update-man' to generate the man page now."$$'\n'"Note that '$$cliName --man-source' must output the man-page source in Markdown format for this to work." | fold -s; \
248 # Updates LICENSE.md if the stated calendar year (e.g., '2015') / the end point in a calendar-year range (e.g., '2014-2015')
249 # lies in the past; E.g., if the current calendary year is 2016, the first example is updated to '2015-2016', and the second
250 # one to '2014-2016'.
251 .PHONY: update-license-year
252 update-license-year:
253 @f='LICENSE.md'; thisYear=`date +%Y`; yearRange=`sed -n 's/.*(c) \([0-9]\{4\}\)\(-[0-9]\{4\}\)\{0,1\}.*/\1\2/p' "$$f"`; \
254 [[ -n $$yearRange ]] || { echo "Failed to extract calendar year(s) from '$$f'." >&2; exit 1; }; laterYear=$${yearRange#*-}; \
255 if (( laterYear < thisYear )); then \
256 replace -s '(\(c\) )([0-9]{4})(-[0-9]{4})?' '$$1$$2-'"$$thisYear" "$$f" || exit; \
257 echo "NOTE: '$$f' updated to reflect current calendar year, $$thisYear."; \
258 elif [[ '$(MAKECMDGOALS)' == 'update-license-year' ]]; then \
259 echo "('$$f' calendar year(s) are up-to-date: $$yearRange)"; \
262 # --------- Aux. targets
264 # If applicable, replaces the usage read-me chapter with the current CLI help output,
265 # enclosed in a fenced codeblock and preceded by '$ <cmd> --help'.
266 # Replacement is attempted if the project at hand has a (at least one) CLI, as defined in the 'bin' key in package.json.
267 # is an *object* that has (at least 1) property (rather than containing a string-scalar value that implies the package name as the CLI name).
268 # - If 'bin' has *multiple* properties, the *1st* is the one whose usage info is to be used.
269 # To change this, modify CLI_HELP_CMD in the shell command below.
270 .PHONY: _update-readme-usage
271 # The arguments to pass to the CLI to have it output its help.
272 CLI_HELP_ARGS:= --help
273 # Note that the recipe exits right away if no CLIs are found in 'package.json'.
274 # TO DISABLE THIS RULE, REMOVE ALL OF ITS RECIPE LINES.
275 _update-readme-usage:
276 @read -r cliName cliPath < <(json -f package.json bin | json -Ma key value | head -n 1) || exit 0; \
277 CLI_HELP_CMD=( "$$cliPath" $(CLI_HELP_ARGS) ); \
278 CLI_HELP_CMD_DISPLAY=( "$${CLI_HELP_CMD[@]}" ); CLI_HELP_CMD_DISPLAY[0]="$$cliName"; \
279 newText="$${CLI_HELP_CMD_DISPLAY[@]}"$$'\n\n'"$$( "$${CLI_HELP_CMD[@]}" )" || { echo "Failed to update read-me chapter: usage: invoking CLI help failed: $${CLI_HELP_CMD[@]}" >&2; exit 1; }; \
280 newText="$${newText//\$$/$$\$$}"; \
281 newText="$${newText//~/\~}"; \
282 replace --count --quiet --multiline=false '(\n)(<!-- DO NOT EDIT .*usage.*?-->\n\s*?\n```nohighlight\n\$$ )[\s\S]*?(\n```\n|$$)' '$$1$$2'"$$newText"'$$3' README.md | grep -Fq ' (1)' || { echo "Failed to update read-me chapter: usage." >&2; exit 1; }
283 # !! REGRETTABLY, the ``` sequences in the line above break syntax coloring for the rest of the file in Sublime Text 3 - ?? unclear, how to work around that.
285 # - Replaces the '## License' chapter with the contents of LICENSE.md
286 .PHONY: _update-readme-license
287 # TO DISABLE THIS RULE, REMOVE ALL OF ITS RECIPE LINES.
288 _update-readme-license:
289 @newText=$$'\n'"$$(< LICENSE.md)"$$'\n'; \
290 newText="$${newText//\$$/$$\$$}"; \
291 replace --count --quiet --multiline=false '(^|\n)(#+ License\n)[\s\S]*?(\n([ \t]*<!-- .*? -->\s*?\n)?#|$$)' '$$1$$2'"$$newText"'$$3' README.md | grep -Fq ' (1)' || { echo "Failed to update read-me chapter: license." >&2; exit 1; }
293 # - Replaces the dependencies chapter with the current list of dependencies.
294 .PHONY: _update-readme-dependencies
295 # A regex that matches the chapter heading to replace in README.md; watch for unintentional trailing whitespace. '#' must be represented as '\#'.
296 README_HEADING_DEPENDENCIES := \#+ npm dependencies
297 # TO DISABLE THIS RULE, REMOVE ALL OF ITS RECIPE LINES.
298 _update-readme-dependencies:
299 @newText=$$'\n'$$( \
300 keys=( dependencies peerDependencies devDependencies optionalDependencies ); \
301 qualifiers=( '' '(P)' '(D)' '(O)'); \
302 i=0; \
303 for key in "$${keys[@]}"; do \
304 json -f ./package.json $$key | json -ka | { \
305 while read -r pn; do \
306 hp=$$(json -f "./node_modules/$$pn/package.json" homepage); \
307 echo "* [$$pn$${qualifiers[i]:+ $${qualifiers[i]}}]($$hp)"; \
308 done \
309 }; \
310 (( ++i )); \
311 done)$$'\n'; \
312 [[ -n $$newText ]] || { echo "Failed to determine npm dependencies." >&2; exit 1; }; \
313 newText="$${newText//\$$/$$\$$}"; \
314 replace --count --quiet --multiline=false '(^|\n)($(README_HEADING_DEPENDENCIES)\n)[\s\S]*?(\n([ \t]*<!-- .*? -->\s*?\n)?#|$$)' '$$1$$2'"$$newText"'$$3' README.md | grep -Fq ' (1)' || { echo "Failed to update read-me chapter: npm dependencies." >&2; exit 1; }
316 # - Replaces the changelog chapter with the contents of CHANGELOG.md
317 .PHONY: _update-readme-changelog
318 # A regex that matches the chapter heading to replace in README.md; watch for unintentional trailing whitespace. '#' must be represented as '\#'.
319 README_HEADING_CHANGELOG := \#+ Changelog
320 # TO DISABLE THIS RULE, REMOVE ALL OF ITS RECIPE LINES.
321 _update-readme-changelog:
322 @newText=$$'\n'"$$(tail -n +3 CHANGELOG.md)"$$'\n'; \
323 newText="$${newText//\$$/$$\$$}"; \
324 replace --count --quiet --multiline=false '(^|\n)($(README_HEADING_CHANGELOG)\n)[\s\S]*?(\n([ \t]*<!-- .*? -->\s*?\n)?#|$$)' '$$1$$2'"$$newText"'$$3' README.md | grep -Fq ' (1)' || { echo "Failed to update read-me chapter: changelog." >&2; exit 1; }
326 .PHONY: _need-master-branch
327 _need-master-branch:
328 @[[ `git symbolic-ref --short HEAD` == 'master' ]] || { echo 'Please release from the master branch only.' >&2; exit 2; }
330 # Ensures that the git workspace is clean or contains no untracked files - any tracked files are implicitly added to the index.
331 .PHONY: _need-clean-ws-or-no-untracked-files
332 _need-clean-ws-or-no-untracked-files:
333 @git add --update . || exit
334 @[[ -z $$(git status --porcelain | awk -F'\0' '$$2 != " " { print $$2 }') ]] || { echo "Workspace must either be clean or contain no untracked files; please add untracked files to the index first (e.g., \`git add .\`) or delete them." >&2; exit 2; }
336 # Ensure that a remote git repo named 'origin' is defined.
337 .PHONY: _need-origin
338 _need-origin:
339 @git remote | grep -Fqx 'origin' || { echo "ERROR: Remote git repo 'origin' must be defined." >&2; exit 2; }
341 # Unless the package is marked private, ensure that npm credentials have been saved.
342 .PHONY: _need-npm-credentials
343 _need-npm-credentials:
344 @[[ `json -f package.json private` == 'true' ]] && exit 0; \
345 grep -Eq '^//registry.npmjs.org/:(_password|_authToken)=' ~/.npmrc || { echo "ERROR: npm-registry credentials not found. Please log in with 'npm login' in order to enable publishing." >&2; exit 2; }; \