@stream44.studio/dco 0.3.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dco-signatures +9 -0
- package/.github/workflows/dco.yml +12 -0
- package/.o/GordianOpenIntegrity-CurrentLifehash.svg +1026 -0
- package/.o/GordianOpenIntegrity-InceptionLifehash.svg +1026 -0
- package/.o/GordianOpenIntegrity.yaml +25 -0
- package/DCO.md +34 -0
- package/README.md +122 -0
- package/action.yml +32 -0
- package/caps/Dco.test.ts +288 -0
- package/caps/Dco.ts +269 -0
- package/commit.sh +468 -0
- package/dco.sh +49 -0
- package/examples/01-Lifecycle/main.test.ts +223 -0
- package/package.json +39 -0
- package/test.sh +422 -0
- package/tsconfig.json +28 -0
- package/validate.sh +353 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"envelope": "ur:envelope/lrtpsotanshdhdcxdkchoebkgohkmyahwnmevtvtrhmytpjsztzemdfdtkvydsjecwpetyimmwfljpksoyaylftpsohdgltansgylftanshfhdcxdthsctfzbdihfmueaygdlpsfrtoeswaxlkeoaobyhpiofsindtfymylosaeofneotansgrhdcxchweztoevyckleryrdsslaiawdgtperyckfplbtkbbwytljotkfnesiycmfhspeeoycsfncsfgoycsfztpsohdjktngdgmgwhflfaxhdimyawzaxjtlddndnlecyqzvdclkskotksafsskdmlgwpbgdszmrerncmuofpaxmkkeahluwnmoidbsiypedmaozsneykjpplurloolghesjedwjtbzstimswwladghiartaxjptaeemewdesehahvybtfleowsjsvavwmtvarfsbwkhestknprpywncycmfpsbssfristkahtniectuofgoytpsojyfljljpieinhsjtgwjoihjtgajtjyihiojpinjykktpsokshnjkjkisdpihieeyececehescxfpfpfpfpfxeoglknhsfxehjzhtfygaehglghfeecfpfpfpfpgagdjtgoknhkfxgokpkkgmflgmgleogsdniofdemksksghgaidhdethfjnjegtecfxfphfeeflkphdkkjzkogwfecxjyeeeedpjkiniojtinjtiodpjeihkkutztrteh",
|
|
4
|
+
"mark": "0fbc3988",
|
|
5
|
+
"$defs": {
|
|
6
|
+
"envelope": {
|
|
7
|
+
"$ref": "https://datatracker.ietf.org/doc/draft-mcnally-envelope/"
|
|
8
|
+
},
|
|
9
|
+
"mark": {
|
|
10
|
+
"$ref": "https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2025-001-provenance-mark.md"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
---
|
|
15
|
+
# Repository DID: did:repo:d241cef226ebb7495bcbe8422c889ffc088c2b50
|
|
16
|
+
# Current Mark: 0fbc3988 (🅑 BIAS ROOF EYES LOGO)
|
|
17
|
+
# Inception Mark: 1275c22f (🅑 BRAG KEEP SAGA DULL)
|
|
18
|
+
# XID(2417a20a) [
|
|
19
|
+
# 'key': Bytes(78) [
|
|
20
|
+
# 'allow': 'All'
|
|
21
|
+
# ]
|
|
22
|
+
# 'provenance': Bytes(115)
|
|
23
|
+
# "GordianOpenIntegrity": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPnUzYCUuyRGRN3L+gH7xxTIbX8VmkM5CAV4GuXylvOE t44-signing-key"
|
|
24
|
+
# ]
|
|
25
|
+
# Trust established using https://github.com/Stream44/t44-BlockchainCommons.com
|
package/DCO.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Developer Certificate of Origin
|
|
2
|
+
Version 1.1
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
|
5
|
+
|
|
6
|
+
Everyone is permitted to copy and distribute verbatim copies of this
|
|
7
|
+
license document, but changing it is not allowed.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
Developer's Certificate of Origin 1.1
|
|
11
|
+
|
|
12
|
+
By making a contribution to this project, I certify that:
|
|
13
|
+
|
|
14
|
+
(a) The contribution was created in whole or in part by me and I
|
|
15
|
+
have the right to submit it under the open source license
|
|
16
|
+
indicated in the file; or
|
|
17
|
+
|
|
18
|
+
(b) The contribution is based upon previous work that, to the best
|
|
19
|
+
of my knowledge, is covered under an appropriate open source
|
|
20
|
+
license and I have the right under that license to submit that
|
|
21
|
+
work with modifications, whether created in whole or in part
|
|
22
|
+
by me, under the same open source license (unless I am
|
|
23
|
+
permitted to submit under a different license), as indicated
|
|
24
|
+
in the file; or
|
|
25
|
+
|
|
26
|
+
(c) The contribution was provided directly to me by some other
|
|
27
|
+
person who certified (a), (b) or (c) and I have not modified
|
|
28
|
+
it.
|
|
29
|
+
|
|
30
|
+
(d) I understand and agree that this project and the contribution
|
|
31
|
+
are public and that a record of the contribution (including all
|
|
32
|
+
personal information I submit with it, including my sign-off) is
|
|
33
|
+
maintained indefinitely and may be redistributed consistent with
|
|
34
|
+
this project or the open source license(s) involved.
|
package/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
⚠️ **WARNING:** This repository may get squashed and force-pushed if the [GordianOpenIntegrity](https://github.com/Stream44/t44-blockchaincommons.com) implementation must change in incompatible ways. Keep your diffs until the **GordianOpenIntegrity** system is stable.
|
|
2
|
+
|
|
3
|
+
🔷 **Open Development Project:** The implementation is a preview release for community feedback.
|
|
4
|
+
|
|
5
|
+
⚠️ **Disclaimer:** Under active development. Code has not been audited, APIs and interfaces are subject to change.
|
|
6
|
+
|
|
7
|
+
Developer Certificate of Origin (DCO) Tools
|
|
8
|
+
===
|
|
9
|
+
|
|
10
|
+
DCOs are a simple way to have contributors agree to terms present in a `DCO.md` file whenever they commit to your repository.
|
|
11
|
+
|
|
12
|
+
It is assurance for you that every commit adheres to the terms present in git at the time of the commit.
|
|
13
|
+
|
|
14
|
+
This project contains tools to facilitate a DCO process for any project.
|
|
15
|
+
|
|
16
|
+
**No outside service is required. Use github actions for signature verification on pull requests.**
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
Usage
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
### Setup
|
|
23
|
+
|
|
24
|
+
Create a `DCO.md` file and sign.
|
|
25
|
+
|
|
26
|
+
A great template for open source projects is: [https://developercertificate.org](https://developercertificate.org)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Signing
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
bunx @stream44.studio/dco commit [--signing-key ~/.ssh/key] <git arguments>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Verifying
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
bunx @stream44.studio/dco validate
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Also see [Github Actions](#github-action) below.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
Tools
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
### Git Commit Script
|
|
48
|
+
|
|
49
|
+
The script provides a nice experience for contributors of your project.
|
|
50
|
+
|
|
51
|
+
Instead of running `git commit ...`, run `commit.sh ...`.
|
|
52
|
+
|
|
53
|
+
The **first time** you run the script you will see the DCO terms of the repository you are comitting to so you can agree.
|
|
54
|
+
|
|
55
|
+
It will add an entry in `.dco-signatures` to record the signature and commit the change.
|
|
56
|
+
|
|
57
|
+
It will then always add `--signoff` to every `git commit` invocation in order to **sign off** on the commit.
|
|
58
|
+
|
|
59
|
+
These are the details from `git commit --help`:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
-s, --signoff, --no-signoff
|
|
63
|
+
Add a Signed-off-by trailer by the committer at the end of the commit log message.
|
|
64
|
+
The meaning of a signoff depends on the project to which you’re committing.
|
|
65
|
+
For example, it may certify that the committer has the rights to submit the work under the project’s license
|
|
66
|
+
or agrees to some contributor representation, such as a Developer Certificate of Origin.
|
|
67
|
+
(See https://developercertificate.org for the one used by the Linux kernel and Git projects.)
|
|
68
|
+
Consult the documentation or leadership of the project to which you’re contributing to understand how
|
|
69
|
+
the signoffs are used in that project.
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Optionally a signing key can be supplied to cryptographically sign commits as well. The fingreprint of the signing
|
|
73
|
+
key will be sored in the `.dco-signatures` file.
|
|
74
|
+
|
|
75
|
+
A project can choose to require signing keys or not by setting `enforceSignatureFingerprints` for the github action.
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
### Verification Script
|
|
79
|
+
|
|
80
|
+
Ensures all commits were signed off my signatures recorded in `.dco-signatures`.
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
### Github Actions
|
|
84
|
+
|
|
85
|
+
The github action enforces DCO sign-offs by ensuring all commits have a `Signed-off-by: Jane Doe <jane@example.com>`
|
|
86
|
+
line in the respective commit messages and the same is found in `.dco-signatures`.
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
Add to `.github/workflows/dco.yml` in your repository:
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
name: Validate DCO Signatures
|
|
93
|
+
on: [push, pull_request]
|
|
94
|
+
jobs:
|
|
95
|
+
dco:
|
|
96
|
+
runs-on: ubuntu-latest
|
|
97
|
+
steps:
|
|
98
|
+
- uses: actions/checkout@v4
|
|
99
|
+
with:
|
|
100
|
+
fetch-depth: 0
|
|
101
|
+
- uses: Stream44/dco@main
|
|
102
|
+
with:
|
|
103
|
+
enforceSignatureFingerprints: true
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
Provenance
|
|
108
|
+
===
|
|
109
|
+
|
|
110
|
+
Repository DID: `did:repo:d241cef226ebb7495bcbe8422c889ffc088c2b50`
|
|
111
|
+
|
|
112
|
+
<table>
|
|
113
|
+
<tr>
|
|
114
|
+
<td><strong>Inception Mark</strong></td>
|
|
115
|
+
<td><img src=".o/GordianOpenIntegrity-InceptionLifehash.svg" width="64" height="64"></td>
|
|
116
|
+
<td><strong>Current Mark</strong></td>
|
|
117
|
+
<td><img src=".o/GordianOpenIntegrity-CurrentLifehash.svg" width="64" height="64"></td>
|
|
118
|
+
<td>Trust established using<br/><a href="https://github.com/Stream44/t44-blockchaincommons.com">Stream44/t44-BlockchainCommons.com</a></td>
|
|
119
|
+
</tr>
|
|
120
|
+
</table>
|
|
121
|
+
|
|
122
|
+
(c) 2026 [Christoph.diy](https://christoph.diy) • Code: `Apache 2.0` • Text: `CC-BY` • Created with [Stream44.Studio](https://Stream44.Studio)
|
package/action.yml
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: 'DCO Check'
|
|
2
|
+
description: 'Validate Developer Certificate of Origin (DCO) signatures on all commits'
|
|
3
|
+
author: 'Stream44'
|
|
4
|
+
|
|
5
|
+
inputs:
|
|
6
|
+
enforceSignatureFingerprints:
|
|
7
|
+
description: 'Enforce that SSH signature fingerprints on commits match those recorded in .dco-signatures'
|
|
8
|
+
required: false
|
|
9
|
+
default: 'false'
|
|
10
|
+
|
|
11
|
+
branding:
|
|
12
|
+
icon: 'check-circle'
|
|
13
|
+
color: 'green'
|
|
14
|
+
|
|
15
|
+
runs:
|
|
16
|
+
using: 'composite'
|
|
17
|
+
steps:
|
|
18
|
+
- name: Validate DCO
|
|
19
|
+
shell: bash
|
|
20
|
+
run: |
|
|
21
|
+
chmod +x ${{ github.action_path }}/validate.sh
|
|
22
|
+
ENFORCE_FLAG=""
|
|
23
|
+
if [[ "${{ inputs.enforceSignatureFingerprints }}" == "true" ]]; then
|
|
24
|
+
ENFORCE_FLAG="--enforce-signature-fingerprints"
|
|
25
|
+
fi
|
|
26
|
+
${{ github.action_path }}/validate.sh $ENFORCE_FLAG
|
|
27
|
+
env:
|
|
28
|
+
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
|
29
|
+
GITHUB_BASE_REF: ${{ github.base_ref }}
|
|
30
|
+
GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
31
|
+
GITHUB_BEFORE: ${{ github.event.before }}
|
|
32
|
+
GITHUB_SHA: ${{ github.sha }}
|
package/caps/Dco.test.ts
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/usr/bin/env bun test
|
|
2
|
+
|
|
3
|
+
import * as bunTest from 'bun:test'
|
|
4
|
+
import { run } from 't44/workspace-rt'
|
|
5
|
+
import { join } from 'path'
|
|
6
|
+
import { rm, mkdir, writeFile, readFile, copyFile, access } from 'fs/promises'
|
|
7
|
+
import { constants, existsSync } from 'fs'
|
|
8
|
+
import { $ } from 'bun'
|
|
9
|
+
|
|
10
|
+
const WORK_DIR = join(import.meta.dir, '.~dco')
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
test: { describe, it, expect },
|
|
14
|
+
dco,
|
|
15
|
+
} = await run(async ({ encapsulate, CapsulePropertyTypes, makeImportStack }: any) => {
|
|
16
|
+
const spine = await encapsulate({
|
|
17
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
18
|
+
'#@stream44.studio/encapsulate/structs/Capsule': {},
|
|
19
|
+
'#': {
|
|
20
|
+
test: {
|
|
21
|
+
type: CapsulePropertyTypes.Mapping,
|
|
22
|
+
value: 't44/caps/WorkspaceTest',
|
|
23
|
+
options: {
|
|
24
|
+
'#': {
|
|
25
|
+
bunTest,
|
|
26
|
+
env: {}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
dco: {
|
|
31
|
+
type: CapsulePropertyTypes.Mapping,
|
|
32
|
+
value: './Dco'
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}, {
|
|
37
|
+
importMeta: import.meta,
|
|
38
|
+
importStack: makeImportStack(),
|
|
39
|
+
capsuleName: '@stream44.studio/dco/caps/Dco.test'
|
|
40
|
+
})
|
|
41
|
+
return { spine }
|
|
42
|
+
}, async ({ spine, apis }: any) => {
|
|
43
|
+
return apis[spine.capsuleSourceLineRef]
|
|
44
|
+
}, {
|
|
45
|
+
importMeta: import.meta
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
await rm(WORK_DIR, { recursive: true, force: true })
|
|
49
|
+
await mkdir(WORK_DIR, { recursive: true })
|
|
50
|
+
|
|
51
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
52
|
+
//
|
|
53
|
+
// DCO — Developer Certificate of Origin Capsule Tests
|
|
54
|
+
//
|
|
55
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
56
|
+
|
|
57
|
+
// Helper: create a fresh git repo with DCO.md
|
|
58
|
+
async function createTestRepo(name: string): Promise<string> {
|
|
59
|
+
const repoDir = join(WORK_DIR, name)
|
|
60
|
+
await mkdir(repoDir, { recursive: true })
|
|
61
|
+
await $`git init`.cwd(repoDir).quiet()
|
|
62
|
+
await $`git config user.name "Test User"`.cwd(repoDir).quiet()
|
|
63
|
+
await $`git config user.email "test@example.com"`.cwd(repoDir).quiet()
|
|
64
|
+
await $`git checkout -b main`.cwd(repoDir).quiet().nothrow()
|
|
65
|
+
|
|
66
|
+
// Copy DCO.md from the package
|
|
67
|
+
const packageDir = join(import.meta.dir, '..')
|
|
68
|
+
await copyFile(join(packageDir, 'DCO.md'), join(repoDir, 'DCO.md'))
|
|
69
|
+
|
|
70
|
+
return repoDir
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
describe('Dco', function () {
|
|
74
|
+
|
|
75
|
+
// ──────────────────────────────────────────────────────────────
|
|
76
|
+
// 1. hasDco
|
|
77
|
+
// ──────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
describe('1. hasDco', function () {
|
|
80
|
+
|
|
81
|
+
it('should return true when DCO.md exists', async function () {
|
|
82
|
+
const repoDir = await createTestRepo('has-dco-true')
|
|
83
|
+
const result = await dco.hasDco({ repoDir })
|
|
84
|
+
expect(result).toBe(true)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('should return false when DCO.md does not exist', async function () {
|
|
88
|
+
const repoDir = join(WORK_DIR, 'has-dco-false')
|
|
89
|
+
await mkdir(repoDir, { recursive: true })
|
|
90
|
+
const result = await dco.hasDco({ repoDir })
|
|
91
|
+
expect(result).toBe(false)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// ──────────────────────────────────────────────────────────────
|
|
96
|
+
// 2. sign
|
|
97
|
+
// ──────────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
describe('2. sign', function () {
|
|
100
|
+
|
|
101
|
+
it('should sign the DCO with --yes-signoff', async function () {
|
|
102
|
+
const repoDir = await createTestRepo('sign-auto')
|
|
103
|
+
|
|
104
|
+
await dco.sign({ repoDir, autoAgree: true })
|
|
105
|
+
|
|
106
|
+
// Verify marker file was created
|
|
107
|
+
const markerPath = join(repoDir, '.git/.dco-agreed')
|
|
108
|
+
expect(existsSync(markerPath)).toBe(true)
|
|
109
|
+
|
|
110
|
+
// Verify .dco-signatures was created
|
|
111
|
+
const sigPath = join(repoDir, '.dco-signatures')
|
|
112
|
+
expect(existsSync(sigPath)).toBe(true)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('should throw when DCO.md is missing', async function () {
|
|
116
|
+
const repoDir = join(WORK_DIR, 'sign-no-dco')
|
|
117
|
+
await mkdir(repoDir, { recursive: true })
|
|
118
|
+
|
|
119
|
+
let threw = false
|
|
120
|
+
try {
|
|
121
|
+
await dco.sign({ repoDir, autoAgree: true })
|
|
122
|
+
} catch (e: any) {
|
|
123
|
+
threw = true
|
|
124
|
+
expect(e.message).toContain('DCO.md not found')
|
|
125
|
+
}
|
|
126
|
+
expect(threw).toBe(true)
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// ──────────────────────────────────────────────────────────────
|
|
131
|
+
// 3. isSigned
|
|
132
|
+
// ──────────────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
describe('3. isSigned', function () {
|
|
135
|
+
|
|
136
|
+
it('should return signed: false for a fresh repo', async function () {
|
|
137
|
+
const repoDir = await createTestRepo('is-signed-false')
|
|
138
|
+
const result = await dco.isSigned({ repoDir })
|
|
139
|
+
expect(result.signed).toBe(false)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('should return signed: true after signing', async function () {
|
|
143
|
+
const repoDir = await createTestRepo('is-signed-true')
|
|
144
|
+
await dco.sign({ repoDir, autoAgree: true })
|
|
145
|
+
|
|
146
|
+
const result = await dco.isSigned({ repoDir })
|
|
147
|
+
expect(result.signed).toBe(true)
|
|
148
|
+
expect(result.name).toBe('Test User')
|
|
149
|
+
expect(result.email).toBe('test@example.com')
|
|
150
|
+
expect(result.date).toBeDefined()
|
|
151
|
+
expect(result.agreementCommit).toBeDefined()
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// ──────────────────────────────────────────────────────────────
|
|
156
|
+
// 4. getSignatures
|
|
157
|
+
// ──────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
describe('4. getSignatures', function () {
|
|
160
|
+
|
|
161
|
+
it('should return found: false when no signatures file', async function () {
|
|
162
|
+
const repoDir = await createTestRepo('sigs-none')
|
|
163
|
+
const result = await dco.getSignatures({ repoDir })
|
|
164
|
+
expect(result.found).toBe(false)
|
|
165
|
+
expect(result.signatures).toEqual([])
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('should parse signatures after signing', async function () {
|
|
169
|
+
const repoDir = await createTestRepo('sigs-parse')
|
|
170
|
+
await dco.sign({ repoDir, autoAgree: true })
|
|
171
|
+
|
|
172
|
+
const result = await dco.getSignatures({ repoDir })
|
|
173
|
+
expect(result.found).toBe(true)
|
|
174
|
+
expect(result.signatures.length).toBe(1)
|
|
175
|
+
expect(result.signatures[0].name).toBe('Test User')
|
|
176
|
+
expect(result.signatures[0].email).toBe('test@example.com')
|
|
177
|
+
expect(result.signatures[0].signedDate).toBeDefined()
|
|
178
|
+
expect(result.signatures[0].agreementCommit.length).toBeGreaterThan(0)
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
// ──────────────────────────────────────────────────────────────
|
|
183
|
+
// 5. signAndCommit
|
|
184
|
+
// ──────────────────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
describe('5. signAndCommit', function () {
|
|
187
|
+
|
|
188
|
+
it('should sign DCO and commit changes with --signoff', async function () {
|
|
189
|
+
const repoDir = await createTestRepo('sign-commit')
|
|
190
|
+
|
|
191
|
+
// Create a test file to commit
|
|
192
|
+
await writeFile(join(repoDir, 'README.md'), '# Test Project\n')
|
|
193
|
+
|
|
194
|
+
await dco.signAndCommit({
|
|
195
|
+
repoDir,
|
|
196
|
+
message: 'Initial commit',
|
|
197
|
+
autoAgree: true,
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
// Verify commits exist
|
|
201
|
+
const logResult = await $`git log --oneline`.cwd(repoDir).quiet()
|
|
202
|
+
const commits = logResult.text().trim().split('\n').filter(Boolean)
|
|
203
|
+
// Should have: DCO.md commit + DCO signature commit + user commit
|
|
204
|
+
expect(commits.length).toBe(3)
|
|
205
|
+
|
|
206
|
+
// Verify the user commit has Signed-off-by
|
|
207
|
+
const lastBody = await $`git log -1 --format=%b`.cwd(repoDir).quiet()
|
|
208
|
+
expect(lastBody.text()).toContain('Signed-off-by:')
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('should copy .dco-signatures to projectSourceDir', async function () {
|
|
212
|
+
const repoDir = await createTestRepo('sign-commit-copy')
|
|
213
|
+
const projectDir = join(WORK_DIR, 'sign-commit-copy-source')
|
|
214
|
+
await mkdir(projectDir, { recursive: true })
|
|
215
|
+
|
|
216
|
+
await writeFile(join(repoDir, 'README.md'), '# Test\n')
|
|
217
|
+
|
|
218
|
+
await dco.signAndCommit({
|
|
219
|
+
repoDir,
|
|
220
|
+
message: 'Test commit',
|
|
221
|
+
autoAgree: true,
|
|
222
|
+
projectSourceDir: projectDir,
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
// Verify .dco-signatures was copied to project source
|
|
226
|
+
const copiedSigPath = join(projectDir, '.dco-signatures')
|
|
227
|
+
expect(existsSync(copiedSigPath)).toBe(true)
|
|
228
|
+
|
|
229
|
+
const content = await readFile(copiedSigPath, 'utf-8')
|
|
230
|
+
expect(content).toContain('Test User')
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
// ──────────────────────────────────────────────────────────────
|
|
235
|
+
// 6. validate
|
|
236
|
+
// ──────────────────────────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
describe('6. validate', function () {
|
|
239
|
+
|
|
240
|
+
it('should validate a properly signed repository', async function () {
|
|
241
|
+
const repoDir = await createTestRepo('validate-pass')
|
|
242
|
+
|
|
243
|
+
await writeFile(join(repoDir, 'README.md'), '# Test\n')
|
|
244
|
+
|
|
245
|
+
await dco.signAndCommit({
|
|
246
|
+
repoDir,
|
|
247
|
+
message: 'Signed commit',
|
|
248
|
+
autoAgree: true,
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
const result = await dco.validate({ repoDir })
|
|
252
|
+
expect(result.valid).toBe(true)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it('should fail validation for unsigned commits', async function () {
|
|
256
|
+
const repoDir = await createTestRepo('validate-fail')
|
|
257
|
+
|
|
258
|
+
// Create a commit WITHOUT --signoff
|
|
259
|
+
await writeFile(join(repoDir, 'README.md'), '# Test\n')
|
|
260
|
+
await $`git add -A`.cwd(repoDir).quiet()
|
|
261
|
+
await $`git commit -m "Unsigned commit"`.cwd(repoDir).quiet()
|
|
262
|
+
|
|
263
|
+
const result = await dco.validate({ repoDir })
|
|
264
|
+
expect(result.valid).toBe(false)
|
|
265
|
+
})
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
// ──────────────────────────────────────────────────────────────
|
|
269
|
+
// 7. Re-sign (idempotent)
|
|
270
|
+
// ──────────────────────────────────────────────────────────────
|
|
271
|
+
|
|
272
|
+
describe('7. Re-sign idempotent', function () {
|
|
273
|
+
|
|
274
|
+
it('should not create additional commits on re-sign', async function () {
|
|
275
|
+
const repoDir = await createTestRepo('re-sign')
|
|
276
|
+
|
|
277
|
+
// First sign
|
|
278
|
+
await dco.sign({ repoDir, autoAgree: true })
|
|
279
|
+
const countAfterFirst = await $`git rev-list --count HEAD`.cwd(repoDir).quiet()
|
|
280
|
+
|
|
281
|
+
// Second sign (should be idempotent)
|
|
282
|
+
await dco.sign({ repoDir, autoAgree: true })
|
|
283
|
+
const countAfterSecond = await $`git rev-list --count HEAD`.cwd(repoDir).quiet()
|
|
284
|
+
|
|
285
|
+
expect(countAfterSecond.text().trim()).toBe(countAfterFirst.text().trim())
|
|
286
|
+
})
|
|
287
|
+
})
|
|
288
|
+
})
|