backpack 0.4.2 → 0.4.4
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/README.md +6 -3
- data/bin/backpack +6 -0
- data/demo/Gemfile.lock +1 -1
- data/lib/backpack/cli.rb +39 -2
- data/lib/backpack/commands/sync.rb +153 -0
- data/lib/backpack/components/button/button.css +1 -1
- data/lib/backpack/components/icon_sprite.rb +7 -4
- data/lib/backpack/version.rb +1 -1
- data/spec/cli/import_spec.rb +15 -1
- data/spec/cli/sync_spec.rb +251 -0
- data/spec/components/icon_spec.rb +5 -1
- data/spec/components/icon_sprite_spec.rb +3 -0
- data/spec/fixtures/figma/Backpack.json +133 -0
- data/spec/fixtures/tokens/_test.css +19 -0
- metadata +12 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5734904d7d161fa9097deef8000ec5f21b7cf8ed6f425b11072980287b890cb0
|
|
4
|
+
data.tar.gz: 3e8f346c3815541434a3894c8445ea283094a2a89565ba3fff84317030c8e43e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f60ec9fa255a439fd864f8b63ff5f3ead905cd25bc83fc5765fb55beac0c61a81f01dc1061003d3841e2d6d6a939f55787d84af7d8d55e8434974dd5f29079d
|
|
7
|
+
data.tar.gz: 2a18e3f53810b4f0b477e76e8f9b349566c340282570c5f8171e6b8a10b3bf42af79d7d47b3e4ec70d46ac3be73093c04b0a0ec69c0f1f9cdf6a3d75a086b5f5
|
data/README.md
CHANGED
|
@@ -2,23 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## Getting started
|
|
4
4
|
|
|
5
|
+
Install Backpack globally, then import it in your Rails project.
|
|
6
|
+
|
|
5
7
|
```bash
|
|
6
8
|
gem install backpack
|
|
9
|
+
|
|
10
|
+
# At your Rails project root:
|
|
7
11
|
backpack import
|
|
8
|
-
gem uninstall backpack
|
|
9
12
|
```
|
|
10
13
|
|
|
11
14
|
## Usage
|
|
12
15
|
|
|
13
16
|
### Configure colors tokens
|
|
14
17
|
|
|
15
|
-
Open `
|
|
18
|
+
Open `app/assets/stylesheets/tokens/_colors.scss` and follow the instructions.
|
|
16
19
|
|
|
17
20
|
### Sync tokens with Figma
|
|
18
21
|
|
|
19
22
|
1. Use [Export/Import variables](https://www.figma.com/community/plugin/1256972111705530093/export-import-variables) plugin to export the Backpack group.
|
|
20
23
|
2. Save `Backpack.json` in the `figma/` directory.
|
|
21
|
-
3. Run `
|
|
24
|
+
3. Run `backpack sync`
|
|
22
25
|
|
|
23
26
|
## Development
|
|
24
27
|
|
data/bin/backpack
ADDED
data/demo/Gemfile.lock
CHANGED
data/lib/backpack/cli.rb
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
require "thor"
|
|
2
2
|
require "backpack"
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "commands/sync"
|
|
3
5
|
|
|
4
6
|
module Backpack
|
|
5
7
|
class CLI < Thor
|
|
6
8
|
include Thor::Actions
|
|
7
9
|
|
|
8
|
-
desc "import", "Import Backpack in the current project"
|
|
9
|
-
|
|
10
10
|
source_root File.expand_path(__dir__)
|
|
11
11
|
|
|
12
|
+
desc "import", "Import Backpack in the current project"
|
|
12
13
|
def import
|
|
13
14
|
say ascii_art, :green
|
|
14
15
|
|
|
16
|
+
add_radix_colors_dependency if File.exist?("package.json")
|
|
17
|
+
|
|
15
18
|
directory "stylesheets", "app/assets/stylesheets", recursive: true
|
|
16
19
|
directory "components", "app/components", recursive: true
|
|
17
20
|
|
|
@@ -23,10 +26,44 @@ module Backpack
|
|
|
23
26
|
directory "../../spec/components", "spec/components", recursive: true do |content|
|
|
24
27
|
content.gsub("require 'spec_helper'", "require 'rails_helper'")
|
|
25
28
|
end
|
|
29
|
+
directory "../../spec/fixtures/icons", "spec/fixtures/icons", recursive: true
|
|
30
|
+
|
|
31
|
+
copy_file "../../vendor/normalize.css", "vendor/normalize.css"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
desc "sync", "Sync design tokens from Figma to CSS files"
|
|
35
|
+
def sync
|
|
36
|
+
Backpack::Commands::Sync.new(shell).sync
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.exit_on_failure?
|
|
40
|
+
true
|
|
26
41
|
end
|
|
27
42
|
|
|
28
43
|
private
|
|
29
44
|
|
|
45
|
+
def add_radix_colors_dependency
|
|
46
|
+
package_json_path = "package.json"
|
|
47
|
+
|
|
48
|
+
# Read existing package.json or create minimal structure
|
|
49
|
+
if File.exist?(package_json_path) && !File.zero?(package_json_path)
|
|
50
|
+
begin
|
|
51
|
+
package_data = JSON.parse(File.read(package_json_path))
|
|
52
|
+
rescue JSON::ParserError
|
|
53
|
+
package_data = {}
|
|
54
|
+
end
|
|
55
|
+
else
|
|
56
|
+
package_data = {}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Add the dependency
|
|
60
|
+
package_data["dependencies"] ||= {}
|
|
61
|
+
package_data["dependencies"]["@radix-ui/colors"] = "^3.0.0"
|
|
62
|
+
|
|
63
|
+
# Write back to file
|
|
64
|
+
File.write(package_json_path, "#{JSON.pretty_generate(package_data)}\n")
|
|
65
|
+
end
|
|
66
|
+
|
|
30
67
|
def ascii_art
|
|
31
68
|
<<~ASCII
|
|
32
69
|
┌┐ ┌─┐┌─┐┬┌─┌─┐┌─┐┌─┐┬┌─
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'date'
|
|
5
|
+
require 'thor'
|
|
6
|
+
|
|
7
|
+
module Backpack
|
|
8
|
+
module Commands
|
|
9
|
+
class Sync
|
|
10
|
+
include Thor::Actions
|
|
11
|
+
|
|
12
|
+
def initialize(shell = Thor::Base.shell.new)
|
|
13
|
+
@shell = shell
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def sync
|
|
17
|
+
@shell.say "Syncing design tokens from Figma...", :green
|
|
18
|
+
|
|
19
|
+
layout_json = File.read(layout_tokens_file)
|
|
20
|
+
layout = JSON.parse(layout_json, symbolize_names: true)
|
|
21
|
+
|
|
22
|
+
raise "Wrong name" if layout[:name] != NAME
|
|
23
|
+
|
|
24
|
+
modes = layout[:modes]
|
|
25
|
+
tokens = extract_tokens(layout[:variables], modes)
|
|
26
|
+
|
|
27
|
+
# Read all CSS files
|
|
28
|
+
tokens_css_files = Dir.glob("#{tokens_folder}/*.css")
|
|
29
|
+
|
|
30
|
+
rewritten_lines_count = 0
|
|
31
|
+
remaining_tokens = tokens.keys
|
|
32
|
+
|
|
33
|
+
tokens_css_files.each do |file|
|
|
34
|
+
lines_rewritten = process_css_file(file, tokens, remaining_tokens)
|
|
35
|
+
rewritten_lines_count += lines_rewritten
|
|
36
|
+
|
|
37
|
+
if lines_rewritten > 0
|
|
38
|
+
file_name = File.basename(file)
|
|
39
|
+
@shell.say "#{file_name}: #{lines_rewritten} lines rewritten", :yellow
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if remaining_tokens.any?
|
|
44
|
+
@shell.say ""
|
|
45
|
+
@shell.say "#{remaining_tokens.size} tokens have not been found:", :red
|
|
46
|
+
remaining_tokens.each do |token|
|
|
47
|
+
@shell.say token
|
|
48
|
+
end
|
|
49
|
+
@shell.say "Please check that these tokens are correctly named."
|
|
50
|
+
else
|
|
51
|
+
@shell.say "All tokens synced successfully!", :green
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
NAME = "Backpack".freeze
|
|
58
|
+
CSS_VARIABLE_REGEXP = /var\(--([^:]+)\)/
|
|
59
|
+
CSS_LINE_REGEXP = /--([^:]+): ([^;]+);(.*)/
|
|
60
|
+
APPEND_PX = proc { |value| "#{value}px" }
|
|
61
|
+
APPEND_REM = proc { |value| "#{value}rem" }
|
|
62
|
+
CONVERT_TO_REM = proc { |value| "#{value.to_f / 10.0}rem".gsub(".0rem", "rem") }
|
|
63
|
+
CONVERSION_RULES = {
|
|
64
|
+
/(font|height|radius|space)-(\d+|base|full)/ => CONVERT_TO_REM,
|
|
65
|
+
/(breakpoint)-(.+)/ => APPEND_PX,
|
|
66
|
+
/container-width/ => APPEND_REM
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
def layout_tokens_file
|
|
70
|
+
ENV['BACKPACK_TEST_FIGMA_PATH'] || File.expand_path("../../../figma/#{NAME}.json", __dir__)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def tokens_folder
|
|
74
|
+
ENV['BACKPACK_TEST_TOKENS_PATH'] || File.expand_path("../stylesheets/tokens", __dir__)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def normalized_figma_variable(name)
|
|
78
|
+
name.split('/').last.downcase.gsub(' ', '-')
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def extract_tokens(variables, modes)
|
|
82
|
+
tokens = {}
|
|
83
|
+
|
|
84
|
+
variables.each do |variable|
|
|
85
|
+
name = if (css_name = variable.dig(:codeSyntax, :WEB))
|
|
86
|
+
CSS_VARIABLE_REGEXP.match(css_name)[1]
|
|
87
|
+
else
|
|
88
|
+
normalized_figma_variable(variable[:name])
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
value = variable[:resolvedValuesByMode][modes.keys[0]]
|
|
92
|
+
|
|
93
|
+
final_value = if value[:aliasName]
|
|
94
|
+
"var(--#{normalized_figma_variable(value[:aliasName])})"
|
|
95
|
+
else
|
|
96
|
+
apply_conversion_rules(name, value[:resolvedValue])
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
tokens[name] = final_value
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
tokens
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def apply_conversion_rules(name, value)
|
|
106
|
+
CONVERSION_RULES.each do |pattern, conversion|
|
|
107
|
+
if pattern.match?(name)
|
|
108
|
+
return conversion.call(value)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
value
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def process_css_file(file, tokens, remaining_tokens)
|
|
115
|
+
lines = []
|
|
116
|
+
lines_rewritten = 0
|
|
117
|
+
|
|
118
|
+
File.open(file, 'r') do |css_file|
|
|
119
|
+
css_file.each_line do |line|
|
|
120
|
+
variable = line.match(CSS_LINE_REGEXP)
|
|
121
|
+
|
|
122
|
+
unless variable
|
|
123
|
+
lines << line
|
|
124
|
+
next
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
token_name = variable[1]
|
|
128
|
+
token_value = tokens[token_name]
|
|
129
|
+
|
|
130
|
+
unless token_value
|
|
131
|
+
lines << line
|
|
132
|
+
next
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
rewritten_line = line.gsub(CSS_LINE_REGEXP) do |match|
|
|
136
|
+
"--#{variable[1]}: #{token_value}; /* imported from Figma on #{Date.today} */"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
lines_rewritten += 1
|
|
140
|
+
remaining_tokens.delete(token_name)
|
|
141
|
+
lines << rewritten_line
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
if lines_rewritten > 0
|
|
146
|
+
File.write(file, lines.join)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
lines_rewritten
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "rails-html-sanitizer"
|
|
4
4
|
|
|
5
5
|
class Components::IconSprite < Components::Base
|
|
6
6
|
ICON_PATHS = [
|
|
7
7
|
Rails.root&.join("app/assets/icons"),
|
|
8
|
-
Rails.root&.join("node_modules/lucide-static/icons")
|
|
9
|
-
File.expand_path("../../../spec/fixtures/icons", __dir__)
|
|
8
|
+
Rails.root&.join("node_modules/lucide-static/icons")
|
|
10
9
|
].compact.freeze
|
|
11
10
|
|
|
12
11
|
def view_template
|
|
13
12
|
svg(**mix({ class: root_classes }, @attributes, hidden: true)) do |s|
|
|
14
13
|
s.defs do
|
|
15
14
|
Current.icons.sort.map do |key|
|
|
15
|
+
# rubocop:disable Rails/OutputSafety
|
|
16
16
|
raw Icon.new(key).as_svg_symbol
|
|
17
|
+
# rubocop:enable Rails/OutputSafety
|
|
17
18
|
end
|
|
18
19
|
end
|
|
19
20
|
end
|
|
@@ -28,7 +29,7 @@ class Components::IconSprite < Components::Base
|
|
|
28
29
|
prop :key, String, :positional
|
|
29
30
|
|
|
30
31
|
def as_svg_symbol
|
|
31
|
-
icon_files = Dir.glob("{#{ICON_PATHS.join(
|
|
32
|
+
icon_files = Dir.glob("{#{ICON_PATHS.join(",")}}/#{@key}.svg")
|
|
32
33
|
|
|
33
34
|
return if icon_files.empty?
|
|
34
35
|
|
|
@@ -39,6 +40,7 @@ class Components::IconSprite < Components::Base
|
|
|
39
40
|
|
|
40
41
|
private
|
|
41
42
|
|
|
43
|
+
# rubocop:disable Rails/OutputSafety
|
|
42
44
|
def sanitized_svg(svg_string)
|
|
43
45
|
Rails::Html::SafeListSanitizer
|
|
44
46
|
.new
|
|
@@ -48,6 +50,7 @@ class Components::IconSprite < Components::Base
|
|
|
48
50
|
.strip
|
|
49
51
|
.html_safe
|
|
50
52
|
end
|
|
53
|
+
# rubocop:enable Rails/OutputSafety
|
|
51
54
|
|
|
52
55
|
def allowed_tags
|
|
53
56
|
%w[svg path circle rect line polyline polygon ellipse g defs use]
|
data/lib/backpack/version.rb
CHANGED
data/spec/cli/import_spec.rb
CHANGED
|
@@ -30,6 +30,7 @@ describe Backpack::CLI, type: :cli do
|
|
|
30
30
|
expect(output).to include("create spec/components/button_spec.rb")
|
|
31
31
|
expect(output).to include("create spec/components/previews/button_preview.rb")
|
|
32
32
|
expect(output).to include("create spec/components/previews/button_preview/overview.html.erb")
|
|
33
|
+
expect(output).to include("create vendor/normalize.css")
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
it "copies the folders" do
|
|
@@ -38,14 +39,27 @@ describe Backpack::CLI, type: :cli do
|
|
|
38
39
|
expect(File.directory?("app/components")).to be true
|
|
39
40
|
expect(File.directory?("spec/components")).to be true
|
|
40
41
|
expect(File.directory?("spec/components/previews")).to be true
|
|
42
|
+
expect(File.directory?("spec/fixtures/icons")).to be true
|
|
41
43
|
end
|
|
42
44
|
|
|
43
|
-
it "copies the lib files" do
|
|
45
|
+
it "copies the lib & vendor files" do
|
|
44
46
|
command
|
|
45
47
|
expect(File.exist?("lib/backpack/attributes.rb")).to be true
|
|
46
48
|
expect(File.exist?("lib/backpack/classes.rb")).to be true
|
|
47
49
|
expect(File.exist?("lib/backpack/identifier.rb")).to be true
|
|
48
50
|
expect(File.exist?("lib/backpack/tokens.rb")).to be true
|
|
51
|
+
expect(File.exist?("vendor/normalize.css")).to be true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context "when there is a package.json" do
|
|
55
|
+
before do
|
|
56
|
+
FileUtils.touch("package.json")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "installs radix colors" do
|
|
60
|
+
command
|
|
61
|
+
expect(File.read("package.json")).to include('"@radix-ui/colors": "^3.0.0"')
|
|
62
|
+
end
|
|
49
63
|
end
|
|
50
64
|
end
|
|
51
65
|
end
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Backpack::CLI, type: :cli do
|
|
4
|
+
sandbox_dir = "sandbox"
|
|
5
|
+
|
|
6
|
+
around do |example|
|
|
7
|
+
FileUtils.mkdir(sandbox_dir) unless File.exist?(sandbox_dir)
|
|
8
|
+
within_dir(sandbox_dir) do
|
|
9
|
+
example.run
|
|
10
|
+
end
|
|
11
|
+
ensure
|
|
12
|
+
FileUtils.rm_rf(sandbox_dir)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe "sync" do
|
|
16
|
+
let(:figma_fixture_path) { File.expand_path("../fixtures/figma/Backpack.json", __dir__) }
|
|
17
|
+
let(:css_fixture_path) { File.expand_path("../fixtures/tokens/_test.css", __dir__) }
|
|
18
|
+
let(:figma_dir) { "figma" }
|
|
19
|
+
let(:tokens_dir) { "lib/backpack/stylesheets/tokens" }
|
|
20
|
+
let(:figma_file) { "#{figma_dir}/Backpack.json" }
|
|
21
|
+
let(:css_file) { "#{tokens_dir}/_test.css" }
|
|
22
|
+
|
|
23
|
+
before do
|
|
24
|
+
# Create directory structure
|
|
25
|
+
FileUtils.mkdir_p(figma_dir)
|
|
26
|
+
FileUtils.mkdir_p(tokens_dir)
|
|
27
|
+
|
|
28
|
+
# Copy fixtures to test environment
|
|
29
|
+
FileUtils.cp(figma_fixture_path, figma_file)
|
|
30
|
+
FileUtils.cp(css_fixture_path, css_file)
|
|
31
|
+
|
|
32
|
+
# Set environment variables to override paths for testing
|
|
33
|
+
ENV['BACKPACK_TEST_FIGMA_PATH'] = File.expand_path(figma_file)
|
|
34
|
+
ENV['BACKPACK_TEST_TOKENS_PATH'] = File.expand_path(tokens_dir)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
after do
|
|
38
|
+
# Clean up environment variables
|
|
39
|
+
ENV.delete('BACKPACK_TEST_FIGMA_PATH')
|
|
40
|
+
ENV.delete('BACKPACK_TEST_TOKENS_PATH')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
context "when figma file exists and tokens are found" do
|
|
44
|
+
let(:command) { `bundle exec ruby ../bin/backpack sync 2>&1` }
|
|
45
|
+
let(:output) { command.chomp }
|
|
46
|
+
|
|
47
|
+
it "successfully syncs tokens from Figma to CSS files" do
|
|
48
|
+
expect(output).to include("Syncing design tokens from Figma...")
|
|
49
|
+
expect(output).to include("_test.css: 6 lines rewritten")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "updates CSS variables with converted values" do
|
|
53
|
+
command
|
|
54
|
+
css_content = File.read(css_file)
|
|
55
|
+
|
|
56
|
+
# Font sizes should be converted to rem
|
|
57
|
+
expect(css_content).to include("--font-1: 1.2rem; /* imported from Figma on #{Date.today} */")
|
|
58
|
+
expect(css_content).to include("--font-2: 1.4rem; /* imported from Figma on #{Date.today} */")
|
|
59
|
+
|
|
60
|
+
# Radius should be converted to rem
|
|
61
|
+
expect(css_content).to include("--radius-1: 0.2rem; /* imported from Figma on #{Date.today} */")
|
|
62
|
+
|
|
63
|
+
# Space should be converted to rem
|
|
64
|
+
expect(css_content).to include("--space-1: var(--space-base); /* imported from Figma on #{Date.today} */")
|
|
65
|
+
expect(css_content).to include("--space-base: 0.2rem; /* imported from Figma on #{Date.today} */")
|
|
66
|
+
|
|
67
|
+
# Container width should be converted to rem
|
|
68
|
+
expect(css_content).to include("--container-width: 128rem; /* imported from Figma on #{Date.today} */")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "preserves unchanged CSS variables" do
|
|
72
|
+
command
|
|
73
|
+
css_content = File.read(css_file)
|
|
74
|
+
|
|
75
|
+
# These should remain unchanged
|
|
76
|
+
expect(css_content).to include("--font-unchanged: 20px;")
|
|
77
|
+
expect(css_content).to include("--unknown-token: 999px;")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "handles alias references correctly" do
|
|
81
|
+
command
|
|
82
|
+
css_content = File.read(css_file)
|
|
83
|
+
|
|
84
|
+
# space-1 should reference space-base as an alias
|
|
85
|
+
expect(css_content).to include("--space-1: var(--space-base);")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "reports remaining unmatched tokens" do
|
|
89
|
+
# Create a CSS file that doesn't have all tokens
|
|
90
|
+
limited_css = ":root {\n --font-1: 10px;\n}"
|
|
91
|
+
File.write(css_file, limited_css)
|
|
92
|
+
|
|
93
|
+
output = `bundle exec ruby ../bin/backpack sync 2>&1`.chomp
|
|
94
|
+
|
|
95
|
+
expect(output).to include("5 tokens have not been found:")
|
|
96
|
+
expect(output).to include("font-2")
|
|
97
|
+
expect(output).to include("radius-1")
|
|
98
|
+
expect(output).to include("space-1")
|
|
99
|
+
expect(output).to include("space-base")
|
|
100
|
+
expect(output).to include("container-width")
|
|
101
|
+
expect(output).to include("Please check that these tokens are correctly named.")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "reports success when all tokens are matched" do
|
|
105
|
+
expect(output).to include("All tokens synced successfully!")
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
context "when figma file is missing" do
|
|
110
|
+
before do
|
|
111
|
+
FileUtils.rm(figma_file)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
let(:command) { `bundle exec ruby ../bin/backpack sync 2>&1` }
|
|
115
|
+
|
|
116
|
+
it "raises an error" do
|
|
117
|
+
expect { command }.not_to raise_error
|
|
118
|
+
expect(command).to include("No such file or directory")
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
context "when figma file has wrong name" do
|
|
123
|
+
before do
|
|
124
|
+
wrong_figma_data = File.read(figma_file).gsub('"name": "Backpack"', '"name": "WrongName"')
|
|
125
|
+
File.write(figma_file, wrong_figma_data)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
let(:command) { `bundle exec ruby ../bin/backpack sync 2>&1` }
|
|
129
|
+
|
|
130
|
+
it "raises an error for wrong name" do
|
|
131
|
+
expect { command }.not_to raise_error
|
|
132
|
+
expect(command).to include("Wrong name")
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
context "when no CSS files exist in tokens directory" do
|
|
137
|
+
before do
|
|
138
|
+
FileUtils.rm_rf(tokens_dir)
|
|
139
|
+
FileUtils.mkdir_p(tokens_dir)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
let(:command) { `bundle exec ruby ../bin/backpack sync 2>&1` }
|
|
143
|
+
let(:output) { command.chomp }
|
|
144
|
+
|
|
145
|
+
it "completes without errors but reports unmatched tokens" do
|
|
146
|
+
expect(output).to include("Syncing design tokens from Figma...")
|
|
147
|
+
expect(output).to include("6 tokens have not been found:")
|
|
148
|
+
expect(output).to include("Please check that these tokens are correctly named.")
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
context "when CSS files exist but have no matching tokens" do
|
|
153
|
+
before do
|
|
154
|
+
empty_css = ":root {\n /* no matching tokens */\n --other-token: 123px;\n}"
|
|
155
|
+
File.write(css_file, empty_css)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
let(:command) { `bundle exec ruby ../bin/backpack sync 2>&1` }
|
|
159
|
+
let(:output) { command.chomp }
|
|
160
|
+
|
|
161
|
+
it "reports all tokens as unmatched" do
|
|
162
|
+
expect(output).to include("6 tokens have not been found:")
|
|
163
|
+
expect(output).to include("font-1")
|
|
164
|
+
expect(output).to include("font-2")
|
|
165
|
+
expect(output).to include("radius-1")
|
|
166
|
+
expect(output).to include("space-1")
|
|
167
|
+
expect(output).to include("space-base")
|
|
168
|
+
expect(output).to include("container-width")
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
describe "conversion rules" do
|
|
173
|
+
before do
|
|
174
|
+
# Create specific test cases for conversion rules
|
|
175
|
+
test_css = <<~CSS
|
|
176
|
+
:root {
|
|
177
|
+
--font-1: 10px;
|
|
178
|
+
--height-1: 10px;
|
|
179
|
+
--radius-1: 1px;
|
|
180
|
+
--space-1: 1px;
|
|
181
|
+
--breakpoint-xs: 100px;
|
|
182
|
+
--container-width: 100px;
|
|
183
|
+
}
|
|
184
|
+
CSS
|
|
185
|
+
File.write(css_file, test_css)
|
|
186
|
+
|
|
187
|
+
# Create figma data with breakpoint token
|
|
188
|
+
figma_data = JSON.parse(File.read(figma_file))
|
|
189
|
+
figma_data["variables"] << {
|
|
190
|
+
"id" => "VariableID:test:1",
|
|
191
|
+
"name" => "Layout/Media queries/Breakpoint XS",
|
|
192
|
+
"type" => "FLOAT",
|
|
193
|
+
"valuesByMode" => { "1:0" => 576 },
|
|
194
|
+
"resolvedValuesByMode" => { "1:0" => { "resolvedValue" => 576, "alias" => nil } },
|
|
195
|
+
"codeSyntax" => {}
|
|
196
|
+
}
|
|
197
|
+
figma_data["variables"] << {
|
|
198
|
+
"id" => "VariableID:test:2",
|
|
199
|
+
"name" => "Typography/Line height/height-1",
|
|
200
|
+
"type" => "FLOAT",
|
|
201
|
+
"valuesByMode" => { "1:0" => 16 },
|
|
202
|
+
"resolvedValuesByMode" => { "1:0" => { "resolvedValue" => 16, "alias" => nil } },
|
|
203
|
+
"codeSyntax" => {}
|
|
204
|
+
}
|
|
205
|
+
File.write(figma_file, JSON.generate(figma_data))
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
let(:command) { `bundle exec ruby ../bin/backpack sync 2>&1` }
|
|
209
|
+
|
|
210
|
+
it "applies correct conversion rules" do
|
|
211
|
+
command
|
|
212
|
+
css_content = File.read(css_file)
|
|
213
|
+
|
|
214
|
+
# Font and height should convert to rem (value / 10)
|
|
215
|
+
expect(css_content).to include("--font-1: 1.2rem;")
|
|
216
|
+
expect(css_content).to include("--height-1: 1.6rem;")
|
|
217
|
+
|
|
218
|
+
# Radius should convert to rem (value / 10)
|
|
219
|
+
expect(css_content).to include("--radius-1: 0.2rem;")
|
|
220
|
+
|
|
221
|
+
# Space should convert to rem (value / 10)
|
|
222
|
+
expect(css_content).to include("--space-1: var(--space-base);")
|
|
223
|
+
|
|
224
|
+
# Breakpoint should append px
|
|
225
|
+
expect(css_content).to include("--breakpoint-xs: 576px;")
|
|
226
|
+
|
|
227
|
+
# Container width should append rem
|
|
228
|
+
expect(css_content).to include("--container-width: 128rem;")
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
describe "help command" do
|
|
233
|
+
let(:help_output) { `bundle exec ruby ../bin/backpack help sync 2>&1`.chomp }
|
|
234
|
+
|
|
235
|
+
it "shows sync command in help" do
|
|
236
|
+
expect(help_output).to include("Usage:")
|
|
237
|
+
expect(help_output).to include("backpack sync")
|
|
238
|
+
expect(help_output).to include("Sync design tokens from Figma to CSS files")
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
describe "command availability" do
|
|
243
|
+
let(:commands_output) { `bundle exec ruby ../bin/backpack help 2>&1`.chomp }
|
|
244
|
+
|
|
245
|
+
it "lists sync as an available command" do
|
|
246
|
+
expect(commands_output).to include("backpack sync")
|
|
247
|
+
expect(commands_output).to include("Sync design tokens from Figma to CSS files")
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "VariableCollectionId:1:26",
|
|
3
|
+
"name": "Backpack",
|
|
4
|
+
"modes": {
|
|
5
|
+
"1:0": "Desktop"
|
|
6
|
+
},
|
|
7
|
+
"variableIds": [
|
|
8
|
+
"VariableID:1:27",
|
|
9
|
+
"VariableID:5:3",
|
|
10
|
+
"VariableID:79:12949",
|
|
11
|
+
"VariableID:79:13034",
|
|
12
|
+
"VariableID:79:13053",
|
|
13
|
+
"VariableID:5:24"
|
|
14
|
+
],
|
|
15
|
+
"variables": [
|
|
16
|
+
{
|
|
17
|
+
"id": "VariableID:1:27",
|
|
18
|
+
"name": "Typography/Sizes/font-1",
|
|
19
|
+
"description": "",
|
|
20
|
+
"type": "FLOAT",
|
|
21
|
+
"valuesByMode": {
|
|
22
|
+
"1:0": 12
|
|
23
|
+
},
|
|
24
|
+
"resolvedValuesByMode": {
|
|
25
|
+
"1:0": {
|
|
26
|
+
"resolvedValue": 12,
|
|
27
|
+
"alias": null
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"scopes": ["FONT_SIZE"],
|
|
31
|
+
"hiddenFromPublishing": false,
|
|
32
|
+
"codeSyntax": {
|
|
33
|
+
"WEB": "var(--font-1)"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "VariableID:5:3",
|
|
38
|
+
"name": "Typography/Sizes/font-2",
|
|
39
|
+
"description": "",
|
|
40
|
+
"type": "FLOAT",
|
|
41
|
+
"valuesByMode": {
|
|
42
|
+
"1:0": 14
|
|
43
|
+
},
|
|
44
|
+
"resolvedValuesByMode": {
|
|
45
|
+
"1:0": {
|
|
46
|
+
"resolvedValue": 14,
|
|
47
|
+
"alias": null
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"scopes": ["FONT_SIZE"],
|
|
51
|
+
"hiddenFromPublishing": false,
|
|
52
|
+
"codeSyntax": {
|
|
53
|
+
"WEB": "var(--font-2)"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "VariableID:79:12949",
|
|
58
|
+
"name": "Radius/radius-1",
|
|
59
|
+
"description": "",
|
|
60
|
+
"type": "FLOAT",
|
|
61
|
+
"valuesByMode": {
|
|
62
|
+
"1:0": 2
|
|
63
|
+
},
|
|
64
|
+
"resolvedValuesByMode": {
|
|
65
|
+
"1:0": {
|
|
66
|
+
"resolvedValue": 2,
|
|
67
|
+
"alias": null
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"scopes": ["CORNER_RADIUS"],
|
|
71
|
+
"hiddenFromPublishing": false,
|
|
72
|
+
"codeSyntax": {}
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "VariableID:79:13034",
|
|
76
|
+
"name": "Spacing/space-1",
|
|
77
|
+
"description": "",
|
|
78
|
+
"type": "FLOAT",
|
|
79
|
+
"valuesByMode": {
|
|
80
|
+
"1:0": {
|
|
81
|
+
"type": "VARIABLE_ALIAS",
|
|
82
|
+
"id": "VariableID:79:13053"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"resolvedValuesByMode": {
|
|
86
|
+
"1:0": {
|
|
87
|
+
"resolvedValue": 2,
|
|
88
|
+
"alias": "VariableID:79:13053",
|
|
89
|
+
"aliasName": "Spacing/space-base"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"scopes": ["GAP", "LETTER_SPACING"],
|
|
93
|
+
"hiddenFromPublishing": false,
|
|
94
|
+
"codeSyntax": {}
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"id": "VariableID:79:13053",
|
|
98
|
+
"name": "Spacing/space-base",
|
|
99
|
+
"description": "",
|
|
100
|
+
"type": "FLOAT",
|
|
101
|
+
"valuesByMode": {
|
|
102
|
+
"1:0": 2
|
|
103
|
+
},
|
|
104
|
+
"resolvedValuesByMode": {
|
|
105
|
+
"1:0": {
|
|
106
|
+
"resolvedValue": 2,
|
|
107
|
+
"alias": null
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"scopes": ["GAP", "LETTER_SPACING"],
|
|
111
|
+
"hiddenFromPublishing": false,
|
|
112
|
+
"codeSyntax": {}
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"id": "VariableID:5:24",
|
|
116
|
+
"name": "Layout/Container/Container Width",
|
|
117
|
+
"description": "",
|
|
118
|
+
"type": "FLOAT",
|
|
119
|
+
"valuesByMode": {
|
|
120
|
+
"1:0": 128
|
|
121
|
+
},
|
|
122
|
+
"resolvedValuesByMode": {
|
|
123
|
+
"1:0": {
|
|
124
|
+
"resolvedValue": 128,
|
|
125
|
+
"alias": null
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
"scopes": [],
|
|
129
|
+
"hiddenFromPublishing": false,
|
|
130
|
+
"codeSyntax": {}
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
/* font-size */
|
|
3
|
+
--font-1: 10px; /* old value to be updated */
|
|
4
|
+
--font-2: 12px; /* old value to be updated */
|
|
5
|
+
--font-unchanged: 20px; /* this should remain unchanged */
|
|
6
|
+
|
|
7
|
+
/* radius */
|
|
8
|
+
--radius-1: 1px; /* old value to be updated */
|
|
9
|
+
|
|
10
|
+
/* spacing */
|
|
11
|
+
--space-1: 1px; /* old value to be updated */
|
|
12
|
+
--space-base: 1px; /* old value to be updated */
|
|
13
|
+
|
|
14
|
+
/* container */
|
|
15
|
+
--container-width: 100rem; /* old value to be updated */
|
|
16
|
+
|
|
17
|
+
/* unmatched token */
|
|
18
|
+
--unknown-token: 999px; /* this should remain unchanged */
|
|
19
|
+
}
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: backpack
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lucas Heymès
|
|
8
8
|
- Hans Lemuet
|
|
9
9
|
- Hugo Chantelauze
|
|
10
|
-
bindir:
|
|
10
|
+
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
12
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
@@ -97,7 +97,8 @@ dependencies:
|
|
|
97
97
|
version: '0'
|
|
98
98
|
email:
|
|
99
99
|
- team@etamin.studio
|
|
100
|
-
executables:
|
|
100
|
+
executables:
|
|
101
|
+
- backpack
|
|
101
102
|
extensions: []
|
|
102
103
|
extra_rdoc_files: []
|
|
103
104
|
files:
|
|
@@ -108,6 +109,7 @@ files:
|
|
|
108
109
|
- LICENSE
|
|
109
110
|
- README.md
|
|
110
111
|
- Rakefile
|
|
112
|
+
- bin/backpack
|
|
111
113
|
- demo/.gitignore
|
|
112
114
|
- demo/.overmind.env
|
|
113
115
|
- demo/.yarn/cache/@babel-code-frame-npm-7.27.1-4dbcabb137-5874edc5d3.zip
|
|
@@ -602,6 +604,7 @@ files:
|
|
|
602
604
|
- lib/backpack/attributes.rb
|
|
603
605
|
- lib/backpack/classes.rb
|
|
604
606
|
- lib/backpack/cli.rb
|
|
607
|
+
- lib/backpack/commands/sync.rb
|
|
605
608
|
- lib/backpack/components.rb
|
|
606
609
|
- lib/backpack/components/badge.rb
|
|
607
610
|
- lib/backpack/components/badge/badge.css
|
|
@@ -647,6 +650,7 @@ files:
|
|
|
647
650
|
- lib/backpack/tokens.rb
|
|
648
651
|
- lib/backpack/version.rb
|
|
649
652
|
- spec/cli/import_spec.rb
|
|
653
|
+
- spec/cli/sync_spec.rb
|
|
650
654
|
- spec/components/badge_spec.rb
|
|
651
655
|
- spec/components/breadcrumb_spec.rb
|
|
652
656
|
- spec/components/button_spec.rb
|
|
@@ -671,9 +675,11 @@ files:
|
|
|
671
675
|
- spec/components/quotation_spec.rb
|
|
672
676
|
- spec/components/rich_text_spec.rb
|
|
673
677
|
- spec/components/skip_links_spec.rb
|
|
678
|
+
- spec/fixtures/figma/Backpack.json
|
|
674
679
|
- spec/fixtures/icons/arrow-right.svg
|
|
675
680
|
- spec/fixtures/icons/close.svg
|
|
676
681
|
- spec/fixtures/icons/user.svg
|
|
682
|
+
- spec/fixtures/tokens/_test.css
|
|
677
683
|
- spec/spec_helper.rb
|
|
678
684
|
- spec/support/cli_helpers.rb
|
|
679
685
|
- spec/support/components.rb
|
|
@@ -704,6 +710,7 @@ specification_version: 4
|
|
|
704
710
|
summary: When you go on a trek, don't forget your backpack.
|
|
705
711
|
test_files:
|
|
706
712
|
- spec/cli/import_spec.rb
|
|
713
|
+
- spec/cli/sync_spec.rb
|
|
707
714
|
- spec/components/badge_spec.rb
|
|
708
715
|
- spec/components/breadcrumb_spec.rb
|
|
709
716
|
- spec/components/button_spec.rb
|
|
@@ -728,9 +735,11 @@ test_files:
|
|
|
728
735
|
- spec/components/quotation_spec.rb
|
|
729
736
|
- spec/components/rich_text_spec.rb
|
|
730
737
|
- spec/components/skip_links_spec.rb
|
|
738
|
+
- spec/fixtures/figma/Backpack.json
|
|
731
739
|
- spec/fixtures/icons/arrow-right.svg
|
|
732
740
|
- spec/fixtures/icons/close.svg
|
|
733
741
|
- spec/fixtures/icons/user.svg
|
|
742
|
+
- spec/fixtures/tokens/_test.css
|
|
734
743
|
- spec/spec_helper.rb
|
|
735
744
|
- spec/support/cli_helpers.rb
|
|
736
745
|
- spec/support/components.rb
|