1- import { cancel , isCancel , log , select , spinner } from "@clack/prompts" ;
1+ import { cancel , isCancel , log , select , spinner , taskLog } from "@clack/prompts" ;
22import { execa } from "execa" ;
3+ import { createInterface } from "node:readline" ;
34
45import { PRISMA_PLATFORM_CLI_PACKAGE } from "../constants/dependencies" ;
56import type { PackageManager } from "../types" ;
@@ -44,6 +45,13 @@ type ProjectShowResult = {
4445 project : { id : string ; name : string } | null ;
4546} ;
4647
48+ type ProjectListResult = {
49+ items : Array < {
50+ id : string ;
51+ name : string ;
52+ } > ;
53+ } ;
54+
4755type ComposerDeployCommandResult = {
4856 summary : {
4957 app : string ;
@@ -64,13 +72,17 @@ export type ComposerDeployResult = {
6472 } ;
6573} ;
6674
67- function redactSecrets ( message : string ) : string {
75+ export function redactSecrets ( message : string ) : string {
6876 return message
69- . replace ( / \b ( (?: p r i s m a \+ ) ? p o s t g r e s (?: q l ) ? : \/ \/ ) [ ^ \s ' " ] + / gi, "$1<redacted>" )
7077 . replace (
71- / \b ( [ A - Z 0 - 9 _ ] * (?: D A T A B A S E _ U R L | T O K E N | S E C R E T | P A S S W O R D | A P I _ K E Y | P R I V A T E _ K E Y ) [ A - Z 0 - 9 _ ] * = ) [ ^ \s ] + / g ,
78+ / \b ( (?: (?: p r i s m a \+ ) ? p o s t g r e s (?: q l ) ? | m o n g o d b (?: \+ s r v ) ? ) : \/ \/ ) [ ^ \s ' " ] + / gi ,
7279 "$1<redacted>" ,
73- ) ;
80+ )
81+ . replace (
82+ / \b ( [ A - Z 0 - 9 _ ] * (?: M O N G O D B _ (?: U R L | U R I ) | D A T A B A S E _ U R L | T O K E N | S E C R E T | P A S S W O R D | A P I _ K E Y | P R I V A T E _ K E Y ) [ A - Z 0 - 9 _ ] * \s * = \s * ) (?: " [ ^ " ] * " | ' [ ^ ' ] * ' | [ ^ \s ] + ) / gi,
83+ "$1<redacted>" ,
84+ )
85+ . replace ( / ( \b A u t h o r i z a t i o n \s * : \s * B e a r e r \s + ) [ ^ \s ' " ] + / gi, "$1<redacted>" ) ;
7486}
7587
7688function getErrorMessage ( error : unknown ) : string {
@@ -123,22 +135,28 @@ async function runPrismaJsonCommand<Result>(options: {
123135 packageManager : PackageManager ;
124136 projectDir : string ;
125137 args : string [ ] ;
126- forwardStderr ?: boolean ;
138+ onStderrLine ?: ( line : string ) => void ;
127139} ) : Promise < Result > {
128140 const invocation = getPrismaCliArgs ( options . packageManager , [
129141 ...options . args ,
130142 "--json" ,
131143 "--no-interactive" ,
132144 ] ) ;
133- const result = await execa ( invocation . command , invocation . args , {
145+ const subprocess = execa ( invocation . command , invocation . args , {
134146 cwd : options . projectDir ,
135147 env : process . env ,
136148 reject : false ,
137149 } ) ;
138-
139- if ( options . forwardStderr && result . stderr ) {
140- process . stderr . write ( result . stderr . endsWith ( "\n" ) ? result . stderr : `${ result . stderr } \n` ) ;
141- }
150+ const stderrLines =
151+ options . onStderrLine && subprocess . stderr
152+ ? ( async ( ) => {
153+ const lines = createInterface ( { input : subprocess . stderr } ) ;
154+ for await ( const line of lines ) {
155+ if ( line . trim ( ) ) options . onStderrLine ?.( line ) ;
156+ }
157+ } ) ( )
158+ : Promise . resolve ( ) ;
159+ const [ result ] = await Promise . all ( [ subprocess , stderrLines ] ) ;
142160
143161 let envelope : PrismaCliEnvelope < Result > ;
144162 try {
@@ -159,6 +177,36 @@ async function runPrismaJsonCommand<Result>(options: {
159177 return envelope . result ;
160178}
161179
180+ export function findProjectNameCollisions (
181+ projects : ProjectListResult [ "items" ] ,
182+ appName : string ,
183+ ) : ProjectListResult [ "items" ] {
184+ return projects . filter ( ( project ) => project . name === appName ) ;
185+ }
186+
187+ async function ensureProjectNameAvailable ( options : {
188+ appName : string ;
189+ packageManager : PackageManager ;
190+ projectDir : string ;
191+ workspace : PrismaWorkspace ;
192+ } ) : Promise < void > {
193+ const result = await runPrismaJsonCommand < ProjectListResult > ( {
194+ packageManager : options . packageManager ,
195+ projectDir : options . projectDir ,
196+ args : [ "project" , "list" ] ,
197+ } ) ;
198+ const collisions = findProjectNameCollisions ( result . items , options . appName ) ;
199+ if ( collisions . length === 0 ) return ;
200+
201+ const projectIds = collisions . map ( ( project ) => project . id ) . join ( ", " ) ;
202+ throw new Error (
203+ `A Prisma project named "${ options . appName } " already exists in workspace ${ workspaceLabel (
204+ options . workspace ,
205+ ) } (${ options . workspace . id } ). Choose a different project name or delete the existing project ` +
206+ `(${ projectIds } ) in Prisma Console, then retry.` ,
207+ ) ;
208+ }
209+
162210async function ensureAuthentication (
163211 packageManager : PackageManager ,
164212 projectDir : string ,
@@ -326,7 +374,11 @@ async function getProjectDetails(options: {
326374 }
327375}
328376
329- export async function deployWithComposer ( options : {
377+ /**
378+ * Performs the optional one-shot deployment at the end of a create-prisma scaffold.
379+ * Generated projects use their own `deploy` script for every subsequent deployment.
380+ */
381+ export async function deployNewProjectWithComposer ( options : {
330382 appName : string ;
331383 packageManager : PackageManager ;
332384 projectDir : string ;
@@ -335,6 +387,7 @@ export async function deployWithComposer(options: {
335387 workspace ?: string ;
336388} ) : Promise < ComposerDeployResult | undefined > {
337389 const progress = options . verbose ? undefined : spinner ( ) ;
390+ let deploymentLog : ReturnType < typeof taskLog > | undefined ;
338391 let progressRunning = false ;
339392 const showProgress = ( message : string ) => {
340393 if ( ! progress ) return ;
@@ -373,6 +426,15 @@ export async function deployWithComposer(options: {
373426 } ) ;
374427 if ( ! selectedWorkspace ) return ;
375428
429+ showProgress ( "Checking Prisma project name..." ) ;
430+ if ( options . verbose ) log . step ( "Checking Prisma project name." ) ;
431+ await ensureProjectNameAvailable ( {
432+ appName : options . appName ,
433+ packageManager : options . packageManager ,
434+ projectDir : options . projectDir ,
435+ workspace : selectedWorkspace ,
436+ } ) ;
437+
376438 showProgress ( "Building for deployment..." ) ;
377439 if ( options . verbose ) log . step ( "Building for deployment." ) ;
378440 const build = getRunScriptArgs ( options . packageManager , "build" ) ;
@@ -382,26 +444,48 @@ export async function deployWithComposer(options: {
382444 stdio : options . verbose ? "inherit" : "pipe" ,
383445 } ) ;
384446
385- showProgress ( "Deploying to Prisma..." ) ;
386- if ( options . verbose ) log . step ( "Deploying to Prisma." ) ;
447+ clearProgress ( ) ;
448+ const deployCommand = getPackageExecutionCommand ( options . packageManager , [
449+ PRISMA_PLATFORM_CLI_PACKAGE ,
450+ "deploy" ,
451+ "module.ts" ,
452+ ] ) ;
453+ if ( options . verbose ) {
454+ log . step ( `Deploying to Prisma with ${ deployCommand } .` ) ;
455+ } else {
456+ deploymentLog = taskLog ( { title : "Deploying to Prisma..." , limit : 10 } ) ;
457+ deploymentLog . message ( `$ ${ deployCommand } ` ) ;
458+ }
387459 const deployment = parseComposerDeployResult (
388460 await runPrismaJsonCommand < ComposerDeployCommandResult > ( {
389461 packageManager : options . packageManager ,
390462 projectDir : options . projectDir ,
391463 args : [ "deploy" , "module.ts" ] ,
392- forwardStderr : options . verbose ,
464+ onStderrLine : ( line ) => {
465+ const redactedLine = redactSecrets ( line ) ;
466+ if ( options . verbose ) {
467+ process . stderr . write ( `${ redactedLine } \n` ) ;
468+ } else {
469+ deploymentLog ?. message ( redactedLine ) ;
470+ }
471+ } ,
393472 } ) ,
394473 ) ;
395474 const appName = deployment ?. appName ?? options . appName ;
396475
397- showProgress ( "Loading deployment details..." ) ;
476+ if ( options . verbose ) {
477+ log . step ( "Loading deployment details." ) ;
478+ } else {
479+ deploymentLog ?. message ( "Loading deployment details..." ) ;
480+ }
398481 const details = await getProjectDetails ( {
399482 packageManager : options . packageManager ,
400483 projectDir : options . projectDir ,
401484 appName,
402485 } ) ;
403486
404- progress ?. stop ( "Deployed to Prisma." ) ;
487+ deploymentLog ?. success ( "Deployed to Prisma." ) ;
488+ deploymentLog = undefined ;
405489 progressRunning = false ;
406490 if ( options . verbose ) log . success ( "Deployed to Prisma." ) ;
407491 const workspace = details ?. workspace ?? selectedWorkspace ;
@@ -412,7 +496,12 @@ export async function deployWithComposer(options: {
412496 project : details ?. project ?? { name : appName } ,
413497 } ;
414498 } catch ( error ) {
415- progress ?. error ( "Deployment failed." ) ;
499+ if ( deploymentLog ) {
500+ deploymentLog . error ( "Deployment failed." ) ;
501+ deploymentLog = undefined ;
502+ } else {
503+ progress ?. error ( "Deployment failed." ) ;
504+ }
416505 progressRunning = false ;
417506 log . error ( `Deploy failed: ${ getErrorMessage ( error ) } ` ) ;
418507 return ;
0 commit comments