@@ -18,32 +18,58 @@ import (
18
18
"context"
19
19
"fmt"
20
20
"os"
21
+ "os/exec"
22
+ "path/filepath"
21
23
"strings"
24
+ "time"
22
25
23
26
"github.com/AlecAivazis/survey/v2"
27
+ "github.com/briandowns/spinner"
24
28
"github.com/go-git/go-git/v5"
25
29
"github.com/lainio/err2"
26
30
"github.com/lainio/err2/try"
27
31
"github.com/lekkodev/cli/pkg/dotlekko"
28
32
"github.com/lekkodev/cli/pkg/gen"
33
+ "github.com/lekkodev/cli/pkg/logging"
29
34
"github.com/lekkodev/cli/pkg/native"
30
35
"github.com/lekkodev/cli/pkg/repo"
31
36
"github.com/pkg/errors"
32
37
"github.com/spf13/cobra"
33
38
)
34
39
40
+ type projectFramework int
41
+
42
+ const (
43
+ pfUnknown projectFramework = iota
44
+ pfGo
45
+ pfNode
46
+ pfReact
47
+ pfVite
48
+ pfNext
49
+ )
50
+
51
+ type packageManager string
52
+
53
+ const (
54
+ pmUnknown packageManager = ""
55
+ pmNPM packageManager = "npm"
56
+ pmYarn packageManager = "yarn"
57
+ )
58
+
35
59
func initCmd () * cobra.Command {
36
60
var lekkoPath , repoName string
37
61
cmd := & cobra.Command {
38
62
Use : "init" ,
39
63
Short : "initialize Lekko in your project" ,
40
64
RunE : func (cmd * cobra.Command , args []string ) (err error ) {
41
65
defer err2 .Handle (& err )
66
+ successCheck := logging .Green ("\u2713 " )
67
+ spin := spinner .New (spinner .CharSets [14 ], 100 * time .Millisecond )
42
68
// TODO:
43
69
// + create .lekko file
44
70
// + generate from `default` namespace
45
- // - install lekko deps (depending on project type)
46
- // - setup github actions
71
+ // + install lekko deps (depending on project type)
72
+ // + setup github actions
47
73
// - install linter
48
74
_ , err = dotlekko .ReadDotLekko ("" )
49
75
if err == nil {
@@ -53,23 +79,45 @@ func initCmd() *cobra.Command {
53
79
// TODO: print some info
54
80
55
81
// naive check for "known" project types
56
- isGo := false
57
- isNode := false
82
+ // TODO: Consolidate into DetectNativeLang
83
+ pf := pfUnknown
84
+ pm := pmUnknown
58
85
if _ , err = os .Stat ("go.mod" ); err == nil {
59
- isGo = true
86
+ pf = pfGo
60
87
} else if _ , err = os .Stat ("package.json" ); err == nil {
61
- isNode = true
88
+ pf = pfNode
89
+ pjBytes , err := os .ReadFile ("package.json" )
90
+ if err != nil {
91
+ return errors .Wrap (err , "failed to open package.json" )
92
+ }
93
+ pjString := string (pjBytes )
94
+ if strings .Contains (pjString , "react-dom" ) {
95
+ pf = pfReact
96
+ }
97
+ // Vite config file could be js, cjs, mjs, etc.
98
+ if matches , err := filepath .Glob ("vite.config.*" ); matches != nil && err == nil {
99
+ pf = pfVite
100
+ }
101
+ // Next config file could be js, cjs, mjs, etc.
102
+ if matches , err := filepath .Glob ("next.config.*" ); matches != nil && err == nil {
103
+ pf = pfNext
104
+ }
105
+
106
+ pm = pmNPM
107
+ if _ , err := os .Stat ("yarn.lock" ); err == nil {
108
+ pm = pmYarn
109
+ }
62
110
}
63
- if ! isGo && ! isNode {
111
+ if pf == pfUnknown {
64
112
return errors .New ("Unknown project type, Lekko currently supports Go and NPM projects." )
65
113
}
66
114
67
115
if lekkoPath == "" {
68
116
lekkoPath = "lekko"
69
- if fi , err := os .Stat ("src" ); err == nil && fi .IsDir () && isNode {
117
+ if fi , err := os .Stat ("src" ); err == nil && fi .IsDir () {
70
118
lekkoPath = "src/lekko"
71
119
}
72
- if fi , err := os .Stat ("internal" ); err == nil && fi .IsDir () && isGo {
120
+ if fi , err := os .Stat ("internal" ); err == nil && fi .IsDir () && pf == pfGo {
73
121
lekkoPath = "internal/lekko"
74
122
}
75
123
try .To (survey .AskOne (& survey.Input {
@@ -109,16 +157,169 @@ func initCmd() *cobra.Command {
109
157
repoName = fmt .Sprintf ("%s/lekko-configs" , owner )
110
158
}
111
159
try .To (survey .AskOne (& survey.Input {
112
- Message : "Config repository name, for example `my-org/lekko-configs`:" ,
160
+ Message : "Lekko repository name, for example `my-org/lekko-configs`:" ,
113
161
Default : repoName ,
162
+ Help : "If you set up your team on app.lekko.com, you can find your Lekko repository by logging in." ,
114
163
}, & repoName ))
115
164
}
116
165
117
166
dot := dotlekko .NewDotLekko (lekkoPath , repoName )
118
167
try .To (dot .WriteBack ())
168
+
169
+ // Add GitHub workflow file
170
+ var addWorkflow bool
171
+ if err := survey .AskOne (& survey.Confirm {
172
+ Message : "Add GitHub workflow file at .github/workflows/lekko.yaml?" ,
173
+ Default : true ,
174
+ Help : "This workflow will use the Lekko Push Action, which enables the automatic mirrorring feature." ,
175
+ }, & addWorkflow ); err != nil {
176
+ return err
177
+ }
178
+ if addWorkflow {
179
+ if err := os .MkdirAll (".github/workflows" , os .ModePerm ); err != nil {
180
+ return errors .Wrap (err , "failed to mkdir .github/workflows" )
181
+ }
182
+ workflowTemplate := getGitHubWorkflowTemplateBase ()
183
+ if suffix , err := getGitHubWorkflowTemplateSuffix (pf , pm ); err != nil {
184
+ return err
185
+ } else {
186
+ workflowTemplate += suffix
187
+ }
188
+ if err := os .WriteFile (".github/workflows/lekko.yaml" , []byte (workflowTemplate ), 0600 ); err != nil {
189
+ return errors .Wrap (err , "failed to write Lekko workflow file" )
190
+ }
191
+ // TODO: Consider moving instructions to end?
192
+ fmt .Printf ("%s Successfully added .github/workflows/lekko.yaml, please make sure to add LEKKO_API_KEY as a secret in your GitHub repository/org settings.\n " , successCheck )
193
+ }
194
+
195
+ // TODO: Install deps depending on project type
196
+ // TODO: Determine package manager (npm/yarn/pnpm/etc.) for ts projects
197
+ spin .Suffix = " Installing dependencies..."
198
+ spin .Start ()
199
+ switch pf {
200
+ case pfGo :
201
+ {
202
+ goGetCmd := exec .Command ("go" , "get" , "github.com/lekkodev/go-sdk@latest" )
203
+ if out , err := goGetCmd .CombinedOutput (); err != nil {
204
+ spin .Stop ()
205
+ fmt .Println (goGetCmd .String ())
206
+ fmt .Println (string (out ))
207
+ return errors .Wrap (err , "failed to run go get" )
208
+ }
209
+ spin .Stop ()
210
+ fmt .Printf ("%s Successfully installed Lekko Go SDK.\n " , successCheck )
211
+ spin .Start ()
212
+ }
213
+ case pfVite :
214
+ // NOTE: Vite doesn't necessarily mean React but we assume for now
215
+ {
216
+ var installArgs , installDevArgs []string
217
+ switch pm {
218
+ case pmNPM :
219
+ {
220
+ installArgs = []string {"install" , "@lekko/react-sdk" }
221
+ installDevArgs = []string {"install" , "-D" , "@lekko/vite-plugin" , "@lekko/eslint-plugin" }
222
+ }
223
+ case pmYarn :
224
+ {
225
+ installArgs = []string {"add" , "@lekko/react-sdk" }
226
+ installDevArgs = []string {"add" , "-D" , "@lekko/vite-plugin" , "@lekko/eslint-plugin" }
227
+ }
228
+ default :
229
+ {
230
+ return errors .Errorf ("unsupported package manager %s" , pm )
231
+ }
232
+ }
233
+ installCmd := exec .Command (string (pm ), installArgs ... ) // #nosec G204
234
+ if out , err := installCmd .CombinedOutput (); err != nil {
235
+ spin .Stop ()
236
+ fmt .Println (installCmd .String ())
237
+ fmt .Println (string (out ))
238
+ return errors .Wrap (err , "failed to run install deps command" )
239
+ }
240
+ spin .Stop ()
241
+ fmt .Printf ("%s Successfully installed @lekko/react-sdk.\n " , successCheck )
242
+ spin .Start ()
243
+ installCmd = exec .Command (string (pm ), installDevArgs ... ) // #nosec G204
244
+ if out , err := installCmd .CombinedOutput (); err != nil {
245
+ spin .Stop ()
246
+ fmt .Println (installCmd .String ())
247
+ fmt .Println (string (out ))
248
+ return errors .Wrap (err , "failed to run install dev deps command" )
249
+ }
250
+ spin .Stop ()
251
+ fmt .Printf ("%s Successfully installed @lekko/vite-plugin and @lekko/eslint-plugin. See the docs to configure these plugins.\n " , successCheck )
252
+ spin .Start ()
253
+ }
254
+ case pfNext :
255
+ {
256
+ var installArgs , installDevArgs []string
257
+ switch pm {
258
+ case pmNPM :
259
+ {
260
+ installArgs = []string {"install" , "@lekko/next-sdk" }
261
+ installDevArgs = []string {"install" , "-D" , "@lekko/eslint-plugin" }
262
+ }
263
+ case pmYarn :
264
+ {
265
+ installArgs = []string {"add" , "@lekko/next-sdk" }
266
+ installDevArgs = []string {"add" , "-D" , "@lekko/eslint-plugin" }
267
+ }
268
+ default :
269
+ {
270
+ return errors .Errorf ("unsupported package manager %s" , pm )
271
+ }
272
+ }
273
+ installCmd := exec .Command (string (pm ), installArgs ... ) // #nosec G204
274
+ if out , err := installCmd .CombinedOutput (); err != nil {
275
+ spin .Stop ()
276
+ fmt .Println (installCmd .String ())
277
+ fmt .Println (string (out ))
278
+ return errors .Wrap (err , "failed to run install deps command" )
279
+ }
280
+ spin .Stop ()
281
+ fmt .Printf ("%s Successfully installed @lekko/next-sdk. See the docs to configure the SDK.\n " , successCheck )
282
+ spin .Start ()
283
+ installCmd = exec .Command (string (pm ), installDevArgs ... ) // #nosec G204
284
+ if out , err := installCmd .CombinedOutput (); err != nil {
285
+ spin .Stop ()
286
+ fmt .Println (installCmd .String ())
287
+ fmt .Println (string (out ))
288
+ return errors .Wrap (err , "failed to run install dev deps command" )
289
+ }
290
+ spin .Stop ()
291
+ fmt .Printf ("%s Successfully installed @lekko/eslint-plugin. See the docs to configure this plugin.\n " , successCheck )
292
+ spin .Start ()
293
+ }
294
+ }
295
+ spin .Stop ()
296
+
297
+ // Codegen
298
+ spin .Suffix = " Running codegen..."
299
+ spin .Start ()
119
300
// TODO: make sure that `default` namespace exists
120
301
try .To (runGen (cmd .Context (), lekkoPath , "default" ))
302
+ spin .Stop ()
121
303
304
+ // Post-gen steps
305
+ spin .Suffix = " Running post-codegen steps..."
306
+ spin .Start ()
307
+ switch pf {
308
+ case pfGo :
309
+ {
310
+ // For Go we want to run `go mod tidy` - this handles transitive deps
311
+ goTidyCmd := exec .Command ("go" , "mod" , "tidy" )
312
+ if out , err := goTidyCmd .CombinedOutput (); err != nil {
313
+ spin .Stop ()
314
+ fmt .Println (goTidyCmd .String ())
315
+ fmt .Println (string (out ))
316
+ return errors .Wrap (err , "failed to run go mod tidy" )
317
+ }
318
+ }
319
+ }
320
+ spin .Stop ()
321
+
322
+ fmt .Printf ("%s Complete! Your project is now set up to use Lekko.\n " , successCheck )
122
323
return nil
123
324
},
124
325
}
@@ -136,3 +337,75 @@ func runGen(ctx context.Context, lekkoPath, ns string) (err error) {
136
337
Namespaces : []string {ns },
137
338
})
138
339
}
340
+
341
+ func getGitHubWorkflowTemplateBase () string {
342
+ // TODO: determine default branch name (might not be main)
343
+ return `name: lekko
344
+ on:
345
+ pull_request:
346
+ branches:
347
+ - main
348
+ push:
349
+ branches:
350
+ - main
351
+ permissions:
352
+ contents: read
353
+ jobs:
354
+ build:
355
+ runs-on: ubuntu-latest
356
+ steps:
357
+ - uses: actions/checkout@v4
358
+ `
359
+ }
360
+
361
+ func getGitHubWorkflowTemplateSuffix (pf projectFramework , pm packageManager ) (string , error ) {
362
+ // NOTE: Make sure to keep the indentation matched with base
363
+ var ret string
364
+ switch pf {
365
+ case pfGo :
366
+ {
367
+ ret = ` - uses: actions/setup-go@v5
368
+ with:
369
+ go-version-file: go.mod
370
+ `
371
+ }
372
+ case pfNode :
373
+ fallthrough
374
+ case pfReact :
375
+ fallthrough
376
+ case pfVite :
377
+ fallthrough
378
+ case pfNext :
379
+ {
380
+ ret = ` - uses: actions/setup-node@v4
381
+ with:
382
+ node-version: lts/Hydrogen
383
+ `
384
+ switch pm {
385
+ case pmNPM :
386
+ {
387
+ ret += ` - run: npm install
388
+ `
389
+ }
390
+ case pmYarn :
391
+ {
392
+ ret += ` cache: yarn
393
+ - run: yarn install
394
+ `
395
+ }
396
+ default :
397
+ return "" , errors .New ("unsupported package manager for GitHub workflow setup" )
398
+ }
399
+ }
400
+ // TODO: For TS projects need to detect package manager
401
+ default :
402
+ {
403
+ return "" , errors .New ("unsupported framework for GitHub workflow setup" )
404
+ }
405
+ }
406
+ ret += ` - uses: lekkodev/push-action@v1
407
+ with:
408
+ api_key: ${{ secrets.LEKKO_API_KEY }}
409
+ `
410
+ return ret , nil
411
+ }
0 commit comments