Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Changed
- Replace `sqlct.config.json` with `sqlct.config.yaml` as the project configuration file; config files are now written in YAML with a header comment block containing a tool introduction, installation instructions, usage instructions, and a link to the GitHub repository.

### Removed
- `sqlct.config.json` is no longer supported; existing users should rename the file to `sqlct.config.yaml` and run `sqlct config` to rewrite it in the new format.

## [0.3.0] - 2026-04-12

### Fixed
Expand Down
71 changes: 31 additions & 40 deletions specs/02-config.md
Original file line number Diff line number Diff line change
@@ -1,53 +1,47 @@
# Config

Status: draft
Last updated: 2026-04-06
Last updated: 2026-04-13

## Authoritative Configuration
`sqlct.config.json` is the authoritative configuration file for `sqlct` projects.
`sqlct.config.yaml` is the authoritative configuration file for `sqlct` projects.

- Required for baseline `sqlct` workflows.
- Stores database connection settings and tool options.
- All command behavior is resolved from this file (with CLI overrides where supported).

## Schema
The `sqlct.config.json` structure defines the current contract.
The `sqlct.config.yaml` structure defines the current contract.
Current shape:

```json
{
"database": {
"server": "localhost",
"name": "MyDb",
"auth": "integrated",
"user": "",
"password": "",
"trustServerCertificate": false
},
"options": {
"parallelism": 0
},
"data": {
"trackedTables": [
"dbo.Customer",
"Sales.SalesOrderHeader"
]
}
}
```yaml
database:
server: localhost
name: MyDb
auth: integrated
user: ''
password: ''
trustServerCertificate: false
options:
parallelism: 0
data:
trackedTables:
- dbo.Customer
- Sales.SalesOrderHeader
```

## Init Behavior
`sqlct init` initializes `sqlct.config.json` in the target project directory.
- Writes default config when missing.
`sqlct init` initializes `sqlct.config.yaml` in the target project directory.
- Writes default config when missing. The written file begins with a header comment block containing a short intro, installation instructions, usage instructions, and a link to the GitHub repository.
- Preserves existing compatibility files if present.

## Config Command Behavior
`sqlct config` parses, validates, and writes configuration from the project directory.

- Requires `sqlct.config.json` to already exist in the project directory. If the file is missing, `config` exits with code `2` (`invalid config`) and prints: `Error: project directory is not initialized.` with hint `run \`sqlct init\` first.` No file is created.
- Validates `sqlct.config.json` as the primary source.
- Requires `sqlct.config.yaml` to already exist in the project directory. If the file is missing, `config` exits with code `2` (`invalid config`) and prints: `Error: project directory is not initialized.` with hint `run \`sqlct init\` first.` No file is created.
- Validates `sqlct.config.yaml` as the primary source.
- Detects optional compatibility file presence for summary output only.
- Writes normalized configuration back to `sqlct.config.json`.
- Writes normalized configuration back to `sqlct.config.yaml`. The header comment block is regenerated on every write; any user-added comments inside the file are not preserved.
- Deprecated fields are tolerated on read and omitted on rewrite.

Deprecated runtime fields removed from v1 contract:
Expand All @@ -56,7 +50,7 @@ Deprecated runtime fields removed from v1 contract:
- `options.comparison.*`

## File Handling Policy
- `sqlct.config.json`: required; read/write by `init` and `config`.
- `sqlct.config.yaml`: required; read/write by `init` and `config`.
- External compatibility files: optional; presence may be scanned/reported; files are preserved as-is.
- Do not fail baseline workflows solely because optional external compatibility files are missing.

Expand All @@ -67,17 +61,14 @@ Deprecated runtime fields removed from v1 contract:
- `sql`: Uses SQL Server Authentication. `user` is required; `password` is optional (empty string is accepted).

