andromeda_cms 0.6.2-x86_64-linux → 0.7.0-x86_64-linux
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7fed8a5c28ff08d1728653c97d478b32eb93fa9ecae50ad15cefbd5a77da6684
|
|
4
|
+
data.tar.gz: 87d38dba0a49d1569597bfdc62377bbf5b59eb483338b24b3c45a0bf3505eeaa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bbfe873f404858bbe8b9e8e171a4bcbd78a6ae3fa16496d007b2a573c5b634f7e2411b57dad2cde732fb60561fefeaeeeb1bdcbe33b1f4936c1f7598a29f0ac4
|
|
7
|
+
data.tar.gz: 99ea310660ed7e61c9dbd5630eb508e9dcb6f69629ad2228a0931cf72bf2de8b21037b45c1bd62d94836f1e61c3a9c54da4c955adbc80827c1a48215d0cd06cf
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,43 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.7.0] - 2026-10-16
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- YAML frontmatter is now read the way Astro 7.3.6 and later read it. Astro
|
|
13
|
+
replaced js-yaml with the `yaml` package (core schema, timestamps, merge
|
|
14
|
+
keys), and Andromeda follows. Plain scalars that only YAML 1.1 or Psych
|
|
15
|
+
treat as numbers are now strings, as they are in Astro: values with
|
|
16
|
+
underscores (`1_000`, `0x1_F`), the `0b` prefix, and signed hexadecimal
|
|
17
|
+
or octal (`-0x1F`). Numbers in the core schema are unchanged, including
|
|
18
|
+
`010` (10), `0o17`, `0x1F` and `1e3`.
|
|
19
|
+
- A mapping that repeats a key now raises `Andromeda::SyntaxError` at the
|
|
20
|
+
second key, with its line and column, as Astro does. It used to keep the
|
|
21
|
+
last value silently. Keys are compared after resolution, so `1` and `01`
|
|
22
|
+
collide while `1` and `"1"` do not; repeated `<<` merge keys are allowed.
|
|
23
|
+
Content that repeats a key will fail to build until the duplicate is
|
|
24
|
+
removed.
|
|
25
|
+
|
|
26
|
+
## [0.6.3] - 2026-10-16
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- `andromeda:import_astro` no longer silently mishandles a `glob()` `pattern`
|
|
31
|
+
it cannot carry over. An array pattern is reduced to its first
|
|
32
|
+
non-exclusion element (it used to take the first string, even a `!`
|
|
33
|
+
exclusion), and a note in the report's "Needs a human" section plus a
|
|
34
|
+
`# TODO` above the generated `collection` line name what was lost:
|
|
35
|
+
exclusions that are not applied (those files are still copied and
|
|
36
|
+
loaded) and extra patterns that were dropped (those files are copied but
|
|
37
|
+
not loaded). An extglob pattern such as `!(drafts)/**/*.md`, which Ruby's
|
|
38
|
+
glob cannot match, is flagged the same way instead of leaving the
|
|
39
|
+
collection empty.
|
|
40
|
+
- The gem now requires `tomlrb ~> 2.0`. tomlrb 3.0 removed
|
|
41
|
+
`Tomlrb::Scanner`, which TOML frontmatter parsing uses to report the line
|
|
42
|
+
of a syntax error, so on Ruby 3.3 and later a fresh install picked up 3.0
|
|
43
|
+
and every TOML frontmatter raised `NameError`.
|
|
44
|
+
|
|
8
45
|
## [0.6.2] - 2026-10-09
|
|
9
46
|
|
|
10
47
|
### Fixed
|
|
@@ -189,6 +226,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
189
226
|
- Generators: `andromeda:install`, `andromeda:collection`,
|
|
190
227
|
`andromeda:component` and `andromeda:import_astro`.
|
|
191
228
|
|
|
229
|
+
[0.7.0]: https://github.com/AndromedaCMS/andromeda_cms/releases/tag/v0.7.0
|
|
230
|
+
[0.6.3]: https://github.com/AndromedaCMS/andromeda_cms/releases/tag/v0.6.3
|
|
192
231
|
[0.6.2]: https://github.com/AndromedaCMS/andromeda_cms/releases/tag/v0.6.2
|
|
193
232
|
[0.6.1]: https://github.com/AndromedaCMS/andromeda_cms/releases/tag/v0.6.1
|
|
194
233
|
[0.6.0]: https://github.com/AndromedaCMS/andromeda_cms/releases/tag/v0.6.0
|
|
@@ -25,45 +25,53 @@ module Andromeda
|
|
|
25
25
|
# a non-blank line is seen, no `---`/`+++` after it is a fence.
|
|
26
26
|
LEADING_RE = /\A\uFEFF?(?:[ \t]*\r?\n)*/
|
|
27
27
|
|
|
28
|
-
# Psych resolves plain scalars by YAML 1.1 rules;
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
28
|
+
# Psych resolves plain scalars by YAML 1.1 rules; Astro 7.3.6 and later
|
|
29
|
+
# read frontmatter with the `yaml` package (core schema, `timestamp` tag,
|
|
30
|
+
# merge keys), which follows YAML 1.2's core schema. The two disagree on
|
|
31
|
+
# values that really do appear in hand-written frontmatter, and a silent
|
|
32
|
+
# disagreement is worse than an error: `time: 12:30` becoming the integer
|
|
33
|
+
# 45000 passes every schema check and then displays nonsense. Each rule
|
|
34
|
+
# below mirrors what that parser does with the same plain scalar.
|
|
34
35
|
class ScalarScanner < Psych::ScalarScanner
|
|
35
36
|
# 1.1-only booleans; 1.2 keeps them as strings.
|
|
36
37
|
YAML_1_1_ONLY_BOOL = /\A(?:[Yy]es|YES|[Nn]o|NO|[Oo]n|ON|[Oo]ff|OFF|[Yy]|[Nn])\z/
|
|
37
38
|
# 1.1 base-60 (`1:30`) and comma-grouped (`1,000`) numbers; 1.2 has
|
|
38
39
|
# neither, so these stay strings.
|
|
39
40
|
YAML_1_1_ONLY_NUMBER = /\A[-+]?[0-9][0-9_]*(?:(?::[0-5]?[0-9])+|(?:,[0-9_]+)+)(?:\.[0-9_]*)?\z/
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
OCTAL = /\
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
# The core schema's integers. A leading zero is plain decimal (`010` is
|
|
42
|
+
# 10, where 1.1 reads octal); `0o` octal and `0x` hex take no sign.
|
|
43
|
+
INTEGER = /\A[-+]?[0-9]+\z/
|
|
44
|
+
OCTAL = /\A0o[0-7]+\z/
|
|
45
|
+
HEX = /\A0x[0-9a-fA-F]+\z/
|
|
46
|
+
# The core schema's floats, including an exponent without a decimal
|
|
47
|
+
# point (`1e3`), which Psych rejects. `.inf`/`.nan` are left to Psych.
|
|
48
|
+
FLOAT = /\A[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)(?:[eE][-+]?[0-9]+)?\z/
|
|
49
|
+
# Number-like spellings Psych accepts but the core schema does not
|
|
50
|
+
# (`1_000`, `0b1010`, `-0x1F`, `0o1_7`); they stay strings.
|
|
51
|
+
NON_CORE_NUMBER = /\A[-+]?(?:0[xXbBoO][0-9a-fA-F_]+|(?:[0-9][0-9_]*(?:\.[0-9_]*)?|\.[0-9_]+)(?:[eE][-+]?[0-9_]+)?)\z/
|
|
46
52
|
|
|
47
53
|
def tokenize(string)
|
|
48
54
|
case string
|
|
49
55
|
when YAML_1_1_ONLY_BOOL, YAML_1_1_ONLY_NUMBER then string
|
|
50
|
-
when
|
|
51
|
-
when OCTAL then Integer(string
|
|
52
|
-
when
|
|
56
|
+
when INTEGER then Integer(string, 10)
|
|
57
|
+
when OCTAL then Integer(string[2..], 8)
|
|
58
|
+
when HEX then Integer(string, 16)
|
|
59
|
+
when FLOAT then Float(normalize_float(string))
|
|
60
|
+
when NON_CORE_NUMBER then string
|
|
53
61
|
else super
|
|
54
62
|
end
|
|
55
63
|
end
|
|
56
64
|
|
|
57
65
|
private
|
|
58
66
|
|
|
59
|
-
# Ruby's Float() rejects the `1.e3` and `.5e3` spellings YAML allows.
|
|
67
|
+
# Ruby's Float() rejects the `1.`, `1.e3` and `.5e3` spellings YAML allows.
|
|
60
68
|
def normalize_float(string)
|
|
61
|
-
string.sub(/\.(?=[eE])/, ".0").sub(/\A([-+]?)\./, "\\10.")
|
|
69
|
+
string.sub(/\.(?=[eE]|\z)/, ".0").sub(/\A([-+]?)\./, "\\10.")
|
|
62
70
|
end
|
|
63
71
|
end
|
|
64
72
|
|
|
65
|
-
# Restricts `!!` tags to Date/Time (matching
|
|
66
|
-
# which also has no notion of arbitrary Ruby classes) while still
|
|
73
|
+
# Restricts `!!` tags to Date/Time (matching the `yaml` package's core
|
|
74
|
+
# schema, which also has no notion of arbitrary Ruby classes) while still
|
|
67
75
|
# permitting the two classes plain `YYYY-MM-DD`/timestamp scalars resolve
|
|
68
76
|
# to, so unquoted dates round-trip instead of raising
|
|
69
77
|
# Psych::DisallowedClass.
|
|
@@ -76,7 +84,7 @@ module Andromeda
|
|
|
76
84
|
end
|
|
77
85
|
|
|
78
86
|
# Plain (non-"safe") visitor so anchors/aliases resolve instead of
|
|
79
|
-
# raising --
|
|
87
|
+
# raising -- the `yaml` package resolves them by default, and copied Astro content may
|
|
80
88
|
# rely on them.
|
|
81
89
|
class ToRuby < Psych::Visitors::ToRuby
|
|
82
90
|
def initialize
|
|
@@ -199,6 +207,8 @@ module Andromeda
|
|
|
199
207
|
return {} if node.nil?
|
|
200
208
|
|
|
201
209
|
reject_alias_explosion!(node, path: path, content_start_line: content_start_line)
|
|
210
|
+
reject_duplicate_keys!(node, ScalarScanner.new(ClassLoader.new), path: path,
|
|
211
|
+
content_start_line: content_start_line)
|
|
202
212
|
ToRuby.new.accept(node) || {}
|
|
203
213
|
rescue Psych::SyntaxError => e
|
|
204
214
|
# Psych's own #line/#column are 0-based and relative to `text` (the
|
|
@@ -305,6 +315,36 @@ module Andromeda
|
|
|
305
315
|
end
|
|
306
316
|
private_class_method :expanded_size
|
|
307
317
|
|
|
318
|
+
# Astro's `yaml` parser reports a repeated mapping key as an error, where
|
|
319
|
+
# Psych silently keeps the last value. Keys are compared after resolution,
|
|
320
|
+
# so `1` and `01` collide while `1` and `"1"` do not. Plain `<<` merge
|
|
321
|
+
# keys, alias keys and collection keys are left out.
|
|
322
|
+
def reject_duplicate_keys!(node, scanner, path:, content_start_line:)
|
|
323
|
+
if node.is_a?(Psych::Nodes::Mapping)
|
|
324
|
+
seen = {}
|
|
325
|
+
node.children.each_slice(2) do |key, _value|
|
|
326
|
+
next unless key.is_a?(Psych::Nodes::Scalar)
|
|
327
|
+
next if key.plain && key.value == "<<"
|
|
328
|
+
|
|
329
|
+
resolved = key.plain ? scanner.tokenize(key.value) : key.value
|
|
330
|
+
resolved = resolved.to_f if resolved.is_a?(Numeric)
|
|
331
|
+
if seen.key?(resolved)
|
|
332
|
+
raise syntax_error("duplicate key #{key.value.inspect}", path: path,
|
|
333
|
+
content_start_line: content_start_line,
|
|
334
|
+
relative_line: key.start_line,
|
|
335
|
+
relative_column: key.start_column)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
seen[resolved] = true
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
node.children.to_a.each do |child|
|
|
343
|
+
reject_duplicate_keys!(child, scanner, path: path, content_start_line: content_start_line)
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
private_class_method :reject_duplicate_keys!
|
|
347
|
+
|
|
308
348
|
def syntax_error(message, path:, content_start_line:, relative_line:, relative_column:)
|
|
309
349
|
Andromeda::SyntaxError.new(
|
|
310
350
|
message,
|
data/lib/andromeda/version.rb
CHANGED
|
@@ -18,7 +18,19 @@ module Andromeda
|
|
|
18
18
|
# already makes for MDX import statements. Anything shaped differently
|
|
19
19
|
# falls through to `Attribute#unsupported?` rather than guessing.
|
|
20
20
|
module ContentConfigConverter
|
|
21
|
-
|
|
21
|
+
# `notes` are things about the collection itself (not one field)
|
|
22
|
+
# that still need a human -- e.g. a `pattern` this importer cannot
|
|
23
|
+
# carry over as written. Defaults to empty so existing
|
|
24
|
+
# `Collection.new` calls stay valid.
|
|
25
|
+
Collection = Struct.new(:name, :base, :pattern, :attributes, :notes, keyword_init: true) do
|
|
26
|
+
def initialize(notes: [], **rest) = super(notes: notes, **rest)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
DEFAULT_PATTERN = "**/*.{md,mdx}"
|
|
30
|
+
|
|
31
|
+
# Bash-style extglob openers (`!(drafts)`, `@(a|b)`, ...), which
|
|
32
|
+
# Astro's glob loader accepts but Ruby's `Dir.glob` does not.
|
|
33
|
+
EXTGLOB_RE = /[!@+*?]\(/.freeze
|
|
22
34
|
|
|
23
35
|
# One frontmatter field. `ruby_type` is nil when the Zod expression
|
|
24
36
|
# could not be recognized at all -- `source` (the original
|
|
@@ -65,16 +77,64 @@ module Andromeda
|
|
|
65
77
|
def build_collection(name, body, astro_root:)
|
|
66
78
|
body = BalancedScanner.strip_comments(body)
|
|
67
79
|
base = extract_string(body, /base\s*:/) || "./src/content/#{name}"
|
|
68
|
-
pattern =
|
|
80
|
+
pattern, notes = extract_pattern(body)
|
|
69
81
|
|
|
70
82
|
Collection.new(
|
|
71
83
|
name: name,
|
|
72
84
|
base: File.expand_path(base, astro_root),
|
|
73
85
|
pattern: pattern,
|
|
74
|
-
attributes: extract_attributes(body)
|
|
86
|
+
attributes: extract_attributes(body),
|
|
87
|
+
notes: notes
|
|
75
88
|
)
|
|
76
89
|
end
|
|
77
90
|
|
|
91
|
+
# Astro's `glob({ pattern: ... })` also takes an array (with `!`
|
|
92
|
+
# exclusions) and extglob syntax -- neither of which `Dir.glob`
|
|
93
|
+
# understands. Rather than silently dropping them, the closest
|
|
94
|
+
# usable pattern is kept and a note says what was lost.
|
|
95
|
+
#
|
|
96
|
+
# @return [Array(String, Array<String>)] the pattern and any notes.
|
|
97
|
+
def extract_pattern(body)
|
|
98
|
+
match = /pattern\s*:\s*\[/.match(body)
|
|
99
|
+
return extract_array_pattern(body, match.end(0) - 1) if match
|
|
100
|
+
|
|
101
|
+
pattern = extract_string(body, /pattern\s*:/) || DEFAULT_PATTERN
|
|
102
|
+
return [pattern, []] unless pattern.match?(EXTGLOB_RE)
|
|
103
|
+
|
|
104
|
+
[pattern, ["pattern `#{pattern}` uses extglob syntax, which Ruby's glob does not support -- " \
|
|
105
|
+
"no files will match this pattern"]]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Only what was actually lost gets a note: `!` exclusions (still
|
|
109
|
+
# copied and loaded) and any further non-exclusion patterns (copied
|
|
110
|
+
# but not loaded). A one-element or empty array loses nothing.
|
|
111
|
+
def extract_array_pattern(body, open_index)
|
|
112
|
+
close = BalancedScanner.matching_close(body, open_index)
|
|
113
|
+
# Squished onto one line: the note also becomes a `# TODO` comment.
|
|
114
|
+
source = body[open_index..close].gsub(/\s+/, " ")
|
|
115
|
+
elements = BalancedScanner.split_top_level(body[(open_index + 1)...close]).map do |literal|
|
|
116
|
+
literal.gsub(/\A(['"`])(.*)\1\z/, '\2')
|
|
117
|
+
end.reject(&:empty?)
|
|
118
|
+
exclusions, includes = elements.partition { |element| element.start_with?("!") }
|
|
119
|
+
pattern = includes.first || DEFAULT_PATTERN
|
|
120
|
+
dropped = includes.drop(1)
|
|
121
|
+
|
|
122
|
+
notes = []
|
|
123
|
+
unless exclusions.empty?
|
|
124
|
+
notes << "exclusions #{backticked(exclusions)} from pattern array #{source} are not applied, " \
|
|
125
|
+
"so the files they exclude are still copied and loaded"
|
|
126
|
+
end
|
|
127
|
+
unless dropped.empty?
|
|
128
|
+
notes << "patterns #{backticked(dropped)} from pattern array #{source} were dropped " \
|
|
129
|
+
"(only `#{pattern}` was kept), so files matching only them are copied but not loaded"
|
|
130
|
+
end
|
|
131
|
+
[pattern, notes]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def backticked(elements)
|
|
135
|
+
elements.map { |element| "`#{element}`" }.join(", ")
|
|
136
|
+
end
|
|
137
|
+
|
|
78
138
|
# @return [String, nil] the quoted value right after `label_re`, e.g.
|
|
79
139
|
# `base: './src/content/blog'` -> `"./src/content/blog"`.
|
|
80
140
|
def extract_string(body, label_re)
|
|
@@ -80,7 +80,10 @@ module Andromeda
|
|
|
80
80
|
fallback_collections
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
@collections.each
|
|
83
|
+
@collections.each do |collection|
|
|
84
|
+
refine_with_json_schema!(collection)
|
|
85
|
+
collection.notes.each { |note| @report[:notes] << "#{collection.name}: #{note}" }
|
|
86
|
+
end
|
|
84
87
|
end
|
|
85
88
|
|
|
86
89
|
# The fallback clause: every top-level directory under
|
|
@@ -231,6 +234,9 @@ module Andromeda
|
|
|
231
234
|
lines = ["# frozen_string_literal: true", ""]
|
|
232
235
|
namespace_modules.each_with_index { |mod, i| lines << "#{" " * i}module #{mod}" }
|
|
233
236
|
lines << "#{indent}class #{collection.name.to_s.singularize.camelize} < Andromeda::Entry"
|
|
237
|
+
collection.notes.each do |note|
|
|
238
|
+
lines << "#{indent} # TODO: #{note} (content.config.ts) -- fix `pattern` by hand."
|
|
239
|
+
end
|
|
234
240
|
lines << "#{indent} collection :#{collection.name}, base: \"app/content/#{collection.name}\", " \
|
|
235
241
|
"pattern: \"#{collection.pattern}\""
|
|
236
242
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: andromeda_cms
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: x86_64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- LIMHAUS Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: railties
|
|
@@ -56,14 +56,14 @@ dependencies:
|
|
|
56
56
|
name: tomlrb
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
|
-
- - "
|
|
59
|
+
- - "~>"
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
61
|
version: '2.0'
|
|
62
62
|
type: :runtime
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
|
-
- - "
|
|
66
|
+
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '2.0'
|
|
69
69
|
- !ruby/object:Gem::Dependency
|