1- import { OptionValues } from 'commander'
1+ import type { OptionValues } from 'commander'
2+ import { resolve } from 'node:path'
3+ import { promises as fs } from 'node:fs'
4+ import type { NetlifyAPI } from '@netlify/api'
25
3- import { chalk , log , logAndThrowError } from '../../utils/command-helpers.js'
6+ import { chalk , log , logAndThrowError , type APIError } from '../../utils/command-helpers.js'
47import { normalizeRepoUrl } from '../../utils/normalize-repo-url.js'
58import { runGit } from '../../utils/run-git.js'
69import { startSpinner } from '../../lib/spinner.js'
7- import BaseCommand from '../base-command.js'
8-
9- // Decode hash to get the encoded URL
10- const decodeHash = ( hash : string ) : string => {
11- // In real implementation, this would decode the hash to get the actual URL
12- // For now, return a mock URL
13- return 'https://api.netlify.com/api/v1/ai/projects/mock-endpoint'
14- }
10+ import type BaseCommand from '../base-command.js'
11+ import type { SiteInfo } from '../../utils/types.js'
1512
1613interface ProjectInfo {
1714 success : boolean
1815 projectId : string
19- aiInstructions : string
16+ prompt : string
2017}
2118
22- // Call the decoded URL to get project information
23- const fetchProjectInfo = async ( url : string , authToken : string ) : Promise < ProjectInfo > => {
24- try {
25- // Mock response for now - in real implementation, fetch from the decoded URL
26- await new Promise ( resolve => setTimeout ( resolve , 1000 ) )
27-
28- // Mock response with project ID and AI instructions (no repository URL)
29- return {
30- success : true ,
31- projectId : '4d6c8c75-2278-409e-bcb7-06e07b79e1bc' ,
32- aiInstructions : `# AI Project Instructions
33-
34- This is your AI-powered project setup guide.
35-
36- ## Getting Started
37-
38- 1. Review the project structure
39- 2. Install dependencies: \`npm install\`
40- 3. Start development: \`netlify dev\`
41- 4. Deploy your project: \`netlify deploy\`
42-
43- ## AI Features
44-
45- - Automated code analysis
46- - Smart deployment optimizations
47- - Performance monitoring
48- - Error detection and suggestions
49-
50- ## Next Steps
19+ interface AIStartOptions extends OptionValues {
20+ debug ?: boolean
21+ }
5122
52- - Configure your build settings
53- - Set up environment variables
54- - Explore the AI dashboard for insights
23+ // Move helper functions to a separate utils file
24+ const decodeHash = ( hash : string ) : string => {
25+ try {
26+ return atob ( hash )
27+ } catch ( error ) {
28+ throw new Error ( `Failed to decode hash: ${ error instanceof Error ? error . message : 'Invalid base64 or URL' } ` )
29+ }
30+ }
5531
56- Happy coding! 🚀`
32+ const fetchProjectInfo = async ( url : string ) : Promise < ProjectInfo > => {
33+ try {
34+ const response = await fetch ( url , {
35+ headers : {
36+ 'content-type' : 'text/plain' ,
37+ } ,
38+ } )
39+
40+ if ( ! response . ok ) {
41+ throw new Error ( `HTTP error! status: ${ String ( response . status ) } ` )
5742 }
43+ const data = ( await response . text ( ) ) as unknown as string
44+ const parsedData = JSON . parse ( data ) as unknown as ProjectInfo
45+ return parsedData
5846 } catch ( error ) {
5947 throw new Error ( `Failed to fetch project information: ${ error instanceof Error ? error . message : 'Unknown error' } ` )
6048 }
6149}
6250
63- // Get repository URL from project ID using existing site API functionality
64- const getRepoUrlFromProjectId = async ( api : any , projectId : string ) : Promise < string > => {
51+ const getRepoUrlFromProjectId = async ( api : NetlifyAPI , projectId : string ) : Promise < string > => {
6552 try {
66- // Use project ID as site ID to get site data
67- const siteData = await api . getSite ( { siteId : projectId } )
68-
69- const repoUrl = siteData . build_settings ?. repo_url
70-
53+ const SiteInfo = ( await api . getSite ( { siteId : projectId } ) ) as SiteInfo
54+ const repoUrl = SiteInfo . build_settings ?. repo_url
55+
7156 if ( ! repoUrl ) {
7257 throw new Error ( `No repository URL found for project ID: ${ projectId } ` )
7358 }
74-
59+
7560 return repoUrl
76-
77- } catch ( error : any ) {
78- if ( error . status === 404 ) {
61+ } catch ( error ) {
62+ if ( ( error as APIError ) . status === 404 ) {
7963 throw new Error ( `Project with ID ${ projectId } not found` )
8064 }
81- throw new Error ( `Failed to fetch project data: ${ error . message } ` )
65+ throw new Error ( `Failed to fetch project data: ${ ( error as Error ) . message } ` )
8266 }
8367}
8468
85- // Save AI instructions to markdown file
86- const saveAiInstructions = async ( instructions : string , targetDir : string ) : Promise < void > => {
87- const fs = await import ( 'node:fs/promises' )
88- const path = await import ( 'node:path' )
89-
69+ const saveprompt = async ( instructions : string , targetDir : string ) : Promise < void > => {
9070 try {
91- const filePath = path . resolve ( targetDir , 'AI-instructions.md' )
71+ const filePath = resolve ( targetDir , 'AI-instructions.md' )
9272 await fs . writeFile ( filePath , instructions , 'utf-8' )
9373 log ( `${ chalk . green ( '✅' ) } AI instructions saved to ${ chalk . cyan ( 'AI-instructions.md' ) } ` )
9474 } catch ( error ) {
@@ -105,7 +85,7 @@ const cloneRepo = async (repoUrl: string, targetDir: string, debug: boolean): Pr
10585 }
10686}
10787
108- export const aiStartCommand = async ( options : OptionValues , command : BaseCommand ) => {
88+ export const aiStartCommand = async ( options : AIStartOptions , command : BaseCommand ) : Promise < void > => {
10989 const hash = command . args [ 0 ]
11090
11191 // Validate hash parameter
@@ -115,104 +95,65 @@ export const aiStartCommand = async (options: OptionValues, command: BaseCommand
11595 return
11696 }
11797
118- // Check authentication - this will automatically handle login if needed
98+ // Authenticate first before any API operations
11999 await command . authenticate ( )
120-
121100 const { api } = command . netlify
122101
123102 log ( `${ chalk . blue ( '🤖 AI Start' ) } - Initializing AI project...` )
124103 log ( `${ chalk . gray ( 'Hash:' ) } ${ hash } ` )
125104 log ( `${ chalk . gray ( 'User:' ) } ${ api . accessToken ? 'Authenticated ✅' : 'Not authenticated ❌' } ` )
126105
127- // Step 1: Decode hash and fetch project information
128- log ( '\n📋 Decoding project hash...' )
129- const decodedUrl = decodeHash ( hash )
130- log ( `${ chalk . cyan ( 'Decoded URL:' ) } ${ decodedUrl } ` )
131-
132- log ( '\n🔍 Fetching project information...' )
133- let projectInfo : ProjectInfo
134106 try {
135- projectInfo = await fetchProjectInfo ( decodedUrl , api . accessToken ?? '' )
136- } catch ( error ) {
137- const errorMessage = error instanceof Error ? error . message : 'Unknown error'
138- log ( chalk . red ( '❌ Error:' ) , errorMessage )
139- return
140- }
107+ // Step 1: Decode hash and fetch project information
108+ log ( '\n📋 Decoding project hash...' )
109+ const decodedUrl = decodeHash ( hash )
110+ log ( `${ chalk . cyan ( 'Decoded URL:' ) } ${ decodedUrl } ` )
141111
142- if ( ! projectInfo . success ) {
143- log ( chalk . red ( '❌ Failed to fetch project information' ) )
144- return
145- }
112+ log ( '\n🔍 Fetching project information...' )
113+ const projectInfo = await fetchProjectInfo ( decodedUrl )
146114
147- log ( `${ chalk . cyan ( 'Project ID:' ) } ${ projectInfo . projectId } ` )
115+ log ( `${ chalk . cyan ( 'Project ID:' ) } ${ projectInfo . projectId } ` )
148116
149- // Step 2: Get repository URL from project ID via Netlify site API
150- log ( '\n🔗 Linking to Netlify site and fetching repository...' )
151- let repositoryUrl : string
152- try {
153- repositoryUrl = await getRepoUrlFromProjectId ( api , projectInfo . projectId )
117+ // Step 2: Get repository URL from project ID via Netlify site API
118+ log ( '\n🔗 Linking to Netlify site and fetching repository...' )
119+ const repositoryUrl = await getRepoUrlFromProjectId ( api , projectInfo . projectId )
154120 log ( `${ chalk . cyan ( 'Repository:' ) } ${ repositoryUrl } ` )
155- } catch ( error ) {
156- const errorMessage = error instanceof Error ? error . message : 'Unknown error'
157- log ( chalk . red ( '❌ Error:' ) , errorMessage )
158- return
159- }
160121
161- // Step 3: Clone repository using existing functionality
162- try {
122+ // Step 3: Clone repository
163123 const { repoUrl, repoName } = normalizeRepoUrl ( repositoryUrl )
164124 const targetDir = `ai-project-${ repoName } -${ hash . substring ( 0 , 8 ) } `
165125
166126 const cloneSpinner = startSpinner ( { text : `Cloning repository to ${ chalk . cyan ( targetDir ) } ` } )
167-
168- try {
169- await cloneRepo ( repoUrl , targetDir , Boolean ( options . debug ) )
170- cloneSpinner . success ( { text : `Cloned repository to ${ chalk . cyan ( targetDir ) } ` } )
171- } catch ( error ) {
172- cloneSpinner . error ( )
173- return logAndThrowError ( error )
174- }
175127
176- // Update working directory to cloned repo
177- command . workingDir = targetDir
178- process . chdir ( targetDir )
128+ await cloneRepo ( repoUrl , targetDir , Boolean ( options . debug ) )
129+ cloneSpinner . success ( { text : `Cloned repository to ${ chalk . cyan ( targetDir ) } ` } )
179130
180131 // Step 4: Save AI instructions to file
181- if ( projectInfo . aiInstructions ) {
132+ if ( projectInfo . prompt ) {
182133 log ( '\n📝 Saving AI instructions...' )
183- // Use command working directory which is now set to the cloned repo
184- await saveAiInstructions ( projectInfo . aiInstructions , command . workingDir )
134+ await saveprompt ( projectInfo . prompt , targetDir )
185135 }
186136
137+ // Update working directory to cloned repo
138+ process . chdir ( targetDir )
139+ command . workingDir = targetDir
187140 // Success message with next steps
188141 log ( )
189142 log ( chalk . green ( '✔ Your AI project is ready to go!' ) )
190143 log ( `→ Project ID: ${ chalk . cyanBright ( projectInfo . projectId ) } ` )
191144 log ( `→ Project cloned to: ${ chalk . cyanBright ( targetDir ) } ` )
192- if ( projectInfo . aiInstructions ) {
145+ if ( projectInfo . prompt ) {
193146 log ( `→ AI instructions saved: ${ chalk . cyanBright ( 'AI-instructions.md' ) } ` )
194147 }
195148 log ( )
196149 log ( chalk . yellowBright ( `📁 Step 1: Enter your project directory` ) )
197150 log ( ` ${ chalk . cyanBright ( `cd ${ targetDir } ` ) } ` )
198151 log ( )
199- if ( projectInfo . aiInstructions ) {
152+ if ( projectInfo . prompt ) {
200153 log ( chalk . yellowBright ( `🤖 Step 2: Ask your AI assistant to process the instructions` ) )
201- log ( ` ${ chalk . gray ( 'Tell your AI:' ) } ${ chalk . cyanBright ( '"Please read and follow the AI-instructions.md file"' ) } ` )
202- log ( )
154+ log ( ` ${ chalk . cyanBright ( `follow instructions in ${ targetDir } /AI-instructions.md` ) } ` )
203155 }
204- log ( chalk . yellowBright ( `🚀 Step 3: Start development` ) )
205- log ( ` ${ chalk . cyanBright ( 'netlify dev' ) } ${ chalk . gray ( '- Start local development server' ) } ` )
206- log ( ` ${ chalk . cyanBright ( 'netlify deploy' ) } ${ chalk . gray ( '- Deploy your project' ) } ` )
207- log ( )
208- log ( chalk . gray ( `💡 Pro tip: Your AI assistant can help you understand and implement` ) )
209- log ( chalk . gray ( ` the project-specific instructions in AI-instructions.md` ) )
210- log ( )
211-
212156 } catch ( error ) {
213- const errorMessage = error instanceof Error ? error . message : 'Unknown error occurred'
214-
215- log ( chalk . red ( '❌ Error:' ) , errorMessage )
216- log ( chalk . gray ( 'Please try again or contact support if the issue persists.' ) )
157+ return logAndThrowError ( error )
217158 }
218- }
159+ }
0 commit comments