metanorma-release 0.2.21 → 0.2.23
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.
- checksums.yaml +4 -4
- data/TODO.cleanups/01-channel-resolver-ocp.md +28 -0
- data/TODO.cleanups/02-documentation.md +31 -0
- data/TODO.cleanups/03-cleanup-repos.sh +86 -0
- data/TODO.cleanups/03-repo-channels-yml-cleanup.md +20 -0
- data/TODO.cleanups/04-version-bump.md +13 -0
- data/lib/metanorma/release/aggregation_pipeline.rb +6 -4
- data/lib/metanorma/release/channel_filter.rb +2 -10
- data/lib/metanorma/release/cli.rb +0 -3
- data/lib/metanorma/release/commands/aggregate.rb +6 -31
- data/lib/metanorma/release/commands/release_command.rb +5 -51
- data/lib/metanorma/release/config.rb +46 -65
- data/lib/metanorma/release/platform/github/manifest_reader.rb +3 -1
- data/lib/metanorma/release/site.rb +8 -4
- data/lib/metanorma/release/version.rb +1 -1
- data/lib/metanorma/release.rb +2 -1
- metadata +7 -3
- data/lib/metanorma/release/org_config.rb +0 -90
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fcbd94afb53b355f5637604bd713ec156adaf4e061c49dda44c1cf6cf777938a
|
|
4
|
+
data.tar.gz: 6dfa4f1d17e6aa4bac1bccc991510d9ac3856012d2205c9ce46644aa045d1ede
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5acb0eb4eaaa919c5a5f5f5a868aea80cc0d1de643fc9377d4497e0148f6b84171dee1208832f037b3dcab9ab285e8e7fdcba600cb0db99a2169ed4da484f50d
|
|
7
|
+
data.tar.gz: 9e55e666c20c4e5c93a2545ff5d8927328721878f83ae8f4affdbbe40bc31d94f021f796ce437d3389297eeecca802875dc2f3174545db67f2d586218f10156c
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# 01 — Extract routing domain model (OCP / model-driven)
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
`Config` mixes data access with routing behavior. Matching logic for pattern,
|
|
5
|
+
source, stage, and doctype is embedded as private methods using raw hashes.
|
|
6
|
+
This violates OCP — adding a new routing criterion requires modifying Config.
|
|
7
|
+
|
|
8
|
+
## Solution
|
|
9
|
+
Extract value objects that encapsulate matching logic:
|
|
10
|
+
|
|
11
|
+
- `DocumentEntry` — matches by `pattern` (slug glob) or `source` (file path)
|
|
12
|
+
- `RoutingRule` — matches by `stage` and/or `doctype`
|
|
13
|
+
- `ChannelResolver` — composes strategies, resolves channels for a Publication
|
|
14
|
+
|
|
15
|
+
Config becomes a pure data reader. ChannelResolver owns the behavior.
|
|
16
|
+
New routing criteria = new value object + register in resolver, no existing code changes.
|
|
17
|
+
|
|
18
|
+
## Files
|
|
19
|
+
- Create `lib/metanorma/release/channel_resolver.rb`
|
|
20
|
+
- Update `lib/metanorma/release/config.rb` — remove private routing methods
|
|
21
|
+
- Update `lib/metanorma/release/release_pipeline.rb` — use ChannelResolver
|
|
22
|
+
- Update `lib/metanorma/release.rb` — add autoload
|
|
23
|
+
- Update `spec/domain/config_spec.rb` — move routing specs
|
|
24
|
+
- Create `spec/domain/channel_resolver_spec.rb`
|
|
25
|
+
|
|
26
|
+
## Status
|
|
27
|
+
- [x] Implement — DocumentEntry merged with RoutingRule, single `documents` list
|
|
28
|
+
- [x] Specs pass — 234 examples, 0 failures
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 02 — Update metanorma.org documentation
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
Three documentation files still reference the old three-file architecture
|
|
5
|
+
(org config repo, per-repo channels.yml, org: key).
|
|
6
|
+
|
|
7
|
+
## Files to update
|
|
8
|
+
|
|
9
|
+
### `_pages/install/publication-setup.adoc`
|
|
10
|
+
- Remove org config repo section and channels.yml schema
|
|
11
|
+
- Remove `org:` key from config examples
|
|
12
|
+
- Remove `.metanorma/channels.yml` from file checklist
|
|
13
|
+
- Move `display_categories` into aggregator config schema
|
|
14
|
+
- Document two-file architecture: release manifest + aggregate config
|
|
15
|
+
|
|
16
|
+
### `_posts/2026-05-13-channel-based-publication.adoc`
|
|
17
|
+
- Remove `.metanorma/channels.yml` sections (lines 64-77, 124-130, 238-242)
|
|
18
|
+
- Remove CalConnect/.metanorma references (line 504)
|
|
19
|
+
- Update `display_category` reference (line 449) — now in aggregator config
|
|
20
|
+
- Reflect two-file architecture throughout
|
|
21
|
+
|
|
22
|
+
### `_pages/install/cicd.adoc`
|
|
23
|
+
- Remove "Channel discovery manifest" subsection (lines 476-483)
|
|
24
|
+
- Remove `channels.yml` from file tree (line 224)
|
|
25
|
+
- Remove `.metanorma/channels.yml` section (line 243)
|
|
26
|
+
- Remove org config references (lines 408-432)
|
|
27
|
+
|
|
28
|
+
## Status
|
|
29
|
+
- [x] publication-setup.adoc
|
|
30
|
+
- [x] blog post
|
|
31
|
+
- [x] cicd.adoc
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Delete .metanorma/channels.yml (and .metanorma/ dir) from CalConnect repos.
|
|
3
|
+
# Usage: bash TODO.cleanups/03-cleanup-repos.sh [--dry-run]
|
|
4
|
+
#
|
|
5
|
+
# This removes dead config files — the gem never reads per-repo channels.yml.
|
|
6
|
+
|
|
7
|
+
set -uo pipefail
|
|
8
|
+
|
|
9
|
+
DRY_RUN=false
|
|
10
|
+
[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=true
|
|
11
|
+
|
|
12
|
+
BRANCH="chore/remove-dead-channels-yml"
|
|
13
|
+
COMMIT_MSG="chore: remove dead .metanorma/channels.yml
|
|
14
|
+
|
|
15
|
+
The metanorma-release gem never reads per-repo .metanorma/channels.yml.
|
|
16
|
+
Channel routing is configured in metanorma.release.yml via documents[]
|
|
17
|
+
entries. This file was dead code that created confusion about the
|
|
18
|
+
architecture."
|
|
19
|
+
|
|
20
|
+
count=0
|
|
21
|
+
skipped=0
|
|
22
|
+
|
|
23
|
+
for channels_file in $(find /Users/mulgogi/src/calconnect -maxdepth 3 -name "channels.yml" -path "*/.metanorma/*" 2>/dev/null | sort); do
|
|
24
|
+
metanorma_dir=$(dirname "$channels_file")
|
|
25
|
+
repo_dir=$(dirname "$metanorma_dir")
|
|
26
|
+
repo_name=$(basename "$repo_dir")
|
|
27
|
+
|
|
28
|
+
# Skip the org config repo
|
|
29
|
+
if [[ "$repo_name" == "dot-metanorma" ]]; then
|
|
30
|
+
echo "SKIP: $repo_name (org config repo — handle separately)"
|
|
31
|
+
skipped=$((skipped + 1))
|
|
32
|
+
continue
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
count=$((count + 1))
|
|
36
|
+
|
|
37
|
+
if $DRY_RUN; then
|
|
38
|
+
echo "DRY: $repo_name — would remove $channels_file"
|
|
39
|
+
continue
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
cd "$repo_dir"
|
|
43
|
+
|
|
44
|
+
# Check for uncommitted changes
|
|
45
|
+
if ! git diff --quiet HEAD 2>/dev/null; then
|
|
46
|
+
echo "SKIP: $repo_name — uncommitted changes"
|
|
47
|
+
continue
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Create branch from main
|
|
51
|
+
git checkout main -q 2>/dev/null
|
|
52
|
+
git pull -q 2>/dev/null || true
|
|
53
|
+
git checkout -b "$BRANCH" -q 2>/dev/null || git checkout "$BRANCH" -q
|
|
54
|
+
|
|
55
|
+
# Remove the file and directory
|
|
56
|
+
rm -rf "$metanorma_dir"
|
|
57
|
+
|
|
58
|
+
# Commit and push
|
|
59
|
+
git add -A
|
|
60
|
+
if git diff --cached --quiet; then
|
|
61
|
+
echo "SKIP: $repo_name — no changes (already removed?)"
|
|
62
|
+
git checkout main -q 2>/dev/null
|
|
63
|
+
continue
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
git commit -m "$COMMIT_MSG" -q
|
|
67
|
+
git push -u origin "$BRANCH" -q 2>/dev/null
|
|
68
|
+
|
|
69
|
+
# Create PR
|
|
70
|
+
gh pr create \
|
|
71
|
+
--title "chore: remove dead .metanorma/channels.yml" \
|
|
72
|
+
--body "$COMMIT_MSG" \
|
|
73
|
+
--base main \
|
|
74
|
+
--head "$BRANCH" \
|
|
75
|
+
2>/dev/null || echo "WARN: $repo_name — PR creation failed (may already exist)"
|
|
76
|
+
|
|
77
|
+
git checkout main -q 2>/dev/null
|
|
78
|
+
|
|
79
|
+
echo "DONE: $repo_name"
|
|
80
|
+
done
|
|
81
|
+
|
|
82
|
+
echo ""
|
|
83
|
+
echo "Processed: $count repos, skipped: $skipped"
|
|
84
|
+
if $DRY_RUN; then
|
|
85
|
+
echo "(dry run — no changes made)"
|
|
86
|
+
fi
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# 03 — Delete .metanorma/channels.yml from 55 CalConnect repos
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
55 CalConnect repos have `.metanorma/channels.yml` that is dead code.
|
|
5
|
+
The gem never reads these files. They create confusion about the architecture.
|
|
6
|
+
|
|
7
|
+
## Approach
|
|
8
|
+
Script-based cleanup via GitHub API:
|
|
9
|
+
1. Find all repos with `.metanorma/channels.yml`
|
|
10
|
+
2. Create branch `chore/remove-dead-channels-yml`
|
|
11
|
+
3. Delete `.metanorma/channels.yml` (and `.metanorma/` dir if empty)
|
|
12
|
+
4. Commit, push, create PR
|
|
13
|
+
|
|
14
|
+
## Repo list (55 repos)
|
|
15
|
+
Found via: `find /Users/mulgogi/src/calconnect -maxdepth 3 -name "channels.yml"`
|
|
16
|
+
|
|
17
|
+
## Status
|
|
18
|
+
- [x] Script written — `03-cleanup-repos.sh`
|
|
19
|
+
- [x] Dry run verified — 54 repos found
|
|
20
|
+
- [x] Run on all repos — 53 branches pushed, 53 PRs created, all 53 rebase-merged
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# 04 — Version bump
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
Breaking changes require a version bump. Removed OrgConfig class, stages filter,
|
|
5
|
+
and org: key support. Added pattern-based routing, display_categories in
|
|
6
|
+
aggregator config.
|
|
7
|
+
|
|
8
|
+
## Changes
|
|
9
|
+
- Bump version in `lib/metanorma/release/version.rb`
|
|
10
|
+
- Update CHANGELOG if present
|
|
11
|
+
|
|
12
|
+
## Status
|
|
13
|
+
- [x] Bumped to 0.2.21
|
|
@@ -84,7 +84,7 @@ module Metanorma
|
|
|
84
84
|
|
|
85
85
|
private
|
|
86
86
|
|
|
87
|
-
def process_repo(repo,
|
|
87
|
+
def process_repo(repo, output_dir, config)
|
|
88
88
|
repo_key = repo.to_s
|
|
89
89
|
|
|
90
90
|
manifest_channels = @deps.manifest_reader.read(repo)
|
|
@@ -120,9 +120,11 @@ module Metanorma
|
|
|
120
120
|
|
|
121
121
|
if @deps.delta_state.processed?(repo_key, tag, content_hash)
|
|
122
122
|
files = @deps.delta_state.release_files(repo_key, tag)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
if files.all? { |f| File.exist?(File.join(output_dir, f)) }
|
|
124
|
+
publications << build_publication(metadata, files, content_hash,
|
|
125
|
+
release, repo)
|
|
126
|
+
next
|
|
127
|
+
end
|
|
126
128
|
end
|
|
127
129
|
|
|
128
130
|
zip_asset = find_zip_asset(release)
|
|
@@ -3,15 +3,13 @@
|
|
|
3
3
|
module Metanorma
|
|
4
4
|
module Release
|
|
5
5
|
class MetadataFilter
|
|
6
|
-
def initialize(channels: []
|
|
6
|
+
def initialize(channels: [])
|
|
7
7
|
@channels = channels.map { |c| Channel.new(c) }
|
|
8
|
-
@stages = Set.new(stages.map(&:downcase))
|
|
9
8
|
@all_channels = @channels.empty?
|
|
10
|
-
@all_stages = @stages.empty?
|
|
11
9
|
end
|
|
12
10
|
|
|
13
11
|
def matches?(release_metadata)
|
|
14
|
-
channel_match?(release_metadata)
|
|
12
|
+
channel_match?(release_metadata)
|
|
15
13
|
end
|
|
16
14
|
|
|
17
15
|
def overlaps?(manifest_channels)
|
|
@@ -31,12 +29,6 @@ module Metanorma
|
|
|
31
29
|
end
|
|
32
30
|
release_channels.any? { |rc| rc.matches?(@channels) }
|
|
33
31
|
end
|
|
34
|
-
|
|
35
|
-
def stage_match?(release_metadata)
|
|
36
|
-
return true if @all_stages
|
|
37
|
-
|
|
38
|
-
@stages.include?(release_metadata["stage"].to_s.downcase)
|
|
39
|
-
end
|
|
40
32
|
end
|
|
41
33
|
end
|
|
42
34
|
end
|
|
@@ -75,8 +75,6 @@ module Metanorma
|
|
|
75
75
|
option :repos, type: :array, desc: "Explicit repo list"
|
|
76
76
|
option :channels, type: :array, default: [],
|
|
77
77
|
desc: "Filter channels"
|
|
78
|
-
option :stages, type: :array, default: [],
|
|
79
|
-
desc: "Filter stages"
|
|
80
78
|
option :output_dir, type: :string, default: "_site/cc",
|
|
81
79
|
desc: "Output directory"
|
|
82
80
|
option :file_routing, type: :string,
|
|
@@ -98,7 +96,6 @@ module Metanorma
|
|
|
98
96
|
topic: options[:topic],
|
|
99
97
|
repos: options[:repos],
|
|
100
98
|
channels: options[:channels],
|
|
101
|
-
stages: options[:stages],
|
|
102
99
|
output_dir: options[:output_dir],
|
|
103
100
|
file_routing: options[:file_routing],
|
|
104
101
|
cache_dir: options[:cache_dir],
|
|
@@ -8,9 +8,9 @@ module Metanorma
|
|
|
8
8
|
class AggregateCommand
|
|
9
9
|
Config = Struct.new(
|
|
10
10
|
:source, :organizations, :topic, :repos, :repo_pattern, :local_path,
|
|
11
|
-
:channels, :
|
|
11
|
+
:channels, :output_dir, :file_routing, :cache_dir,
|
|
12
12
|
:data_dir, :include_drafts, :concurrency, :min_documents, :token,
|
|
13
|
-
:create_zip, :
|
|
13
|
+
:create_zip, :display_categories,
|
|
14
14
|
keyword_init: true
|
|
15
15
|
)
|
|
16
16
|
|
|
@@ -19,17 +19,16 @@ module Metanorma
|
|
|
19
19
|
|
|
20
20
|
def initialize(config)
|
|
21
21
|
@config = config
|
|
22
|
-
@org_config = nil
|
|
23
22
|
end
|
|
24
23
|
|
|
25
24
|
def call
|
|
26
|
-
load_org_config
|
|
27
25
|
result = run_aggregation
|
|
28
26
|
return result unless result.publications.any?
|
|
29
27
|
|
|
30
28
|
index = build_index(result)
|
|
31
29
|
site = Site.new(index: index, output_dir: @config.output_dir,
|
|
32
|
-
data_dir: @config.data_dir,
|
|
30
|
+
data_dir: @config.data_dir,
|
|
31
|
+
display_categories: @config.display_categories)
|
|
33
32
|
site.write!
|
|
34
33
|
site.enrich!
|
|
35
34
|
site.package! if @config.create_zip
|
|
@@ -47,7 +46,6 @@ module Metanorma
|
|
|
47
46
|
topic: merged[:topic],
|
|
48
47
|
repos: merged[:repos],
|
|
49
48
|
channels: merged[:channels],
|
|
50
|
-
stages: merged[:stages],
|
|
51
49
|
output_dir: merged[:output_dir],
|
|
52
50
|
file_routing: merged[:file_routing],
|
|
53
51
|
cache_dir: merged[:cache_dir] || DEFAULT_CACHE_DIR,
|
|
@@ -57,7 +55,7 @@ module Metanorma
|
|
|
57
55
|
min_documents: merged[:min_documents],
|
|
58
56
|
token: merged[:token],
|
|
59
57
|
create_zip: merged[:create_zip],
|
|
60
|
-
|
|
58
|
+
display_categories: merged[:display_categories],
|
|
61
59
|
)
|
|
62
60
|
end
|
|
63
61
|
|
|
@@ -76,7 +74,6 @@ module Metanorma
|
|
|
76
74
|
topic: cli_options[:topic] || gh["topic"],
|
|
77
75
|
repos: cli_options[:repos] || file_data["repos"],
|
|
78
76
|
channels: cli_options[:channels].any? ? cli_options[:channels] : Array(file_data["channels"]),
|
|
79
|
-
stages: cli_options[:stages].any? ? cli_options[:stages] : Array(file_data["stages"]),
|
|
80
77
|
output_dir: cli_options[:output_dir] || file_data["output_dir"],
|
|
81
78
|
file_routing: cli_options[:file_routing] || file_data["file_routing"] || "by-document",
|
|
82
79
|
cache_dir: cli_options[:cache_dir] || file_data["cache_dir"],
|
|
@@ -86,7 +83,7 @@ module Metanorma
|
|
|
86
83
|
min_documents: cli_options[:min_documents] || file_data["min_documents"],
|
|
87
84
|
token: cli_options[:token],
|
|
88
85
|
create_zip: cli_options[:create_zip],
|
|
89
|
-
|
|
86
|
+
display_categories: file_data["display_categories"] || [],
|
|
90
87
|
}
|
|
91
88
|
end
|
|
92
89
|
|
|
@@ -104,7 +101,6 @@ module Metanorma
|
|
|
104
101
|
|
|
105
102
|
metadata_filter = MetadataFilter.new(
|
|
106
103
|
channels: Channel.parse_list(@config.channels),
|
|
107
|
-
stages: @config.stages || [],
|
|
108
104
|
)
|
|
109
105
|
routing = FileRoutingFactory.from_name(@config.file_routing)
|
|
110
106
|
asset_processor = AssetProcessor.new(
|
|
@@ -169,27 +165,6 @@ module Metanorma
|
|
|
169
165
|
rescue LoadError
|
|
170
166
|
warn " (relaton gem not available — bibliography skipped)"
|
|
171
167
|
end
|
|
172
|
-
|
|
173
|
-
def load_org_config
|
|
174
|
-
return unless @config.org
|
|
175
|
-
|
|
176
|
-
ref = OrgConfig.parse_ref(@config.org)
|
|
177
|
-
local_path = OrgConfig.remote_path(ref)
|
|
178
|
-
@org_config = File.exist?(local_path) ? OrgConfig.from_file(local_path) : fetch_org_config_from_github(ref)
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def fetch_org_config_from_github(ref)
|
|
182
|
-
require "octokit"
|
|
183
|
-
token = @config.token || ENV.fetch("GITHUB_TOKEN", nil)
|
|
184
|
-
client = token ? Octokit::Client.new(access_token: token) : Octokit::Client.new
|
|
185
|
-
remote = OrgConfig.remote_path(ref)
|
|
186
|
-
contents = client.contents("#{ref.owner}/#{ref.repo}", path: remote)
|
|
187
|
-
decoded = Base64.decode64(contents[:content])
|
|
188
|
-
OrgConfig.from_yaml(decoded)
|
|
189
|
-
rescue StandardError => e
|
|
190
|
-
warn " (org config not loaded from #{ref.owner}/#{ref.repo}: #{e.message})"
|
|
191
|
-
OrgConfig.defaults
|
|
192
|
-
end
|
|
193
168
|
end
|
|
194
169
|
end
|
|
195
170
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "base64"
|
|
4
3
|
require "yaml"
|
|
5
4
|
|
|
6
5
|
module Metanorma
|
|
@@ -17,8 +16,7 @@ module Metanorma
|
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
def call
|
|
20
|
-
|
|
21
|
-
config = load_config(org_config: org_config)
|
|
19
|
+
config = load_config
|
|
22
20
|
options = { token: @config.token }
|
|
23
21
|
publisher = PlatformFactory.build_publisher(@config.platform, options)
|
|
24
22
|
channel_override = Channel.parse_list(@config.channels) if @config.channels
|
|
@@ -49,59 +47,15 @@ module Metanorma
|
|
|
49
47
|
|
|
50
48
|
private
|
|
51
49
|
|
|
52
|
-
def load_config
|
|
50
|
+
def load_config
|
|
53
51
|
if @config.config_source && File.exist?(@config.config_source)
|
|
54
|
-
Metanorma::Release::Config.from_file(@config.config_source
|
|
52
|
+
Metanorma::Release::Config.from_file(@config.config_source)
|
|
55
53
|
elsif @config.manifest && File.exist?(@config.manifest)
|
|
56
|
-
Metanorma::Release::Config.from_file(@config.manifest
|
|
54
|
+
Metanorma::Release::Config.from_file(@config.manifest)
|
|
57
55
|
else
|
|
58
|
-
Metanorma::Release::Config.defaults
|
|
56
|
+
Metanorma::Release::Config.defaults
|
|
59
57
|
end
|
|
60
58
|
end
|
|
61
|
-
|
|
62
|
-
def load_org_config
|
|
63
|
-
path = find_config_path
|
|
64
|
-
return nil unless path
|
|
65
|
-
|
|
66
|
-
org_ref = extract_org_ref(path)
|
|
67
|
-
return nil unless org_ref
|
|
68
|
-
|
|
69
|
-
resolve_org_config(org_ref)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def find_config_path
|
|
73
|
-
if @config.config_source && File.exist?(@config.config_source)
|
|
74
|
-
@config.config_source
|
|
75
|
-
elsif @config.manifest && File.exist?(@config.manifest)
|
|
76
|
-
@config.manifest
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def extract_org_ref(path)
|
|
81
|
-
raw = YAML.safe_load_file(path, permitted_classes: [Symbol]) || {}
|
|
82
|
-
raw["org"]
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def resolve_org_config(org_ref)
|
|
86
|
-
ref = OrgConfig.parse_ref(org_ref)
|
|
87
|
-
local_path = OrgConfig.remote_path(ref)
|
|
88
|
-
return OrgConfig.from_file(local_path) if File.exist?(local_path)
|
|
89
|
-
|
|
90
|
-
fetch_org_config_from_github(ref)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def fetch_org_config_from_github(ref)
|
|
94
|
-
require "octokit"
|
|
95
|
-
token = @config.token || ENV.fetch("GITHUB_TOKEN", nil)
|
|
96
|
-
client = token ? Octokit::Client.new(access_token: token) : Octokit::Client.new
|
|
97
|
-
remote = OrgConfig.remote_path(ref)
|
|
98
|
-
contents = client.contents("#{ref.owner}/#{ref.repo}", path: remote)
|
|
99
|
-
decoded = Base64.decode64(contents[:content])
|
|
100
|
-
OrgConfig.from_yaml(decoded)
|
|
101
|
-
rescue StandardError => e
|
|
102
|
-
warn " (org config not loaded from #{ref.owner}/#{ref.repo}: #{e.message})"
|
|
103
|
-
OrgConfig.defaults
|
|
104
|
-
end
|
|
105
59
|
end
|
|
106
60
|
end
|
|
107
61
|
end
|
|
@@ -5,27 +5,26 @@ require "yaml"
|
|
|
5
5
|
module Metanorma
|
|
6
6
|
module Release
|
|
7
7
|
class Config
|
|
8
|
-
def self.from_yaml(yaml_string
|
|
8
|
+
def self.from_yaml(yaml_string)
|
|
9
9
|
data = YAML.safe_load(yaml_string, permitted_classes: [Symbol])
|
|
10
|
-
new(data || {}
|
|
10
|
+
new(data || {})
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def self.from_file(path
|
|
13
|
+
def self.from_file(path)
|
|
14
14
|
unless File.exist?(path)
|
|
15
15
|
raise ArgumentError,
|
|
16
16
|
"Config file not found: #{path}"
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
from_yaml(File.read(path)
|
|
19
|
+
from_yaml(File.read(path))
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def self.defaults
|
|
23
|
-
new({}
|
|
22
|
+
def self.defaults
|
|
23
|
+
new({})
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def initialize(data
|
|
26
|
+
def initialize(data)
|
|
27
27
|
@data = data
|
|
28
|
-
@org_config = org_config
|
|
29
28
|
end
|
|
30
29
|
|
|
31
30
|
def org
|
|
@@ -36,18 +35,6 @@ module Metanorma
|
|
|
36
35
|
@data.fetch("channels", [])
|
|
37
36
|
end
|
|
38
37
|
|
|
39
|
-
def routing
|
|
40
|
-
@data.fetch("routing", {})
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def routing_default
|
|
44
|
-
routing.fetch("default", ["public"])
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def routing_rules
|
|
48
|
-
routing.fetch("rules", [])
|
|
49
|
-
end
|
|
50
|
-
|
|
51
38
|
def slug_config
|
|
52
39
|
@data.fetch("slug", {})
|
|
53
40
|
end
|
|
@@ -64,66 +51,60 @@ module Metanorma
|
|
|
64
51
|
@data.fetch("documents", [])
|
|
65
52
|
end
|
|
66
53
|
|
|
67
|
-
def
|
|
68
|
-
@
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def default_channels
|
|
72
|
-
list = defaults.fetch("channels", nil)
|
|
73
|
-
return ["public"] unless list
|
|
74
|
-
|
|
75
|
-
list
|
|
54
|
+
def document_entries
|
|
55
|
+
@document_entries ||= documents.map { |d| DocumentEntry.new(d) }
|
|
76
56
|
end
|
|
77
57
|
|
|
78
58
|
def resolve_channels(publication)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
rule_channels = resolve_routing_rules(publication)
|
|
83
|
-
return rule_channels if rule_channels
|
|
84
|
-
|
|
85
|
-
org_rule_channels = resolve_org_routing_rules(publication)
|
|
86
|
-
return org_rule_channels if org_rule_channels
|
|
59
|
+
ChannelResolver.resolve(publication, self)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
87
62
|
|
|
88
|
-
|
|
89
|
-
|
|
63
|
+
# Single routing entry — matches by any combination of pattern, source,
|
|
64
|
+
# stage, and doctype. An entry with no criteria matches everything (catch-all).
|
|
65
|
+
DocumentEntry = Struct.new(:pattern, :source, :stages, :doctypes, :channels, keyword_init: true) do
|
|
66
|
+
def initialize(data)
|
|
67
|
+
super(
|
|
68
|
+
pattern: data["pattern"],
|
|
69
|
+
source: data["source"],
|
|
70
|
+
stages: Array(data["stage"]).map(&:to_s),
|
|
71
|
+
doctypes: Array(data["doctype"]).map(&:to_s),
|
|
72
|
+
channels: Array(data["channels"]).map(&:to_s),
|
|
73
|
+
)
|
|
74
|
+
end
|
|
90
75
|
|
|
91
|
-
|
|
92
|
-
return
|
|
76
|
+
def matches?(publication)
|
|
77
|
+
return false if channels.empty?
|
|
93
78
|
|
|
94
|
-
|
|
95
|
-
|
|
79
|
+
if pattern && !File.fnmatch?(pattern, publication.slug)
|
|
80
|
+
return false
|
|
81
|
+
end
|
|
96
82
|
|
|
97
|
-
|
|
83
|
+
if source && !(publication.source_path&.end_with?(source) || false)
|
|
84
|
+
return false
|
|
85
|
+
end
|
|
98
86
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
next unless entry["source"] && publication.source_path&.end_with?(entry["source"])
|
|
102
|
-
return entry["channels"] if entry["channels"]
|
|
87
|
+
if !stages.empty? && !stages.include?(publication.stage.to_s)
|
|
88
|
+
return false
|
|
103
89
|
end
|
|
104
|
-
nil
|
|
105
|
-
end
|
|
106
90
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
match = true
|
|
110
|
-
match &&= Array(rule["stage"]).map(&:to_s).include?(publication.stage.to_s) if rule["stage"]
|
|
111
|
-
match &&= Array(rule["doctype"]).map(&:to_s).include?(publication.doctype.to_s) if rule["doctype"]
|
|
112
|
-
return rule["channels"] if match && rule["channels"]
|
|
91
|
+
if !doctypes.empty? && !doctypes.include?(publication.doctype.to_s)
|
|
92
|
+
return false
|
|
113
93
|
end
|
|
114
|
-
|
|
94
|
+
|
|
95
|
+
true
|
|
115
96
|
end
|
|
97
|
+
end
|
|
116
98
|
|
|
117
|
-
|
|
118
|
-
|
|
99
|
+
# Iterates document entries: first match wins. Falls back to ["public"].
|
|
100
|
+
class ChannelResolver
|
|
101
|
+
FALLBACK = ["public"].freeze
|
|
119
102
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
match &&= Array(rule["doctype"]).map(&:to_s).include?(publication.doctype.to_s) if rule["doctype"]
|
|
124
|
-
return rule["channels"] if match && rule["channels"]
|
|
103
|
+
def self.resolve(publication, config)
|
|
104
|
+
config.document_entries.each do |entry|
|
|
105
|
+
return entry.channels if entry.matches?(publication)
|
|
125
106
|
end
|
|
126
|
-
|
|
107
|
+
FALLBACK
|
|
127
108
|
end
|
|
128
109
|
end
|
|
129
110
|
end
|
|
@@ -21,7 +21,9 @@ module Metanorma
|
|
|
21
21
|
parsed = YAML.safe_load(yaml, permitted_classes: [Symbol])
|
|
22
22
|
return nil unless parsed.is_a?(Hash)
|
|
23
23
|
|
|
24
|
-
(parsed["channels"]
|
|
24
|
+
channels = Array(parsed["channels"])
|
|
25
|
+
Array(parsed["documents"]).each { |doc| channels.concat(Array(doc["channels"])) }
|
|
26
|
+
channels.map(&:to_s).uniq
|
|
25
27
|
rescue StandardError
|
|
26
28
|
nil
|
|
27
29
|
end
|
|
@@ -9,11 +9,11 @@ module Metanorma
|
|
|
9
9
|
class Site
|
|
10
10
|
attr_reader :index, :output_dir
|
|
11
11
|
|
|
12
|
-
def initialize(index:, output_dir:, data_dir: nil,
|
|
12
|
+
def initialize(index:, output_dir:, data_dir: nil, display_categories: [])
|
|
13
13
|
@index = index
|
|
14
14
|
@output_dir = output_dir
|
|
15
15
|
@data_dir = data_dir
|
|
16
|
-
@
|
|
16
|
+
@display_categories = display_categories || []
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def write!
|
|
@@ -168,9 +168,13 @@ module Metanorma
|
|
|
168
168
|
end
|
|
169
169
|
|
|
170
170
|
def resolve_display_category(doctype)
|
|
171
|
-
return nil
|
|
171
|
+
return nil if doctype.nil? || doctype.empty?
|
|
172
172
|
|
|
173
|
-
@
|
|
173
|
+
@display_categories.each do |cat|
|
|
174
|
+
doctypes = cat["doctypes"] || []
|
|
175
|
+
return { "name" => cat["name"], "slug" => cat["slug"] } if doctypes.include?(doctype)
|
|
176
|
+
end
|
|
177
|
+
nil
|
|
174
178
|
end
|
|
175
179
|
|
|
176
180
|
def add_contributors(hash, bib)
|
data/lib/metanorma/release.rb
CHANGED
|
@@ -11,8 +11,9 @@ module Metanorma
|
|
|
11
11
|
autoload :Index, "metanorma/release/index"
|
|
12
12
|
autoload :Site, "metanorma/release/site"
|
|
13
13
|
autoload :Channel, "metanorma/release/channel"
|
|
14
|
-
autoload :OrgConfig, "metanorma/release/org_config"
|
|
15
14
|
autoload :Config, "metanorma/release/config"
|
|
15
|
+
autoload :DocumentEntry, "metanorma/release/config"
|
|
16
|
+
autoload :ChannelResolver, "metanorma/release/config"
|
|
16
17
|
autoload :ContentHash, "metanorma/release/content_hash"
|
|
17
18
|
|
|
18
19
|
# Strategies
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: metanorma-release
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.23
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: relaton-bib
|
|
@@ -84,6 +84,11 @@ files:
|
|
|
84
84
|
- PROMPT.md
|
|
85
85
|
- README.adoc
|
|
86
86
|
- Rakefile
|
|
87
|
+
- TODO.cleanups/01-channel-resolver-ocp.md
|
|
88
|
+
- TODO.cleanups/02-documentation.md
|
|
89
|
+
- TODO.cleanups/03-cleanup-repos.sh
|
|
90
|
+
- TODO.cleanups/03-repo-channels-yml-cleanup.md
|
|
91
|
+
- TODO.cleanups/04-version-bump.md
|
|
87
92
|
- exe/metanorma-release
|
|
88
93
|
- lib/metanorma/release.rb
|
|
89
94
|
- lib/metanorma/release/aggregation_pipeline.rb
|
|
@@ -102,7 +107,6 @@ files:
|
|
|
102
107
|
- lib/metanorma/release/file_routing.rb
|
|
103
108
|
- lib/metanorma/release/index.rb
|
|
104
109
|
- lib/metanorma/release/interfaces.rb
|
|
105
|
-
- lib/metanorma/release/org_config.rb
|
|
106
110
|
- lib/metanorma/release/platform.rb
|
|
107
111
|
- lib/metanorma/release/platform/github.rb
|
|
108
112
|
- lib/metanorma/release/platform/github/manifest_reader.rb
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "yaml"
|
|
4
|
-
|
|
5
|
-
module Metanorma
|
|
6
|
-
module Release
|
|
7
|
-
class OrgConfig
|
|
8
|
-
Ref = Struct.new(:owner, :repo, :name, keyword_init: true)
|
|
9
|
-
|
|
10
|
-
def self.parse_ref(org_string)
|
|
11
|
-
parts = org_string.to_s.split("#", 2)
|
|
12
|
-
slug = parts[0].to_s.strip
|
|
13
|
-
segments = slug.split("/", 2)
|
|
14
|
-
raise ArgumentError, "Invalid org reference: #{org_string}" unless segments.length == 2
|
|
15
|
-
|
|
16
|
-
Ref.new(owner: segments[0], repo: segments[1], name: parts[1]&.strip)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def self.default_config_name
|
|
20
|
-
"channels"
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def self.remote_path(ref)
|
|
24
|
-
name = ref.name || default_config_name
|
|
25
|
-
".metanorma/#{name}.yml"
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def self.from_yaml(yaml_string)
|
|
29
|
-
data = YAML.safe_load(yaml_string, permitted_classes: [Symbol])
|
|
30
|
-
new(data || {})
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def self.from_file(path)
|
|
34
|
-
raise ArgumentError, "Org config file not found: #{path}" unless File.exist?(path)
|
|
35
|
-
|
|
36
|
-
from_yaml(File.read(path))
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def self.defaults
|
|
40
|
-
new({})
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def initialize(data)
|
|
44
|
-
@data = data
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def channels
|
|
48
|
-
@data.fetch("channels", [])
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def routing_default
|
|
52
|
-
dig_defaults_routing("default") || []
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def routing_rules
|
|
56
|
-
dig_defaults_routing("rules") || []
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def valid_channel?(name)
|
|
60
|
-
return true if channels.empty?
|
|
61
|
-
|
|
62
|
-
ch = Channel.new(name)
|
|
63
|
-
channels.any? do |valid|
|
|
64
|
-
valid_ch = Channel.new(valid)
|
|
65
|
-
ch.eql?(valid_ch) || ch.name.start_with?("#{valid_ch.name}/") || valid_ch.name.start_with?("#{ch.name}/")
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def display_categories
|
|
70
|
-
@data.fetch("display_categories", [])
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def display_category_for(doctype)
|
|
74
|
-
return nil if doctype.nil? || doctype.empty?
|
|
75
|
-
|
|
76
|
-
display_categories.each do |cat|
|
|
77
|
-
doctypes = cat["doctypes"] || []
|
|
78
|
-
return { "name" => cat["name"], "slug" => cat["slug"] } if doctypes.include?(doctype)
|
|
79
|
-
end
|
|
80
|
-
nil
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
private
|
|
84
|
-
|
|
85
|
-
def dig_defaults_routing(key)
|
|
86
|
-
@data.dig("defaults", "routing", key)
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|