Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add cargo-update-toml #296

Merged
merged 1 commit into from
Feb 22, 2024
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"workspaces": {
"packages": [
"VKUI/*",
"vkui-tokens/*"
"vkui-tokens/*",
"shared/rust/cargo-update-toml"
]
},
"scripts": {
Expand Down
10 changes: 10 additions & 0 deletions shared/rust/cargo-update-toml/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
root: false,
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
rules: {
'@typescript-eslint/no-magic-numbers': 'off',
},
};
10 changes: 10 additions & 0 deletions shared/rust/cargo-update-toml/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: 'Cargo update toml'
description: 'Update dependency in Cargo.toml'
inputs:
packages:
required: true
description: 'Packages that need to be updated'

runs:
using: 'node20'
main: 'dist/index.js'
3 changes: 3 additions & 0 deletions shared/rust/cargo-update-toml/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import config from '../../../jest.config';

export default config;
19 changes: 19 additions & 0 deletions shared/rust/cargo-update-toml/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@actions-internal/shared-rust-cargo-update-toml",
"version": "0.0.0",
"main": "src/main.ts",
"license": "MIT",
"private": true,
"devDependencies": {
"@types/node": "^20.11.19",
"typescript": "^5.3.3"
},
"dependencies": {
"@actions/core": "^1.10.1"
},
"scripts": {
"prebuild": "shx rm -rf dist/*",
"build": "esbuild ./src/main.ts --bundle --outfile=dist/index.js --platform=node",
"test": "jest --passWithNoTests"
}
}
12 changes: 12 additions & 0 deletions shared/rust/cargo-update-toml/src/getLastLine.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { expect, test } from '@jest/globals';
import { Readable } from 'node:stream';
import { getLastLine } from './getLastLine';

test('getLastLine', async () => {
const stream = new Readable();

stream.push([1, 2, 3, 4].join('\n'));
stream.push(null);

expect(await getLastLine(stream)).toBe('4');
});
21 changes: 21 additions & 0 deletions shared/rust/cargo-update-toml/src/getLastLine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as readline from 'node:readline/promises';

/**
* Возвращает последнюю строку из стрима
*/
export function getLastLine(input: NodeJS.ReadableStream) {
return new Promise<string>((resolve, reject) => {
const rl = readline.createInterface(input);

let lastLine = '';
rl.on('line', function (line) {
lastLine = line;
});

rl.on('error', reject);

rl.on('close', function () {
resolve(lastLine);
});
});
}
55 changes: 55 additions & 0 deletions shared/rust/cargo-update-toml/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as fs from 'node:fs/promises';
import * as core from '@actions/core';
import { versionStyle } from './versionStyle';
import { cargoRegistryLastIndexPackage } from './registry';

/**
* Возвращает последнюю версию crate пакета
*/
async function crateRegistryVersion(name: string) {
const index = await cargoRegistryLastIndexPackage(name);
return index.vers;
}

/**
* Обновляет зависимость
*/
async function updateDependency(data: string, name: string) {
const lastVersion = await crateRegistryVersion(name);
const searchValue = new RegExp(
`(${name}[\\s]+=[\\s\\n]+(\\{.*?version\\s+=\\s+|))"([\\d.]+)"`,
'm',
);

const match = data.match(searchValue);
if (match === null) {
throw new Error('Dependency not found in Cargo.toml');
}

const replaceValue = `$1"${versionStyle(match[3], lastVersion)}"`;

return data.replace(searchValue, replaceValue);
}

async function run() {
try {
const packages = core.getInput('packages', { required: true }).split(/[\n\,]/);
const filepath = 'Cargo.toml';
const encoding = 'utf8';

const data = await fs.readFile(filepath, encoding);

let result = data;
for (const name of packages) {
result = await updateDependency(result, name);
}

await fs.writeFile(filepath, result, encoding);
} catch (error) {
if (error instanceof Error) {
core.setFailed(error.message);
}
}
}

void run();
9 changes: 9 additions & 0 deletions shared/rust/cargo-update-toml/src/registry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { expect, test } from '@jest/globals';

import { cargoRegistryLastIndexPackage } from './registry';

test('cargoRegistryLastIndexPackage get md4', async () => {
const lastPackage = await cargoRegistryLastIndexPackage('md4');

expect(lastPackage.name).toBe('md4');
});
183 changes: 183 additions & 0 deletions shared/rust/cargo-update-toml/src/registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import * as readline from 'node:readline/promises';
import * as https from 'node:https';
import { getLastLine } from './getLastLine';

const HTTPStatusOK = 200;

/**
* HTTPS get запрос обернутый в промис
*/
async function get(url: string | https.RequestOptions | URL) {
return new Promise<NodeJS.ReadableStream>((resolve, reject) => {
https
.get(url, (response) => {
if (response.statusCode !== HTTPStatusOK) {
response.resume();
reject(new Error(`Get "${url}" failed. HTTP status is ${response.statusCode}`));

return;
}

resolve(response);
})
.on('error', reject);
});
}

/**
* Возвращает url для crate пакета
*/
function indexPackageURL(name: string) {
const url = ['https://raw.githubusercontent.com/rust-lang/crates.io-index/master'];

if (name.length <= 3) {
url.push(name.length.toString(), name[0]);
} else {
url.push(name.substring(0, 2), name.substring(2, 4));
}

url.push(name);

return url.join('/');
}

type Features = Record<string, string[]>;

