feat(projects): offer a connection URL in create:sql / create:nosql prompts - #807
Open
Gawtier wants to merge 4 commits into
Open
feat(projects): offer a connection URL in create:sql / create:nosql prompts#807Gawtier wants to merge 4 commits into
Gawtier wants to merge 4 commits into
Conversation
Gawtier
force-pushed
the
feat/create-prompt-connection-uri
branch
from
July 26, 2026 15:12
2518856 to
9222948
Compare
…rompts
Interactive onboarding was field-only; the connection URL was reachable only via
the -c flag (prompter was null). This brings the CLI to parity with the web
onboarding (which has a Form/URL choice).
Scoped to the sql/nosql commands (no change to shared options, so init / create /
demo prompts are untouched): each overrides databaseConnectionURL with a leading
prompter ("leave blank to enter the details manually") and marks its field prompts
`when: skipWhenConnectionUrl` so they're skipped once a URL is given. sql validates
a SQL scheme, nosql a mongodb(+srv) scheme (rejects a bare `foo` and a cross-engine
URL). A blank answer is coerced to undefined so the fields path is used.
Tests: options.test.ts covers the helpers (scheme validation + skip predicate);
sql/nosql each get a command test of the interactive URL path (project created from
the URL, field prompts skipped). init/create prompt expectations are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gawtier
force-pushed
the
feat/create-prompt-connection-uri
branch
from
July 26, 2026 16:18
9222948 to
1e3a9ab
Compare
Review fixes on the interactive connection-URL onboarding: - skip the URL prompt when any database field flag is passed (exclusive list on the option, mirroring the field options): scripted/CI invocations that pass -d/-n/-h/-p/-u must not hang on a new question - drop mariadb:// from the accepted SQL schemes: the mariadb driver is not shipped with generated projects, so Sequelize would throw after the project was already created server-side (use mysql:// instead) - make scheme validation case-sensitive and say so in the error: getDialect() and the generated project match lowercase schemes only, so Postgres://… used to pass validation then silently produce a project with databaseType null and no driver - test the blank-URL round-trip: empty answer falls back to the field prompts and the '' value is normalized to undefined for the DB config Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review follow-up on the optional connection URL prompt. The URL embeds the database password, but the prompt was rendered as a plain `input`: `optionToInquirer` only masks options whose *name* matches /(password|secret)/, so the pasted credentials were echoed on screen and left in the terminal scrollback — a regression against the field-by-field flow, whose `databasePassword` prompt is masked. Options can now opt into masking explicitly via `prompter.secret`, with a `*` mask so a failed paste stays distinguishable from the blank answer that falls back to the fields. A pasted URL routinely drags whitespace along. `' postgres://…'` was rejected with a message about lowercase schemes, and `'postgres://… '` was accepted verbatim and flowed into the connection test and the generated .env. Worse, the untrimmed value reached `getDialect()` *during* prompting, so a leading space made the database schema question disappear. Options can now declare a `filter`, run by inquirer before `when`/`validate`; the URL trims itself at the source, and `getCommandOptions` normalizes the `-c` flag path the same way. Every rejection used to read "the scheme must be lowercase", including `mariadb://`, `pg://`, `sqlite://` and a URL with a leading space. Each case now says what is actually wrong, and mariadb:// points at mysql://. Tested: the command test helper stubs inquirer and returns canned answers, so nothing proved the field prompts were skipped. connection-url-prompt.test.ts drives the real inquirer against the real command options and asserts which questions are asked, that the credentials never reach the screen, and that the blank answer still falls back to the field prompts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| // A blank answer to the optional connection URL prompt means "use the field prompts instead", | ||
| // and a pasted URL routinely carries surrounding whitespace. Normalize once, before the | ||
| // dialect is derived from it and before it reaches the connection test and the generated .env. | ||
| options.databaseConnectionURL = options.databaseConnectionURL?.trim() || undefined; |
There was a problem hiding this comment.
🟠 High src/abstract-project-create-command.ts:223
Whitespace-only -c values still suppress all database field prompts, so this normalization leaves dbConfig without either a connection URL or dialect and the command exits with “Missing database dialect.” Normalize the value before getCommandLineOptions runs its interactive skip logic, or trim it in skipWhenConnectionUrl.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/abstract-project-create-command.ts around line 223:
Whitespace-only `-c` values still suppress all database field prompts, so this normalization leaves `dbConfig` without either a connection URL or dialect and the command exits with “Missing database dialect.” Normalize the value before `getCommandLineOptions` runs its interactive skip logic, or trim it in `skipWhenConnectionUrl`.
Review follow-up: shrink what the new connection URL validators reject.
Uppercase schemes were rejected because `getDialect()` matched them with a
case-sensitive `startsWith`, returned null, and left the generated project
without a driver in its package.json. But URL schemes are case-insensitive, and
both Sequelize and @forestadmin/datasource-sql parse the connection URL with a
URL parser that normalizes the scheme — verified: `new Sequelize('Postgres://…')`
yields the postgres dialect with the password untouched. So `Postgres://…` works
end to end, and the bug was ours. `getDialect()` now compares on a lowercased
copy (the credentials the URL carries are case-sensitive, and the URL itself is
handed to the driver untouched), here and on the schema:update path, which had
the same bug.
The SQL validator therefore accepts uppercase schemes, which cuts the breaking
change on the `-c` flag down to mariadb:// and genuinely unsupported schemes.
The mongo validator keeps requiring lowercase: unlike the SQL drivers, the
mongodb driver compares the scheme verbatim and throws "Invalid scheme, expected
connection string to start with mongodb://" on `MongoDB://…`. Rejecting it in
the prompt keeps that failure before the project is created on Forest.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Interactive
projects:create:sql/projects:create:nosqlnow open with an optional connection URL prompt —Database connection URL (leave blank to enter the details manually):(MongoDB wording on nosql). Paste a URL and the field prompts (dialect/name/host/port/user/password, Mongo SRV) are skipped; leave it blank and today's field-by-field flow runs unchanged.This brings the CLI to parity with the web onboarding, which offers the same Form/URL choice. Until now the connection URL was reachable only via the
-cflag (prompter: null), so a human running the interactive create could never paste a connection string.How it works
Scoped to the two commands — the shared options are untouched, so
init/projects:create/demoprompts are unchanged:databaseConnectionURLwith a leadingprompterand a per-engine validator (validateSqlConnectionUrl/validateMongoConnectionUrlinsrc/services/projects/create/options.ts). No new flag, no extra "choose a method" question.when: skipWhenConnectionUrl, so they are skipped once a URL is answered.exclusivelist mirroring the field options': passing any DB field flag (-d/-n/-h/-p/-u/--databasePassword,--mongoDBSRV) drops the URL question, so scripted/non-TTY usage never faces a new question to hang on.prompter.secret, new): the URL embeds the database password, andoptionToInquireronly masks options whose name matches/(password|secret)/. Without it the credentials were echoed on screen and left in the terminal scrollback — a regression against the field-by-field flow, whosedatabasePasswordprompt is masked. A*mask keeps a failed paste distinguishable from the blank answer.filter, new — inquirer runs it beforewhen/validate). A pasted URL routinely drags whitespace along, and the untrimmed value reachesgetDialect()during prompting, so a leading space used to make the database schema question disappear.getCommandOptionsnormalizes the-cflag path the same way, and a blank answer becomesundefinedso the DB config falls back to the field values.Behaviour change for scripted usage (
-c)getCommandLineOptionsruns an option'svalidateon values coming from the command line too, so the new validators also gate the-cflag.projects:create:sql -c mariadb://…now fails at parse time where it used to be accepted.This is deliberate.
runAuthenticatedcreates the project on Forest before testing the database connection, so a URL the toolbelt cannot interpret either leaves an orphan project behind, or — worse — succeeds all the way and hands the user a generated project whosepackage.jsonships no driver, broken at the firstnpm start. Failing in the parser, with a message that says what to do, is the point.What is rejected, and why:
mariadb://—getDialect()maps it to themysqldialect, so the generated project shipsmysql2, while@forestadmin/datasource-sqlderivesmariadbfrom the URL scheme and needs themariadbdriver. Usemysql://. (The dumper does have mariadb support, butgetDialect()never returns that dialect; making it reachable is a separate change —dbDialectflows into the project meta sent to the API, whose v1 dialect choices only knowmysql.)postgres://,postgresql://,mysql://,mssql://(SQL) andmongodb://,mongodb+srv://(Mongo) —pg://,mysql2://,sqlite://and friends all end up with no dialect, hence no driver in the generated project.What is not rejected: uppercase schemes.
getDialect()was matching them case-sensitively and returningnull, which is what actually brokePostgres://…. Both Sequelize and@forestadmin/datasource-sqlparse the URL with a URL parser that normalizes the scheme, soPostgres://user:MyPass@host/dbconnects and generates a working project —getDialect()is now case-insensitive (on a lowercased copy: the credentials the URL carries are case-sensitive), insrc/services/projects/create/options.tsand insrc/services/schema/update/database.js, which had the same bug on theschema:updatepath. The mongo validator is the exception and still requires lowercase, because the mongodb driver itself throwsInvalid scheme, expected connection string to start with "mongodb://".projects:create(v1, forest-express) is deliberately left untouched: adding a new hard failure to a legacy path costs more than the consistency is worth.Error messages
Every rejection used to read "the scheme must be lowercase", including
mariadb://,pg://,sqlite://and a URL with a leading space. Each case now says what is actually wrong: unsupported scheme (listing the supported ones), URL truncated after://, not a URL at all, andmariadb://pointing atmysql://.Tested
options.test.ts— the helpers: skip predicate (incl. blank), scheme validation per engine, each exact message, uppercase accepted on SQL and refused on Mongo,getDialect()case-insensitivity leaving the credentials alone, and a guard against a scheme resolving onObject.prototype.connection-url-prompt.test.ts(new) — the command test helper stubs inquirer and returns canned answers, so nothing proved the field prompts were actually skipped. This one drives the real inquirer against the real command options and asserts which questions the user is asked (URL → fields skipped, blank → fields asked, nosql likewise), that the pasted credentials never reach the screen, and that the URL is stored trimmed.database.unit.test.js—getDialect()on theschema:updatepath, case-insensitive and non-destructive.abstractProjectCreateCommand.unit.test.ts— a blank URL normalizes toundefinedand the connection uses the field values; a URL pasted with whitespace is trimmed before the dialect is derived.sql.test.js/nosql.test.js— interactive URL path (project created, dialect derived from the URL), field-flags path (no URL question in the prompt list), and the blank-URL round-trip against the test database.tsc+ lint clean.🤖 Generated with Claude Code
Note
Add connection URL prompt to
create:sqlandcreate:nosqlproject commandscreate:sqlandcreate:nosql; when a URL is supplied, the individual database field prompts (host, port, user, password, name, dialect) are skippedvalidateSqlConnectionUrlaccepts PostgreSQL, MySQL, and MSSQL schemes (rejects MariaDB with guidance), andvalidateMongoConnectionUrlacceptsmongodbandmongodb+srv(requires lowercase scheme)trimConnectionUrlfilter andskipWhenConnectionUrlpredicate so blank URL answers fall back to the existing field promptsoption-parser.optionToInquirerto treat explicitly secret options as masked password prompts and forward configured filters to InquirerMacroscope summarized 826d38c.