Skip to content
Closed
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
46 changes: 46 additions & 0 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: OpenSSF Scorecard

on:
push:
branches: [main]
schedule:
- cron: "30 1 * * 6"
workflow_dispatch:

# Keep the default token read-only. The analysis job grants only the minimal
# additional capabilities needed to check out the repository and publish its
# independently generated result.
permissions: read-all

jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
id-token: write
Comment on lines +19 to +22

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Grant contents read to the analysis job

This job-level permissions block overrides the workflow-level read-all; GitHub's permissions docs say omitted scopes are set to none, so the default token used by the very next actions/checkout step no longer has contents: read. Since checkout's documented recommended permission is contents: read unless an alternate token/SSH key is provided, add that scope here so scheduled/manual Scorecard runs can fetch and scan the repository.

Useful? React with 👍 / 👎.

steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

- name: Run analysis
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
publish_results: true

- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: SARIF file
path: results.sarif
retention-days: 5

- name: Upload to code scanning
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with:
sarif_file: results.sarif
17 changes: 16 additions & 1 deletion Sources/CodexBar/Localization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,22 @@ func L(_ key: String, language: String) -> String {
}

func codexBarLocalizedLocale() -> Locale {
let language = resolvedAppLanguage()
codexBarLocale(forLanguage: resolvedAppLanguage())
}

/// Returns the locale of the resource bundle currently selected by `L`.
///
/// This can differ from `Locale.current` when the app falls back to a supported language. Plural
/// formatting must use this locale so it follows the same language as the resolved strings.
func codexBarLocalizedResourceLocale() -> Locale {
let bundleURL = localizedBundle().bundleURL
guard bundleURL.pathExtension == "lproj" else {
return codexBarLocalizedLocale()
}
return codexBarLocale(forLanguage: bundleURL.deletingPathExtension().lastPathComponent)
}