// A single line in the index representing a single version of a package.
//
// https://github.com/rust-lang/cargo/blob/e7ff7a6618ad6f35372da1777f96dfb5716fded9/src/cargo/sources/registry/index.rs#L336
export interface IndexPackage {
// Name of the package.
name: string;
// The version of this dependency.
vers: string;
// All kinds of direct dependencies of the package, including dev and
// build dependencies.
deps: RegistryDependency[];
// Set of features defined for the package, i.e., `[features]` table.
features: Features;
// This field contains features with new, extended syntax. Specifically,
// namespaced features (`dep:`) and weak dependencies (`pkg?/feat`).
//
// This is separated from `features` because versions older than 1.19
// will fail to load due to not being able to parse the new syntax, even
// with a `Cargo.lock` file.
features2?: Features;
// Checksum for verifying the integrity of the corresponding downloaded package.
cksum: string;
// If `true`, Cargo will skip this version when resolving.
//
// This was added in 2014. Everything in the crates.io index has this set
// now, so this probably doesn't need to be an option anymore.
yanked?: boolean;
// Native library name this package links to.
//
// Added early 2018 (see <https://github.com/rust-lang/cargo/pull/4978>),
// can be `None` if published before then.
links?: string;
// Required version of rust
//
// Corresponds to `package.rust-version`.
//
// Added in 2023 (see <https://github.com/rust-lang/crates.io/pull/6267>),
// can be `None` if published before then or if not set in the manifest.
rust_version?: string;
// The schema version for this entry.
//
// If this is None, it defaults to version `1`. Entries with unknown
// versions are ignored.
//
// Version `2` schema adds the `features2` field.
//
// Version `3` schema adds `artifact`, `bindep_targes`, and `lib` for
// artifact dependencies support.
//
// This provides a method to safely introduce changes to index entries
// and allow older versions of cargo to ignore newer entries it doesn't
// understand. This is honored as of 1.51, so unfortunately older
// versions will ignore it, and potentially misinterpret version 2 and
// newer entries.
//
// The intent is that versions older than 1.51 will work with a
// pre-existing `Cargo.lock`, but they may not correctly process `cargo
// update` or build a lock from scratch. In that case, cargo may
// incorrectly select a new package that uses a new index schema. A
// workaround is to downgrade any packages that are incompatible with the
// `--precise` flag of `cargo update`.
v?: number;
}

// A dependency as encoded in the [`IndexPackage`] index JSON.
export interface RegistryDependency {
// Name of the dependency. If the dependency is renamed, the original
// would be stored in [`RegistryDependency::package`].
name: string;
// The SemVer requirement for this dependency.
req: string;
// Set of features enabled for this dependency.
features: string[];
// Whether or not this is an optional dependency.
optional: boolean;
// Whether or not default features are enabled.
default_features: boolean;
// The target platform for this dependency.
target?: string;
// The dependency kind. "dev", "build", and "normal"
kind?: 'dev' | 'build' | 'normal';
// The URL of the index of the registry where this dependency is from.
// `None` if it is from the same index.
registry?: string;
// The original name if the dependency is renamed.
package?: string;
// Whether or not this is a public dependency. Unstable. See [RFC 1977].
//
// [RFC 1977]: https://rust-lang.github.io/rfcs/1977-public-private-dependencies.html
public?: boolean;
artifact?: string[];
bindep_target?: string;
lib?: boolean;
}

/**
* Парсит индекс из стрима построчно
*/
export function parsePackagesFromStream(input: NodeJS.ReadableStream) {
return new Promise<IndexPackage[]>((resolve, reject) => {
const rl = readline.createInterface(input);

const packages: IndexPackage[] = [];
rl.on('line', function (line) {
packages.push(JSON.parse(line));
});

rl.on('error', reject);

rl.on('close', function () {
resolve(packages);
});
});
}

/**
* Возвращает все версии пакета из индекса.
*/
export async function cargoRegistryIndexPackages(name: string): Promise<IndexPackage[]> {
const url = indexPackageURL(name);

const stream = await get(url);

return await parsePackagesFromStream(stream);
}

/**
* Возвращает последнюю информацию о пакете
*/
export async function cargoRegistryLastIndexPackage(name: string): Promise<IndexPackage> {
const url = indexPackageURL(name);

const stream = await get(url);

const lastLine = await getLastLine(stream);

return JSON.parse(lastLine);
}
20 changes: 20 additions & 0 deletions shared/rust/cargo-update-toml/src/versionStyle.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { expect, test } from '@jest/globals';
import { versionStyle } from './versionStyle';

test.each([
{ version: '0', newVersion: '1.2.3', expected: '1' },
{ version: '0.1', newVersion: '1.2.3', expected: '1.2' },
{ version: '0.1.2', newVersion: '1.2.3', expected: '1.2.3' },
{ version: '^0.1.2', newVersion: '1.2.3', expected: '^1.2.3' },
{ version: '~0', newVersion: '1.2.3', expected: '~1' },
{ version: '~0.1', newVersion: '1.2.3', expected: '~1.2' },
{ version: '~0.1.2', newVersion: '1.2.3', expected: '~1.2.3' },
{ version: '*', newVersion: '1.2.3', expected: '*' },
{ version: '0.*', newVersion: '1.2.3', expected: '1.*' },
{ version: '0.1.*', newVersion: '1.2.3', expected: '1.2.*' },
])(
'versionStyle("$version", "$newVersion") is "$expected"',
({ version, newVersion, expected }) => {
expect(versionStyle(version, newVersion)).toBe(expected);
},
);
Loading