@deployanyway/excuse-js 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,7 +1,18 @@
1
- # Changelog
2
-
3
- ## 0.1.0 — Unreleased
4
-
5
- - 33 original workplace-safe excuses across 11 categories.
6
- - Library API, stateless seeded selection, and CLI with category listing.
7
- - Tests, linting, formatting, and CI on Node 22/24.
1
+ # Changelog
2
+
3
+ ## 0.2.0 — 2026-10-08
4
+
5
+ - Meeting preparation mode: Generate 1–20 excuses with `excuseBatch(category, { count, seed })`. A batch cycles through the category without repeats until its pool is exhausted. Seeds keep the starting point repeatable. CLI `--json` returns a string or array.
6
+ - Add npm and CI badges to the published README.
7
+
8
+ ## 0.1.1 — 2026-10-08
9
+
10
+ - Correct npm installation and npx documentation after the initial publication.
11
+ - Add a searchable, humorous package description and relevant npm keywords.
12
+ - No API, CLI behavior, or dependency changes.
13
+
14
+ ## 0.1.0 — 2026-10-08
15
+
16
+ - 33 original workplace-safe excuses across 11 categories.
17
+ - Library API, stateless seeded selection, and CLI with category listing.
18
+ - Tests, linting, formatting, and CI on Node 22/24.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Deploy Anyway
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Deploy Anyway
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,110 +1,128 @@
1
- # excuse-js
2
-
3
- Original developer excuses for when the technology needs a spokesperson.
4
-
5
- ```text
6
- The edge case moved to the center and brought furniture.
7
- ```
8
-
9
- ## Installation
10
-
11
- Not published to npm yet. Try version 0.1.0 from source with Node 22 or 24:
12
-
13
- ```sh
14
- git clone https://github.com/DeployAnyway/excuse-js.git
15
- cd excuse-js
16
- git checkout feature/initial-mvp
17
- npm ci
18
- node bin/cli.js bug
19
- ```
20
-
21
- After an approved release: `npm install @deployanyway/excuse-js`.
22
-
23
- ## Quick start
24
-
25
- ```js
26
- import { excuse, categories } from "@deployanyway/excuse-js";
27
-
28
- console.log(excuse("deployment"));
29
- console.log(excuse("bug", { seed: "demo" }));
30
- console.log(categories());
31
- ```
32
-
33
- ## CLI example
34
-
35
- ```sh
36
- node bin/cli.js deployment --seed demo
37
- ```
38
-
39
- After publication: `npx @deployanyway/excuse-js deployment --seed demo`.
40
-
41
- ## API and options
42
-
43
- `excuse(category = 'deployment', { seed } = {})` returns a string. Category names
44
- are trimmed and case insensitive. Unknown categories throw RangeError. Invalid
45
- categories, options, and seeds throw TypeError. The library never exits or logs.
46
-
47
- `seed` accepts a string (including empty string) or a finite number (including zero).
48
- Without a seed, selection uses Math.random. With a seed, selection uses FNV-1a
49
- over the UTF-16 code units of `category:type:seed`, then takes the unsigned hash
50
- modulo the phrase count. String and numeric seeds are distinct. Calls have no
51
- shared seed state; the same inputs select the same phrase within this version.
52
- Different seeds can select the same phrase. Seeded output can change when the
53
- catalog changes between versions; this is not a cryptographic generator.
54
-
55
- `categories()` returns a fresh array of available lowercase names.
56
-
57
- ## Categories
58
-
59
- deployment, build, production, bug, api, database, deadline, testing, network,
60
- merge-conflict, demo. Each starts with three original, workplace-safe phrases.
61
-
62
- Custom runtime categories are deferred. Contributors can extend the data file
63
- without changing the selection logic.
64
-
65
- ## CLI reference
66
-
67
- `excuse-js [category] [options]`
68
-
69
- | Option | Behavior |
70
- | ----------------- | ---------------------------------------------------------- |
71
- | `--seed text` | Deterministic selection; CLI seeds are always strings |
72
- | `--list` | Print category names; cannot combine with category or seed |
73
- | `--help`, `-h` | Show help |
74
- | `--version`, `-v` | Show version |
75
-
76
- No category defaults to deployment. Quote seeds containing spaces. Exit code 0
77
- means success; 2 means invalid arguments. No stdin support in this MVP.
78
-
79
- ## Examples and development
80
-
81
- ```sh
82
- npm ci
83
- node examples/basic.js
84
- npm run lint
85
- npm run format:check
86
- npm test
87
- npm pack --dry-run
88
- ```
89
-
90
- ES modules, Node's built-in test runner, zero production dependencies. CI runs
91
- Node 22/24. ESLint development requires Node 22.13+ or 24. Phrases are in
92
- `src/excuses.js`, API in `src/index.js`, and CLI in `bin/cli.js`.
93
-
94
- ## Contributing
95
-
96
- See [CONTRIBUTING.md](CONTRIBUTING.md). Useful first, funny second.
97
-
98
- ## License
99
-
100
- [MIT](LICENSE).
101
-
102
- ## More from DeployAnyway
103
-
104
- **Tools for developers who probably know better.**
105
-
106
- - [error-translator](https://github.com/DeployAnyway/error-translator)
107
- - [excuse-js](https://github.com/DeployAnyway/excuse-js)
108
- - [doggo-log](https://github.com/DeployAnyway/doggo-log)
109
- - [ship-it-meter](https://github.com/DeployAnyway/ship-it-meter)
110
- - [bro-say](https://github.com/DeployAnyway/bro-say)
1
+ # excuse-js
2
+
3
+ [![npm version](https://img.shields.io/npm/v/%40deployanyway%2Fexcuse-js)](https://www.npmjs.com/package/@deployanyway/excuse-js)
4
+ [![CI](https://github.com/DeployAnyway/excuse-js/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/DeployAnyway/excuse-js/actions/workflows/ci.yml)
5
+
6
+ A seeded developer excuse generator for bugs, builds, and deployments. Finally, a dependency that takes the blame.
7
+
8
+ ```text
9
+ The edge case moved to the center and brought furniture.
10
+ ```
11
+
12
+ ## Installation
13
+
14
+ Available on npm. Requires Node 22 or later.
15
+ You can also run from source with Node 22 or 24:
16
+
17
+ ```sh
18
+ git clone https://github.com/DeployAnyway/excuse-js.git
19
+ cd excuse-js
20
+ git checkout main
21
+ npm ci
22
+ node bin/cli.js bug
23
+ ```
24
+
25
+ Install from npm: `npm install @deployanyway/excuse-js`.
26
+
27
+ ## Quick start
28
+
29
+ ```js
30
+ import { excuse, categories } from "@deployanyway/excuse-js";
31
+
32
+ console.log(excuse("deployment"));
33
+ console.log(excuse("bug", { seed: "demo" }));
34
+ console.log(categories());
35
+ ```
36
+
37
+ ## CLI example
38
+
39
+ ```sh
40
+ node bin/cli.js deployment --seed demo
41
+ ```
42
+
43
+ Run with npx: `npx @deployanyway/excuse-js deployment --seed demo`.
44
+
45
+ ## API and options
46
+
47
+ `excuse(category = 'deployment', { seed } = {})` returns a string. Category names
48
+ are trimmed and case insensitive. Unknown categories throw RangeError. Invalid
49
+ categories, options, and seeds throw TypeError. The library never exits or logs.
50
+
51
+ `seed` accepts a string (including empty string) or a finite number (including zero).
52
+ Without a seed, selection uses Math.random. With a seed, selection uses FNV-1a
53
+ over the UTF-16 code units of `category:type:seed`, then takes the unsigned hash
54
+ modulo the phrase count. String and numeric seeds are distinct. Calls have no
55
+ shared seed state; the same inputs select the same phrase within this version.
56
+ Different seeds can select the same phrase. Seeded output can change when the
57
+ catalog changes between versions; this is not a cryptographic generator.
58
+
59
+ `categories()` returns a fresh array of available lowercase names.
60
+
61
+ ## Categories
62
+
63
+ deployment, build, production, bug, api, database, deadline, testing, network,
64
+ merge-conflict, demo. Each starts with three original, workplace-safe phrases.
65
+
66
+ Custom runtime categories are deferred. Contributors can extend the data file
67
+ without changing the selection logic.
68
+
69
+ ## CLI reference
70
+
71
+ `excuse-js [category] [options]`
72
+
73
+ | Option | Behavior |
74
+ | ----------------- | ---------------------------------------------------------- |
75
+ | `--seed text` | Deterministic selection; CLI seeds are always strings |
76
+ | `--list` | Print category names; cannot combine with category or seed |
77
+ | `--help`, `-h` | Show help |
78
+ | `--version`, `-v` | Show version |
79
+
80
+ No category defaults to deployment. Quote seeds containing spaces. Exit code 0
81
+ means success; 2 means invalid arguments. No stdin support in this MVP.
82
+
83
+ ## Examples and development
84
+
85
+ ```sh
86
+ npm ci
87
+ node examples/basic.js
88
+ npm run lint
89
+ npm run format:check
90
+ npm test
91
+ npm pack --dry-run
92
+ ```
93
+
94
+ ES modules, Node's built-in test runner, zero production dependencies. CI runs
95
+ Node 22/24. ESLint development requires Node 22.13+ or 24. Phrases are in
96
+ `src/excuses.js`, API in `src/index.js`, and CLI in `bin/cli.js`.
97
+
98
+ ## Contributing
99
+
100
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Useful first, funny second.
101
+
102
+ ## License
103
+
104
+ [MIT](LICENSE).
105
+
106
+ ## More from DeployAnyway
107
+
108
+ **Tools for developers who probably know better.**
109
+
110
+ - [error-translator](https://github.com/DeployAnyway/error-translator)
111
+ - [excuse-js](https://github.com/DeployAnyway/excuse-js)
112
+ - [doggo-log](https://github.com/DeployAnyway/doggo-log)
113
+ - [ship-it-meter](https://github.com/DeployAnyway/ship-it-meter)
114
+ - [bro-say](https://github.com/DeployAnyway/bro-say)
115
+
116
+ ## Meeting preparation mode
117
+
118
+ Generate 1–20 excuses with `excuseBatch(category, { count, seed })`. A batch cycles through the category without repeats until its pool is exhausted. Seeds keep the starting point repeatable. CLI `--json` returns a string or array.
119
+
120
+ ```sh
121
+ npx @deployanyway/excuse-js deployment --count 3 --seed demo
122
+ ```
123
+
124
+ API (import the named functions from this package):
125
+
126
+ ```js
127
+ excuseBatch("deployment", { count: 3, seed: "demo" });
128
+ ```
package/bin/cli.js CHANGED
@@ -1,39 +1,61 @@
1
- #!/usr/bin/env node
2
- import { parseArgs } from "node:util";
3
- import { readFileSync } from "node:fs";
4
- import { URL } from "node:url";
5
- import { excuse, categories } from "../src/index.js";
6
-
7
- try {
8
- const { values, positionals } = parseArgs({
9
- allowPositionals: true,
10
- options: {
11
- help: { type: "boolean", short: "h" },
12
- version: { type: "boolean", short: "v" },
13
- seed: { type: "string" },
14
- list: { type: "boolean" },
15
- },
16
- });
17
- if (values.help) {
18
- console.log(
19
- "Usage: excuse-js [category] [--seed text]\n\nDefault category: deployment\n\nOptions:\n --seed text Repeatable selection (always a string seed)\n --list List categories\n -h, --help Show help\n -v, --version Show version\n\nExample: excuse-js deployment --seed demo\nExit codes: 0 success; 2 invalid arguments.",
20
- );
21
- } else if (values.version) {
22
- console.log(
23
- JSON.parse(
24
- readFileSync(new URL("../package.json", import.meta.url), "utf8"),
25
- ).version,
26
- );
27
- } else if (values.list) {
28
- if (positionals.length || values.seed !== undefined)
29
- throw new TypeError("--list does not accept a category or seed.");
30
- console.log(categories().join("\n"));
31
- } else {
32
- if (positionals.length > 1)
33
- throw new TypeError("Provide only one category.");
34
- console.log(excuse(positionals[0], { seed: values.seed }));
35
- }
36
- } catch (error) {
37
- console.error(`excuse-js: ${error.message}\nRun with --help for usage.`);
38
- process.exitCode = 2;
39
- }
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from "node:util";
3
+ import { readFileSync } from "node:fs";
4
+ import { URL } from "node:url";
5
+ import { excuse, categories, excuseBatch } from "../src/index.js";
6
+
7
+ try {
8
+ const { values, positionals } = parseArgs({
9
+ allowPositionals: true,
10
+ options: {
11
+ help: { type: "boolean", short: "h" },
12
+ version: { type: "boolean", short: "v" },
13
+ seed: { type: "string" },
14
+ list: { type: "boolean" },
15
+ count: { type: "string" },
16
+ json: { type: "boolean" },
17
+ },
18
+ });
19
+ if (values.help) {
20
+ console.log(
21
+ "Usage: excuse-js [category] [--seed text] [--count 3] [--json]\n\nDefault category: deployment\n\nOptions:\n --seed text Repeatable selection (always a string seed)\n --count 1..20 Prepare a batch for the meeting\n --json Structured string or batch\n --list List categories\n -h, --help Show help\n -v, --version Show version\n\nExample: excuse-js deployment --seed demo\nExit codes: 0 success; 2 invalid arguments.",
22
+ );
23
+ } else if (values.version) {
24
+ console.log(
25
+ JSON.parse(
26
+ readFileSync(new URL("../package.json", import.meta.url), "utf8"),
27
+ ).version,
28
+ );
29
+ } else if (values.list) {
30
+ if (
31
+ positionals.length ||
32
+ values.seed !== undefined ||
33
+ values.count !== undefined ||
34
+ values.json
35
+ )
36
+ throw new TypeError("--list does not accept a category or seed.");
37
+ console.log(categories().join("\n"));
38
+ } else {
39
+ if (positionals.length > 1)
40
+ throw new TypeError("Provide only one category.");
41
+ if (values.count !== undefined && !/^[0-9]+$/.test(values.count))
42
+ throw new TypeError("count must be an integer.");
43
+ const result =
44
+ values.count === undefined
45
+ ? excuse(positionals[0], { seed: values.seed })
46
+ : excuseBatch(positionals[0], {
47
+ seed: values.seed,
48
+ count: Number(values.count),
49
+ });
50
+ console.log(
51
+ values.json
52
+ ? JSON.stringify(result)
53
+ : Array.isArray(result)
54
+ ? result.map((line, i) => `${i + 1}. ${line}`).join("\n")
55
+ : result,
56
+ );
57
+ }
58
+ } catch (error) {
59
+ console.error(`excuse-js: ${error.message}\nRun with --help for usage.`);
60
+ process.exitCode = 2;
61
+ }
package/package.json CHANGED
@@ -1,41 +1,51 @@
1
- {
2
- "name": "@deployanyway/excuse-js",
3
- "version": "0.1.0",
4
- "description": "Workplace-safe developer excuses for when the technology needs a spokesperson.",
5
- "type": "module",
6
- "exports": "./src/index.js",
7
- "bin": {
8
- "excuse-js": "./bin/cli.js"
9
- },
10
- "files": [
11
- "src",
12
- "bin",
13
- "README.md",
14
- "LICENSE",
15
- "CHANGELOG.md"
16
- ],
17
- "engines": {
18
- "node": ">=22"
19
- },
20
- "scripts": {
21
- "test": "node --test",
22
- "lint": "eslint .",
23
- "format": "prettier --write .",
24
- "format:check": "prettier --check ."
25
- },
26
- "repository": {
27
- "type": "git",
28
- "url": "git+https://github.com/DeployAnyway/excuse-js.git"
29
- },
30
- "bugs": {
31
- "url": "https://github.com/DeployAnyway/excuse-js/issues"
32
- },
33
- "license": "MIT",
34
- "publishConfig": {
35
- "access": "public"
36
- },
37
- "devDependencies": {
38
- "eslint": "^10.12.0",
39
- "prettier": "^3.6.2"
40
- }
41
- }
1
+ {
2
+ "name": "@deployanyway/excuse-js",
3
+ "version": "0.2.0",
4
+ "description": "A seeded developer excuse generator for bugs, builds, and deployments. Finally, a dependency that takes the blame.",
5
+ "type": "module",
6
+ "exports": "./src/index.js",
7
+ "bin": {
8
+ "excuse-js": "./bin/cli.js"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "bin",
13
+ "README.md",
14
+ "LICENSE",
15
+ "CHANGELOG.md"
16
+ ],
17
+ "engines": {
18
+ "node": ">=22"
19
+ },
20
+ "scripts": {
21
+ "test": "node --test",
22
+ "lint": "eslint .",
23
+ "format": "prettier --write .",
24
+ "format:check": "prettier --check ."
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/DeployAnyway/excuse-js.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/DeployAnyway/excuse-js/issues"
32
+ },
33
+ "license": "MIT",
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "devDependencies": {
38
+ "eslint": "^10.12.0",
39
+ "prettier": "^3.6.2"
40
+ },
41
+ "keywords": [
42
+ "nodejs",
43
+ "javascript",
44
+ "cli",
45
+ "developer-tools",
46
+ "excuse-generator",
47
+ "seeded",
48
+ "developer-humor",
49
+ "deployment"
50
+ ]
51
+ }
package/src/excuses.js CHANGED
@@ -1,58 +1,58 @@
1
- // Original, workplace-safe phrases. Add new categories and phrases here.
2
- export const phrases = {
3
- deployment: [
4
- "The deployment passed rehearsal and requested a stunt double for production.",
5
- "The release arrived on time. Its dependencies are still finding parking.",
6
- "The deployment is waiting for the server to finish its dramatic entrance.",
7
- ],
8
- build: [
9
- "The compiler found a typo and promoted it to a company-wide announcement.",
10
- "The build is assembling a committee to discuss the missing semicolon.",
11
- "The artifacts are ready; the build is negotiating custody.",
12
- ],
13
- production: [
14
- "Production interpreted the happy path as an optional scenic route.",
15
- "The server is performing an unscheduled demonstration of our recovery plan.",
16
- "Production has requested that our assumptions submit identification.",
17
- ],
18
- bug: [
19
- "The edge case moved to the center and brought furniture.",
20
- "That variable has been exploring career opportunities outside its scope.",
21
- "The bug declined to reproduce until the screen-sharing session began.",
22
- ],
23
- api: [
24
- "The API sent a response, but it used the envelope for the payload.",
25
- "The endpoint is observing a moment of silence for our timeout setting.",
26
- "The contract was clear. The response preferred interpretive dance.",
27
- ],
28
- database: [
29
- "The database is taking a full-table stroll to consider our question.",
30
- "The query asked for one row and accidentally invited the whole neighborhood.",
31
- "The migration packed the columns but forgot the forwarding address.",
32
- ],
33
- deadline: [
34
- "The estimate included coding time, but the calendar added plot twists.",
35
- "The deadline arrived before the requirements finished introducing themselves.",
36
- "The last ten percent has applied for its own project manager.",
37
- ],
38
- testing: [
39
- "The test fixture has developed stronger opinions than the specification.",
40
- "The flaky test only accepts results delivered under a full cache.",
41
- "The assertion expected certainty; the asynchronous task offered suspense.",
42
- ],
43
- network: [
44
- "The packets took a detour to admire the routing table.",
45
- "DNS is still deciding which address feels most like home.",
46
- "The connection is buffering its explanation for the delay.",
47
- ],
48
- "merge-conflict": [
49
- "Both branches brought a solution and neither brought a seating chart.",
50
- "Git discovered that two correct answers wanted the same line.",
51
- "The branches agree on the goal and disagree on every comma.",
52
- ],
53
- demo: [
54
- "The demo saw an audience and switched from software to performance art.",
55
- "The prototype is saving its best behavior for the recording we forgot to start.",
56
- "The live demo has added a surprise question to the presentation.",
57
- ],
58
- };
1
+ // Original, workplace-safe phrases. Add new categories and phrases here.
2
+ export const phrases = {
3
+ deployment: [
4
+ "The deployment passed rehearsal and requested a stunt double for production.",
5
+ "The release arrived on time. Its dependencies are still finding parking.",
6
+ "The deployment is waiting for the server to finish its dramatic entrance.",
7
+ ],
8
+ build: [
9
+ "The compiler found a typo and promoted it to a company-wide announcement.",
10
+ "The build is assembling a committee to discuss the missing semicolon.",
11
+ "The artifacts are ready; the build is negotiating custody.",
12
+ ],
13
+ production: [
14
+ "Production interpreted the happy path as an optional scenic route.",
15
+ "The server is performing an unscheduled demonstration of our recovery plan.",
16
+ "Production has requested that our assumptions submit identification.",
17
+ ],
18
+ bug: [
19
+ "The edge case moved to the center and brought furniture.",
20
+ "That variable has been exploring career opportunities outside its scope.",
21
+ "The bug declined to reproduce until the screen-sharing session began.",
22
+ ],
23
+ api: [
24
+ "The API sent a response, but it used the envelope for the payload.",
25
+ "The endpoint is observing a moment of silence for our timeout setting.",
26
+ "The contract was clear. The response preferred interpretive dance.",
27
+ ],
28
+ database: [
29
+ "The database is taking a full-table stroll to consider our question.",
30
+ "The query asked for one row and accidentally invited the whole neighborhood.",
31
+ "The migration packed the columns but forgot the forwarding address.",
32
+ ],
33
+ deadline: [
34
+ "The estimate included coding time, but the calendar added plot twists.",
35
+ "The deadline arrived before the requirements finished introducing themselves.",
36
+ "The last ten percent has applied for its own project manager.",
37
+ ],
38
+ testing: [
39
+ "The test fixture has developed stronger opinions than the specification.",
40
+ "The flaky test only accepts results delivered under a full cache.",
41
+ "The assertion expected certainty; the asynchronous task offered suspense.",
42
+ ],
43
+ network: [
44
+ "The packets took a detour to admire the routing table.",
45
+ "DNS is still deciding which address feels most like home.",
46
+ "The connection is buffering its explanation for the delay.",
47
+ ],
48
+ "merge-conflict": [
49
+ "Both branches brought a solution and neither brought a seating chart.",
50
+ "Git discovered that two correct answers wanted the same line.",
51
+ "The branches agree on the goal and disagree on every comma.",
52
+ ],
53
+ demo: [
54
+ "The demo saw an audience and switched from software to performance art.",
55
+ "The prototype is saving its best behavior for the recording we forgot to start.",
56
+ "The live demo has added a surprise question to the presentation.",
57
+ ],
58
+ };
package/src/index.js CHANGED
@@ -1,44 +1,60 @@
1
- import { phrases } from "./excuses.js";
2
-
3
- /** Return the available lowercase category names as a fresh array. */
4
- export function categories() {
5
- return Object.keys(phrases);
6
- }
7
-
8
- /**
9
- * Choose an excuse. Seeds are stateless and deterministic within this version.
10
- * @param {string} [category='deployment'] Category (case insensitive).
11
- * @param {{seed?: string | number}} [options] Optional string or finite number seed.
12
- * @returns {string} An original workplace-safe excuse.
13
- */
14
- export function excuse(category = "deployment", options = {}) {
15
- if (typeof category !== "string" || !category.trim())
16
- throw new TypeError("Category must be a nonempty string.");
17
- if (!options || typeof options !== "object" || Array.isArray(options))
18
- throw new TypeError("Options must be an object.");
19
- const key = category.trim().toLowerCase();
20
- if (!Object.hasOwn(phrases, key))
21
- throw new RangeError(
22
- `Unknown category: ${category}. Choose: ${categories().join(", ")}.`,
23
- );
24
- const { seed } = options;
25
- let index;
26
- if (seed === undefined) {
27
- index = Math.floor(Math.random() * phrases[key].length);
28
- } else {
29
- if (
30
- typeof seed !== "string" &&
31
- !(typeof seed === "number" && Number.isFinite(seed))
32
- )
33
- throw new TypeError("Seed must be a string or finite number.");
34
- // FNV-1a over UTF-16 code units. Type tagging distinguishes 1 from "1".
35
- const input = `${key}:${typeof seed}:${seed}`;
36
- let hash = 2166136261;
37
- for (let i = 0; i < input.length; i++) {
38
- hash ^= input.charCodeAt(i);
39
- hash = Math.imul(hash, 16777619) >>> 0;
40
- }
41
- index = hash % phrases[key].length;
42
- }
43
- return phrases[key][index];
44
- }
1
+ import { phrases } from "./excuses.js";
2
+
3
+ /** Return the available lowercase category names as a fresh array. */
4
+ export function categories() {
5
+ return Object.keys(phrases);
6
+ }
7
+
8
+ /**
9
+ * Choose an excuse. Seeds are stateless and deterministic within this version.
10
+ * @param {string} [category='deployment'] Category (case insensitive).
11
+ * @param {{seed?: string | number}} [options] Optional string or finite number seed.
12
+ * @returns {string} An original workplace-safe excuse.
13
+ */
14
+ export function excuse(category = "deployment", options = {}) {
15
+ if (typeof category !== "string" || !category.trim())
16
+ throw new TypeError("Category must be a nonempty string.");
17
+ if (!options || typeof options !== "object" || Array.isArray(options))
18
+ throw new TypeError("Options must be an object.");
19
+ const key = category.trim().toLowerCase();
20
+ if (!Object.hasOwn(phrases, key))
21
+ throw new RangeError(
22
+ `Unknown category: ${category}. Choose: ${categories().join(", ")}.`,
23
+ );
24
+ const { seed } = options;
25
+ let index;
26
+ if (seed === undefined) {
27
+ index = Math.floor(Math.random() * phrases[key].length);
28
+ } else {
29
+ if (
30
+ typeof seed !== "string" &&
31
+ !(typeof seed === "number" && Number.isFinite(seed))
32
+ )
33
+ throw new TypeError("Seed must be a string or finite number.");
34
+ // FNV-1a over UTF-16 code units. Type tagging distinguishes 1 from "1".
35
+ const input = `${key}:${typeof seed}:${seed}`;
36
+ let hash = 2166136261;
37
+ for (let i = 0; i < input.length; i++) {
38
+ hash ^= input.charCodeAt(i);
39
+ hash = Math.imul(hash, 16777619) >>> 0;
40
+ }
41
+ index = hash % phrases[key].length;
42
+ }
43
+ return phrases[key][index];
44
+ }
45
+
46
+ /** Generate a batch without repeats until the category pool is exhausted. */
47
+ export function excuseBatch(category = "deployment", options = {}) {
48
+ if (!options || typeof options !== "object" || Array.isArray(options))
49
+ throw new TypeError("Options must be an object.");
50
+ const count = options.count ?? 3;
51
+ if (!Number.isSafeInteger(count) || count < 1 || count > 20)
52
+ throw new RangeError("count must be an integer between 1 and 20.");
53
+ const first = excuse(category, { seed: options.seed });
54
+ const pool = phrases[category.trim().toLowerCase()];
55
+ const start = pool.indexOf(first);
56
+ return Array.from(
57
+ { length: count },
58
+ (_, i) => pool[(start + i) % pool.length],
59
+ );
60
+ }