private func codexBarLocale(forLanguage language: String) -> Locale {
guard !language.isEmpty else { return .current }
let normalized = language.lowercased()
if normalized == "ar" || normalized.hasPrefix("ar-") {
Expand Down
16 changes: 9 additions & 7 deletions Sources/CodexBar/UsagePaceText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,21 @@ enum UsagePaceText {

static func sessionEquivalentDetail(forecast: SessionEquivalentForecast) -> SessionEquivalentDetail {
let displayedEstimate = Self.boundedFullWindowCount(forecast.estimatedWindowsToExhaustWeekly)
let numberText = String.localizedStringWithFormat(
L("≈%d full 5h windows of weekly left · %d windows until reset"),
displayedEstimate,
forecast.windowsUntilReset)
let formattingLocale = codexBarLocalizedResourceLocale()
let numberText = String(
format: L("≈%d full 5h windows of weekly left · %d windows until reset"),
locale: formattingLocale,
arguments: [displayedEstimate, forecast.windowsUntilReset])
let verdictText: String
if forecast.estimatedWindowsToExhaustWeekly >= forecast.availableWindowsUntilReset {
verdictText = L("Weekly cannot run out before reset at this pace")
} else {
let windowsEarly = Self.boundedWindowCount(
forecast.availableWindowsUntilReset - forecast.estimatedWindowsToExhaustWeekly)
verdictText = String.localizedStringWithFormat(
L("Weekly can run out ≈%d windows early"),
max(1, windowsEarly))
verdictText = String(
format: L("Weekly can run out ≈%d windows early"),
locale: formattingLocale,
arguments: [max(1, windowsEarly)])
}
return SessionEquivalentDetail(
verdictText: verdictText,
Expand Down
25 changes: 25 additions & 0 deletions Tests/CodexBarTests/LocalizationBundleCacheTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,31 @@ struct LocalizationBundleCacheTests {
#expect(bundle.bundleURL.lastPathComponent == "en.lproj")
}

@Test
func `format locale follows the resolved resource bundle`() {
let english = CodexBarLocalizationOverride.$appLanguage.withValue("en") {
codexBarLocalizedResourceLocale()
}
#expect(english.language.languageCode?.identifier == "en")

let fallback = CodexBarLocalizationOverride.$appLanguage.withValue("zz-unknown") {
codexBarLocalizedResourceLocale()
}
#expect(fallback.language.languageCode?.identifier == "en")
}

@Test
func `resource locale expands English stringsdict singular forms`() {
let rendered = CodexBarLocalizationOverride.$appLanguage.withValue("en") {
String(
format: L("≈%d full 5h windows of weekly left · %d windows until reset"),
locale: codexBarLocalizedResourceLocale(),
arguments: [1, 1])
}

#expect(rendered == "≈1 full 5h window of weekly left · 1 window until reset")
}

@Test
func `resolution survives an explicit cache reset`() {
let first = CodexBarLocalizationOverride.$appLanguage.withValue("uk") {
Expand Down
59 changes: 37 additions & 22 deletions Tests/CodexBarTests/SpendDashboardControllerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Testing
@testable import CodexBar

@MainActor
@Suite(.serialized)
struct SpendDashboardControllerTests {
@Test
func `empty codex history loads as successful inactive source`() async {
Expand Down Expand Up @@ -288,7 +289,7 @@ struct SpendDashboardControllerTests {
let snapshot = Self.input(id: "claude", provider: .claude, cost: 3).snapshot
store._setTokenSnapshotForTesting(snapshot, provider: .claude)
store._test_tokenUsageRefreshOverride = { _, _ in }
let controller = SpendDashboardController(requestBuilder: { mode in
let controller = SpendDashboardController(userDefaults: settings.userDefaults, requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})

Expand Down Expand Up @@ -408,9 +409,11 @@ struct SpendDashboardControllerTests {
environmentBase: [:])
store._setTokenSnapshotForTesting(Self.input(provider: .claude, cost: 3).snapshot, provider: .claude)
store._test_tokenUsageRefreshOverride = { _, _ in }
let controller = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let controller = SpendDashboardController(
userDefaults: settings.userDefaults,
requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})

let firstConfiguration = SpendDashboardSource.configuration(settings: settings, store: store)
controller.update(configuration: firstConfiguration)
Expand All @@ -431,9 +434,11 @@ struct SpendDashboardControllerTests {
#expect(controller.failedSourceCount == 1)
#expect(store.tokenSnapshot(for: .claude)?.last30DaysCostUSD == 3)

let reopenedController = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let reopenedController = SpendDashboardController(
userDefaults: settings.userDefaults,
requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
reopenedController.update(configuration: replacementConfiguration)
await Self.waitUntil { !reopenedController.isRefreshing }
#expect(reopenedController.model.groups.isEmpty)
Expand Down Expand Up @@ -487,9 +492,11 @@ struct SpendDashboardControllerTests {

store._setTokenSnapshotForTesting(Self.input(provider: .mistral, cost: 3).snapshot, provider: .mistral)
store._test_providerRefreshOverride = { _ in }
let controller = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let controller = SpendDashboardController(
userDefaults: settings.userDefaults,
requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
controller.update(configuration: selectedBackupConfiguration)
await Self.waitUntil { !controller.isRefreshing }
#expect(controller.model.groups.first?.totalCost == 3)
Expand Down Expand Up @@ -527,9 +534,11 @@ struct SpendDashboardControllerTests {
environmentBase: [:])
store._setTokenSnapshotForTesting(Self.input(provider: .claude, cost: 4).snapshot, provider: .claude)
store._test_tokenUsageRefreshOverride = { _, _ in }
let controller = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let controller = SpendDashboardController(
userDefaults: settings.userDefaults,
requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
controller.update(configuration: SpendDashboardSource.configuration(settings: settings, store: store))
await Self.waitUntil { !controller.isRefreshing }
#expect(controller.model.groups.first?.totalCost == 4)
Expand Down Expand Up @@ -557,9 +566,11 @@ struct SpendDashboardControllerTests {
environmentBase: [:])
store._setTokenSnapshotForTesting(Self.input(provider: .claude, cost: 5).snapshot, provider: .claude)
store._test_tokenUsageRefreshOverride = { _, _ in }
let controller = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let controller = SpendDashboardController(
userDefaults: settings.userDefaults,
requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let firstConfiguration = SpendDashboardSource.configuration(settings: settings, store: store)
controller.update(configuration: firstConfiguration)
await Self.waitUntil { !controller.isRefreshing }
Expand Down Expand Up @@ -626,9 +637,11 @@ struct SpendDashboardControllerTests {
environmentBase: [:])
store._setTokenSnapshotForTesting(Self.input(provider: .claude, cost: 5).snapshot, provider: .claude)
store._test_tokenUsageRefreshOverride = { _, _ in }
let controller = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let controller = SpendDashboardController(
userDefaults: settings.userDefaults,
requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
controller.update(configuration: SpendDashboardSource.configuration(settings: settings, store: store))
await Self.waitUntil { !controller.isRefreshing }
#expect(controller.model.groups.first?.totalCost == 5)
Expand All @@ -644,9 +657,11 @@ struct SpendDashboardControllerTests {
#expect(controller.model.groups.isEmpty)
#expect(controller.failedSourceCount == 1)

let reopenedController = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let reopenedController = SpendDashboardController(
userDefaults: settings.userDefaults,
requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
reopenedController.update(configuration: reenabledConfiguration)
await Self.waitUntil { !reopenedController.isRefreshing }
#expect(reopenedController.model.groups.isEmpty)
Expand Down
24 changes: 15 additions & 9 deletions Tests/CodexBarTests/SpendDashboardTokenProvenanceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,11 @@ struct SpendDashboardTokenProvenanceTests {
store.activateCachedTokenAccountSnapshot(provider: .mistral, accountID: account.id)
#expect(store.tokenSnapshotPublicationRevision(for: .mistral) == baselineRevision)
store._test_providerRefreshOverride = { _ in }
let controller = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let controller = SpendDashboardController(
userDefaults: settings.userDefaults,
requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
controller.update(configuration: SpendDashboardSource.configuration(settings: settings, store: store))
await Self.waitUntil { !controller.isRefreshing }
#expect(controller.model.groups.first?.totalCost == 3)
Expand All @@ -148,9 +150,11 @@ struct SpendDashboardTokenProvenanceTests {
return loadCount == 1 ? Self.tokenSnapshot(cost: 4) : Self.emptyTokenSnapshot()
}
await store.refreshTokenUsageNow(for: .bedrock, force: true)
let controller = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let controller = SpendDashboardController(
userDefaults: settings.userDefaults,
requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
controller.update(configuration: SpendDashboardSource.configuration(settings: settings, store: store))
await Self.waitUntil { !controller.isRefreshing }
#expect(controller.model.groups.first?.totalCost == 4)
Expand All @@ -177,9 +181,11 @@ struct SpendDashboardTokenProvenanceTests {
}
await store.refreshTokenUsageNow(for: .bedrock, force: true)
let publicationRevision = store.tokenSnapshotPublicationRevision(for: .bedrock)
let controller = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let controller = SpendDashboardController(
userDefaults: settings.userDefaults,
requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})

controller.update(configuration: SpendDashboardSource.configuration(settings: settings, store: store))
await Self.waitUntil { !controller.isRefreshing }
Expand Down
Loading