### SQL Authentication example
```json
{
"database": {
"server": "my-server.example.com",
"name": "MyDb",
"auth": "sql",
"user": "my_login",
"password": "my_password",
"trustServerCertificate": false
}
}
```yaml
database:
server: my-server.example.com
name: MyDb
auth: sql
user: my_login
password: my_password
trustServerCertificate: false
```

Validation rules:
Expand Down
4 changes: 2 additions & 2 deletions specs/12-project-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Status values: `not_started`, `in_progress`, `blocked`, `done`.
| Stream | Scope | Status | Notes |
| --- | --- | --- | --- |
| S1 | CLI foundation and command wiring | done | Command registration and global settings are in place. |
| S2 | Config and init flows | done | `sqlct.config.json` read/write and project seeding are functional. |
| S2 | Config and init flows | done | `sqlct.config.yaml` read/write and project seeding are functional. |
| S3 | SQL adapter and schema mapping | done | Active object-type services are integrated into command runtime. |
| S3b | Additional object-type activation | done | Active scope covers broker, full-text, XML schema collection, search-property-list, and assembly support needed for current compatibility work. |
| S4 | Status/diff engine | done | End-to-end behavior implemented for active object types. |
Expand Down Expand Up @@ -164,7 +164,7 @@ Status values: `not_started`, `in_progress`, `blocked`, `done`.

## Cross-Cutting Rules
- Specs are authoritative over inferred behavior.
- `sqlct.config.json` is the primary runtime configuration source.
- `sqlct.config.yaml` is the primary runtime configuration source.
- Local artifacts under `local/` remain untracked.
- Keep naming compatibility-neutral in code and docs.
- Keep v1 runtime scope constrained to the active object types and simplified config contract.
Expand Down
4 changes: 2 additions & 2 deletions src/SqlChangeTracker/Commands/InitCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ private static ConnectionSetup PromptForConnectionSetup()
string? password = null;
if (string.Equals(auth, "sql", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine(" WARNING: password will be stored in plain text in sqlct.config.json.");
Console.WriteLine(" WARNING: password will be stored in plain text in sqlct.config.yaml.");
Console.Write(" Username: ");
user = Console.ReadLine()?.Trim();
Console.Write(" Password: ");
Expand Down Expand Up @@ -225,7 +225,7 @@ private static IReadOnlyList<string> GetNextSteps(InitConnectionTestResult? conn

return
[
"Edit 'sqlct.config.json' to configure your database connection.",
"Edit 'sqlct.config.yaml' to configure your database connection.",
"Run 'sqlct config' to validate your configuration.",
"Run 'sqlct pull' to pull the current database schema into your folder.",
];
Expand Down
2 changes: 1 addition & 1 deletion src/SqlChangeTracker/Config/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ internal static class ExitCodes

internal static class ConfigFileNames
{
public const string SqlctConfigFileName = "sqlct.config.json";
public const string SqlctConfigFileName = "sqlct.config.yaml";
}

internal static class ErrorCodes
Expand Down
22 changes: 12 additions & 10 deletions src/SqlChangeTracker/Config/SqlctConfigReader.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Text.Json;
using YamlDotNet.Core;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

namespace SqlChangeTracker.Config;

Expand All @@ -20,13 +22,13 @@ public SqlctConfigReadResult Read(string configPath)

try
{
var json = File.ReadAllText(configPath);
var config = JsonSerializer.Deserialize<SqlctConfig>(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
ReadCommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true
});
var yaml = File.ReadAllText(configPath);
var deserializer = new DeserializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.IgnoreUnmatchedProperties()
.Build();

var config = deserializer.Deserialize<SqlctConfig>(yaml);

if (config == null)
{
Expand All @@ -38,10 +40,10 @@ public SqlctConfigReadResult Read(string configPath)
SqlctConfigNormalizer.Normalize(config);
return SqlctConfigReadResult.Ok(config);
}
catch (JsonException ex)
catch (YamlException ex)
{
return SqlctConfigReadResult.Failure(
new ErrorInfo(ErrorCodes.InvalidConfig, "invalid config file.", Detail: $"invalid JSON: {ex.Message}"),
new ErrorInfo(ErrorCodes.InvalidConfig, "invalid config file.", Detail: $"invalid YAML: {ex.Message}"),
ExitCodes.InvalidConfig);
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
Expand Down
33 changes: 26 additions & 7 deletions src/SqlChangeTracker/Config/SqlctConfigWriter.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
using System.Text.Json;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

namespace SqlChangeTracker.Config;

internal sealed class SqlctConfigWriter
{
private const string HeaderComment =
"# SQL Change Tracker (sqlct)\n" +
"# https://github.com/ElegantCodeAtelier/sql-change-tracker\n" +
"#\n" +
"# Installation:\n" +
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add Update: section after Installation:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in afa2cae:

# Update:
#   dotnet tool update --global sqlct
#

"# dotnet tool install --global sqlct\n" +
"#\n" +
"# Update:\n" +
"# dotnet tool update --global sqlct\n" +
"#\n" +
"# Usage:\n" +
"# sqlct --help - print help\n" +
"# sqlct init - initialize this project\n" +
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add info on how to print help

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 5f0b91d:

#   sqlct --help   - print help

as the first entry under Usage:.

"# sqlct config - validate and rewrite configuration\n" +
"# sqlct status - compare database against schema folder\n" +
"# sqlct diff - show textual schema differences\n" +
"# sqlct pull - pull database schema into folder\n" +
"#\n";

public static string GetDefaultPath(string baseDirectory)
=> Path.Combine(baseDirectory, ConfigFileNames.SqlctConfigFileName);

Expand All @@ -24,13 +44,12 @@ public ConfigWriteResult Write(string configPath, SqlctConfig config, bool overw
return ConfigWriteResult.Ok(Array.Empty<string>(), new[] { ConfigFileNames.SqlctConfigFileName });
}

var options = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var serializer = new SerializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.Build();

var payload = JsonSerializer.Serialize(config, options);
var yaml = serializer.Serialize(config);
var payload = HeaderComment + yaml;
File.WriteAllText(configPath, payload);

return ConfigWriteResult.Ok(new[] { ConfigFileNames.SqlctConfigFileName }, Array.Empty<string>());
Expand Down
1 change: 1 addition & 0 deletions src/SqlChangeTracker/SqlChangeTracker.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<ItemGroup>
<PackageReference Include="Spectre.Console.Cli" Version="0.53.1" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.4" />
<PackageReference Include="YamlDotNet" Version="17.0.1" />
</ItemGroup>

<ItemGroup>
Expand Down
20 changes: 10 additions & 10 deletions tests/SqlChangeTracker.Tests/Commands/DataCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public void DataTrackCommand_WhenNoMatches_ReturnsSuccessWithoutConfirmation()
new DataTrackPlan(
".\\schema",
".\\schema",
".\\schema\\sqlct.config.json",
".\\schema\\sqlct.config.yaml",
"dbo.*",
[],
["dbo.Customer"],
Expand All @@ -44,7 +44,7 @@ public void DataTrackCommand_WhenDeclined_ReturnsSuccessWithoutApplying()
new DataTrackPlan(
".\\schema",
".\\schema",
".\\schema\\sqlct.config.json",
".\\schema\\sqlct.config.yaml",
"dbo.*",
["dbo.Customer", "dbo.Order"],
["dbo.Customer"],
Expand Down Expand Up @@ -79,7 +79,7 @@ public void DataTrackCommand_WhenConfirmationUnavailable_ReturnsExecutionFailure
new DataTrackPlan(
".\\schema",
".\\schema",
".\\schema\\sqlct.config.json",
".\\schema\\sqlct.config.yaml",
"dbo.*",
["dbo.Customer"],
[],
Expand Down Expand Up @@ -114,7 +114,7 @@ public void DataUntrackCommand_WhenNoMatches_ReturnsSuccessWithoutConfirmation()
new DataUntrackPlan(
".\\schema",
".\\schema",
".\\schema\\sqlct.config.json",
".\\schema\\sqlct.config.yaml",
"*.Missing",
[],
["dbo.Customer"],
Expand All @@ -139,7 +139,7 @@ public void DataUntrackCommand_WhenDeclined_WritesPreviewAndDoesNotApply()
new DataUntrackPlan(
".\\schema",
".\\schema",
".\\schema\\sqlct.config.json",
".\\schema\\sqlct.config.yaml",
"dbo.*",
["dbo.Customer"],
["dbo.Customer", "dbo.Order"],
Expand Down Expand Up @@ -181,7 +181,7 @@ public void DataTrackCommand_WithJsonFlag_WritesPromptToStdErrAndJsonToStdOut()
new DataTrackPlan(
".\\schema",
".\\schema",
".\\schema\\sqlct.config.json",
".\\schema\\sqlct.config.yaml",
"dbo.*",
["dbo.Customer"],
[],
Expand Down Expand Up @@ -344,7 +344,7 @@ public void DataTrackCommand_WithObjectOption_PassesObjectPatternToService()
new DataTrackPlan(
".\\schema",
".\\schema",
".\\schema\\sqlct.config.json",
".\\schema\\sqlct.config.yaml",
"dbo.*",
[],
[],
Expand All @@ -370,7 +370,7 @@ public void DataTrackCommand_WithFilterOption_PassesFilterPatternToService()
new DataTrackPlan(
".\\schema",
".\\schema",
".\\schema\\sqlct.config.json",
".\\schema\\sqlct.config.yaml",
"^dbo\\.",
[],
[],
Expand Down Expand Up @@ -435,7 +435,7 @@ public void DataUntrackCommand_WithObjectOption_PassesObjectPatternToService()
new DataUntrackPlan(
".\\schema",
".\\schema",
".\\schema\\sqlct.config.json",
".\\schema\\sqlct.config.yaml",
"dbo.*",
[],
[],
Expand All @@ -461,7 +461,7 @@ public void DataUntrackCommand_WithFilterOption_PassesFilterPatternToService()
new DataUntrackPlan(
".\\schema",
".\\schema",
".\\schema\\sqlct.config.json",
".\\schema\\sqlct.config.yaml",
"^dbo\\.",
[],
[],
Expand Down
Loading
Loading