Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 24
- run: node --check app-0.3.0.js && node --check siblings-0.3.0.js && node --check candidate/redirect.js
- run: node --check app-0.4.0.js && node --check siblings-0.4.0.js && node --check candidate/redirect.js
- run: node --test test/*.test.js
deploy:
if: github.event_name != 'pull_request'
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Public interactive demo: https://deployanyway.github.io/

Static HTML, CSS and browser ES modules. No analytics, accounts or backend. Inputs stay in the browser. The five demos use vendored 0.3.0 package source; the dog logger uses a single-string `node:util` adapter because the form does not accept formatting arguments. This adapter is not a replacement for the npm library's full Node formatting behavior.
Static HTML, CSS and browser ES modules. No analytics, accounts or backend. Inputs stay in the browser. The five demos use vendored 0.4.0 package source; the dog logger uses a single-string `node:util` adapter because the form does not accept formatting arguments. This adapter is not a replacement for the npm library's full Node formatting behavior.

## Develop

Expand All @@ -12,6 +12,16 @@ The root introduces DeployAnyway and gives all five released tools equal discove

To refresh demos, copy the four companion packages' browser-safe `src/*.js` and MIT licenses into `vendor/<package>/`. Build bro-say and copy its `dist/browser.js` and third-party license notices into this site's `dist/`. Review API changes and verify all browser interactions. The site does not fetch package code from a third-party CDN at runtime.

## Explore the released libraries

- bro-say: 13 original characters, 20 moods, six themes and 48 message presets. Choose a category to inspect all six messages; seeded selection is repeatable within this version.
- error-translator: 46 supported errors, full catalog, custom messages and JSON batches. Unknown input receives an explicit fallback.
- excuse-js: 132 original excuses across eleven categories. Inspect each category, generate seeded batches or pair a phrase with a practical next step.
- doggo-log: 48 optional commentary lines across six levels. Explore classic or rotating commentary, an eight-log sequence, filtering and structured context. Sequence and catalog examples use the Node API and include installation/run instructions.
- ship-it-meter: twelve example evidence scenarios, editable JSON, prioritized release plans, gates and scores. These are heuristics based on supplied facts; the browser does not run your CI.

The organization palette and home badge follow the supplied husky artwork. Dallas and Benji inspire the story and playful tone. All five tools retain equal discovery and feedback links.

## License

MIT. Vendored package licenses are included in their directories.
126 changes: 126 additions & 0 deletions app-0.4.0.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {
renderBro,
listCharacters,
moods,
listThemes,
broMessage,
messageCategories,
messagePresets,
} from "./dist/browser.js";

const element = (id) => document.getElementById(id);
for (const [id, names, selected] of [
["character", listCharacters(), "husky"],
["mood", moods(), "sarcastic"],
["theme", listThemes(), "classic"],
]) {
for (const name of names) {
const option = document.createElement("option");
option.value = option.textContent = name;
option.selected = name === selected;
element(id).append(option);
}
}
const presetLabel = document.createElement("label");
presetLabel.textContent = "Message source";
const presetSelect = document.createElement("select");
presetSelect.id = "bro-preset";
for (const value of ["Your message", ...messageCategories()]) {
const option = document.createElement("option");
option.value = option.textContent = value;
presetSelect.append(option);
}
presetLabel.append(presetSelect);
element("controls").prepend(presetLabel);
const presetList = document.createElement("pre");
presetList.className = "preset-library";
presetList.setAttribute("aria-label", "Message preset library");
element("controls").after(presetList);
function render() {
try {
const random = element("bro-selection").value === "random";
for (const id of ["character", "mood", "theme"])
element(id).disabled = random;
const preset = element("bro-preset").value;
const generated = preset !== "Your message";
element("message").disabled = generated;
element("bro-seed").disabled = !random && !generated;
const text = generated
? broMessage(preset, { seed: element("bro-seed").value })
: element("message").value;
presetList.hidden = !generated;
presetList.textContent = generated
? "All six " +
preset +
" presets:\n" +
messagePresets(preset)
.map((line, i) => i + 1 + ". " + line)
.join("\n")
: "";
const result = renderBro({
text,
character: random ? undefined : element("character").value,
mood: random ? undefined : element("mood").value,
theme: random ? undefined : element("theme").value,
mode: element("mode").value,
width: Number(element("width").value),
wrap: element("bro-wrap").checked,
...(element("bro-layout").value === "plain"
? { layout: "plain" }
: element("bro-layout").value === "box"
? { box: true }
: {}),
...(random ? { random: true, seed: element("bro-seed").value } : {}),
});
element("output").textContent =
element("bro-format").value === "json"
? JSON.stringify(result, null, 2)
: result.rendered;
const quote = (value) => "'" + value.replaceAll("'", "'\\''") + "'";
element("bro-command").textContent =
`npx @deployanyway/bro-say@0.4.0 ${generated ? `--preset ${preset}` : quote(text)} --width ${element("width").value}` +
(random
? ` --random --seed ${quote(element("bro-seed").value)}`
: `${generated ? ` --seed ${quote(element("bro-seed").value)}` : ""} --character ${element("character").value} --mood ${element("mood").value} --theme ${element("theme").value}`) +
(element("mode").value === "think" ? " --think" : "") +
(element("bro-format").value === "json" ? " --json" : "") +
(!element("bro-wrap").checked ? " --no-wrap" : "") +
(element("bro-layout").value === "plain"
? " --plain"
: element("bro-layout").value === "box"
? " --box"
: "");
element("status").textContent = "";
element("copy").disabled = false;
element("bro-copy-command").disabled = false;
} catch (error) {
element("status").textContent = error.message;
element("output").textContent = "";
element("copy").disabled = true;
element("bro-copy-command").disabled = true;
element("bro-command").textContent = "";
}
}
element("controls").addEventListener("input", render);
element("controls").addEventListener("submit", (event) =>
event.preventDefault(),
);
element("copy").addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(element("output").textContent);
element("status").textContent = "Copied. Ship the message responsibly.";
} catch {
element("status").textContent =
"Clipboard unavailable. Select and copy the output manually.";
}
});
render();

element("bro-copy-command").addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(element("bro-command").textContent);
element("status").textContent = "Command copied.";
} catch {
element("status").textContent = "Select and copy the command manually.";
}
});
130 changes: 130 additions & 0 deletions dist/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,46 @@ var characters = Object.fromEntries(

// src/personas.js
var personas = {
dallas: {
intro: "DALLAS HAS ARRIVED AT FULL ZOOMIE SPEED.",
label: "ENTHUSIASM WITH EVIDENCE",
outro: "Take the victory lap after the checks pass."
},
benji: {
intro: "BENJI HAS AN IDEA AND WOULD LIKE YOUR ATTENTION.",
label: "NEXT ADVENTURE",
outro: "Investigate the interesting thing. Keep a return route."
},
"rubber-duck": {
intro: "Tell me what you expected. Then tell me what happened.",
label: "QUACK-SIZED REPRODUCTION",
outro: "One clear example beats a pond full of guesses."
},
"on-call": {
intro: "The alert is loud. We can stay methodical.",
label: "INCIDENT NOTE",
outro: "Check impact, capture evidence, and confirm rollback."
},
"code-review": {
intro: "A friendly question before the merge.",
label: "KINDNESS IS A REVIEW TOOL",
outro: "Make the reasoning easy for the next developer to follow."
},
minimalist: {
intro: "Less magic. More clarity.",
label: "SMALLEST USEFUL STEP",
outro: "Keep the part that solves the problem."
},
optimist: {
intro: "This can get better. Let us prove it.",
label: "HOPE WITH A TEST CASE",
outro: "A small verified improvement is still an improvement."
},
skeptic: {
intro: "Interesting claim. Where are the receipts?",
label: "SHOW THE EVIDENCE",
outro: "Confidence is welcome. Verification gets the merge."
},
classic: { intro: "BRO...", label: "", outro: "" },
hype: {
intro: "BRO! THE TERMINAL IS APPLAUDING!",
Expand Down Expand Up @@ -1368,12 +1408,102 @@ function brothink(text, options = {}) {
throw new TypeError("Options must be an object.");
return renderBro({ ...options, text, mode: "think" }).rendered;
}

// src/messages.js
var presets = {
deployment: [
"The release has landed. Please keep your monitoring inside the vehicle.",
"Shipped with confidence. Verified with health checks.",
"Today we deploy code and a reasonable rollback plan.",
"The canary is singing. Let us keep listening.",
"A small release for the app. A large relief for the team.",
"The deployment passed. The dashboard gets the final word."
],
testing: [
"Red, green, refactor. Snacks are an optional fourth step.",
"The test failed usefully. That is a good beginning.",
"A passing test is a receipt, not a prophecy.",
"Make the edge case boring enough to test.",
"One regression test today saves a mystery tomorrow.",
"The suite is green. Check what it actually covered."
],
debugging: [
"Read the first error before collecting the entire stack trace.",
"The rubber duck requests a smaller reproduction.",
"Follow the value. It knows where it has been.",
"A breakpoint is cheaper than a theory committee.",
"The bug cannot hide from a clear input and a patient developer.",
"One hypothesis. One experiment. One less guess."
],
review: [
"Review the change, not the person who wrote it.",
"A kind question can save a very loud incident.",
"The clever line would appreciate a simple explanation.",
"If future-you needs a map, leave a comment today.",
"LGTM comes after understanding the diff.",
"Small commits make excellent conversation starters."
],
coffee: [
"The code is brewing. So is the coffee.",
"A refill is not a retry policy, but it can improve the meeting.",
"This mug contains the unofficial build coordinator.",
"The coffee is hot. The take can wait.",
"Hydrate between compilations. Your compiler does not need another cup.",
"A short break is a legitimate debugging technique."
],
celebration: [
"The fix worked. Give the test its share of the applause.",
"Celebrate the boring release. It took good work to make it boring.",
"One fewer alert. One more reason to take a walk.",
"The team shipped a useful thing. That deserves a victory lap.",
"Success has receipts and a very happy tail.",
"Treat budget approved after verification."
],
husky: [
"Dallas brings the zoomies. Benji brings the next idea.",
"Two huskies, one keyboard, absolutely no quiet roadmap.",
"Commit small. Run fast. Leave the socks out of production.",
"Dallas heard deploy. Benji heard play. Both approve a smoke test.",
"High energy is a feature. Good boundaries keep it useful.",
"Our huskies believe every adventure deserves enthusiasm and a return route."
],
focus: [
"Pick the next useful step. Finish that one.",
"Reduce the scope until the problem fits in your afternoon.",
"Your terminal can have personality. Your interfaces should have clarity.",
"Small progress counts even without a dramatic soundtrack.",
"A helpful tool should make the next action easier to see.",
"Close one loop before opening six more tabs."
]
};
var messageCategories = () => Object.keys(presets);
function messagePresets(category) {
if (typeof category !== "string" || !Object.hasOwn(presets, category.trim().toLowerCase()))
throw new RangeError(
"Choose a supported message category: " + messageCategories().join(", ")
);
return [...presets[category.trim().toLowerCase()]];
}
function broMessage(category = "deployment", options = {}) {
if (!options || typeof options !== "object" || Array.isArray(options))
throw new TypeError("Options must be an object.");
if (options.seed !== void 0 && typeof options.seed !== "string" && !(typeof options.seed === "number" && Number.isFinite(options.seed)))
throw new TypeError("seed must be a string or finite number.");
return choose(
messagePresets(category),
options.seed,
"message:" + category.trim().toLowerCase()
);
}
export {
broMessage,
brosay2 as brosay,
brothink,
displayWidth,
listCharacters,
listThemes,
messageCategories,
messagePresets,
moods,
renderBro
};
Loading
Loading