@tutorialkit-rb/cli 1.5.2-rb.0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +15 -0
  3. package/dist/index.js +1384 -0
  4. package/package.json +66 -0
  5. package/template/.gitignore +13 -0
  6. package/template/.vscode/extensions.json +4 -0
  7. package/template/.vscode/launch.json +11 -0
  8. package/template/README.md +179 -0
  9. package/template/astro.config.ts +21 -0
  10. package/template/bin/build-wasm +30 -0
  11. package/template/icons/languages/css.svg +1 -0
  12. package/template/icons/languages/html.svg +1 -0
  13. package/template/icons/languages/js.svg +1 -0
  14. package/template/icons/languages/json.svg +1 -0
  15. package/template/icons/languages/markdown.svg +1 -0
  16. package/template/icons/languages/ruby.svg +1 -0
  17. package/template/icons/languages/sass.svg +1 -0
  18. package/template/icons/languages/ts.svg +1 -0
  19. package/template/icons/phosphor/file-erb.svg +1 -0
  20. package/template/icons/phosphor/file-rb.svg +5 -0
  21. package/template/package.json +37 -0
  22. package/template/package.json.bak +37 -0
  23. package/template/public/favicon.svg +4 -0
  24. package/template/public/logo-dark.svg +4 -0
  25. package/template/public/logo.svg +4 -0
  26. package/template/ruby-wasm/.railsrc +12 -0
  27. package/template/ruby-wasm/Gemfile +22 -0
  28. package/template/ruby-wasm/Gemfile.lock +292 -0
  29. package/template/ruby-wasm/README.md +19 -0
  30. package/template/ruby-wasm/bin/pack +16 -0
  31. package/template/ruby-wasm/boot.rb +32 -0
  32. package/template/ruby-wasm/config/wasmify.yml +23 -0
  33. package/template/src/components/FileManager.tsx +116 -0
  34. package/template/src/components/GitHubLink.astro +17 -0
  35. package/template/src/components/HeadTags.astro +65 -0
  36. package/template/src/components/HelpDropdown.tsx +72 -0
  37. package/template/src/components/RailsPathLinkHandler.tsx +107 -0
  38. package/template/src/components/ShellConfigurator.tsx +95 -0
  39. package/template/src/components/TopBar.astro +48 -0
  40. package/template/src/content/config.ts +9 -0
  41. package/template/src/content/tutorial/1-getting-started/1-creating-your-first-rails-app/_files/workspace/.keep +0 -0
  42. package/template/src/content/tutorial/1-getting-started/1-creating-your-first-rails-app/content.md +34 -0
  43. package/template/src/content/tutorial/1-getting-started/2-rails-console/_files/.tk-config.json +3 -0
  44. package/template/src/content/tutorial/1-getting-started/2-rails-console/_files/workspace/.keep +0 -0
  45. package/template/src/content/tutorial/1-getting-started/2-rails-console/content.md +37 -0
  46. package/template/src/content/tutorial/1-getting-started/meta.md +4 -0
  47. package/template/src/content/tutorial/2-controllers/2-crud-operations/_files/.tk-config.json +3 -0
  48. package/template/src/content/tutorial/2-controllers/2-crud-operations/_files/workspace/.keep +0 -0
  49. package/template/src/content/tutorial/2-controllers/2-crud-operations/content.md +99 -0
  50. package/template/src/content/tutorial/2-controllers/meta.md +4 -0
  51. package/template/src/content/tutorial/meta.md +18 -0
  52. package/template/src/env.d.ts +3 -0
  53. package/template/src/plugins/remarkRailsPathLinks.ts +39 -0
  54. package/template/src/templates/crud-products/.tk-config.json +3 -0
  55. package/template/src/templates/crud-products/workspace/.keep +0 -0
  56. package/template/src/templates/crud-products/workspace/store/app/controllers/products_controller.rb +48 -0
  57. package/template/src/templates/crud-products/workspace/store/app/models/product.rb +3 -0
  58. package/template/src/templates/crud-products/workspace/store/app/views/products/_form.html.erb +10 -0
  59. package/template/src/templates/crud-products/workspace/store/app/views/products/edit.html.erb +4 -0
  60. package/template/src/templates/crud-products/workspace/store/app/views/products/index.html.erb +11 -0
  61. package/template/src/templates/crud-products/workspace/store/app/views/products/new.html.erb +4 -0
  62. package/template/src/templates/crud-products/workspace/store/app/views/products/show.html.erb +5 -0
  63. package/template/src/templates/crud-products/workspace/store/config/routes.rb +6 -0
  64. package/template/src/templates/crud-products/workspace/store/db/migrate/20250521010850_create_products.rb +9 -0
  65. package/template/src/templates/crud-products/workspace/store/db/schema.rb +22 -0
  66. package/template/src/templates/crud-products/workspace/store/db/seeds.rb +3 -0
  67. package/template/src/templates/crud-products/workspace/store/test/fixtures/products.yml +7 -0
  68. package/template/src/templates/crud-products/workspace/store/test/models/product_test.rb +7 -0
  69. package/template/src/templates/default/bin/console +9 -0
  70. package/template/src/templates/default/bin/rackup +11 -0
  71. package/template/src/templates/default/bin/rails +41 -0
  72. package/template/src/templates/default/bin/ruby +37 -0
  73. package/template/src/templates/default/lib/commands.js +39 -0
  74. package/template/src/templates/default/lib/database.js +46 -0
  75. package/template/src/templates/default/lib/irb.js +110 -0
  76. package/template/src/templates/default/lib/patches/app_generator.rb +43 -0
  77. package/template/src/templates/default/lib/patches/authentication.rb +24 -0
  78. package/template/src/templates/default/lib/rails.js +69 -0
  79. package/template/src/templates/default/lib/server/frame_location_middleware.js +77 -0
  80. package/template/src/templates/default/lib/server.js +307 -0
  81. package/template/src/templates/default/package-lock.json +1830 -0
  82. package/template/src/templates/default/package.json +23 -0
  83. package/template/src/templates/default/pgdata/.keep +0 -0
  84. package/template/src/templates/default/scripts/createdb.js +7 -0
  85. package/template/src/templates/default/scripts/rails.js +52 -0
  86. package/template/src/templates/default/scripts/wait-for-wasm.js +103 -0
  87. package/template/src/templates/default/workspace/.keep +0 -0
  88. package/template/src/templates/rails-app/workspace/store/.ruby-version +1 -0
  89. package/template/src/templates/rails-app/workspace/store/Gemfile +37 -0
  90. package/template/src/templates/rails-app/workspace/store/README.md +24 -0
  91. package/template/src/templates/rails-app/workspace/store/Rakefile +6 -0
  92. package/template/src/templates/rails-app/workspace/store/app/assets/images/.keep +0 -0
  93. package/template/src/templates/rails-app/workspace/store/app/assets/stylesheets/application.css +10 -0
  94. package/template/src/templates/rails-app/workspace/store/app/controllers/application_controller.rb +4 -0
  95. package/template/src/templates/rails-app/workspace/store/app/helpers/application_helper.rb +2 -0
  96. package/template/src/templates/rails-app/workspace/store/app/javascript/application.js +4 -0
  97. package/template/src/templates/rails-app/workspace/store/app/javascript/controllers/application.js +9 -0
  98. package/template/src/templates/rails-app/workspace/store/app/javascript/controllers/index.js +4 -0
  99. package/template/src/templates/rails-app/workspace/store/app/jobs/application_job.rb +7 -0
  100. package/template/src/templates/rails-app/workspace/store/app/mailers/application_mailer.rb +4 -0
  101. package/template/src/templates/rails-app/workspace/store/app/models/application_record.rb +3 -0
  102. package/template/src/templates/rails-app/workspace/store/app/models/concerns/.keep +0 -0
  103. package/template/src/templates/rails-app/workspace/store/app/views/layouts/application.html.erb +28 -0
  104. package/template/src/templates/rails-app/workspace/store/app/views/layouts/mailer.html.erb +13 -0
  105. package/template/src/templates/rails-app/workspace/store/app/views/layouts/mailer.text.erb +1 -0
  106. package/template/src/templates/rails-app/workspace/store/app/views/pwa/manifest.json.erb +22 -0
  107. package/template/src/templates/rails-app/workspace/store/app/views/pwa/service-worker.js +26 -0
  108. package/template/src/templates/rails-app/workspace/store/bin/importmap +4 -0
  109. package/template/src/templates/rails-app/workspace/store/bin/rails +4 -0
  110. package/template/src/templates/rails-app/workspace/store/config/application.rb +30 -0
  111. package/template/src/templates/rails-app/workspace/store/config/boot.rb +3 -0
  112. package/template/src/templates/rails-app/workspace/store/config/cable.yml +10 -0
  113. package/template/src/templates/rails-app/workspace/store/config/credentials.yml.enc +1 -0
  114. package/template/src/templates/rails-app/workspace/store/config/database.yml +32 -0
  115. package/template/src/templates/rails-app/workspace/store/config/environment.rb +5 -0
  116. package/template/src/templates/rails-app/workspace/store/config/environments/development.rb +69 -0
  117. package/template/src/templates/rails-app/workspace/store/config/environments/production.rb +89 -0
  118. package/template/src/templates/rails-app/workspace/store/config/environments/test.rb +53 -0
  119. package/template/src/templates/rails-app/workspace/store/config/importmap.rb +9 -0
  120. package/template/src/templates/rails-app/workspace/store/config/initializers/assets.rb +7 -0
  121. package/template/src/templates/rails-app/workspace/store/config/initializers/content_security_policy.rb +25 -0
  122. package/template/src/templates/rails-app/workspace/store/config/initializers/filter_parameter_logging.rb +8 -0
  123. package/template/src/templates/rails-app/workspace/store/config/initializers/inflections.rb +16 -0
  124. package/template/src/templates/rails-app/workspace/store/config/locales/en.yml +31 -0
  125. package/template/src/templates/rails-app/workspace/store/config/master.key +1 -0
  126. package/template/src/templates/rails-app/workspace/store/config/puma.rb +41 -0
  127. package/template/src/templates/rails-app/workspace/store/config/routes.rb +4 -0
  128. package/template/src/templates/rails-app/workspace/store/config/storage.yml +34 -0
  129. package/template/src/templates/rails-app/workspace/store/config.ru +6 -0
  130. package/template/src/templates/rails-app/workspace/store/db/seeds.rb +9 -0
  131. package/template/src/templates/rails-app/workspace/store/log/.keep +0 -0
  132. package/template/src/templates/rails-app/workspace/store/public/400.html +114 -0
  133. package/template/src/templates/rails-app/workspace/store/public/404.html +114 -0
  134. package/template/src/templates/rails-app/workspace/store/public/406-unsupported-browser.html +114 -0
  135. package/template/src/templates/rails-app/workspace/store/public/422.html +114 -0
  136. package/template/src/templates/rails-app/workspace/store/public/500.html +114 -0
  137. package/template/src/templates/rails-app/workspace/store/public/icon.png +0 -0
  138. package/template/src/templates/rails-app/workspace/store/public/icon.svg +3 -0
  139. package/template/src/templates/rails-app/workspace/store/public/robots.txt +1 -0
  140. package/template/src/templates/rails-app/workspace/store/script/.keep +0 -0
  141. package/template/src/templates/rails-app/workspace/store/storage/.keep +0 -0
  142. package/template/src/templates/rails-app/workspace/store/test/controllers/.keep +0 -0
  143. package/template/src/templates/rails-app/workspace/store/test/helpers/.keep +0 -0
  144. package/template/src/templates/rails-app/workspace/store/test/integration/.keep +0 -0
  145. package/template/src/templates/rails-app/workspace/store/test/test_helper.rb +15 -0
  146. package/template/src/templates/rails-app/workspace/store/tmp/.keep +0 -0
  147. package/template/src/templates/rails-app/workspace/store/tmp/pids/.keep +0 -0
  148. package/template/src/templates/rails-app/workspace/store/tmp/storage/.keep +0 -0
  149. package/template/src/templates/rails-app/workspace/store/vendor/javascripts/.keep +0 -0
  150. package/template/tsconfig.json +16 -0
  151. package/template/uno.config.ts +10 -0
