@tutorialkit-rb/cli 0.1.5 → 0.1.7
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.
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/template/.claude/skills/rails-file-management/SKILL.md +211 -0
- package/template/.claude/skills/rails-lesson-recipes/SKILL.md +415 -0
- package/template/.claude/skills/rails-wasm-author-constraints/SKILL.md +181 -0
- package/template/.claude/skills/tutorial-content-structure/SKILL.md +377 -0
- package/template/.claude/skills/tutorial-lesson-config/SKILL.md +389 -0
- package/template/.claude/skills/tutorial-quickstart/SKILL.md +440 -0
- package/template/.github/workflows/deploy.yml +85 -0
- package/template/.gitignore +10 -0
- package/template/CLAUDE.md +47 -0
- package/template/astro.config.ts +12 -0
- package/template/bin/build-pack +84 -0
- package/template/bin/build-ruby-base +180 -0
- package/template/bin/link-local +13 -0
- package/template/bin/unlink-local +7 -0
- package/template/cors-proxy/README.md +63 -0
- package/template/cors-proxy/package-lock.json +1504 -0
- package/template/cors-proxy/package.json +12 -0
- package/template/cors-proxy/src/index.ts +87 -0
- package/template/cors-proxy/wrangler.toml +7 -0
- package/template/netlify.toml +9 -0
- package/template/package.json +12 -4
- package/template/ruby-wasm/Gemfile +6 -0
- package/template/ruby-wasm/Gemfile.base +19 -0
- package/template/ruby-wasm/Gemfile.base.lock +50 -0
- package/template/ruby-wasm/Gemfile.lock +8 -0
- package/template/ruby-wasm/bin/pack-gems +368 -0
- package/template/ruby-wasm/package.json +6 -0
- package/template/src/components/FileManager.tsx +33 -0
- package/template/src/components/HeadTags.astro +6 -6
- package/template/src/components/HelpDropdown.tsx +1 -1
- package/template/src/components/RailsPathLinkHandler.tsx +2 -2
- package/template/src/components/ShellConfigurator.tsx +6 -1
- package/template/src/content/tutorial/1-getting-started/1-creating-your-first-rails-app/content.md +4 -4
- package/template/src/content/tutorial/1-getting-started/2-rails-console/content.md +4 -4
- package/template/src/content/tutorial/2-controllers/2-crud-operations/content.md +2 -2
- package/template/src/content/tutorial/9-outbound-http/1-making-http-requests/_files/.tk-config.json +3 -0
- package/template/src/content/tutorial/9-outbound-http/1-making-http-requests/_files/workspace/app/controllers/http_demo_controller.rb +65 -0
- package/template/src/content/tutorial/9-outbound-http/1-making-http-requests/_files/workspace/app/views/http_demo/index.html.erb +172 -0
- package/template/src/content/tutorial/9-outbound-http/1-making-http-requests/_files/workspace/config/routes.rb +8 -0
- package/template/src/content/tutorial/9-outbound-http/1-making-http-requests/content.md +97 -0
- package/template/src/content/tutorial/9-outbound-http/meta.md +4 -0
- package/template/src/content/tutorial/meta.md +5 -0
- package/template/src/middleware.ts +14 -0
- package/template/src/templates/default/lib/boot-progress.js +49 -0
- package/template/src/templates/default/lib/http-bridge.js +55 -0
- package/template/src/templates/default/lib/patches/http_bridge.rb +167 -0
- package/template/src/templates/default/lib/rails.js +52 -5
- package/template/src/templates/default/lib/server.js +33 -1
- package/template/src/templates/default/package.json +4 -1
- package/template/src/templates/default/scripts/rails.js +1 -1
- package/template/src/templates/default/scripts/smoke-test.js +349 -0
- package/template/src/templates/default/scripts/wasi-loader.mjs +10 -0
- package/template/src/templates/default/workspace/_debug_app/.github/dependabot.yml +12 -0
- package/template/src/templates/default/workspace/_debug_app/.github/workflows/ci.yml +51 -0
- package/template/src/templates/default/workspace/_debug_app/.ruby-version +1 -0
- package/template/src/templates/default/workspace/_debug_app/Gemfile +37 -0
- package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/app/views/layouts/application.html.erb +1 -2
- package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/app/views/pwa/manifest.json.erb +2 -2
- package/template/src/templates/default/workspace/_debug_app/bin/dev +2 -0
- package/template/src/templates/default/workspace/_debug_app/bin/rake +4 -0
- package/template/src/templates/default/workspace/_debug_app/bin/setup +34 -0
- package/template/src/templates/default/workspace/_debug_app/config/application.rb +30 -0
- package/template/src/templates/default/workspace/_debug_app/config/cable.yml +10 -0
- package/template/src/templates/default/workspace/_debug_app/config/credentials.yml.enc +1 -0
- package/template/src/templates/default/workspace/_debug_app/config/master.key +1 -0
- package/template/src/templates/default/workspace/_debug_app/config/routes.rb +14 -0
- package/template/src/templates/default/workspace/_debug_app/test/models/.keep +0 -0
- package/template/src/templates/default/workspace/_debug_app/test/test_helper.rb +15 -0
- package/template/src/templates/default/workspace/_debug_app/tmp/.keep +0 -0
- package/template/src/templates/default/workspace/_debug_app/tmp/pids/.keep +0 -0
- package/template/src/templates/default/workspace/_debug_app/tmp/storage/.keep +0 -0
- package/template/src/templates/default/workspace/_debug_app/vendor/.keep +0 -0
- package/template/src/templates/rails-app/workspace/README.md +24 -0
- package/template/src/templates/rails-app/workspace/Rakefile +6 -0
- package/template/src/templates/rails-app/workspace/app/assets/images/.keep +0 -0
- package/template/src/templates/rails-app/workspace/app/assets/stylesheets/application.css +534 -0
- package/template/src/templates/rails-app/workspace/app/controllers/application_controller.rb +2 -0
- package/template/src/templates/rails-app/workspace/app/helpers/application_helper.rb +2 -0
- package/template/src/templates/rails-app/workspace/app/jobs/application_job.rb +7 -0
- package/template/src/templates/rails-app/workspace/app/mailers/application_mailer.rb +4 -0
- package/template/src/templates/rails-app/workspace/app/models/application_record.rb +3 -0
- package/template/src/templates/rails-app/workspace/app/models/concerns/.keep +0 -0
- package/template/src/templates/rails-app/workspace/app/views/layouts/application.html.erb +38 -0
- package/template/src/templates/rails-app/workspace/app/views/layouts/mailer.html.erb +13 -0
- package/template/src/templates/rails-app/workspace/app/views/layouts/mailer.text.erb +1 -0
- package/template/src/templates/rails-app/workspace/bin/rails +4 -0
- package/template/src/templates/rails-app/workspace/{store/config → config}/application.rb +1 -1
- package/template/src/templates/rails-app/workspace/config/boot.rb +3 -0
- package/template/src/templates/rails-app/workspace/config/database.yml +32 -0
- package/template/src/templates/rails-app/workspace/config/environment.rb +5 -0
- package/template/src/templates/rails-app/workspace/config/environments/development.rb +69 -0
- package/template/src/templates/rails-app/workspace/config/environments/production.rb +89 -0
- package/template/src/templates/rails-app/workspace/config/environments/test.rb +54 -0
- package/template/src/templates/rails-app/workspace/config/initializers/assets.rb +7 -0
- package/template/src/templates/rails-app/workspace/config/initializers/content_security_policy.rb +25 -0
- package/template/src/templates/rails-app/workspace/config/initializers/filter_parameter_logging.rb +8 -0
- package/template/src/templates/rails-app/workspace/config/initializers/inflections.rb +16 -0
- package/template/src/templates/rails-app/workspace/config/locales/en.yml +31 -0
- package/template/src/templates/rails-app/workspace/config/puma.rb +41 -0
- package/template/src/templates/rails-app/workspace/{store/config → config}/routes.rb +1 -1
- package/template/src/templates/rails-app/workspace/config/storage.yml +34 -0
- package/template/src/templates/rails-app/workspace/config.ru +6 -0
- package/template/src/templates/rails-app/workspace/db/seeds.rb +9 -0
- package/template/src/templates/rails-app/workspace/log/.keep +0 -0
- package/template/src/templates/rails-app/workspace/public/400.html +114 -0
- package/template/src/templates/rails-app/workspace/public/404.html +114 -0
- package/template/src/templates/rails-app/workspace/public/406-unsupported-browser.html +114 -0
- package/template/src/templates/rails-app/workspace/public/422.html +114 -0
- package/template/src/templates/rails-app/workspace/public/500.html +114 -0
- package/template/src/templates/rails-app/workspace/public/icon.png +0 -0
- package/template/src/templates/rails-app/workspace/public/icon.svg +3 -0
- package/template/src/templates/rails-app/workspace/public/robots.txt +1 -0
- package/template/src/templates/rails-app/workspace/script/.keep +0 -0
- package/template/src/templates/rails-app/workspace/storage/.keep +0 -0
- package/template/src/templates/rails-app/workspace/test/controllers/.keep +0 -0
- package/template/src/templates/rails-app/workspace/test/helpers/.keep +0 -0
- package/template/src/templates/rails-app/workspace/test/integration/.keep +0 -0
- package/template/src/templates/rails-app/workspace/tmp/.keep +0 -0
- package/template/src/templates/rails-app/workspace/tmp/cache/.keep +0 -0
- package/template/src/templates/rails-app/workspace/tmp/pids/.keep +0 -0
- package/template/src/templates/rails-app/workspace/tmp/sockets/.keep +0 -0
- package/template/src/templates/rails-app/workspace/tmp/storage/.keep +0 -0
- package/template/src/templates/rails-app/workspace/vendor/javascripts/.keep +0 -0
- package/template/tsconfig.json +3 -1
- package/template/uno.config.ts +17 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/README.md +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/Rakefile +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/app/assets/images/.keep +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/app/assets/stylesheets/application.css +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/app/controllers/application_controller.rb +0 -0
- /package/template/src/templates/{rails-app/workspace/store/app/models → default/workspace/_debug_app/app/controllers}/concerns/.keep +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/app/helpers/application_helper.rb +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/app/jobs/application_job.rb +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/app/mailers/application_mailer.rb +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/app/models/application_record.rb +0 -0
- /package/template/src/templates/{rails-app/workspace/store/log → default/workspace/_debug_app/app/models/concerns}/.keep +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/app/views/layouts/mailer.html.erb +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/app/views/layouts/mailer.text.erb +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/app/views/pwa/service-worker.js +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/bin/rails +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/config/boot.rb +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/config/database.yml +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/config/environment.rb +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/config/environments/development.rb +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/config/environments/production.rb +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/config/environments/test.rb +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/config/initializers/assets.rb +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/config/initializers/content_security_policy.rb +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/config/initializers/filter_parameter_logging.rb +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/config/initializers/inflections.rb +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/config/locales/en.yml +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/config/puma.rb +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/config/storage.yml +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/config.ru +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/db/seeds.rb +0 -0
- /package/template/src/templates/{rails-app/workspace/store/script → default/workspace/_debug_app/lib/tasks}/.keep +0 -0
- /package/template/src/templates/{rails-app/workspace/store/storage → default/workspace/_debug_app/log}/.keep +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/public/400.html +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/public/404.html +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/public/406-unsupported-browser.html +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/public/422.html +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/public/500.html +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/public/icon.png +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/public/icon.svg +0 -0
- /package/template/src/templates/{rails-app/workspace/store → default/workspace/_debug_app}/public/robots.txt +0 -0
- /package/template/src/templates/{rails-app/workspace/store/test/controllers → default/workspace/_debug_app/script}/.keep +0 -0
- /package/template/src/templates/{rails-app/workspace/store/test/helpers → default/workspace/_debug_app/storage}/.keep +0 -0
- /package/template/src/templates/{rails-app/workspace/store/test/integration → default/workspace/_debug_app/test/controllers}/.keep +0 -0
- /package/template/src/templates/{rails-app/workspace/store/tmp → default/workspace/_debug_app/test/fixtures/files}/.keep +0 -0
- /package/template/src/templates/{rails-app/workspace/store/tmp/pids → default/workspace/_debug_app/test/helpers}/.keep +0 -0
- /package/template/src/templates/{rails-app/workspace/store/tmp/storage → default/workspace/_debug_app/test/integration}/.keep +0 -0
- /package/template/src/templates/{rails-app/workspace/store/vendor/javascripts → default/workspace/_debug_app/test/mailers}/.keep +0 -0
- /package/template/src/templates/rails-app/workspace/{store/.ruby-version → .ruby-version} +0 -0
- /package/template/src/templates/rails-app/workspace/{store/Gemfile → Gemfile} +0 -0
- /package/template/src/templates/rails-app/workspace/{store/app → app}/javascript/application.js +0 -0
- /package/template/src/templates/rails-app/workspace/{store/app → app}/javascript/controllers/application.js +0 -0
- /package/template/src/templates/rails-app/workspace/{store/app → app}/javascript/controllers/index.js +0 -0
- /package/template/src/templates/rails-app/workspace/{store/bin → bin}/importmap +0 -0
- /package/template/src/templates/rails-app/workspace/{store/config → config}/cable.yml +0 -0
- /package/template/src/templates/rails-app/workspace/{store/config → config}/credentials.yml.enc +0 -0
- /package/template/src/templates/rails-app/workspace/{store/config → config}/importmap.rb +0 -0
- /package/template/src/templates/rails-app/workspace/{store/config → config}/master.key +0 -0
- /package/template/src/templates/rails-app/workspace/{store/test → test}/test_helper.rb +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
interface Env {
|
|
2
|
+
ALLOWED_HOSTS: string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
7
|
+
// Handle CORS preflight
|
|
8
|
+
if (request.method === 'OPTIONS') {
|
|
9
|
+
return new Response(null, {
|
|
10
|
+
headers: corsHeaders(request),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Extract target URL from query string: /proxy?https://api.example.com/data
|
|
15
|
+
const url = new URL(request.url);
|
|
16
|
+
const targetUrl = url.search.slice(1); // Remove leading '?'
|
|
17
|
+
|
|
18
|
+
if (!targetUrl) {
|
|
19
|
+
return new Response(JSON.stringify({ error: 'Missing target URL. Usage: /proxy?https://api.example.com/path' }), {
|
|
20
|
+
status: 400,
|
|
21
|
+
headers: { 'content-type': 'application/json', ...corsHeaders(request) },
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Validate target URL
|
|
26
|
+
let target: URL;
|
|
27
|
+
try {
|
|
28
|
+
target = new URL(targetUrl);
|
|
29
|
+
} catch {
|
|
30
|
+
return new Response(JSON.stringify({ error: 'Invalid target URL' }), {
|
|
31
|
+
status: 400,
|
|
32
|
+
headers: { 'content-type': 'application/json', ...corsHeaders(request) },
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check allowed hosts
|
|
37
|
+
if (env.ALLOWED_HOSTS) {
|
|
38
|
+
const allowed = env.ALLOWED_HOSTS.split(',').map(h => h.trim()).filter(Boolean);
|
|
39
|
+
if (allowed.length > 0 && !allowed.some(h => target.hostname === h || target.hostname.endsWith(`.${h}`))) {
|
|
40
|
+
return new Response(JSON.stringify({ error: `Host ${target.hostname} is not allowed` }), {
|
|
41
|
+
status: 403,
|
|
42
|
+
headers: { 'content-type': 'application/json', ...corsHeaders(request) },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Forward the request
|
|
48
|
+
const headers = new Headers(request.headers);
|
|
49
|
+
headers.delete('host');
|
|
50
|
+
headers.delete('origin');
|
|
51
|
+
headers.delete('referer');
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const response = await fetch(targetUrl, {
|
|
55
|
+
method: request.method,
|
|
56
|
+
headers,
|
|
57
|
+
body: request.method !== 'GET' && request.method !== 'HEAD' ? request.body : undefined,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Return response with CORS headers
|
|
61
|
+
const responseHeaders = new Headers(response.headers);
|
|
62
|
+
for (const [k, v] of Object.entries(corsHeaders(request))) {
|
|
63
|
+
responseHeaders.set(k, v);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return new Response(response.body, {
|
|
67
|
+
status: response.status,
|
|
68
|
+
statusText: response.statusText,
|
|
69
|
+
headers: responseHeaders,
|
|
70
|
+
});
|
|
71
|
+
} catch (err) {
|
|
72
|
+
return new Response(JSON.stringify({ error: `Proxy error: ${(err as Error).message}` }), {
|
|
73
|
+
status: 502,
|
|
74
|
+
headers: { 'content-type': 'application/json', ...corsHeaders(request) },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
function corsHeaders(request: Request): Record<string, string> {
|
|
81
|
+
return {
|
|
82
|
+
'access-control-allow-origin': request.headers.get('origin') || '*',
|
|
83
|
+
'access-control-allow-methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
|
|
84
|
+
'access-control-allow-headers': request.headers.get('access-control-request-headers') || '*',
|
|
85
|
+
'access-control-max-age': '86400',
|
|
86
|
+
};
|
|
87
|
+
}
|
package/template/package.json
CHANGED
|
@@ -7,7 +7,14 @@
|
|
|
7
7
|
"astro": "astro",
|
|
8
8
|
"build": "astro check && astro build",
|
|
9
9
|
"build:wasm": "./bin/build-wasm",
|
|
10
|
+
"build:wasm:clean": "cd ruby-wasm && rm -rf build rubies tmp dist vendor .bundle && cd .. && npm run build:wasm",
|
|
11
|
+
"build:wasm:base": "./bin/build-ruby-base",
|
|
12
|
+
"build:wasm:base:clean": "cd ruby-wasm && rm -rf build rubies tmp dist/ruby-base.wasm dist/usr-dir.txt vendor .bundle Gemfile.base.lock && cd .. && npm run build:wasm:base",
|
|
13
|
+
"pack:wasm": "./bin/build-pack",
|
|
14
|
+
"pack:wasm:clean": "cd ruby-wasm && rm -rf tmp/gems_bundle dist/ruby.wasm vendor .bundle && rm -f ../public/ruby.wasm ../public/ruby.wasm.hash && cd .. && npm run pack:wasm",
|
|
10
15
|
"dev": "astro dev",
|
|
16
|
+
"link:local": "./bin/link-local",
|
|
17
|
+
"unlink:local": "./bin/unlink-local",
|
|
11
18
|
"preview": "astro preview",
|
|
12
19
|
"start": "astro dev"
|
|
13
20
|
},
|
|
@@ -15,7 +22,8 @@
|
|
|
15
22
|
"@codemirror/lang-yaml": "^6.1.2",
|
|
16
23
|
"@codemirror/legacy-modes": "^6.5.1",
|
|
17
24
|
"@nanostores/react": "0.7.2",
|
|
18
|
-
"@tutorialkit-rb/react": "0.1.
|
|
25
|
+
"@tutorialkit-rb/react": "0.1.7",
|
|
26
|
+
"@tutorialkit-rb/runtime": "0.1.7",
|
|
19
27
|
"nanostores": "^0.10.3",
|
|
20
28
|
"react": "^18.3.1",
|
|
21
29
|
"react-dom": "^18.3.1"
|
|
@@ -23,9 +31,9 @@
|
|
|
23
31
|
"devDependencies": {
|
|
24
32
|
"@astrojs/check": "^0.7.0",
|
|
25
33
|
"@astrojs/react": "^3.6.0",
|
|
26
|
-
"@tutorialkit-rb/astro": "0.1.
|
|
27
|
-
"@tutorialkit-rb/theme": "0.1.
|
|
28
|
-
"@tutorialkit-rb/types": "0.1.
|
|
34
|
+
"@tutorialkit-rb/astro": "0.1.7",
|
|
35
|
+
"@tutorialkit-rb/theme": "0.1.7",
|
|
36
|
+
"@tutorialkit-rb/types": "0.1.7",
|
|
29
37
|
"@types/mdast": "^4.0.4",
|
|
30
38
|
"@types/node": "^20.14.6",
|
|
31
39
|
"@types/react": "^18.3.3",
|
|
@@ -20,3 +20,9 @@ gem "solid_cable"
|
|
|
20
20
|
gem "image_processing", "~> 1.2"
|
|
21
21
|
|
|
22
22
|
gem "tzinfo-data"
|
|
23
|
+
|
|
24
|
+
# Pin json < 2.8 — newer versions redefine rb_hash_bulk_insert as static,
|
|
25
|
+
# which conflicts with Ruby 3.3's headers during WASM cross-compilation.
|
|
26
|
+
gem "json", "< 2.8"
|
|
27
|
+
|
|
28
|
+
# gem "ruby_llm" — removed; outbound HTTP demo uses Net::HTTP only
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
source "https://rubygems.org"
|
|
2
|
+
|
|
3
|
+
# Pin to match monolithic pipeline's Gemfile.lock. Unpinned, Bundler resolves
|
|
4
|
+
# to the latest (2.8.1), which may differ from the monolithic lockfile (2.7.1).
|
|
5
|
+
gem "js", "2.7.1"
|
|
6
|
+
|
|
7
|
+
# Gems with C extensions that must be statically linked into the base binary.
|
|
8
|
+
# WASI has no dlopen() — C extensions cannot be loaded at runtime from the
|
|
9
|
+
# filesystem. They must be compiled and linked during `rbwasm build`.
|
|
10
|
+
# Pure Ruby gems are handled by the dynamic packing layer (bin/pack-gems).
|
|
11
|
+
gem "bcrypt", "~> 3.1.7"
|
|
12
|
+
gem "websocket-driver", "~> 0.7"
|
|
13
|
+
|
|
14
|
+
# Pin to < 2.8: ruby_wasm 2.8+ adds ac_cv_func_chmod=no which causes Ruby
|
|
15
|
+
# to generate its own chmod stub (returns ENOSYS) that shadows wasi-libc's
|
|
16
|
+
# stub (returns ENOTSUP). FileUtils rescues ENOTSUP but not ENOSYS, so
|
|
17
|
+
# `rails new` crashes. Without the flag, wasi-libc's ENOTSUP stub is used
|
|
18
|
+
# and FileUtils handles it — matching the monolithic approach.
|
|
19
|
+
gem "ruby_wasm", "2.7.1", group: :development
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
GEM
|
|
2
|
+
remote: https://rubygems.org/
|
|
3
|
+
specs:
|
|
4
|
+
base64 (0.3.0)
|
|
5
|
+
bcrypt (3.1.22)
|
|
6
|
+
js (2.7.1)
|
|
7
|
+
ruby_wasm (2.7.1)
|
|
8
|
+
ruby_wasm (2.7.1-aarch64-linux)
|
|
9
|
+
ruby_wasm (2.7.1-aarch64-linux-musl)
|
|
10
|
+
ruby_wasm (2.7.1-arm64-darwin)
|
|
11
|
+
ruby_wasm (2.7.1-x86_64-darwin)
|
|
12
|
+
ruby_wasm (2.7.1-x86_64-linux)
|
|
13
|
+
ruby_wasm (2.7.1-x86_64-linux-musl)
|
|
14
|
+
websocket-driver (0.8.0)
|
|
15
|
+
base64
|
|
16
|
+
websocket-extensions (>= 0.1.0)
|
|
17
|
+
websocket-extensions (0.1.5)
|
|
18
|
+
|
|
19
|
+
PLATFORMS
|
|
20
|
+
aarch64-linux
|
|
21
|
+
aarch64-linux-musl
|
|
22
|
+
arm64-darwin
|
|
23
|
+
ruby
|
|
24
|
+
wasm32-wasi
|
|
25
|
+
x86_64-darwin
|
|
26
|
+
x86_64-linux
|
|
27
|
+
x86_64-linux-musl
|
|
28
|
+
|
|
29
|
+
DEPENDENCIES
|
|
30
|
+
bcrypt (~> 3.1.7)
|
|
31
|
+
js (= 2.7.1)
|
|
32
|
+
ruby_wasm (= 2.7.1)
|
|
33
|
+
websocket-driver (~> 0.7)
|
|
34
|
+
|
|
35
|
+
CHECKSUMS
|
|
36
|
+
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
37
|
+
bcrypt (3.1.22) sha256=1f0072e88c2d705d94aff7f2c5cb02eb3f1ec4b8368671e19112527489f29032
|
|
38
|
+
js (2.7.1) sha256=a609ea04ae96f67f2005f339f2400f3a6e52002cadaac59e7718bc63d13d45f0
|
|
39
|
+
ruby_wasm (2.7.1) sha256=b3c9c6cfab73c3c47924cde88c552487fb2ff15d69f9dff3a812372ea4af1c33
|
|
40
|
+
ruby_wasm (2.7.1-aarch64-linux) sha256=e4c7984effc226308d245237205a607f88956b4e6cda08c911e411a16ea45b27
|
|
41
|
+
ruby_wasm (2.7.1-aarch64-linux-musl) sha256=46123cfcc66db9f18c39c640edc26578ce441311dd44051d2f76d4eab289d561
|
|
42
|
+
ruby_wasm (2.7.1-arm64-darwin) sha256=f46a720d6375ad246c8a19da47dc15aad3ae51e01f893a84b02890d75aac9ce0
|
|
43
|
+
ruby_wasm (2.7.1-x86_64-darwin) sha256=d41b5e18aa6f5da8aee736e5373305695b6e15db0874d4e3443837c635d7d0e8
|
|
44
|
+
ruby_wasm (2.7.1-x86_64-linux) sha256=22390b51ae5eb437eb50aff773c89b75cca53188c9bb641265eb128406598d22
|
|
45
|
+
ruby_wasm (2.7.1-x86_64-linux-musl) sha256=245e75ee1ce23340c2f7a75916a8ddf03ff37fbed1360f23bb19270d38b412fe
|
|
46
|
+
websocket-driver (0.8.0) sha256=ed0dba4b943c22f17f9a734817e808bc84cdce6a7e22045f5315aa57676d4962
|
|
47
|
+
websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241
|
|
48
|
+
|
|
49
|
+
BUNDLED WITH
|
|
50
|
+
4.0.7
|
|
@@ -85,6 +85,7 @@ GEM
|
|
|
85
85
|
erubi (1.13.1)
|
|
86
86
|
et-orbi (1.2.11)
|
|
87
87
|
tzinfo
|
|
88
|
+
ffi (1.17.1)
|
|
88
89
|
ffi (1.17.1-aarch64-linux-gnu)
|
|
89
90
|
ffi (1.17.1-aarch64-linux-musl)
|
|
90
91
|
ffi (1.17.1-arm-linux-gnu)
|
|
@@ -116,6 +117,7 @@ GEM
|
|
|
116
117
|
actionview (>= 5.0.0)
|
|
117
118
|
activesupport (>= 5.0.0)
|
|
118
119
|
js (2.7.1)
|
|
120
|
+
json (2.7.6)
|
|
119
121
|
logger (1.7.0)
|
|
120
122
|
loofah (2.24.0)
|
|
121
123
|
crass (~> 1.0.2)
|
|
@@ -128,6 +130,7 @@ GEM
|
|
|
128
130
|
marcel (1.0.4)
|
|
129
131
|
mini_magick (4.13.2)
|
|
130
132
|
mini_mime (1.1.5)
|
|
133
|
+
mini_portile2 (2.8.9)
|
|
131
134
|
minitest (5.25.5)
|
|
132
135
|
net-imap (0.5.8)
|
|
133
136
|
date
|
|
@@ -139,6 +142,9 @@ GEM
|
|
|
139
142
|
net-smtp (0.5.1)
|
|
140
143
|
net-protocol
|
|
141
144
|
nio4r (2.7.4)
|
|
145
|
+
nokogiri (1.18.8)
|
|
146
|
+
mini_portile2 (~> 2.8.2)
|
|
147
|
+
racc (~> 1.4)
|
|
142
148
|
nokogiri (1.18.8-aarch64-linux-gnu)
|
|
143
149
|
racc (~> 1.4)
|
|
144
150
|
nokogiri (1.18.8-aarch64-linux-musl)
|
|
@@ -268,6 +274,7 @@ PLATFORMS
|
|
|
268
274
|
arm-linux-gnu
|
|
269
275
|
arm-linux-musl
|
|
270
276
|
arm64-darwin
|
|
277
|
+
wasm32-wasi
|
|
271
278
|
x86_64-darwin
|
|
272
279
|
x86_64-linux
|
|
273
280
|
x86_64-linux-gnu
|
|
@@ -278,6 +285,7 @@ DEPENDENCIES
|
|
|
278
285
|
image_processing (~> 1.2)
|
|
279
286
|
importmap-rails
|
|
280
287
|
jbuilder
|
|
288
|
+
json (< 2.8)
|
|
281
289
|
propshaft
|
|
282
290
|
rails (~> 8.0.0)
|
|
283
291
|
solid_cable
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Cross-compile gems for wasm32-wasi and pack them into a Ruby WASM base
|
|
5
|
+
# binary via wasi-vfs (single-pack strategy).
|
|
6
|
+
#
|
|
7
|
+
# This replaces the monolithic wasmify-rails build with a faster approach:
|
|
8
|
+
# a raw pre-VFS base binary (Ruby + statically-linked C extensions) gets
|
|
9
|
+
# all gems + stdlib + config packed in a single wasi-vfs pass (~30 seconds).
|
|
10
|
+
#
|
|
11
|
+
# The base binary has NO embedded VFS data. This script does the only
|
|
12
|
+
# wasi-vfs pack, mapping three directories: /bundle (gems), /usr (stdlib),
|
|
13
|
+
# /rails-vm (boot config). Uses RubyWasmExt::WasiVfs (bundled in ruby_wasm
|
|
14
|
+
# gem) to ensure version parity with the monolithic pipeline.
|
|
15
|
+
#
|
|
16
|
+
# Usage: bundle exec ruby bin/pack-gems
|
|
17
|
+
# (called from bin/build-pack orchestrator)
|
|
18
|
+
|
|
19
|
+
require "bundler/setup"
|
|
20
|
+
require "fileutils"
|
|
21
|
+
require "find"
|
|
22
|
+
|
|
23
|
+
# Load RubyWasmExt::WasiVfs — the bundled wasi-vfs implementation from the
|
|
24
|
+
# ruby_wasm gem. This ensures version parity with the monolithic pipeline
|
|
25
|
+
# (wasi-vfs 0.5.3-p1 + Wizer 6.0.0) instead of using the Homebrew CLI
|
|
26
|
+
# (wasi-vfs 0.5.5 + Wizer 7.0.0) which produces different binary layouts.
|
|
27
|
+
require "ruby_wasm"
|
|
28
|
+
begin
|
|
29
|
+
RUBY_VERSION =~ /(\d+\.\d+)/
|
|
30
|
+
require "ruby_wasm/#{$1}/ruby_wasm.so"
|
|
31
|
+
rescue LoadError
|
|
32
|
+
require "ruby_wasm/ruby_wasm.so"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# --- Configuration -----------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
SCRIPT_DIR = File.expand_path("..", __dir__)
|
|
38
|
+
TMP_DIR = File.join(SCRIPT_DIR, "tmp", "gems_bundle")
|
|
39
|
+
BUNDLE_DIR = File.join(TMP_DIR, "bundle")
|
|
40
|
+
RAILS_VM_DIR = File.join(TMP_DIR, "rails-vm")
|
|
41
|
+
OUTPUT_DIR = File.join(SCRIPT_DIR, "dist")
|
|
42
|
+
OUTPUT_FILE = File.join(OUTPUT_DIR, "ruby.wasm")
|
|
43
|
+
|
|
44
|
+
# Gems whose cross-compiled native extensions don't work in WASM.
|
|
45
|
+
# We strip their .so files so Ruby falls back to the stdlib versions
|
|
46
|
+
# compiled into the base WASM binary. We keep gem.build_complete markers
|
|
47
|
+
# so Bundler considers them installed.
|
|
48
|
+
STRIP_EXTENSION_GEMS = %w[nio4r io-console psych bigdecimal date].freeze
|
|
49
|
+
|
|
50
|
+
# Build-time gems that should not be packed into the WASM VFS.
|
|
51
|
+
# ruby_wasm: provides rbwasm CLI + 23MB native extension for host machine
|
|
52
|
+
# ffi: dependency of ruby_wasm, not WASM-compatible
|
|
53
|
+
# NOTE: wasmify-rails MUST stay — boot.rb requires it at runtime for WASM shims
|
|
54
|
+
EXCLUDE_FROM_PACK = %w[ruby_wasm ffi].freeze
|
|
55
|
+
|
|
56
|
+
# Paths to strip from the bundle to reduce size
|
|
57
|
+
STRIP_PATTERNS = %w[
|
|
58
|
+
**/cache/*.gem
|
|
59
|
+
**/cache/bundler
|
|
60
|
+
**/*.o
|
|
61
|
+
**/*.a
|
|
62
|
+
].freeze
|
|
63
|
+
|
|
64
|
+
# --- Helpers -----------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
def human_size(bytes)
|
|
67
|
+
if bytes < 1024
|
|
68
|
+
"#{bytes} bytes"
|
|
69
|
+
elsif bytes < 1024 * 1024
|
|
70
|
+
"#{(bytes.to_f / 1024).round(1)} KB"
|
|
71
|
+
else
|
|
72
|
+
"#{(bytes.to_f / 1024 / 1024).round(1)} MB"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def find_rbconfig
|
|
77
|
+
# 1. Shipped rbconfig in this repo (checked-in)
|
|
78
|
+
shipped = File.join(SCRIPT_DIR, "rbconfig", "wasm32-wasi", "rbconfig.rb")
|
|
79
|
+
return shipped if File.exist?(shipped)
|
|
80
|
+
|
|
81
|
+
# 2. From @tutorialkit-rb/ruby-3.3 npm package
|
|
82
|
+
npm_pkg = File.join(SCRIPT_DIR, "node_modules", "@tutorialkit-rb", "ruby-3.3",
|
|
83
|
+
"rbconfig", "wasm32-wasi", "rbconfig.rb")
|
|
84
|
+
return npm_pkg if File.exist?(npm_pkg)
|
|
85
|
+
|
|
86
|
+
# 3. Fallback: generated by a previous base binary build
|
|
87
|
+
Dir.glob(File.join(SCRIPT_DIR, "rubies", "*/usr/local/lib/ruby/*/wasm32-wasi/rbconfig.rb")).first
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def find_base_wasm
|
|
91
|
+
# 1. Locally-built custom binary (from bin/build-ruby-base)
|
|
92
|
+
local = File.join(SCRIPT_DIR, "dist", "ruby-base.wasm")
|
|
93
|
+
return local if File.exist?(local)
|
|
94
|
+
|
|
95
|
+
# 2. From @tutorialkit-rb/ruby-3.3 npm package
|
|
96
|
+
npm_pkg = File.join(SCRIPT_DIR, "node_modules", "@tutorialkit-rb", "ruby-3.3",
|
|
97
|
+
"dist", "ruby-base.wasm")
|
|
98
|
+
return npm_pkg if File.exist?(npm_pkg)
|
|
99
|
+
|
|
100
|
+
$stderr.puts "Error: No base WASM binary found."
|
|
101
|
+
$stderr.puts "Either run 'bin/build-ruby-base' to build a custom binary,"
|
|
102
|
+
$stderr.puts "or install @tutorialkit-rb/ruby-3.3 npm package."
|
|
103
|
+
exit 1
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def find_usr_dir
|
|
107
|
+
# 1. Path recorded by build-ruby-base (local build, single-pack strategy)
|
|
108
|
+
txt = File.join(SCRIPT_DIR, "dist", "usr-dir.txt")
|
|
109
|
+
if File.exist?(txt)
|
|
110
|
+
path = File.read(txt).strip
|
|
111
|
+
return path if Dir.exist?(path)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# 2. From @tutorialkit-rb/ruby-3.3 npm package
|
|
115
|
+
npm_pkg = File.join(SCRIPT_DIR, "node_modules", "@tutorialkit-rb", "ruby-3.3",
|
|
116
|
+
"rubies", "usr")
|
|
117
|
+
return npm_pkg if Dir.exist?(npm_pkg)
|
|
118
|
+
|
|
119
|
+
# 3. Fallback: glob for the rubies/ directory left by rbwasm
|
|
120
|
+
candidates = Dir.glob(File.join(SCRIPT_DIR, "rubies", "ruby-3.3-wasm32-unknown-wasip1-full-*", "usr"))
|
|
121
|
+
return candidates.first if candidates.any?
|
|
122
|
+
|
|
123
|
+
nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# --- Main --------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
puts "=== Cross-compiling gems + wasi-vfs pack ==="
|
|
129
|
+
puts
|
|
130
|
+
|
|
131
|
+
# 1. Find rbconfig
|
|
132
|
+
rbconfig_path = find_rbconfig
|
|
133
|
+
unless rbconfig_path
|
|
134
|
+
$stderr.puts "Error: Could not find rbconfig.rb for cross-compilation"
|
|
135
|
+
$stderr.puts "Expected at: rbconfig/wasm32-wasi/rbconfig.rb"
|
|
136
|
+
$stderr.puts "Or install @tutorialkit-rb/ruby-3.3 npm package."
|
|
137
|
+
exit 1
|
|
138
|
+
end
|
|
139
|
+
puts "rbconfig: #{rbconfig_path}"
|
|
140
|
+
|
|
141
|
+
# 2. Find base WASM binary
|
|
142
|
+
base_wasm = find_base_wasm
|
|
143
|
+
puts "base WASM: #{base_wasm} (#{human_size(File.size(base_wasm))})"
|
|
144
|
+
|
|
145
|
+
# 2b. Find usr/ directory for single-pack /usr mapping
|
|
146
|
+
usr_dir = find_usr_dir
|
|
147
|
+
if usr_dir
|
|
148
|
+
puts "usr dir: #{usr_dir} (will map to /usr in wasi-vfs pack)"
|
|
149
|
+
else
|
|
150
|
+
$stderr.puts "Warning: Could not find usr/ directory for /usr mapping."
|
|
151
|
+
$stderr.puts " The packed binary may be missing Ruby stdlib at /usr."
|
|
152
|
+
$stderr.puts " Run 'bin/build-ruby-base' or install @tutorialkit-rb/ruby-3.3."
|
|
153
|
+
end
|
|
154
|
+
puts
|
|
155
|
+
|
|
156
|
+
# 3. Add wasm32-wasi platform to lockfile if missing
|
|
157
|
+
Bundler.with_unbundled_env do
|
|
158
|
+
lockfile = File.join(SCRIPT_DIR, "Gemfile.lock")
|
|
159
|
+
if File.exist?(lockfile) && !File.read(lockfile).include?("wasm32-wasi")
|
|
160
|
+
puts "Adding wasm32-wasi platform to Gemfile.lock..."
|
|
161
|
+
system("bundle", "lock", "--add-platform", "wasm32-wasi", chdir: SCRIPT_DIR) or abort "Failed to add platform"
|
|
162
|
+
puts
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# 4. Cross-compile gems into Bundler layout
|
|
167
|
+
puts "Cross-compiling gems..."
|
|
168
|
+
FileUtils.rm_rf(TMP_DIR)
|
|
169
|
+
FileUtils.mkdir_p(TMP_DIR)
|
|
170
|
+
|
|
171
|
+
# Use a temp config dir so .bundle/config (which sets BUNDLE_PATH=vendor/bundle)
|
|
172
|
+
# doesn't override our BUNDLE_PATH for cross-compilation.
|
|
173
|
+
xcompile_config = File.join(TMP_DIR, ".bundle")
|
|
174
|
+
FileUtils.mkdir_p(xcompile_config)
|
|
175
|
+
|
|
176
|
+
env = {
|
|
177
|
+
"BUNDLE_APP_CONFIG" => xcompile_config,
|
|
178
|
+
"BUNDLE_PATH" => BUNDLE_DIR,
|
|
179
|
+
"BUNDLE_FROZEN" => "true",
|
|
180
|
+
"BUNDLE_WITHOUT" => "development:test",
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
cmd = ["bundle", "install", "--target-rbconfig", rbconfig_path]
|
|
184
|
+
|
|
185
|
+
Bundler.with_unbundled_env do
|
|
186
|
+
env.each { |k, v| ENV[k] = v }
|
|
187
|
+
success = system(*cmd, chdir: SCRIPT_DIR)
|
|
188
|
+
unless success
|
|
189
|
+
$stderr.puts "Bundle install (cross-compile) failed"
|
|
190
|
+
exit 1
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# 5. Strip broken native extensions
|
|
195
|
+
puts
|
|
196
|
+
puts "Stripping incompatible native extensions..."
|
|
197
|
+
|
|
198
|
+
ext_dir = Dir.glob(File.join(BUNDLE_DIR, "ruby/*/extensions")).first
|
|
199
|
+
if ext_dir
|
|
200
|
+
STRIP_EXTENSION_GEMS.each do |gem_name|
|
|
201
|
+
Dir.glob(File.join(ext_dir, "**/#{gem_name}-*")).each do |ext_gem_dir|
|
|
202
|
+
Dir.glob(File.join(ext_gem_dir, "**/*.so")).each { |so| FileUtils.rm(so) }
|
|
203
|
+
puts " Stripped .so: #{gem_name}"
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# 6. Remove build-time gems that shouldn't be packed into the WASM VFS
|
|
209
|
+
puts
|
|
210
|
+
puts "Removing build-time gems..."
|
|
211
|
+
|
|
212
|
+
gems_src_dir = Dir.glob(File.join(BUNDLE_DIR, "ruby/*/gems")).first
|
|
213
|
+
if gems_src_dir
|
|
214
|
+
EXCLUDE_FROM_PACK.each do |gem_name|
|
|
215
|
+
Dir.glob(File.join(gems_src_dir, "#{gem_name}-*")).each do |gem_dir|
|
|
216
|
+
size = `du -sh "#{gem_dir}" 2>/dev/null`.split("\t").first rescue "?"
|
|
217
|
+
FileUtils.rm_rf(gem_dir)
|
|
218
|
+
puts " Removed #{File.basename(gem_dir)} (#{size})"
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# 7. Clean up the bundle to reduce size
|
|
224
|
+
puts
|
|
225
|
+
puts "Cleaning bundle..."
|
|
226
|
+
|
|
227
|
+
STRIP_PATTERNS.each do |pattern|
|
|
228
|
+
Dir.glob(File.join(TMP_DIR, pattern)).each do |path|
|
|
229
|
+
FileUtils.rm_rf(path)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Strip build artifacts from gems
|
|
234
|
+
if gems_src_dir
|
|
235
|
+
Dir.glob(File.join(gems_src_dir, "*")).each do |gem_dir|
|
|
236
|
+
%w[test spec ext ports gumbo-parser patches sig rakelib samples benchmarks].each do |subdir|
|
|
237
|
+
path = File.join(gem_dir, subdir)
|
|
238
|
+
FileUtils.rm_rf(path) if Dir.exist?(path)
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# 8. Reorganize from Bundler layout to rbwasm layout
|
|
244
|
+
#
|
|
245
|
+
# Bundler layout: bundle/ruby/3.3.0/gems/<name>/lib/...
|
|
246
|
+
# rbwasm layout: bundle/gems/<name>/lib/...
|
|
247
|
+
#
|
|
248
|
+
# We move gems out of the ruby/<version> nesting and drop specifications/
|
|
249
|
+
# extensions directories (not needed at runtime with our generated setup.rb).
|
|
250
|
+
puts
|
|
251
|
+
puts "Reorganizing to rbwasm layout..."
|
|
252
|
+
|
|
253
|
+
bundler_gems_dir = gems_src_dir # bundle/ruby/3.3.0/gems
|
|
254
|
+
rbwasm_gems_dir = File.join(BUNDLE_DIR, "gems")
|
|
255
|
+
|
|
256
|
+
if bundler_gems_dir && Dir.exist?(bundler_gems_dir)
|
|
257
|
+
FileUtils.mkdir_p(rbwasm_gems_dir)
|
|
258
|
+
Dir.glob(File.join(bundler_gems_dir, "*")).each do |gem_dir|
|
|
259
|
+
dest = File.join(rbwasm_gems_dir, File.basename(gem_dir))
|
|
260
|
+
FileUtils.mv(gem_dir, dest)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Remove the now-empty ruby/<version> tree
|
|
264
|
+
ruby_dir = File.join(BUNDLE_DIR, "ruby")
|
|
265
|
+
FileUtils.rm_rf(ruby_dir)
|
|
266
|
+
|
|
267
|
+
puts " Moved #{Dir.glob(File.join(rbwasm_gems_dir, '*')).length} gems to bundle/gems/"
|
|
268
|
+
else
|
|
269
|
+
$stderr.puts "Warning: Could not find Bundler gems directory for reorganization"
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# 9. Generate /bundle/setup.rb
|
|
273
|
+
# Each gem gets its lib/ added to $LOAD_PATH. One exception:
|
|
274
|
+
# concurrent-ruby uses lib/concurrent-ruby/ as its require path
|
|
275
|
+
# (the only gem in the Rails dependency tree with this pattern).
|
|
276
|
+
puts
|
|
277
|
+
puts "Generating setup.rb..."
|
|
278
|
+
|
|
279
|
+
# Gems whose require_path is not the standard "lib"
|
|
280
|
+
CUSTOM_REQUIRE_PATHS = {
|
|
281
|
+
"concurrent-ruby" => "lib/concurrent-ruby",
|
|
282
|
+
}.freeze
|
|
283
|
+
|
|
284
|
+
setup_lines = ["# Auto-generated by pack-gems"]
|
|
285
|
+
Dir.glob(File.join(rbwasm_gems_dir, "*")).sort.each do |gem_dir|
|
|
286
|
+
gem_name = File.basename(gem_dir)
|
|
287
|
+
# Extract gem base name (without version) for lookup
|
|
288
|
+
base_name = gem_name.sub(/-\d+(\.\d+)*\z/, "")
|
|
289
|
+
rpath = CUSTOM_REQUIRE_PATHS.fetch(base_name, "lib")
|
|
290
|
+
full_path = File.join(gem_dir, rpath)
|
|
291
|
+
if Dir.exist?(full_path)
|
|
292
|
+
setup_lines << "$:.unshift File.expand_path(\"/bundle/gems/#{gem_name}/#{rpath}\")"
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
setup_rb = File.join(BUNDLE_DIR, "setup.rb")
|
|
297
|
+
File.write(setup_rb, setup_lines.join("\n") + "\n")
|
|
298
|
+
puts " Generated setup.rb with #{setup_lines.length - 1} gem load paths"
|
|
299
|
+
|
|
300
|
+
# 10. Prepare /rails-vm/ files
|
|
301
|
+
puts
|
|
302
|
+
puts "Preparing /rails-vm/ files..."
|
|
303
|
+
FileUtils.mkdir_p(RAILS_VM_DIR)
|
|
304
|
+
|
|
305
|
+
%w[boot.rb Gemfile Gemfile.lock .railsrc].each do |file|
|
|
306
|
+
src = File.join(SCRIPT_DIR, file)
|
|
307
|
+
if File.exist?(src)
|
|
308
|
+
FileUtils.cp(src, RAILS_VM_DIR)
|
|
309
|
+
puts " Copied #{file} → rails-vm/"
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# 11. Stage usr/ directory (strip non-runtime files to reduce binary size)
|
|
314
|
+
#
|
|
315
|
+
# The rubies/ directory has the full install tree including bin/ruby (32MB),
|
|
316
|
+
# include/ (headers), and libruby-static.a — none of which are needed at
|
|
317
|
+
# runtime. We create a staging copy with only the runtime lib/ files,
|
|
318
|
+
# matching what rbwasm's remove_non_runtime_files() does.
|
|
319
|
+
usr_staging_dir = nil
|
|
320
|
+
if usr_dir
|
|
321
|
+
puts
|
|
322
|
+
puts "Staging usr/ directory (stripping non-runtime files)..."
|
|
323
|
+
usr_staging_dir = File.join(TMP_DIR, "usr_staging", "usr")
|
|
324
|
+
FileUtils.rm_rf(File.join(TMP_DIR, "usr_staging"))
|
|
325
|
+
FileUtils.mkdir_p(usr_staging_dir)
|
|
326
|
+
|
|
327
|
+
# Copy only local/lib/ and local/share/ (runtime files)
|
|
328
|
+
%w[local/lib local/share].each do |subdir|
|
|
329
|
+
src = File.join(usr_dir, subdir)
|
|
330
|
+
if Dir.exist?(src)
|
|
331
|
+
dest = File.join(usr_staging_dir, subdir)
|
|
332
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
|
333
|
+
FileUtils.cp_r(src, dest)
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Remove libruby-static.a (not needed at runtime)
|
|
338
|
+
Dir.glob(File.join(usr_staging_dir, "**/libruby-static.a")).each do |f|
|
|
339
|
+
FileUtils.rm(f)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
staged_size = `du -sh "#{usr_staging_dir}" 2>/dev/null`.split("\t").first rescue "?"
|
|
343
|
+
puts " Staged usr/ size: #{staged_size} (stripped bin/, include/, libruby-static.a)"
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# 12. Pack via RubyWasmExt::WasiVfs (bundled in ruby_wasm gem)
|
|
347
|
+
#
|
|
348
|
+
# Uses the same wasi-vfs implementation as the monolithic pipeline
|
|
349
|
+
# (RubyWasmExt::WasiVfs from ruby_wasm 2.7.1, which bundles wasi-vfs 0.5.3
|
|
350
|
+
# and Wizer 6.0.0) instead of shelling out to the Homebrew wasi-vfs CLI
|
|
351
|
+
# (v0.5.5 + Wizer 7.0.0). This ensures identical binary layout.
|
|
352
|
+
puts
|
|
353
|
+
puts "Packing with RubyWasmExt::WasiVfs (bundled wasi-vfs)..."
|
|
354
|
+
|
|
355
|
+
FileUtils.mkdir_p(OUTPUT_DIR)
|
|
356
|
+
|
|
357
|
+
wasi_vfs = RubyWasmExt::WasiVfs.new
|
|
358
|
+
wasi_vfs.map_dir("/bundle", BUNDLE_DIR)
|
|
359
|
+
wasi_vfs.map_dir("/rails-vm", RAILS_VM_DIR)
|
|
360
|
+
wasi_vfs.map_dir("/usr", usr_staging_dir) if usr_staging_dir
|
|
361
|
+
|
|
362
|
+
wasm_bytes = wasi_vfs.pack(File.binread(base_wasm))
|
|
363
|
+
File.binwrite(OUTPUT_FILE, wasm_bytes)
|
|
364
|
+
|
|
365
|
+
puts
|
|
366
|
+
puts "=== Pack Complete ==="
|
|
367
|
+
puts " Output: #{OUTPUT_FILE}"
|
|
368
|
+
puts " Size: #{human_size(File.size(OUTPUT_FILE))}"
|