Skip to content

feat: improve build profile DevEx — a la carte collection picker, copy buttons, contributing guide link#7588

Open
yi-nuo426 wants to merge 1 commit intomasterfrom
work/gracious-yalow
Open

feat: improve build profile DevEx — a la carte collection picker, copy buttons, contributing guide link#7588
yi-nuo426 wants to merge 1 commit intomasterfrom
work/gracious-yalow

Conversation

@yi-nuo426
Copy link
Copy Markdown
Contributor

Summary

This PR improves the developer experience around site build profiles, make targets, and the lite-mode placeholder page that contributors see when visiting a disabled route.

Make targets

  • make site-content — new target for the content profile (blog/news/events/resources included; members/integrations skipped). Previously the content profile existed in code but had no make target — it was only discoverable via an echo hint in make site.
  • make site-custom — new target for fully à la carte builds. Uses a new none base profile (zero preset exclusions) so BUILD_COLLECTIONS_EXCLUDE is the only thing that controls what is skipped. Example: BUILD_COLLECTIONS_EXCLUDE=members,events make site-custom
  • make profiles — new target that lists all profiles, their excluded collections, and the BUILD_COLLECTIONS_EXCLUDE escape hatch.
  • make cache-clean — new target that clears the Gatsby cache without triggering a full production rebuild. The existing make clean does both, which surprised contributors who just wanted to reset cache.
  • make site-analyze — fixed missing ## help comment and .PHONY entry (was invisible to make help output).
  • site-fast — description updated from the useless "Alternate method" to a precise description of what it actually skips.

npm scripts

  • develop:content — mirrors develop:lite with LITE_BUILD_PROFILE=content.
  • develop:custom — uses LITE_BUILD_PROFILE=none for the à la carte target.
  • start:content — alias consistent with the existing start / start:full pattern.

build-collections.js

  • Added none profile — excludes nothing by default, making BUILD_COLLECTIONS_EXCLUDE the sole driver of exclusions for make site-custom.

Lite-mode placeholder page

