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'
2
5
3
- import { chalk , log , logAndThrowError } from '../../utils/command-helpers.js'
6
+ import { chalk , log , logAndThrowError , type APIError } from '../../utils/command-helpers.js'
4
7
import { normalizeRepoUrl } from '../../utils/normalize-repo-url.js'
5
8
import { runGit } from '../../utils/run-git.js'
6
9
import { 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'
15
12
16
13
interface ProjectInfo {
17
14
success : boolean
18
15
projectId : string
19
- aiInstructions : string
16
+ prompt : string
20
17
}
21
18
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
+ }
51
22
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
+ }
55
31
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 ) } ` )
57
42
}
43
+ const data = ( await response . text ( ) ) as unknown as string
44
+ const parsedData = JSON . parse ( data ) as unknown as ProjectInfo
45
+ return parsedData
58
46
} catch ( error ) {
59
47
throw new Error ( `Failed to fetch project information: ${ error instanceof Error ? error . message : 'Unknown error' } ` )
60
48
}
61
49
}
62
50
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 > => {
65
52
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
+
71
56
if ( ! repoUrl ) {
72
57
throw new Error ( `No repository URL found for project ID: ${ projectId } ` )
73
58
}
74
-
59
+
75
60
return repoUrl
76
-
77
- } catch ( error : any ) {
78
- if ( error . status === 404 ) {
61
+ } catch ( error ) {
62
+ if ( ( error as APIError ) . status === 404 ) {
79
63
throw new Error ( `Project with ID ${ projectId } not found` )
80
64
}
81
- throw new Error ( `Failed to fetch project data: ${ error . message } ` )
65
+ throw new Error ( `Failed to fetch project data: ${ ( error as Error ) . message } ` )
82
66
}
83
67
}
84
68
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 > => {
90
70
try {
91
- const filePath = path . resolve ( targetDir , 'AI-instructions.md' )
71
+ const filePath = resolve ( targetDir , 'AI-instructions.md' )
92
72
await fs . writeFile ( filePath , instructions , 'utf-8' )
93
73
log ( `${ chalk . green ( '✅' ) } AI instructions saved to ${ chalk . cyan ( 'AI-instructions.md' ) } ` )
94
74
} catch ( error ) {
@@ -105,7 +85,7 @@ const cloneRepo = async (repoUrl: string, targetDir: string, debug: boolean): Pr
105
85
}
106
86
}
107
87
108
- export const aiStartCommand = async ( options : OptionValues , command : BaseCommand ) => {
88
+ export const aiStartCommand = async ( options : AIStartOptions , command : BaseCommand ) : Promise < void > => {
109
89
const hash = command . args [ 0 ]
110
90
111
91
// Validate hash parameter
@@ -115,104 +95,65 @@ export const aiStartCommand = async (options: OptionValues, command: BaseCommand
115
95
return
116
96
}
117
97
118
- // Check authentication - this will automatically handle login if needed
98
+ // Authenticate first before any API operations
119
99
await command . authenticate ( )
120
-
121
100
const { api } = command . netlify
122
101
123
102
log ( `${ chalk . blue ( '🤖 AI Start' ) } - Initializing AI project...` )
124
103
log ( `${ chalk . gray ( 'Hash:' ) } ${ hash } ` )
125
104
log ( `${ chalk . gray ( 'User:' ) } ${ api . accessToken ? 'Authenticated ✅' : 'Not authenticated ❌' } ` )
126
105
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
134
106
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 } ` )
141
111
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 )
146
114
147
- log ( `${ chalk . cyan ( 'Project ID:' ) } ${ projectInfo . projectId } ` )
115
+ log ( `${ chalk . cyan ( 'Project ID:' ) } ${ projectInfo . projectId } ` )
148
116
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 )
154
120
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
- }
160
121
161
- // Step 3: Clone repository using existing functionality
162
- try {
122
+ // Step 3: Clone repository
163
123
const { repoUrl, repoName } = normalizeRepoUrl ( repositoryUrl )
164
124
const targetDir = `ai-project-${ repoName } -${ hash . substring ( 0 , 8 ) } `
165
125
166
126
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
- }
175
127
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 ) } ` } )
179
130
180
131
// Step 4: Save AI instructions to file
181
- if ( projectInfo . aiInstructions ) {
132
+ if ( projectInfo . prompt ) {
182
133
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 )
185
135
}
186
136
137
+ // Update working directory to cloned repo
138
+ process . chdir ( targetDir )
139
+ command . workingDir = targetDir
187
140
// Success message with next steps
188
141
log ( )
189
142
log ( chalk . green ( '✔ Your AI project is ready to go!' ) )
190
143
log ( `→ Project ID: ${ chalk . cyanBright ( projectInfo . projectId ) } ` )
191
144
log ( `→ Project cloned to: ${ chalk . cyanBright ( targetDir ) } ` )
192
- if ( projectInfo . aiInstructions ) {
145
+ if ( projectInfo . prompt ) {
193
146
log ( `→ AI instructions saved: ${ chalk . cyanBright ( 'AI-instructions.md' ) } ` )
194
147
}
195
148
log ( )
196
149
log ( chalk . yellowBright ( `📁 Step 1: Enter your project directory` ) )
197
150
log ( ` ${ chalk . cyanBright ( `cd ${ targetDir } ` ) } ` )
198
151
log ( )
199
- if ( projectInfo . aiInstructions ) {
152
+ if ( projectInfo . prompt ) {
200
153
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` ) } ` )
203
155
}
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
-
212
156
} 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 )
217
158
}
218
- }
159
+ }
0 commit comments