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
1 change: 0 additions & 1 deletion .github/workflows/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ jobs:
- uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3
id: changes
with:
predicate-quantifier: 'every'
filters: |
changed:
- "packages/**"
Expand Down
5 changes: 5 additions & 0 deletions packages/pnpm-plugin-skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,17 @@ Then create a `skills.json` in your project root:
{
"installDir": ".agents/skills",
"linkTargets": [".claude/skills"],
"pnpmPlugin": {
"removePnpmfileChecksum": true
},
"skills": {
"my-skill": "https://github.com/owner/repo.git#path:/skills/my-skill"
}
}
```

`pnpmPlugin.removePnpmfileChecksum` is a temporary compatibility switch for repositories that need `pnpm-plugin-skills` to remove `pnpmfileChecksum` from `pnpm-lock.yaml` in `afterAllResolved`.

Now `pnpm install` will automatically install your skills.

## Architecture
Expand Down
3 changes: 2 additions & 1 deletion packages/pnpm-plugin-skills/pnpmfile.cjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// pnpm v10
const { preResolution } = require('./dist/index.js')
const { afterAllResolved, preResolution } = require('./dist/index.js')

module.exports = {
hooks: {
afterAllResolved,
preResolution,
},
}
4 changes: 2 additions & 2 deletions packages/pnpm-plugin-skills/pnpmfile.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// pnpm v11
import { preResolution } from './dist/index.mjs'
import { afterAllResolved, preResolution } from './dist/index.mjs'

export const hooks = { preResolution }
export const hooks = { afterAllResolved, preResolution }
42 changes: 41 additions & 1 deletion packages/pnpm-plugin-skills/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
import { installCommand } from 'skills-package-manager'
import { readFileSync } from 'node:fs'
import path from 'node:path'
import { installCommand, type SkillsManifest } from 'skills-package-manager'

function readPluginManifest(rootDir: string): SkillsManifest | null {
const filePath = path.join(rootDir, 'skills.json')

try {
const parsed = JSON.parse(readFileSync(filePath, 'utf8')) as {
pnpmPlugin?: SkillsManifest['pnpmPlugin']
}
return parsed as SkillsManifest
} catch {
return null
}
}

export async function preResolution(
options: { lockfileDir?: string; workspaceRoot?: string } = {},
Expand All @@ -11,3 +26,28 @@ export async function preResolution(
await installCommand({ cwd: lockfileDir })
return undefined
}

export function afterAllResolved(
lockfile: Record<string, unknown>,
context: { lockfileDir?: string; workspaceDir?: string } = {},
) {
const manifestRoot = context.lockfileDir ?? context.workspaceDir
if (!manifestRoot) {
return lockfile
}

const manifest = readPluginManifest(manifestRoot)
if (!manifest) {
return lockfile
}

if (manifest.pnpmPlugin?.removePnpmfileChecksum !== true) {
return lockfile
}

if ('pnpmfileChecksum' in lockfile) {
delete lockfile.pnpmfileChecksum
}

return lockfile
}
47 changes: 47 additions & 0 deletions packages/pnpm-plugin-skills/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,50 @@ describe('preResolution', () => {
expect(existsSync(path.join(root, '.claude/skills/hello-skill'))).toBe(true)
})
})

describe('afterAllResolved', () => {
it('removes pnpmfileChecksum when enabled in skills.json', async () => {
const { afterAllResolved } = await import('../src/index')
const root = mkdtempSync(path.join(tmpdir(), 'pnpm-plugin-skills-config-'))

writeFileSync(
path.join(root, 'skills.json'),
JSON.stringify(
{
installDir: '.agents/skills',
linkTargets: [],
pnpmPlugin: {
removePnpmfileChecksum: true,
},
skills: {},
},
null,
2,
),
)

const lockfile = {
lockfileVersion: '9.0',
pnpmfileChecksum: 'checksum-to-remove',
}

const result = afterAllResolved(lockfile, { lockfileDir: root })

expect(result).toBe(lockfile)
expect(result).not.toHaveProperty('pnpmfileChecksum')
})

it('keeps pnpmfileChecksum by default', async () => {
const { afterAllResolved } = await import('../src/index')

const lockfile = {
lockfileVersion: '9.0',
pnpmfileChecksum: 'checksum-to-keep',
}

const result = afterAllResolved(lockfile, {})

expect(result).toBe(lockfile)
expect(result).toHaveProperty('pnpmfileChecksum', 'checksum-to-keep')
})
})
11 changes: 11 additions & 0 deletions packages/skills-package-manager/src/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ export const skillsManifestSchema = z
.default([])
.describe('Directories where skill symlinks will be created'),
selfSkill: z.boolean().optional().describe('Whether this project is itself a skill'),
pnpmPlugin: z
.object({
removePnpmfileChecksum: z
.boolean()
.optional()
.describe(
'Temporarily remove pnpmfileChecksum from pnpm lockfiles in pnpm-plugin-skills afterAllResolved',
),
})
.optional()
.describe('pnpm-plugin-skills specific compatibility settings'),
skills: z
.record(z.string(), z.string())
.default({})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export function normalizeSkillsManifest(manifest: Partial<SkillsManifest>): Skil
installDir: manifest.installDir ?? '.agents/skills',
linkTargets: manifest.linkTargets ?? [],
selfSkill: manifest.selfSkill ?? false,
pnpmPlugin: manifest.pnpmPlugin,
skills: manifest.skills ?? {},
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export async function writeSkillsManifest(
nextManifest.selfSkill = manifest.selfSkill
}

if (manifest.pnpmPlugin !== undefined) {
nextManifest.pnpmPlugin = manifest.pnpmPlugin
}

try {
await writeFile(filePath, `${JSON.stringify(nextManifest, null, 2)}\n`, 'utf8')
} catch (error) {
Expand Down
30 changes: 30 additions & 0 deletions packages/skills-package-manager/test/manifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,36 @@ describe('manifest io', () => {
$schema: DEFAULT_SCHEMA_URL,
installDir: '.agents/skills',
linkTargets: [],
pnpmPlugin: undefined,
skills: { hello: 'link:./skills/hello' },
})
})

it('reads pnpmPlugin config from skills.json', async () => {
const root = mkdtempSync(path.join(tmpdir(), 'skills-pm-manifest-pnpm-plugin-'))
writeFileSync(
path.join(root, 'skills.json'),
JSON.stringify(
{
installDir: '.agents/skills',
linkTargets: [],
pnpmPlugin: {
removePnpmfileChecksum: true,
},
skills: {},
},
null,
2,
),
)

const manifest = await readSkillsManifest(root)

expect(manifest?.pnpmPlugin).toEqual({
removePnpmfileChecksum: true,
})
})

it('defaults selfSkill to undefined when omitted from skills.json', async () => {
const root = mkdtempSync(path.join(tmpdir(), 'skills-pm-manifest-default-self-'))
writeFileSync(
Expand Down Expand Up @@ -104,6 +130,9 @@ describe('manifest validation', () => {
installDir: '.custom/skills',
linkTargets: ['.claude/skills'],
selfSkill: true,
pnpmPlugin: {
removePnpmfileChecksum: true,
},
skills: { test: 'link:./test' },
}

Expand All @@ -123,6 +152,7 @@ describe('manifest validation', () => {
expect(result.data.installDir).toBe('.agents/skills')
expect(result.data.linkTargets).toEqual([])
expect(result.data.selfSkill).toBeUndefined()
expect(result.data.pnpmPlugin).toBeUndefined()
expect(result.data.skills).toEqual({})
}
})
Expand Down
5 changes: 5 additions & 0 deletions website/docs/architecture/pnpm-plugin.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ Then add the following to the workspace root:
{
"installDir": ".agents/skills",
"linkTargets": [".claude/skills"],
"pnpmPlugin": {
"removePnpmfileChecksum": true
},
"skills": {
"my-skill": "https://github.com/owner/repo.git#path:/skills/my-skill"
}
}
```

`pnpmPlugin.removePnpmfileChecksum` is a temporary compatibility option for `pnpm-plugin-skills`. When enabled, the plugin removes `pnpmfileChecksum` from `pnpm-lock.yaml` in `afterAllResolved` to work around tools that run `pnpm install --ignore-pnpmfile`.

## Suitable team scenarios

- You want developers to run only `pnpm install` without remembering extra skill installation commands
Expand Down
Loading