Skip to content

Commit

Permalink
Merge pull request #61 from Araxeus/commit-hash-versioning
Browse files Browse the repository at this point in the history
  • Loading branch information
Araxeus authored May 31, 2024
2 parents 28b3b69 + e27c70f commit 7f70bfa
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 49 deletions.
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ But that's not all - Vendorfiles is not limited to managing text files - it can

- [Installation](#installation)
- [Configuration](#configuration)
- [Versioning Dependencies](#versioning-dependencies)
- [GitHub Releases](#github-releases)
- [Default Configuration](#default-configuration)
- [Commands](#commands)
- [Sync](#sync)
- [Update](#update)
Expand Down Expand Up @@ -125,6 +127,41 @@ To rename or move files, you can specify an object with the source file as the k
}
```

### Versioning Dependencies

This project uses GitHub releases to determine the version of a dependency. When a new release is made on GitHub, the version of the dependency in this project is updated accordingly, and the files are based on the tag of that release.

However, there is an optional `hashVersionFile` key for each dependency that allows for a different versioning strategy. If `hashVersionFile` is specified, the version is based on the latest commit hash of the file specified by hashVersionFile.

The `hashVersionFile` key can be either:

- A string: In this case, it should be the path to the file in the dependency repository. The version of the dependency will be the latest commit hash of this file.

- A boolean: If `hashVersionFile` is set to true, the path of the first file provided in the file list for that dependency will be used. The version of the dependency will be the latest commit hash of this file.

This versioning strategy allows for more granular control over the version of a dependency, as it can be updated whenever a specific file in the dependency repository changes.

```json
{
"vendorDependencies": {
"Cooltipz": {
"repository": "https://github.com/jackdomleo7/Cooltipz.css",
"version": "f6ec482ea395cead4fd849c05df6edd8da284a52",
"hashVersionFile": "package.json",
"files": ["cooltipz.min.css", "package.json"],
},
"Coloris": {
"repository": "https://github.com/mdbassit/Coloris",
"version": "v0.17.1",
"hashVersionFile": true,
"files": ["dist/coloris.min.js"],
}
}
}
```

> in this example, the version of Cooltipz will be the latest commit hash of the `package.json` file, <br> and the version of Coloris will be the latest commit hash of the `dist/coloris.min.js` file.
### GitHub Releases

You can download release assets by using the `{release}/` placeholder in the file path.
Expand Down Expand Up @@ -171,6 +208,28 @@ To extract files from a compressed release archive, you can define an object tha
}
```

## Default Configuration

For shared options across dependencies, use a `default` object at the same level as `vendorConfig` and `vendorDependencies`. Here's an example:

```yml
vendorConfig:
vendorFolder: .
default:
vendorFolder: "{vendorFolder}"
repository: https://github.com/nushell/nu_scripts
hashVersionFile: true
vendorDependencies:
nu-winget-completions:
files: custom-completions/winget/winget-completions.nu
version: 912bea4588ba089aebe956349488e7f78e56061c
nu-cargo-completions:
files: custom-completions/cargo/cargo-completions.nu
version: afde2592a6254be7c14ccac520cb608bd1adbaf9
```
In this example, the `default` object specifies the `vendorFolder`, `repository`, and `hashVersionFile` options. These options will be applied to all dependencies listed under `vendorDependencies`, unless they are overridden in the individual dependency configuration.

## Commands

```text
Expand Down
20 changes: 8 additions & 12 deletions lib/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
flatFiles,
getDependencyFolder,
getFilesFromLockfile,
getNewVersion,
green,
info,
ownerAndNameFromRepoUrl,
Expand Down Expand Up @@ -206,12 +207,9 @@ export async function install({
}

if (!newVersion) {
const latestRelease = await github.getLatestRelease(repo);
newVersion = latestRelease.tag_name as string;
newVersion = await getNewVersion(dependency, repo, showOutdatedOnly);
}

assert(!!newVersion, `Could not find a version for ${dependency.name}`);

const needUpdate =
force ||
(await checkIfNeedsUpdate({
Expand Down Expand Up @@ -312,7 +310,9 @@ export async function install({
error(
`${err.toString()}:\nCould not download file "${
typeof file === 'string' ? file : file[0]
}" from ${dependency.repository}`,
}" from ${
dependency.repository
} with version ${ref}`,
);
}
});
Expand Down Expand Up @@ -409,8 +409,8 @@ export async function install({
await writeLockfile(
dependency.name,
{
version: newVersion,
repository: dependency.repository,
version: newVersion,
files: dependency.files,
},
lockfilePath,
Expand All @@ -419,12 +419,8 @@ export async function install({
const oldVersion = dependency.version;

if (newVersion !== oldVersion) {
configFile.vendorDependencies[dependency.name] = {
version: newVersion,
repository: dependency.repository,
files: dependency.files,
vendorFolder: dependency.vendorFolder,
} as VendorDependency;
configFile.vendorDependencies[dependency.name].version = newVersion;

await writeConfig({
configFile,
configFileSettings,
Expand Down
13 changes: 13 additions & 0 deletions lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,19 @@ export async function getConfig(): Promise<VendorsOptions> {
`Invalid vendorConfig key in ${configFile.path}`,
);

const defaultOptions = configFile.data.default || {};

for (const depName of Object.keys(dependencies)) {
const files = dependencies[depName].files;
if (typeof files === 'string') {
dependencies[depName].files = [files];
}
for (const defaultKey of Object.keys(defaultOptions)) {
// @ts-expect-error expression of type 'string' can't be used to index type 'VendorDependency'
dependencies[depName][defaultKey] ??= defaultOptions[defaultKey];
}
}

res = {
dependencies,
config,
Expand Down
25 changes: 23 additions & 2 deletions lib/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,35 @@ export async function getLatestRelease({ owner, name: repo }: Repository) {
return res.data;
}

export async function getFileCommitSha({
repo,
path,
}: {
repo: Repository;
path: string;
}) {
const commit = await octokit().repos.listCommits({
owner: repo.owner,
repo: repo.name,
path,
per_page: 1,
});
if (!commit.data?.[0]?.sha) {
error(`No commits found for ${repo.owner}/${repo.name}: ${path}`);
}
return commit.data[0].sha;
}

export async function getFile({
repo,
path,
ref,
}: {
repo: Repository;
path: string;
ref: string;
ref: string | undefined;
}) {
ref = ref || undefined;
const requestOptions = octokit().repos.getContent.endpoint({
owner: repo.owner,
repo: repo.name,
Expand All @@ -94,7 +114,7 @@ export async function getFile({
const req = await fetch(requestOptions.url, requestOptions);

if (!(req.ok && req.body)) {
throw 'Request failed';
throw `Request failed with status ${req.status}`;
}

return req.body;
Expand Down Expand Up @@ -223,6 +243,7 @@ export async function login(token?: string) {
export default {
login,
getFile,
getFileCommitSha,
getLatestRelease,
downloadReleaseFile,
findRepoUrl,
Expand Down
10 changes: 10 additions & 0 deletions lib/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type VendorsOptions = {
export type ConfigFile = {
vendorConfig: VendorConfig;
vendorDependencies: VendorDependencies;
default?: DefaultOptions;
[key: string]: unknown;
};

Expand All @@ -32,10 +33,19 @@ export type FileInputOutput = {

export type FilesArray = (string | FileInputOutput)[];

export type DefaultOptions = {
repository?: string;
files?: FilesArray;
hashVersionFile?: string | boolean;
vendorFolder?: string;
version?: string;
};

export type VendorDependency = {
repository: string;
files: FilesArray;
version?: string;
hashVersionFile?: string | boolean;
name?: string;
vendorFolder?: string;
};
Expand Down
56 changes: 54 additions & 2 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { finished } from 'node:stream/promises';
import parseJson from 'parse-json';

import { getConfig, getRunOptions } from './config.js';
import github from './github.js';

export function assert(condition: boolean, message: string): asserts condition {
if (!condition) {
Expand Down Expand Up @@ -96,16 +97,16 @@ export function replaceVersion(path: string, version: string) {
export async function writeLockfile(
name: string,
data: {
version: string;
repository: string;
version: string;
files: FilesArray;
},
filepath: string,
): Promise<void> {
let lockfile: Lockfile;
const vendorLock: VendorLock = {
version: data.version,
repository: data.repository,
version: data.version,
files: configFilesToVendorlockFiles(data.files, data.version),
};

Expand All @@ -119,6 +120,57 @@ export async function writeLockfile(
await writeFile(filepath, JSON.stringify(lockfile, null, 2));
}

export async function getNewVersion(
dependency: VendorDependency,
repo: Repository,
showOutdatedOnly?: boolean,
): Promise<string> {
let newVersion: string;

if (dependency.hashVersionFile) {
let hashVersionFile = dependency.hashVersionFile;
if (hashVersionFile === true) {
if (typeof dependency.files[0] === 'string') {
hashVersionFile = dependency.files[0];
} else if (typeof dependency.files[0] === 'object') {
hashVersionFile = Object.keys(dependency.files[0])[0];
} else {
error(
`files[0] is invalid for hashVersionFile, must be a string or an object - got ${typeof dependency
.files[0]}`,
);
}
}
if (typeof hashVersionFile === 'string') {
const fileCommitSha = await github
.getFileCommitSha({
repo,
path: hashVersionFile,
})
.catch((err) => {
error(
`Error while getting commit sha for ${hashVersionFile}:\n${err}`,
);
});
newVersion = fileCommitSha;
} else {
error('hashVersionFile is invalid, must be a string or true');
}
} else {
try {
const latestRelease = await github.getLatestRelease(repo);
newVersion = latestRelease.tag_name as string;
} catch {
if (showOutdatedOnly) {
error(`Could not find a version for ${dependency.name}`);
}
newVersion = '';
}
}

return newVersion;
}

export async function checkIfNeedsUpdate({
lockfilePath,
name,
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "vendorfiles",
"author": "Araxeus",
"description": "A CLI tool to manage vendor files",
"version": "1.1.6",
"version": "1.2.0",
"type": "module",
"license": "MIT",
"repository": "https://github.com/Araxeus/vendorfiles",
Expand Down Expand Up @@ -41,7 +41,7 @@
"publish": "yarn check && yarn npm publish"
},
"dependencies": {
"@commander-js/extra-typings": "^12.0.1",
"@commander-js/extra-typings": "^12.1.0",
"@ltd/j-toml": "^1.38.0",
"@octokit/auth-oauth-device": "^7.1.1",
"@octokit/rest": "^20.1.1",
Expand All @@ -52,17 +52,17 @@
"make-fetch-happen": "^13.0.1",
"open": "^10.1.0",
"parse-json": "^8.1.0",
"unarchive": "^1.1.1",
"unarchive": "^1.1.2",
"yaml": "^2.4.2"
},
"devDependencies": {
"@biomejs/biome": "^1.7.3",
"@types/make-fetch-happen": "^10.0.4",
"@types/node": "^20.12.12",
"@types/node": "^20.12.13",
"@types/parse-json": "^4.0.2",
"cpy-cli": "^5.0.0",
"del-cli": "^5.1.0",
"type-fest": "^4.18.2",
"type-fest": "^4.18.3",
"typescript": "=5.0.4"
},
"vendorDependencies": {
Expand Down
Loading

0 comments on commit 7f70bfa

Please sign in to comment.