create-basalt 1.1.1 → 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 +4 -1
- package/dist/{chunk-TYOPE3RU.js → chunk-YJ7RXFTU.js} +242 -0
- package/dist/cli.js +20 -28
- package/dist/index.d.ts +96 -1
- package/dist/index.js +17 -3
- package/package.json +1 -1
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
|
|
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
|
|
|
@@ -1045,6 +1045,241 @@ function MfaCard({ status, api, onChange }: { status: { enabled: boolean; pendin
|
|
|
1045
1045
|
`;
|
|
1046
1046
|
}
|
|
1047
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
|
+
|
|
1048
1283
|
// src/index.ts
|
|
1049
1284
|
var TargetNotEmptyError = class extends Error {
|
|
1050
1285
|
constructor(dir) {
|
|
@@ -1093,6 +1328,13 @@ async function createProject(input) {
|
|
|
1093
1328
|
}
|
|
1094
1329
|
|
|
1095
1330
|
export {
|
|
1331
|
+
WizardCancelledError,
|
|
1332
|
+
ttyPrompter,
|
|
1333
|
+
scriptedPrompter,
|
|
1334
|
+
FEATURES,
|
|
1335
|
+
PRESETS,
|
|
1336
|
+
validateProjectName,
|
|
1337
|
+
runWizard,
|
|
1096
1338
|
TargetNotEmptyError,
|
|
1097
1339
|
detectPackageManager,
|
|
1098
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
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
|
-
|
|
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