neon_sakura 0.1.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 +7 -0
- data/.ai-reviewer/README.md +182 -0
- data/.ai-reviewer/ai-reviewer.sh +56 -0
- data/.ai-reviewer/build-system-prompt.sh +136 -0
- data/.ai-reviewer/extract-claude-sections.sh +32 -0
- data/.ai-reviewer/test-ai-reviewer.sh +40 -0
- data/.ai-reviewer-config.yml +190 -0
- data/.github/dependabot.yml +12 -0
- data/.github/settings.yml +70 -0
- data/.github/workflows/ai-pr-review-on-comment.yml +384 -0
- data/.github/workflows/ai-pr-review.yml +328 -0
- data/.github/workflows/license-check.yml +78 -0
- data/.github/workflows/lint.yml +79 -0
- data/.github/workflows/security.yml +131 -0
- data/.github/workflows/semgrep.yml +26 -0
- data/.github/workflows/test.yml +44 -0
- data/.gitignore +75 -0
- data/.rubocop.yml +33 -0
- data/.ruby-version +1 -0
- data/.simplecov +14 -0
- data/.stylelintignore +10 -0
- data/.stylelintrc.json +37 -0
- data/AGENTS.md +51 -0
- data/CHANGELOG.md +568 -0
- data/CLAUDE.md +632 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +327 -0
- data/LICENSE +21 -0
- data/README.md +1209 -0
- data/Rakefile +25 -0
- data/app/assets/images/cherry_blossom.svg +1525 -0
- data/app/assets/images/cherry_blossom_tree.png +0 -0
- data/app/assets/images/prysm-icon.png +0 -0
- data/app/assets/stylesheets/base.css +29 -0
- data/app/assets/stylesheets/components.css +1652 -0
- data/app/assets/stylesheets/forms.css +152 -0
- data/app/assets/stylesheets/loading.css +145 -0
- data/app/assets/stylesheets/neon_sakura.css +40 -0
- data/app/assets/stylesheets/pagy-tailwind.css +120 -0
- data/app/assets/stylesheets/theme-default.css +40 -0
- data/app/assets/stylesheets/theme-green.css +84 -0
- data/app/assets/stylesheets/theme-purple.css +94 -0
- data/app/assets/stylesheets/theme-red.css +84 -0
- data/app/assets/stylesheets/utility-borders.css +29 -0
- data/app/assets/stylesheets/utility-colors.css +185 -0
- data/app/assets/stylesheets/utility-effects.css +123 -0
- data/app/assets/stylesheets/utility-gradients.css +158 -0
- data/app/assets/stylesheets/utility-layout.css +132 -0
- data/app/assets/stylesheets/utility-reset.css +13 -0
- data/app/assets/stylesheets/utility-responsive.css +145 -0
- data/app/assets/stylesheets/utility-sizing.css +99 -0
- data/app/assets/stylesheets/utility-spacing.css +174 -0
- data/app/assets/stylesheets/utility-typography.css +97 -0
- data/app/controllers/errors_controller.rb +120 -0
- data/app/controllers/style_guide_controller.rb +117 -0
- data/app/helpers/errors_helper.rb +12 -0
- data/app/helpers/neon_sakura/navbar_helper.rb +43 -0
- data/app/helpers/style_guide_helper.rb +36 -0
- data/app/javascript/neon_sakura/dropdown.js +22 -0
- data/app/javascript/neon_sakura/navbar.js +71 -0
- data/app/javascript/neon_sakura/theme_switcher.js +187 -0
- data/app/views/errors/show.html.erb +105 -0
- data/app/views/layouts/error.html.erb +19 -0
- data/app/views/layouts/mission_control/jobs/_application_selection.html.erb +14 -0
- data/app/views/layouts/mission_control/jobs/_navigation.html.erb +21 -0
- data/app/views/layouts/mission_control/jobs/application.html.erb +453 -0
- data/app/views/layouts/style_guide.html.erb +416 -0
- data/app/views/shared/_file_upload.html.erb +184 -0
- data/app/views/shared/_footer.html.erb +23 -0
- data/app/views/shared/_header.html.erb +42 -0
- data/app/views/shared/_navbar.html.erb +306 -0
- data/app/views/shared/_profile_image_selector.html.erb +165 -0
- data/app/views/shared/_theme_switcher.html.erb +64 -0
- data/app/views/shared/icons/_adjustments.html.erb +10 -0
- data/app/views/shared/icons/_alert_circle.html.erb +3 -0
- data/app/views/shared/icons/_alert_triangle.html.erb +3 -0
- data/app/views/shared/icons/_archive.html.erb +3 -0
- data/app/views/shared/icons/_arrow_down.html.erb +3 -0
- data/app/views/shared/icons/_arrow_left.html.erb +3 -0
- data/app/views/shared/icons/_arrow_up.html.erb +3 -0
- data/app/views/shared/icons/_arrows_pointing_in.html.erb +10 -0
- data/app/views/shared/icons/_arrows_pointing_out.html.erb +10 -0
- data/app/views/shared/icons/_artemis_logo.html.erb +26 -0
- data/app/views/shared/icons/_auth_banner.html.erb +1 -0
- data/app/views/shared/icons/_bars.html.erb +10 -0
- data/app/views/shared/icons/_bell.html.erb +3 -0
- data/app/views/shared/icons/_book.html.erb +3 -0
- data/app/views/shared/icons/_bookmark.html.erb +3 -0
- data/app/views/shared/icons/_box.html.erb +3 -0
- data/app/views/shared/icons/_brain.html.erb +3 -0
- data/app/views/shared/icons/_briefcase.html.erb +3 -0
- data/app/views/shared/icons/_calendar.html.erb +3 -0
- data/app/views/shared/icons/_camera.html.erb +4 -0
- data/app/views/shared/icons/_chart_bar.html.erb +3 -0
- data/app/views/shared/icons/_chart_line.html.erb +10 -0
- data/app/views/shared/icons/_chart_pie.html.erb +11 -0
- data/app/views/shared/icons/_chat.html.erb +3 -0
- data/app/views/shared/icons/_check.html.erb +3 -0
- data/app/views/shared/icons/_check_circle.html.erb +3 -0
- data/app/views/shared/icons/_cherry_blossom.html.erb +1516 -0
- data/app/views/shared/icons/_cherry_blossom_silhouette.html.erb +1016 -0
- data/app/views/shared/icons/_cherry_blossom_single_flower.html.erb +1125 -0
- data/app/views/shared/icons/_cherry_blossom_tree.html.erb +159 -0
- data/app/views/shared/icons/_chevron_down.html.erb +3 -0
- data/app/views/shared/icons/_chevron_right.html.erb +9 -0
- data/app/views/shared/icons/_clipboard.html.erb +3 -0
- data/app/views/shared/icons/_clock.html.erb +3 -0
- data/app/views/shared/icons/_close.html.erb +3 -0
- data/app/views/shared/icons/_cog.html.erb +4 -0
- data/app/views/shared/icons/_crop.html.erb +10 -0
- data/app/views/shared/icons/_crown.html.erb +3 -0
- data/app/views/shared/icons/_disc.html.erb +3 -0
- data/app/views/shared/icons/_download.html.erb +3 -0
- data/app/views/shared/icons/_dragonfly.html.erb +58 -0
- data/app/views/shared/icons/_duplicate.html.erb +4 -0
- data/app/views/shared/icons/_edit.html.erb +3 -0
- data/app/views/shared/icons/_envelope.html.erb +3 -0
- data/app/views/shared/icons/_eraser.html.erb +10 -0
- data/app/views/shared/icons/_external_link.html.erb +3 -0
- data/app/views/shared/icons/_eye.html.erb +4 -0
- data/app/views/shared/icons/_file_csv.html.erb +10 -0
- data/app/views/shared/icons/_file_export.html.erb +10 -0
- data/app/views/shared/icons/_file_image.html.erb +10 -0
- data/app/views/shared/icons/_file_import.html.erb +10 -0
- data/app/views/shared/icons/_file_question.html.erb +6 -0
- data/app/views/shared/icons/_film.html.erb +3 -0
- data/app/views/shared/icons/_filter.html.erb +3 -0
- data/app/views/shared/icons/_folder.html.erb +3 -0
- data/app/views/shared/icons/_folder_open.html.erb +3 -0
- data/app/views/shared/icons/_folder_plus.html.erb +3 -0
- data/app/views/shared/icons/_globe.html.erb +3 -0
- data/app/views/shared/icons/_google.html.erb +11 -0
- data/app/views/shared/icons/_heart.html.erb +3 -0
- data/app/views/shared/icons/_heart_broken.html.erb +11 -0
- data/app/views/shared/icons/_heart_pulse.html.erb +4 -0
- data/app/views/shared/icons/_history.html.erb +11 -0
- data/app/views/shared/icons/_home.html.erb +10 -0
- data/app/views/shared/icons/_image.html.erb +3 -0
- data/app/views/shared/icons/_inbox.html.erb +3 -0
- data/app/views/shared/icons/_info_circle.html.erb +10 -0
- data/app/views/shared/icons/_key.html.erb +3 -0
- data/app/views/shared/icons/_layers.html.erb +10 -0
- data/app/views/shared/icons/_lightbulb.html.erb +10 -0
- data/app/views/shared/icons/_lightning.html.erb +3 -0
- data/app/views/shared/icons/_list.html.erb +3 -0
- data/app/views/shared/icons/_lock.html.erb +3 -0
- data/app/views/shared/icons/_logout.html.erb +3 -0
- data/app/views/shared/icons/_magazine.html.erb +3 -0
- data/app/views/shared/icons/_magic.html.erb +3 -0
- data/app/views/shared/icons/_minus.html.erb +10 -0
- data/app/views/shared/icons/_mobile.html.erb +10 -0
- data/app/views/shared/icons/_moon.html.erb +3 -0
- data/app/views/shared/icons/_network.html.erb +10 -0
- data/app/views/shared/icons/_new_item_banner.html.erb +1 -0
- data/app/views/shared/icons/_ouroboros.html.erb +24 -0
- data/app/views/shared/icons/_package.html.erb +3 -0
- data/app/views/shared/icons/_palette.html.erb +3 -0
- data/app/views/shared/icons/_paper_plane.html.erb +10 -0
- data/app/views/shared/icons/_photo.html.erb +10 -0
- data/app/views/shared/icons/_play.html.erb +4 -0
- data/app/views/shared/icons/_plus.html.erb +3 -0
- data/app/views/shared/icons/_pocket.html.erb +11 -0
- data/app/views/shared/icons/_prysm-icon.html.erb +34 -0
- data/app/views/shared/icons/_prysm.html.erb +13 -0
- data/app/views/shared/icons/_pushbullet-1.html.erb +29 -0
- data/app/views/shared/icons/_pushbullet-2.html.erb +2 -0
- data/app/views/shared/icons/_puzzle.html.erb +10 -0
- data/app/views/shared/icons/_qrcode.html.erb +3 -0
- data/app/views/shared/icons/_question.html.erb +3 -0
- data/app/views/shared/icons/_receipt.html.erb +10 -0
- data/app/views/shared/icons/_redo.html.erb +3 -0
- data/app/views/shared/icons/_refresh.html.erb +3 -0
- data/app/views/shared/icons/_rocket.html.erb +10 -0
- data/app/views/shared/icons/_rss.html.erb +3 -0
- data/app/views/shared/icons/_save.html.erb +3 -0
- data/app/views/shared/icons/_search.html.erb +3 -0
- data/app/views/shared/icons/_search_minus.html.erb +10 -0
- data/app/views/shared/icons/_search_plus.html.erb +10 -0
- data/app/views/shared/icons/_server_error.html.erb +6 -0
- data/app/views/shared/icons/_share.html.erb +3 -0
- data/app/views/shared/icons/_shield_check.html.erb +3 -0
- data/app/views/shared/icons/_sign_in.html.erb +3 -0
- data/app/views/shared/icons/_spinner.html.erb +4 -0
- data/app/views/shared/icons/_star.html.erb +3 -0
- data/app/views/shared/icons/_store.html.erb +10 -0
- data/app/views/shared/icons/_sun.html.erb +3 -0
- data/app/views/shared/icons/_sync.html.erb +3 -0
- data/app/views/shared/icons/_table.html.erb +3 -0
- data/app/views/shared/icons/_tag.html.erb +3 -0
- data/app/views/shared/icons/_tags.html.erb +11 -0
- data/app/views/shared/icons/_tools.html.erb +4 -0
- data/app/views/shared/icons/_trash.html.erb +3 -0
- data/app/views/shared/icons/_undo.html.erb +3 -0
- data/app/views/shared/icons/_unlock.html.erb +3 -0
- data/app/views/shared/icons/_upload.html.erb +3 -0
- data/app/views/shared/icons/_user.html.erb +3 -0
- data/app/views/shared/icons/_user_circle.html.erb +10 -0
- data/app/views/shared/icons/_user_plus.html.erb +10 -0
- data/app/views/shared/icons/_video.html.erb +3 -0
- data/app/views/shared/icons/_wrench.html.erb +11 -0
- data/app/views/style_guide/index.html.erb +77 -0
- data/app/views/style_guide/sections/_alerts.html.erb +114 -0
- data/app/views/style_guide/sections/_badges.html.erb +78 -0
- data/app/views/style_guide/sections/_buttons.html.erb +130 -0
- data/app/views/style_guide/sections/_cards.html.erb +84 -0
- data/app/views/style_guide/sections/_colors.html.erb +106 -0
- data/app/views/style_guide/sections/_file_upload.html.erb +135 -0
- data/app/views/style_guide/sections/_forms.html.erb +129 -0
- data/app/views/style_guide/sections/_gradients.html.erb +253 -0
- data/app/views/style_guide/sections/_header.html.erb +12 -0
- data/app/views/style_guide/sections/_icons.html.erb +55 -0
- data/app/views/style_guide/sections/_images.html.erb +40 -0
- data/app/views/style_guide/sections/_loading.html.erb +242 -0
- data/app/views/style_guide/sections/_pagination.html.erb +212 -0
- data/app/views/style_guide/sections/_profile_components.html.erb +203 -0
- data/app/views/style_guide/sections/_theme_switcher.html.erb +72 -0
- data/app/views/style_guide/sections/_typography.html.erb +65 -0
- data/bin/ai-optimize-claude-md +540 -0
- data/bin/ai-review-local +345 -0
- data/bin/ai-security-review +585 -0
- data/bin/brakeman +9 -0
- data/bin/install-hooks +57 -0
- data/bin/rake +7 -0
- data/bin/rubocop +10 -0
- data/bin/verify_setup.rb +31 -0
- data/config/brakeman.ignore +28 -0
- data/config/initializers/neon_sakura.rb +15 -0
- data/config/license_overrides.yml +13 -0
- data/config/routes.rb +21 -0
- data/config/theme_mappings.yml +61 -0
- data/docs/PRYSM_ASSETS.md +210 -0
- data/docs/plans/extract_ai_reviewer_plan.md +151 -0
- data/docs/plans/neon_sakura_gem_plan.md +138 -0
- data/lib/neon_sakura/configuration.rb +94 -0
- data/lib/neon_sakura/engine.rb +48 -0
- data/lib/neon_sakura/icon_helper.rb +54 -0
- data/lib/neon_sakura/profile_helper.rb +24 -0
- data/lib/neon_sakura/stylesheet_helper.rb +40 -0
- data/lib/neon_sakura/theme_helper.rb +63 -0
- data/lib/neon_sakura/theme_importer.rb +112 -0
- data/lib/neon_sakura/version.rb +5 -0
- data/lib/neon_sakura.rb +13 -0
- data/neon_sakura.gemspec +50 -0
- data/package.json +18 -0
- data/scripts/git-hooks/post-merge +132 -0
- data/scripts/git-hooks/pre-commit +123 -0
- data/scripts/git-hooks/pre-push +127 -0
- data/scripts/license-check.rb +587 -0
- data/settings.local.json +12 -0
- data/yarn.lock +778 -0
- metadata +503 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# License checking script for commercial Ruby projects
|
|
5
|
+
# Checks gem licenses against a whitelist of commercial-friendly licenses
|
|
6
|
+
|
|
7
|
+
require 'json'
|
|
8
|
+
require 'bundler'
|
|
9
|
+
require 'net/http'
|
|
10
|
+
require 'uri'
|
|
11
|
+
require 'open3'
|
|
12
|
+
|
|
13
|
+
class LicenseChecker
|
|
14
|
+
# Commercial-friendly license whitelist
|
|
15
|
+
APPROVED_LICENSES = Set.new([
|
|
16
|
+
'MIT',
|
|
17
|
+
'Apache-2.0',
|
|
18
|
+
'Apache 2.0',
|
|
19
|
+
'Apache License 2.0',
|
|
20
|
+
'Apache Software License',
|
|
21
|
+
'Apache',
|
|
22
|
+
'HPND',
|
|
23
|
+
'BSD-2-Clause',
|
|
24
|
+
'BSD-3-Clause',
|
|
25
|
+
'BSD',
|
|
26
|
+
'ISC',
|
|
27
|
+
'CC0-1.0',
|
|
28
|
+
'Unlicense',
|
|
29
|
+
'Public Domain',
|
|
30
|
+
'Ruby',
|
|
31
|
+
'Ruby License',
|
|
32
|
+
'Simplified BSD',
|
|
33
|
+
'Modified BSD',
|
|
34
|
+
'New BSD',
|
|
35
|
+
'BSD-2-Clause-Patent',
|
|
36
|
+
'BSD-3-Clause-Clear',
|
|
37
|
+
'CC-BY-3.0',
|
|
38
|
+
'CC-BY-4.0',
|
|
39
|
+
'WTFPL',
|
|
40
|
+
'Zlib',
|
|
41
|
+
'PostgreSQL',
|
|
42
|
+
'OpenSSL',
|
|
43
|
+
'SSLeay',
|
|
44
|
+
'curl',
|
|
45
|
+
'libpng',
|
|
46
|
+
'libjpeg',
|
|
47
|
+
'FTL',
|
|
48
|
+
'Bzip2',
|
|
49
|
+
'zlib/libpng',
|
|
50
|
+
'MIT-0',
|
|
51
|
+
'MPL-2.0',
|
|
52
|
+
'Mozilla Public License 2.0',
|
|
53
|
+
'EPL-1.0',
|
|
54
|
+
'EPL-2.0',
|
|
55
|
+
'Eclipse Public License 1.0',
|
|
56
|
+
'Eclipse Public License 2.0',
|
|
57
|
+
'CDDL-1.0',
|
|
58
|
+
'CDDL-1.1',
|
|
59
|
+
'Common Development and Distribution License 1.0',
|
|
60
|
+
'Common Development and Distribution License 1.1',
|
|
61
|
+
'X11',
|
|
62
|
+
'MIT/X11',
|
|
63
|
+
'Artistic-2.0',
|
|
64
|
+
'Artistic License 2.0',
|
|
65
|
+
'Unicode-TOU',
|
|
66
|
+
'Unicode License Agreement - Data Files and Software',
|
|
67
|
+
'W3C',
|
|
68
|
+
'W3C Software Notice and License',
|
|
69
|
+
'JSON',
|
|
70
|
+
'same as ruby',
|
|
71
|
+
'same as ruby\'s',
|
|
72
|
+
'Ruby\'s',
|
|
73
|
+
'same as Ruby\'s',
|
|
74
|
+
'Ruby License',
|
|
75
|
+
'same as Ruby',
|
|
76
|
+
'Ruby license',
|
|
77
|
+
'same as ruby license',
|
|
78
|
+
'Ruby License/GPL',
|
|
79
|
+
'Ruby/GPL',
|
|
80
|
+
'GPL-2.0+ OR Ruby',
|
|
81
|
+
'GPL-3.0+ OR Ruby',
|
|
82
|
+
'BSD-2-Clause OR Ruby',
|
|
83
|
+
'BSD-3-Clause OR Ruby',
|
|
84
|
+
'MIT OR Ruby',
|
|
85
|
+
'Apache-2.0 OR Ruby',
|
|
86
|
+
'Dual licensed under Ruby or GPLv2+',
|
|
87
|
+
'Dual licensed under Ruby or GPLv3+',
|
|
88
|
+
'Dual MIT/GPL',
|
|
89
|
+
'MIT OR GPL-2.0',
|
|
90
|
+
'MIT OR GPL-3.0',
|
|
91
|
+
'Apache-2.0 OR MIT',
|
|
92
|
+
'BSD-2-Clause OR MIT',
|
|
93
|
+
'BSD-3-Clause OR MIT',
|
|
94
|
+
'GPL-2.0+ OR MIT',
|
|
95
|
+
'GPL-3.0+ OR MIT',
|
|
96
|
+
'LGPL-2.1',
|
|
97
|
+
'LGPL-2.1+',
|
|
98
|
+
'LGPL-3.0',
|
|
99
|
+
'LGPL-3.0+',
|
|
100
|
+
'Lesser General Public License 2.1',
|
|
101
|
+
'Lesser General Public License 3.0',
|
|
102
|
+
'GNU Lesser General Public License v2.1',
|
|
103
|
+
'GNU Lesser General Public License v3.0',
|
|
104
|
+
'GPL-2.0+ OR LGPL-2.1+',
|
|
105
|
+
'GPL-3.0+ OR LGPL-3.0+',
|
|
106
|
+
'Nonstandard',
|
|
107
|
+
'none',
|
|
108
|
+
'unknown',
|
|
109
|
+
'proprietary',
|
|
110
|
+
'Brakeman Public Use License'
|
|
111
|
+
]).freeze
|
|
112
|
+
|
|
113
|
+
# Known problematic licenses that require override
|
|
114
|
+
PROBLEMATIC_LICENSES = Set.new([
|
|
115
|
+
'GPL-2.0',
|
|
116
|
+
'GPL-3.0',
|
|
117
|
+
'GPL-2.0+',
|
|
118
|
+
'GPL-3.0+',
|
|
119
|
+
'GPLv2',
|
|
120
|
+
'GPLv3',
|
|
121
|
+
'GNU GPL v2',
|
|
122
|
+
'GNU GPL v3',
|
|
123
|
+
'GNU General Public License v2.0',
|
|
124
|
+
'GNU General Public License v3.0',
|
|
125
|
+
'AGPL-3.0',
|
|
126
|
+
'AGPL-3.0+',
|
|
127
|
+
'GNU Affero General Public License v3.0',
|
|
128
|
+
'CC-BY-SA-3.0',
|
|
129
|
+
'CC-BY-SA-4.0',
|
|
130
|
+
'Creative Commons Attribution-ShareAlike 3.0',
|
|
131
|
+
'Creative Commons Attribution-ShareAlike 4.0',
|
|
132
|
+
'EUPL-1.1',
|
|
133
|
+
'EUPL-1.2',
|
|
134
|
+
'European Union Public License 1.1',
|
|
135
|
+
'European Union Public License 1.2',
|
|
136
|
+
'CPAL-1.0',
|
|
137
|
+
'Common Public Attribution License 1.0',
|
|
138
|
+
'OSL-3.0',
|
|
139
|
+
'Open Software License 3.0',
|
|
140
|
+
'QPL-1.0',
|
|
141
|
+
'Q Public License 1.0',
|
|
142
|
+
'Sleepycat',
|
|
143
|
+
'Berkeley Database License',
|
|
144
|
+
'CPOL-1.02',
|
|
145
|
+
'Code Project Open License 1.02'
|
|
146
|
+
]).freeze
|
|
147
|
+
|
|
148
|
+
def initialize
|
|
149
|
+
@override_file = 'config/license_overrides.yml'
|
|
150
|
+
@overrides = load_overrides
|
|
151
|
+
@exit_code = 0
|
|
152
|
+
@python_requirements = find_python_requirements
|
|
153
|
+
@ml_models = find_ml_models
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def check_licenses
|
|
157
|
+
puts '🔍 Checking licenses for commercial compatibility...'
|
|
158
|
+
puts '📦 Scanning Ruby gems...'
|
|
159
|
+
|
|
160
|
+
problematic_gems = []
|
|
161
|
+
unknown_gems = []
|
|
162
|
+
|
|
163
|
+
# Get all gems and their specifications
|
|
164
|
+
Bundler.load.specs.each do |spec|
|
|
165
|
+
next if spec.name == 'bundler' # Skip bundler itself
|
|
166
|
+
|
|
167
|
+
licenses = get_gem_licenses(spec)
|
|
168
|
+
|
|
169
|
+
# Check if gem is overridden (for both licensed and unlicensed gems)
|
|
170
|
+
if @overrides.dig('approved_gems', spec.name)
|
|
171
|
+
puts "✅ #{spec.name} (#{spec.version}) - OVERRIDE: #{@overrides['approved_gems'][spec.name]}"
|
|
172
|
+
next
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
if licenses.empty?
|
|
176
|
+
unknown_gems << { name: spec.name, version: spec.version.to_s, type: 'ruby' }
|
|
177
|
+
next
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Check licenses
|
|
181
|
+
approved = licenses.any? { |license| license_approved?(license) }
|
|
182
|
+
|
|
183
|
+
if approved
|
|
184
|
+
puts "✅ 💎 #{spec.name} (#{spec.version}) - #{licenses.join(', ')}"
|
|
185
|
+
else
|
|
186
|
+
problematic_gems << {
|
|
187
|
+
name: spec.name,
|
|
188
|
+
version: spec.version.to_s,
|
|
189
|
+
type: 'ruby',
|
|
190
|
+
licenses: licenses,
|
|
191
|
+
problematic: licenses.any? { |license| PROBLEMATIC_LICENSES.include?(license) }
|
|
192
|
+
}
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Check Python dependencies
|
|
197
|
+
puts "\n🐍 Scanning Python dependencies..."
|
|
198
|
+
python_results = check_python_dependencies
|
|
199
|
+
|
|
200
|
+
# Check ML models
|
|
201
|
+
puts "\n🤖 Scanning ML models..."
|
|
202
|
+
model_results = check_ml_models
|
|
203
|
+
|
|
204
|
+
# Check Docker containers
|
|
205
|
+
puts "\n🐳 Scanning Docker containers..."
|
|
206
|
+
container_results = check_docker_containers
|
|
207
|
+
|
|
208
|
+
# Combine all results
|
|
209
|
+
all_problematic = problematic_gems + python_results[:problematic] + model_results[:problematic] + container_results[:problematic]
|
|
210
|
+
all_unknown = unknown_gems + python_results[:unknown] + model_results[:unknown] + container_results[:unknown]
|
|
211
|
+
|
|
212
|
+
# Report results
|
|
213
|
+
if all_problematic.any?
|
|
214
|
+
puts "\n❌ PROBLEMATIC LICENSES FOUND:"
|
|
215
|
+
all_problematic.each do |item|
|
|
216
|
+
severity = item[:problematic] ? '🚨 BLOCKED' : '⚠️ REVIEW'
|
|
217
|
+
type_icon = get_type_icon(item[:type])
|
|
218
|
+
puts " #{severity} #{type_icon} #{item[:name]} (#{item[:version]}) - #{item[:licenses].join(', ')}"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
puts "\n💡 To approve items, add them to #{@override_file}:"
|
|
222
|
+
puts 'approved_gems:'
|
|
223
|
+
all_problematic.each do |item|
|
|
224
|
+
puts " #{item[:name]}: \"Commercial license approved - [reason]\""
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
@exit_code = 1
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
if all_unknown.any?
|
|
231
|
+
puts "\n⚠️ UNKNOWN LICENSES (please investigate):"
|
|
232
|
+
all_unknown.each do |item|
|
|
233
|
+
type_icon = get_type_icon(item[:type])
|
|
234
|
+
puts " #{type_icon} #{item[:name]} (#{item[:version]}) - No license information"
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
puts "\n💡 To approve items with unknown licenses, add them to #{@override_file}:"
|
|
238
|
+
puts 'approved_gems:'
|
|
239
|
+
all_unknown.each do |item|
|
|
240
|
+
puts " #{item[:name]}: \"License verified manually - [details]\""
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
@exit_code = 1
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
if @exit_code.zero?
|
|
247
|
+
puts "\n✅ All licenses are commercial-friendly!"
|
|
248
|
+
puts '📊 Summary:'
|
|
249
|
+
puts " 💎 Ruby gems: #{Bundler.load.specs.count - 1} checked"
|
|
250
|
+
puts " 🐍 Python packages: #{python_results[:total]} checked"
|
|
251
|
+
puts " 🤖 ML models: #{model_results[:total]} checked"
|
|
252
|
+
puts " 🐳 Docker containers: #{container_results[:total]} checked"
|
|
253
|
+
else
|
|
254
|
+
puts "\n❌ License check failed. Please review the items above."
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
exit @exit_code
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
private
|
|
261
|
+
|
|
262
|
+
def find_python_requirements
|
|
263
|
+
# Use the unified requirements.txt at project root
|
|
264
|
+
# requirements_files << 'requirements.txt' if File.exist?('requirements.txt')
|
|
265
|
+
# requirements_files
|
|
266
|
+
|
|
267
|
+
[ 'docker/donut-service/requirements.txt' ]
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def find_ml_models
|
|
271
|
+
models = []
|
|
272
|
+
# Search for model references in Python files
|
|
273
|
+
Dir.glob('**/*.py').each do |file|
|
|
274
|
+
content = File.read(file)
|
|
275
|
+
# Look for Hugging Face model references
|
|
276
|
+
content.scan(%r{["']([\w-]+/[\w-]+)["']}) do |match|
|
|
277
|
+
model_name = match[0]
|
|
278
|
+
models << { name: model_name, file: file } if model_name.include?('/') && !model_name.include?('http')
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
models.uniq
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def check_python_dependencies
|
|
285
|
+
problematic = []
|
|
286
|
+
unknown = []
|
|
287
|
+
total = 0
|
|
288
|
+
|
|
289
|
+
@python_requirements.each do |req_file|
|
|
290
|
+
next unless File.exist?(req_file)
|
|
291
|
+
|
|
292
|
+
File.readlines(req_file).each do |line|
|
|
293
|
+
line.strip!
|
|
294
|
+
next if line.empty? || line.start_with?('#')
|
|
295
|
+
|
|
296
|
+
# Parse package name and version
|
|
297
|
+
package_name = line.split(/[=<>!]/)[0].strip
|
|
298
|
+
# Handle uvicorn[standard] style package names
|
|
299
|
+
package_name = package_name.split('[')[0] if package_name.include?('[')
|
|
300
|
+
total += 1
|
|
301
|
+
|
|
302
|
+
# Check if overridden
|
|
303
|
+
if @overrides.dig('approved_gems', package_name)
|
|
304
|
+
puts "✅ 🐍 #{package_name} - OVERRIDE: #{@overrides['approved_gems'][package_name]}"
|
|
305
|
+
next
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Get license from PyPI
|
|
309
|
+
license_info = get_pypi_license(package_name)
|
|
310
|
+
|
|
311
|
+
if license_info.nil?
|
|
312
|
+
unknown << {
|
|
313
|
+
name: package_name,
|
|
314
|
+
version: line.split('==')[1] || 'latest',
|
|
315
|
+
type: 'python',
|
|
316
|
+
licenses: []
|
|
317
|
+
}
|
|
318
|
+
elsif license_approved?(license_info)
|
|
319
|
+
puts "✅ 🐍 #{package_name} - #{license_info}"
|
|
320
|
+
else
|
|
321
|
+
problematic << {
|
|
322
|
+
name: package_name,
|
|
323
|
+
version: line.split('==')[1] || 'latest',
|
|
324
|
+
type: 'python',
|
|
325
|
+
licenses: [ license_info ],
|
|
326
|
+
problematic: PROBLEMATIC_LICENSES.include?(license_info)
|
|
327
|
+
}
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
{ problematic: problematic, unknown: unknown, total: total }
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def check_ml_models
|
|
336
|
+
problematic = []
|
|
337
|
+
unknown = []
|
|
338
|
+
total = 0
|
|
339
|
+
|
|
340
|
+
@ml_models.each do |model_info|
|
|
341
|
+
total += 1
|
|
342
|
+
model_name = model_info[:name]
|
|
343
|
+
|
|
344
|
+
# Check if overridden
|
|
345
|
+
if @overrides.dig('approved_gems', model_name)
|
|
346
|
+
puts "✅ 🤖 #{model_name} - OVERRIDE: #{@overrides['approved_gems'][model_name]}"
|
|
347
|
+
next
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# Get license from Hugging Face API
|
|
351
|
+
license_info = get_huggingface_license(model_name)
|
|
352
|
+
|
|
353
|
+
if license_info.nil?
|
|
354
|
+
unknown << {
|
|
355
|
+
name: model_name,
|
|
356
|
+
version: 'latest',
|
|
357
|
+
type: 'ml_model',
|
|
358
|
+
licenses: []
|
|
359
|
+
}
|
|
360
|
+
elsif license_approved?(license_info)
|
|
361
|
+
puts "✅ 🤖 #{model_name} - #{license_info}"
|
|
362
|
+
else
|
|
363
|
+
problematic << {
|
|
364
|
+
name: model_name,
|
|
365
|
+
version: 'latest',
|
|
366
|
+
type: 'ml_model',
|
|
367
|
+
licenses: [ license_info ],
|
|
368
|
+
problematic: PROBLEMATIC_LICENSES.include?(license_info)
|
|
369
|
+
}
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
{ problematic: problematic, unknown: unknown, total: total }
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def check_docker_containers
|
|
377
|
+
problematic = []
|
|
378
|
+
unknown = []
|
|
379
|
+
total = 0
|
|
380
|
+
|
|
381
|
+
# Check Dockerfiles for base images and their licenses
|
|
382
|
+
dockerfiles = Dir.glob('**/Dockerfile*')
|
|
383
|
+
|
|
384
|
+
dockerfiles.each do |dockerfile|
|
|
385
|
+
content = File.read(dockerfile)
|
|
386
|
+
|
|
387
|
+
# Extract FROM statements
|
|
388
|
+
content.scan(/^FROM\s+([^\s]+)/) do |match|
|
|
389
|
+
image_name = match[0]
|
|
390
|
+
next if image_name == 'scratch' || image_name.start_with?('$') || image_name == 'base'
|
|
391
|
+
|
|
392
|
+
total += 1
|
|
393
|
+
|
|
394
|
+
# Check if overridden
|
|
395
|
+
if @overrides.dig('approved_gems', image_name)
|
|
396
|
+
puts "✅ 🐳 #{image_name} - OVERRIDE: #{@overrides['approved_gems'][image_name]}"
|
|
397
|
+
next
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# For common base images, assume they're fine
|
|
401
|
+
if image_name.match?(/(ubuntu|debian|alpine|python|node|ruby):/)
|
|
402
|
+
puts "✅ 🐳 #{image_name} - Standard base image (assumed MIT/Apache compatible)"
|
|
403
|
+
else
|
|
404
|
+
unknown << {
|
|
405
|
+
name: image_name,
|
|
406
|
+
version: 'latest',
|
|
407
|
+
type: 'docker',
|
|
408
|
+
licenses: []
|
|
409
|
+
}
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
{ problematic: problematic, unknown: unknown, total: total }
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
def get_pypi_license(package_name)
|
|
418
|
+
uri = URI("https://pypi.org/pypi/#{package_name}/json")
|
|
419
|
+
response = Net::HTTP.get_response(uri)
|
|
420
|
+
|
|
421
|
+
return nil unless response.code == '200'
|
|
422
|
+
|
|
423
|
+
data = JSON.parse(response.body)
|
|
424
|
+
license_info = data.dig('info', 'license')
|
|
425
|
+
|
|
426
|
+
# Try classifiers if license field is empty
|
|
427
|
+
if license_info.nil? || license_info.empty?
|
|
428
|
+
classifiers = data.dig('info', 'classifiers') || []
|
|
429
|
+
license_classifiers = classifiers.select { |c| c.start_with?('License ::') }
|
|
430
|
+
|
|
431
|
+
unless license_classifiers.empty?
|
|
432
|
+
# Extract license from classifier like "License :: OSI Approved :: MIT License"
|
|
433
|
+
license_info = license_classifiers.first.split(' :: ').last
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
normalize_license(license_info)
|
|
438
|
+
rescue StandardError => e
|
|
439
|
+
puts "⚠️ Error fetching PyPI info for #{package_name}: #{e.message}"
|
|
440
|
+
nil
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def get_huggingface_license(model_name)
|
|
444
|
+
uri = URI("https://huggingface.co/api/models/#{model_name}")
|
|
445
|
+
response = Net::HTTP.get_response(uri)
|
|
446
|
+
|
|
447
|
+
return nil unless response.code == '200'
|
|
448
|
+
|
|
449
|
+
data = JSON.parse(response.body)
|
|
450
|
+
license_info = data['license']
|
|
451
|
+
|
|
452
|
+
normalize_license(license_info)
|
|
453
|
+
rescue StandardError => e
|
|
454
|
+
puts "⚠️ Error fetching Hugging Face info for #{model_name}: #{e.message}"
|
|
455
|
+
nil
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
def get_type_icon(type)
|
|
459
|
+
case type
|
|
460
|
+
when 'ruby' then '💎'
|
|
461
|
+
when 'python' then '🐍'
|
|
462
|
+
when 'ml_model' then '🤖'
|
|
463
|
+
when 'docker' then '🐳'
|
|
464
|
+
else '📦'
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def get_gem_licenses(spec)
|
|
469
|
+
licenses = []
|
|
470
|
+
|
|
471
|
+
# Try spec.licenses first (array)
|
|
472
|
+
if spec.respond_to?(:licenses) && spec.licenses.any?
|
|
473
|
+
licenses = spec.licenses
|
|
474
|
+
elsif spec.respond_to?(:license) && spec.license
|
|
475
|
+
licenses = [ spec.license ]
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
# Normalize license names
|
|
479
|
+
licenses.map { |license| normalize_license(license) }.compact.uniq
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def normalize_license(license)
|
|
483
|
+
return nil if license.nil? || license.strip.empty?
|
|
484
|
+
|
|
485
|
+
normalized = license.strip
|
|
486
|
+
|
|
487
|
+
# If license text is very long (likely full license text), try to extract just the license name
|
|
488
|
+
if normalized.length > 200
|
|
489
|
+
# Look for common license identifiers in long text
|
|
490
|
+
case normalized.downcase
|
|
491
|
+
when /bsd.*3.*clause/i
|
|
492
|
+
return 'BSD-3-Clause'
|
|
493
|
+
when /bsd.*2.*clause/i
|
|
494
|
+
return 'BSD-2-Clause'
|
|
495
|
+
when /bsd/i
|
|
496
|
+
return 'BSD'
|
|
497
|
+
when /mit/i
|
|
498
|
+
return 'MIT'
|
|
499
|
+
when /apache.*2/i
|
|
500
|
+
return 'Apache-2.0'
|
|
501
|
+
when /apache/i
|
|
502
|
+
return 'Apache'
|
|
503
|
+
when /gpl.*3/i
|
|
504
|
+
return 'GPL-3.0'
|
|
505
|
+
when /gpl.*2/i
|
|
506
|
+
return 'GPL-2.0'
|
|
507
|
+
when /lgpl.*3/i
|
|
508
|
+
return 'LGPL-3.0'
|
|
509
|
+
when /lgpl.*2/i
|
|
510
|
+
return 'LGPL-2.1'
|
|
511
|
+
when /mozilla.*public.*license/i
|
|
512
|
+
return 'MPL-2.0'
|
|
513
|
+
else
|
|
514
|
+
# Truncate very long license text and add indicator
|
|
515
|
+
return "#{normalized[0..100]}... [truncated - full license text]"
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# Common normalizations for shorter license names
|
|
520
|
+
case normalized.downcase
|
|
521
|
+
when 'mit license', 'mit-license', 'mit (http://opensource.org/licenses/mit-license.php)'
|
|
522
|
+
'MIT'
|
|
523
|
+
when 'apache license, version 2.0', 'apache license 2.0', 'apache-2.0 license', 'apache 2.0 license'
|
|
524
|
+
'Apache-2.0'
|
|
525
|
+
when 'bsd license', 'bsd-style', 'bsd style'
|
|
526
|
+
'BSD'
|
|
527
|
+
when 'bsd 3-clause license', 'bsd-3-clause'
|
|
528
|
+
'BSD-3-Clause'
|
|
529
|
+
when 'bsd 2-clause license', 'bsd-2-clause'
|
|
530
|
+
'BSD-2-Clause'
|
|
531
|
+
when 'gpl-2.0', 'gpl v2', 'gpl version 2', 'gnu gpl v2'
|
|
532
|
+
'GPL-2.0'
|
|
533
|
+
when 'gpl-3.0', 'gpl v3', 'gpl version 3', 'gnu gpl v3'
|
|
534
|
+
'GPL-3.0'
|
|
535
|
+
when 'lgpl-2.1', 'lgpl v2.1', 'lgpl version 2.1', 'gnu lgpl v2.1'
|
|
536
|
+
'LGPL-2.1'
|
|
537
|
+
when 'lgpl-3.0', 'lgpl v3', 'lgpl version 3', 'gnu lgpl v3'
|
|
538
|
+
'LGPL-3.0'
|
|
539
|
+
when 'mozilla public license 2.0'
|
|
540
|
+
'MPL-2.0'
|
|
541
|
+
when 'eclipse public license 1.0'
|
|
542
|
+
'EPL-1.0'
|
|
543
|
+
when 'eclipse public license 2.0'
|
|
544
|
+
'EPL-2.0'
|
|
545
|
+
when 'artistic license 2.0'
|
|
546
|
+
'Artistic-2.0'
|
|
547
|
+
when 'public domain'
|
|
548
|
+
'Public Domain'
|
|
549
|
+
when 'unlicense'
|
|
550
|
+
'Unlicense'
|
|
551
|
+
when 'cc0', 'cc0 1.0', 'cc0-1.0 universal'
|
|
552
|
+
'CC0-1.0'
|
|
553
|
+
when 'ruby license', 'ruby\'s license', 'same as ruby\'s license'
|
|
554
|
+
'Ruby'
|
|
555
|
+
when 'isc license'
|
|
556
|
+
'ISC'
|
|
557
|
+
when 'zlib license'
|
|
558
|
+
'Zlib'
|
|
559
|
+
when 'postgresql license'
|
|
560
|
+
'PostgreSQL'
|
|
561
|
+
else
|
|
562
|
+
# Still truncate if somehow a long license made it here
|
|
563
|
+
normalized.length > 100 ? "#{normalized[0..100]}..." : normalized
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
def license_approved?(license)
|
|
568
|
+
APPROVED_LICENSES.include?(license) ||
|
|
569
|
+
APPROVED_LICENSES.any? { |approved| license.match?(/\b#{Regexp.escape(approved)}\b/i) }
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def load_overrides
|
|
573
|
+
return {} unless File.exist?(@override_file)
|
|
574
|
+
|
|
575
|
+
require 'yaml'
|
|
576
|
+
YAML.load_file(@override_file) || {}
|
|
577
|
+
rescue StandardError => e
|
|
578
|
+
puts "⚠️ Warning: Could not load license overrides: #{e.message}"
|
|
579
|
+
{}
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
# Run license check
|
|
584
|
+
if __FILE__ == $PROGRAM_NAME
|
|
585
|
+
checker = LicenseChecker.new
|
|
586
|
+
checker.check_licenses
|
|
587
|
+
end
|