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: 15 additions & 2 deletions .github/workflows/drift-detection.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: Run drift detection
id: drift
env:
WORLDDRIVEN_GITHUB_TOKEN: ${{ secrets.WORLDDRIVEN_GITHUB_TOKEN }}
WORLDDRIVEN_GITHUB_TOKEN: ${{ secrets.WORLDDRIVEN_GITHUB_TOKEN || github.token }}
run: |
set +e
node scripts/detect-drift.js > drift-report.md 2>&1
Expand All @@ -41,7 +41,7 @@ jobs:
- name: Preview sync actions (dry-run)
id: sync
env:
WORLDDRIVEN_GITHUB_TOKEN: ${{ secrets.WORLDDRIVEN_GITHUB_TOKEN }}
WORLDDRIVEN_GITHUB_TOKEN: ${{ secrets.WORLDDRIVEN_GITHUB_TOKEN || github.token }}
run: |
set +e
node scripts/sync-repositories.js > sync-preview.md 2>&1
Expand All @@ -50,7 +50,20 @@ jobs:
set -e
continue-on-error: true

- name: Add drift report to workflow summary
if: always()
run: |
echo "## Repository Drift Detection Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
cat drift-report.md >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
cat sync-preview.md >> $GITHUB_STEP_SUMMARY

- name: Comment PR with drift report and sync preview
# Skip commenting on fork PRs (no write permissions), but drift report is available in workflow summary above
if: github.event.pull_request.head.repo.full_name == github.repository
uses: actions/github-script@v7
with:
script: |
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/sync-repositories.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ on:
jobs:
sync:
runs-on: ubuntu-latest
# Only run on the main worlddriven organization, not on forks
if: github.repository_owner == 'worlddriven'
permissions:
contents: write
issues: write
Expand Down
34 changes: 34 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Tests

