create-skaff 0.0.2 → 0.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-skaff",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Interactive Next.js project scaffolder: Tailwind, shadcn/ui, Motion, Lucide, Oxlint, Oxfmt, Ultracite, Claude and Codex config",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,15 @@
1
+ type AnsweredRowProps = { label: string; value: string };
2
+
3
+ export function AnsweredRow({ label, value }: AnsweredRowProps) {
4
+ return (
5
+ <box flexDirection="column">
6
+ <text>
7
+ <span fg="#4ade80">◇</span> {label}
8
+ </text>
9
+ <text>
10
+ <span fg="#555555">│</span> <span fg="#888888">{value}</span>
11
+ </text>
12
+ <text fg="#555555">│</text>
13
+ </box>
14
+ );
15
+ }
@@ -0,0 +1,17 @@
1
+ import { type ScaffoldConfig, projectDir } from "../lib/scaffold-steps";
2
+
3
+ type ConfirmPromptProps = { config: ScaffoldConfig };
4
+
5
+ export function ConfirmPrompt({ config }: ConfirmPromptProps) {
6
+ return (
7
+ <box flexDirection="column">
8
+ <text>
9
+ <span fg="#a78bfa">◆</span> <strong>Scaffold into {projectDir(config)}?</strong>
10
+ </text>
11
+ <text>
12
+ <span fg="#a78bfa">│ </span>
13
+ <span fg="#666666">Enter to start · Esc to go back</span>
14
+ </text>
15
+ </box>
16
+ );
17
+ }
@@ -0,0 +1,36 @@
1
+ import { useState } from "react";
2
+
3
+ type NamePromptProps = { initialValue: string; onSubmit: (name: string) => void };
4
+
5
+ export function NamePrompt({ initialValue, onSubmit }: NamePromptProps) {
6
+ const [value, setValue] = useState(initialValue);
7
+ const name = value.trim();
8
+ const valid = name === "." || /^[a-z0-9][a-z0-9._-]*$/.test(name);
9
+ return (
10
+ <box flexDirection="column">
11
+ <text>
12
+ <span fg="#a78bfa">◆</span> <strong>Project name</strong>
13
+ </text>
14
+ <box flexDirection="row">
15
+ <text fg="#a78bfa">│ </text>
16
+ <box width={40}>
17
+ <input
18
+ focused
19
+ placeholder="my-app or . for the current directory"
20
+ value={initialValue}
21
+ onInput={setValue}
22
+ onSubmit={() => {
23
+ if (valid) {
24
+ onSubmit(name);
25
+ }
26
+ }}
27
+ />
28
+ </box>
29
+ </box>
30
+ <text>
31
+ <span fg="#a78bfa">│ </span>
32
+ <span fg={valid || name === "" ? "#666666" : "#f87171"}>lowercase letters, numbers, dots, dashes · Enter to continue</span>
33
+ </text>
34
+ </box>
35
+ );
36
+ }
@@ -0,0 +1,38 @@
1
+ import { useKeyboard } from "@opentui/react";
2
+ import { useState } from "react";
3
+ import { type PackageManager, packageManagers } from "../lib/package-manager";
4
+
5
+ type PackageManagerPromptProps = { initialValue: PackageManager; onSelect: (packageManager: PackageManager) => void };
6
+
7
+ export function PackageManagerPrompt({ initialValue, onSelect }: PackageManagerPromptProps) {
8
+ const [index, setIndex] = useState(Math.max(0, packageManagers.indexOf(initialValue)));
9
+
10
+ useKeyboard((key) => {
11
+ if (key.name === "up" || key.name === "k") {
12
+ setIndex((current) => (current + packageManagers.length - 1) % packageManagers.length);
13
+ } else if (key.name === "down" || key.name === "j") {
14
+ setIndex((current) => (current + 1) % packageManagers.length);
15
+ } else if (key.name === "return") {
16
+ onSelect(packageManagers[index]);
17
+ }
18
+ });
19
+
20
+ return (
21
+ <box flexDirection="column">
22
+ <text>
23
+ <span fg="#a78bfa">◆</span> <strong>Package manager</strong>
24
+ </text>
25
+ {packageManagers.map((value, i) => (
26
+ <text key={value}>
27
+ <span fg="#a78bfa">│ </span>
28
+ {i === index ? <span fg="#4ade80">◉ </span> : <span fg="#555555">○ </span>}
29
+ <span fg={i === index ? "#ffffff" : "#888888"}>{value}</span>
30
+ </text>
31
+ ))}
32
+ <text>
33
+ <span fg="#a78bfa">│ </span>
34
+ <span fg="#666666">↑↓ to move · Enter to continue · Esc to go back</span>
35
+ </text>
36
+ </box>
37
+ );
38
+ }
@@ -0,0 +1,40 @@
1
+ import { useKeyboard } from "@opentui/react";
2
+ import { useScaffoldRunner } from "../lib/hooks/use-scaffold-runner";
3
+ import type { ScaffoldConfig } from "../lib/scaffold-steps";
4
+ import { StepRow } from "./step-row";
5
+
6
+ type ProgressTimelineProps = { config: ScaffoldConfig; onExit: () => void };
7
+
8
+ export function ProgressTimeline({ config, onExit }: ProgressTimelineProps) {
9
+ const { steps, finished, error } = useScaffoldRunner(config);
10
+ useKeyboard((key) => {
11
+ if (finished && (key.name === "q" || key.name === "return")) {
12
+ onExit();
13
+ }
14
+ });
15
+ return (
16
+ <box flexDirection="column">
17
+ {steps.map((step) => (
18
+ <StepRow key={step.id} step={step} />
19
+ ))}
20
+ {error ? (
21
+ <box flexDirection="column">
22
+ <text>
23
+ <span fg="#f87171">└ Failed</span>
24
+ </text>
25
+ <text fg="#f87171">{error}</text>
26
+ </box>
27
+ ) : null}
28
+ {finished && !error ? (
29
+ <box flexDirection="column">
30
+ <text>
31
+ <span fg="#4ade80">└ Done.</span> <span fg="#888888">Next steps:</span>
32
+ </text>
33
+ {config.name === "." ? null : <text>{` cd ${config.name}`}</text>}
34
+ <text>{` ${config.packageManager} run dev`}</text>
35
+ </box>
36
+ ) : null}
37
+ {finished ? <text fg="#666666">Enter or q to exit</text> : null}
38
+ </box>
39
+ );
40
+ }
@@ -1,10 +1,10 @@
1
1
  import type { StepState } from "../lib/hooks/use-scaffold-runner";
