Skip to content

Commit

Permalink
feat: improve bun support (#604)
Browse files Browse the repository at this point in the history
  • Loading branch information
exKAZUu authored Aug 15, 2024
1 parent f1329b1 commit 340a86a
Show file tree
Hide file tree
Showing 11 changed files with 356 additions and 87 deletions.
39 changes: 34 additions & 5 deletions src/generators/asdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ import path from 'node:path';

import { logger } from '../logger.js';
import type { PackageConfig } from '../packageConfig.js';
import { octokit } from '../utils/githubUtil.js';
import { promisePool } from '../utils/promisePool.js';
import { spawnSync, spawnSyncWithStringResult } from '../utils/spawnUtil.js';
import { convertVersionIntoNumber } from '../utils/version.js';
import { JAVA_VERSION, PYTHON_VERSION } from '../utils/versionConstants.js';

export async function generateVersionConfigs(config: PackageConfig): Promise<void> {
return logger.functionIgnoringException('generateVersionConfigs', async () => {
export async function generateToolVersions(config: PackageConfig): Promise<void> {
return logger.functionIgnoringException('generateToolVersions', async () => {
await core(config);
});
}

const CORE_TOOLS = new Set(['java', 'nodejs', 'python']);
const CORE_TOOLS = new Set(['java', 'nodejs', 'bun', 'python']);
const DEPRECATED_VERSION_PREFIXES = ['java', 'node', 'python'];

async function core(config: PackageConfig): Promise<void> {
Expand All @@ -25,9 +26,11 @@ async function core(config: PackageConfig): Promise<void> {
.split('\n')
.map((line) => {
const [name, version] = line.trim().split(/\s+/);
// To move the top of the list, we need to add a space.
return `${CORE_TOOLS.has(name) ? ' ' : ''}${name} ${version}`;
})
.sort()
// Remove added spaces.
.map((line) => line.trim());
const lines = [...new Set(duplicatableLines)];

Expand All @@ -44,8 +47,16 @@ async function core(config: PackageConfig): Promise<void> {
updateVersion(lines, 'java', JAVA_VERSION, true);
}
if (config.doesContainsPackageJson) {
const version = spawnSyncWithStringResult('npm', ['show', 'yarn', 'version'], config.dirPath);
updateVersion(lines, 'yarn', version);
if (config.isBun) {
const lefthookVersion = await getLatestVersionFromTagOnGitHub('evilmartians', 'lefthook');
if (lefthookVersion) updateVersion(lines, 'lefthook', lefthookVersion);

const bunVersion = await getLatestVersionFromTagOnGitHub('oven-sh', 'bun');
if (bunVersion) updateVersion(lines, 'bun', bunVersion);
} else {
const version = spawnSyncWithStringResult('npm', ['show', 'yarn', 'version'], config.dirPath);
updateVersion(lines, 'yarn', version);
}
}

for (const prefix of DEPRECATED_VERSION_PREFIXES) {
Expand Down Expand Up @@ -74,3 +85,21 @@ function updateVersion(lines: string[], toolName: string, newVersion: string, he
lines.splice(head ? 0 : lines.length, 0, newLine);
}
}

async function getLatestVersionFromTagOnGitHub(organization: string, repository: string): Promise<string | undefined> {
try {
// Fetch the latest release from the repository
const response = await octokit.request('GET /repos/{owner}/{repo}/releases/latest', {
owner: organization,
repo: repository,
});
const version = response.data.tag_name;
const index = version.lastIndexOf('v');
const versionNumberText = index >= 0 ? version.slice(index + 1) : version;
// Check the first character is a number
return /^\d/.test(versionNumberText) ? versionNumberText : undefined;
} catch (error) {
console.error('Failed to fetch Bun tags due to:', error);
return;
}
}
32 changes: 32 additions & 0 deletions src/generators/biome.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import fs from 'node:fs';
import path from 'node:path';

import merge from 'deepmerge';
import cloneDeep from 'lodash.clonedeep';

import { logger } from '../logger.js';
import type { PackageConfig } from '../packageConfig.js';
import { fsUtil } from '../utils/fsUtil.js';
import { overwriteMerge } from '../utils/mergeUtil.js';
import { promisePool } from '../utils/promisePool.js';

const jsonObj = {
$schema: './node_modules/@biomejs/biome/configuration_schema.json',
extends: ['@willbooster/biome-config'],
};

export async function generateBiomeJsonc(config: PackageConfig): Promise<void> {
return logger.functionIgnoringException('generateBiomeJsonc', async () => {
let newSettings: unknown = cloneDeep(jsonObj);
const filePath = path.resolve(config.dirPath, 'biome.jsonc');
try {
const oldContent = await fs.promises.readFile(filePath, 'utf8');
const oldSettings = JSON.parse(oldContent);
newSettings = merge.all([newSettings, oldSettings, newSettings], { arrayMerge: overwriteMerge });
} catch {
// do nothing
}
const newContent = JSON.stringify(newSettings);
await promisePool.run(() => fsUtil.generateFile(filePath, newContent));
});
}
23 changes: 23 additions & 0 deletions src/generators/bunfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import path from 'node:path';

import { logger } from '../logger.js';
import type { PackageConfig } from '../packageConfig.js';
import { fsUtil } from '../utils/fsUtil.js';
import { promisePool } from '../utils/promisePool.js';

const newContent = `telemetry = false
[install]
exact = true
[run]
bun = true
`;

export async function generateBunfigToml(config: PackageConfig): Promise<void> {
return logger.functionIgnoringException('generateBunfigToml', async () => {
const filePath = path.resolve(config.dirPath, 'bunfig.toml');
await promisePool.run(() => fsUtil.generateFile(filePath, newContent));
});
}
29 changes: 18 additions & 11 deletions src/generators/huskyrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { spawnSync } from '../utils/spawnUtil.js';

import { generateScripts } from './packageJson.js';

const settings = {
const scripts = {
preCommit: 'node node_modules/.bin/lint-staged',
prePush: `yarn typecheck`,
prePushForLab: `
Expand Down Expand Up @@ -60,11 +60,11 @@ async function core(config: PackageConfig): Promise<void> {
const preCommitFilePath = path.resolve(dirPath, 'pre-commit');

await promisePool.run(() => fs.promises.rm(path.resolve(config.dirPath, '.huskyrc.json'), { force: true }));
await promisePool.run(() => fs.promises.writeFile(preCommitFilePath, settings.preCommit + '\n'));
await promisePool.run(() => fs.promises.writeFile(preCommitFilePath, scripts.preCommit + '\n'));

const { typecheck } = generateScripts(config);
if (typecheck) {
let prePush = config.repository?.startsWith('github:WillBoosterLab/') ? settings.prePushForLab : settings.prePush;
let prePush = config.repository?.startsWith('github:WillBoosterLab/') ? scripts.prePushForLab : scripts.prePush;
prePush = prePush.replace(
'yarn typecheck',
typecheck
Expand All @@ -79,10 +79,21 @@ async function core(config: PackageConfig): Promise<void> {
})
);
}
const postMergeCommand = `${scripts.postMerge}\n\n${generatePostMergeCommands(config).join('\n')}\n`;
await promisePool.run(() =>
fs.promises.writeFile(path.resolve(dirPath, 'post-merge'), postMergeCommand, {
mode: 0o755,
})
);
}

export function generatePostMergeCommands(config: PackageConfig): string[] {
const postMergeCommands: string[] = [];
if (config.versionsText) {
postMergeCommands.push(String.raw`run_if_changed "\..+-version" "asdf plugin update --all"`);
postMergeCommands.push(
String.raw`run_if_changed "\..+-version" "awk '{print \$1}' .tool-versions | xargs -I{} asdf plugin add {}"`,
String.raw`run_if_changed "\..+-version" "asdf plugin update --all"`
);
}
// Pythonがないとインストールできない処理系が存在するため、強制的に最初にインストールする。
if (config.versionsText?.includes('python ')) {
Expand All @@ -91,8 +102,9 @@ async function core(config: PackageConfig): Promise<void> {
if (config.versionsText) {
postMergeCommands.push(String.raw`run_if_changed "\..+-version" "asdf install"`);
}
const installCommand = config.isBun ? 'bun install' : 'yarn';
const rmNextDirectory = config.depending.blitz || config.depending.next ? ' && rm -Rf .next' : '';
postMergeCommands.push(`run_if_changed "package\\.json" "yarn${rmNextDirectory}"`);
postMergeCommands.push(`run_if_changed "package\\.json" "${installCommand}${rmNextDirectory}"`);
if (config.doesContainsPoetryLock) {
postMergeCommands.push(String.raw`run_if_changed "poetry\.lock" "poetry install"`);
}
Expand All @@ -108,10 +120,5 @@ async function core(config: PackageConfig): Promise<void> {
'run_if_changed "prisma/schema.prisma" "node node_modules/.bin/dotenv -c development -- node node_modules/.bin/prisma generate"'
);
}
const postMergeCommand = `${settings.postMerge}\n\n${postMergeCommands.join('\n')}\n`;
await promisePool.run(() =>
fs.promises.writeFile(path.resolve(dirPath, 'post-merge'), postMergeCommand, {
mode: 0o755,
})
);
return postMergeCommands;
}
2 changes: 1 addition & 1 deletion src/generators/idea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ ${extensions.prettier.map((ext) => createTaskOptions('node', 'node_modules/.bin/
const biomeContent = `<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectTasksOptions">
${extensions.prettier.map((ext) => createTaskOptions('bun', 'node_modules/.bin/biome check --apply', 'Biome', ext)).join('')}
${extensions.prettier.map((ext) => createTaskOptions('bun', 'node_modules/.bin/biome check --fix', 'Biome', ext)).join('')}
</component>
</project>
`;
Expand Down
111 changes: 111 additions & 0 deletions src/generators/lefthook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import fs from 'node:fs';
import path from 'node:path';

import yaml from 'js-yaml';

import { logger } from '../logger.js';
import type { PackageConfig } from '../packageConfig.js';
import { promisePool } from '../utils/promisePool.js';

import { generatePostMergeCommands } from './huskyrc.js';
import { generateScripts } from './packageJson.js';

const newSettings = {
'post-merge': {
scripts: {
'prepare.sh': {
runner: 'bash',
},
},
},
'pre-commit': {
commands: {
cleanup: {
glob: '*.{cjs,css,cts,htm,html,js,json,json5,jsonc,jsx,md,mjs,mts,scss,ts,tsx,vue,yaml,yml}',
run: 'bun --bun wb lint --fix --format {staged_files} && git add {staged_files}',
},
},
},
'pre-push': {
scripts: {
'check.sh': {
runner: 'bash',
},
},
},
};

const scripts = {
prePush: `bun --bun node_modules/.bin/wb typecheck`,
prePushForLab: `
#!/bin/bash
if [ $(git branch --show-current) = "main" ] && [ $(git config user.email) != "[email protected]" ]; then
echo "************************************************"
echo "*** Don't push main branch directly. Use PR! ***"
echo "************************************************"
exit 1
fi
bun --bun node_modules/.bin/wb typecheck
`.trim(),
postMerge: `
#!/bin/bash
changed_files="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)"
run_if_changed() {
if echo "$changed_files" | grep --quiet -E "$1"; then
eval "$2"
fi
}
`.trim(),
};

export async function generateLefthook(config: PackageConfig): Promise<void> {
return logger.functionIgnoringException('generateLefthook', async () => {
await core(config);
});
}

async function core(config: PackageConfig): Promise<void> {
const packageJsonPath = path.resolve(config.dirPath, 'package.json');
const jsonText = await fs.promises.readFile(packageJsonPath, 'utf8');
const packageJson = JSON.parse(jsonText);
packageJson.scripts ||= {};
packageJson.scripts['prepare'] = 'lefthook install || true';

const dirPath = path.resolve(config.dirPath, '.lefthook');
await Promise.all([
fs.promises.writeFile(packageJsonPath, JSON.stringify(packageJson, undefined, 2)),
fs.promises.writeFile(
path.join(config.dirPath, 'lefthook.yml'),
yaml.dump(newSettings, {
lineWidth: -1,
noCompatMode: true,
styles: {
'!!null': 'empty',
},
})
),
fs.promises.rm(dirPath, { force: true, recursive: true }),
]);

const { typecheck } = generateScripts(config);
if (typecheck) {
const prePush = config.repository?.startsWith('github:WillBoosterLab/') ? scripts.prePushForLab : scripts.prePush;
fs.mkdirSync(path.join(dirPath, 'pre-push'), { recursive: true });
await promisePool.run(() =>
fs.promises.writeFile(path.join(dirPath, 'pre-push', 'check.sh'), prePush + '\n', {
mode: 0o755,
})
);
}
const postMergeCommand = `${scripts.postMerge}\n\n${generatePostMergeCommands(config).join('\n')}\n`;
fs.mkdirSync(path.join(dirPath, 'post-merge'), { recursive: true });
await promisePool.run(() =>
fs.promises.writeFile(path.resolve(dirPath, 'post-merge', 'prepare.sh'), postMergeCommand, {
mode: 0o755,
})
);
}
Loading

0 comments on commit 340a86a

Please sign in to comment.