@@ -0,0 +1,307 @@
1
+ import express from 'express';
2
+ import setCookieParser from 'set-cookie-parser';
3
+ import multer from 'multer';
4
+ import createFrameLocationTrackingMiddleware from './server/frame_location_middleware.js';
5
+
6
+ class IncomingRequest {
7
+ // We prepare input outside to avoid async Ruby execution for now
8
+ constructor(request, input = nil) {
9
+ this.request = request;
10
+ this.input = input;
11
+ this._preparedHeaders = undefined;
12
+ }
13
+
14
+ method() {
15
+ return this.request.method;
16
+ }
17
+
18
+ pathWithQuery() {
19
+ const url = this.request.url;
20
+ return url ? url : null;
21
+ }
22
+
23
+ scheme() {
24
+ return this.request.protocol;
25
+ }
26
+
27
+ authority() {
28
+ const host = this.request.headers.host;
29
+ return host ? host : null;
30
+ }
31
+
32
+ headers() {
33
+ if (this._preparedHeaders) return this._preparedHeaders;
34
+
35
+ const req = this.request;
36
+
37
+ this._preparedHeaders = {};
38
+
39
+ for (const key in req.headers) {
40
+ this._preparedHeaders[key] = req.headers[key];
41
+ }
42
+
43
+ return this._preparedHeaders;
44
+ }
45
+
46
+ consume() {
47
+ return this.input;
48
+ }
49
+ }
50
+
51
+ class ResponseOutparam {
52
+ constructor(request, response) {
53
+ this.request = request;
54
+ this.response = response;
55
+ this._resolve = null;
56
+ this.promise = new Promise(resolve => {
57
+ this._resolve = resolve;
58
+ });
59
+ }
60
+
61
+ set(result) {
62
+ this.result = result;
63
+ this._resolve();
64
+ }
65
+
66
+ async finish() {
67
+ const result = this.result;
68
+ const res = this.response;
69
+
70
+ if (result.call("tag").toJS() === "ok") {
71
+ const response = result.call("value");
72
+ const headers = response.call("headers").toJS();
73
+
74
+ Object.entries(headers).forEach(([key, value]) => {
75
+ res.set(key, value);
76
+ });
77
+
78
+ if (headers["set-cookie"]) {
79
+ const cookies = setCookieParser.parse(headers["set-cookie"]);
80
+ cookies.forEach(cookie => {
81
+ res.cookie(cookie.name, cookie.value, {
82
+ domain: cookie.domain,
83
+ path: cookie.path,
84
+ expires: cookie.expires,
85
+ sameSite: cookie.sameSite.toLowerCase()
86
+ });
87
+ });
88
+ }
89
+
90
+ if (headers["location"]) {
91
+ const location = headers["location"];
92
+ if (location.startsWith("http://localhost:3000/")) {
93
+ res.set("location", location.replace("http://localhost:3000", ""));
94
+ }
95
+ }
96
+
97
+ let body = response.call("body").toJS();
98
+
99
+ if (headers["content-type"]?.startsWith("image/")) {
100
+ try {
101
+ const buffer = Buffer.from(body, 'base64');
102
+
103
+ if (buffer.length === 0) {
104
+ console.error('Empty buffer after base64 conversion');
105
+ res.status(500).send('Failed to decode image data');
106
+ return;
107
+ }
108
+
109
+ res.status(response.call("status_code").toJS());
110
+ res.type(headers["content-type"]);
111
+ res.send(buffer);
112
+ return;
113
+ } catch(e) {
114
+ console.error(`failed to decode image (${headers["content-type"]}):`, e)
115
+ res.status(500).send(`Express Error: ${e.message}`);
116
+ }
117
+ }
118
+
119
+ res.status(response.call("status_code").toJS());
120
+ res.send(body);
121
+ } else {
122
+ res.status(result.call("error").toJS()).send(`Internal Application Error: ${result.call("value").toJS()}`);
123
+ }
124
+ }
125
+ }
126
+
127
+ // We convert files from forms into data URIs and handle them via Rack DataUriUploads middleware.
128
+ const DATA_URI_UPLOAD_PREFIX = "BbC14y";
129
+
130
+ const fileToDataURI = async (file, mimetype) => {
131
+ const base64 = file.toString('base64');
132
+ const mimeType = mimetype || 'application/octet-stream';
133
+ return `data:${mimeType};base64,${base64}`;
134
+ };
135
+
136
+ const flattenObject = (obj, prefix = '') => {
137
+ const params = {};
138
+
139
+ for (const [key, value] of Object.entries(obj)) {
140
+ const paramKey = prefix ? `${prefix}[${key}]` : key;
141
+
142
+ if (value === null || value === undefined) {
143
+ // ignore
144
+ } else if (typeof value === 'object' && !Array.isArray(value)) {
145
+ const nestedParams = flattenObject(value, paramKey);
146
+ Object.entries(nestedParams).forEach(([k, v]) => params[k] = v);
147
+ } else if (Array.isArray(value)) {
148
+ value.forEach((item, index) => {
149
+ if (typeof item === 'object' && item !== null) {
150
+ const nestedParams = flattenObject(item, `${paramKey}[${index}]`);
151
+ Object.entries(nestedParams).forEach(([k, v]) => params[k] = v);
152
+ } else {
153
+ params[`${paramKey}[]`] = item.toString();
154
+ }
155
+ });
156
+ } else {
157
+ params[paramKey] = value.toString();
158
+ }
159
+ }
160
+
161
+ return params;
162
+ };
163
+
164
+ const prepareInput = async (req) => {
165
+ let input = null;
166
+
167
+ if (
168
+ req.method === "POST" ||
169
+ req.method === "PUT" ||
170
+ req.method === "PATCH"
171
+ ) {
172
+ const contentType = req.get("content-type");
173
+
174
+ if (contentType?.includes("multipart/form-data")) {
175
+ const formData = flattenObject({ ...req.body });
176
+
177
+ if (req.files && req.files.length > 0) {
178
+ await Promise.all(
179
+ req.files.map(async (file) => {
180
+ try {
181
+ const dataURI = await fileToDataURI(file.buffer, file.mimetype);
182
+ formData[file.fieldname] = DATA_URI_UPLOAD_PREFIX + dataURI;
183
+ } catch (e) {
184
+ console.warn(
185
+ `Failed to convert file into data URI: ${e.message}. Ignoring file form input ${file.fieldname}`,
186
+ );
187
+ }
188
+ })
189
+ );
190
+ }
191
+
192
+ const params = new URLSearchParams(formData);
193
+ input = params.toString();
194
+ } else {
195
+ let body = '';
196
+ req.on('data', chunk => {
197
+ body += chunk.toString();
198
+ });
199
+ await new Promise(resolve => req.on('end', resolve));
200
+ input = body;
201
+ }
202
+ }
203
+
204
+ return input;
205
+ }
206
+
207
+ export class RequestQueue {
208
+ constructor(handler){
209
+ this._handler = handler;
210
+ this.isProcessing = false;
211
+ this.queue = [];
212
+ }
213
+
214
+ async respond(req, res) {
215
+ if (this.isProcessing) {
216
+ return new Promise((resolve) => {
217
+ this.queue.push({ req, res, resolve });
218
+ });
219
+ }
220
+ await this.process(req, res);
221
+ queueMicrotask(() => this.tick());
222
+ }
223
+
224
+ async process(req, res) {
225
+ this.isProcessing = true;
226
+ try {
227
+ await this._handler(req, res);
228
+ } catch (e) {
229
+ console.error(e);
230
+ res.status(500).send(`Application Error: ${e.message}`);
231
+ } finally {
232
+ this.isProcessing = false;
233
+ }
234
+ }
235
+
236
+ async tick() {
237
+ if (this.queue.length === 0) {
238
+ return;
239
+ }
240
+ const { req, res, resolve } = this.queue.shift();
241
+ await this.process(req, res);
242
+ resolve();
243
+ queueMicrotask(() => this.tick());
244
+ }
245
+ }
246
+
247
+ let counter = 0;
248
+
249
+ const requestHandler = async (vm, req, res) => {
250
+ const input = await prepareInput(req);
251
+ const incomingRequest = new IncomingRequest(req, input);
252
+ const responseOut = new ResponseOutparam(req, res);
253
+
254
+ const requestId = `req-${counter++}`
255
+ const responseId = `res-${counter}`
256
+
257
+ global[requestId] = incomingRequest;
258
+ global[responseId] = responseOut;
259
+
260
+ const command = `
261
+ $incoming_handler.handle(
262
+ Rack::WASI::IncomingRequest.new("${requestId}"),
263
+ Rack::WASI::ResponseOutparam.new("${responseId}")
264
+ )
265
+ `
266
+
267
+ try {
268
+ await vm.evalAsync(command);
269
+ await responseOut.promise;
270
+ await responseOut.finish();
271
+ } catch (e) {
272
+ res.status(500).send(`Unexpected Error: ${e.message.slice(0, 100)}`);
273
+ } finally {
274
+ delete global[requestId];
275
+ delete global[responseId];
276
+ }
277
+ }
278
+
279
+ export const createRackServer = async (vm, opts = {}) => {
280
+ const { skipRackup } = opts;
281
+
282
+ if (!skipRackup) {
283
+ // Set up Rack handler (if hasn't been already set up)
284
+ await vm.evalAsync(`
285
+ require "rack/builder"
286
+ require "rack/wasi/incoming_handler"
287
+
288
+ app = Rack::Builder.load_file("./config.ru")
289
+
290
+ $incoming_handler = Rack::WASI::IncomingHandler.new(app)
291
+ `)
292
+ }
293
+
294
+ const app = express();
295
+
296
+ const upload = multer({ storage: multer.memoryStorage() });
297
+ app.use(upload.any());
298
+ app.use(createFrameLocationTrackingMiddleware());
299
+
300
+ const queue = new RequestQueue((req, res) => requestHandler(vm, req, res));
301
+
302
+ app.all('*path', async (req, res) => {
303
+ await queue.respond(req, res)
304
+ });
305
+
306
+ return app;
307
+ }