Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
32b17d4
Add note, date created, and show subcommand
Trenly Apr 29, 2026
697ca1d
Add tests
Trenly Apr 29, 2026
b6502ef
Add pinning to COM APIs
Trenly Apr 29, 2026
ae483b0
Add IsPinned propery for Get-WingetPackage
Trenly Apr 29, 2026
fac4ec4
Add pinning to PowerShell Cmdlets
Trenly Apr 29, 2026
7ce3b27
Spelling
Trenly Apr 29, 2026
35322e7
Inherit from 1.0 Interface
Trenly Apr 29, 2026
236e667
Contract 29 is not released yet
Trenly Apr 29, 2026
1c89957
Adddress comments about resource strings
Trenly May 1, 2026
fa617a6
Update comment to indicate the appropriate contract version
Trenly May 1, 2026
7f33f56
Make methods public
Trenly May 1, 2026
3c7d4a6
Adjust how new schema is created
Trenly May 1, 2026
99a8760
Reduce duplicated code through inheritance
Trenly May 1, 2026
e336194
Use statement builder, savepoints, and tuples for ownership
Trenly May 1, 2026
0cb653b
Use a time point instead of strings
Trenly May 1, 2026
29d6203
Guard against non-existent source
Trenly May 1, 2026
184cc7a
Cache IsPinned
Trenly May 1, 2026
952e56e
Merge default pin add behavior into single test
Trenly May 1, 2026
e823b75
Ensure table is not created in a partial state
Trenly May 1, 2026
195846f
Add an alternate function for optional parms
Trenly May 1, 2026
0468b34
Move to PackageCatalogReference
Trenly May 1, 2026
4bfde99
Move functionality to converters
Trenly May 1, 2026
243798b
Remove version comment
Trenly May 1, 2026
4b7b8ef
Update filters so 1.1 schema is visible
Trenly May 1, 2026
e93e3d1
Create the correct interface and migrate only if newer
Trenly May 1, 2026
ae87ff1
Use correct version info
Trenly May 1, 2026
917db77
Move invariants outside try catch
Trenly May 1, 2026
34a727c
Add TryRemovePin for simplicity
Trenly May 1, 2026
01aad8e
Refactor to use -Blocking and -GatedVersion
Trenly May 1, 2026
ad9932f
Spelling
Trenly May 1, 2026
a96e43e
Add Pester Tests
Trenly May 1, 2026
fde7382
Tests should now be part of default creation test
Trenly May 1, 2026
d5f682c
Further abstract the interface
Trenly May 1, 2026
8f819c1
Update test name
Trenly May 1, 2026
91f532a
Add comment
Trenly May 1, 2026
595abca
Appease our AI overlords with an update to their tomes
Trenly May 1, 2026
7e83a83
Unify on AssignValue to avoid future confusion
Trenly May 1, 2026
615ddca
Ensure pin writes always record the last updated time
Trenly May 1, 2026
1f8932d
Use original symbol for creation
Trenly May 1, 2026
df55096
Add missing System namepace
Trenly May 1, 2026
6d085dd
Restore conditional setting of date
Trenly May 1, 2026
ea9f0d8
Ensure ShouldProcess is always respected
Trenly May 1, 2026
d207c77
Fix minor issue in copilot instructions
Trenly May 1, 2026
349a0a1
Validate provided pin types
Trenly May 1, 2026
840e5ae
Cache all pins and only fall back to get if set is empty
Trenly May 1, 2026
65f8373
Change file name to remove override
Trenly May 1, 2026
a53f6c7
Merge remote-tracking branch 'upstream/master' into PinPlus
Trenly May 29, 2026
e2315ad
Merge remote tracking branch master from microsoft/winget-cli
Trenly Jun 15, 2026
2792834
Rename back to PinTable.h
Trenly Jun 15, 2026
b350a91
Update filters for file name
Trenly Jun 15, 2026
04b5210
Move SQLite builder instructions to separate file
Trenly Jun 15, 2026
e350988
Make adds flow through 1.0 into 1.1
Trenly Jun 15, 2026
3d92b41
Use shared column names
Trenly Jun 15, 2026
37344ea
Add SQLIte Nullopt test
Trenly Jun 15, 2026
d061054
Remove protected
Trenly Jun 15, 2026
b939b8e
Lazy load all pins for catalog packages
Trenly Jun 15, 2026
ff12245
Contract version 30
Trenly Jun 15, 2026
c920cdd
Remove pin show - move into pin list with details
Trenly Jun 15, 2026
41e10af
Update release notes
Trenly Jun 15, 2026
f05bcc8
Revert instructions edit
Trenly Jun 15, 2026
fdcd860
Run renormalize
Trenly Jun 15, 2026
19406e9
Fix release notes
Trenly Jun 15, 2026
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
3 changes: 3 additions & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
appinstallertest
applic
appname
appone
appshutdown
APPTERMINATION
archs
Expand Down Expand Up @@ -345,6 +346,7 @@
microsoftentraid
microsoftentraidforazureblobstorage
midl
migratepintable
minidump
MINORVERSION
missingdependency
Expand Down Expand Up @@ -422,8 +424,8 @@
PARAMETERMAP
pathparts
pathtree
Patil

