create-turbo-wizard 0.1.2 → 0.2.0

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.
Files changed (2) hide show
  1. package/dist/cli.js +386 -67
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -15,13 +15,38 @@ var __export = (target, all) => {
15
15
  };
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
 
18
+ // ../../packages/types/src/command.ts
19
+ function buildCommand(config) {
20
+ const parts = [
21
+ ...CREATE_PREFIX[config.packageManager],
22
+ `--name ${config.name}`,
23
+ `--frontend ${config.frontend}`,
24
+ `--content ${config.content}`,
25
+ `--package-manager ${config.packageManager}`
26
+ ];
27
+ config.deploy !== "node" && parts.push(`--deploy ${config.deploy}`);
28
+ config.modules.length && parts.push(`--modules ${config.modules.join(" ")}`);
29
+ config.experimental.cacheComponents && parts.push("--cache-components");
30
+ config.experimental.reactCompiler && parts.push("--react-compiler");
31
+ return parts.join(" ");
32
+ }
33
+ var CREATE_PREFIX;
34
+ var init_command = __esm(() => {
35
+ CREATE_PREFIX = {
36
+ npm: ["npm create turbo-wizard@latest", "--"],
37
+ pnpm: ["pnpm create turbo-wizard@latest"],
38
+ bun: ["bun create turbo-wizard@latest"]
39
+ };
40
+ });
41
+
18
42
  // ../../packages/types/src/index.ts
19
43
  import { z } from "zod";
20
44
  function applyPreset(preset, overrides = {}) {
21
45
  return { ...PRESET_CONFIGS[preset], ...overrides };
22
46
  }