The placeholder shown when a contributor visits a disabled route (e.g. /blog in core mode) now has three improvements:

  1. Copy-to-clipboard buttons on every suggested make command.
  2. A la carte collection picker — six checkboxes that dynamically generate the correct make command as you toggle them. The current route's collection is pre-checked.
  3. Link to the contributing guide (CONTRIBUTING.md#environment-variables) for full profile documentation.

Lint-staged fix

Added --no-warn-ignored to the ESLint call in .lintstagedrc.js. src/utils/ is in ESLint's ignore list, but lint-staged passes files explicitly, causing a warning that tripped --max-warnings=0 on every commit touching a src/utils/ file.

Test plan

  • make (no args) — help output lists site-content, site-custom, profiles, cache-clean, site-analyze
  • make profiles — prints all four profiles with accurate exclusion lists
  • make site — echo banner mentions site-content and site-full
  • make site-content — dev server starts with blog/news/events/resources enabled
  • make cache-clean — clears .cache without starting a build
  • BUILD_COLLECTIONS_EXCLUDE=members,events make site-custom — only listed collections excluded
  • Visit /blog in core mode — placeholder shows make site-content first with Copy button; blog pre-checked in picker
  • Visit /community/members — placeholder shows only make site-full
  • Toggle checkboxes — command updates live; all checked → make site-full; none checked → make site
  • Contributing guide link opens CONTRIBUTING.md#environment-variables

- Add `make site-content` target for the content profile (blog/news/events/resources
  included; members/integrations skipped) — previously only discoverable via an echo hint
- Add `make site-custom` target backed by a new `none` profile that starts with zero
  preset exclusions, so BUILD_COLLECTIONS_EXCLUDE fully controls what is skipped
- Add `make profiles` target that lists all profiles with their excluded collections
  and documents the BUILD_COLLECTIONS_EXCLUDE escape hatch
- Add `make cache-clean` to clear Gatsby cache without triggering a full production
  rebuild (the existing `make clean` does both, which surprised contributors)
- Add `develop:content` and `develop:custom` npm scripts to support the new targets
- Fix `site-analyze` missing ## help comment and .PHONY entry
- Improve `site-fast` description from "Alternate method" to something accurate
- Update lite-placeholder.js with three contributor-facing improvements:
  * Copy-to-clipboard button on every suggested make command
  * A la carte collection picker — checkboxes generate the exact make command
    needed; the current route's collection is pre-checked for convenience
  * Link to the contributing guide's Environment Variables section
- Pass `collection` name through gatsby-node.js litePlaceholderPages context so
  the template can pre-select the relevant checkbox
- Add `none` profile to build-collections.js as the foundation for a la carte builds
- Fix lint-staged silently failing on files in ESLint-ignored directories (add
  --no-warn-ignored to suppress the spurious warning that tripped --max-warnings=0)

Signed-off-by: Lee Calcote <lee.calcote@layer5.io>
Copilot AI review requested due to automatic review settings April 4, 2026 17:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves developer experience around build profiles/targets and enhances the lite-mode placeholder UX to help contributors quickly re-enable disabled collections.

Changes:

  • Adds new build profiles and ergonomics: site-content, site-custom, profiles, and cache-clean; improves Makefile help text and fixes site-analyze visibility.
  • Adds npm scripts for content and custom lite development modes.
  • Upgrades the lite placeholder page with copy-to-clipboard buttons, an à la carte collection picker, and a link to contributing docs.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/utils/build-collections.js Adds a none lite profile to enable fully à la carte exclusion via BUILD_COLLECTIONS_EXCLUDE.
src/templates/lite-placeholder.js Adds copy buttons, collection picker, and contributing guide link to the lite-mode placeholder page.
package.json Adds develop:content, develop:custom, and start:content scripts for new profiles.
gatsby-node.js Passes enabledBy and collection into placeholder context to drive UI defaults.
Makefile Introduces new targets (site-content, site-custom, profiles, cache-clean) and improves help output.
.lintstagedrc.js Prevents --max-warnings=0 failures caused by ESLint “ignored file” warnings during lint-staged runs.

Comment on lines +57 to +61
if (typeof navigator === "undefined") return;
navigator.clipboard.writeText(text).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 1500);
});
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

