Skip to content
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
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: CI

on:
pull_request:
types: [opened, synchronize, reopened]

jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: jdx/mise-action@v3
- run: yarn install --immutable
- run: yarn lint
- run: yarn format:check
- run: yarn build
- run: yarn test
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: jdx/mise-action@v3
- run: yarn install
- run: yarn install --immutable
- name: Create Changesets Pull Request or Publish to NPM
id: changesets
uses: changesets/action@c48e67d110a68bc90ccf1098e9646092baacaa87 # v1.6.0
with:
title: "chore: version packages"
commit: "chore: version packages"
title: 'chore: version packages'
commit: 'chore: version packages'
version: yarn changeset version
publish: yarn changeset publish
env:
Expand Down
7 changes: 7 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"tabWidth": 2,
"printWidth": 120,
"singleQuote": true,
"semi": true,
"trailingComma": "all"
}
6 changes: 1 addition & 5 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
{
"recommendations": [
"arcanis.vscode-zipfs",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint"
]
"recommendations": ["arcanis.vscode-zipfs", "esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
}
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"**/.yarn": true,
"**/.pnp.*": true
},
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs",
"prettier.configPath": ".prettierrc.json",
"js/ts.tsdk.path": ".yarn/sdks/typescript/lib",
"eslint.nodePath": ".yarn/sdks"
}
4 changes: 2 additions & 2 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ enableGlobalCache: false

nodeLinker: pnp

npmPublishRegistry: "https://registry.npmjs.org"
npmPublishRegistry: 'https://registry.npmjs.org'

npmRegistries:
"https://registry.npmjs.org":
'https://registry.npmjs.org':
npmAlwaysAuth: true
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pnpm add -D @pull-up/cli
Create a config file in your project root:

```ts
import { defineConfig, codeownersJob } from "@pull-up/cli";
import { defineConfig, codeownersJob } from '@pull-up/cli';

export default defineConfig(codeownersJob());
// or export default defineConfig([codeownersJob()]);
Expand All @@ -60,8 +60,8 @@ Collects `CODEOWNERS` files from your monorepo and merges them into a single fil

```ts
codeownersJob({
from: ["**/CODEOWNERS"], // default
output: ".github/CODEOWNERS", // default
from: ['**/CODEOWNERS'], // default
output: '.github/CODEOWNERS', // default
});
```

Expand All @@ -70,12 +70,12 @@ codeownersJob({
You can define custom jobs using `defineJob`:

```ts
import { defineConfig, defineJob } from "@pull-up/cli";
import { defineConfig, defineJob } from '@pull-up/cli';

const myJob = defineJob({
name: "my-job",
from: ["packages/*/config.json"],
output: "merged-config.json",
name: 'my-job',
from: ['packages/*/config.json'],
output: 'merged-config.json',
transform: (sources, context) => {
// sources: array of { path, contents }
// context: { root, outputPath, existingContents }
Expand Down
36 changes: 17 additions & 19 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,47 +1,45 @@
// @ts-check

import eslint from "@eslint/js";
import importX from "eslint-plugin-import-x";
import simpleImportSort from "eslint-plugin-simple-import-sort";
import { defineConfig } from "eslint/config";
import globals from "globals";
import tseslint from "typescript-eslint";
import eslint from '@eslint/js';
import { defineConfig } from 'eslint/config';
import importX from 'eslint-plugin-import-x';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
import globals from 'globals';
import tseslint from 'typescript-eslint';

export default defineConfig(
{
ignores: ["**/node_modules/**", "**/dist/**", ".yarn/**"],
ignores: ['**/node_modules/**', '**/dist/**', '.yarn/**', '**/.pnp.cjs', '**/.pnp.loader.mjs'],
},
eslint.configs.recommended,
tseslint.configs.recommended,
{
files: ["**/*.{ts,tsx}"],
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
project: ["./tsconfig.json"],
project: ['./tsconfig.json'],
},
},
rules: {
"@typescript-eslint/strict-boolean-expressions": "error",
'@typescript-eslint/strict-boolean-expressions': 'error',
},
},
{
files: ["**/*.{js,mjs,cjs,ts,tsx}"],
files: ['**/*.{js,mjs,cjs,ts,tsx}'],
languageOptions: {
globals: {
...globals.es2026,
...globals.node,
},
},
plugins: {
"simple-import-sort": simpleImportSort,
"import-x": importX,
'simple-import-sort': simpleImportSort,
'import-x': importX,
},
rules: {
"simple-import-sort/imports": "error",
"import-x/first": "error",
"import-x/newline-after-import": "error",
"import-x/no-duplicates": "error",
'simple-import-sort/imports': 'error',
'import-x/first': 'error',
'import-x/newline-after-import': 'error',
'import-x/no-duplicates': 'error',
},
},
);
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,15 @@
"dev": "tsdown --watch",
"prepack": "yarn build",
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"format": "prettier --write .",
"format:check": "prettier --check .",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@changesets/changelog-github": "^0.5.2",
"@changesets/cli": "^2.29.8",
"@eslint/js": "^9.39.2",
"@fixture-kit/core": "^1.0.1",
"@types/node": "^24.10.0",
"eslint": "^9.7.0",
Expand Down
38 changes: 17 additions & 21 deletions src/cli/commands/check.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,45 @@
import { Command, Option } from "clipanion";
import pc from "picocolors";
import { resolveConfig } from "../../core/resolve-config";
import { runJob } from "../../core/run-job";
import { resolveRepositoryRoot } from "../utils";
import { Command, Option } from 'clipanion';
import pc from 'picocolors';

import { resolveConfig } from '../../core/resolve-config';
import { runJob } from '../../core/run-job';
import { resolveRepositoryRoot } from '../utils';

export class CheckCommand extends Command {
static paths = [["check"]];
static paths = [['check']];
static usage = Command.Usage({
description: "Check if generated files are up to date",
examples: [["Check all jobs", "pullup check"]],
description: 'Check if generated files are up to date',
examples: [['Check all jobs', 'pullup check']],
});

root = Option.String("--root", {
description: "The path to the repository root",
root = Option.String('--root', {
description: 'The path to the repository root',
required: false,
});

cwd = Option.String("--cwd", {
description: "The path to the working directory",
cwd = Option.String('--cwd', {
description: 'The path to the working directory',
required: false,
});

async execute() {
const cwd = this.cwd ?? process.cwd();
const [repoRoot, jobs] = await Promise.all([
resolveRepositoryRoot(cwd, this.root),
resolveConfig(cwd),
]);
const [repoRoot, jobs] = await Promise.all([resolveRepositoryRoot(cwd, this.root), resolveConfig(cwd)]);

if (jobs.length === 0) {
console.log(pc.yellow("✘ No jobs found to check"));
console.log(pc.yellow('✘ No jobs found to check'));
return;
}

const results = await Promise.all(jobs.map((job) => runJob(job, repoRoot)));

for (const { isSame, jobInfo } of results) {
if (!isSame) {
console.error(
pc.red(`✘ ${jobInfo.name} is outdated. Run 'pullup sync' to update.`),
);
console.error(pc.red(`✘ ${jobInfo.name} is outdated. Run 'pullup sync' to update.`));
process.exit(1);
}
}

console.log(pc.green("✔ All files are up to date"));
console.log(pc.green('✔ All files are up to date'));
}
}
4 changes: 2 additions & 2 deletions src/cli/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from "./check.js";
export * from "./sync.js";
export * from './check.js';
export * from './sync.js';
48 changes: 25 additions & 23 deletions src/cli/commands/sync.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
import { Command, Option } from "clipanion";
import pc from "picocolors";
import fs from "node:fs/promises";
import { resolveRepositoryRoot } from "../utils";
import path from "node:path";
import { resolveConfig } from "../../core/resolve-config";
import { runJob } from "../../core/run-job";
import fs from 'node:fs/promises';
import path from 'node:path';

import { Command, Option } from 'clipanion';
import pc from 'picocolors';

import { resolveConfig } from '../../core/resolve-config';
import { runJob } from '../../core/run-job';
import { resolveRepositoryRoot } from '../utils';

export class SyncCommand extends Command {
static paths = [["sync"]];
static paths = [['sync']];
static usage = Command.Usage({
description: "sync files",
examples: [["Sync files", "pullup sync"]],
description: 'sync files',
examples: [['Sync files', 'pullup sync']],
});

root = Option.String("--root", {
description: "The path to the repository root",
root = Option.String('--root', {
description: 'The path to the repository root',
required: false,
});

cwd = Option.String("--cwd", {
description: "The path to the working directory",
cwd = Option.String('--cwd', {
description: 'The path to the working directory',
required: false,
});

dryRun = Option.Boolean("--dry-run", {
description: "Whether to dry run the check",
dryRun = Option.Boolean('--dry-run', {
description: 'Whether to dry run the check',
required: false,
});

Expand All @@ -34,21 +36,21 @@ export class SyncCommand extends Command {
const jobs = await resolveConfig(cwd);

if (jobs.length === 0) {
console.log(pc.yellow("✘ No jobs found to sync"));
console.log(pc.yellow('✘ No jobs found to sync'));
return;
}

const results = await Promise.all(jobs.map((job) => runJob(job, repoRoot)));

for (const { jobInfo, generated, isSame } of results) {
if (this.dryRun) {
if (this.dryRun === true) {
console.log(pc.cyan(`┌─ [Job] ${jobInfo.name}`));
console.log(`${pc.cyan("│")} ${pc.dim(`Output: ${jobInfo.output}`)}`);
console.log(`${pc.cyan("│")}`);
generated.contents.split("\n").forEach((line) => {
console.log(`${pc.cyan("│")} ${line}`);
console.log(`${pc.cyan('│')} ${pc.dim(`Output: ${jobInfo.output}`)}`);
console.log(`${pc.cyan('│')}`);
generated.contents.split('\n').forEach((line) => {
console.log(`${pc.cyan('│')} ${line}`);
});
console.log(`${pc.cyan("└─")}`);
console.log(`${pc.cyan('└─')}`);
continue;
}

Expand Down
11 changes: 6 additions & 5 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Cli } from "clipanion";
import packageJson from "../../package.json" with { type: "json" };
import { CheckCommand, SyncCommand } from "./commands";
import { Cli } from 'clipanion';

import packageJson from '../../package.json' with { type: 'json' };
import { CheckCommand, SyncCommand } from './commands';

const [, , ...args] = process.argv;

const cli = new Cli({
binaryLabel: "pullup",
binaryName: "pullup",
binaryLabel: 'pullup',
binaryName: 'pullup',
binaryVersion: packageJson.version,
});

Expand Down
10 changes: 5 additions & 5 deletions src/cli/utils.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { findUp } from "find-up";
import path from "path";
import { findUp } from 'find-up';
import path from 'path';

export async function resolveRepositoryRoot(cwd: string, root?: string) {
if (root != null) return root;

const dotGit = await findUp(".git", {
type: "both",
const dotGit = await findUp('.git', {
type: 'both',
cwd,
});

if (dotGit == null) {
throw new Error("Repository root not found");
throw new Error('Repository root not found');
}

return path.dirname(dotGit);
Expand Down
2 changes: 1 addition & 1 deletion src/core/define-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Job } from "./types";
import type { Job } from './types';

export function defineConfig(config: Job | Job[]) {
if (Array.isArray(config)) {
Expand Down
Loading
Loading