2
2
 
3
3
  const glyphs: Record<StepState["status"], { icon: string; color: string }> = {
4
- pending: { icon: "○", color: "#666666" },
5
- running: { icon: "", color: "#facc15" },
6
- done: { icon: "", color: "#4ade80" },
7
- failed: { icon: "", color: "#f87171" },
4
+ pending: { icon: "○", color: "#555555" },
5
+ running: { icon: "", color: "#facc15" },
6
+ done: { icon: "", color: "#4ade80" },
7
+ failed: { icon: "", color: "#f87171" },
8
8
  };
9
9
 
10
10
  type StepRowProps = { step: StepState };
@@ -14,11 +14,12 @@ export function StepRow({ step }: StepRowProps) {
14
14
  return (
15
15
  <box flexDirection="column">
16
16
  <text>
17
- <span fg={glyph.color}>{glyph.icon}</span> {step.label}
17
+ <span fg={glyph.color}>{glyph.icon}</span> <span fg={step.status === "pending" ? "#666666" : "#ffffff"}>{step.label}</span>
18
+ </text>
19
+ <text>
20
+ <span fg="#555555">│ </span>
21
+ <span fg="#666666">{step.status === "running" ? step.lastLine.slice(0, 80) : ""}</span>
18
22
  </text>
19
- {step.status === "running" && step.lastLine ? (
20
- <text fg="#888888">{` ${step.lastLine.slice(0, 80)}`}</text>
21
- ) : null}
22
23
  </box>
23
24
  );
24
25
  }
@@ -2,10 +2,11 @@ import { useKeyboard, useRenderer } from "@opentui/react";
2
2
  import { useState } from "react";
3
3
  import type { PackageManager } from "../lib/package-manager";