navigator.clipboardis not guaranteed to exist (and can fail outside secure contexts or due to permissions). As written, this can throw (clipboard undefined) or create an unhandled promise rejection (writeText rejected). Guard with a check fornavigator.clipboard?.writeText(and optionallywindow.isSecureContext) and add a .catch(...)` path (or fallback copy approach) so the UI fails gracefully instead of erroring.

Suggested change
if (typeof navigator === "undefined") return;
navigator.clipboard.writeText(text).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 1500);
});
if (
typeof navigator === "undefined" ||
typeof window === "undefined" ||
!window.isSecureContext ||
!navigator.clipboard?.writeText
) {
return;
}
navigator.clipboard
.writeText(text)
.then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 1500);
})
.catch(() => {});

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +62
const [copied, setCopied] = useState(false);

const handleCopy = useCallback(() => {
if (typeof navigator === "undefined") return;
navigator.clipboard.writeText(text).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 1500);
});
}, [text]);
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setTimeout is not cleaned up, so this can attempt to update state after unmount (React warns about state updates on unmounted components). Store the timeout id (e.g., via useRef) and clear it in an effect cleanup; also consider clearing any existing timer before setting a new one on repeated clicks.

Copilot uses AI. Check for mistakes.
Comment on lines +65 to +83
<button
onClick={handleCopy}
aria-label={copied ? "Copied!" : `Copy command: ${text}`}
style={{
flexShrink: 0,
padding: "0.2rem 0.6rem",
background: copied ? "#00b39f" : "transparent",
border: "1px solid #00b39f",
borderRadius: "4px",
color: copied ? "#fff" : "#00b39f",
cursor: "pointer",
fontSize: "0.75rem",
lineHeight: 1.4,
transition: "background 0.15s, color 0.15s",
whiteSpace: "nowrap",
}}
>
{copied ? "Copied!" : "Copy"}
</button>
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “Copied!” state is purely visual; many screen readers won’t announce the change reliably. Consider adding an aria-live="polite" status element (or role="status") that announces “Copied” on success. Also consider adding type="button" to avoid unintended form submission behavior if this component is ever rendered inside a form.

Suggested change
<button
onClick={handleCopy}
aria-label={copied ? "Copied!" : `Copy command: ${text}`}
style={{
flexShrink: 0,
padding: "0.2rem 0.6rem",
background: copied ? "#00b39f" : "transparent",
border: "1px solid #00b39f",
borderRadius: "4px",
color: copied ? "#fff" : "#00b39f",
cursor: "pointer",
fontSize: "0.75rem",
lineHeight: 1.4,
transition: "background 0.15s, color 0.15s",
whiteSpace: "nowrap",
}}
>
{copied ? "Copied!" : "Copy"}
</button>
<>
<button
type="button"
onClick={handleCopy}
aria-label={copied ? "Copied!" : `Copy command: ${text}`}
style={{
flexShrink: 0,
padding: "0.2rem 0.6rem",
background: copied ? "#00b39f" : "transparent",
border: "1px solid #00b39f",
borderRadius: "4px",
color: copied ? "#fff" : "#00b39f",
cursor: "pointer",
fontSize: "0.75rem",
lineHeight: 1.4,
transition: "background 0.15s, color 0.15s",
whiteSpace: "nowrap",
}}
>
{copied ? "Copied!" : "Copy"}
</button>
<span
aria-live="polite"
role="status"
style={{
position: "absolute",
width: "1px",
height: "1px",
padding: 0,
margin: "-1px",
overflow: "hidden",
clip: "rect(0, 0, 0, 0)",
whiteSpace: "nowrap",
border: 0,
}}
>
{copied ? "Copied!" : ""}
</span>
</>

Copilot uses AI. Check for mistakes.
Comment on lines +158 to +161
<SEO
title={heading}
description={`${description} ${restoreCommands[0].cmd}`}
/>
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SEO is rendered both in the component body and in the Gatsby Head export, which can produce duplicate meta tags and inconsistent head state. Prefer a single approach (typically the Head export in Gatsby 5). Removing the in-body <SEO ... /> will avoid duplication and keep head management in one place.

Copilot uses AI. Check for mistakes.
@@ -41,15 +302,16 @@ const LitePlaceholder = ({ pageContext, location }) => {
export default LitePlaceholder;

export const Head = ({ pageContext }) => {
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SEO is rendered both in the component body and in the Gatsby Head export, which can produce duplicate meta tags and inconsistent head state. Prefer a single approach (typically the Head export in Gatsby 5). Removing the in-body <SEO ... /> will avoid duplication and keep head management in one place.

Copilot uses AI. Check for mistakes.
Comment on lines 311 to 315
return (
<SEO
title={heading}
description={`${description} ${instructions}`.trim()}
description={`${description} ${commands[0].cmd}`.trim()}
/>
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SEO is rendered both in the component body and in the Gatsby Head export, which can produce duplicate meta tags and inconsistent head state. Prefer a single approach (typically the Head export in Gatsby 5). Removing the in-body <SEO ... /> will avoid duplication and keep head management in one place.

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +49
function generateCommand(included) {
const excluded = ALL_COLLECTIONS.filter((c) => !included.has(c)).sort();

if (excluded.length === 0) return "make site-full";
if (excluded.length === ALL_COLLECTIONS.length) return "make site";

const isContentProfile =
excluded.length === 2 &&
excluded[0] === "integrations" &&
excluded[1] === "members";
if (isContentProfile) return "make site-content";

return `BUILD_COLLECTIONS_EXCLUDE=${excluded.join(",")} make site-custom`;
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isContentProfile detection relies on excluded.sort() producing a specific positional order, which is fragile if collection names change or new collections are introduced. Prefer checking membership (e.g., “excluded has exactly members+integrations”) instead of index-based comparisons to make the logic resilient.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants