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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
.zeplin/
.zeplin-cli/
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.5.0

- Move global config from `~/.zeplin/` to `~/.config/zeplin-cli/` (XDG Base Directory)
- Move update cache from `~/.zeplin/` to `~/.cache/zeplin-cli/`
- Move project-local config from `.zeplin/` to `.zeplin-cli/`
- Support `XDG_CONFIG_HOME` and `XDG_CACHE_HOME` environment variables

## 0.4.3

- Fix `--limit` not returning more than 100 results (now paginates automatically)
Expand Down
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,11 @@ zeplin-cli user profile
zeplin-cli auth init
```

This prompts for your token, optionally a default organization ID, and saves credentials to `~/.zeplin/config.json` with restricted permissions (600). It also verifies the token works.
This prompts for your token, optionally a default organization ID, and saves credentials to `~/.config/zeplin-cli/config.json` with restricted permissions (600). It also verifies the token works.

### Manual Config File

Create `~/.zeplin/config.json`:
Create `~/.config/zeplin-cli/config.json`:

```json
{
Expand All @@ -252,14 +252,24 @@ zeplin-cli projects list
zeplin-cli projects list --token "your-personal-access-token"
```

### Configuration Paths

zeplin-cli follows the [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/latest/):

| Purpose | Default Path | Override |
|---------|-------------|----------|
| Config | `~/.config/zeplin-cli/config.json` | `$XDG_CONFIG_HOME` |
| Cache | `~/.cache/zeplin-cli/update-check.json` | `$XDG_CACHE_HOME` |
| Project | `.zeplin-cli/config.json` | — |

### Credential Resolution Order

Credentials are resolved in this order (first match wins):

1. `--token` command-line flag
2. `ZEPLIN_TOKEN` environment variable
3. Project-local config (`.zeplin/config.json`)
4. Global config (`~/.zeplin/config.json`)
3. Project-local config (`.zeplin-cli/config.json`)
4. Global config (`~/.config/zeplin-cli/config.json`)

### Multiple Profiles

Expand Down Expand Up @@ -588,7 +598,7 @@ zeplin-cli design-tokens get --project <project-id> --pretty > tokens.json

Run `zeplin-cli auth init` to set up credentials interactively. In interactive mode (`zeplin-cli` with no arguments), you'll be prompted to set up credentials automatically.

You can also check that your config file exists at `~/.zeplin/config.json`.
You can also check that your config file exists at `~/.config/zeplin-cli/config.json`.

### "Unauthorized" errors

Expand Down
6 changes: 3 additions & 3 deletions Sources/ZeplinCLI/CLI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ public struct Zeplin: ParsableCommand {
Credentials can be provided via:
1. Command-line flag (--token)
2. Environment variable (ZEPLIN_TOKEN)
3. Project-local config (.zeplin/config.json)
4. Global config (~/.zeplin/config.json)
3. Project-local config (.zeplin-cli/config.json)
4. Global config (~/.config/zeplin-cli/config.json)

Run 'zeplin-cli auth init' to set up credentials interactively.
Get a personal access token at https://app.zeplin.io/profile/developer
Expand Down Expand Up @@ -45,7 +45,7 @@ public struct Zeplin: ParsableCommand {
DOCUMENTATION
https://docs.zeplin.dev/reference/introduction
""",
version: "0.4.3",
version: "0.5.0",
subcommands: [
InteractiveCommand.self,
AuthCommand.self,
Expand Down
8 changes: 4 additions & 4 deletions Sources/ZeplinCLI/Commands/Auth/AuthCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ struct AuthCommand: ParsableCommand {
discussion: """
Set up and manage Zeplin API credentials.

Credentials are stored in ~/.zeplin/config.json with restricted permissions (600).
Credentials are stored in ~/.config/zeplin-cli/config.json with restricted permissions (600).
You can configure multiple profiles for different accounts.

GETTING A TOKEN
Expand Down Expand Up @@ -155,7 +155,7 @@ struct AuthProfilesCommand: ParsableCommand {
let resolver = CredentialResolver()

if let config = try resolver.loadConfig(from: CredentialResolver.globalConfigPath) {
print("Global config (~/.zeplin/config.json):")
print("Global config (\(CredentialResolver.globalConfigPath)):")
print(" Default: \(config.defaultProfile ?? "(none)")")
print(" Profiles:")
for (name, profile) in config.profiles.sorted(by: { $0.key < $1.key }) {
Expand All @@ -166,11 +166,11 @@ struct AuthProfilesCommand: ParsableCommand {
}
}
} else {
print("No global config found at ~/.zeplin/config.json")
print("No global config found at \(CredentialResolver.globalConfigPath)")
}

if let config = try resolver.loadConfig(from: CredentialResolver.localConfigPath) {
print("\nLocal config (.zeplin/config.json):")
print("\nLocal config (\(CredentialResolver.localConfigPath)):")
print(" Default: \(config.defaultProfile ?? "(none)")")
print(" Profiles:")
for (name, _) in config.profiles.sorted(by: { $0.key < $1.key }) {
Expand Down
5 changes: 2 additions & 3 deletions Sources/ZeplinCLI/UpdateChecker.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import ZeplinKit

public enum UpdateChecker {

Expand All @@ -9,9 +10,7 @@ public enum UpdateChecker {
private static let fetchTimeout: TimeInterval = 3

private static var cacheURL: URL {
FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent(".zeplin")
.appendingPathComponent("update-check.json")
Paths.updateCheckCacheFile
}

public struct Cache: Codable {
Expand Down
6 changes: 3 additions & 3 deletions Sources/ZeplinKit/Auth/CredentialResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ public struct CredentialOptions: Sendable {
}

public struct CredentialResolver: Sendable {
public static let globalConfigPath = "~/.zeplin/config.json"
public static let localConfigPath = ".zeplin/config.json"
public static var globalConfigPath: String { Paths.globalConfigPath }
public static var localConfigPath: String { Paths.localConfigPath }

public static let envToken = "ZEPLIN_TOKEN"
public static let envOrganizationId = "ZEPLIN_ORGANIZATION_ID"
Expand Down Expand Up @@ -49,7 +49,7 @@ public struct CredentialResolver: Sendable {
Credentials can also be provided via:
- Command-line flag (--token)
- Environment variable (ZEPLIN_TOKEN)
- Config file (~/.zeplin/config.json)
- Config file (\(Paths.globalConfigPath))

Get a personal access token at:
https://app.zeplin.io/profile/developer
Expand Down
48 changes: 48 additions & 0 deletions Sources/ZeplinKit/Paths.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Foundation

public enum Paths {

// MARK: - Config

public static var configDirectory: URL {
let base: URL
if let xdg = ProcessInfo.processInfo.environment["XDG_CONFIG_HOME"], !xdg.isEmpty {
base = URL(fileURLWithPath: xdg)
} else {
base = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".config")
}
return base.appendingPathComponent("zeplin-cli")
}

public static var globalConfigFile: URL {
configDirectory.appendingPathComponent("config.json")
}

public static var globalConfigPath: String {
if let xdg = ProcessInfo.processInfo.environment["XDG_CONFIG_HOME"], !xdg.isEmpty {
return URL(fileURLWithPath: xdg)
.appendingPathComponent("zeplin-cli/config.json").path
}
return "~/.config/zeplin-cli/config.json"
}

// MARK: - Cache

public static var cacheDirectory: URL {
let base: URL
if let xdg = ProcessInfo.processInfo.environment["XDG_CACHE_HOME"], !xdg.isEmpty {
base = URL(fileURLWithPath: xdg)
} else {
base = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".cache")
}
return base.appendingPathComponent("zeplin-cli")
}

public static var updateCheckCacheFile: URL {
cacheDirectory.appendingPathComponent("update-check.json")
}

// MARK: - Local (project-scoped)

public static let localConfigPath = ".zeplin-cli/config.json"
}
2 changes: 1 addition & 1 deletion Tests/ZeplinKitTests/CredentialsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ struct CredentialsTests {

@Test func missingProfileThrows() throws {
let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
let subDir = tempDir.appendingPathComponent(".zeplin")
let subDir = tempDir.appendingPathComponent(".zeplin-cli")
try FileManager.default.createDirectory(at: subDir, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: tempDir) }

Expand Down
Loading