diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d7e594..cf3d5ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ For **user-facing release notes** (RU/EN block per version) and **growth handoff ### Changed - [RELEASE.md](docs/RELEASE.md): **Public launch readiness (CLI)** section (CI, docs funnel, live smoke, npm parity, publish vs automated token) and link to [docs/README.md](docs/README.md). -- Dependency: `skillget-manager` **v0.1.3** (archive download transport/HTTP hints, README; keep in sync with manager releases). +- Dependency: `skillget-manager` **v0.1.4** (registry compiled-core JSON: `versions` map + semver latest + optional read bearer; [REGISTRY_CLIENT_CONTRACT.md](https://github.com/getskillpack/skillget-manager/blob/main/docs/REGISTRY_CLIENT_CONTRACT.md)). - CLI: `skillget config -h` / `--help` and rejection of stray arguments on `config`. - CLI: `skillget -h` / `--help` on `list`, `search`, `install`, and `publish` prints command-specific flags and exits 0 (was a generic error exit). - CI: committed `vendor/` for private `skillget-manager`; default **Go** workflow job builds with `-mod=vendor` **without** repository secrets (fork PRs included). Second job skips heavy steps when `GETSKILLPACK_ORG_PAT` is absent; with the secret it runs remote `go mod download` / build and checks `vendor/` matches `go.mod`. diff --git a/go.mod b/go.mod index 3fdc64c..a2b3cd5 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,11 @@ module github.com/getskillpack/cli -go 1.22 +go 1.22.0 -require github.com/getskillpack/skillget-manager v0.1.3 +toolchain go1.22.12 + +require github.com/getskillpack/skillget-manager v0.1.4 + +require golang.org/x/mod v0.23.0 // indirect // Private org: GOPRIVATE / GONOSUMDB + Git credentials for `go build` — см. docs/BOARD_PAT_QUICK_RU.md § Go modules. diff --git a/go.sum b/go.sum index e2b6b93..96b7876 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ -github.com/getskillpack/skillget-manager v0.1.3 h1:avVsRsf6UVCgmznxOwmPT9Y3pXJAhTFPTRFou5d+wCA= -github.com/getskillpack/skillget-manager v0.1.3/go.mod h1:XDmEMMPsIni59OVQLAYlNXZqAZE7smTmj4Djsx8Gr54= +github.com/getskillpack/skillget-manager v0.1.4 h1:Qc5uvKCKWeG22MxI6u1dNKG2QDvNG7zDAyXW9c91jzo= +github.com/getskillpack/skillget-manager v0.1.4/go.mod h1:nuCuVtegzpePhhGfsqGlLiHBwg6NIgauqaNKPfK4I8E= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= diff --git a/vendor/github.com/getskillpack/skillget-manager/CHANGELOG.md b/vendor/github.com/getskillpack/skillget-manager/CHANGELOG.md index 6440314..36f9a42 100644 --- a/vendor/github.com/getskillpack/skillget-manager/CHANGELOG.md +++ b/vendor/github.com/getskillpack/skillget-manager/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.1.4 — 2026-03-29 + +- **Breaking (контракт JSON):** `SkillDetail` приведён к канону `GET /skills/{name}` из [registry-api.md](https://github.com/getskillpack/registry/blob/main/docs/registry-api.md): поле `versions` — объект `map[version]`, флаг **`yanked`**, без вымышленного `latest_version` на детали скилла. +- `ResolveInstallTarget` без pin: выбор **максимальной semver** среди неснятых версий (как `latestNonYanked` в reference store). +- `FetchJSON` и загрузка архива: опциональный `Authorization: Bearer` из `SKILLGET_REGISTRY_READ_TOKEN` или fallback на токен записи (`RegistryReadBearer`). +- Документ для board/интеграций: [docs/REGISTRY_CLIENT_CONTRACT.md](docs/REGISTRY_CLIENT_CONTRACT.md). +- Зависимость: `golang.org/x/mod/semver` для сортировки версий. + ## 0.1.3 — 2026-03-28 - `DownloadSkillArchive`: подсказки при сетевых сбоях и при ответах **401 / 403 / 404 / 429 / 503** от хоста архива (URL может отличаться от базы реестра). @@ -27,5 +35,3 @@ - Сообщения об ошибках HTTP к реестру с краткими подсказками (как в TS `registry.ts`). - Документация: `SECURITY.md`, ссылки на лицензию и процесс безопасности в README. -## Unreleased - diff --git a/vendor/github.com/getskillpack/skillget-manager/README.md b/vendor/github.com/getskillpack/skillget-manager/README.md index 3b78502..af56a4b 100644 --- a/vendor/github.com/getskillpack/skillget-manager/README.md +++ b/vendor/github.com/getskillpack/skillget-manager/README.md @@ -10,14 +10,15 @@ CLI ([cli](https://github.com/getskillpack/cli)) остаётся тонкой - `RegistryBaseURL` / `RegistryConfigSource` — выбор базы API из env (`SKILLGET_REGISTRY_URL`, legacy `SKPKG_REGISTRY_URL`). - `RegistryToken` — bearer для записи в реестр из `SKILLGET_REGISTRY_TOKEN` или `SKILLGET_TOKEN`. -- `FetchJSON` — JSON GET к реестру (через `context.Context`). +- `RegistryReadBearer` — bearer для чтения (`SKILLGET_REGISTRY_READ_TOKEN` или, если пусто, тот же токен, что и для записи): добавляется к `GET` API и к загрузке `archive_url`, когда токен задан. +- `FetchJSON` — JSON GET к реестру (через `context.Context`); типичные ответы реестра сопровождаются короткими подсказками (сеть, токен, 404, 409, 429 и др.). - `ReadSkillsLock` / `WriteSkillsLock` — файл `skills.lock`. - `SearchSkills` — список/поиск скиллов (опции `Query`, `Author`, `Limit`, `Offset`). -- `ParseNameVersion` / `ResolveInstallTarget` — разрешение версии и метаданные архива (для «latest» поле `latest_version`, если оно не помечено как yanked в списке версий; иначе первая неснятая версия). -- `DownloadSkillArchive` — скачивание tarball, проверка `checksum` вида `sha256:` при наличии, обновление lockfile. +- `ParseNameVersion` / `ResolveInstallTarget` — разрешение версии и метаданные архива; для «latest» без pin — **наибольшая semver** среди записей `versions` в ответе `GET /skills/{name}`, исключая `yanked: true` (как в reference registry). +- `DownloadSkillArchive` — скачивание tarball, проверка `checksum` вида `sha256:` при наличии, обновление lockfile; при сбоях HTTP к **archive URL** (часто отдельно от базы реестра) — свои подсказки (401–429, 503, сеть). - `PublishSkill` — multipart POST `/skills` (manifest + archive), как у TS-клиента в `skpkg-cli`. -Контракт HTTP API реестра: [registry/API.md](https://github.com/getskillpack/registry/blob/main/API.md). +Контракт HTTP API реестра: [registry/API.md](https://github.com/getskillpack/registry/blob/main/API.md). Поведение этой библиотеки как клиента: [docs/REGISTRY_CLIENT_CONTRACT.md](docs/REGISTRY_CLIENT_CONTRACT.md). ## Разработка diff --git a/vendor/github.com/getskillpack/skillget-manager/client.go b/vendor/github.com/getskillpack/skillget-manager/client.go index 9150930..a2a68b6 100644 --- a/vendor/github.com/getskillpack/skillget-manager/client.go +++ b/vendor/github.com/getskillpack/skillget-manager/client.go @@ -28,6 +28,9 @@ func FetchJSON(ctx context.Context, path string, out any) error { return err } req.Header.Set("Accept", "application/json") + if t := RegistryReadBearer(); t != "" { + req.Header.Set("Authorization", "Bearer "+t) + } res, err := HTTPClient.Do(req) if err != nil { diff --git a/vendor/github.com/getskillpack/skillget-manager/config.go b/vendor/github.com/getskillpack/skillget-manager/config.go index e94052d..754a0d3 100644 --- a/vendor/github.com/getskillpack/skillget-manager/config.go +++ b/vendor/github.com/getskillpack/skillget-manager/config.go @@ -54,3 +54,13 @@ func RegistryToken() string { } return trimToken(os.Getenv("SKILLGET_TOKEN")) } + +// RegistryReadBearer returns the Bearer token for GET /api/v1/* and archive downloads +// when the registry operator enabled REGISTRY_READ_TOKEN. SKILLGET_REGISTRY_READ_TOKEN +// wins when set; otherwise falls back to RegistryToken() so one secret can cover read+write. +func RegistryReadBearer() string { + if t := trimToken(os.Getenv("SKILLGET_REGISTRY_READ_TOKEN")); t != "" { + return t + } + return RegistryToken() +} diff --git a/vendor/github.com/getskillpack/skillget-manager/download.go b/vendor/github.com/getskillpack/skillget-manager/download.go index 32fe6fe..20128d9 100644 --- a/vendor/github.com/getskillpack/skillget-manager/download.go +++ b/vendor/github.com/getskillpack/skillget-manager/download.go @@ -81,6 +81,9 @@ func DownloadSkillArchive(ctx context.Context, spec string, opts DownloadSkillOp if err != nil { return nil, err } + if t := RegistryReadBearer(); t != "" { + req.Header.Set("Authorization", "Bearer "+t) + } res, err := HTTPClient.Do(req) if err != nil { return nil, fmt.Errorf("archive request failed: %w%s", err, archiveRequestNetworkHint()) diff --git a/vendor/github.com/getskillpack/skillget-manager/registry_errors.go b/vendor/github.com/getskillpack/skillget-manager/registry_errors.go index 7b3c9a6..4d78ad1 100644 --- a/vendor/github.com/getskillpack/skillget-manager/registry_errors.go +++ b/vendor/github.com/getskillpack/skillget-manager/registry_errors.go @@ -9,7 +9,7 @@ func registryHintForStatus(status int) string { case 400: return "\nHint: request may be invalid — check query params and paths against the registry API." case 401: - return "\nHint: set SKILLGET_REGISTRY_TOKEN (or SKILLGET_TOKEN) for authenticated requests." + return "\nHint: set SKILLGET_REGISTRY_READ_TOKEN for GET /api/v1 (read token), or SKILLGET_REGISTRY_TOKEN / SKILLGET_TOKEN if the same bearer is used for reads." case 403: return "\nHint: token may lack permission, or the registry blocks this operation for anonymous clients." case 404: diff --git a/vendor/github.com/getskillpack/skillget-manager/resolve.go b/vendor/github.com/getskillpack/skillget-manager/resolve.go index fd108b3..9e1e071 100644 --- a/vendor/github.com/getskillpack/skillget-manager/resolve.go +++ b/vendor/github.com/getskillpack/skillget-manager/resolve.go @@ -4,7 +4,10 @@ import ( "context" "fmt" "net/url" + "sort" "strings" + + "golang.org/x/mod/semver" ) // NameVersion holds a parsed skill spec "name" or "name@version". @@ -48,34 +51,32 @@ func ResolveInstallTarget(ctx context.Context, spec string) (*VersionDetail, err return &vd, nil } -// pickInstallableVersion chooses a version for an unpinned install. -// Prefers registry latest_version when present and not marked yanked in versions[]; -// otherwise the first non-yanked entry in versions (registry order). +// pickInstallableVersion chooses the highest semver among non-yanked versions in the +// skill detail map, matching reference registry logic (see getskillpack/registry filestore). func pickInstallableVersion(d SkillDetail) string { - if d.LatestVersion != nil { - candidate := strings.TrimSpace(*d.LatestVersion) - if candidate != "" && !versionMarkedYanked(d, candidate) { - return candidate - } + if len(d.Versions) == 0 { + return "" } - for _, v := range d.Versions { - if v.IsYanked { + var keys []string + for v, info := range d.Versions { + v = strings.TrimSpace(v) + if v == "" || info.Yanked { continue } - if v.Version != "" { - return v.Version - } + keys = append(keys, v) } - return "" + if len(keys) == 0 { + return "" + } + sort.Slice(keys, func(i, j int) bool { + return semver.Compare(canonicalSemver(keys[i]), canonicalSemver(keys[j])) < 0 + }) + return keys[len(keys)-1] } -// versionMarkedYanked reports whether v appears in d.Versions with is_yanked true. -// If v is not listed, returns false (trust latest_version from the registry). -func versionMarkedYanked(d SkillDetail, v string) bool { - for _, row := range d.Versions { - if row.Version == v { - return row.IsYanked - } +func canonicalSemver(v string) string { + if !strings.HasPrefix(v, "v") { + return "v" + v } - return false + return v } diff --git a/vendor/github.com/getskillpack/skillget-manager/types.go b/vendor/github.com/getskillpack/skillget-manager/types.go index 67e47ce..aab1034 100644 --- a/vendor/github.com/getskillpack/skillget-manager/types.go +++ b/vendor/github.com/getskillpack/skillget-manager/types.go @@ -1,5 +1,10 @@ package skillgetmanager +import ( + "encoding/json" + "time" +) + // SkillsLockfile is the on-disk lockfile shape (v1). type SkillsLockfile struct { LockfileVersion int `json:"lockfileVersion"` @@ -9,36 +14,41 @@ type SkillsLockfile struct { // ListSkillsResponse is the registry list/search JSON envelope. type ListSkillsResponse struct { Data []struct { - Name string `json:"name"` - Description string `json:"description,omitempty"` - Author string `json:"author,omitempty"` - LatestVersion string `json:"latest_version,omitempty"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Author string `json:"author,omitempty"` + LatestVersion string `json:"latest_version,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` } `json:"data"` Meta *struct { - Total int `json:"total,omitempty"` + Total int `json:"total,omitempty"` + Limit int `json:"limit,omitempty"` + Offset int `json:"offset,omitempty"` } `json:"meta,omitempty"` } -// VersionDetail is returned for a concrete skill version (install target). +// VersionPublicInfo matches GET /skills/{name} → versions[version] in registry-api.md. +type VersionPublicInfo struct { + Manifest json.RawMessage `json:"manifest,omitempty"` + Checksum string `json:"checksum,omitempty"` + ArchiveURL string `json:"archive_url,omitempty"` + PublishedAt time.Time `json:"published_at,omitempty"` + Yanked bool `json:"yanked,omitempty"` +} + +// SkillDetail is returned by GET /skills/{name} (registry compiled-core contract). +type SkillDetail struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + Author string `json:"author,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + Versions map[string]VersionPublicInfo `json:"versions,omitempty"` +} + +// VersionDetail is returned for GET /skills/{name}/versions/{version} (install target). type VersionDetail struct { Name string `json:"name"` Version string `json:"version"` ArchiveURL string `json:"archive_url"` Checksum string `json:"checksum,omitempty"` } - -// SkillDetail is skill metadata from the registry. -type SkillDetail struct { - Name string `json:"name"` - RepositoryURL *string `json:"repository_url"` - Homepage *string `json:"homepage"` - Dependencies []struct { - Name string `json:"name"` - Range string `json:"range,omitempty"` - } `json:"dependencies,omitempty"` - LatestVersion *string `json:"latest_version"` - Versions []struct { - Version string `json:"version"` - IsYanked bool `json:"is_yanked,omitempty"` - } `json:"versions,omitempty"` -} diff --git a/vendor/golang.org/x/mod/LICENSE b/vendor/golang.org/x/mod/LICENSE new file mode 100644 index 0000000..2a7cf70 --- /dev/null +++ b/vendor/golang.org/x/mod/LICENSE @@ -0,0 +1,27 @@ +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/mod/PATENTS b/vendor/golang.org/x/mod/PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/vendor/golang.org/x/mod/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/mod/semver/semver.go b/vendor/golang.org/x/mod/semver/semver.go new file mode 100644 index 0000000..9a2dfd3 --- /dev/null +++ b/vendor/golang.org/x/mod/semver/semver.go @@ -0,0 +1,401 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package semver implements comparison of semantic version strings. +// In this package, semantic version strings must begin with a leading "v", +// as in "v1.0.0". +// +// The general form of a semantic version string accepted by this package is +// +// vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]] +// +// where square brackets indicate optional parts of the syntax; +// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros; +// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers +// using only alphanumeric characters and hyphens; and +// all-numeric PRERELEASE identifiers must not have leading zeros. +// +// This package follows Semantic Versioning 2.0.0 (see semver.org) +// with two exceptions. First, it requires the "v" prefix. Second, it recognizes +// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes) +// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0. +package semver + +import "sort" + +// parsed returns the parsed form of a semantic version string. +type parsed struct { + major string + minor string + patch string + short string + prerelease string + build string +} + +// IsValid reports whether v is a valid semantic version string. +func IsValid(v string) bool { + _, ok := parse(v) + return ok +} + +// Canonical returns the canonical formatting of the semantic version v. +// It fills in any missing .MINOR or .PATCH and discards build metadata. +// Two semantic versions compare equal only if their canonical formattings +// are identical strings. +// The canonical invalid semantic version is the empty string. +func Canonical(v string) string { + p, ok := parse(v) + if !ok { + return "" + } + if p.build != "" { + return v[:len(v)-len(p.build)] + } + if p.short != "" { + return v + p.short + } + return v +} + +// Major returns the major version prefix of the semantic version v. +// For example, Major("v2.1.0") == "v2". +// If v is an invalid semantic version string, Major returns the empty string. +func Major(v string) string { + pv, ok := parse(v) + if !ok { + return "" + } + return v[:1+len(pv.major)] +} + +// MajorMinor returns the major.minor version prefix of the semantic version v. +// For example, MajorMinor("v2.1.0") == "v2.1". +// If v is an invalid semantic version string, MajorMinor returns the empty string. +func MajorMinor(v string) string { + pv, ok := parse(v) + if !ok { + return "" + } + i := 1 + len(pv.major) + if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor { + return v[:j] + } + return v[:i] + "." + pv.minor +} + +// Prerelease returns the prerelease suffix of the semantic version v. +// For example, Prerelease("v2.1.0-pre+meta") == "-pre". +// If v is an invalid semantic version string, Prerelease returns the empty string. +func Prerelease(v string) string { + pv, ok := parse(v) + if !ok { + return "" + } + return pv.prerelease +} + +// Build returns the build suffix of the semantic version v. +// For example, Build("v2.1.0+meta") == "+meta". +// If v is an invalid semantic version string, Build returns the empty string. +func Build(v string) string { + pv, ok := parse(v) + if !ok { + return "" + } + return pv.build +} + +// Compare returns an integer comparing two versions according to +// semantic version precedence. +// The result will be 0 if v == w, -1 if v < w, or +1 if v > w. +// +// An invalid semantic version string is considered less than a valid one. +// All invalid semantic version strings compare equal to each other. +func Compare(v, w string) int { + pv, ok1 := parse(v) + pw, ok2 := parse(w) + if !ok1 && !ok2 { + return 0 + } + if !ok1 { + return -1 + } + if !ok2 { + return +1 + } + if c := compareInt(pv.major, pw.major); c != 0 { + return c + } + if c := compareInt(pv.minor, pw.minor); c != 0 { + return c + } + if c := compareInt(pv.patch, pw.patch); c != 0 { + return c + } + return comparePrerelease(pv.prerelease, pw.prerelease) +} + +// Max canonicalizes its arguments and then returns the version string +// that compares greater. +// +// Deprecated: use [Compare] instead. In most cases, returning a canonicalized +// version is not expected or desired. +func Max(v, w string) string { + v = Canonical(v) + w = Canonical(w) + if Compare(v, w) > 0 { + return v + } + return w +} + +// ByVersion implements [sort.Interface] for sorting semantic version strings. +type ByVersion []string + +func (vs ByVersion) Len() int { return len(vs) } +func (vs ByVersion) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] } +func (vs ByVersion) Less(i, j int) bool { + cmp := Compare(vs[i], vs[j]) + if cmp != 0 { + return cmp < 0 + } + return vs[i] < vs[j] +} + +// Sort sorts a list of semantic version strings using [ByVersion]. +func Sort(list []string) { + sort.Sort(ByVersion(list)) +} + +func parse(v string) (p parsed, ok bool) { + if v == "" || v[0] != 'v' { + return + } + p.major, v, ok = parseInt(v[1:]) + if !ok { + return + } + if v == "" { + p.minor = "0" + p.patch = "0" + p.short = ".0.0" + return + } + if v[0] != '.' { + ok = false + return + } + p.minor, v, ok = parseInt(v[1:]) + if !ok { + return + } + if v == "" { + p.patch = "0" + p.short = ".0" + return + } + if v[0] != '.' { + ok = false + return + } + p.patch, v, ok = parseInt(v[1:]) + if !ok { + return + } + if len(v) > 0 && v[0] == '-' { + p.prerelease, v, ok = parsePrerelease(v) + if !ok { + return + } + } + if len(v) > 0 && v[0] == '+' { + p.build, v, ok = parseBuild(v) + if !ok { + return + } + } + if v != "" { + ok = false + return + } + ok = true + return +} + +func parseInt(v string) (t, rest string, ok bool) { + if v == "" { + return + } + if v[0] < '0' || '9' < v[0] { + return + } + i := 1 + for i < len(v) && '0' <= v[i] && v[i] <= '9' { + i++ + } + if v[0] == '0' && i != 1 { + return + } + return v[:i], v[i:], true +} + +func parsePrerelease(v string) (t, rest string, ok bool) { + // "A pre-release version MAY be denoted by appending a hyphen and + // a series of dot separated identifiers immediately following the patch version. + // Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. + // Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes." + if v == "" || v[0] != '-' { + return + } + i := 1 + start := 1 + for i < len(v) && v[i] != '+' { + if !isIdentChar(v[i]) && v[i] != '.' { + return + } + if v[i] == '.' { + if start == i || isBadNum(v[start:i]) { + return + } + start = i + 1 + } + i++ + } + if start == i || isBadNum(v[start:i]) { + return + } + return v[:i], v[i:], true +} + +func parseBuild(v string) (t, rest string, ok bool) { + if v == "" || v[0] != '+' { + return + } + i := 1 + start := 1 + for i < len(v) { + if !isIdentChar(v[i]) && v[i] != '.' { + return + } + if v[i] == '.' { + if start == i { + return + } + start = i + 1 + } + i++ + } + if start == i { + return + } + return v[:i], v[i:], true +} + +func isIdentChar(c byte) bool { + return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-' +} + +func isBadNum(v string) bool { + i := 0 + for i < len(v) && '0' <= v[i] && v[i] <= '9' { + i++ + } + return i == len(v) && i > 1 && v[0] == '0' +} + +func isNum(v string) bool { + i := 0 + for i < len(v) && '0' <= v[i] && v[i] <= '9' { + i++ + } + return i == len(v) +} + +func compareInt(x, y string) int { + if x == y { + return 0 + } + if len(x) < len(y) { + return -1 + } + if len(x) > len(y) { + return +1 + } + if x < y { + return -1 + } else { + return +1 + } +} + +func comparePrerelease(x, y string) int { + // "When major, minor, and patch are equal, a pre-release version has + // lower precedence than a normal version. + // Example: 1.0.0-alpha < 1.0.0. + // Precedence for two pre-release versions with the same major, minor, + // and patch version MUST be determined by comparing each dot separated + // identifier from left to right until a difference is found as follows: + // identifiers consisting of only digits are compared numerically and + // identifiers with letters or hyphens are compared lexically in ASCII + // sort order. Numeric identifiers always have lower precedence than + // non-numeric identifiers. A larger set of pre-release fields has a + // higher precedence than a smaller set, if all of the preceding + // identifiers are equal. + // Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < + // 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0." + if x == y { + return 0 + } + if x == "" { + return +1 + } + if y == "" { + return -1 + } + for x != "" && y != "" { + x = x[1:] // skip - or . + y = y[1:] // skip - or . + var dx, dy string + dx, x = nextIdent(x) + dy, y = nextIdent(y) + if dx != dy { + ix := isNum(dx) + iy := isNum(dy) + if ix != iy { + if ix { + return -1 + } else { + return +1 + } + } + if ix { + if len(dx) < len(dy) { + return -1 + } + if len(dx) > len(dy) { + return +1 + } + } + if dx < dy { + return -1 + } else { + return +1 + } + } + } + if x == "" { + return -1 + } else { + return +1 + } +} + +func nextIdent(x string) (dx, rest string) { + i := 0 + for i < len(x) && x[i] != '.' { + i++ + } + return x[:i], x[i:] +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 1ed0597..91eaef2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,3 +1,6 @@ -# github.com/getskillpack/skillget-manager v0.1.3 -## explicit; go 1.22 +# github.com/getskillpack/skillget-manager v0.1.4 +## explicit; go 1.22.0 github.com/getskillpack/skillget-manager +# golang.org/x/mod v0.23.0 +## explicit; go 1.22.0 +golang.org/x/mod/semver