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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ jobs:
- name: Production build
run: pnpm build

- name: Build and inspect npm CLI package
run: pnpm test:npm-cli

- name: Self-test GitHub Action
uses: ./
with:
Expand Down
57 changes: 57 additions & 0 deletions .github/workflows/publish-npm-cli.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Publish npm CLI

on:
workflow_dispatch:
inputs:
version:
description: "Semantic version to publish, for example 1.2.0"
required: true
type: string

permissions:
contents: read
id-token: write

jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Validate version
shell: bash
run: |
VERSION="${{ inputs.version }}"
[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
echo "Version must be x.y.z"
exit 1
}

- name: Checkout matching release tag
uses: actions/checkout@v6
with:
ref: v${{ inputs.version }}

- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
package-manager-cache: false

- name: Enable Corepack
run: corepack enable

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build CLI package
env:
CLI_PACKAGE_VERSION: ${{ inputs.version }}
run: pnpm build:npm-cli

- name: Verify package
run: |
node dist/npm/cli/index.mjs --help
npm pack --dry-run ./dist/npm

- name: Publish with npm trusted publishing
run: npm publish ./dist/npm --access public
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ For private owned repositories, create a user-owned fine-grained token with only

### Local CLI

Once the npm package is published, run without installing:

```bash
npx code-life-balance --username octocat --timezone Europe/Helsinki
```

For repository development, or before the npm package is published, use `pnpm cli --`.

Authenticate with GitHub CLI:

```bash
Expand Down Expand Up @@ -197,6 +205,8 @@ Open [http://localhost:3000](http://localhost:3000) and click **Analyze my GitHu
| `pnpm typecheck` | Run strict TypeScript validation. |
| `pnpm check` | Run tests, typecheck, and production build. |
| `pnpm cli -- --help` | Show local CLI options. |
| `pnpm build:npm-cli` | Build the dependency-free npm CLI package into `dist/npm`. |
| `pnpm test:npm-cli` | Build, execute, and dry-run-pack the npm CLI artifact. |

## Deployment

Expand Down
2 changes: 1 addition & 1 deletion cli/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Options:

Credentials:
1. GITHUB_TOKEN environment variable, if set.
2. Otherwise `gh auth token`, unless --no-gh is passed.
2. Otherwise gh auth token, unless --no-gh is passed.
3. Public analysis can run without a token when --username is provided.

No credential or report is sent to CodeLifeBalance infrastructure.
Expand Down
62 changes: 62 additions & 0 deletions docs/npm-cli-readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Code Life Balance CLI

Run Code Life Balance locally without sending your GitHub credential or generated report to Code Life Balance infrastructure.

## Run without installing

```bash
npx code-life-balance --username octocat --timezone Europe/Helsinki
```

Or install globally:

```bash
npm install --global code-life-balance
code-life-balance --username octocat
```

## Authentication

The CLI resolves credentials in this order:

1. `GITHUB_TOKEN`, when present.
2. Your existing `gh auth token` session.
3. No token for public-only username analysis.

For private owned-repository analysis:

```bash
gh auth login
code-life-balance --include-private --timezone Europe/Helsinki
```

Private mode verifies that the authenticated token owner matches the username being analyzed.

## Options

```text
--username <login> GitHub username
--timezone <IANA> IANA timezone, default UTC
--workday-start <0-23> Workday start hour, default 9
--workday-end <1-24> Workday end hour, default 18
--output-dir <path> Output directory
--theme <dark|light> SVG theme
--card-style <style> detailed or compact
--formats <list> svg,json,markdown
--include-private Include authenticated private owned activity
--public-only Force public-only analysis
--no-gh Do not read GitHub CLI authentication
--help Show CLI help
```

Generated output can include:

- `code-life.svg`
- `stats.json`
- `report.md`

## Privacy

The CLI calls the GitHub API directly from your machine. It does not send your GitHub token or generated report to a Code Life Balance service.

Repository: https://github.com/alihd-tech/CodeLifeBalance
55 changes: 55 additions & 0 deletions docs/npm-publishing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Publishing the Code Life Balance CLI to npm

The npm distribution is generated into `dist/npm`. It contains only the CLI and the shared runtime modules. It does not ship the Next.js website or web dependencies.

## Build and verify locally

```bash
pnpm install
pnpm test:npm-cli
```

The generated package has the unscoped npm name:

```text
code-life-balance
```

Users will be able to run:

```bash
npx code-life-balance --username octocat
```

## First publication

npm trusted-publisher configuration requires the package to already exist. The first publication therefore needs to be performed once by an npm maintainer with 2FA:

```bash
pnpm build:npm-cli
cd dist/npm
npm login
npm publish --access public
```

Before publishing, verify that the generated `package.json` has the intended version and that `npm pack --dry-run` lists only the CLI/shared runtime files.

## Configure trusted publishing

After the package exists on npmjs.com, open the package's trusted-publisher settings and add GitHub Actions:

- GitHub owner: `alihd-tech`
- Repository: `CodeLifeBalance`
- Workflow filename: `publish-npm-cli.yml`
- Allow action: direct `npm publish`

The workflow uses a GitHub-hosted runner and `id-token: write`, so npm can authenticate it using OIDC without a long-lived npm publish token.

## Subsequent releases

1. Publish the matching GitHub release/tag, for example `v1.2.0`.
2. Run **Publish npm CLI** from GitHub Actions.
3. Enter `1.2.0`.
4. The workflow checks out exactly `v1.2.0`, builds the CLI artifact, performs a dry-run pack, then publishes with npm trusted publishing.

The workflow intentionally requires an exact release tag so npm packages cannot accidentally be built from a moving branch.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "code-life-balance",
"version": "0.1.0",
"version": "1.2.0",
"private": true,
"license": "MIT",
"repository": {
Expand All @@ -17,7 +17,9 @@
"test:report": "node --test packages/report/index.test.mjs",
"test": "node --test packages/core/index.test.mjs packages/report/index.test.mjs",
"typecheck": "tsc --noEmit",
"check": "pnpm test && pnpm typecheck && pnpm build"
"check": "pnpm test && pnpm typecheck && pnpm build && pnpm test:npm-cli",
"build:npm-cli": "node scripts/build-npm-cli.mjs",
"test:npm-cli": "pnpm build:npm-cli && node dist/npm/cli/index.mjs --help && npm pack --dry-run ./dist/npm"
},
"dependencies": {
"@base-ui/react": "^1.5.0",
Expand Down
67 changes: 67 additions & 0 deletions scripts/build-npm-cli.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises"
import { resolve } from "node:path"

const root = process.cwd()
const out = resolve(root, "dist/npm")
const rootPackage = JSON.parse(await readFile(resolve(root, "package.json"), "utf8"))

await rm(out, { recursive: true, force: true })
await mkdir(resolve(out, "cli"), { recursive: true })
await mkdir(resolve(out, "packages/core"), { recursive: true })
await mkdir(resolve(out, "packages/github-client"), { recursive: true })
await mkdir(resolve(out, "packages/report"), { recursive: true })

await cp(resolve(root, "cli/index.mjs"), resolve(out, "cli/index.mjs"))
await cp(resolve(root, "packages/core/index.mjs"), resolve(out, "packages/core/index.mjs"))
await cp(resolve(root, "packages/github-client/index.mjs"), resolve(out, "packages/github-client/index.mjs"))
await cp(resolve(root, "packages/report/index.mjs"), resolve(out, "packages/report/index.mjs"))
await cp(resolve(root, "LICENSE"), resolve(out, "LICENSE"))
await cp(resolve(root, "docs/npm-cli-readme.md"), resolve(out, "README.md"))

const packageJson = {
name: "code-life-balance",
version: process.env.CLI_PACKAGE_VERSION || rootPackage.version,
description: "Privacy-first GitHub activity and code-life balance reports from your local machine.",
type: "module",
bin: {
"code-life-balance": "./cli/index.mjs"
},
files: [
"cli",
"packages/core",
"packages/github-client",
"packages/report",
"README.md",
"LICENSE"
],
engines: {
node: ">=20"
},
license: "MIT",
repository: {
type: "git",
url: "git+https://github.com/alihd-tech/CodeLifeBalance.git"
},
homepage: "https://github.com/alihd-tech/CodeLifeBalance#readme",
bugs: {
url: "https://github.com/alihd-tech/CodeLifeBalance/issues"
},
keywords: [
"github",
"developer",
"activity",
"analytics",
"cli",
"privacy",
"productivity",
"code-life-balance"
]
}

await writeFile(
resolve(out, "package.json"),
JSON.stringify(packageJson, null, 2) + "\n",
"utf8"
)

console.log(`Built npm CLI package at ${out}`)
Loading