create-basalt 1.1.0 → 1.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.
package/README.md CHANGED
@@ -93,13 +93,16 @@ Behavior notes (faithful to the code):
93
93
 
94
94
  - **Package manager detection**: by default, detects who invoked the command via the `npm_config_user_agent` variable (set by npm/pnpm/yarn/bun); unknown managers fall back to `npm`. `--pm=` overrides this.
95
95
  - **`--ui` forces pnpm**: the `web/` frontend is a member of a pnpm *workspace* (declared in the generated `pnpm-workspace.yaml`). npm, yarn, and bun can't install or run that structure, so if you request `--ui` with another manager, you'll see `Note: --ui projects are pnpm workspaces — using pnpm instead of <manager>.` and pnpm is used.
96
- - **Interactive mode**: only happens when you don't pass a name, you're in a terminal (TTY), and you didn't use `--yes`. Ctrl+C during a question ends cleanly with "Cancelled." (exit code 130).
96
+ - **Interactive wizard**: when you don't pass a name, you're in a terminal (TTY), and you didn't use `--yes`, you get the guided wizard — an intro, a **starting-point preset** (SaaS starter / API only / Full stack / Minimal / Custom), an arrow-key **feature multiselect** on the custom path, package-manager select, and a **summary + confirm** step before anything is written. Ctrl+C (or declining the final confirm) ends cleanly with "Cancelled." (exit code 130).
97
97
  - **Occupied folder**: if the destination folder exists and isn't empty, the command refuses with `Target directory "<dir>" already exists and is not empty.` and exits with code 1.
98
98
  - At the end, it prints the created files and the "Next steps" appropriate to your choices.
99
99
 
100
100
  ### Invocation examples
101
101
 
102
102
  ```bash
103
+ # Interactive wizard (presets, feature multiselect, summary):
104
+ pnpm create basalt
105
+
103
106
  # Full project, no questions, with everything:
104
107
  pnpm create basalt my-app --billing --ui --cli --install --git
105
108
 
@@ -80,7 +80,7 @@ function tsconfigJson() {
80
80
  `;
81
81
  }