on:
pull_request:
paths:
- 'scripts/**'
- 'package.json'
- '.github/workflows/test.yml'
push:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Make scripts executable
run: chmod +x scripts/*.js

- name: Run unit tests
run: npm test

- name: Report test results
if: success()
run: echo "✅ All tests passed"
8 changes: 8 additions & 0 deletions REPOSITORIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,11 @@ Each repository is defined using markdown headers and properties:
## Current Repositories

<!-- Add repositories below this line -->

## documentation
- Description: Core documentation repository for worlddriven project
- Topics: documentation, worlddriven

## webapp
- Description: Web application interface for worlddriven
- Topics: webapp, web, frontend, worlddriven
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"description": "Vision, philosophy, and organization management for worlddriven",
"type": "module",
"scripts": {
"test": "node --test scripts/*.test.js",
"parse": "node scripts/parse-repositories.js",
"fetch-github": "node scripts/fetch-github-state.js",
"detect-drift": "node scripts/detect-drift.js",
Expand Down
12 changes: 12 additions & 0 deletions scripts/parse-repositories.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,22 @@ function parseRepositories(content) {
const lines = content.split('\n');

let currentRepo = null;
let inCodeBlock = false;

for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();

// Track code block boundaries
if (line.startsWith('```')) {
inCodeBlock = !inCodeBlock;
continue;
}

// Skip lines inside code blocks
if (inCodeBlock) {
continue;
}

// Repository name (## heading)
if (line.startsWith('## ')) {
// Save previous repo if exists
Expand Down
224 changes: 224 additions & 0 deletions scripts/parse-repositories.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
#!/usr/bin/env node

import { describe, test } from 'node:test';
import assert from 'node:assert';
import { parseRepositories } from './parse-repositories.js';

describe('parseRepositories', () => {
test('should return empty array for empty content', () => {
const result = parseRepositories('');
assert.deepStrictEqual(result, []);
});

test('should parse a single repository with description', () => {
const content = `
## my-repo
- Description: A test repository
`;
const result = parseRepositories(content);
assert.deepStrictEqual(result, [
{
name: 'my-repo',
description: 'A test repository'
}
]);
});

test('should parse repository with description and topics', () => {
const content = `
## my-repo
- Description: A test repository
- Topics: topic1, topic2, topic3
`;
const result = parseRepositories(content);
assert.deepStrictEqual(result, [
{
name: 'my-repo',
description: 'A test repository',
topics: ['topic1', 'topic2', 'topic3']
}
]);
});

test('should parse multiple repositories', () => {
const content = `
## repo-one
- Description: First repository
- Topics: topic1

## repo-two
- Description: Second repository
- Topics: topic2, topic3
`;
const result = parseRepositories(content);
assert.strictEqual(result.length, 2);
assert.strictEqual(result[0].name, 'repo-one');
assert.strictEqual(result[1].name, 'repo-two');
});

test('should skip repositories inside code blocks', () => {
const content = `
## Format

Example:

\`\`\`markdown
## example-repo
- Description: This is inside a code block
- Topics: example, test
\`\`\`

## Current Repositories

## real-repo
- Description: This is a real repository
`;
const result = parseRepositories(content);
assert.strictEqual(result.length, 1);
assert.strictEqual(result[0].name, 'real-repo');
assert.strictEqual(result[0].description, 'This is a real repository');
});

test('should skip repositories without descriptions', () => {
const content = `
## valid-repo
- Description: Valid repository

## invalid-repo
- Topics: topic1, topic2

## another-valid
- Description: Another valid one
`;
const result = parseRepositories(content);
assert.strictEqual(result.length, 2);
assert.strictEqual(result[0].name, 'valid-repo');
assert.strictEqual(result[1].name, 'another-valid');
});

test('should skip heading with "example" in name', () => {
const content = `
## Example
- Description: This should be skipped

## real-repo
- Description: This is real
`;
const result = parseRepositories(content);
assert.strictEqual(result.length, 1);
assert.strictEqual(result[0].name, 'real-repo');
});

test('should skip heading with "format" in name', () => {
const content = `
## Format
- Description: This should be skipped

## real-repo
- Description: This is real
`;
const result = parseRepositories(content);
assert.strictEqual(result.length, 1);
assert.strictEqual(result[0].name, 'real-repo');
});

test('should skip heading with "current repositories" in name', () => {
const content = `
## Current Repositories
- Description: This should be skipped

## real-repo
- Description: This is real
`;
const result = parseRepositories(content);
assert.strictEqual(result.length, 1);
assert.strictEqual(result[0].name, 'real-repo');
});

test('should handle nested code blocks correctly', () => {
const content = `
## Documentation

Here's an example:

\`\`\`markdown
## worlddriven-core
- Description: Democratic governance system for GitHub pull requests
- Topics: democracy, open-source, governance, automation

## worlddriven-documentation
- Description: Vision, philosophy, and organization management for worlddriven
- Topics: documentation, organization-management, governance
\`\`\`

---

## Current Repositories

## actual-repo
- Description: This is the only real repository
- Topics: real, test
`;
const result = parseRepositories(content);
assert.strictEqual(result.length, 1);
assert.strictEqual(result[0].name, 'actual-repo');
assert.strictEqual(result[0].description, 'This is the only real repository');
assert.deepStrictEqual(result[0].topics, ['real', 'test']);
});

test('should handle topics with extra whitespace', () => {
const content = `
## my-repo
- Description: Test repository
- Topics: topic1 , topic2 , topic3
`;
const result = parseRepositories(content);
assert.deepStrictEqual(result[0].topics, ['topic1', 'topic2', 'topic3']);
});

test('should handle repositories without topics', () => {
const content = `
## my-repo
- Description: Test repository without topics
`;
const result = parseRepositories(content);
assert.strictEqual(result[0].topics, undefined);
});

test('should match actual REPOSITORIES.md structure', () => {
const content = `# Worlddriven Organization Repositories

This file serves as the source of truth for all repositories in the worlddriven GitHub organization.

## Format

Each repository is defined using markdown headers and properties:

\`\`\`markdown
## repository-name
- Description: Brief description of the repository
- Topics: topic1, topic2, topic3
\`\`\`

## Example

\`\`\`markdown
## worlddriven-core
- Description: Democratic governance system for GitHub pull requests
- Topics: democracy, open-source, governance, automation

## worlddriven-documentation
- Description: Vision, philosophy, and organization management for worlddriven
- Topics: documentation, organization-management, governance
\`\`\`

---

## Current Repositories

<!-- Add repositories below this line -->
`;
const result = parseRepositories(content);
assert.deepStrictEqual(result, [], 'Should return empty array when no actual repositories are defined');
});
});