Check warning on line 427 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

Entry has inconsistent line endings (unexpected-line-ending)
pbstr

Check warning on line 428 in .github/actions/spelling/expect.txt

View workflow job for this annotation

GitHub Actions / Check Spelling

Entry has inconsistent line endings (unexpected-line-ending)
pcb
PCCERT
PCs
Expand All @@ -438,6 +440,7 @@
pid
pidl
pidlist
pintable
PKCS
pkgmgr
pkindex
Expand Down Expand Up @@ -706,3 +709,3 @@
Zanollo
ZIPHASH
zoro
114 changes: 114 additions & 0 deletions .github/instructions/sqlite-builder.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
---
applyTo: "src/AppInstallerRepositoryCore/Microsoft/Schema/*, src/Microsoft.Management.Configuration/Database/Schema/*"
---

## SQLite Statement Builder

**Always use `AppInstaller::SQLite::Builder::StatementBuilder` when writing SQLite database code. Never write raw SQL strings.**

The builder is in `<winget/SQLiteStatementBuilder.h>` (namespace `AppInstaller::SQLite::Builder`). It generates type-safe, parameterized SQL and ensures symbolic names are used for tables and columns throughout.

### Key conventions

- Define table and column names as `constexpr std::string_view` constants, then pass them to the builder.
- Use `ColumnBuilder` with chained modifiers (`.NotNull()`, `.Unique()`, `.Default(value)`) when creating tables.
- Use `IntegerPrimaryKey()` for auto-increment rowid primary keys.
- Use `Unbound` as a placeholder and bind values later via `Statement::Bind()`, or pass values directly to have them bound automatically.

### Common operations

```cpp
using namespace AppInstaller::SQLite::Builder;

// Symbolic names
static constexpr std::string_view s_MyTable = "my_table"sv;
static constexpr std::string_view s_Col_Id = "id"sv;
static constexpr std::string_view s_Col_Name = "name"sv;
static constexpr std::string_view s_Col_Value = "value"sv;

// CREATE TABLE
StatementBuilder builder;
builder.CreateTable(s_MyTable).Columns({
IntegerPrimaryKey(),
ColumnBuilder(s_Col_Name, Type::Text).NotNull().Unique(),
ColumnBuilder(s_Col_Value, Type::Int64).NotNull().Default(0),
});
builder.Execute(connection);

// SELECT
StatementBuilder builder;
builder.Select({ s_Col_Id, s_Col_Name })
.From(s_MyTable)
.Where(s_Col_Name).Equals(nameValue);
auto stmt = builder.Prepare(connection);
while (stmt.Step()) { /* stmt.GetColumn<T>(index) */ }

// INSERT
StatementBuilder builder;
builder.InsertInto(s_MyTable)
.Columns({ s_Col_Name, s_Col_Value })
.Values(nameValue, intValue);
builder.Execute(connection);

// UPDATE
StatementBuilder builder;
builder.Update(s_MyTable).Set()
.Column(s_Col_Value).AssignValue(newValue)
.Where(s_Col_Id).Equals(rowId);
builder.Execute(connection);

// DELETE
StatementBuilder builder;
builder.DeleteFrom(s_MyTable)
.Where(s_Col_Id).Equals(rowId);
builder.Execute(connection);

// ALTER TABLE – add a column
StatementBuilder builder;
builder.AlterTable(s_MyTable).Add(s_Col_NewCol, Type::Text).NotNull().Default(0);
builder.Execute(connection);
```

### Nullable values: `Equals()` vs `AssignValue()`

Both accept `std::optional<T>`, but they behave differently when the optional is empty and must be used in the right context:

| Method | Empty optional emits | Use in |
|---|---|---|
| `Equals(optional<T>)` | `IS NULL` | **WHERE** / filter clauses |
| `AssignValue(optional<T>)` | `= ?` (binds NULL) | **UPDATE SET** assignments |

Using `Equals(optional)` in an UPDATE SET clause is a bug — SQLite does not accept `col = IS NULL`.

```cpp
// ✅ Correct: Equals for WHERE filter, AssignValue for SET assignment
std::optional<int64_t> maybeEpoch = ...;
std::optional<std::string> maybeNote = ...;

builder.Update(s_MyTable).Set()
.Column(s_Col_Name).AssignValue(requiredName) // non-optional: AssignValue in SET
.Column(s_Col_Epoch).AssignValue(maybeEpoch) // nullable: must use AssignValue in SET
.Column(s_Col_Note).AssignValue(maybeNote) // nullable: must use AssignValue in SET
.Where(s_Col_Id).Equals(rowId); // filter: Equals is correct here

// ✅ Correct: Equals(optional) in WHERE — emits "IS NULL" when empty
builder.Select(s_Col_Id).From(s_MyTable)
.Where(s_Col_Note).Equals(maybeNote); // → "WHERE note IS NULL" when empty
```

