Compare commits
13 commits
Author | SHA1 | Date | |
---|---|---|---|
c97472f6f5 | |||
3239a7c611 | |||
820a49efd8 | |||
e44fc052f2 | |||
bb375e62ea | |||
0ce99d9235 | |||
13b4c3a640 | |||
e7a67ebfa0 | |||
f8cf9bad7d | |||
acb07d0cdc | |||
a2e53e32a6 | |||
6bbfd25745 | |||
536aaa01b0 |
20 changed files with 265 additions and 101 deletions
16
Makefile
16
Makefile
|
@ -1,9 +1,10 @@
|
||||||
.PHONY: help build clean update-npmjs-readme release publish
|
.PHONY: help prod dev clean update-npmjs-readme release publish
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "Available targets:"
|
@echo "Available targets:"
|
||||||
@echo " - help: Show this help message"
|
@echo " - help: Show this help message"
|
||||||
@echo " - build: Build the project"
|
@echo " - prod: Build the project in production mode"
|
||||||
|
@echo " - dev: Build the project in development mode"
|
||||||
@echo " - clean: Remove build artifacts"
|
@echo " - clean: Remove build artifacts"
|
||||||
@echo " - release: Create a new release version"
|
@echo " - release: Create a new release version"
|
||||||
@echo " - publish: Publish the new version created with the release target"
|
@echo " - publish: Publish the new version created with the release target"
|
||||||
|
@ -11,14 +12,17 @@ help:
|
||||||
node_modules:
|
node_modules:
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
build: node_modules
|
prod: node_modules
|
||||||
npm run build
|
npm run prod
|
||||||
chmod +x dist/wtc
|
|
||||||
|
dev: node_modules
|
||||||
|
npm run dev
|
||||||
|
chmod +x dist/wtc-dev.mjs
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -r dist node_modules
|
rm -r dist node_modules
|
||||||
|
|
||||||
release: build
|
release: prod
|
||||||
@read -p "Enter version bump (patch, minor, major): " bump && \
|
@read -p "Enter version bump (patch, minor, major): " bump && \
|
||||||
version=$$(npm version $$bump | grep -oP "(?<=v)[^']+") && \
|
version=$$(npm version $$bump | grep -oP "(?<=v)[^']+") && \
|
||||||
echo "Version $$version created. Run 'make publish' to push the changes and publish the package."
|
echo "Version $$version created. Run 'make publish' to push the changes and publish the package."
|
||||||
|
|
|
@ -35,6 +35,10 @@ After installation, you should be able to run the program with
|
||||||
wtc
|
wtc
|
||||||
----
|
----
|
||||||
|
|
||||||
|
== Update
|
||||||
|
|
||||||
|
To update, just run the install command again
|
||||||
|
|
||||||
== Rationale
|
== Rationale
|
||||||
|
|
||||||
Don't know if it's just me but calculating my working hours sometimes
|
Don't know if it's just me but calculating my working hours sometimes
|
||||||
|
|
4
bin/wtc
Executable file
4
bin/wtc
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
|
node "$DIR/../dist/wtc.mjs" "$@"
|
|
@ -2,7 +2,7 @@
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"lunchBreakDuration": {
|
"unpaidLunchBreakDuration": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Comment out or remove if you don't have an unpaid lunch break or if you normally log your lunch break hours"
|
"description": "Comment out or remove if you don't have an unpaid lunch break or if you normally log your lunch break hours"
|
||||||
},
|
},
|
||||||
|
|
109
package-lock.json
generated
109
package-lock.json
generated
|
@ -1,17 +1,17 @@
|
||||||
{
|
{
|
||||||
"name": "work-time-calculator",
|
"name": "work-time-calculator",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "work-time-calculator",
|
"name": "work-time-calculator",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^2.2.5",
|
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
|
"iarna-toml-esm": "^3.0.5",
|
||||||
"xdg-basedir": "^5.1.0",
|
"xdg-basedir": "^5.1.0",
|
||||||
"yargs": "^17.7.2"
|
"yargs": "^17.7.2"
|
||||||
},
|
},
|
||||||
|
@ -19,6 +19,7 @@
|
||||||
"wtc": "bin/wtc"
|
"wtc": "bin/wtc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"@rollup/plugin-typescript": "^11.1.5",
|
"@rollup/plugin-typescript": "^11.1.5",
|
||||||
"@types/node": "^20.9.0",
|
"@types/node": "^20.9.0",
|
||||||
|
@ -134,11 +135,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@iarna/toml": {
|
|
||||||
"version": "2.2.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
|
|
||||||
"integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="
|
|
||||||
},
|
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.3",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
|
||||||
|
@ -232,6 +228,31 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@rollup/plugin-node-resolve": {
|
||||||
|
"version": "15.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
|
||||||
|
"integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/pluginutils": "^5.0.1",
|
||||||
|
"@types/resolve": "1.20.2",
|
||||||
|
"deepmerge": "^4.2.2",
|
||||||
|
"is-builtin-module": "^3.2.1",
|
||||||
|
"is-module": "^1.0.0",
|
||||||
|
"resolve": "^1.22.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^2.78.0||^3.0.0||^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/plugin-terser": {
|
"node_modules/@rollup/plugin-terser": {
|
||||||
"version": "0.4.4",
|
"version": "0.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
|
||||||
|
@ -479,6 +500,12 @@
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/resolve": {
|
||||||
|
"version": "1.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
||||||
|
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/semver": {
|
"node_modules/@types/semver": {
|
||||||
"version": "7.5.5",
|
"version": "7.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz",
|
||||||
|
@ -810,6 +837,18 @@
|
||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/builtin-modules": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/callsites": {
|
"node_modules/callsites": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
|
@ -917,6 +956,15 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/deepmerge": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dir-glob": {
|
"node_modules/dir-glob": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||||
|
@ -942,6 +990,14 @@
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/emitter-component": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-QdXO3nXOzZB4pAjM0n6ZE+R9/+kPpECA/XSELIcc54NeYVnBqIk+4DFiBgK+8QbV3mdvTG6nedl7dTYgO+5wDw==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
@ -1430,6 +1486,14 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/iarna-toml-esm": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/iarna-toml-esm/-/iarna-toml-esm-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-CgeDbPohnFG827UoRaCqKxJ8idiIDZDWlcHf5hUReQnZ8jHnNnhN4QJFiY12fKvr0LvuDuKAimqQfrmQnacbtw==",
|
||||||
|
"dependencies": {
|
||||||
|
"stream": "^0.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.2.4",
|
"version": "5.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
|
||||||
|
@ -1484,6 +1548,21 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/is-builtin-module": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"builtin-modules": "^3.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-core-module": {
|
"node_modules/is-core-module": {
|
||||||
"version": "2.13.1",
|
"version": "2.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
|
||||||
|
@ -1525,6 +1604,12 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-module": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/is-number": {
|
"node_modules/is-number": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
|
@ -2124,6 +2209,14 @@
|
||||||
"deprecated": "Please use @jridgewell/sourcemap-codec instead",
|
"deprecated": "Please use @jridgewell/sourcemap-codec instead",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/stream": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/stream/-/stream-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-gCq3NDI2P35B2n6t76YJuOp7d6cN/C7Rt0577l91wllh0sY9ZBuw9KaSGqH/b0hzn3CWWJbpbW0W0WvQ1H/Q7g==",
|
||||||
|
"dependencies": {
|
||||||
|
"emitter-component": "^1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string-width": {
|
"node_modules/string-width": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
|
12
package.json
12
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "work-time-calculator",
|
"name": "work-time-calculator",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"description": "An interactive CLI tool to calculate work time",
|
"description": "An interactive CLI tool to calculate work time",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -12,13 +12,14 @@
|
||||||
"url": "https://git.korhonen.cc/FunctionalHacker/work-time-calculator/issues",
|
"url": "https://git.korhonen.cc/FunctionalHacker/work-time-calculator/issues",
|
||||||
"email": "wtc@functionalhacker.korhonen.cc"
|
"email": "wtc@functionalhacker.korhonen.cc"
|
||||||
},
|
},
|
||||||
"main": "src/main.ts",
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
"wtc": "dist/wtc"
|
"wtc": "bin/wtc"
|
||||||
},
|
},
|
||||||
|
"main": "dist/wtc.mjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rollup -c"
|
"prod": "rollup -c ./rollup.prod.config.js",
|
||||||
|
"dev": "rollup -c ./rollup.dev.config.js"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"work",
|
"work",
|
||||||
|
@ -27,6 +28,7 @@
|
||||||
],
|
],
|
||||||
"author": "Marko Korhonen <marko@korhonen.cc>",
|
"author": "Marko Korhonen <marko@korhonen.cc>",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"@rollup/plugin-typescript": "^11.1.5",
|
"@rollup/plugin-typescript": "^11.1.5",
|
||||||
"@types/node": "^20.9.0",
|
"@types/node": "^20.9.0",
|
||||||
|
@ -38,9 +40,9 @@
|
||||||
"tslib": "^2.6.2"
|
"tslib": "^2.6.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^2.2.5",
|
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
|
"iarna-toml-esm": "^3.0.5",
|
||||||
"xdg-basedir": "^5.1.0",
|
"xdg-basedir": "^5.1.0",
|
||||||
"yargs": "^17.7.2"
|
"yargs": "^17.7.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import typescript from '@rollup/plugin-typescript';
|
|
||||||
import terser from '@rollup/plugin-terser';
|
|
||||||
import shebang from 'rollup-plugin-add-shebang';
|
|
||||||
|
|
||||||
/** @type {import('rollup').RollupOptions} */
|
|
||||||
const config = {
|
|
||||||
input: 'src/main.ts',
|
|
||||||
output: {
|
|
||||||
format: 'esm',
|
|
||||||
file: 'dist/wtc',
|
|
||||||
},
|
|
||||||
plugins: [typescript(), terser(), shebang({ include: 'dist/wtc' })],
|
|
||||||
external: [
|
|
||||||
'@iarna/toml',
|
|
||||||
'chalk',
|
|
||||||
'dayjs',
|
|
||||||
'dayjs/plugin/customParseFormat.js',
|
|
||||||
'dayjs/plugin/duration.js',
|
|
||||||
'fs',
|
|
||||||
'path',
|
|
||||||
'readline/promises',
|
|
||||||
'xdg-basedir',
|
|
||||||
'yargs',
|
|
||||||
'yargs/helpers',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
15
rollup.dev.config.js
Normal file
15
rollup.dev.config.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import typescript from '@rollup/plugin-typescript';
|
||||||
|
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||||
|
import shebang from 'rollup-plugin-add-shebang';
|
||||||
|
|
||||||
|
/** @type {import('rollup').RollupOptions} */
|
||||||
|
const config = {
|
||||||
|
input: 'src/main.ts',
|
||||||
|
output: {
|
||||||
|
format: 'esm',
|
||||||
|
file: 'dist/wtc-dev.mjs',
|
||||||
|
},
|
||||||
|
plugins: [typescript(), nodeResolve({ exportConditions: ['node'] }), shebang({ include: 'dist/wtc-dev.mjs' })],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
15
rollup.prod.config.js
Normal file
15
rollup.prod.config.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import typescript from '@rollup/plugin-typescript';
|
||||||
|
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||||
|
import terser from '@rollup/plugin-terser';
|
||||||
|
|
||||||
|
/** @type {import('rollup').RollupOptions} */
|
||||||
|
const config = {
|
||||||
|
input: 'src/main.ts',
|
||||||
|
output: {
|
||||||
|
format: 'esm',
|
||||||
|
file: 'dist/wtc.mjs',
|
||||||
|
},
|
||||||
|
plugins: [typescript(), nodeResolve({ exportConditions: ['node'] }), terser()],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
|
@ -1,7 +1,7 @@
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { xdgConfig } from 'xdg-basedir';
|
import { xdgConfig } from 'xdg-basedir';
|
||||||
import toml from '@iarna/toml';
|
import {parse} from 'iarna-toml-esm';
|
||||||
import { parseDuration, parseTimestamp } from './parse.js';
|
import { parseDuration, parseTimestamp } from './parse.js';
|
||||||
import WtcConfig from './types/WtcConfig.js';
|
import WtcConfig from './types/WtcConfig.js';
|
||||||
import Language from './types/Language.js';
|
import Language from './types/Language.js';
|
||||||
|
@ -27,7 +27,7 @@ const defaultConfig: RawConfig = {
|
||||||
hadLunch: true,
|
hadLunch: true,
|
||||||
},
|
},
|
||||||
askInput: {
|
askInput: {
|
||||||
workDayLength: true,
|
workDayDuration: true,
|
||||||
startTime: true,
|
startTime: true,
|
||||||
stopTime: true,
|
stopTime: true,
|
||||||
logged: true,
|
logged: true,
|
||||||
|
@ -36,32 +36,34 @@ const defaultConfig: RawConfig = {
|
||||||
|
|
||||||
const getConfig = (): WtcConfig => {
|
const getConfig = (): WtcConfig => {
|
||||||
const configDir = xdgConfig || path.join(process.env.HOME ?? './', '.config');
|
const configDir = xdgConfig || path.join(process.env.HOME ?? './', '.config');
|
||||||
let configFilePath = path.join(configDir, 'wtc', 'config.toml');
|
const configFilePath = path.join(configDir, 'wtc', 'config.toml');
|
||||||
|
|
||||||
let configData: RawConfig;
|
let configData: Partial<RawConfig>;
|
||||||
if (fs.existsSync(configFilePath)) {
|
if (fs.existsSync(configFilePath)) {
|
||||||
configData = toml.parse(fs.readFileSync(configFilePath, 'utf8')) as unknown as RawConfig;
|
configData = parse(fs.readFileSync(configFilePath, 'utf8')) as unknown as RawConfig;
|
||||||
} else {
|
} else {
|
||||||
configData = defaultConfig;
|
configData = defaultConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { language, timestampFormat, unpaidLunchBreakDuration, defaults, askInput } = configData;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
language: configData.language ?? defaultConfig.language,
|
language: language ?? defaultConfig.language,
|
||||||
timestampFormat: configData.timestampFormat ?? defaultConfig.timestampFormat,
|
timestampFormat: timestampFormat ?? defaultConfig.timestampFormat,
|
||||||
unpaidLunchBreakDuration: !configData.unpaidLunchBreakDuration ? undefined : parseDuration(configData.unpaidLunchBreakDuration),
|
unpaidLunchBreakDuration: !unpaidLunchBreakDuration
|
||||||
|
? undefined
|
||||||
|
: parseDuration(unpaidLunchBreakDuration),
|
||||||
defaults: {
|
defaults: {
|
||||||
workDayDuration: parseDuration(
|
workDayDuration: parseDuration(defaults?.workDayDuration ?? defaultConfig.defaults.workDayDuration),
|
||||||
configData.defaults.workDayDuration ?? defaultConfig.defaults.workDayDuration,
|
startTime: parseTimestamp(defaults?.startTime ?? defaultConfig.defaults.startTime),
|
||||||
),
|
stopTime: parseTimestamp(defaults?.stopTime ?? defaultConfig.defaults.stopTime),
|
||||||
startTime: parseTimestamp(configData.defaults.startTime ?? defaultConfig.defaults.startTime),
|
hadLunch: defaults?.hadLunch ?? defaultConfig.defaults.hadLunch,
|
||||||
stopTime: parseTimestamp(configData.defaults.stopTime ?? defaultConfig.defaults.stopTime),
|
|
||||||
hadLunch: configData.defaults.hadLunch ?? defaultConfig.defaults.hadLunch,
|
|
||||||
},
|
},
|
||||||
askInput: {
|
askInput: {
|
||||||
workDayLength: configData.askInput.workDayLength ?? defaultConfig.askInput.workDayLength,
|
workDayDuration: askInput?.workDayDuration ?? defaultConfig.askInput.workDayDuration,
|
||||||
startTime: configData.askInput.startTime ?? defaultConfig.askInput.startTime,
|
startTime: askInput?.startTime ?? defaultConfig.askInput.startTime,
|
||||||
stopTime: configData.askInput.stopTime ?? defaultConfig.askInput.stopTime,
|
stopTime: askInput?.stopTime ?? defaultConfig.askInput.stopTime,
|
||||||
logged: configData.askInput.logged ?? defaultConfig.askInput.logged,
|
logged: askInput?.logged ?? defaultConfig.askInput.logged,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
10
src/dayjs.ts
Normal file
10
src/dayjs.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import dayjs, {Dayjs} from 'dayjs/esm';
|
||||||
|
import duration, {Duration} from 'dayjs/esm/plugin/duration';
|
||||||
|
import customParseFormat from 'dayjs/esm/plugin/customParseFormat';
|
||||||
|
|
||||||
|
dayjs.extend(duration);
|
||||||
|
dayjs.extend(customParseFormat);
|
||||||
|
|
||||||
|
export default dayjs;
|
||||||
|
|
||||||
|
export type {Dayjs, Duration};
|
|
@ -1,4 +1,4 @@
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from './dayjs';
|
||||||
import { Duration } from 'dayjs/plugin/duration.js';
|
import { Duration } from 'dayjs/plugin/duration.js';
|
||||||
import Language from './types/Language';
|
import Language from './types/Language';
|
||||||
import { MessageKey, message } from './i18n';
|
import { MessageKey, message } from './i18n';
|
||||||
|
@ -23,7 +23,7 @@ export const formatDuration =
|
||||||
? `H [tunti${duration.hours() > 1 ? 'a' : ''} ja] m [minuuttia]`
|
? `H [tunti${duration.hours() > 1 ? 'a' : ''} ja] m [minuuttia]`
|
||||||
: duration.hours() > 0
|
: duration.hours() > 0
|
||||||
? `H [tunti${duration.hours() > 1 ? 'a' : ''}]`
|
? `H [tunti${duration.hours() > 1 ? 'a' : ''}]`
|
||||||
: 'm [minuttia]';
|
: 'm [minuuttia]';
|
||||||
} else {
|
} else {
|
||||||
formatString =
|
formatString =
|
||||||
duration.hours() > 0 && duration.minutes() > 0
|
duration.hours() > 0 && duration.minutes() > 0
|
||||||
|
|
11
src/i18n.ts
11
src/i18n.ts
|
@ -1,7 +1,8 @@
|
||||||
import Language from './types/Language';
|
import Language from './types/Language';
|
||||||
|
|
||||||
|
|
||||||
export enum MessageKey {
|
export enum MessageKey {
|
||||||
|
cliHelp,
|
||||||
|
cliVersion,
|
||||||
promptWorkDayDuration,
|
promptWorkDayDuration,
|
||||||
excludingLunch,
|
excludingLunch,
|
||||||
promptStartTime,
|
promptStartTime,
|
||||||
|
@ -27,6 +28,14 @@ export enum MessageKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages: Record<MessageKey, Record<Language, string>> = {
|
const messages: Record<MessageKey, Record<Language, string>> = {
|
||||||
|
[MessageKey.cliHelp]: {
|
||||||
|
[Language.en]: 'Show this help',
|
||||||
|
[Language.fi]: 'Näytä tämä ohje',
|
||||||
|
},
|
||||||
|
[MessageKey.cliVersion]: {
|
||||||
|
[Language.en]: 'Show program version',
|
||||||
|
[Language.fi]: 'Näytä ohjelman versio',
|
||||||
|
},
|
||||||
[MessageKey.promptWorkDayDuration]: {
|
[MessageKey.promptWorkDayDuration]: {
|
||||||
[Language.en]: 'How long is your work day today{0}? [{1}]: ',
|
[Language.en]: 'How long is your work day today{0}? [{1}]: ',
|
||||||
[Language.fi]: 'Kuinka pitkä työpäiväsi on tänään{0}? [{1}]: ',
|
[Language.fi]: 'Kuinka pitkä työpäiväsi on tänään{0}? [{1}]: ',
|
||||||
|
|
19
src/input.ts
19
src/input.ts
|
@ -1,20 +1,17 @@
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { Duration } from 'dayjs/plugin/duration';
|
|
||||||
import { parseDuration, parseTimestamp } from './parse';
|
import { parseDuration, parseTimestamp } from './parse';
|
||||||
import * as readline from 'readline/promises';
|
import * as readline from 'readline/promises';
|
||||||
import { formatDuration, formatTime } from './format';
|
import { formatDuration, formatTime } from './format';
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import { Dayjs } from 'dayjs';
|
||||||
import { WtcPromptResult } from './types/WtcPromptResult';
|
import { WtcPromptResult } from './types/WtcPromptResult';
|
||||||
import duration from 'dayjs/plugin/duration.js';
|
import { WtcRuntimeConfig } from './types/WtcConfig';
|
||||||
import WtcConfig from './types/WtcConfig';
|
import { MessageKey } from './i18n';
|
||||||
import { MessageKey, message } from './i18n';
|
import dayjs, { Duration } from './dayjs';
|
||||||
|
|
||||||
dayjs.extend(duration);
|
|
||||||
|
|
||||||
const { error } = console;
|
const { error } = console;
|
||||||
|
|
||||||
const input = async (config: WtcConfig): Promise<WtcPromptResult> => {
|
const input = async (runtimeCfg: WtcRuntimeConfig): Promise<WtcPromptResult> => {
|
||||||
const msg = message(config.language);
|
const { config, msg } = runtimeCfg;
|
||||||
const fmtDuration = formatDuration(config.language);
|
const fmtDuration = formatDuration(config.language);
|
||||||
const { defaults, askInput, unpaidLunchBreakDuration: lunchBreakDuration } = config;
|
const { defaults, askInput, unpaidLunchBreakDuration: lunchBreakDuration } = config;
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
|
@ -30,7 +27,7 @@ const input = async (config: WtcConfig): Promise<WtcPromptResult> => {
|
||||||
// Get work day duration
|
// Get work day duration
|
||||||
let workDayDuration: Duration | undefined = undefined;
|
let workDayDuration: Duration | undefined = undefined;
|
||||||
|
|
||||||
if (askInput.workDayLength) {
|
if (askInput.workDayDuration) {
|
||||||
const durationAnswer = await rl.question(
|
const durationAnswer = await rl.question(
|
||||||
msg(
|
msg(
|
||||||
MessageKey.promptWorkDayDuration,
|
MessageKey.promptWorkDayDuration,
|
||||||
|
@ -123,7 +120,7 @@ const input = async (config: WtcConfig): Promise<WtcPromptResult> => {
|
||||||
const logged = parseDuration(loggedAnswer);
|
const logged = parseDuration(loggedAnswer);
|
||||||
const unLogged = worked.subtract(logged);
|
const unLogged = worked.subtract(logged);
|
||||||
const workLeft = workDayDuration.subtract(worked);
|
const workLeft = workDayDuration.subtract(worked);
|
||||||
let workLeftMinutes = workLeft.asMinutes();
|
const workLeftMinutes = workLeft.asMinutes();
|
||||||
let workedOvertime: Duration | undefined;
|
let workedOvertime: Duration | undefined;
|
||||||
|
|
||||||
if (workLeftMinutes < 0) {
|
if (workLeftMinutes < 0) {
|
||||||
|
|
35
src/main.ts
35
src/main.ts
|
@ -1,9 +1,38 @@
|
||||||
import yargs from 'yargs';
|
import yargs from 'yargs';
|
||||||
import { hideBin } from 'yargs/helpers';
|
import { hideBin } from 'yargs/helpers';
|
||||||
import ui from './ui.js';
|
import ui from './ui.js';
|
||||||
|
import update from './update.js';
|
||||||
|
import getConfig from './config.js';
|
||||||
|
import { MessageKey, message } from './i18n.js';
|
||||||
|
import { WtcRuntimeConfig } from './types/WtcConfig.js';
|
||||||
|
|
||||||
|
// Build runtime config
|
||||||
|
const config = getConfig();
|
||||||
|
const msg = message(config.language);
|
||||||
|
const runtimeConfig: WtcRuntimeConfig = {
|
||||||
|
config,
|
||||||
|
msg,
|
||||||
|
};
|
||||||
|
|
||||||
// Process args. Yargs will exit if it detects help or version
|
// Process args. Yargs will exit if it detects help or version
|
||||||
yargs(hideBin(process.argv)).usage('Work time calculator').alias('help', 'h').alias('version', 'v').argv;
|
const args = await yargs(hideBin(process.argv))
|
||||||
|
.usage('Work time calculator')
|
||||||
|
.alias('help', 'h')
|
||||||
|
.alias('version', 'v')
|
||||||
|
.options({
|
||||||
|
help: {
|
||||||
|
description: msg(MessageKey.cliHelp),
|
||||||
|
},
|
||||||
|
version: {
|
||||||
|
description: msg(MessageKey.cliVersion),
|
||||||
|
},
|
||||||
|
}).argv;
|
||||||
|
|
||||||
// Run UI if help or version is not prompted
|
// Run updater if requested
|
||||||
ui();
|
if (args.update) {
|
||||||
|
update();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run UI if no arguments
|
||||||
|
ui(runtimeConfig);
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { formatDuration, getHoursRoundedStr } from './format';
|
import { formatDuration, getHoursRoundedStr } from './format';
|
||||||
import { WtcPromptResult } from './types/WtcPromptResult';
|
import { WtcPromptResult } from './types/WtcPromptResult';
|
||||||
import { MessageKey, message } from './i18n.js';
|
import { MessageKey } from './i18n.js';
|
||||||
import WtcConfig from './types/WtcConfig';
|
import { WtcRuntimeConfig } from './types/WtcConfig';
|
||||||
|
import dayjs from './dayjs';
|
||||||
|
|
||||||
const { log } = console;
|
const { log } = console;
|
||||||
|
|
||||||
const output = (result: WtcPromptResult, config: WtcConfig) => {
|
const output = (result: WtcPromptResult, runtimeCfg: WtcRuntimeConfig) => {
|
||||||
|
const {config, msg} = runtimeCfg;
|
||||||
const { language, timestampFormat } = config;
|
const { language, timestampFormat } = config;
|
||||||
const msg = message(language);
|
|
||||||
const fmtDuration = formatDuration(language);
|
const fmtDuration = formatDuration(language);
|
||||||
const hoursRounded = getHoursRoundedStr(language);
|
const hoursRounded = getHoursRoundedStr(language);
|
||||||
const { startedAt, stoppedAt, stoppedWorking, worked, unLogged, workLeft, workedOvertime, hadLunch } = result;
|
const { startedAt, stoppedAt, stoppedWorking, worked, unLogged, workLeft, workedOvertime, hadLunch } = result;
|
||||||
|
@ -34,7 +35,8 @@ const output = (result: WtcPromptResult, config: WtcConfig) => {
|
||||||
unLoggedMinutes === 0 ? '' : chalk.yellow(hoursRounded(unLogged)),
|
unLoggedMinutes === 0 ? '' : chalk.yellow(hoursRounded(unLogged)),
|
||||||
);
|
);
|
||||||
} else if (unLoggedMinutes < 0) {
|
} else if (unLoggedMinutes < 0) {
|
||||||
log(chalk.red(msg(MessageKey.loggedOver, fmtDuration(unLogged))), chalk.yellow(hoursRounded(unLogged)));
|
const overLogged = dayjs.duration(Math.abs(unLogged.asMilliseconds()), 'milliseconds');
|
||||||
|
log(chalk.red(msg(MessageKey.loggedOver, fmtDuration(overLogged))));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workLeft.asMinutes() > 0) {
|
if (workLeft.asMinutes() > 0) {
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs, Duration } from './dayjs';
|
||||||
import customParseFormat from 'dayjs/plugin/customParseFormat.js';
|
|
||||||
import duration, { Duration } from 'dayjs/plugin/duration.js';
|
|
||||||
|
|
||||||
dayjs.extend(customParseFormat);
|
|
||||||
dayjs.extend(duration);
|
|
||||||
|
|
||||||
export const parseTimestamp = (time: string): Dayjs => (time === 'now' ? dayjs() : dayjs(time, 'HH:mm', true));
|
export const parseTimestamp = (time: string): Dayjs => (time === 'now' ? dayjs() : dayjs(time, 'HH:mm', true));
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Dayjs } from 'dayjs';
|
import { Dayjs } from 'dayjs';
|
||||||
import { Duration } from 'dayjs/plugin/duration.js';
|
import { Duration } from 'dayjs/plugin/duration.js';
|
||||||
import Language from './Language.js';
|
import Language from './Language.js';
|
||||||
|
import { message } from '../i18n.js';
|
||||||
|
|
||||||
export default interface WtcConfig {
|
export default interface WtcConfig {
|
||||||
language: Language,
|
language: Language,
|
||||||
|
@ -13,9 +14,16 @@ export default interface WtcConfig {
|
||||||
hadLunch: boolean;
|
hadLunch: boolean;
|
||||||
};
|
};
|
||||||
askInput: {
|
askInput: {
|
||||||
workDayLength: boolean;
|
workDayDuration: boolean;
|
||||||
startTime: boolean;
|
startTime: boolean;
|
||||||
stopTime: boolean;
|
stopTime: boolean;
|
||||||
logged: boolean;
|
logged: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Config and current language msg function together */
|
||||||
|
export interface WtcRuntimeConfig {
|
||||||
|
config: WtcConfig;
|
||||||
|
msg: ReturnType<typeof message>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
import getConfig from './config.js';
|
|
||||||
import input from './input.js';
|
import input from './input.js';
|
||||||
import output from './output.js';
|
import output from './output.js';
|
||||||
|
import { WtcRuntimeConfig } from './types/WtcConfig.js';
|
||||||
|
|
||||||
const ui = async () => {
|
const ui = async (config: WtcRuntimeConfig) => output(await input(config), config);
|
||||||
const config = getConfig();
|
|
||||||
const result = await input(config);
|
|
||||||
output(result, config);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ui;
|
export default ui;
|
||||||
|
|
7
src/update.ts
Normal file
7
src/update.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
const { log } = console;
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
log('update');
|
||||||
|
};
|
||||||
|
|
||||||
|
export default update;
|
Loading…
Add table
Add a link
Reference in a new issue