fix: complete module creation progress lifecycle
This commit is contained in:
@@ -4,7 +4,7 @@ import { AURORA_ENV, powerOffProjects } from './auroraEngine'
|
||||
const running = new Map<string, ChildProcess>(); const cancelledIds = new Set<string>()
|
||||
const ANSI_PATTERN = /[\u001B\u009B][[\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\d/#&.:=?%@~_]+)*|[a-zA-Z\d]+(?:;[-a-zA-Z\d/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-ntqry=><~]))/g
|
||||
const stripAnsi=(s:string)=>s.replace(ANSI_PATTERN,'')
|
||||
export function runCommandStreamed(operationId:string, command:string, args:string[], sender:WebContents, options:{cwd?:string}={}):Promise<void>{return new Promise((resolve,reject)=>{const child=spawn(command,args,{env:AURORA_ENV,cwd:options.cwd,stdio:['ignore','pipe','pipe']});running.set(operationId,child);const tail:string[]=[];const forward=(stream:'stdout'|'stderr')=>(data:Buffer)=>{const chunk=stripAnsi(data.toString());tail.push(chunk);if(tail.length>20)tail.shift();if(!sender.isDestroyed())sender.send('terminal:data',{operationId,stream,chunk})};child.stdout.on('data',forward('stdout'));child.stderr.on('data',forward('stderr'));child.on('error',e=>{running.delete(operationId);sender.send('terminal:exit',{operationId,exitCode:null,cancelled:false});reject(e)});child.on('close',code=>{running.delete(operationId);const cancelled=cancelledIds.delete(operationId);if(!sender.isDestroyed())sender.send('terminal:exit',{operationId,exitCode:code,cancelled});if(cancelled)reject(new Error('Command cancelled'));else if(code===0)resolve();else reject(new Error(tail.join('').trim()||`${command} exited with code ${code}`))})})}
|
||||
export function runCommandStreamed(operationId:string, command:string, args:string[], sender:WebContents, options:{cwd?:string;emitExit?:boolean}={}):Promise<void>{return new Promise((resolve,reject)=>{const child=spawn(command,args,{env:AURORA_ENV,cwd:options.cwd,stdio:['ignore','pipe','pipe']});running.set(operationId,child);const tail:string[]=[];const forward=(stream:'stdout'|'stderr')=>(data:Buffer)=>{const chunk=stripAnsi(data.toString());tail.push(chunk);if(tail.length>20)tail.shift();if(!sender.isDestroyed())sender.send('terminal:data',{operationId,stream,chunk})};child.stdout.on('data',forward('stdout'));child.stderr.on('data',forward('stderr'));child.on('error',e=>{running.delete(operationId);if(options.emitExit!==false&&!sender.isDestroyed())sender.send('terminal:exit',{operationId,exitCode:null,cancelled:false});reject(e)});child.on('close',code=>{running.delete(operationId);const cancelled=cancelledIds.delete(operationId);if(options.emitExit!==false&&!sender.isDestroyed())sender.send('terminal:exit',{operationId,exitCode:code,cancelled});if(cancelled)reject(new Error('Command cancelled'));else if(code===0)resolve();else reject(new Error(tail.join('').trim()||`${command} exited with code ${code}`))})})}
|
||||
export function cancelCommand(id:string):boolean{const c=running.get(id);if(!c)return false;cancelledIds.add(id);c.kill();return true}
|
||||
export function startLogStream(operationId:string, command:string,args:string[],sender:WebContents,options:{cwd?:string}={}):void{const child=spawn(command,args,{env:AURORA_ENV,cwd:options.cwd,stdio:['ignore','pipe','pipe']});running.set(operationId,child);const f=(stream:'stdout'|'stderr')=>(d:Buffer)=>{if(!sender.isDestroyed())sender.send('logs:data',{operationId,stream,chunk:stripAnsi(d.toString())})};child.stdout.on('data',f('stdout'));child.stderr.on('data',f('stderr'));const done=()=>{running.delete(operationId);if(!sender.isDestroyed())sender.send('logs:exit',{operationId})};child.on('close',done);child.on('error',done)}
|
||||
export function killAllRunningCommands():void{for(const c of running.values())c.kill();running.clear()}
|
||||
|
||||
@@ -19,15 +19,20 @@ export async function runModuleProjectCreate(moduleId: string, operationId: stri
|
||||
if (typeof loaded.projectCreate !== 'function') return
|
||||
const config = await getProjectConfigByRoot(directory)
|
||||
const urls = projectUrls(config.name)
|
||||
await loaded.projectCreate(Object.freeze({
|
||||
try { await loaded.projectCreate(Object.freeze({
|
||||
moduleId,
|
||||
directory,
|
||||
projectName,
|
||||
settings: Object.freeze({ ...settings }),
|
||||
urls: Object.freeze(urls),
|
||||
run: (suffix: string, command: string, args: string[]) => runCommandStreamed(`${operationId}-${suffix}`, command, args, sender, { cwd: directory }),
|
||||
run: (_suffix: string, command: string, args: string[]) => runCommandStreamed(operationId, command, args, sender, { cwd: directory, emitExit: false }),
|
||||
ensureRouter,
|
||||
setProjectMetadata: (metadata: Record<string, string | number | boolean>) => setProjectModuleMetadata(directory, metadata),
|
||||
saveCredentials: (credentials: { platform: string; adminUrl: string; username: string; password: string; email: string }) => saveSiteCredentials(directory, credentials)
|
||||
}))
|
||||
if (!sender.isDestroyed()) sender.send('terminal:exit', { operationId, exitCode: 0, cancelled: false })
|
||||
} catch (error) {
|
||||
if (!sender.isDestroyed()) sender.send('terminal:exit', { operationId, exitCode: 1, cancelled: false })
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'
|
||||
import { ChevronDown, ChevronUp, Eye, EyeOff, Globe2, KeyRound, Mail, RefreshCw, Settings2, Type, UserRound } from 'lucide-react'
|
||||
import type { AuroraModuleManifest, AuroraModuleSetting } from '@shared/types'
|
||||
import type { TypeSetupHandle, TypeSetupProps } from './shared'
|
||||
import { useTerminalStore } from '../../../stores/terminalStore'
|
||||
import { useStatusStore } from '../../../stores/statusStore'
|
||||
|
||||
const inputClass = 'w-full rounded-lg border border-neutral-300 bg-white/80 px-3 py-2 text-sm shadow-sm transition placeholder:text-neutral-400 focus:border-cyan-400 focus:outline-none focus:ring-2 focus:ring-cyan-400/15 dark:border-white/10 dark:bg-neutral-950/70 dark:placeholder:text-neutral-600'
|
||||
const labelClass = 'mb-1.5 block text-xs font-semibold uppercase tracking-wide text-neutral-500 dark:text-neutral-400'
|
||||
@@ -21,7 +23,13 @@ export const ExternalModuleSetup = forwardRef<TypeSetupHandle, TypeSetupProps &
|
||||
const [visibleSecrets, setVisibleSecrets] = useState<Set<string>>(new Set())
|
||||
const valid = fields.every((field) => !field.required || String(values[field.id] ?? '').trim().length > 0)
|
||||
useEffect(() => onValidityChange(valid), [valid, onValidityChange])
|
||||
useImperativeHandle(ref, () => ({ runPostCreate: async ({ directory }) => window.api.create.runModuleProjectCreate(crypto.randomUUID(), module.id, directory, projectName, values) }), [module.id, projectName, values])
|
||||
useImperativeHandle(ref, () => ({ runPostCreate: async ({ directory }) => {
|
||||
const operationId = crypto.randomUUID()
|
||||
const label = `Install ${module.name} for ${projectName}`
|
||||
useTerminalStore.getState().startOperation(operationId, label)
|
||||
useStatusStore.getState().begin(operationId, label)
|
||||
await window.api.create.runModuleProjectCreate(operationId, module.id, directory, projectName, values)
|
||||
} }), [module.id, module.name, projectName, values])
|
||||
|
||||
function fieldControl(field: AuroraModuleSetting): React.JSX.Element {
|
||||
if (field.type === 'boolean') return <label className="flex items-center gap-2 rounded-lg border border-neutral-200 bg-white/70 px-3 py-2 text-sm font-medium dark:border-white/10 dark:bg-neutral-950/50"><input type="checkbox" checked={Boolean(values[field.id])} onChange={(event) => setValues({ ...values, [field.id]: event.target.checked })}/>{field.label}</label>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { AuroraStackOptions } from '@shared/types'
|
||||
import { useMutation, useQueryClient, type UseMutationResult } from '@tanstack/react-query'
|
||||
import { useTerminalStore } from '../stores/terminalStore'
|
||||
import { useStatusStore } from '../stores/statusStore'
|
||||
|
||||
export interface CreateProjectInput { directory: string; projectName: string; projectType: string; docroot: string; stack?: Partial<AuroraStackOptions> }
|
||||
@@ -10,9 +9,9 @@ export function useCreateProject(): UseMutationResult<void, Error, CreateProject
|
||||
return useMutation({
|
||||
mutationFn: async ({ directory, projectName, projectType, docroot, stack }) => {
|
||||
const operationId = crypto.randomUUID()
|
||||
useTerminalStore.getState().startOperation(operationId, `Create project ${projectName}`)
|
||||
useStatusStore.getState().begin(operationId, `Create project ${projectName}`)
|
||||
await window.api.create.configure(operationId, directory, projectName, projectType, docroot, stack)
|
||||
try { await window.api.create.configure(operationId, directory, projectName, projectType, docroot, stack) }
|
||||
finally { if (useStatusStore.getState().operationId === operationId) useStatusStore.getState().end() }
|
||||
},
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['projects'] })
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user