82
82
  function envTs(options) {
83
- return `import { defineEnv } from '@basaltkit/env'
83
+ return `import { defineEnv${options.auth ? ", secret" : ""} } from '@basaltkit/env'
84
84
  import { z } from 'zod'
85
85
 
86
86
  export const env = defineEnv({
@@ -88,7 +88,9 @@ export const env = defineEnv({
88
88
  HOST: z.string().default('0.0.0.0'),
89
89
  LOG_LEVEL: z.string().default('info'),
90
90
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),${options.auth ? `
91
- APP_SECRET: z.string().min(16).default('change-me-in-production--'),` : ""}
91
+ // Signs JWTs and sessions. secret() is fail-closed: required in production
92
+ // (no fallback), rejected if it looks like a placeholder. Dev uses devDefault.
93
+ APP_SECRET: secret({ minLength: 32, devDefault: 'dev-only-insecure-secret-please-change-me' }),` : ""}
92
94
  })
93
95
  `;
94
96
  }
@@ -97,20 +99,26 @@ function envExample(options) {
97
99
  HOST=0.0.0.0
98
100
  LOG_LEVEL=info
99
101
  NODE_ENV=development
100
- ${options.auth ? "APP_SECRET=change-me-in-production--\n" : ""}`;
102
+ ${options.auth ? "# Required in production (the app refuses to boot without it).\n# Generate a strong one: openssl rand -base64 48\n# APP_SECRET=\n" : ""}`;
101
103
  }
102
104
  function appTs(options) {
103
105
  const imports = [
104
106
  `import { createApp } from '@basaltkit/core'`,
105
107
  `import { configPlugin } from '@basaltkit/config'`,
106
108
  `import { eventsPlugin } from '@basaltkit/events'`,
107
- `import { fastifyPlugin } from '@basaltkit/fastify'`,
109
+ `import { fastifyPlugin, securityPlugin } from '@basaltkit/fastify'`,
108
110
  `import { loggerPlugin } from '@basaltkit/logger'`
109
111
  ];
110
112
  const plugins = [
111
113
  `configPlugin({ app: { name: '${options.name}' } })`,
112
114
  `loggerPlugin({ level: options.logLevel ?? 'info', ...(options.pretty ? { pretty: true } : {}) })`,
113
- `eventsPlugin()`
115
+ `eventsPlugin()`,
116
+ `securityPlugin({
117
+ // Secure response headers (HSTS, X-Frame-Options, nosniff, \u2026) are on by
118
+ // default. Turn on rate limiting and a CORS allow-list for production:
119
+ // rateLimit: { limit: 120, windowMs: 60_000 },
120
+ // cors: { origin: ['https://app.example.com'], credentials: true },
121
+ })`
114
122
  ];
115
123
  let routesExpression = "appRoutes";
116
124
  if (options.tenancy) {
@@ -1037,6 +1045,241 @@ function MfaCard({ status, api, onChange }: { status: { enabled: boolean; pendin
1037
1045
  `;
1038
1046
  }
1039
1047
 
1048
+ // src/prompt.ts
1049
+ import { emitKeypressEvents } from "readline";
1050
+ import { createInterface } from "readline/promises";
1051
+ import { stdin, stdout } from "process";
1052
+ var WizardCancelledError = class extends Error {
1053
+ constructor() {
1054
+ super("Cancelled.");
1055
+ this.name = "WizardCancelledError";
1056
+ }
1057
+ };
1058
+ var c = {
1059
+ dim: (s) => `\x1B[2m${s}\x1B[0m`,
1060
+ cyan: (s) => `\x1B[36m${s}\x1B[0m`,
1061
+ green: (s) => `\x1B[32m${s}\x1B[0m`,
1062
+ bold: (s) => `\x1B[1m${s}\x1B[0m`
1063
+ };
1064
+ function ttyPrompter() {
1065
+ return {
1066
+ intro: (message) => stdout.write(`
1067
+ ${c.bold(c.cyan("\u25C6 " + message))}
1068
+
1069
+ `),
1070
+ note: (message) => stdout.write(`${message}
1071
+ `),
1072
+ outro: (message) => stdout.write(`
1073
+ ${c.green("\u2713 " + message)}
1074
+ `),
1075
+ async text({ message, placeholder, initial, validate }) {
1076
+ const rl = createInterface({ input: stdin, output: stdout });
1077
+ rl.on("SIGINT", () => {
1078
+ rl.close();
1079
+ throw new WizardCancelledError();
1080
+ });
1081
+ try {
1082
+ for (; ; ) {
1083
+ const hint = placeholder ? c.dim(` (${placeholder})`) : "";
1084
+ const answer = (await rl.question(`${c.cyan("?")} ${message}${hint}: `)).trim() || initial || "";
1085
+ const error = validate?.(answer);
1086
+ if (!error) return answer;
1087
+ stdout.write(` ${c.dim(error)}
1088
+ `);
1089
+ }
1090
+ } catch (error) {
1091
+ if (error.code === "ABORT_ERR") throw new WizardCancelledError();
1092
+ throw error;
1093
+ } finally {
1094
+ rl.close();
1095
+ }
1096
+ },
1097
+ select({ message, choices, initial = 0 }) {
1098
+ return keyboardSelect(message, choices, initial, false).then((v) => v[0]);
1099
+ },
1100
+ multiselect({ message, choices, initial = [] }) {
1101
+ const preselected = choices.map((choice) => initial.includes(choice.value));
1102
+ return keyboardSelect(message, choices, 0, true, preselected);
1103
+ },
1104
+ async confirm({ message, initial = false }) {
1105
+ const rl = createInterface({ input: stdin, output: stdout });
1106
+ try {
1107
+ const answer = (await rl.question(`${c.cyan("?")} ${message} ${c.dim(initial ? "(Y/n)" : "(y/N)")} `)).trim().toLowerCase();
1108
+ return answer ? answer.startsWith("y") : initial;
1109
+ } finally {
1110
+ rl.close();
1111
+ }
1112
+ }
1113
+ };
1114
+ }
1115
+ function keyboardSelect(message, choices, initialIndex, multi, selected = choices.map(() => false)) {
1116
+ return new Promise((resolve2, reject) => {
1117
+ let index = initialIndex;
1118
+ let rendered = 0;
1119
+ emitKeypressEvents(stdin);
1120
+ const wasRaw = stdin.isTTY ? stdin.isRaw : false;
1121
+ if (stdin.isTTY) stdin.setRawMode(true);
1122
+ const render = () => {
1123
+ if (rendered > 0) stdout.write(`\x1B[${rendered}A\x1B[0J`);
1124
+ const lines = [`${c.cyan("?")} ${message}${multi ? c.dim(" (space to toggle, enter to confirm)") : ""}`];
1125
+ choices.forEach((choice, i) => {
1126
+ const pointer = i === index ? c.cyan("\u276F") : " ";
1127
+ const box = multi ? (selected[i] ? c.green("\u25C9") : "\u25EF") + " " : "";
1128
+ const label = i === index ? c.cyan(choice.label) : choice.label;
1129
+ const hint = choice.hint ? c.dim(` \u2014 ${choice.hint}`) : "";
1130
+ lines.push(`${pointer} ${box}${label}${hint}`);
1131
+ });
1132
+ stdout.write(lines.join("\n") + "\n");
1133
+ rendered = lines.length;
1134
+ };
1135
+ const cleanup = () => {
1136
+ stdin.off("keypress", onKey);
1137
+ if (stdin.isTTY) stdin.setRawMode(wasRaw);
1138
+ stdin.pause();
1139
+ };
1140
+ const onKey = (_str, key) => {
1141
+ if (key.ctrl && key.name === "c") {
1142
+ cleanup();
1143
+ reject(new WizardCancelledError());
1144
+ } else if (key.name === "up" || key.name === "k") {
1145
+ index = (index - 1 + choices.length) % choices.length;
1146
+ render();
1147
+ } else if (key.name === "down" || key.name === "j") {
1148
+ index = (index + 1) % choices.length;
1149
+ render();
1150
+ } else if (multi && key.name === "space") {
1151
+ selected[index] = !selected[index];
1152
+ render();
1153
+ } else if (key.name === "return") {
1154
+ cleanup();
1155
+ resolve2(
1156
+ multi ? choices.filter((_, i) => selected[i]).map((choice) => choice.value) : [choices[index].value]
1157
+ );
1158
+ }
1159
+ };
1160
+ stdin.resume();
1161
+ stdin.on("keypress", onKey);
1162
+ render();
1163
+ });
1164
+ }
1165
+ function scriptedPrompter(script) {
1166
+ const text = [...script.text ?? []];
1167
+ const select = [...script.select ?? []];
1168
+ const multiselect = [...script.multiselect ?? []];
1169
+ const confirm = [...script.confirm ?? []];
1170
+ const log = [];
1171
+ const next = (queue, kind) => {
1172
+ if (queue.length === 0) throw new Error(`scriptedPrompter: no more ${kind} answers`);
1173
+ return queue.shift();
1174
+ };
1175
+ return {
1176
+ log,
1177
+ intro: (m) => log.push(m),
1178
+ note: (m) => log.push(m),
1179
+ outro: (m) => log.push(m),
1180
+ async text() {
1181
+ return next(text, "text");
1182
+ },
1183
+ async select() {
1184
+ return next(select, "select");
1185
+ },
1186
+ async multiselect() {
1187
+ return next(multiselect, "multiselect");
1188
+ },
1189
+ async confirm() {
1190
+ return next(confirm, "confirm");
1191
+ }
1192
+ };
1193
+ }
1194
+
1195
+ // src/wizard.ts
1196
+ var FEATURES = [
1197
+ { value: "tenancy", label: "Multi-tenancy", hint: "per-tenant data isolation" },
1198
+ { value: "auth", label: "Authentication", hint: "JWT, sessions, MFA, OAuth" },
1199
+ { value: "billing", label: "Subscriptions / billing", hint: "Stripe, Paddle, Lemon Squeezy" },
1200
+ { value: "ui", label: "Web UI", hint: "React + shadcn + SDK (pnpm workspace)" },
1201
+ { value: "cli", label: "basalt CLI", hint: "code generators + commands" }
1202
+ ];
1203
+ var PRESETS = [
1204
+ { value: "saas", label: "SaaS starter", hint: "tenancy + auth + billing + CLI", features: ["tenancy", "auth", "billing", "cli"] },
1205
+ { value: "api", label: "API only", hint: "auth, no tenancy or UI", features: ["auth"] },
1206
+ { value: "full", label: "Full stack", hint: "everything + web UI", features: ["tenancy", "auth", "billing", "ui", "cli"] },
1207
+ { value: "minimal", label: "Minimal", hint: "no batteries, add them later", features: [] },
1208
+ { value: "custom", label: "Custom", hint: "pick features yourself", features: null }
1209
+ ];
1210
+ var PACKAGE_MANAGERS = [
1211
+ { value: "pnpm", label: "pnpm", hint: "recommended" },
1212
+ { value: "npm", label: "npm" },
1213
+ { value: "yarn", label: "yarn" },
1214
+ { value: "bun", label: "bun" }
1215
+ ];
1216
+ function validateProjectName(name) {
1217
+ const trimmed = name.trim();
1218
+ if (!trimmed) return "Please enter a project name.";
1219
+ if (trimmed.length > 214) return "Name is too long (max 214 characters).";
1220
+ if (!/^(?:@[a-z0-9-~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(trimmed)) {
1221
+ return "Use a valid npm package name (lowercase, no spaces).";
1222
+ }
1223
+ return void 0;
1224
+ }
1225
+ async function runWizard(prompter, options = {}) {
1226
+ prompter.intro("create-basalt \u2014 scaffold a Basalt application");
1227
+ const name = options.name && !validateProjectName(options.name) ? options.name : await prompter.text({
1228
+ message: "Project name",
1229
+ placeholder: "my-saas",
1230
+ initial: "my-saas",
1231
+ validate: validateProjectName
1232
+ });
1233
+ const presetValue = await prompter.select({
1234
+ message: "Choose a starting point",
1235
+ choices: PRESETS.map((p) => ({ value: p.value, label: p.label, hint: p.hint })),
1236
+ initial: 0
1237
+ });
1238
+ const preset = PRESETS.find((p) => p.value === presetValue) ?? PRESETS[0];
1239
+ const featureSet = new Set(
1240
+ preset.features ?? await prompter.multiselect({
1241
+ message: "Select features",
1242
+ choices: FEATURES,
1243
+ initial: ["tenancy", "auth"]
1244
+ })
1245
+ );
1246
+ let pm = await prompter.select({
1247
+ message: "Package manager",
1248
+ choices: PACKAGE_MANAGERS,
1249
+ initial: Math.max(0, PACKAGE_MANAGERS.findIndex((m) => m.value === (options.defaultPm ?? "pnpm")))
1250
+ });
1251
+ if (featureSet.has("ui") && pm !== "pnpm") {
1252
+ prompter.note(` Note: the Web UI is a pnpm workspace \u2014 using pnpm instead of ${pm}.`);
1253
+ pm = "pnpm";
1254
+ }
1255
+ const install = await prompter.confirm({ message: "Install dependencies now?", initial: false });
1256
+ const git = await prompter.confirm({ message: "Initialize a git repository?", initial: false });
1257
+ const enabled = FEATURES.filter((f) => featureSet.has(f.value)).map((f) => f.label);
1258
+ prompter.note(
1259
+ `
1260
+ Summary
1261
+ name ${name}
1262
+ features ${enabled.length ? enabled.join(", ") : "(none)"}
1263
+ pm ${pm}
1264
+ install ${install ? "yes" : "no"}
1265
+ git ${git ? "yes" : "no"}
1266
+ `
1267
+ );
1268
+ const ok = await prompter.confirm({ message: "Create project?", initial: true });
1269
+ if (!ok) throw new WizardCancelledError();
1270
+ return {
1271
+ name,
1272
+ tenancy: featureSet.has("tenancy"),
1273
+ auth: featureSet.has("auth"),
1274
+ billing: featureSet.has("billing"),
1275
+ ui: featureSet.has("ui"),
1276
+ cli: featureSet.has("cli"),
1277
+ pm,
1278
+ install,
1279
+ git
1280
+ };
1281
+ }
1282
+
1040
1283
  // src/index.ts
1041
1284
  var TargetNotEmptyError = class extends Error {
1042
1285
  constructor(dir) {
@@ -1085,6 +1328,13 @@ async function createProject(input) {
1085
1328
  }
1086
1329
 
1087
1330
  export {
1331
+ WizardCancelledError,
1332
+ ttyPrompter,
1333
+ scriptedPrompter,
1334
+ FEATURES,
1335
+ PRESETS,
1336
+ validateProjectName,
1337
+ runWizard,
1088
1338
  TargetNotEmptyError,
1089
1339
  detectPackageManager,
1090
1340
  createProject
package/dist/cli.js CHANGED
@@ -1,14 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TargetNotEmptyError,
4
+ WizardCancelledError,
4
5
  createProject,
5
- detectPackageManager
6
- } from "./chunk-XV5NMTXT.js";
6
+ detectPackageManager,
7
+ runWizard,
8
+ ttyPrompter
9
+ } from "./chunk-YJ7RXFTU.js";
7
10
 
8
11
  // src/cli.ts
9
12
  import { spawn } from "child_process";
10
13
  import { stdin, stdout } from "process";
11
- import { createInterface } from "readline/promises";
12
14
  var USAGE = `Usage: npm create basalt <name> [options]
13
15
 
14
16
  Options:
@@ -70,36 +72,26 @@ function cancel() {
70
72
  stdout.write("\nCancelled.\n");
71
73
  process.exit(130);
72
74
  }
73
- async function promptMissing(flags2) {
74
- if (flags2.yes || flags2.name !== void 0 || !stdin.isTTY) return flags2;
75
- const rl = createInterface({ input: stdin, output: stdout });
76
- rl.on("SIGINT", cancel);
75
+ var flags = parseArgs(process.argv.slice(2));
76
+ if (!flags.yes && flags.name === void 0 && stdin.isTTY) {
77
77
  try {
78
- const ask = async (question, fallback) => {
79
- const answer = (await rl.question(`${question} `)).trim();
80
- return answer || fallback;
81
- };
82
- const confirm = async (question, fallback) => {
83
- const answer = (await rl.question(`${question} (${fallback ? "Y/n" : "y/N"}) `)).trim().toLowerCase();
84
- return answer ? answer.startsWith("y") : fallback;
85
- };
86
- flags2.name = await ask("Project name:", "my-saas");
87
- flags2.tenancy = await confirm("Multi-tenancy?", true);
88
- flags2.auth = await confirm("Authentication?", true);
89
- flags2.billing = await confirm("Subscriptions / billing?", false);
90
- flags2.ui = await confirm("Web UI (React + shadcn)?", false);
91
- flags2.cli = await confirm("'basalt' CLI (code generators)?", false);
92
- flags2.install = await confirm("Install dependencies now?", false);
93
- flags2.git = await confirm("Initialize a git repository?", false);
78
+ const result = await runWizard(ttyPrompter(), {
79
+ defaultPm: flags.pm ?? detectPackageManager()
80
+ });
81
+ flags.name = result.name;
82
+ flags.tenancy = result.tenancy;
83
+ flags.auth = result.auth;
84
+ flags.billing = result.billing;
85
+ flags.ui = result.ui;
86
+ flags.cli = result.cli;
87
+ flags.install = result.install;
88
+ flags.git = result.git;
89
+ flags.pm = result.pm;
94
90
  } catch (error) {
95
- if (error.code === "ABORT_ERR") cancel();
91
+ if (error instanceof WizardCancelledError) cancel();
96
92
  throw error;
97
- } finally {
98
- rl.close();
99
93
  }
100
- return flags2;
101
94
  }
102
- var flags = await promptMissing(parseArgs(process.argv.slice(2)));
103
95
  if (!flags.name) {
104
96
  stdout.write(USAGE);
105
97
  process.exit(1);
package/dist/index.d.ts CHANGED
@@ -9,6 +9,101 @@ interface ProjectOptions {
9
9
  cli: boolean;
10
10
  }
11
11
 
12
+ /** A selectable option. `value` is returned; `label`/`hint` are shown. */
13
+ interface Choice<T> {
14
+ value: T;
15
+ label: string;
16
+ hint?: string;
17
+ }
18
+ /**
19
+ * The prompt surface the wizard talks to. Abstracted so the flow (`runWizard`)
20
+ * is testable with a scripted implementation — the raw-mode TTY version is the
21
+ * thin, untested I/O shell.
22
+ */
23
+ interface Prompter {
24
+ intro(message: string): void;
25
+ note(message: string): void;
26
+ outro(message: string): void;
27
+ text(opts: {
28
+ message: string;
29
+ placeholder?: string;
30
+ initial?: string;
31
+ validate?: (v: string) => string | undefined;
32
+ }): Promise<string>;
33
+ select<T>(opts: {
34
+ message: string;
35
+ choices: Choice<T>[];
36
+ initial?: number;
37
+ }): Promise<T>;
38
+ multiselect<T>(opts: {
39
+ message: string;
40
+ choices: Choice<T>[];
41
+ initial?: T[];
42
+ }): Promise<T[]>;
43
+ confirm(opts: {
44
+ message: string;
45
+ initial?: boolean;
46
+ }): Promise<boolean>;
47
+ }
48
+ /** Thrown when the user aborts (Ctrl+C, or declines the final confirmation). */
49
+ declare class WizardCancelledError extends Error {
50
+ constructor();
51
+ }
52
+ /**
53
+ * A rich, dependency-free terminal prompter: arrow-key `select`/`multiselect`
54
+ * (raw mode), validated `text`, and `confirm`. Only usable in a TTY.
55
+ */
56
+ declare function ttyPrompter(): Prompter;
57
+ /**
58
+ * A scripted prompter for tests: each call dequeues the next answer of its kind.
59
+ * `log` captures intro/note/outro lines so a test can assert the summary.
60
+ */
61
+ declare function scriptedPrompter(script: {
62
+ text?: string[];
63
+ select?: unknown[];
64
+ multiselect?: unknown[][];
65
+ confirm?: boolean[];
66
+ }): Prompter & {
67
+ log: string[];
68
+ };
69
+
70
+ type FeatureKey = 'tenancy' | 'auth' | 'billing' | 'ui' | 'cli';
71
+ /** The feature toggles offered in the "custom" preset (and shown in the summary). */
72
+ declare const FEATURES: Choice<FeatureKey>[];
73
+ interface Preset {
74
+ value: string;
75
+ label: string;
76
+ hint: string;
77
+ /** null → let the user pick features (custom). */
78
+ features: FeatureKey[] | null;
79
+ }
80
+ declare const PRESETS: Preset[];
81
+ /** Validates a project name as an npm-installable package name. */
82
+ declare function validateProjectName(name: string): string | undefined;
83
+ interface WizardResult {
84
+ name: string;
85
+ tenancy: boolean;
86
+ auth: boolean;
87
+ billing: boolean;
88
+ ui: boolean;
89
+ cli: boolean;
90
+ pm: PackageManager;
91
+ install: boolean;
92
+ git: boolean;
93
+ }
94
+ interface WizardOptions {
95
+ /** Pre-filled name (from argv) — skips the name prompt when valid. */
96
+ name?: string;
97
+ /** Detected package manager, used as the default selection. */
98
+ defaultPm?: PackageManager;
99
+ }
100
+ /**
101
+ * The interactive scaffolding flow. Pure with respect to I/O — it drives the
102
+ * injected {@link Prompter}, so tests script the answers and assert the result.
103
+ * Throws {@link WizardCancelledError} if the user declines the final confirm.
104
+ */
105
+ declare function runWizard(prompter: Prompter, options?: WizardOptions): Promise<WizardResult>;
106
+
12
107
  interface CreateProjectInput {
13
108
  name: string;
14
109
  /** Target directory. Default: ./<name> under cwd. */
@@ -38,4 +133,4 @@ declare function detectPackageManager(userAgent?: string | undefined): PackageMa
38
133
  /** Generates a ready-to-run Basalt app. Does not install dependencies. */
39
134
  declare function createProject(input: CreateProjectInput): Promise<CreateProjectResult>;
40
135
 
41
- export { type CreateProjectInput, type CreateProjectResult, type PackageManager, type ProjectOptions, TargetNotEmptyError, createProject, detectPackageManager };
136
+ export { type Choice, type CreateProjectInput, type CreateProjectResult, FEATURES, type FeatureKey, PRESETS, type PackageManager, type ProjectOptions, type Prompter, TargetNotEmptyError, WizardCancelledError, type WizardOptions, type WizardResult, createProject, detectPackageManager, runWizard, scriptedPrompter, ttyPrompter, validateProjectName };
package/dist/index.js CHANGED
@@ -1,10 +1,24 @@
1
1
  import {
2
+ FEATURES,
3
+ PRESETS,
2
4
  TargetNotEmptyError,
5
+ WizardCancelledError,
3
6
  createProject,
4
- detectPackageManager
5
- } from "./chunk-XV5NMTXT.js";
7
+ detectPackageManager,
8
+ runWizard,
9
+ scriptedPrompter,
10
+ ttyPrompter,
11
+ validateProjectName
12
+ } from "./chunk-YJ7RXFTU.js";
6
13
  export {
14
+ FEATURES,
15
+ PRESETS,
7
16
  TargetNotEmptyError,
17
+ WizardCancelledError,
8
18
  createProject,
9
- detectPackageManager
19
+ detectPackageManager,
20
+ runWizard,
21
+ scriptedPrompter,
22
+ ttyPrompter,
23
+ validateProjectName
10
24
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-basalt",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Scaffold a production-shaped Basalt SaaS app: npm create basalt my-saas [--billing] [--ui] [--cli] [--install] [--git]",
5
5
  "license": "MIT",
6
6
  "type": "module",