qss 0.3.1 → 0.4.0
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/CHANGELOG.md +25 -0
- data/README.md +6 -0
- data/app/assets/stylesheets/qss/qss_core.css +1 -0
- data/config/grammar.yml +14 -0
- data/config/integrity.json +6 -0
- data/exe/qss-seal +26 -0
- data/lib/qss/configuration.rb +35 -3
- data/lib/qss/integrity.rb +124 -0
- data/lib/qss/qss_auditor.rb +1 -1
- data/lib/qss/qss_native_auditor.rb +11 -7
- data/lib/qss/version.rb +1 -1
- data/lib/qss.rb +1 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c68b04b280ac5a32cc4f515273e2209c7f406800fff01205e6b26842ba0b1412
|
|
4
|
+
data.tar.gz: e4b7069f106a33390f8052b21fb9e569e84ef501dc957858ec82f33f638d73b2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b98cd11a7ff2726c2939ead5f8f1a8cb93374cc26b6e1b188cd90c0c06e330feed753ff87f486db086b22aaa0b155ca3ca9f47bd25f8d309175e8bb9755bc644
|
|
7
|
+
data.tar.gz: ae1ad6f606a874f5fbb1b09df7463e290a2d268d97ff0e913d1ce24134d5b9354902e2636eca296d38e5c56d1f9d91093dff777b439464a9545d818ae2833fa4
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ versions follow [Semantic Versioning](https://semver.org/) (before 1.0, minor ve
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2026-10-19
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Three-tier stylesheet audit. The auditor now resolves classes against up to three tiers, and tiers 1 and 2 are
|
|
12
|
+
integrity-checked and otherwise silent (they surface only as an integrity failure):
|
|
13
|
+
1. **Core**: the gem's own `qss_core.css`, always known, never configured. `rake compile` marks it DO NOT EDIT and
|
|
14
|
+
writes `config/integrity.json` (its SHA-256, line endings normalized, plus the gem version); the auditor
|
|
15
|
+
verifies it before use. A mismatch, a missing file or a manifest from another version raises
|
|
16
|
+
`QSS::CoreIntegrityError`.
|
|
17
|
+
2. **Tool**: `auditor.tool_stylesheets`, a dev tool's own compiled chrome (for example Parapet's
|
|
18
|
+
`qss_application.css`). The tool seals each file when it builds it with the new `qss-seal` executable (or
|
|
19
|
+
`QSS::Integrity.seal`), which writes `<file>.qss-seal`; `--header` also marks the file DO NOT EDIT. An unsealed or
|
|
20
|
+
changed file raises `QSS::ToolIntegrityError`.
|
|
21
|
+
3. **Host**: `auditor.host_stylesheets`, the end consumer's own compiled output, as paths or globs relative to the
|
|
22
|
+
project root, plus whatever `QSS.configure(host_stylesheets: -> { ... })` returns (evaluated each time the
|
|
23
|
+
settings are read, in the host's own process). With nothing configured, a Rails host gets
|
|
24
|
+
`app/assets/builds/*.css`.
|
|
25
|
+
- `QSS::IntegrityError`, the base of both integrity errors, so a host's CI can rescue and report one class.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
- Core's classes are now always known to `unresolved_class`. Before, setting `stylesheet_path` replaced core's
|
|
29
|
+
stylesheet, so a consumer that listed only its own file lost every core class. `stylesheet_path` and
|
|
30
|
+
`drywall_stylesheet_path` keep working (`stylesheet_path` counts as a host stylesheet). The Tailwind/QSS boundary
|
|
31
|
+
check is unchanged and still reads `drywall_stylesheet_path` only.
|
|
32
|
+
|
|
8
33
|
## [0.3.1] - 2026-10-19
|
|
9
34
|
|
|
10
35
|
### Fixed
|
data/README.md
CHANGED
|
@@ -153,6 +153,12 @@ to a rule setting a structural property (`display`, `position`, sizing, `flex*`/
|
|
|
153
153
|
`QSS::QssAuditor` is the older class-whitelist auditor: it predates the bracket vocabulary (it reports `qss-w[200]`
|
|
154
154
|
as unknown) and is kept for projects that still use its whitelist.
|
|
155
155
|
|
|
156
|
+
**Three tiers of stylesheet.** Class resolution reads core's own `qss_core.css` (always, verified against the manifest
|
|
157
|
+
shipped with the gem, `QSS::CoreIntegrityError` on a mismatch), any sealed tool stylesheets
|
|
158
|
+
(`auditor.tool_stylesheets`, sealed by the tool's build with `qss-seal`, `QSS::ToolIntegrityError`), and the host's
|
|
159
|
+
own output (`auditor.host_stylesheets`, `stylesheet_path`, `QSS.configure(host_stylesheets: -> { ... })`; a Rails host
|
|
160
|
+
defaults to `app/assets/builds/*.css`).
|
|
161
|
+
|
|
156
162
|
Appearance classes (colors, type styling, shadows, filters) are **deprecated** as of 0.2.0: the auditor reports each as
|
|
157
163
|
orange `deprecated_decorative`, since appearance belongs to whatever fills Drywall; QSS's quantized bracket scale
|
|
158
164
|
(`qss-border[2]`, `qss-text-size[14]`) is unaffected. A Drywall stylesheet compiled with the `qss` prefix is a red
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
@charset "UTF-8";
|
|
2
|
+
/* GENERATED FILE - DO NOT EDIT. Any change is caught by the QSS integrity check (qss-seal) at audit time. */
|
|
2
3
|
/*
|
|
3
4
|
* QSS Core — Quantized Spatial Styling DNA
|
|
4
5
|
* Driven by config/grammar.yml (via exe/qss-sync -> _qss_defaults.scss; a consumer overrides it)
|
data/config/grammar.yml
CHANGED
|
@@ -281,6 +281,20 @@ auditor:
|
|
|
281
281
|
# Drywall framework configured).
|
|
282
282
|
drywall_stylesheet_path: ""
|
|
283
283
|
|
|
284
|
+
# Three-tier stylesheet audit (0.4.0). Tier 1, core's own qss_core.css, is always known and always integrity-
|
|
285
|
+
# checked against the manifest shipped with the gem (QSS::CoreIntegrityError); it is not configured here.
|
|
286
|
+
#
|
|
287
|
+
# Tier 2, tool_stylesheets: a dev tool's own compiled chrome (for example Parapet's qss_application.css). Paths or
|
|
288
|
+
# globs relative to the project root. Each must be sealed by its owner (`qss-seal`); an unsealed or changed one
|
|
289
|
+
# raises QSS::ToolIntegrityError. Its classes resolve silently, nothing else is reported.
|
|
290
|
+
tool_stylesheets: []
|
|
291
|
+
|
|
292
|
+
# Tier 3, host_stylesheets: the end consumer's own compiled output (QSS overrides and Drywall). Paths or globs
|
|
293
|
+
# relative to the project root, plus whatever QSS.configure(host_stylesheets: -> { ... }) returns. When both are
|
|
294
|
+
# empty (and stylesheet_path is too), a Rails host gets app/assets/builds/*.css. stylesheet_path above still
|
|
295
|
+
# works and counts as a host stylesheet.
|
|
296
|
+
host_stylesheets: []
|
|
297
|
+
|
|
284
298
|
# Files exe/qss-sync generates into. core_variables, engine, auditor and native_auditor are core's own files,
|
|
285
299
|
# relative to core's root; variables is the consumer's, relative to the directory the sync is run from, and a consumer
|
|
286
300
|
# normally sets it in its own config file. (Keeps file locations out of the script itself.)
|
data/exe/qss-seal
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Seals a tool-tier stylesheet (a dev tool's own compiled chrome) so the QSS auditor can verify it has not been
|
|
5
|
+
# changed since it was built: writes <stylesheet>.qss-seal next to it. Run it as the last step of the tool's build.
|
|
6
|
+
#
|
|
7
|
+
# qss-seal path/to/qss_application.css [--header]
|
|
8
|
+
#
|
|
9
|
+
# --header also marks the file DO NOT EDIT (idempotent). An audit that finds the file changed after sealing
|
|
10
|
+
# raises QSS::ToolIntegrityError.
|
|
11
|
+
require_relative "../lib/qss/version"
|
|
12
|
+
require_relative "../lib/qss/integrity"
|
|
13
|
+
|
|
14
|
+
header = ARGV.delete("--header")
|
|
15
|
+
paths = ARGV
|
|
16
|
+
if paths.empty? || paths.any? { |p| p.start_with?("-") }
|
|
17
|
+
warn "usage: qss-seal STYLESHEET... [--header]"
|
|
18
|
+
exit 1
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
paths.each do |path|
|
|
22
|
+
puts "Sealed #{path} -> #{QSS::Integrity.seal(path, header: !header.nil?)}"
|
|
23
|
+
rescue QSS::IntegrityError => e
|
|
24
|
+
warn e.message
|
|
25
|
+
exit 1
|
|
26
|
+
end
|
data/lib/qss/configuration.rb
CHANGED
|
@@ -41,8 +41,11 @@ module QSS
|
|
|
41
41
|
class Configuration
|
|
42
42
|
attr_reader :root, :config_path
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
# host_stylesheets: an optional callable returning the host app's compiled stylesheet paths (or globs), called
|
|
45
|
+
# each time the settings are read, in the host's own process. Added to the config file's host_stylesheets.
|
|
46
|
+
def initialize(root: nil, config: nil, host_stylesheets: nil)
|
|
45
47
|
@root = root && File.expand_path(root.to_s)
|
|
48
|
+
@host_stylesheets = host_stylesheets
|
|
46
49
|
@config_path = config && resolve(config.to_s)
|
|
47
50
|
@cache = {}
|
|
48
51
|
end
|
|
@@ -83,6 +86,11 @@ module QSS
|
|
|
83
86
|
settings["registry_path"] = resolve(settings["registry_path"])
|
|
84
87
|
settings["stylesheet_path"] = resolve(settings["stylesheet_path"])
|
|
85
88
|
settings["drywall_stylesheet_path"] = resolve(settings["drywall_stylesheet_path"])
|
|
89
|
+
settings["tool_stylesheets"] = stylesheet_list("tool_stylesheets", settings["tool_stylesheets"]) if settings.key?("tool_stylesheets")
|
|
90
|
+
if settings.key?("host_stylesheets")
|
|
91
|
+
settings["host_stylesheets"] = stylesheet_list("host_stylesheets", settings["host_stylesheets"], @host_stylesheets&.call)
|
|
92
|
+
settings["host_stylesheets"] = rails_default_stylesheets if settings["host_stylesheets"].empty? && settings["stylesheet_path"].empty?
|
|
93
|
+
end
|
|
86
94
|
settings.freeze
|
|
87
95
|
end
|
|
88
96
|
|
|
@@ -93,6 +101,30 @@ module QSS
|
|
|
93
101
|
|
|
94
102
|
private
|
|
95
103
|
|
|
104
|
+
# tool_stylesheets / host_stylesheets: a list of paths or globs (relative to the root), expanded to the files that
|
|
105
|
+
# exist now, in a stable order. Anything but a list of strings is a ConfigurationError.
|
|
106
|
+
def stylesheet_list(key, configured, extra = nil)
|
|
107
|
+
entries = configured.nil? ? [] : configured
|
|
108
|
+
unless entries.is_a?(Array) && entries.all?(String)
|
|
109
|
+
raise ConfigurationError, "auditor.#{key} must be a list of paths or globs, got #{entries.inspect}"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
(entries + Array(extra).map(&:to_s)).flat_map do |entry|
|
|
113
|
+
next [] if entry.empty?
|
|
114
|
+
|
|
115
|
+
path = resolve(entry)
|
|
116
|
+
entry.match?(/[*?\[{]/) ? Dir.glob(path).sort : [ path ]
|
|
117
|
+
end.uniq
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# A Rails host that configures nothing gets its built CSS (cssbundling-rails / tailwindcss-rails output).
|
|
121
|
+
def rails_default_stylesheets
|
|
122
|
+
base = root_or_default
|
|
123
|
+
return [] unless base && defined?(Rails) && Rails.respond_to?(:root)
|
|
124
|
+
|
|
125
|
+
Dir.glob(File.join(base, "app", "assets", "builds", "*.css")).sort
|
|
126
|
+
end
|
|
127
|
+
|
|
96
128
|
def deep_merge(base, over)
|
|
97
129
|
return over unless base.is_a?(Hash) && over.is_a?(Hash)
|
|
98
130
|
|
|
@@ -105,8 +137,8 @@ module QSS
|
|
|
105
137
|
@configuration ||= Configuration.new
|
|
106
138
|
end
|
|
107
139
|
|
|
108
|
-
def configure(root:, config: nil)
|
|
109
|
-
@configuration = Configuration.new(root: root, config: config)
|
|
140
|
+
def configure(root:, config: nil, host_stylesheets: nil)
|
|
141
|
+
@configuration = Configuration.new(root: root, config: config, host_stylesheets: host_stylesheets)
|
|
110
142
|
end
|
|
111
143
|
end
|
|
112
144
|
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module QSS
|
|
7
|
+
# Base class for every integrity failure, so a host app's CI can rescue one thing and report it.
|
|
8
|
+
class IntegrityError < StandardError; end
|
|
9
|
+
|
|
10
|
+
# The gem's own compiled stylesheet no longer matches what this gem version shipped (edited, truncated, or from
|
|
11
|
+
# another version). QSS never audits against a core it cannot vouch for.
|
|
12
|
+
class CoreIntegrityError < IntegrityError; end
|
|
13
|
+
|
|
14
|
+
# A tool-tier stylesheet (a dev tool's own chrome, for example Parapet's qss_application.css) is unsealed or no
|
|
15
|
+
# longer matches its seal. The seal is owned by the tool: it writes it at build time with `qss-seal`.
|
|
16
|
+
class ToolIntegrityError < IntegrityError; end
|
|
17
|
+
|
|
18
|
+
# Tier 1 (core) and tier 2 (tool) stylesheet integrity. A stylesheet's fingerprint is the SHA-256 of its content
|
|
19
|
+
# with line endings normalized to LF, so a CRLF working tree (this repo's own) and an LF checkout agree.
|
|
20
|
+
#
|
|
21
|
+
# Core's fingerprint lives in config/integrity.json, written by `rake compile` and shipped in the gem together
|
|
22
|
+
# with the version it belongs to. A tool's lives in a sidecar `<stylesheet>.qss-seal` written by `qss-seal`.
|
|
23
|
+
module Integrity
|
|
24
|
+
CORE_CSS = File.expand_path("../../app/assets/stylesheets/qss/qss_core.css", __dir__)
|
|
25
|
+
CORE_MANIFEST = File.expand_path("../../config/integrity.json", __dir__)
|
|
26
|
+
SEAL_SUFFIX = ".qss-seal"
|
|
27
|
+
HEADER = "/* GENERATED FILE - DO NOT EDIT. Any change is caught by the QSS integrity check (qss-seal) at audit time. */"
|
|
28
|
+
|
|
29
|
+
VERIFIED = {}
|
|
30
|
+
private_constant :VERIFIED
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
def digest(path)
|
|
34
|
+
Digest::SHA256.hexdigest(File.binread(path).gsub("\r\n", "\n"))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Raises CoreIntegrityError unless core's stylesheet matches the manifest shipped with this gem version.
|
|
38
|
+
def verify_core!
|
|
39
|
+
check(CORE_CSS, :core, CORE_MANIFEST) do
|
|
40
|
+
manifest = read_json(CORE_MANIFEST) { |why| raise CoreIntegrityError, "QSS core integrity manifest #{why}" }
|
|
41
|
+
unless manifest["version"] == QSS::VERSION
|
|
42
|
+
raise CoreIntegrityError, "QSS core integrity manifest is for qss #{manifest['version'].inspect}, " \
|
|
43
|
+
"this is qss #{QSS::VERSION}"
|
|
44
|
+
end
|
|
45
|
+
raise CoreIntegrityError, "QSS core stylesheet #{CORE_CSS} is missing" unless File.file?(CORE_CSS)
|
|
46
|
+
|
|
47
|
+
expected = manifest.dig("files", "qss_core.css")
|
|
48
|
+
raise CoreIntegrityError, "QSS core integrity manifest lists no qss_core.css" if expected.to_s.empty?
|
|
49
|
+
next if expected == digest(CORE_CSS)
|
|
50
|
+
|
|
51
|
+
raise CoreIntegrityError, "#{CORE_CSS} does not match the stylesheet qss #{QSS::VERSION} shipped; " \
|
|
52
|
+
"reinstall the gem, never edit it"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Raises ToolIntegrityError unless the tool's stylesheet exists, is sealed, and still matches its seal.
|
|
57
|
+
def verify_tool!(path)
|
|
58
|
+
check(path, :tool) do
|
|
59
|
+
raise ToolIntegrityError, "tool stylesheet #{path} does not exist" unless File.file?(path)
|
|
60
|
+
|
|
61
|
+
seal = seal_path(path)
|
|
62
|
+
unless File.file?(seal)
|
|
63
|
+
raise ToolIntegrityError, "tool stylesheet #{path} is not sealed; run `qss-seal #{path}` when it is built"
|
|
64
|
+
end
|
|
65
|
+
expected = read_json(seal) { |why| raise ToolIntegrityError, "seal #{seal} #{why}" }["sha256"]
|
|
66
|
+
next if expected == digest(path)
|
|
67
|
+
|
|
68
|
+
raise ToolIntegrityError, "#{path} does not match its seal (#{File.basename(seal)}); it was changed after " \
|
|
69
|
+
"it was built. Rebuild it, then run `qss-seal`"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Writes the seal for a tool stylesheet. With header: true, first marks the file DO NOT EDIT (idempotent).
|
|
74
|
+
def seal(path, header: false)
|
|
75
|
+
raise ToolIntegrityError, "cannot seal #{path}: no such file" unless File.file?(path)
|
|
76
|
+
|
|
77
|
+
add_header(path) if header
|
|
78
|
+
File.write(seal_path(path), JSON.pretty_generate("sha256" => digest(path), "sealed_by" => "qss #{QSS::VERSION}") + "\n")
|
|
79
|
+
VERIFIED.clear
|
|
80
|
+
seal_path(path)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Maintainer step (rake compile / rake seal): writes core's manifest for the current qss_core.css.
|
|
84
|
+
def write_core_manifest!
|
|
85
|
+
add_header(CORE_CSS)
|
|
86
|
+
File.write(CORE_MANIFEST, JSON.pretty_generate("version" => QSS::VERSION, "files" => { "qss_core.css" => digest(CORE_CSS) }) + "\n")
|
|
87
|
+
VERIFIED.clear
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def seal_path(path)
|
|
91
|
+
"#{path}#{SEAL_SUFFIX}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
# Runs the block once per (path, mtime, size): the audit calls this for every file it checks.
|
|
97
|
+
def check(path, tier, *also)
|
|
98
|
+
stamp = File.file?(path) ? [ path, *also ].map { |f| File.file?(f) ? [ File.mtime(f), File.size(f) ] : nil } : nil
|
|
99
|
+
return if VERIFIED[[ tier, path ]] == stamp && stamp
|
|
100
|
+
|
|
101
|
+
yield
|
|
102
|
+
VERIFIED[[ tier, path ]] = stamp
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def read_json(path)
|
|
106
|
+
return yield("is missing (#{path})") unless File.file?(path)
|
|
107
|
+
|
|
108
|
+
JSON.parse(File.read(path))
|
|
109
|
+
rescue JSON::ParserError
|
|
110
|
+
yield("is not valid JSON (#{path})")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# The header goes after a leading @charset (which only counts as the very first thing in the file).
|
|
114
|
+
def add_header(path)
|
|
115
|
+
text = File.binread(path).force_encoding("UTF-8")
|
|
116
|
+
return if text.include?(HEADER)
|
|
117
|
+
|
|
118
|
+
eol = text.include?("\r\n") ? "\r\n" : "\n"
|
|
119
|
+
charset = text[/\A@charset [^;]+;\r?\n/]
|
|
120
|
+
File.binwrite(path, "#{charset}#{HEADER}#{eol}#{text.delete_prefix(charset.to_s)}")
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
data/lib/qss/qss_auditor.rb
CHANGED
|
@@ -123,7 +123,7 @@ module QSS
|
|
|
123
123
|
|
|
124
124
|
BRAND_COLORS = {"black"=>"#000000", "white"=>"#ffffff", "transparent"=>"transparent", "none"=>"none", "slate-50"=>"#f8fafc", "slate-100"=>"#f1f5f9", "slate-200"=>"#e2e8f0", "slate-300"=>"#cbd5e1", "slate-400"=>"#94a3b8", "slate-500"=>"#64748b", "slate-600"=>"#475569", "slate-700"=>"#334155", "slate-800"=>"#1e293b", "slate-900"=>"#0f172a", "slate-950"=>"#020617", "rose-500"=>"#f43f5e"}.freeze
|
|
125
125
|
|
|
126
|
-
AUDITOR_CONFIG = {"id_prefix"=>"", "structural_words"=>["header", "footer", "sidebar", "workspace", "main", "panel", "frame", "wrapper", "nav", "aside"], "narrow_words"=>["header", "footer", "sidebar", "workspace", "main", "panel", "frame", "wrapper", "nav", "aside"], "icon_prefixes"=>[], "registry_path"=>"", "stylesheet_path"=>"", "drywall_stylesheet_path"=>"", "portal_root"=>"#qss-portal"}.freeze
|
|
126
|
+
AUDITOR_CONFIG = {"id_prefix"=>"", "structural_words"=>["header", "footer", "sidebar", "workspace", "main", "panel", "frame", "wrapper", "nav", "aside"], "narrow_words"=>["header", "footer", "sidebar", "workspace", "main", "panel", "frame", "wrapper", "nav", "aside"], "icon_prefixes"=>[], "registry_path"=>"", "stylesheet_path"=>"", "drywall_stylesheet_path"=>"", "tool_stylesheets"=>[], "host_stylesheets"=>[], "portal_root"=>"#qss-portal"}.freeze
|
|
127
127
|
# QSS-GENERATED-END
|
|
128
128
|
# rubocop:enable all
|
|
129
129
|
|
|
@@ -9,7 +9,7 @@ module QSS
|
|
|
9
9
|
# GENERATED FROM grammar.yml - DO NOT EDIT
|
|
10
10
|
PAYLOAD_TAGS = ["p", "h1", "h2", "h3", "h4", "h5", "h6", "span", "label", "blockquote", "li"].freeze
|
|
11
11
|
|
|
12
|
-
AUDITOR_CONFIG = {"id_prefix"=>"", "structural_words"=>["header", "footer", "sidebar", "workspace", "main", "panel", "frame", "wrapper", "nav", "aside"], "narrow_words"=>["header", "footer", "sidebar", "workspace", "main", "panel", "frame", "wrapper", "nav", "aside"], "icon_prefixes"=>[], "registry_path"=>"", "stylesheet_path"=>"", "drywall_stylesheet_path"=>"", "portal_root"=>"#qss-portal"}.freeze
|
|
12
|
+
AUDITOR_CONFIG = {"id_prefix"=>"", "structural_words"=>["header", "footer", "sidebar", "workspace", "main", "panel", "frame", "wrapper", "nav", "aside"], "narrow_words"=>["header", "footer", "sidebar", "workspace", "main", "panel", "frame", "wrapper", "nav", "aside"], "icon_prefixes"=>[], "registry_path"=>"", "stylesheet_path"=>"", "drywall_stylesheet_path"=>"", "tool_stylesheets"=>[], "host_stylesheets"=>[], "portal_root"=>"#qss-portal"}.freeze
|
|
13
13
|
# QSS-GENERATED-END
|
|
14
14
|
# rubocop:enable all
|
|
15
15
|
|
|
@@ -455,6 +455,7 @@ module QSS
|
|
|
455
455
|
|
|
456
456
|
def self.load(path)
|
|
457
457
|
path = path.to_s.empty? ? CORE_CSS_PATH : path
|
|
458
|
+
QSS::Integrity.verify_core! if path == CORE_CSS_PATH
|
|
458
459
|
return Set.new unless File.exist?(path)
|
|
459
460
|
mtime = File.mtime(path)
|
|
460
461
|
cached = CACHE[path]
|
|
@@ -914,15 +915,18 @@ module QSS
|
|
|
914
915
|
narrow_pattern = narrow_structural_pattern(settings)
|
|
915
916
|
portal_label = portal_label(settings)
|
|
916
917
|
registry = Registry.load(settings['registry_path'])
|
|
917
|
-
|
|
918
|
+
# Three tiers (0.4.0). Core's own CSS is always known and always integrity-checked; a tool's sealed chrome
|
|
919
|
+
# resolves silently; the host's own output (host_stylesheets, plus the older stylesheet_path) is what its markup
|
|
920
|
+
# renders against. A class resolving in the Drywall framework's own compiled CSS is just as real (DESIGN_HISTORY
|
|
921
|
+
# #12.8: unresolved_class once ignored drywall_stylesheet_path, flagging every real Tailwind class dead).
|
|
918
922
|
drywall_stylesheet_path = settings['drywall_stylesheet_path'].to_s
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
+
host_stylesheets = ([ settings['stylesheet_path'].to_s ] + Array(settings['host_stylesheets'])).reject(&:empty?).uniq
|
|
924
|
+
Array(settings['tool_stylesheets']).each { |path| QSS::Integrity.verify_tool!(path) }
|
|
925
|
+
known_classes = Stylesheet.load('')
|
|
926
|
+
(Array(settings['tool_stylesheets']) + host_stylesheets).each { |path| known_classes |= Stylesheet.load(path) }
|
|
923
927
|
known_classes |= Stylesheet.load(drywall_stylesheet_path) unless drywall_stylesheet_path.empty?
|
|
924
928
|
drywall_properties = Stylesheet.load_properties(settings['drywall_stylesheet_path'])
|
|
925
|
-
checked_stylesheets = [
|
|
929
|
+
checked_stylesheets = [ "core's qss_core.css" ] + host_stylesheets
|
|
926
930
|
checked_stylesheets << drywall_stylesheet_path unless drywall_stylesheet_path.empty?
|
|
927
931
|
stylesheet_label = checked_stylesheets.size > 1 ? "either compiled stylesheet (#{checked_stylesheets.join(' or ')})" : "the compiled stylesheet (#{checked_stylesheets.first})"
|
|
928
932
|
exempt_prefixes = [ prefix, IdRules::FRAMEWORK_PREFIX ]
|
data/lib/qss/version.rb
CHANGED
data/lib/qss.rb
CHANGED
|
@@ -10,6 +10,7 @@ require "json"
|
|
|
10
10
|
require "qss/version" # QSS::Version, QSS::VERSION
|
|
11
11
|
require "qss/blueprints" # QSS::Blueprints (QSS.grid, QSS.object: layout arithmetic, object blueprints)
|
|
12
12
|
require "qss/configuration" # QSS::Configuration (the consuming project's settings, read at runtime)
|
|
13
|
+
require "qss/integrity" # QSS::Integrity (core and tool stylesheet integrity; QSS::IntegrityError)
|
|
13
14
|
require "qss/qss_auditor" # QSS::QssAuditor (DSL-whitelist auditor)
|
|
14
15
|
require "qss/qss_native_auditor" # QSS::QssNativeAuditor (native-CSS auditor)
|
|
15
16
|
require "qss/engine" if defined?(::Rails::Engine) # QSS::Engine (Rails only: serves QSS's assets)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: qss
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kraig Fairhurst
|
|
@@ -108,6 +108,7 @@ email:
|
|
|
108
108
|
executables:
|
|
109
109
|
- qss-core-render-check
|
|
110
110
|
- qss-engine-render-check
|
|
111
|
+
- qss-seal
|
|
111
112
|
- qss-sync
|
|
112
113
|
extensions: []
|
|
113
114
|
extra_rdoc_files: []
|
|
@@ -122,14 +123,17 @@ files:
|
|
|
122
123
|
- app/assets/stylesheets/qss/qss_core.css
|
|
123
124
|
- app/assets/stylesheets/qss/qss_core.scss
|
|
124
125
|
- config/grammar.yml
|
|
126
|
+
- config/integrity.json
|
|
125
127
|
- config/qss-objects.json
|
|
126
128
|
- exe/qss-core-render-check
|
|
127
129
|
- exe/qss-engine-render-check
|
|
130
|
+
- exe/qss-seal
|
|
128
131
|
- exe/qss-sync
|
|
129
132
|
- lib/qss.rb
|
|
130
133
|
- lib/qss/blueprints.rb
|
|
131
134
|
- lib/qss/configuration.rb
|
|
132
135
|
- lib/qss/engine.rb
|
|
136
|
+
- lib/qss/integrity.rb
|
|
133
137
|
- lib/qss/qss_auditor.rb
|
|
134
138
|
- lib/qss/qss_native_auditor.rb
|
|
135
139
|
- lib/qss/version.rb
|