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
5 changes: 5 additions & 0 deletions .changeset/mod-hide-missing-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"playground-cli": patch
---

`playground mod`: stop showing a warning when an app has no `setup.sh`. Apps without a setup script are the common case, and surfacing it as a yellow warning row alarmed users. The step is now skipped silently and the usual "Next steps" footer is printed as before.
17 changes: 14 additions & 3 deletions src/commands/mod/SetupScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,11 @@ export function SetupScreen({ domain, metadata: initial, registry, targetDir, on
run: async (log) => {
const setupPath = resolve(targetDir, "setup.sh");
if (!existsSync(setupPath)) {
throw new StepWarning("no setup.sh found");
// Most moddable apps have no setup.sh, and that's normal —
// surfacing it as a warning row alarmed users. Skip the step
// silently so nothing is shown; the parent still prints the
// generic "Next steps" footer because `setupRan` stays false.
throw new SilentSkip("no setup.sh found");
}
const missing = await findUnsatisfiedPackageManagers(
readFileSync(setupPath, "utf8"),
Expand Down Expand Up @@ -216,10 +220,17 @@ export function SetupScreen({ domain, metadata: initial, registry, targetDir, on
);
}

class StepWarning extends Error {
isWarning = true;
/**
* Thrown inside a StepRunner step to remove its row from the UI entirely
* (StepRunner duck-types the `isSilentSkip` flag). Execution continues and the
* run still reports `ok: true`. Used when an optional step has nothing to do
* and its absence is a non-event the user shouldn't see.
*/
class SilentSkip extends Error {
readonly isSilentSkip = true;
constructor(message: string) {
super(message);
this.name = "SilentSkip";
}
}

Expand Down
48 changes: 32 additions & 16 deletions src/utils/ui/components/StepRunner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
* STOP execution, but report `ok: false` with no `error` — for cases the
* parent wants to present gently (its own Callout) rather than as a red
* failure row, while still skipping any success-only output.
* Silent skips (`isSilentSkip = true`) remove the step's row from the UI
* entirely and don't stop execution (`ok` is preserved) — for optional steps
* whose absence is a non-event the user shouldn't see (e.g. an app with no
* `setup.sh` to run).
*/

import { useState, useEffect, useRef } from "react";
Expand All @@ -46,7 +50,7 @@ export interface Step {
keepLogOnSuccess?: boolean;
}

type StepStatus = "pending" | "running" | "ok" | "failed" | "warning";
type StepStatus = "pending" | "running" | "ok" | "failed" | "warning" | "skipped";

interface StepState {
name: string;
Expand Down Expand Up @@ -144,7 +148,14 @@ export function StepRunner({ title, steps, onDone }: Props) {
const isWarning = err instanceof Error && (err as any).isWarning === true;
const haltAsWarning =
err instanceof Error && (err as any).haltAsWarning === true;
const isSilentSkip = err instanceof Error && (err as any).isSilentSkip === true;

if (isSilentSkip) {
setStates((prev) =>
prev.map((s, j) => (j === i ? { ...s, status: "skipped" } : s)),
);
continue;
}
if (haltAsWarning) {
setStates((prev) =>
prev.map((s, j) =>
Expand Down Expand Up @@ -188,21 +199,26 @@ export function StepRunner({ title, steps, onDone }: Props) {

return (
<Section title={title}>
{states.map((step) => (
<Box key={step.name} flexDirection="column">
<Row
mark={toMark(step.status)}
label={step.name}
value={step.message}
tone={step.status === "failed" ? "danger" : "muted"}
/>
{step.retainedLog && step.retainedLog.length > 0 && (
<Box marginTop={1} marginBottom={1}>
<LogTail lines={step.retainedLog} height={step.retainedLog.length} />
</Box>
)}
</Box>
))}
{states
.filter((step) => step.status !== "skipped")
.map((step) => (
<Box key={step.name} flexDirection="column">
<Row
mark={toMark(step.status)}
label={step.name}
value={step.message}
tone={step.status === "failed" ? "danger" : "muted"}
/>
{step.retainedLog && step.retainedLog.length > 0 && (
<Box marginTop={1} marginBottom={1}>
<LogTail
lines={step.retainedLog}
height={step.retainedLog.length}
/>
</Box>
)}
</Box>
))}
{running && liveOutput.length > 0 && (
<Box marginTop={1}>
<LogTail lines={liveOutput} height={LIVE_LOG_LINES} />
Expand Down
Loading