23
- var FRONTENDS, CONTENT_SOURCES, PACKAGE_MANAGERS, MODULES, DEPLOY_TARGETS, FrontendSchema, ContentSourceSchema, PackageManagerSchema, ModuleSchema, DeployTargetSchema, ExperimentalSchema, ProjectNameSchema, WizardConfigSchema, wizardConfigJsonSchema = () => z.toJSONSchema(WizardConfigSchema), PRESETS, PRESET_CONFIGS;
47
+ var FRONTENDS, CONTENT_SOURCES, PACKAGE_MANAGERS, MODULES, MODULE_CATEGORIES, DEPLOY_TARGETS, FrontendSchema, ContentSourceSchema, PackageManagerSchema, ModuleSchema, DeployTargetSchema, ExperimentalSchema, ProjectNameSchema, WizardConfigSchema, wizardConfigJsonSchema = () => z.toJSONSchema(WizardConfigSchema), PRESETS, PRESET_CONFIGS;
24
48
  var init_src = __esm(() => {
49
+ init_command();
25
50
  FRONTENDS = ["astro", "next"];
26
51
  CONTENT_SOURCES = ["sanity", "markdown", "none"];
27
52
  PACKAGE_MANAGERS = ["pnpm", "bun", "npm"];
@@ -39,6 +64,7 @@ var init_src = __esm(() => {
39
64
  "forms",
40
65
  "llms"
41
66
  ];
67
+ MODULE_CATEGORIES = ["devtools", "integration", "ui", "cross-cutting"];
42
68
  DEPLOY_TARGETS = ["node", "vercel"];
43
69
  FrontendSchema = z.enum(FRONTENDS).describe("Web framework");
44
70
  ContentSourceSchema = z.enum(CONTENT_SOURCES).describe("Content source");
@@ -75,7 +101,7 @@ var package_default;
75
101
  var init_package = __esm(() => {
76
102
  package_default = {
77
103
  name: "create-turbo-wizard",
78
- version: "0.1.2",
104
+ version: "0.2.0",
79
105
  description: "Scaffold a Turborepo monorepo: Astro or Next.js, Sanity or Markdown, opt-in modules.",
80
106
  keywords: [
81
107
  "turborepo",
@@ -3886,6 +3912,10 @@ var init_astro = __esm(() => {
3886
3912
  astroFrontend = {
3887
3913
  id: "astro",
3888
3914
  kind: "frontend",
3915
+ prompt: {
3916
+ label: "Astro",
3917
+ hint: "Islands architecture, zero client JS by default"
3918
+ },
3889
3919
  templates: [
3890
3920
  {
3891
3921
  path: "apps/web/package.json",
@@ -4061,6 +4091,10 @@ var init_next = __esm(() => {
4061
4091
  nextFrontend = {
4062
4092
  id: "next",
4063
4093
  kind: "frontend",
4094
+ prompt: {
4095
+ label: "Next.js",
4096
+ hint: "App Router, React Server Components, Turbopack"
4097
+ },
4064
4098
  templates: [
4065
4099
  {
4066
4100
  path: "apps/web/package.json",
@@ -6731,6 +6765,35 @@ var init_vitest = __esm(() => {
6731
6765
  };
6732
6766
  });
6733
6767
 
6768
+ // ../../packages/registry/src/catalog.ts
6769
+ function withPrerequisites(selected) {
6770
+ const chosen = new Set(selected);
6771
+ const added = [];
6772
+ const queue = [...selected];
6773
+ for (const id of queue) {
6774
+ for (const required of MODULE_DESCRIPTORS[id]?.compat?.requiresModules ?? []) {
6775
+ if (chosen.has(required))
6776
+ continue;
6777
+ chosen.add(required);
6778
+ added.push([required, id]);
6779
+ queue.push(required);
6780
+ }
6781
+ }
6782
+ return { modules: MODULES.filter((id) => chosen.has(id)), added };
6783
+ }
6784
+ var entry = (id, descriptor) => ({
6785
+ id,
6786
+ label: descriptor?.prompt?.label ?? id,
6787
+ hint: descriptor?.prompt?.hint ?? "",
6788
+ category: descriptor?.category,
6789
+ requires: descriptor?.compat?.requiresModules ?? []
6790
+ }), moduleCatalog = () => MODULES.map((id) => entry(id, MODULE_DESCRIPTORS[id])), frontendCatalog = () => FRONTENDS.map((id) => entry(id, FRONTEND_DESCRIPTORS[id])), NO_CONTENT, contentCatalog = () => CONTENT_SOURCES.map((id) => id === "none" ? { id, ...NO_CONTENT, category: undefined, requires: [] } : entry(id, CONTENT_DESCRIPTORS[id]));
6791
+ var init_catalog = __esm(() => {
6792
+ init_src();
6793
+ init_src3();
6794
+ NO_CONTENT = { label: "None", hint: "No CMS and no MDX — just the frontend" };
6795
+ });
6796
+
6734
6797
  // ../../packages/registry/src/index.ts
6735
6798
  function buildRegistry(config2) {
6736
6799
  const content = CONTENT_DESCRIPTORS[config2.content];
@@ -6769,6 +6832,7 @@ var init_src3 = __esm(() => {
6769
6832
  init_seo();
6770
6833
  init_ui();
6771
6834
  init_vitest();
6835
+ init_catalog();
6772
6836
  baselineDescriptors = [typescriptConfig, env, logger, biome];
6773
6837
  FRONTEND_DESCRIPTORS = {
6774
6838
  next: nextFrontend,
@@ -6795,40 +6859,82 @@ var init_src3 = __esm(() => {
6795
6859
  });
6796
6860
 
6797
6861
  // src/create.ts
6798
- import { spawnSync } from "node:child_process";
6862
+ import { spawn, spawnSync } from "node:child_process";
6799
6863
  import { mkdir as mkdir2 } from "node:fs/promises";
6800
6864
  import { resolve } from "node:path";
6801
- function step(label, cmd, args, cwd, stdio = "ignore") {
6865
+ function step(label, cmd, args, cwd, stdio = "ignore", onWarn = warn) {
6802
6866
  const res = spawnSync(cmd, args, { cwd, stdio });
6803
6867
  if (res.error) {
6804
- console.error(`! ${label} skipped: ${res.error.message}`);
6868
+ onWarn(`${label} skipped: ${res.error.message}`);
6805
6869
  return false;
6806
6870
  }
6807
6871
  if (res.status !== 0) {
6808
- console.error(`! ${label} failed (exit ${res.status ?? `signal ${res.signal}`})`);
6872
+ onWarn(`${label} failed (exit ${res.status ?? `signal ${res.signal}`})`);
6809
6873
  return false;
6810
6874
  }
6811
6875
  return true;
6812
6876
  }
6813
- async function createProject(config2, opts = {}) {
6814
- const res = generate(config2, buildRegistry(config2));
6815
- if (!res.ok)
6816
- return res;
6877
+ function streamStep(label, cmd, args, cwd, onOutput, onWarn) {
6878
+ return new Promise((done) => {
6879
+ const child = spawn(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
6880
+ const emit = (chunk) => {
6881
+ for (const line of chunk.toString().split(/\r?\n|\r/)) {
6882
+ if (line.trim())
6883
+ onOutput(line);
6884
+ }
6885
+ };
6886
+ child.stdout?.on("data", emit);
6887
+ child.stderr?.on("data", emit);
6888
+ child.on("error", (error) => {
6889
+ onWarn(`${label} skipped: ${error.message}`);
6890
+ done(false);
6891
+ });
6892
+ child.on("close", (code, signal) => {
6893
+ if (code === 0)
6894
+ return done(true);
6895
+ onWarn(`${label} failed (exit ${code ?? `signal ${signal}`})`);
6896
+ done(false);
6897
+ });
6898
+ });
6899
+ }
6900
+ function planProject(config2) {
6901
+ return generate(config2, buildRegistry(config2));
6902
+ }
6903
+ async function writeProject(tree, config2, opts = {}) {
6904
+ const { onWarn = warn, onOutput } = opts;
6817
6905
  const dir = resolve(opts.targetDir ?? config2.name);
6818
6906
  await mkdir2(dir, { recursive: true });
6819
- await writeTree(res.value, dir);
6907
+ await writeTree(tree, dir);
6820
6908
  if (opts.git) {
6821
- const inited = step("git init", "git", ["init", "-q"], dir);
6822
- const added = inited && step("git add", "git", ["add", "-A"], dir);
6909
+ const inited = step("git init", "git", ["init", "-q"], dir, "ignore", onWarn);
6910
+ const added = inited && step("git add", "git", ["add", "-A"], dir, "ignore", onWarn);
6823
6911
  if (added) {
6824
- step("git commit", "git", ["commit", "-qm", "chore: scaffold with create-turbo-wizard"], dir);
6912
+ const args = ["commit", "-qm", "chore: scaffold with create-turbo-wizard"];
6913
+ step("git commit", "git", args, dir, "ignore", onWarn);
6825
6914
  }
6826
6915
  }
6916
+ let installError;
6827
6917
  if (opts.install) {
6828
- step(`${config2.packageManager} install`, config2.packageManager, ["install"], dir, "inherit");
6918
+ const label = `${config2.packageManager} install`;
6919
+ const capture = (message) => {
6920
+ installError = message;
6921
+ };
6922
+ const ok2 = onOutput ? await streamStep(label, config2.packageManager, ["install"], dir, onOutput, capture) : step(label, config2.packageManager, ["install"], dir, "inherit", capture);
6923
+ if (ok2)
6924
+ installError = null;
6829
6925
  }
6830
- return { ok: true, value: { dir, fileCount: res.value.size } };
6926
+ return { dir, fileCount: tree.size, installError };
6831
6927
  }
6928
+ async function createProject(config2, opts = {}) {
6929
+ const res = planProject(config2);
6930
+ if (!res.ok)
6931
+ return res;
6932
+ const value = await writeProject(res.value, config2, opts);
6933
+ if (value.installError)
6934
+ (opts.onWarn ?? warn)(value.installError);
6935
+ return { ok: true, value };
6936
+ }
6937
+ var warn = (message) => console.error(`! ${message}`);
6832
6938
  var init_create = __esm(() => {
6833
6939
  init_src2();
6834
6940
  init_src3();
@@ -6838,7 +6944,7 @@ var init_create = __esm(() => {
6838
6944
  var exports_mcp = {};
6839
6945
  __export(exports_mcp, {
6840
6946
  startMcpServer: () => startMcpServer,
6841
- planProject: () => planProject,
6947
+ planProject: () => planProject2,
6842
6948
  guidanceText: () => guidanceText,
6843
6949
  createProjectFromInput: () => createProjectFromInput,
6844
6950
  buildMcpServer: () => buildMcpServer
@@ -6846,7 +6952,7 @@ __export(exports_mcp, {
6846
6952
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6847
6953
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6848
6954
  import { z as z3 } from "zod";
6849
- function planProject(input) {
6955
+ function planProject2(input) {
6850
6956
  const config2 = toConfig(input);
6851
6957
  const res = generate(config2, buildRegistry(config2));
6852
6958
  if (!res.ok)
@@ -6864,7 +6970,7 @@ function buildMcpServer() {
6864
6970
  description: "Preview the files a configuration would generate (does NOT write to disk).",
6865
6971
  inputSchema: PlanInput
6866
6972
  }, (input) => {
6867
- const res = planProject(input);
6973
+ const res = planProject2(input);
6868
6974
  return res.ok ? text2(res.paths.join(`
6869
6975
  `)) : { ...text2(`Generation failed: ${res.error}`), isError: true };
6870
6976
  });
@@ -6921,59 +7027,259 @@ var init_mcp = __esm(() => {
6921
7027
  init_src();
6922
7028
  init_package();
6923
7029
  init_create();
7030
+ import { resolve as resolve2 } from "node:path";
7031
+ import { cancel as cancel3, confirm, isCancel, log as log3 } from "@clack/prompts";
6924
7032
  import { initTRPC } from "@trpc/server";
6925
7033
  import { createCli } from "trpc-cli";
6926
7034
  import { z as z2 } from "zod";
6927
7035
 
6928
7036
  // src/prompts.ts
7037
+ init_src3();
6929
7038
  init_src();
6930
- import { cancel, intro, isCancel, multiselect, outro, select, text } from "@clack/prompts";
6931
- var orCancel = (value) => {
6932
- if (isCancel(value)) {
6933
- cancel("Cancelled.");
6934
- process.exit(0);
7039
+ import { cancel, group, groupMultiselect, log, select, text } from "@clack/prompts";
7040
+
7041
+ // src/theme.ts
7042
+ import { WriteStream } from "node:tty";
7043
+ import { isCI, unicode } from "@clack/prompts";
7044
+ var resolveDepth = () => {
7045
+ const stream = process.stdout;
7046
+ if (typeof stream.getColorDepth === "function")
7047
+ return stream.getColorDepth();
7048
+ return process.env.FORCE_COLOR ? WriteStream.prototype.getColorDepth() : 1;
7049
+ };
7050
+ var depth = resolveDepth();
7051
+ var wrap = (open, close) => (value) => `\x1B[${open}m${value}\x1B[${close}m`;
7052
+ var plain = (value) => value;
7053
+ var scale = (rgb, x256, basic) => {
7054
+ if (depth >= 24)
7055
+ return wrap(`38;2;${rgb[0]};${rgb[1]};${rgb[2]}`, "39");
7056
+ if (depth >= 8)
7057
+ return wrap(`38;5;${x256}`, "39");
7058
+ if (depth >= 4)
7059
+ return wrap(String(basic), "39");
7060
+ return plain;
7061
+ };
7062
+ var effect = (open, close) => depth >= 4 ? wrap(open, close) : plain;
7063
+ var accent = scale([124, 140, 255], 105, 36);
7064
+ var accentDim = scale([74, 85, 168], 61, 34);
7065
+ var muted = scale([153, 161, 179], 109, 90);
7066
+ var success = scale([74, 222, 128], 78, 32);
7067
+ var warn2 = scale([251, 191, 36], 214, 33);
7068
+ var danger = scale([248, 113, 113], 203, 31);
7069
+ var bold = effect("1", "22");
7070
+ var dim = effect("2", "22");
7071
+ var badge = (value) => depth >= 24 ? `\x1B[48;2;124;140;255m\x1B[30m ${value} \x1B[39m\x1B[49m` : depth >= 8 ? `\x1B[48;5;105m\x1B[30m ${value} \x1B[39m\x1B[49m` : depth >= 4 ? `\x1B[46m\x1B[30m ${value} \x1B[39m\x1B[49m` : ` ${value} `;
7072
+ var WORDMARK = [
7073
+ "████████╗██╗ ██╗██╗███████╗",
7074
+ "╚══██╔══╝██║ ██║██║╚══███╔╝",
7075
+ " ██║ ██║ █╗ ██║██║ ███╔╝ ",
7076
+ " ██║ ██║███╗██║██║ ███╔╝ ",
7077
+ " ██║ ╚███╔███╔╝██║███████╗",
7078
+ " ╚═╝ ╚══╝╚══╝ ╚═╝╚══════╝"
7079
+ ];
7080
+ var WORDMARK_WIDTH = 30;
7081
+ var TAGLINE = "scaffold a Turborepo monorepo";
7082
+ var shade = (row) => (row.match(/█+|[^█]+/g) ?? []).map((run) => run.startsWith("█") ? accent(run) : accentDim(run)).join("");
7083
+ function banner(version) {
7084
+ if (!process.stdout.isTTY || isCI() || !unicode)
7085
+ return null;
7086
+ const label = `v${version}`;
7087
+ const gap = Math.max(2, WORDMARK_WIDTH - 2 - TAGLINE.length - label.length);
7088
+ const width = Math.max(WORDMARK_WIDTH, 2 + TAGLINE.length + gap + label.length);
7089
+ if ((process.stdout.columns ?? 80) < width + 2)
7090
+ return null;
7091
+ const footer = ` ${muted(TAGLINE)}${" ".repeat(gap)}${dim(label)}`;
7092
+ return `
7093
+ ${WORDMARK.map(shade).join(`
7094
+ `)}
7095
+
7096
+ ${footer}
7097
+ `;
7098
+ }
7099
+
7100
+ // src/prompts.ts
7101
+ var toOption = (entry2) => ({
7102
+ value: entry2.id,
7103
+ label: entry2.label,
7104
+ ...entry2.hint ? { hint: entry2.hint } : {}
7105
+ });
7106
+ var PACKAGE_MANAGER_HINTS = {
7107
+ pnpm: "Strict node_modules, workspace: protocol",
7108
+ bun: "Fastest install, workspace: protocol",
7109
+ npm: "No workspace: protocol — deps are rewritten to *"
7110
+ };
7111
+ var DEPLOY_HINTS = {
7112
+ node: "@astrojs/node — runs anywhere, and what `astro preview` serves",
7113
+ vercel: "@astrojs/vercel — required once a server route deploys to Vercel"
7114
+ };
7115
+ var CATEGORY_LABELS = {
7116
+ ui: "UI",
7117
+ integration: "Integrations",
7118
+ devtools: "Developer tooling",
7119
+ "cross-cutting": "Cross-cutting"
7120
+ };
7121
+ var moduleGroups = () => {
7122
+ const catalog = moduleCatalog();
7123
+ const groups = {};
7124
+ for (const category of MODULE_CATEGORIES) {
7125
+ const entries = catalog.filter((entry2) => entry2.category === category);
7126
+ if (entries.length)
7127
+ groups[CATEGORY_LABELS[category]] = entries.map(toOption);
6935
7128
  }
6936
- return value;
7129
+ return groups;
7130
+ };
7131
+ var bail = () => {
7132
+ cancel("Cancelled.");
7133
+ process.exit(0);
6937
7134
  };
6938
7135
  async function gatherConfig(flags) {
6939
- intro("create-turbo-wizard");
6940
- const name = flags.name ?? orCancel(await text({
6941
- message: "Project name",
6942
- placeholder: "my-app",
6943
- defaultValue: "my-app",
6944
- validate: (v) => /^[a-z0-9][a-z0-9._-]*$/.test(v || "my-app") ? undefined : "lowercase, digits, -._ only"
6945
- }));
6946
- const frontend = flags.frontend ?? orCancel(await select({
6947
- message: "Frontend framework",
6948
- options: [
6949
- { value: "next", label: "Next.js" },
6950
- { value: "astro", label: "Astro" }
6951
- ]
6952
- }));
6953
- const content = flags.content ?? orCancel(await select({
6954
- message: "Content source",
6955
- options: CONTENT_SOURCES.map((value) => ({ value, label: value }))
6956
- }));
6957
- const packageManager = flags.packageManager ?? orCancel(await select({
6958
- message: "Package manager",
6959
- options: PACKAGE_MANAGERS.map((value) => ({ value, label: value }))
6960
- }));
6961
- const modules = flags.modules ?? orCancel(await multiselect({
6962
- message: "Optional modules (space to toggle)",
6963
- options: MODULES.map((value) => ({ value, label: value })),
6964
- required: false
6965
- }));
6966
- outro(`Scaffolding ${name} (${frontend} · ${content} · ${packageManager})`);
7136
+ const answers = await group({
7137
+ name: () => flags.name ? Promise.resolve(flags.name) : text({
7138
+ message: "Project name",
7139
+ placeholder: "my-app",
7140
+ defaultValue: "my-app",
7141
+ validate: (value) => {
7142
+ const parsed = ProjectNameSchema.safeParse(value || "my-app");
7143
+ return parsed.success ? undefined : parsed.error.issues[0]?.message;
7144
+ }
7145
+ }),
7146
+ frontend: () => flags.frontend ? Promise.resolve(flags.frontend) : select({
7147
+ message: "Frontend framework",
7148
+ options: frontendCatalog().map(toOption),
7149
+ initialValue: "next"
7150
+ }),
7151
+ content: () => flags.content ? Promise.resolve(flags.content) : select({
7152
+ message: "Content source",
7153
+ options: contentCatalog().map(toOption)
7154
+ }),
7155
+ packageManager: () => flags.packageManager ? Promise.resolve(flags.packageManager) : select({
7156
+ message: "Package manager",
7157
+ options: PACKAGE_MANAGERS.map((value) => ({
7158
+ value,
7159
+ label: value,
7160
+ hint: PACKAGE_MANAGER_HINTS[value]
7161
+ }))
7162
+ }),
7163
+ deploy: ({ results }) => flags.deploy || results.frontend !== "astro" ? Promise.resolve(flags.deploy ?? "node") : select({
7164
+ message: "Deploy target",
7165
+ options: DEPLOY_TARGETS.map((value) => ({
7166
+ value,
7167
+ label: value,
7168
+ hint: DEPLOY_HINTS[value]
7169
+ }))
7170
+ }),
7171
+ modules: () => flags.modules ? Promise.resolve(flags.modules) : groupMultiselect({
7172
+ message: `Modules ${muted("(space to toggle, enter to confirm)")}`,
7173
+ options: moduleGroups(),
7174
+ required: false
7175
+ })
7176
+ }, { onCancel: bail });
7177
+ const picked = (answers.modules ?? []).filter((id) => MODULES.includes(id));
7178
+ const resolved = flags.modules ? { modules: picked, added: [] } : withPrerequisites(picked);
7179
+ for (const [module, requiredBy] of resolved.added) {
7180
+ log.info(`Added ${accent(module)} — required by ${accent(requiredBy)}`);
7181
+ }
6967
7182
  return WizardConfigSchema.parse({
6968
- name,
6969
- frontend,
6970
- content,
6971
- packageManager,
6972
- modules,
7183
+ name: answers.name,
7184
+ frontend: answers.frontend,
7185
+ content: answers.content,
7186
+ packageManager: answers.packageManager,
7187
+ deploy: answers.deploy,
7188
+ modules: resolved.modules,
6973
7189
  experimental: { cacheComponents: flags.cacheComponents, reactCompiler: flags.reactCompiler }
6974
7190
  });
6975
7191
  }
6976
7192
 
7193
+ // src/ui.ts
7194
+ init_src3();
7195
+ init_src();
7196
+ import { relative, sep } from "node:path";
7197
+ import { cancel as cancel2, intro, log as log2, note, outro, taskLog } from "@clack/prompts";
7198
+ var RAW = { format: (line) => line };
7199
+ function printBanner(version) {
7200
+ const art = banner(version);
7201
+ if (art)
7202
+ process.stdout.write(`${art}
7203
+ `);
7204
+ }
7205
+ function start() {
7206
+ intro(badge("create-turbo-wizard"));
7207
+ }
7208
+ function displayPath(dir) {
7209
+ const rel = relative(process.cwd(), dir);
7210
+ return rel && !rel.startsWith("..") && !rel.startsWith(sep) ? `./${rel}` : dir;
7211
+ }
7212
+ function shape(tree) {
7213
+ const nested = new Map;
7214
+ let rootFiles = 0;
7215
+ for (const path of tree.keys()) {
7216
+ const [top, second] = path.split("/");
7217
+ if (!top)
7218
+ continue;
7219
+ if (second && (top === "apps" || top === "packages")) {
7220
+ const names = nested.get(top) ?? new Set;
7221
+ names.add(second);
7222
+ nested.set(top, names);
7223
+ } else if (!second) {
7224
+ rootFiles += 1;
7225
+ }
7226
+ }
7227
+ const parts = [...nested].sort(([a], [b]) => a.localeCompare(b)).map(([top, names]) => names.size > 3 ? `${names.size} ${top}` : `${top}/{${[...names].sort().join(", ")}}`);
7228
+ if (rootFiles)
7229
+ parts.push(`${rootFiles} root files`);
7230
+ return parts.join(" ");
7231
+ }
7232
+ var labelOf = (entries, id) => entries.find((entry2) => entry2.id === id)?.label ?? id;
7233
+ var row = (label, value) => `${muted(label.padEnd(10))}${value}`;
7234
+ function summary(config2, tree, dir) {
7235
+ const stack = [
7236
+ labelOf(frontendCatalog(), config2.frontend),
7237
+ labelOf(contentCatalog(), config2.content),
7238
+ config2.packageManager
7239
+ ].join(" · ");
7240
+ note([
7241
+ row("Directory", accent(displayPath(dir))),
7242
+ row("Stack", stack),
7243
+ row("Modules", config2.modules.length ? config2.modules.join(", ") : muted("none")),
7244
+ row("Writing", `${tree.size} files ${muted(shape(tree))}`),
7245
+ "",
7246
+ muted("Reproduce this with"),
7247
+ dim(buildCommand(config2))
7248
+ ].join(`
7249
+ `), "Summary", RAW);
7250
+ }
7251
+ function installLog(config2) {
7252
+ const task = taskLog({
7253
+ title: `Installing dependencies with ${config2.packageManager}`,
7254
+ limit: 8
7255
+ });
7256
+ return {
7257
+ onOutput: (line) => task.message(line, { raw: true }),
7258
+ finish: (error) => error ? task.error(error, { showLog: true }) : task.success(`${config2.packageManager} install`)
7259
+ };
7260
+ }
7261
+ var runScript = (config2, script) => config2.packageManager === "npm" ? `npm run ${script}` : `${config2.packageManager} ${script}`;
7262
+ function nextSteps(config2, tree, dir, installed) {
7263
+ const commands = [`cd ${displayPath(dir)}`];
7264
+ if (!installed)
7265
+ commands.push(`${config2.packageManager} install`);
7266
+ commands.push(runScript(config2, "dev"));
7267
+ if (config2.content === "sanity")
7268
+ commands.push(runScript(config2, "dev:studio"));
7269
+ const extras = [];
7270
+ if (tree.has(".env.example"))
7271
+ extras.push(row("Env", "copy .env.example to .env and fill it in"));
7272
+ extras.push(row("Docs", "https://turbo-wizard.vercel.app/docs"));
7273
+ note([...commands.map(accent), "", ...extras].join(`
7274
+ `), "Next steps", RAW);
7275
+ outro(success(`Created ${config2.name} — ${tree.size} files`));
7276
+ }
7277
+ function fail(message) {
7278
+ log2.error(message);
7279
+ cancel2("Nothing was written.");
7280
+ process.exit(1);
7281
+ }
7282
+
6977
7283
  // src/router.ts
6978
7284
  var t = initTRPC.create();
6979
7285
  var CreateInput = z2.object({
@@ -7004,6 +7310,8 @@ var router = t.router({
7004
7310
  cacheComponents: input.cacheComponents ?? base.experimental?.cacheComponents,
7005
7311
  reactCompiler: input.reactCompiler ?? base.experimental?.reactCompiler
7006
7312
  };
7313
+ printBanner(package_default.version);
7314
+ start();
7007
7315
  const interactive = !input.yes && !input.preset && Boolean(process.stdout.isTTY) && (!input.frontend || !input.name);
7008
7316
  const config2 = interactive ? await gatherConfig(flags) : WizardConfigSchema.parse({
7009
7317
  name: flags.name ?? "my-app",
@@ -7017,17 +7325,28 @@ var router = t.router({
7017
7325
  reactCompiler: flags.reactCompiler
7018
7326
  }
7019
7327
  });
7020
- const res = await createProject(config2, {
7021
- targetDir: input.dir,
7328
+ const plan = planProject(config2);
7329
+ if (!plan.ok)
7330
+ fail(plan.error.message);
7331
+ const dir = resolve2(input.dir ?? config2.name);
7332
+ summary(config2, plan.value, dir);
7333
+ if (interactive) {
7334
+ const proceed = await confirm({ message: "Proceed with scaffolding?" });
7335
+ if (isCancel(proceed) || !proceed) {
7336
+ cancel3("Nothing was written.");
7337
+ process.exit(0);
7338
+ }
7339
+ }
7340
+ const installer = input.install ? installLog(config2) : undefined;
7341
+ const result = await writeProject(plan.value, config2, {
7342
+ targetDir: dir,
7022
7343
  install: input.install,
7023
- git: input.git
7344
+ git: input.git,
7345
+ onWarn: (message) => log3.warn(message),
7346
+ onOutput: installer?.onOutput
7024
7347
  });
7025
- if (!res.ok) {
7026
- console.error(`✗ ${res.error.message}`);
7027
- process.exit(1);
7028
- }
7029
- console.error(`✓ created ${config2.name} → ${res.value.dir} (${res.value.fileCount} files)`);
7030
- console.error(` next: cd ${res.value.dir} && ${config2.packageManager} install`);
7348
+ installer?.finish(result.installError);
7349
+ nextSteps(config2, plan.value, result.dir, result.installError === null);
7031
7350
  })
7032
7351
  });
7033
7352
  var cli = createCli({ router, name: "create-turbo-wizard", version: package_default.version });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-turbo-wizard",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Scaffold a Turborepo monorepo: Astro or Next.js, Sanity or Markdown, opt-in modules.",
5
5
  "keywords": [
6
6
  "turborepo",