4
4
  import type { ScaffoldConfig } from "../lib/scaffold-steps";
5
- import { ConfirmStep } from "./confirm-step";
6
- import { NameStep } from "./name-step";
7
- import { PackageManagerStep } from "./package-manager-step";
8
- import { ProgressStep } from "./progress-step";
5
+ import { AnsweredRow } from "./answered-row";
6
+ import { ConfirmPrompt } from "./confirm-prompt";
7
+ import { NamePrompt } from "./name-prompt";
8
+ import { PackageManagerPrompt } from "./package-manager-prompt";
9
+ import { ProgressTimeline } from "./progress-timeline";
9
10
 
10
11
  type Screen = "name" | "packageManager" | "confirm" | "progress";
11
12
 
@@ -40,13 +41,19 @@ export function WizardApp({ initialName, cwd }: WizardAppProps) {
40
41
  }
41
42
  });
42
43
 
44
+ const nameAnswered = screen !== "name";
45
+ const packageManagerAnswered = screen === "confirm" || screen === "progress";
46
+
43
47
  return (
44
- <box flexDirection="column" padding={1} gap={1}>
48
+ <box flexDirection="column" paddingLeft={1} paddingTop={1}>
45
49
  <text>
46
- <strong fg="#a78bfa">skaff</strong> <span fg="#888888">· Next.js scaffolder</span>
50
+ <span fg="#555555">┌</span> <strong fg="#a78bfa">skaff</strong> <span fg="#888888">· Next.js scaffolder</span>
47
51
  </text>
52
+ <text fg="#555555">│</text>
53
+ {nameAnswered ? <AnsweredRow label="Project name" value={name} /> : null}
54
+ {packageManagerAnswered ? <AnsweredRow label="Package manager" value={packageManager} /> : null}
48
55
  {screen === "name" ? (
49
- <NameStep
56
+ <NamePrompt
50
57
  initialValue={name}
51
58
  onSubmit={(value) => {
52
59
  setName(value);
@@ -55,15 +62,16 @@ export function WizardApp({ initialName, cwd }: WizardAppProps) {
55
62
  />
56
63
  ) : null}
57
64
  {screen === "packageManager" ? (
58
- <PackageManagerStep
65
+ <PackageManagerPrompt
66
+ initialValue={packageManager}
59
67
  onSelect={(value) => {
60
68
  setPackageManager(value);
61
69
  setScreen("confirm");
62
70
  }}
63
71
  />
64
72
  ) : null}
65
- {screen === "confirm" ? <ConfirmStep config={{ name, packageManager, cwd }} /> : null}
66
- {screen === "progress" && config ? <ProgressStep config={config} onExit={exit} /> : null}
73
+ {screen === "confirm" ? <ConfirmPrompt config={{ name, packageManager, cwd }} /> : null}
74
+ {screen === "progress" && config ? <ProgressTimeline config={config} onExit={exit} /> : null}
67
75
  </box>
68
76
  );
69
77
  }
@@ -1,33 +0,0 @@
1
- import type { ScaffoldConfig } from "../lib/scaffold-steps";
2
- import { projectDir, scaffoldSteps } from "../lib/scaffold-steps";
3
-
4
- type ConfirmStepProps = { config: ScaffoldConfig };
5
-
6
- export function ConfirmStep({ config }: ConfirmStepProps) {
7
- return (
8
- <box flexDirection="column" gap={1}>
9
- <text>
10
- <strong>Ready to scaffold</strong>
11
- </text>
12
- <box flexDirection="column" border borderStyle="rounded" paddingLeft={1} paddingRight={1}>
13
- <text>
14
- name: <span fg="#7dd3fc">{config.name}</span>
15
- </text>
16
- <text>
17
- package manager: <span fg="#7dd3fc">{config.packageManager}</span>
18
- </text>
19
- <text>
20
- directory: <span fg="#7dd3fc">{projectDir(config)}</span>
21
- </text>
22
- </box>
23
- <box flexDirection="column">
24
- {scaffoldSteps.map((step) => (
25
- <text key={step.id} fg="#aaaaaa">
26
- · {step.label}
27
- </text>
28
- ))}
29
- </box>
30
- <text fg="#888888">Enter to start · Esc to go back</text>
31
- </box>
32
- );
33
- }
@@ -1,30 +0,0 @@
1
- import { useState } from "react";
2
-
3
- type NameStepProps = { initialValue: string; onSubmit: (name: string) => void };
4
-
5
- export function NameStep({ initialValue, onSubmit }: NameStepProps) {
6
- const [value, setValue] = useState(initialValue);
7
- const name = value.trim();
8
- const valid = name === "." || /^[a-z0-9][a-z0-9._-]*$/.test(name);
9
- return (
10
- <box flexDirection="column" gap={1}>
11
- <text>
12
- <strong>Project name</strong>
13
- </text>
14
- <box border borderStyle="rounded" paddingLeft={1} paddingRight={1} width={40}>
15
- <input
16
- focused
17
- placeholder="my-app"
18
- value={initialValue}
19
- onInput={setValue}
20
- onSubmit={() => {
21
- if (valid) {
22
- onSubmit(name);
23
- }
24
- }}
25
- />
26
- </box>
27
- <text fg="#888888">lowercase letters, numbers, dots, dashes, or . for the current directory · Enter to continue</text>
28
- </box>
29
- );
30
- }
@@ -1,29 +0,0 @@
1
- import type { SelectOption } from "@opentui/core";
2
- import { type PackageManager, packageManagers } from "../lib/package-manager";
3
-
4
- type PackageManagerStepProps = { onSelect: (packageManager: PackageManager) => void };
5
-
6
- const options: SelectOption[] = packageManagers.map((value) => ({ name: value, value, description: "" }));
7
-
8
- export function PackageManagerStep({ onSelect }: PackageManagerStepProps) {
9
- return (
10
- <box flexDirection="column" gap={1}>
11
- <text>
12
- <strong>Package manager</strong>
13
- </text>
14
- <box border borderStyle="rounded" width={40} height={5}>
15
- <select
16
- focused
17
- options={options}
18
- showDescription={false}
19
- onSelect={(_index, option) => {
20
- if (option && packageManagers.includes(option.value as PackageManager)) {
21
- onSelect(option.value as PackageManager);
22
- }
23
- }}
24
- />
25
- </box>
26
- <text fg="#888888">↑↓ to move · Enter to continue · Esc to go back</text>
27
- </box>
28
- );
29
- }
@@ -1,40 +0,0 @@
1
- import { useKeyboard } from "@opentui/react";
2
- import { useScaffoldRunner } from "../lib/hooks/use-scaffold-runner";
3
- import type { ScaffoldConfig } from "../lib/scaffold-steps";
4
- import { StepRow } from "./step-row";
5
-
6
- type ProgressStepProps = { config: ScaffoldConfig; onExit: () => void };
7
-
8
- export function ProgressStep({ config, onExit }: ProgressStepProps) {
9
- const { steps, finished, error } = useScaffoldRunner(config);
10
- useKeyboard((key) => {
11
- if (finished && (key.name === "q" || key.name === "return")) {
12
- onExit();
13
- }
14
- });
15
- return (
16
- <box flexDirection="column" gap={1}>
17
- <text>
18
- <strong>{finished ? (error ? "Failed" : "Done") : "Scaffolding…"}</strong>
19
- </text>
20
- <box flexDirection="column">
21
- {steps.map((step) => (
22
- <StepRow key={step.id} step={step} />
23
- ))}
24
- </box>
25
- {error ? (
26
- <box border borderStyle="rounded" borderColor="#f87171" paddingLeft={1} paddingRight={1}>
27
- <text fg="#f87171">{error}</text>
28
- </box>
29
- ) : null}
30
- {finished && !error ? (
31
- <box flexDirection="column">
32
- <text fg="#4ade80">Next steps:</text>
33
- {config.name === "." ? null : <text>{` cd ${config.name}`}</text>}
34
- <text>{` ${config.packageManager} run dev`}</text>
35
- </box>
36
- ) : null}
37
- <text fg="#888888">{finished ? "Enter or q to exit" : "running… Ctrl+C to abort"}</text>
38
- </box>
39
- );
40
- }