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.4.0.js && node --check siblings-0.4.0.js && node --check candidate/redirect.js
- run: node --check app-1.0.0.js && node --check siblings-1.0.0.js && node --check value-1.0.0.js && node --check candidate/redirect.js
- run: node --test test/*.test.js
deploy:
if: github.event_name != 'pull_request'
Expand Down
6 changes: 5 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.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.
Static HTML, CSS and browser ES modules. No analytics, accounts or backend. Inputs stay in the browser. The five demos use vendored 1.0.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 @@ -25,3 +25,7 @@ The organization palette and home badge follow the supplied husky artwork. Dalla
## License

MIT. Vendored package licenses are included in their directories.

## Practical v1 integrations

Each tool keeps its playful library and adds an equally visible developer-value panel: measured build summaries, Error cause diagnostics, accountable incident drafts, context redaction plus a runnable Node async-scope example, and report-backed release policies. The browser runs actual pure package APIs. AsyncLocalStorage, file reading, CI report collection and process exit preservation run in Node; examples identify those boundaries. Demo receipt data is labeled sample data, never claimed as real CI evidence.
126 changes: 126 additions & 0 deletions app-1.0.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@1.0.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.";
}
});
79 changes: 78 additions & 1 deletion dist/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -1495,6 +1495,81 @@ function broMessage(category = "deployment", options = {}) {
"message:" + category.trim().toLowerCase()
);
}

// src/build-summary.js
var clean = (value) => Array.from(value).filter(
(char) => char.charCodeAt(0) >= 32 || char === "\n" || char === " "
).join("");
var count = (value, label) => {
if (!Number.isSafeInteger(value) || value < 0)
throw new TypeError(`${label} must be a nonnegative safe integer.`);
return value;
};
function summarizeBuild(input = {}) {
if (!input || typeof input !== "object" || Array.isArray(input))
throw new TypeError("Build input must be an object.");
const result = { label: "Build", status: "unknown" };
for (const key of ["label", "commit"])
if (input[key] !== void 0) {
if (typeof input[key] !== "string" || !input[key].trim() || input[key].length > 200)
throw new TypeError(
`${key} must be a nonempty string up to 200 characters.`
);
result[key] = clean(input[key].trim());
}
if (input.exitCode !== void 0) {
result.exitCode = input.exitCode === null ? null : count(input.exitCode, "exitCode");
if (input.exitCode !== null)
result.status = input.exitCode === 0 ? "passed" : "failed";
}
if (input.durationMs !== void 0) {
if (typeof input.durationMs !== "number" || !Number.isFinite(input.durationMs) || input.durationMs < 0)
throw new TypeError("durationMs must be a nonnegative finite number.");
result.durationMs = input.durationMs;
}
if (input.tests !== void 0) {
if (!input.tests || typeof input.tests !== "object" || Array.isArray(input.tests))
throw new TypeError("tests must contain measured counts.");
result.tests = {
passed: count(input.tests.passed, "tests.passed"),
failed: count(input.tests.failed, "tests.failed"),
skipped: count(input.tests.skipped ?? 0, "tests.skipped")
};
if (result.tests.failed > 0) result.status = "failed";
}
const failures = input.failures ?? [];
if (!Array.isArray(failures) || failures.length > 20 || !failures.every(
(item) => typeof item === "string" && item.trim() && item.length <= 2e3
))
throw new TypeError(
"failures must contain up to 20 nonempty strings of at most 2000 characters."
);
result.failures = failures.map(clean);
if (failures.length) result.status = "failed";
result.text = [
`${result.label}: ${result.status.toUpperCase()}`,
`Build exit: ${result.exitCode === void 0 || result.exitCode === null ? "not provided" : result.exitCode}`,
result.tests ? `Tests: ${result.tests.passed} passed, ${result.tests.failed} failed, ${result.tests.skipped} skipped` : "Tests: not provided",
result.durationMs !== void 0 ? `Duration: ${result.durationMs} ms` : "Duration: not provided",
result.commit ? `Commit: ${result.commit}` : "Commit: not provided",
...result.failures.length ? ["Failures:", ...result.failures.map((item) => "- " + item)] : []
].join("\n");
return result;
}
function renderBuildSummary(input = {}, options = {}) {
if (!options || typeof options !== "object" || Array.isArray(options))
throw new TypeError("Options must be an object.");
const { format = "character", ...broOptions } = options;
if (!["character", "plain"].includes(format))
throw new RangeError("Summary format must be character or plain.");
const summary = summarizeBuild(input);
const rendered = format === "plain" ? summary.text : renderBro({
...broOptions,
text: summary.text,
mood: broOptions.mood ?? (summary.status === "passed" ? "benji" : summary.status === "failed" ? "on-call" : "skeptic")
}).rendered;
return { status: summary.status, summary, rendered };
}
export {
broMessage,
brosay2 as brosay,
Expand All @@ -1505,5 +1580,7 @@ export {
messageCategories,
messagePresets,
moods,
renderBro
renderBro,
renderBuildSummary,
summarizeBuild
};
13 changes: 7 additions & 6 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
</title>
<link rel="canonical" href="https://deployanyway.github.io/" />
<link rel="icon" href="favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="style-54d303f2.css?v=rich-0.4.1" />
<link rel="stylesheet" href="style-54d303f2.css?v=stable-1.0.0" />
<script type="importmap">
{ "imports": { "node:util": "./browser-format.js" } }
</script>
<script type="module" src="app-0.4.0.js?v=rich-0.4.1"></script>
<script type="module" src="siblings-0.4.0.js?v=rich-0.4.1"></script>
<script type="module" src="app-1.0.0.js?v=stable-1.0.0"></script>
<script type="module" src="siblings-1.0.0.js?v=stable-1.0.0"></script>
<script type="module" src="value-1.0.0.js"></script>
</head>
<body>
<main>
Expand All @@ -31,7 +32,7 @@
width="220"
height="220"
/></a>
<p class="eyebrow">0.4.0 · AVAILABLE ON NPM</p>
<p class="eyebrow">1.0.0 · AVAILABLE ON NPM</p>
<h1>DeployAnyway.</h1>
<p class="manifesto">
Software nobody requested, built with questionable priorities, and
Expand Down Expand Up @@ -97,7 +98,7 @@ <h2 id="why-title">Serious about the work. Playful about the day.</h2>
>
</nav>
<p>
All five packages are AVAILABLE ON NPM at version 0.4.0. These demos run
All five packages are AVAILABLE ON NPM at version 1.0.0. These demos run
locally in your browser.
</p>
<section class="tool-section" id="bro-say" aria-labelledby="bro-title">
Expand Down Expand Up @@ -171,7 +172,7 @@ <h2 id="bro-title">bro-say</h2>
</p>
<p>Runs entirely in your browser. No message is sent anywhere.</p>
<p>
<code>npm install -g @deployanyway/bro-say@0.4.0</code> ·
<code>npm install -g @deployanyway/bro-say@1.0.0</code> ·
<a href="https://www.npmjs.com/org/deployanyway"
>All five packages on npm</a
>
Expand Down
Loading
Loading