### Execution

- `builder.Execute(connection)` — prepares, binds, and runs a statement that returns no rows.
- `builder.Prepare(connection)` — returns a `SQLite::Statement`; call `.Step()` to iterate, `.GetColumn<T>(index)` to read values.

## Naming Conventions

- **Namespace structure**: `AppInstaller::<Area>[::<Subarea>]`
- `AppInstaller::CLI::Execution` - CLI execution context
- `AppInstaller::CLI::Workflow` - Workflow functions
- `AppInstaller::Repository` - Repository/source logic
- `AppInstaller::Manifest` - Manifest types
- `AppInstaller::Settings` - User/admin settings

- **Macros**: Prefixed with `AICLI_` for CLI, `WINGET_` for general
- **Data keys**: ExecutionContextData uses enum keys to type-safely store/retrieve data
9 changes: 8 additions & 1 deletion doc/ReleaseNotes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
## New in v1.29

Nothing yet.
## New Features

### Pinning improvements

- `winget pin add` now supports an optional `--note` value for pin metadata.
- `winget pin add` now tracks the date that a pin was added or updated.
- `winget pin list` now supports an optional `--details` argument for showing extended pin data.
- The PowerShell pin cmdlets now expose the new pinning capabilities and metadata.
Comment thread
Trenly marked this conversation as resolved.

## Bug Fixes

Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/Argument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ namespace AppInstaller::CLI
return { type, "blocking"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PinType };
case Execution::Args::Type::PinInstalled:
return { type, "installed"_liv, ArgTypeCategory::None };
case Execution::Args::Type::PinNote:
return { type, "note"_liv, ArgTypeCategory::None };

// Error command
case Execution::Args::Type::ErrorInput:
Expand Down
3 changes: 3 additions & 0 deletions src/AppInstallerCLICore/Commands/PinCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ namespace AppInstaller::CLI
Argument::ForType(Args::Type::Force),
Argument{ Args::Type::BlockingPin, Resource::String::PinAddBlockingArgumentDescription, ArgumentType::Flag },
Argument{ Args::Type::PinInstalled, Resource::String::PinInstalledArgumentDescription, ArgumentType::Flag },
Argument{ Args::Type::PinNote, Resource::String::PinNoteArgumentDescription, ArgumentType::Standard },
};
}

Expand Down Expand Up @@ -244,6 +245,7 @@ namespace AppInstaller::CLI
Argument::ForType(Args::Type::AuthenticationMode),
Argument::ForType(Args::Type::AuthenticationAccount),
Argument::ForType(Args::Type::AcceptSourceAgreements),
Argument::ForType(Args::Type::ListDetails),
};
}

Expand Down Expand Up @@ -343,4 +345,5 @@ namespace AppInstaller::CLI
Workflow::ReportPins;
}
}

}
1 change: 1 addition & 0 deletions src/AppInstallerCLICore/Commands/PinCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,5 @@ namespace AppInstaller::CLI
protected:
void ExecuteInternal(Execution::Context& context) const override;
};

}
1 change: 1 addition & 0 deletions src/AppInstallerCLICore/ExecutionArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ namespace AppInstaller::CLI::Execution
GatedVersion, // Differs from Version in that this supports wildcards
BlockingPin,
PinInstalled,
PinNote, // User-provided note to attach to a pin

// Error command
ErrorInput,
Expand Down
4 changes: 4 additions & 0 deletions src/AppInstallerCLICore/Resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -540,13 +540,16 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(PinCannotOpenIndex);
WINGET_DEFINE_RESOURCE_STRINGID(PinCommandLongDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinCommandShortDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinDateAdded);
WINGET_DEFINE_RESOURCE_STRINGID(PinDoesNotExist);
WINGET_DEFINE_RESOURCE_STRINGID(PinExistsOverwriting);
WINGET_DEFINE_RESOURCE_STRINGID(PinExistsUseForceArg);
WINGET_DEFINE_RESOURCE_STRINGID(PinInstalledArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinInstalledSource);
WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandLongDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandShortDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinNote);
WINGET_DEFINE_RESOURCE_STRINGID(PinNoteArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinNoPinsExist);
WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandLongDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandShortDescription);
Expand All @@ -556,6 +559,7 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(PinResetSuccessful);
WINGET_DEFINE_RESOURCE_STRINGID(PinResettingAll);
WINGET_DEFINE_RESOURCE_STRINGID(PinResetUseForceArg);
WINGET_DEFINE_RESOURCE_STRINGID(PinShowNoMatchFound);
WINGET_DEFINE_RESOURCE_STRINGID(PinType);
WINGET_DEFINE_RESOURCE_STRINGID(PinVersion);
WINGET_DEFINE_RESOURCE_STRINGID(PlatformArgumentDescription);
Expand Down
Loading
Loading