diff --git a/package.json b/package.json index 4a6e012b..2fb49851 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "temp-dir": "^3.0.0", "typescript": "^5.9.3", "typescript-eslint": "^8.46.2", - "vitest": "^4.0.6" + "vitest": "^4.0.15" }, "packageManager": "yarn@4.10.3" } diff --git a/src/execute/create-temporary-directory.ts b/src/execute/create-temporary-directory.ts index 90979382..5c449b9a 100644 --- a/src/execute/create-temporary-directory.ts +++ b/src/execute/create-temporary-directory.ts @@ -1,27 +1,29 @@ -import fs from 'node:fs/promises' -import os from 'node:os' -import path from 'node:path'; -import crypto from "node:crypto"; - -export async function createTemporaryDirectory() { - const directory = path.join( - // The following quoted from https://github.com/es-tooling/module-replacements/blob/27d1acd38f19741e31d2eae561a5c8a914373fc5/docs/modules/tempy.md?plain=1#L20-L21, not sure if it's true - // MacOS and possibly some other platforms return a symlink from `os.tmpdir`. - // For some applications, this can cause problems; thus, we use `realpath`. - await fs.realpath(os.tmpdir()), - crypto.randomBytes(16).toString("hex"), - ); - - fs.mkdir(directory); - - return { - path: directory, - dispatch: () => destroyTemporaryDirectory(directory) - }; -} - -async function destroyTemporaryDirectory(directory: string) { - await fs.rm(directory, {recursive: true, force: true}) -} - -export type TemporaryDirectory = Awaited> \ No newline at end of file +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import crypto from "node:crypto"; + +export async function createTemporaryDirectory() { + const directory = path.join( + // The following quoted from https://github.com/es-tooling/module-replacements/blob/27d1acd38f19741e31d2eae561a5c8a914373fc5/docs/modules/tempy.md?plain=1#L20-L21, not sure if it's true + // MacOS and possibly some other platforms return a symlink from `os.tmpdir`. + // For some applications, this can cause problems; thus, we use `realpath`. + await fs.realpath(os.tmpdir()), + crypto.randomBytes(16).toString("hex"), + ); + + fs.mkdir(directory); + + return { + path: directory, + dispatch: () => destroyTemporaryDirectory(directory), + }; +} + +async function destroyTemporaryDirectory(directory: string) { + await fs.rm(directory, { recursive: true, force: true }); +} + +export type TemporaryDirectory = Awaited< + ReturnType +>; diff --git a/src/execute/index.ts b/src/execute/index.ts index 0937f38f..505241be 100644 --- a/src/execute/index.ts +++ b/src/execute/index.ts @@ -6,7 +6,7 @@ import * as configuration from "../configuration.ts"; import * as git from "../tools/git.ts"; import * as logger from "../logger.ts"; import { getProjects, getTargetRepositoryPath } from "../projects.ts"; -import {installPrettier} from './install-prettier.ts' +import { installPrettier } from "./install-prettier.ts"; export interface ExecuteResultEntry { commitHash: string; @@ -42,7 +42,7 @@ export async function execute({ }), ); - await originalPrettier.dispatch() + await originalPrettier.dispatch(); // Setup alternativeVersionPrettier await logger.log("Setting up alternativeVersionPrettier..."); @@ -54,7 +54,7 @@ export async function execute({ await runPrettier(alternativePrettier, project); }), ); - await alternativePrettier.dispatch() + await alternativePrettier.dispatch(); const diffs = await Promise.all( projects.map(getTargetRepositoryPath).map(git.diffRepository), diff --git a/src/execute/install-prettier.ts b/src/execute/install-prettier.ts index b8924492..67fc289f 100644 --- a/src/execute/install-prettier.ts +++ b/src/execute/install-prettier.ts @@ -1,67 +1,67 @@ -import path from "node:path"; -import { - PrettierVersion, - PrettierPullRequest, - sourceTypes -} from "../parse.ts"; -import * as configuration from "../configuration.ts"; -import * as brew from "../tools/brew.ts"; -import * as gh from "../tools/gh.ts"; -import * as yarn from "../tools/yarn.ts"; -import * as npm from "../tools/npm.ts"; -import * as unix from "../tools/unix.ts"; -import {createTemporaryDirectory, type TemporaryDirectory} from './create-temporary-directory.ts' - -export type InstalledPrettier = Awaited> - -export async function installPrettier( - prettierVersion: PrettierVersion, -) { - let version: string - let pullRequestDirectory: TemporaryDirectory | undefined - if (prettierVersion.type === sourceTypes.pullRequest) { - ({version, directory: pullRequestDirectory} = await getPullRequest(prettierVersion.number) ) - } else { - ({version} = prettierVersion) - } - - - const directory = await createTemporaryDirectory() - const {path: cwd} = directory - await yarn.init(['-y'], {cwd}) - await yarn.add([`prettier@${version}`],{cwd}) - - await pullRequestDirectory?.dispatch() - - return { - dispatch: () => {directory.dispatch()}, - bin: path.join(cwd, 'node_modules/prettier/bin/prettier.cjs'), - name: prettierVersion.raw - } -} - -async function existsGh() { - return !(await unix.which("gh")).includes("gh not found"); -} - -async function getPullRequest( - pullRequestNumber: PrettierPullRequest['number'], -) { - if (!(await existsGh())) { - await brew.install("gh"); - } - if (configuration.authToken !== "nothing") { - // running locally, `gh` can be already authenticated - await gh.authLoginWithToken(configuration.authToken); - } - - const directory = await createTemporaryDirectory() - - await gh.prCheckout(pullRequestNumber, directory.path); - const {stdout} = await npm.pack({cwd: directory.path}); - - return { - directory, - version: `file:${path.join(directory.path, stdout.trim())}` - }; -} +import path from "node:path"; +import { PrettierVersion, PrettierPullRequest, sourceTypes } from "../parse.ts"; +import * as configuration from "../configuration.ts"; +import * as brew from "../tools/brew.ts"; +import * as gh from "../tools/gh.ts"; +import * as yarn from "../tools/yarn.ts"; +import * as npm from "../tools/npm.ts"; +import * as unix from "../tools/unix.ts"; +import { + createTemporaryDirectory, + type TemporaryDirectory, +} from "./create-temporary-directory.ts"; + +export type InstalledPrettier = Awaited>; + +export async function installPrettier(prettierVersion: PrettierVersion) { + let version: string; + let pullRequestDirectory: TemporaryDirectory | undefined; + if (prettierVersion.type === sourceTypes.pullRequest) { + ({ version, directory: pullRequestDirectory } = await getPullRequest( + prettierVersion.number, + )); + } else { + ({ version } = prettierVersion); + } + + const directory = await createTemporaryDirectory(); + const { path: cwd } = directory; + await yarn.init(["-y"], { cwd }); + await yarn.add([`prettier@${version}`], { cwd }); + + await pullRequestDirectory?.dispatch(); + + return { + dispatch: () => { + directory.dispatch(); + }, + bin: path.join(cwd, "node_modules/prettier/bin/prettier.cjs"), + name: prettierVersion.raw, + }; +} + +async function existsGh() { + return !(await unix.which("gh")).includes("gh not found"); +} + +async function getPullRequest( + pullRequestNumber: PrettierPullRequest["number"], +) { + if (!(await existsGh())) { + await brew.install("gh"); + } + if (configuration.authToken !== "nothing") { + // running locally, `gh` can be already authenticated + await gh.authLoginWithToken(configuration.authToken); + } + + const directory = await createTemporaryDirectory(); + + await gh.prCheckout(pullRequestNumber, directory.path); + const { stdout } = await npm.pack({ cwd: directory.path }); + + return { + directory, + version: `file:${path.join(directory.path, stdout.trim())}`, + }; +} diff --git a/src/execute/run-prettier.ts b/src/execute/run-prettier.ts index 8c522873..b1453872 100644 --- a/src/execute/run-prettier.ts +++ b/src/execute/run-prettier.ts @@ -2,7 +2,7 @@ import path from "path"; import spawn from "nano-spawn"; import { type Project, getTargetRepositoryPath } from "../projects.ts"; import * as yarn from "../tools/yarn.ts"; -import {type InstalledPrettier} from './install-prettier.ts' +import { type InstalledPrettier } from "./install-prettier.ts"; export async function runPrettier( prettier: InstalledPrettier, diff --git a/src/parse.ts b/src/parse.ts index f90d0a21..a9014d5a 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -1,24 +1,21 @@ export const sourceTypes = { - pullRequest: 'pull-request', - package: 'package', + pullRequest: "pull-request", + package: "package", } as const; export interface PrettierPackage { type: typeof sourceTypes.package; - version: string, + version: string; raw?: string; } export interface PrettierPullRequest { type: typeof sourceTypes.pullRequest; - number: string, + number: string; raw?: string; } - -export type PrettierVersion = - | PrettierPackage - | PrettierPullRequest; +export type PrettierVersion = PrettierPackage | PrettierPullRequest; export type Project = { repositoryUrl: string; @@ -38,8 +35,7 @@ export function parse(source: string): Command { const tokens = tokenize(source); let alternativePrettier: PrettierVersion | undefined = undefined; - let originalPrettier: PrettierVersion = - defaultPrettierRepositorySource; + let originalPrettier: PrettierVersion = defaultPrettierRepositorySource; for (const [index, token] of tokenize(source).entries()) { const lookahead = (): Token => { @@ -99,7 +95,7 @@ export function parseRepositorySource(token: Token): PrettierVersion { throw new Error(`Unexpected token '${token.kind}', expect 'source'.`); } - const raw = token.value + const raw = token.value; // like "#3465" if (/^#\d+$/.test(raw)) { @@ -112,10 +108,10 @@ export function parseRepositorySource(token: Token): PrettierVersion { // Any source yarn accepts https://yarnpkg.com/cli/add // `sosukesuzuki/prettier#ref`, `3.0.0`, ... and so on - const packagePrefix = 'prettier@' - let version = raw + const packagePrefix = "prettier@"; + let version = raw; if (version.startsWith(packagePrefix)) { - version = version.slice(packagePrefix.length) + version = version.slice(packagePrefix.length); } return { diff --git a/src/tools/npm.ts b/src/tools/npm.ts index 013a8059..333d7438 100644 --- a/src/tools/npm.ts +++ b/src/tools/npm.ts @@ -1,5 +1,5 @@ -import spawn, {type Subprocess, type Options} from "nano-spawn"; - -export async function pack(options: Options): Promise { - return await spawn("npm", ['pack'], options); -} +import spawn, { type Subprocess, type Options } from "nano-spawn"; + +export async function pack(options: Options): Promise { + return await spawn("npm", ["pack"], options); +} diff --git a/src/tools/yarn.ts b/src/tools/yarn.ts index a8442d56..07e51e1e 100644 --- a/src/tools/yarn.ts +++ b/src/tools/yarn.ts @@ -1,13 +1,13 @@ -import spawn, {type Options} from "nano-spawn"; +import spawn, { type Options } from "nano-spawn"; export async function install(cwd: string): Promise { await spawn("yarn", [], { cwd }); } -export async function init(args: string [] ,options: Options): Promise { - await spawn("yarn", ['init',...args], options); +export async function init(args: string[], options: Options): Promise { + await spawn("yarn", ["init", ...args], options); } -export async function add(args: string [] ,options: Options): Promise { - await spawn("yarn", ['add',...args], options); +export async function add(args: string[], options: Options): Promise { + await spawn("yarn", ["add", ...args], options); } diff --git a/yarn.lock b/yarn.lock index e4b365d2..84418ff0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1411,27 +1411,27 @@ __metadata: languageName: node linkType: hard -"@vitest/expect@npm:4.0.6": - version: 4.0.6 - resolution: "@vitest/expect@npm:4.0.6" +"@vitest/expect@npm:4.0.15": + version: 4.0.15 + resolution: "@vitest/expect@npm:4.0.15" dependencies: "@standard-schema/spec": "npm:^1.0.0" "@types/chai": "npm:^5.2.2" - "@vitest/spy": "npm:4.0.6" - "@vitest/utils": "npm:4.0.6" - chai: "npm:^6.0.1" + "@vitest/spy": "npm:4.0.15" + "@vitest/utils": "npm:4.0.15" + chai: "npm:^6.2.1" tinyrainbow: "npm:^3.0.3" - checksum: 10c0/6ebe9fc669392be0550be805a6d11b6fe703ea04618bacabef2e097e3996075ac08687e62b3680d640fa16d252a3d62147f5139780b4593e3b8bb08638879168 + checksum: 10c0/0cb98a4918ca84b28cd14120bb66c1bc3084f8f95b649066cdab2f5234ecdbe247cdc6bc47c0d939521d964ff3c150aadd9558272495c26872c9f3a97373bf7b languageName: node linkType: hard -"@vitest/mocker@npm:4.0.6": - version: 4.0.6 - resolution: "@vitest/mocker@npm:4.0.6" +"@vitest/mocker@npm:4.0.15": + version: 4.0.15 + resolution: "@vitest/mocker@npm:4.0.15" dependencies: - "@vitest/spy": "npm:4.0.6" + "@vitest/spy": "npm:4.0.15" estree-walker: "npm:^3.0.3" - magic-string: "npm:^0.30.19" + magic-string: "npm:^0.30.21" peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0-0 @@ -1440,54 +1440,54 @@ __metadata: optional: true vite: optional: true - checksum: 10c0/e610baac2ba45b19bb08e7e3b19a539f45cccbc59b4fd5b7487817c76855b0b1ec92baaaa06397600b1a665dd1becebccd9b9abc6db24525452b91e7597e9dd7 + checksum: 10c0/7a164aa25daab3e92013ec95aab5c5778e805b1513e266ce6c00e0647eb9f7b281e33fcaf0d9d2aed88321079183b60c1aeab90961f618c24e2e3a5143308850 languageName: node linkType: hard -"@vitest/pretty-format@npm:4.0.6": - version: 4.0.6 - resolution: "@vitest/pretty-format@npm:4.0.6" +"@vitest/pretty-format@npm:4.0.15": + version: 4.0.15 + resolution: "@vitest/pretty-format@npm:4.0.15" dependencies: tinyrainbow: "npm:^3.0.3" - checksum: 10c0/e7dba3ca676d3a14d9c8bf8042c3cf40d1f77060dfe0426dfd6ed531416b7a85dfc97326d1d475b44718899f96352b4014ec85cb6ea72415fcd157ccea680286 + checksum: 10c0/d863f3818627b198f9c66515f8faa200e76a1c30c7f2b25ac805e253204ae51abbfa6b6211c58b2d75e1a273a2e6925e3a8fa480ebfa9c479d75a19815e1cbea languageName: node linkType: hard -"@vitest/runner@npm:4.0.6": - version: 4.0.6 - resolution: "@vitest/runner@npm:4.0.6" +"@vitest/runner@npm:4.0.15": + version: 4.0.15 + resolution: "@vitest/runner@npm:4.0.15" dependencies: - "@vitest/utils": "npm:4.0.6" + "@vitest/utils": "npm:4.0.15" pathe: "npm:^2.0.3" - checksum: 10c0/a28bf8f1674f4e880e7d60a7211bbb2a696a7c0b68d8bd88114e633867708c456daf0c0aa1c5b59e8243c93fe23dabc03f864cf2927d037209a425f8dc928cdf + checksum: 10c0/0b0f23b8fed1a98bb85d71a7fc105726e0fae68667b090c40b636011126fef548a5f853eab40aaf47314913ab6480eefe2aa5bd6bcefc4116e034fdc1ac0f7d0 languageName: node linkType: hard -"@vitest/snapshot@npm:4.0.6": - version: 4.0.6 - resolution: "@vitest/snapshot@npm:4.0.6" +"@vitest/snapshot@npm:4.0.15": + version: 4.0.15 + resolution: "@vitest/snapshot@npm:4.0.15" dependencies: - "@vitest/pretty-format": "npm:4.0.6" - magic-string: "npm:^0.30.19" + "@vitest/pretty-format": "npm:4.0.15" + magic-string: "npm:^0.30.21" pathe: "npm:^2.0.3" - checksum: 10c0/9d7a3c826e95c73dc5578683fab55db5d82bba61e793c8511993aad5e3cc06e79590dd0932aae3eb5331b943d5a29a13705db9185498c3fcfa61da42e8dcedc3 + checksum: 10c0/f05a19f74512cbad9bcfe4afe814c676b72b7e54ccf09c5b36e06e73614a24fdba47bdb8a94279162b7fdf83c9c840f557073a114a9339df7e75ccb9f4e99218 languageName: node linkType: hard -"@vitest/spy@npm:4.0.6": - version: 4.0.6 - resolution: "@vitest/spy@npm:4.0.6" - checksum: 10c0/d4a1837a53e90c7fe469079e6ffaef9def7fb3fbb80cbac4b3a79a93f76bf25948affd223e609527d2a3083992b974267cf0141376754c6d961baaa5bbe3d26e +"@vitest/spy@npm:4.0.15": + version: 4.0.15 + resolution: "@vitest/spy@npm:4.0.15" + checksum: 10c0/22319cead44964882d9e7bd197a9cd317c945ff75a4a9baefe06c32c5719d4cb887e86b4560d79716765f288a93a6c76e78e3eeab0000f24b2236dab678b7c34 languageName: node linkType: hard -"@vitest/utils@npm:4.0.6": - version: 4.0.6 - resolution: "@vitest/utils@npm:4.0.6" +"@vitest/utils@npm:4.0.15": + version: 4.0.15 + resolution: "@vitest/utils@npm:4.0.15" dependencies: - "@vitest/pretty-format": "npm:4.0.6" + "@vitest/pretty-format": "npm:4.0.15" tinyrainbow: "npm:^3.0.3" - checksum: 10c0/3a981e8af8ab280f226ff420bf3a7cb00540dc78c048acc1b2b257285d9604682f8668c3750a64cdb7018a433cdedee389def1cebeafa1f0cb56e0f2cdc343cc + checksum: 10c0/2ef661c2c2359ae956087f0b728b6a0f7555cd10524a7def27909f320f6b8ba00560ee1bd856bf68d4debc01020cea21b200203a5d2af36c44e94528c5587aee languageName: node linkType: hard @@ -1828,10 +1828,10 @@ __metadata: languageName: node linkType: hard -"chai@npm:^6.0.1": - version: 6.2.0 - resolution: "chai@npm:6.2.0" - checksum: 10c0/a4b7d7f5907187e09f1847afa838d6d1608adc7d822031b7900813c4ed5d9702911ac2468bf290676f22fddb3d727b1be90b57c1d0a69b902534ee29cdc6ff8a +"chai@npm:^6.2.1": + version: 6.2.1 + resolution: "chai@npm:6.2.1" + checksum: 10c0/0c2d84392d7c6d44ca5d14d94204f1760e22af68b83d1f4278b5c4d301dabfc0242da70954dd86b1eda01e438f42950de6cf9d569df2103678538e4014abe50b languageName: node linkType: hard @@ -1990,7 +1990,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.3": +"debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -3176,7 +3176,7 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.19": +"magic-string@npm:^0.30.21": version: 0.30.21 resolution: "magic-string@npm:0.30.21" dependencies: @@ -3527,6 +3527,13 @@ __metadata: languageName: node linkType: hard +"obug@npm:^2.1.1": + version: 2.1.1 + resolution: "obug@npm:2.1.1" + checksum: 10c0/59dccd7de72a047e08f8649e94c1015ec72f94eefb6ddb57fb4812c4b425a813bc7e7cd30c9aca20db3c59abc3c85cc7a62bb656a968741d770f4e8e02bc2e78 + languageName: node + linkType: hard + "once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -3743,7 +3750,7 @@ __metadata: temp-dir: "npm:^3.0.0" typescript: "npm:^5.9.3" typescript-eslint: "npm:^8.46.2" - vitest: "npm:^4.0.6" + vitest: "npm:^4.0.15" languageName: unknown linkType: soft @@ -4177,7 +4184,7 @@ __metadata: languageName: node linkType: hard -"std-env@npm:^3.9.0": +"std-env@npm:^3.10.0": version: 3.10.0 resolution: "std-env@npm:3.10.0" checksum: 10c0/1814927a45004d36dde6707eaf17552a546769bc79a6421be2c16ce77d238158dfe5de30910b78ec30d95135cc1c59ea73ee22d2ca170f8b9753f84da34c427f @@ -4377,10 +4384,10 @@ __metadata: languageName: node linkType: hard -"tinyexec@npm:^0.3.2": - version: 0.3.2 - resolution: "tinyexec@npm:0.3.2" - checksum: 10c0/3efbf791a911be0bf0821eab37a3445c2ba07acc1522b1fa84ae1e55f10425076f1290f680286345ed919549ad67527d07281f1c19d584df3b74326909eb1f90 +"tinyexec@npm:^1.0.2": + version: 1.0.2 + resolution: "tinyexec@npm:1.0.2" + checksum: 10c0/1261a8e34c9b539a9aae3b7f0bb5372045ff28ee1eba035a2a059e532198fe1a182ec61ac60fa0b4a4129f0c4c4b1d2d57355b5cb9aa2d17ac9454ecace502ee languageName: node linkType: hard @@ -4684,44 +4691,44 @@ __metadata: languageName: node linkType: hard -"vitest@npm:^4.0.6": - version: 4.0.6 - resolution: "vitest@npm:4.0.6" +"vitest@npm:^4.0.15": + version: 4.0.15 + resolution: "vitest@npm:4.0.15" dependencies: - "@vitest/expect": "npm:4.0.6" - "@vitest/mocker": "npm:4.0.6" - "@vitest/pretty-format": "npm:4.0.6" - "@vitest/runner": "npm:4.0.6" - "@vitest/snapshot": "npm:4.0.6" - "@vitest/spy": "npm:4.0.6" - "@vitest/utils": "npm:4.0.6" - debug: "npm:^4.4.3" + "@vitest/expect": "npm:4.0.15" + "@vitest/mocker": "npm:4.0.15" + "@vitest/pretty-format": "npm:4.0.15" + "@vitest/runner": "npm:4.0.15" + "@vitest/snapshot": "npm:4.0.15" + "@vitest/spy": "npm:4.0.15" + "@vitest/utils": "npm:4.0.15" es-module-lexer: "npm:^1.7.0" expect-type: "npm:^1.2.2" - magic-string: "npm:^0.30.19" + magic-string: "npm:^0.30.21" + obug: "npm:^2.1.1" pathe: "npm:^2.0.3" picomatch: "npm:^4.0.3" - std-env: "npm:^3.9.0" + std-env: "npm:^3.10.0" tinybench: "npm:^2.9.0" - tinyexec: "npm:^0.3.2" + tinyexec: "npm:^1.0.2" tinyglobby: "npm:^0.2.15" tinyrainbow: "npm:^3.0.3" vite: "npm:^6.0.0 || ^7.0.0" why-is-node-running: "npm:^2.3.0" peerDependencies: "@edge-runtime/vm": "*" - "@types/debug": ^4.1.12 + "@opentelemetry/api": ^1.9.0 "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 - "@vitest/browser-playwright": 4.0.6 - "@vitest/browser-preview": 4.0.6 - "@vitest/browser-webdriverio": 4.0.6 - "@vitest/ui": 4.0.6 + "@vitest/browser-playwright": 4.0.15 + "@vitest/browser-preview": 4.0.15 + "@vitest/browser-webdriverio": 4.0.15 + "@vitest/ui": 4.0.15 happy-dom: "*" jsdom: "*" peerDependenciesMeta: "@edge-runtime/vm": optional: true - "@types/debug": + "@opentelemetry/api": optional: true "@types/node": optional: true @@ -4739,7 +4746,7 @@ __metadata: optional: true bin: vitest: vitest.mjs - checksum: 10c0/dbe265955cee6677c0f4022769cadfccf7be9c69cd76f9565f8752276abc438170042ffb10b175f9225d1a8041465be5aaa579726d5dac53e88d9e139c5e33c0 + checksum: 10c0/fd57913dbcba81b67ca67bae37f0920f2785a60939a9029a82ebb843253f7a67f93f2c959cb90bb23a57055c0256ec0a6059ec9a10c129e8912c09b6e407242b languageName: node linkType: hard