@supatype/cli 0.1.2 → 0.1.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +111 -108
- package/.turbo/turbo-typecheck.log +1 -1
- package/dist/commands/admin.d.ts +12 -0
- package/dist/commands/admin.d.ts.map +1 -1
- package/dist/commands/admin.js +97 -23
- package/dist/commands/admin.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +287 -182
- package/dist/commands/init.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +12 -1
- package/dist/config.js.map +1 -1
- package/dist/dev-compose.js +3 -3
- package/dist/dev-compose.js.map +1 -1
- package/dist/dev-log-bus.d.ts +1 -1
- package/dist/dev-log-bus.d.ts.map +1 -1
- package/dist/dev-log-bus.js +5 -3
- package/dist/dev-log-bus.js.map +1 -1
- package/dist/dev-ready-panel.d.ts +2 -0
- package/dist/dev-ready-panel.d.ts.map +1 -1
- package/dist/dev-ready-panel.js +18 -0
- package/dist/dev-ready-panel.js.map +1 -1
- package/dist/init-project-detect.d.ts +14 -0
- package/dist/init-project-detect.d.ts.map +1 -0
- package/dist/init-project-detect.js +89 -0
- package/dist/init-project-detect.js.map +1 -0
- package/dist/type-extractor.js +44 -0
- package/dist/type-extractor.js.map +1 -1
- package/dist/ui/dev/DevDashboard.d.ts.map +1 -1
- package/dist/ui/dev/DevDashboard.js +11 -4
- package/dist/ui/dev/DevDashboard.js.map +1 -1
- package/dist/ui/dev/DevReadyPanelView.d.ts +2 -1
- package/dist/ui/dev/DevReadyPanelView.d.ts.map +1 -1
- package/dist/ui/dev/DevReadyPanelView.js +21 -2
- package/dist/ui/dev/DevReadyPanelView.js.map +1 -1
- package/dist/ui/flows/FlowApp.d.ts.map +1 -1
- package/dist/ui/flows/FlowApp.js +3 -1
- package/dist/ui/flows/FlowApp.js.map +1 -1
- package/dist/ui/flows/prompt-fields.d.ts.map +1 -1
- package/dist/ui/flows/prompt-fields.js +9 -5
- package/dist/ui/flows/prompt-fields.js.map +1 -1
- package/dist/ui/runtime/run-clack-flow.d.ts.map +1 -1
- package/dist/ui/runtime/run-clack-flow.js +15 -3
- package/dist/ui/runtime/run-clack-flow.js.map +1 -1
- package/dist/ui/runtime/stdin-after-ink.d.ts +3 -0
- package/dist/ui/runtime/stdin-after-ink.d.ts.map +1 -0
- package/dist/ui/runtime/stdin-after-ink.js +16 -0
- package/dist/ui/runtime/stdin-after-ink.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/admin.ts +135 -26
- package/src/commands/init.ts +360 -213
- package/src/config.ts +13 -1
- package/src/dev-compose.ts +4 -4
- package/src/dev-log-bus.ts +5 -3
- package/src/dev-ready-panel.ts +17 -0
- package/src/init-project-detect.ts +99 -0
- package/src/type-extractor.ts +57 -0
- package/src/ui/dev/DevDashboard.tsx +14 -4
- package/src/ui/dev/DevReadyPanelView.tsx +76 -9
- package/src/ui/flows/FlowApp.tsx +3 -1
- package/src/ui/flows/prompt-fields.tsx +13 -7
- package/src/ui/runtime/run-clack-flow.tsx +16 -3
- package/src/ui/runtime/stdin-after-ink.ts +18 -0
- package/tests/admin-ensure.test.ts +80 -0
- package/tests/config.test.ts +19 -0
- package/tests/dev-ready-panel.test.ts +18 -1
- package/tests/init-project-detect.test.ts +53 -0
- package/tests/stdin-after-ink.test.ts +8 -0
- package/tests/type-extractor.test.ts +101 -0
- package/tsconfig.tsbuildinfo +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { resolve, join, basename } from "node:path";
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { p, runClackFlow } from "../ui/clack.js";
|
|
5
|
-
import { ensureNotCancelled
|
|
5
|
+
import { ensureNotCancelled } from "../ui/prompts.js";
|
|
6
6
|
import { generateAndWriteKeys } from "./keys.js";
|
|
7
7
|
import { file, error, info, plain, warn } from "../ui/messages.js";
|
|
8
8
|
import { nextSteps } from "../ui/next-steps.js";
|
|
@@ -12,6 +12,7 @@ import { ensureComponentBinaries, reportComponentBinaryFailures, } from "../ensu
|
|
|
12
12
|
import { mergeSupatypePackageJson } from "../init-package-json.js";
|
|
13
13
|
import { cliPackageVersion } from "../cli-package-version.js";
|
|
14
14
|
import { initDependencyVersionsFallback, resolveInitDependencyVersions, } from "../init-dependency-versions.js";
|
|
15
|
+
import { detectProjectSetup } from "../init-project-detect.js";
|
|
15
16
|
const STORAGE_PROVIDER_OPTIONS = [
|
|
16
17
|
{ value: "local", label: "Local", hint: "storage you host yourself (MinIO)" },
|
|
17
18
|
{ value: "s3", label: "S3", hint: "external bucket (AWS S3 or compatible)" },
|
|
@@ -67,8 +68,14 @@ export function registerInit(program) {
|
|
|
67
68
|
const modeTarget = productionTargetFromMode(opts.mode);
|
|
68
69
|
let result;
|
|
69
70
|
if (interactive) {
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
await runInteractiveInit({
|
|
72
|
+
name,
|
|
73
|
+
dir,
|
|
74
|
+
defaultName,
|
|
75
|
+
modeTarget,
|
|
76
|
+
opts,
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
72
79
|
}
|
|
73
80
|
else {
|
|
74
81
|
result = {
|
|
@@ -106,12 +113,21 @@ export function registerInit(program) {
|
|
|
106
113
|
info(`Using published npm versions: @supatype/cli ^${deps.cli}, @supatype/types ^${deps.types}`);
|
|
107
114
|
}
|
|
108
115
|
scaffold(dir, result, deps);
|
|
109
|
-
const installOk = doInstall ? runInstall(dir, result.packageManager) :
|
|
110
|
-
const
|
|
116
|
+
const installOk = doInstall ? runInstall(dir, result.packageManager) : false;
|
|
117
|
+
const keysGenerated = doKeys ? writeKeys(dir) : false;
|
|
118
|
+
const binariesReady = doInstall && installOk ? await ensureInitBinaries(dir) : false;
|
|
111
119
|
if (doInstall && !installOk) {
|
|
112
|
-
|
|
120
|
+
error(`Dependency install failed. Run "${result.packageManager} install" in the project directory, then "supatype update".`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
if (doKeys && !keysGenerated) {
|
|
124
|
+
error("Could not generate API keys. Check .env has JWT_SECRET, then run `supatype keys`.");
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
if (doInstall && installOk && !binariesReady) {
|
|
128
|
+
error("Component binaries could not be downloaded. Run `supatype update` in the project directory.");
|
|
129
|
+
process.exit(1);
|
|
113
130
|
}
|
|
114
|
-
const keysGenerated = doKeys ? writeKeys(dir) : false;
|
|
115
131
|
warnDockerUnavailableForProvider(result.provider);
|
|
116
132
|
printNextSteps({
|
|
117
133
|
name,
|
|
@@ -122,151 +138,218 @@ export function registerInit(program) {
|
|
|
122
138
|
});
|
|
123
139
|
});
|
|
124
140
|
}
|
|
125
|
-
|
|
126
|
-
async function
|
|
127
|
-
|
|
141
|
+
/** Interactive init — one Ink session for wizard + scaffold + install + finish. */
|
|
142
|
+
async function runInteractiveInit(args) {
|
|
143
|
+
const { name, dir, defaultName, modeTarget, opts } = args;
|
|
144
|
+
await runClackFlow(async () => {
|
|
128
145
|
p.intro("Create a new Supatype project");
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
options: [
|
|
138
|
-
{ value: "npm", label: "npm" },
|
|
139
|
-
{ value: "pnpm", label: "pnpm" },
|
|
140
|
-
{ value: "yarn", label: "yarn" },
|
|
141
|
-
{ value: "bun", label: "bun" },
|
|
142
|
-
],
|
|
143
|
-
}));
|
|
144
|
-
const productionTarget = ensureNotCancelled(await p.select({
|
|
145
|
-
message: "Where will this run in production?",
|
|
146
|
-
initialValue: defaultTarget,
|
|
147
|
-
options: [
|
|
148
|
-
{ value: "cloud", label: "Supatype Cloud", hint: "managed; deploy via supatype link" },
|
|
149
|
-
{ value: "self-host", label: "Self-host", hint: "your own server with TLS" },
|
|
150
|
-
{ value: "later", label: "Decide later", hint: "local development only for now" },
|
|
151
|
-
],
|
|
152
|
-
}));
|
|
153
|
-
let domain;
|
|
154
|
-
let tlsEmail;
|
|
155
|
-
if (productionTarget === "self-host") {
|
|
156
|
-
domain = ensureNotCancelled(await p.text({
|
|
157
|
-
message: "Production domain for ACME TLS (optional, can set later)",
|
|
158
|
-
placeholder: "api.example.com",
|
|
159
|
-
defaultValue: "",
|
|
160
|
-
})).trim();
|
|
161
|
-
if (domain) {
|
|
162
|
-
tlsEmail =
|
|
163
|
-
ensureNotCancelled(await p.text({
|
|
164
|
-
message: "Email for Let's Encrypt (HTTPS) certificates",
|
|
165
|
-
placeholder: "you@example.com",
|
|
166
|
-
defaultValue: "",
|
|
167
|
-
})).trim() || undefined;
|
|
168
|
-
}
|
|
146
|
+
const detected = detectProjectSetup(dir);
|
|
147
|
+
if (detected.hasExistingFiles && detected.summaryLines.length > 0) {
|
|
148
|
+
p.note(["Existing project detected:", ...detected.summaryLines.map((line) => ` • ${line}`)].join("\n"));
|
|
149
|
+
}
|
|
150
|
+
let result = await collectWizardAnswers(defaultName, modeTarget, dir, detected);
|
|
151
|
+
if (opts.admin === false) {
|
|
152
|
+
delete result.adminEmail;
|
|
153
|
+
delete result.adminPassword;
|
|
169
154
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
options: [
|
|
174
|
-
{ value: "docker", label: "Docker", hint: "Docker Compose stack (recommended)" },
|
|
175
|
-
{ value: "native", label: "Native", hint: "host Postgres + server binaries, no Docker" },
|
|
176
|
-
],
|
|
177
|
-
}));
|
|
178
|
-
let kongPort;
|
|
179
|
-
if (provider === "docker") {
|
|
180
|
-
const { promptKongPortChoice } = await import("../dev-ports.js");
|
|
181
|
-
kongPort = await promptKongPortChoice();
|
|
155
|
+
else if (opts.adminEmail && opts.adminPassword) {
|
|
156
|
+
result.adminEmail = opts.adminEmail;
|
|
157
|
+
result.adminPassword = opts.adminPassword;
|
|
182
158
|
}
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
|
|
159
|
+
const doInstall = opts.install !== false && result.install;
|
|
160
|
+
const doKeys = opts.keys !== false && result.generateKeys;
|
|
161
|
+
if (name)
|
|
162
|
+
mkdirSync(dir, { recursive: true });
|
|
163
|
+
const deps = await resolveInitDependencyVersions();
|
|
164
|
+
const runningCli = cliPackageVersion();
|
|
165
|
+
if (deps.cli !== runningCli || deps.types !== runningCli) {
|
|
166
|
+
info(`Using published npm versions: @supatype/cli ^${deps.cli}, @supatype/types ^${deps.types}`);
|
|
167
|
+
}
|
|
168
|
+
const setupSpinner = p.spinner();
|
|
169
|
+
setupSpinner.start("Setting up your project...");
|
|
170
|
+
scaffold(dir, result, deps);
|
|
171
|
+
let installOk = !doInstall;
|
|
172
|
+
if (doInstall) {
|
|
173
|
+
setupSpinner.start(`Installing dependencies with ${result.packageManager}...`);
|
|
174
|
+
installOk = runInstall(dir, result.packageManager);
|
|
175
|
+
setupSpinner.stop(installOk ? "Dependencies installed." : "Dependency install failed.");
|
|
176
|
+
}
|
|
177
|
+
let keysGenerated = !doKeys;
|
|
178
|
+
if (doKeys) {
|
|
179
|
+
setupSpinner.start("Generating API keys...");
|
|
180
|
+
keysGenerated = writeKeys(dir);
|
|
181
|
+
setupSpinner.stop(keysGenerated ? "API keys generated." : "Could not generate API keys.");
|
|
182
|
+
}
|
|
183
|
+
let binariesReady = false;
|
|
184
|
+
if (doInstall && installOk) {
|
|
185
|
+
setupSpinner.start("Preparing component binaries...");
|
|
186
|
+
binariesReady = await ensureInitBinaries(dir);
|
|
187
|
+
setupSpinner.stop(binariesReady ? "Component binaries ready." : "Some binaries are missing.");
|
|
188
|
+
}
|
|
189
|
+
if (doInstall && !installOk) {
|
|
190
|
+
error(`Dependency install failed. Run "${result.packageManager} install" in the project directory, then "supatype update".`);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
if (doKeys && !keysGenerated) {
|
|
194
|
+
error("Could not generate API keys. Check .env has JWT_SECRET, then run `supatype keys`.");
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
if (doInstall && installOk && !binariesReady) {
|
|
198
|
+
error("Component binaries could not be downloaded. Run `supatype update` in the project directory.");
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
warnDockerUnavailableForProvider(result.provider);
|
|
202
|
+
p.outro(`Supatype project ready${name ? ` in ${name}/` : ""}.`);
|
|
203
|
+
printNextSteps({
|
|
204
|
+
name,
|
|
205
|
+
result,
|
|
206
|
+
installed: doInstall && installOk,
|
|
207
|
+
keysGenerated,
|
|
208
|
+
binariesReady,
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
process.exit(0);
|
|
212
|
+
}
|
|
213
|
+
// ─── Wizard ──────────────────────────────────────────────────────────────────
|
|
214
|
+
async function collectWizardAnswers(defaultName, defaultTarget, dir, detected) {
|
|
215
|
+
const projectName = ensureNotCancelled(await p.text({
|
|
216
|
+
message: "Project name",
|
|
217
|
+
defaultValue: defaultName,
|
|
218
|
+
placeholder: defaultName,
|
|
219
|
+
})).trim() || defaultName;
|
|
220
|
+
const admin = await promptAdminUser();
|
|
221
|
+
const packageManager = ensureNotCancelled(await p.select({
|
|
222
|
+
message: "Package manager",
|
|
223
|
+
initialValue: detectInvokingPackageManager(),
|
|
224
|
+
options: [
|
|
225
|
+
{ value: "npm", label: "npm" },
|
|
226
|
+
{ value: "pnpm", label: "pnpm" },
|
|
227
|
+
{ value: "yarn", label: "yarn" },
|
|
228
|
+
{ value: "bun", label: "bun" },
|
|
229
|
+
],
|
|
230
|
+
}));
|
|
231
|
+
const productionTarget = ensureNotCancelled(await p.select({
|
|
232
|
+
message: "Where will this run in production?",
|
|
233
|
+
initialValue: defaultTarget,
|
|
234
|
+
options: [
|
|
235
|
+
{ value: "cloud", label: "Supatype Cloud", hint: "managed; deploy via supatype link" },
|
|
236
|
+
{ value: "self-host", label: "Self-host", hint: "your own server with TLS" },
|
|
237
|
+
{ value: "later", label: "Decide later", hint: "local development only for now" },
|
|
238
|
+
],
|
|
239
|
+
}));
|
|
240
|
+
let domain;
|
|
241
|
+
let tlsEmail;
|
|
242
|
+
if (productionTarget === "self-host") {
|
|
243
|
+
domain = ensureNotCancelled(await p.text({
|
|
244
|
+
message: "Production domain for ACME TLS (optional, can set later)",
|
|
245
|
+
placeholder: "api.example.com",
|
|
246
|
+
defaultValue: "",
|
|
247
|
+
})).trim();
|
|
248
|
+
if (domain) {
|
|
249
|
+
tlsEmail =
|
|
229
250
|
ensureNotCancelled(await p.text({
|
|
230
|
-
message: "
|
|
251
|
+
message: "Email for Let's Encrypt (HTTPS) certificates",
|
|
231
252
|
placeholder: "you@example.com",
|
|
232
253
|
defaultValue: "",
|
|
233
254
|
})).trim() || undefined;
|
|
234
|
-
if (adminEmail) {
|
|
235
|
-
adminPassword =
|
|
236
|
-
ensureNotCancelled(await p.text({
|
|
237
|
-
message: "Admin password (min 8 chars)",
|
|
238
|
-
defaultValue: "",
|
|
239
|
-
})).trim() || undefined;
|
|
240
|
-
if (adminPassword && adminPassword.length < 8) {
|
|
241
|
-
adminPassword = undefined;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
255
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
256
|
+
}
|
|
257
|
+
const provider = ensureNotCancelled(await p.select({
|
|
258
|
+
message: "How should Postgres and the server run for local development?",
|
|
259
|
+
initialValue: "docker",
|
|
260
|
+
options: [
|
|
261
|
+
{ value: "docker", label: "Docker", hint: "Docker Compose stack (recommended)" },
|
|
262
|
+
{ value: "native", label: "Native", hint: "host Postgres + server binaries, no Docker" },
|
|
263
|
+
],
|
|
264
|
+
}));
|
|
265
|
+
let kongPort;
|
|
266
|
+
if (provider === "docker") {
|
|
267
|
+
const { promptKongPortChoice } = await import("../dev-ports.js");
|
|
268
|
+
kongPort = await promptKongPortChoice();
|
|
269
|
+
}
|
|
270
|
+
const defaultSchemaPath = existsSync(join(dir, "schema/index.ts"))
|
|
271
|
+
? "schema/index.ts"
|
|
272
|
+
: "schema/index.ts";
|
|
273
|
+
const schemaPath = ensureNotCancelled(await p.text({
|
|
274
|
+
message: "Where should your schema live?",
|
|
275
|
+
defaultValue: defaultSchemaPath,
|
|
276
|
+
placeholder: "schema/index.ts",
|
|
277
|
+
})).trim() || defaultSchemaPath;
|
|
278
|
+
const app = await promptApp(detected, productionTarget);
|
|
279
|
+
const email = ensureNotCancelled(await p.select({
|
|
280
|
+
message: "Email provider",
|
|
281
|
+
initialValue: "console",
|
|
282
|
+
options: [
|
|
283
|
+
{ value: "console", label: "console", hint: "log emails to the terminal (dev)" },
|
|
284
|
+
{ value: "smtp", label: "SMTP" },
|
|
285
|
+
{ value: "resend", label: "Resend" },
|
|
286
|
+
{ value: "ses", label: "Amazon SES" },
|
|
287
|
+
],
|
|
288
|
+
}));
|
|
289
|
+
const storageLocal = ensureNotCancelled(await p.select({
|
|
290
|
+
message: "Local storage (for development)?",
|
|
291
|
+
initialValue: "local",
|
|
292
|
+
options: STORAGE_PROVIDER_OPTIONS,
|
|
293
|
+
}));
|
|
294
|
+
const storageProduction = ensureNotCancelled(await p.select({
|
|
295
|
+
message: "Production storage?",
|
|
296
|
+
initialValue: "local",
|
|
297
|
+
options: STORAGE_PROVIDER_OPTIONS,
|
|
298
|
+
}));
|
|
299
|
+
const helloFunction = ensureNotCancelled(await p.confirm({
|
|
300
|
+
message: "Create a hello-world edge function?",
|
|
301
|
+
initialValue: false,
|
|
302
|
+
}));
|
|
303
|
+
return {
|
|
304
|
+
projectName,
|
|
305
|
+
provider,
|
|
306
|
+
...(kongPort !== undefined ? { kongPort } : {}),
|
|
307
|
+
productionTarget,
|
|
308
|
+
...(domain !== undefined ? { domain } : {}),
|
|
309
|
+
...(tlsEmail !== undefined ? { tlsEmail } : {}),
|
|
310
|
+
schemaPath,
|
|
311
|
+
app,
|
|
312
|
+
email,
|
|
313
|
+
storageLocal,
|
|
314
|
+
storageProduction,
|
|
315
|
+
helloFunction,
|
|
316
|
+
packageManager,
|
|
317
|
+
install: true,
|
|
318
|
+
generateKeys: true,
|
|
319
|
+
...admin,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
async function promptAdminUser() {
|
|
323
|
+
const createAdmin = ensureNotCancelled(await p.confirm({
|
|
324
|
+
message: "Create an admin user for /admin?",
|
|
325
|
+
initialValue: true,
|
|
326
|
+
}));
|
|
327
|
+
if (!createAdmin)
|
|
328
|
+
return {};
|
|
329
|
+
p.note("First sign-in at /admin — created automatically on supatype dev or push.");
|
|
330
|
+
const adminEmail = ensureNotCancelled(await p.text({
|
|
331
|
+
message: "Admin email",
|
|
332
|
+
placeholder: "you@example.com",
|
|
333
|
+
validate: (value) => (value.trim() ? undefined : "Email is required"),
|
|
334
|
+
})).trim();
|
|
335
|
+
let adminPassword = "";
|
|
336
|
+
while (true) {
|
|
337
|
+
adminPassword = ensureNotCancelled(await p.password({ message: "Admin password (min 8 characters)" })).trim();
|
|
338
|
+
if (adminPassword.length >= 8)
|
|
339
|
+
break;
|
|
340
|
+
p.log.warn("Password must be at least 8 characters.");
|
|
341
|
+
}
|
|
342
|
+
return { adminEmail, adminPassword };
|
|
265
343
|
}
|
|
266
|
-
async function promptApp() {
|
|
344
|
+
async function promptApp(detected, productionTarget) {
|
|
345
|
+
const initialMode = detected.hasVite && productionTarget !== "later"
|
|
346
|
+
? "static"
|
|
347
|
+
: detected.hasVite
|
|
348
|
+
? "proxy"
|
|
349
|
+
: "none";
|
|
267
350
|
const mode = ensureNotCancelled(await p.select({
|
|
268
351
|
message: "Host a frontend app at /?",
|
|
269
|
-
initialValue:
|
|
352
|
+
initialValue: initialMode,
|
|
270
353
|
options: [
|
|
271
354
|
{ value: "none", label: "No app", hint: "API only" },
|
|
272
355
|
{ value: "static", label: "Static site", hint: "serve a built directory" },
|
|
@@ -276,40 +359,44 @@ async function promptApp() {
|
|
|
276
359
|
if (mode === "static") {
|
|
277
360
|
const staticDir = ensureNotCancelled(await p.text({
|
|
278
361
|
message: "Directory to serve",
|
|
279
|
-
defaultValue:
|
|
280
|
-
placeholder:
|
|
281
|
-
})).trim() ||
|
|
282
|
-
const viteDevUrl = await promptViteDevUrl();
|
|
362
|
+
defaultValue: detected.staticDir,
|
|
363
|
+
placeholder: detected.staticDir,
|
|
364
|
+
})).trim() || detected.staticDir;
|
|
365
|
+
const viteDevUrl = await promptViteDevUrl(detected, productionTarget);
|
|
283
366
|
return { mode, staticDir, ...(viteDevUrl ? { viteDevUrl } : {}) };
|
|
284
367
|
}
|
|
285
368
|
if (mode === "proxy") {
|
|
286
369
|
const upstream = ensureNotCancelled(await p.text({
|
|
287
370
|
message: "URL of your running dev server",
|
|
288
|
-
defaultValue: "http://localhost:3000",
|
|
289
|
-
placeholder: "http://localhost:3000",
|
|
290
|
-
})).trim() || "http://localhost:3000";
|
|
371
|
+
defaultValue: detected.hasVite ? detected.viteDevUrl : "http://localhost:3000",
|
|
372
|
+
placeholder: detected.hasVite ? detected.viteDevUrl : "http://localhost:3000",
|
|
373
|
+
})).trim() || (detected.hasVite ? detected.viteDevUrl : "http://localhost:3000");
|
|
291
374
|
const start = ensureNotCancelled(await p.text({
|
|
292
375
|
message: "package.json script that starts your dev server",
|
|
293
|
-
defaultValue: "dev",
|
|
294
|
-
placeholder: "dev",
|
|
295
|
-
})).trim() || "dev";
|
|
296
|
-
const viteDevUrl = await promptViteDevUrl();
|
|
376
|
+
defaultValue: detected.hasVite ? "vite" : "dev",
|
|
377
|
+
placeholder: detected.hasVite ? "vite" : "dev",
|
|
378
|
+
})).trim() || (detected.hasVite ? "vite" : "dev");
|
|
379
|
+
const viteDevUrl = await promptViteDevUrl(detected, productionTarget);
|
|
297
380
|
return { mode, upstream, start, ...(viteDevUrl ? { viteDevUrl } : {}) };
|
|
298
381
|
}
|
|
299
382
|
return { mode: "none" };
|
|
300
383
|
}
|
|
301
|
-
async function promptViteDevUrl() {
|
|
384
|
+
async function promptViteDevUrl(detected, productionTarget) {
|
|
385
|
+
const defaultYes = detected.hasVite || productionTarget !== "later";
|
|
302
386
|
const useVite = ensureNotCancelled(await p.confirm({
|
|
303
|
-
message:
|
|
304
|
-
|
|
387
|
+
message: detected.hasVite
|
|
388
|
+
? "Use Vite for local development?"
|
|
389
|
+
: "Develop locally with Vite (live reload)?",
|
|
390
|
+
initialValue: defaultYes,
|
|
305
391
|
}));
|
|
306
392
|
if (!useVite)
|
|
307
393
|
return undefined;
|
|
394
|
+
p.note(`Press Enter for ${detected.viteDevUrl}, or type a custom URL.`);
|
|
308
395
|
return (ensureNotCancelled(await p.text({
|
|
309
396
|
message: "Vite dev server URL",
|
|
310
|
-
defaultValue:
|
|
311
|
-
placeholder:
|
|
312
|
-
})).trim() ||
|
|
397
|
+
defaultValue: detected.viteDevUrl,
|
|
398
|
+
placeholder: detected.viteDevUrl,
|
|
399
|
+
})).trim() || detected.viteDevUrl);
|
|
313
400
|
}
|
|
314
401
|
// ─── Package manager ───────────────────────────────────────────────────────--
|
|
315
402
|
function detectInvokingPackageManager() {
|
|
@@ -323,14 +410,12 @@ function detectInvokingPackageManager() {
|
|
|
323
410
|
return "npm";
|
|
324
411
|
}
|
|
325
412
|
function runInstall(dir, pm) {
|
|
326
|
-
info(`Installing dependencies with ${pm}...`);
|
|
327
413
|
const res = spawnSync(pm, ["install"], {
|
|
328
414
|
cwd: dir,
|
|
329
415
|
stdio: "inherit",
|
|
330
416
|
shell: process.platform === "win32",
|
|
331
417
|
});
|
|
332
418
|
if (res.status !== 0 || res.error) {
|
|
333
|
-
warn(`Dependency install did not complete (run "${pm} install" manually).`);
|
|
334
419
|
return false;
|
|
335
420
|
}
|
|
336
421
|
return true;
|
|
@@ -398,7 +483,7 @@ export function scaffold(dir, optsOrName, deps = initDependencyVersionsFallback(
|
|
|
398
483
|
write(".env", envTemplate(opts));
|
|
399
484
|
write("seed.ts", seedTemplate(opts.projectName));
|
|
400
485
|
write("seeds/.gitkeep", "");
|
|
401
|
-
scaffoldAppAssets(opts, write);
|
|
486
|
+
scaffoldAppAssets(dir, opts, write);
|
|
402
487
|
if (opts.helloFunction)
|
|
403
488
|
scaffoldHelloFunction(dir, write);
|
|
404
489
|
const gitignorePath = join(dir, ".gitignore");
|
|
@@ -420,37 +505,50 @@ function staticDirRelative(staticDir) {
|
|
|
420
505
|
const raw = (staticDir ?? "./public").trim();
|
|
421
506
|
return raw.replace(/^\.\//, "").replace(/\/+$/, "") || "public";
|
|
422
507
|
}
|
|
423
|
-
function scaffoldAppAssets(opts, write) {
|
|
508
|
+
function scaffoldAppAssets(dir, opts, write) {
|
|
509
|
+
const writeUnlessExists = (rel, content) => {
|
|
510
|
+
const full = join(dir, rel);
|
|
511
|
+
if (existsSync(full)) {
|
|
512
|
+
file("skipped", `${rel} (already exists)`);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
write(rel, content);
|
|
516
|
+
};
|
|
424
517
|
const holding = holdingPageTemplate(opts.projectName);
|
|
425
518
|
const hasVite = Boolean(opts.app.viteDevUrl);
|
|
426
519
|
if (opts.app.mode === "static") {
|
|
427
520
|
const staticRel = staticDirRelative(opts.app.staticDir);
|
|
428
521
|
if (opts.app.viteDevUrl && opts.productionTarget !== "later") {
|
|
429
|
-
|
|
430
|
-
scaffoldVite(opts,
|
|
522
|
+
writeUnlessExists("dist/index.html", holding);
|
|
523
|
+
scaffoldVite(dir, opts, writeUnlessExists, holding);
|
|
431
524
|
return;
|
|
432
525
|
}
|
|
433
|
-
|
|
526
|
+
writeUnlessExists(`${staticRel}/index.html`, holding);
|
|
434
527
|
if (opts.app.viteDevUrl)
|
|
435
|
-
scaffoldVite(opts,
|
|
528
|
+
scaffoldVite(dir, opts, writeUnlessExists, holding);
|
|
436
529
|
return;
|
|
437
530
|
}
|
|
438
531
|
if (opts.app.mode === "proxy" && opts.productionTarget !== "later") {
|
|
439
|
-
|
|
532
|
+
writeUnlessExists("dist/index.html", holding);
|
|
440
533
|
if (hasVite)
|
|
441
|
-
scaffoldVite(opts,
|
|
534
|
+
scaffoldVite(dir, opts, writeUnlessExists, holding);
|
|
442
535
|
return;
|
|
443
536
|
}
|
|
444
537
|
if (opts.app.mode === "proxy" && hasVite) {
|
|
445
|
-
scaffoldVite(opts,
|
|
538
|
+
scaffoldVite(dir, opts, writeUnlessExists, holding);
|
|
446
539
|
return;
|
|
447
540
|
}
|
|
448
|
-
|
|
541
|
+
writeUnlessExists("public/.gitkeep", "");
|
|
449
542
|
}
|
|
450
|
-
function scaffoldVite(opts, write, holding) {
|
|
543
|
+
function scaffoldVite(dir, opts, write, holding) {
|
|
451
544
|
if (!opts.app.viteDevUrl)
|
|
452
545
|
return;
|
|
453
546
|
write("index.html", holding);
|
|
547
|
+
const viteConfigRel = ["vite.config.ts", "vite.config.js", "vite.config.mjs", "vite.config.cjs"].find((name) => existsSync(join(dir, name)));
|
|
548
|
+
if (viteConfigRel) {
|
|
549
|
+
file("skipped", `${viteConfigRel} (already exists)`);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
454
552
|
write("vite.config.ts", viteConfigTemplate(opts.app.viteDevUrl));
|
|
455
553
|
}
|
|
456
554
|
function scaffoldHelloFunction(dir, write) {
|
|
@@ -1025,28 +1123,35 @@ function warnDockerUnavailableForProvider(provider) {
|
|
|
1025
1123
|
}
|
|
1026
1124
|
function printNextSteps(args) {
|
|
1027
1125
|
const { name, result, installed, keysGenerated, binariesReady } = args;
|
|
1126
|
+
const setupComplete = installed && keysGenerated && binariesReady;
|
|
1028
1127
|
const steps = [];
|
|
1029
1128
|
if (name)
|
|
1030
1129
|
steps.push(`cd ${name}`);
|
|
1031
|
-
if (!installed)
|
|
1032
|
-
steps.push(`${result.packageManager} install`);
|
|
1033
|
-
if (!binariesReady)
|
|
1034
|
-
steps.push("supatype update # download component binaries");
|
|
1035
|
-
if (!keysGenerated)
|
|
1036
|
-
steps.push("supatype keys");
|
|
1037
1130
|
const kongHint = result.provider === "docker" && result.kongPort !== undefined
|
|
1038
1131
|
? `supatype dev # Docker Compose stack (Kong :${result.kongPort})`
|
|
1039
1132
|
: "supatype dev # Docker Compose stack (Kong :18473)";
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1133
|
+
if (setupComplete) {
|
|
1134
|
+
steps.push(kongHint);
|
|
1135
|
+
steps.push("supatype push # apply schema + generate types");
|
|
1136
|
+
if (result.adminEmail) {
|
|
1137
|
+
steps.push(" # first admin user is created on dev or push");
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
else {
|
|
1141
|
+
if (!installed)
|
|
1142
|
+
steps.push(`${result.packageManager} install`);
|
|
1143
|
+
if (!binariesReady)
|
|
1144
|
+
steps.push("supatype update # download component binaries");
|
|
1145
|
+
if (!keysGenerated)
|
|
1146
|
+
steps.push("supatype keys");
|
|
1147
|
+
steps.push(kongHint);
|
|
1148
|
+
steps.push("supatype push # apply schema + generate types");
|
|
1044
1149
|
}
|
|
1045
1150
|
if (result.helloFunction) {
|
|
1046
1151
|
steps.push("supatype functions serve # run edge functions locally");
|
|
1047
1152
|
}
|
|
1048
1153
|
info(`Supatype project ready${name ? ` in ${name}/` : ""}.`);
|
|
1049
|
-
nextSteps("Next steps:", steps);
|
|
1154
|
+
nextSteps(setupComplete ? "Next steps:" : "Finish setup:", steps);
|
|
1050
1155
|
if (result.app.mode === "none") {
|
|
1051
1156
|
nextSteps("Static frontend (self-host):", [
|
|
1052
1157
|
"supatype app add --static ./public",
|