Repository: CreateAPI/CreateAPI (swift)

Issue:
Align behaviour of `output` directory and make `--clean` a little safer
While the usage of the `output` dir is technically consistent, it comes across a little confusing. Lets take the following examples:

This is not always the case though, because sometimes `create-api` will generate the nested directory for you:

**`create-api generate schema.json --package "FooKit" --output ./ --no-split`**:

``` 
$ tree .
.
├── FooKit
│   ├── Package.swift
│   └── Sources
│       ├── Entities.swift
│       └── Paths.swift
├── .create-api.yaml
└── schema.json
```

**`create-api generate schema.json --module "FooKit" --output ./ --no-split`**:

```
$ tree .
.
├── Entities.swift
├── Paths.swift
├── .create-api.yaml
└── schema.json
```

Writing generated code to the same directory as non-generated code is probably not a great idea in general, but when using `--package`, it seems to align with the kind of output that you might expect since a single folder with the name of the package is written into the output directory. 

This however is not the case with using `--module`. 

Making `--split` the default/recommended option now means that its more common to run into scenarios where you need to clean the generated outputs in the event that an entity or path was removed from the schema (because the next generator run wouldn't overwrite it). 

Clean is documented (and behaves) like so:

```
-c, --clean             Removes the output folder before continuing
```

Using `--clean` in the above two examples would cause the generator to unexpectedly(?) delete the entire contents of `./` including `schema.json` and `.create-api.yaml`, something I don't think that we ever want to happen.

For consistency, I propose that we align `--package` and `--module` behaviours so that the generated output always goes directly into `--output`. This should hopefully encourage people to always specify a subdirectory for their generated outputs instead of writing them into `./`. 

To demonstrate, with my proposed changes, it would be more common for people to run the following instead:

**`create-api generate schema.json --package "FooKit" --output ./Generated --no-split`**:
``` 

$ tree .
.
├── Generated
│   ├── Package.swift
│   └── Sources
│       ├── Entities.swift
│       └── Paths.swift
├── .create-api.yaml
└── schema.json
```

**`create-api generate schema.json --module "FooKit" --output ./Generated --no-split`**:

```
$ tree .
.
├── Generated
│   ├── Entities.swift
│   └── Paths.swift
├── .create-api.yaml
└── schema.json
```

While this helps with using `--clean` in these scenarios, it doesn't completely make clean safe. There are probably still times when you might set ``--output`` to `./`, for example if you are pushing a package to a repository, you need **Package.swift** at the top level, but you might also want to commit the schema/config to keep everything in one place. 

For the immediate problem of mistakenly deleting configs/schemas, we can add a check to make sure that when using `--clean`, the `config` and `schema` path aren't inside `output`.

Pull request description:
- closes #88 

Prior to this change, if you ran the generator like this:

```bash
$ create-api generate --config ".create-api.yml" --package "Foo" --output "./" "schema.json" --clean
```

Then you would find that **.create-api.yml** and **schema.json** along with any other files within **./** end up being deleted.

This is not ideal, but its mostly a problem because of how `--package` **use** to work as it nested the output meaning that people were more likely to use **./** as their output directory. We've since solved that inconsistency however and now it's expected that people will use a separate output directory. 

If they don't however, and try using `--clean`, this PR will now cause the generator to error instead.

Patch:
diff --git a/Sources/CreateAPI/Generate.swift b/Sources/CreateAPI/Generate.swift
index f6f9ad5d..ec047d53 100644
--- a/Sources/CreateAPI/Generate.swift
+++ b/Sources/CreateAPI/Generate.swift
@@ -110,6 +110,13 @@ struct Generate: ParsableCommand {
     }
 
     private func validateOptions(options: GenerateOptions) throws {
+        let outputPath = URL(fileURLWithPath: output).resolvingSymlinksInPath().path
+        if clean, let configPath = try config.fileURL?.resolvingSymlinksInPath().path, configPath.hasPrefix(outputPath) {
+            throw GeneratorError("Unable to clean because your config file is in the output directory")
+        }
+        if clean, URL(fileURLWithPath: input).resolvingSymlinksInPath().path.hasPrefix(outputPath) {
+            throw GeneratorError("Unable to clean because your input spec is in the output directory")
+        }
         if module != nil && package != nil {
             throw GeneratorError("`module` and `package` parameters are mutually exclusive")
         }


Test patch:
diff --git a/Tests/CreateAPITests/GenerateArgumentTests.swift b/Tests/CreateAPITests/GenerateArgumentTests.swift
index fb0dfb39..cb1ed67c 100644
--- a/Tests/CreateAPITests/GenerateArgumentTests.swift
+++ b/Tests/CreateAPITests/GenerateArgumentTests.swift
@@ -54,4 +54,63 @@ final class GenerateArgumentTests: GenerateTestCase {
             XCTAssertTrue(error.localizedDescription.hasSuffix("options.json does not exist."))
         }
     }
+
+    func testClean() throws {
+        // Given old source files exist in the output directory
+        let oldFileURL = temp.url.appendingPathComponent("Old.swift")
+        try "".write(to: oldFileURL)
+
+        // When the arguments result in cleaning the output
+        let arguments: [String] = [
+            "--clean",
+            "--package", "Package",
+            "--output", temp.url.path,
+            SpecFixture.edgecases.path
+        ]
+
+        // Then the generator will succeed and the old source file will have been removed
+        XCTAssertNoThrow(try generate(arguments))
+        XCTAssertFalse(FileManager.default.fileExists(atPath: oldFileURL.path))
+    }
+
+    func testClean_notAllowedWhenConfigIsInTheOutput() throws {
+        // Given `--config` file exists in the output directory
+        let configURL = temp.url.appendingPathComponent(".create-api.yml")
+        try "{}".write(to: configURL)
+
+        // When the arguments result in trying to clean the config file
+        let arguments: [String] = [
+            "--clean",
+            "--config", configURL.path,
+            "--package", "Package",
+            "--output", temp.url.path,
+            SpecFixture.edgecases.path
+        ]
+
+        // Then the generator will throw an error to protect the config
+        XCTAssertThrowsError(try generate(arguments)) { error in
+            XCTAssertEqual(error.localizedDescription, "Unable to clean because your config file is in the output directory")
+        }
+        XCTAssertTrue(FileManager.default.fileExists(atPath: configURL.path))
+    }
+
+    func testClean_notAllowedWhenSpecIsInTheOutput() throws {
+        // Given the input spec file exists in the output directory
+        let specURL = temp.url.appendingPathComponent("schema.json")
+        try FileManager.default.copyItem(atPath: SpecFixture.edgecases.path, toPath: specURL.path)
+
+        // When the arguments result in trying to clean the spec file
+        let arguments: [String] = [
+            "--clean",
+            "--package", "Package",
+            "--output", temp.url.path,
+            specURL.path
+        ]
+
+        // Then the generator will throw an error because the spec cannot be deleted
+        XCTAssertThrowsError(try generate(arguments)) { error in
+            XCTAssertEqual(error.localizedDescription, "Unable to clean because your input spec is in the output directory")
+        }
+        XCTAssertTrue(FileManager.default.fileExists(atPath: specURL.path))
+    }
 }