Skip to content

Commit ec95290

Browse files
committed
fix: import schema and config sheet from files
1 parent 52b0d07 commit ec95290

File tree

12 files changed

+180
-134
lines changed

12 files changed

+180
-134
lines changed

internal/command/apply.go

Lines changed: 24 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22
package command
33

44
import (
5-
"bufio"
65
"fmt"
76
"os"
87
"os/exec"
9-
"path/filepath"
108
"runtime"
119
"strings"
1210

1311
"github.com/spf13/cobra"
1412

13+
"github.com/n1rna/ee-cli/internal/config"
1514
"github.com/n1rna/ee-cli/internal/entities"
1615
"github.com/n1rna/ee-cli/internal/output"
16+
"github.com/n1rna/ee-cli/internal/parser"
1717
"github.com/n1rna/ee-cli/internal/util"
1818
)
1919

@@ -100,7 +100,7 @@ func (c *ApplyCommand) Run(cmd *cobra.Command, args []string) error {
100100
var values map[string]string
101101

102102
// Detect if the argument is a file path or environment name
103-
if c.isFilePath(envOrSheetName) {
103+
if entities.IsFilePath(envOrSheetName) {
104104
// Apply .env file directly
105105
values, err = c.applyEnvFile(envOrSheetName)
106106
if err != nil {
@@ -174,7 +174,10 @@ func (c *ApplyCommand) applyProjectEnvironment(
174174
) (map[string]string, error) {
175175
// Check if we're in a project context
176176
if !context.IsInProject {
177-
return nil, fmt.Errorf("no .ee file found - not in a project context")
177+
return nil, fmt.Errorf(
178+
"no %s file found - not in a project context",
179+
config.ProjectConfigFileName,
180+
)
178181
}
179182

180183
// Validate that the environment exists
@@ -202,6 +205,23 @@ func (c *ApplyCommand) applyProjectEnvironment(
202205
return values, nil
203206
}
204207

208+
// applyEnvFile reads and parses a .env file using the parser.ParseFile function
209+
func (c *ApplyCommand) applyEnvFile(filePath string) (map[string]string, error) {
210+
// Check if file exists
211+
if _, err := os.Stat(filePath); os.IsNotExist(err) {
212+
return nil, fmt.Errorf(".env file not found: %s", filePath)
213+
}
214+
215+
// Use the parser to parse the .env file
216+
p := parser.NewAnnotatedDotEnvParser()
217+
values, _, err := p.ParseFile(filePath)
218+
if err != nil {
219+
return nil, fmt.Errorf("failed to parse .env file: %w", err)
220+
}
221+
222+
return values, nil
223+
}
224+
205225
// runCommandWithEnvironment runs a command with the specified environment variables
206226
func (c *ApplyCommand) runCommandWithEnvironment(
207227
values map[string]string,
@@ -284,92 +304,3 @@ func (c *ApplyCommand) startShellWithEnvironment(
284304

285305
return nil
286306
}
287-
288-
// isFilePath detects if the argument is a file path rather than an environment name
289-
// Returns true if the argument starts with '.', '/', '~', or contains a file extension
290-
func (c *ApplyCommand) isFilePath(arg string) bool {
291-
// Check if it's a relative path starting with '.' or current directory
292-
if strings.HasPrefix(arg, ".") {
293-
return true
294-
}
295-
296-
// Check if it's an absolute path starting with '/' or '~'
297-
if strings.HasPrefix(arg, "/") || strings.HasPrefix(arg, "~") {
298-
return true
299-
}
300-
301-
// Check if it contains a file extension
302-
if filepath.Ext(arg) != "" {
303-
return true
304-
}
305-
306-
// Check if the file actually exists
307-
if _, err := os.Stat(arg); err == nil {
308-
return true
309-
}
310-
311-
return false
312-
}
313-
314-
// applyEnvFile reads and parses a .env file
315-
func (c *ApplyCommand) applyEnvFile(filePath string) (map[string]string, error) {
316-
// Check if file exists
317-
if _, err := os.Stat(filePath); os.IsNotExist(err) {
318-
return nil, fmt.Errorf(".env file not found: %s", filePath)
319-
}
320-
321-
// Open file
322-
file, err := os.Open(filePath)
323-
if err != nil {
324-
return nil, fmt.Errorf("failed to open .env file: %w", err)
325-
}
326-
defer func() {
327-
if err := file.Close(); err != nil {
328-
// Log error but don't override the main error
329-
}
330-
}()
331-
332-
values := make(map[string]string)
333-
scanner := bufio.NewScanner(file)
334-
lineNum := 0
335-
336-
for scanner.Scan() {
337-
lineNum++
338-
line := strings.TrimSpace(scanner.Text())
339-
340-
// Skip empty lines and comments
341-
if line == "" || strings.HasPrefix(line, "#") {
342-
continue
343-
}
344-
345-
// Parse KEY=VALUE format
346-
parts := strings.SplitN(line, "=", 2)
347-
if len(parts) != 2 {
348-
return nil, fmt.Errorf("invalid line %d in .env file: %s", lineNum, line)
349-
}
350-
351-
key := strings.TrimSpace(parts[0])
352-
value := strings.TrimSpace(parts[1])
353-
354-
// Remove surrounding quotes if present
355-
if len(value) >= 2 {
356-
if (strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"")) ||
357-
(strings.HasPrefix(value, "'") && strings.HasSuffix(value, "'")) {
358-
value = value[1 : len(value)-1]
359-
}
360-
}
361-
362-
// Validate key name
363-
if key == "" {
364-
return nil, fmt.Errorf("empty variable name on line %d", lineNum)
365-
}
366-
367-
values[key] = value
368-
}
369-
370-
if err := scanner.Err(); err != nil {
371-
return nil, fmt.Errorf("error reading .env file: %w", err)
372-
}
373-
374-
return values, nil
375-
}

internal/command/init.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/spf13/cobra"
1111

12+
"github.com/n1rna/ee-cli/internal/config"
1213
"github.com/n1rna/ee-cli/internal/entities"
1314
"github.com/n1rna/ee-cli/internal/output"
1415
"github.com/n1rna/ee-cli/internal/parser"
@@ -51,12 +52,12 @@ Examples:
5152
}
5253

5354
cmd.Flags().
54-
String("schema", "", "Schema reference to use (local://schema-name or remote://path)")
55-
cmd.Flags().String("remote", "", "Remote URL for synchronization")
55+
StringP("schema", "s", "", "Schema reference to use (local://schema-name or remote://path)")
56+
cmd.Flags().StringP("remote", "r", "", "Remote URL for synchronization")
5657
cmd.Flags().
5758
StringSlice("var", []string{}, "Add schema variable (format:name:type:title:required:default)")
58-
cmd.Flags().Bool("force", false, "Overwrite existing .ee file")
59-
cmd.Flags().Bool("quiet", false, "Suppress non-error output")
59+
cmd.Flags().BoolP("force", "f", false, "Overwrite existing .ee file")
60+
cmd.Flags().BoolP("quiet", "q", false, "Suppress non-error output")
6061

6162
return cmd
6263
}
@@ -88,9 +89,12 @@ func (c *InitCommand) Run(cmd *cobra.Command, args []string) error {
8889
}
8990

9091
// Check if .ee file already exists
91-
eeFile := ".ee"
92+
eeFile := config.ProjectConfigFileName
9293
if _, err := os.Stat(eeFile); err == nil && !force {
93-
return fmt.Errorf(".ee file already exists (use --force to overwrite)")
94+
return fmt.Errorf(
95+
"%s file already exists (use --force to overwrite)",
96+
config.ProjectConfigFileName,
97+
)
9498
}
9599

96100
// Build schema configuration
@@ -117,7 +121,7 @@ func (c *InitCommand) Run(cmd *cobra.Command, args []string) error {
117121
// Save .ee file
118122
err = parser.SaveProjectConfig(projectConfig, eeFile)
119123
if err != nil {
120-
return fmt.Errorf("failed to save .ee file: %w", err)
124+
return fmt.Errorf("failed to save %s file: %w", config.ProjectConfigFileName, err)
121125
}
122126

123127
// Get entity manager from context for schema loading
@@ -133,7 +137,7 @@ func (c *InitCommand) Run(cmd *cobra.Command, args []string) error {
133137
}
134138

135139
printer.Success(fmt.Sprintf("✓ Initialized ee project: %s", projectName))
136-
printer.Info("✓ Created .ee configuration file")
140+
printer.Info(fmt.Sprintf("✓ Created %s configuration file", config.ProjectConfigFileName))
137141
if len(projectConfig.Environments) > 0 {
138142
printer.Info("✓ Created sample .env files for environments")
139143
}

internal/command/schema.go

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ import (
66
"encoding/json"
77
"fmt"
88
"os"
9+
"path/filepath"
910
"strings"
1011

1112
"github.com/spf13/cobra"
1213
"gopkg.in/yaml.v3"
1314

1415
"github.com/n1rna/ee-cli/internal/entities"
1516
"github.com/n1rna/ee-cli/internal/output"
17+
"github.com/n1rna/ee-cli/internal/parser"
1618
)
1719

1820
type SchemaCommand struct {
@@ -69,7 +71,7 @@ Examples:
6971
RunE: c.runCreate,
7072
}
7173

72-
cmd.Flags().String("import", "", "Import schema definition from a YAML file")
74+
cmd.Flags().String("import", "", "Import schema definition from a YAML, JSON, or dotenv file")
7375
cmd.Flags().String("description", "", "Schema description")
7476
cmd.Flags().
7577
StringSlice("variable", []string{}, "Add variable in format 'name:type:title:required[:default]'")
@@ -379,22 +381,59 @@ func (c *SchemaCommand) importSchema(
379381
name string,
380382
filename string,
381383
) error {
382-
data, err := os.ReadFile(filename)
383-
if err != nil {
384-
return fmt.Errorf("failed to read import file: %w", err)
385-
}
384+
// Detect file format based on extension
385+
ext := strings.ToLower(filepath.Ext(filename))
386386

387387
var schemaObj entities.Schema
388-
if err := yaml.Unmarshal(data, &schemaObj); err != nil {
389-
return fmt.Errorf("failed to parse schema file: %w", err)
388+
var variables []entities.Variable
389+
var description string
390+
var extends []string
391+
392+
// If it's a .env file, use the dotenv parser to extract schema
393+
if ext == ".env" || strings.Contains(strings.ToLower(filename), ".env") {
394+
p := parser.NewAnnotatedDotEnvParser()
395+
_, schema, err := p.ParseFile(filename)
396+
if err != nil {
397+
return fmt.Errorf("failed to parse .env file: %w", err)
398+
}
399+
variables = schema.Variables
400+
description = schema.Description
401+
} else {
402+
// For other files, read and try YAML/JSON
403+
data, err := os.ReadFile(filename)
404+
if err != nil {
405+
return fmt.Errorf("failed to read import file: %w", err)
406+
}
407+
408+
// Try YAML first, then JSON, then dotenv as fallback
409+
if err := yaml.Unmarshal(data, &schemaObj); err != nil {
410+
if err := json.Unmarshal(data, &schemaObj); err != nil {
411+
// Try parsing as dotenv file as fallback
412+
p := parser.NewAnnotatedDotEnvParser()
413+
_, schema, parseErr := p.ParseFile(filename)
414+
if parseErr != nil {
415+
return fmt.Errorf("file is neither valid YAML, JSON, nor dotenv format: %w", parseErr)
416+
}
417+
variables = schema.Variables
418+
description = schema.Description
419+
} else {
420+
variables = schemaObj.Variables
421+
description = schemaObj.Description
422+
extends = schemaObj.Extends
423+
}
424+
} else {
425+
variables = schemaObj.Variables
426+
description = schemaObj.Description
427+
extends = schemaObj.Extends
428+
}
390429
}
391430

392431
// Create schema using the manager
393432
s, err := manager.Schemas.Create(
394433
name,
395-
schemaObj.Description,
396-
schemaObj.Variables,
397-
schemaObj.Extends,
434+
description,
435+
variables,
436+
extends,
398437
)
399438
if err != nil {
400439
return fmt.Errorf("failed to create schema: %w", err)
@@ -458,8 +497,6 @@ func (c *SchemaCommand) runDelete(cmd *cobra.Command, args []string) error {
458497

459498
schemaName := args[0]
460499

461-
// TODO: Add dependency checking - see if schema is in use by projects/config sheets
462-
463500
if err := manager.Schemas.Delete(schemaName); err != nil {
464501
return fmt.Errorf("failed to delete schema: %w", err)
465502
}

internal/command/sheet.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ import (
66
"encoding/json"
77
"fmt"
88
"os"
9+
"path/filepath"
10+
"strings"
911

1012
"github.com/spf13/cobra"
1113
"gopkg.in/yaml.v3"
1214

1315
"github.com/n1rna/ee-cli/internal/entities"
1416
"github.com/n1rna/ee-cli/internal/output"
17+
"github.com/n1rna/ee-cli/internal/parser"
1518
)
1619

1720
// SheetCommand handles the ee sheet command
@@ -226,6 +229,8 @@ func (c *SheetCommand) runCreate(cmd *cobra.Command, args []string) error {
226229
schemaRef.Ref = "#/schemas/" + s.ID
227230
}
228231

232+
// TODO: Add logic for guessing the schema of the sheet
233+
229234
// Create standalone config sheet
230235
cs := entities.NewConfigSheet(sheetName, description, schemaRef, values)
231236

@@ -245,17 +250,37 @@ func (c *SheetCommand) runCreate(cmd *cobra.Command, args []string) error {
245250
}
246251

247252
func (c *SheetCommand) importValuesFromFile(filename string) (map[string]string, error) {
253+
// Detect file format based on extension
254+
ext := strings.ToLower(filepath.Ext(filename))
255+
256+
// If it's a .env file, use the dotenv parser
257+
if ext == ".env" || strings.Contains(strings.ToLower(filename), ".env") {
258+
p := parser.NewAnnotatedDotEnvParser()
259+
values, _, err := p.ParseFile(filename)
260+
if err != nil {
261+
return nil, fmt.Errorf("failed to parse .env file: %w", err)
262+
}
263+
return values, nil
264+
}
265+
266+
// For other files, read and try YAML/JSON
248267
data, err := os.ReadFile(filename)
249268
if err != nil {
250269
return nil, fmt.Errorf("failed to read file: %w", err)
251270
}
252271

253272
values := make(map[string]string)
254273

255-
// Try YAML first, then JSON
274+
// Try YAML first, then JSON, then dotenv as fallback
256275
if err := yaml.Unmarshal(data, &values); err != nil {
257276
if err := json.Unmarshal(data, &values); err != nil {
258-
return nil, fmt.Errorf("file is neither valid YAML nor JSON")
277+
// Try parsing as dotenv file as fallback
278+
p := parser.NewAnnotatedDotEnvParser()
279+
parsedValues, _, parseErr := p.ParseFile(filename)
280+
if parseErr != nil {
281+
return nil, fmt.Errorf("file is neither valid YAML, JSON, nor dotenv format")
282+
}
283+
return parsedValues, nil
259284
}
260285
}
261286

0 commit comments

Comments
 (0)