@tutorialkit-rb/cli 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.
Files changed (150) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +15 -0
  3. package/dist/index.js +1364 -0
  4. package/package.json +61 -0
  5. package/template/.gitignore +14 -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 +40 -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/public/favicon.svg +4 -0
  23. package/template/public/logo-dark.svg +4 -0
  24. package/template/public/logo.svg +4 -0
  25. package/template/ruby-wasm/.railsrc +12 -0
  26. package/template/ruby-wasm/Gemfile +22 -0
  27. package/template/ruby-wasm/Gemfile.lock +292 -0
  28. package/template/ruby-wasm/README.md +19 -0
  29. package/template/ruby-wasm/bin/pack +16 -0
  30. package/template/ruby-wasm/boot.rb +32 -0
  31. package/template/ruby-wasm/config/wasmify.yml +23 -0
  32. package/template/src/components/FileManager.tsx +165 -0
  33. package/template/src/components/GitHubLink.astro +17 -0
  34. package/template/src/components/HeadTags.astro +65 -0
  35. package/template/src/components/HelpDropdown.tsx +72 -0
  36. package/template/src/components/RailsPathLinkHandler.tsx +107 -0
  37. package/template/src/components/ShellConfigurator.tsx +95 -0
  38. package/template/src/components/TopBar.astro +48 -0
  39. package/template/src/content/config.ts +9 -0
  40. package/template/src/content/tutorial/1-getting-started/1-creating-your-first-rails-app/_files/workspace/.keep +0 -0
  41. package/template/src/content/tutorial/1-getting-started/1-creating-your-first-rails-app/content.md +34 -0
  42. package/template/src/content/tutorial/1-getting-started/2-rails-console/_files/.tk-config.json +3 -0
  43. package/template/src/content/tutorial/1-getting-started/2-rails-console/_files/workspace/.keep +0 -0
  44. package/template/src/content/tutorial/1-getting-started/2-rails-console/content.md +37 -0
  45. package/template/src/content/tutorial/1-getting-started/meta.md +4 -0
  46. package/template/src/content/tutorial/2-controllers/2-crud-operations/_files/.tk-config.json +3 -0
  47. package/template/src/content/tutorial/2-controllers/2-crud-operations/_files/workspace/.keep +0 -0
  48. package/template/src/content/tutorial/2-controllers/2-crud-operations/content.md +99 -0
  49. package/template/src/content/tutorial/2-controllers/meta.md +4 -0
  50. package/template/src/content/tutorial/meta.md +18 -0
  51. package/template/src/env.d.ts +3 -0
  52. package/template/src/plugins/remarkRailsPathLinks.ts +39 -0
  53. package/template/src/templates/crud-products/.tk-config.json +3 -0
  54. package/template/src/templates/crud-products/workspace/.keep +0 -0
  55. package/template/src/templates/crud-products/workspace/store/app/controllers/products_controller.rb +48 -0
  56. package/template/src/templates/crud-products/workspace/store/app/models/product.rb +3 -0
  57. package/template/src/templates/crud-products/workspace/store/app/views/products/_form.html.erb +10 -0
  58. package/template/src/templates/crud-products/workspace/store/app/views/products/edit.html.erb +4 -0
  59. package/template/src/templates/crud-products/workspace/store/app/views/products/index.html.erb +11 -0
  60. package/template/src/templates/crud-products/workspace/store/app/views/products/new.html.erb +4 -0
  61. package/template/src/templates/crud-products/workspace/store/app/views/products/show.html.erb +5 -0
  62. package/template/src/templates/crud-products/workspace/store/config/routes.rb +6 -0
  63. package/template/src/templates/crud-products/workspace/store/db/migrate/20250521010850_create_products.rb +9 -0
  64. package/template/src/templates/crud-products/workspace/store/db/schema.rb +22 -0
  65. package/template/src/templates/crud-products/workspace/store/db/seeds.rb +3 -0
  66. package/template/src/templates/crud-products/workspace/store/test/fixtures/products.yml +7 -0
  67. package/template/src/templates/crud-products/workspace/store/test/models/product_test.rb +7 -0
  68. package/template/src/templates/default/bin/console +9 -0
  69. package/template/src/templates/default/bin/rackup +11 -0
  70. package/template/src/templates/default/bin/rails +41 -0
  71. package/template/src/templates/default/bin/ruby +37 -0
  72. package/template/src/templates/default/lib/commands.js +39 -0
  73. package/template/src/templates/default/lib/database.js +46 -0
  74. package/template/src/templates/default/lib/irb.js +110 -0
  75. package/template/src/templates/default/lib/patches/app_generator.rb +43 -0
  76. package/template/src/templates/default/lib/patches/authentication.rb +24 -0
  77. package/template/src/templates/default/lib/rails.js +69 -0
  78. package/template/src/templates/default/lib/server/frame_location_middleware.js +77 -0
  79. package/template/src/templates/default/lib/server.js +307 -0
  80. package/template/src/templates/default/package-lock.json +1830 -0
  81. package/template/src/templates/default/package.json +23 -0
  82. package/template/src/templates/default/pgdata/.keep +0 -0
  83. package/template/src/templates/default/scripts/createdb.js +7 -0
  84. package/template/src/templates/default/scripts/rails.js +52 -0
  85. package/template/src/templates/default/scripts/wait-for-wasm.js +103 -0
  86. package/template/src/templates/default/workspace/.keep +0 -0
  87. package/template/src/templates/rails-app/workspace/store/.ruby-version +1 -0
  88. package/template/src/templates/rails-app/workspace/store/Gemfile +37 -0
  89. package/template/src/templates/rails-app/workspace/store/README.md +24 -0
  90. package/template/src/templates/rails-app/workspace/store/Rakefile +6 -0
  91. package/template/src/templates/rails-app/workspace/store/app/assets/images/.keep +0 -0
  92. package/template/src/templates/rails-app/workspace/store/app/assets/stylesheets/application.css +10 -0
  93. package/template/src/templates/rails-app/workspace/store/app/controllers/application_controller.rb +4 -0
  94. package/template/src/templates/rails-app/workspace/store/app/helpers/application_helper.rb +2 -0
  95. package/template/src/templates/rails-app/workspace/store/app/javascript/application.js +4 -0
  96. package/template/src/templates/rails-app/workspace/store/app/javascript/controllers/application.js +9 -0
  97. package/template/src/templates/rails-app/workspace/store/app/javascript/controllers/index.js +4 -0
  98. package/template/src/templates/rails-app/workspace/store/app/jobs/application_job.rb +7 -0
  99. package/template/src/templates/rails-app/workspace/store/app/mailers/application_mailer.rb +4 -0
  100. package/template/src/templates/rails-app/workspace/store/app/models/application_record.rb +3 -0
  101. package/template/src/templates/rails-app/workspace/store/app/models/concerns/.keep +0 -0
  102. package/template/src/templates/rails-app/workspace/store/app/views/layouts/application.html.erb +28 -0
  103. package/template/src/templates/rails-app/workspace/store/app/views/layouts/mailer.html.erb +13 -0
  104. package/template/src/templates/rails-app/workspace/store/app/views/layouts/mailer.text.erb +1 -0
  105. package/template/src/templates/rails-app/workspace/store/app/views/pwa/manifest.json.erb +22 -0
  106. package/template/src/templates/rails-app/workspace/store/app/views/pwa/service-worker.js +26 -0
  107. package/template/src/templates/rails-app/workspace/store/bin/importmap +4 -0
  108. package/template/src/templates/rails-app/workspace/store/bin/rails +4 -0
  109. package/template/src/templates/rails-app/workspace/store/config/application.rb +30 -0
  110. package/template/src/templates/rails-app/workspace/store/config/boot.rb +3 -0
  111. package/template/src/templates/rails-app/workspace/store/config/cable.yml +10 -0
  112. package/template/src/templates/rails-app/workspace/store/config/credentials.yml.enc +1 -0
  113. package/template/src/templates/rails-app/workspace/store/config/database.yml +32 -0
  114. package/template/src/templates/rails-app/workspace/store/config/environment.rb +5 -0
  115. package/template/src/templates/rails-app/workspace/store/config/environments/development.rb +69 -0
  116. package/template/src/templates/rails-app/workspace/store/config/environments/production.rb +89 -0
  117. package/template/src/templates/rails-app/workspace/store/config/environments/test.rb +53 -0
  118. package/template/src/templates/rails-app/workspace/store/config/importmap.rb +9 -0
  119. package/template/src/templates/rails-app/workspace/store/config/initializers/assets.rb +7 -0
  120. package/template/src/templates/rails-app/workspace/store/config/initializers/content_security_policy.rb +25 -0
  121. package/template/src/templates/rails-app/workspace/store/config/initializers/filter_parameter_logging.rb +8 -0
  122. package/template/src/templates/rails-app/workspace/store/config/initializers/inflections.rb +16 -0
  123. package/template/src/templates/rails-app/workspace/store/config/locales/en.yml +31 -0
  124. package/template/src/templates/rails-app/workspace/store/config/master.key +1 -0
  125. package/template/src/templates/rails-app/workspace/store/config/puma.rb +41 -0
  126. package/template/src/templates/rails-app/workspace/store/config/routes.rb +4 -0
  127. package/template/src/templates/rails-app/workspace/store/config/storage.yml +34 -0
  128. package/template/src/templates/rails-app/workspace/store/config.ru +6 -0
  129. package/template/src/templates/rails-app/workspace/store/db/seeds.rb +9 -0
  130. package/template/src/templates/rails-app/workspace/store/log/.keep +0 -0
  131. package/template/src/templates/rails-app/workspace/store/public/400.html +114 -0
  132. package/template/src/templates/rails-app/workspace/store/public/404.html +114 -0
  133. package/template/src/templates/rails-app/workspace/store/public/406-unsupported-browser.html +114 -0
  134. package/template/src/templates/rails-app/workspace/store/public/422.html +114 -0
  135. package/template/src/templates/rails-app/workspace/store/public/500.html +114 -0
  136. package/template/src/templates/rails-app/workspace/store/public/icon.png +0 -0
  137. package/template/src/templates/rails-app/workspace/store/public/icon.svg +3 -0
  138. package/template/src/templates/rails-app/workspace/store/public/robots.txt +1 -0
  139. package/template/src/templates/rails-app/workspace/store/script/.keep +0 -0
  140. package/template/src/templates/rails-app/workspace/store/storage/.keep +0 -0
  141. package/template/src/templates/rails-app/workspace/store/test/controllers/.keep +0 -0
  142. package/template/src/templates/rails-app/workspace/store/test/helpers/.keep +0 -0
  143. package/template/src/templates/rails-app/workspace/store/test/integration/.keep +0 -0
  144. package/template/src/templates/rails-app/workspace/store/test/test_helper.rb +15 -0
  145. package/template/src/templates/rails-app/workspace/store/tmp/.keep +0 -0
  146. package/template/src/templates/rails-app/workspace/store/tmp/pids/.keep +0 -0
  147. package/template/src/templates/rails-app/workspace/store/tmp/storage/.keep +0 -0
  148. package/template/src/templates/rails-app/workspace/store/vendor/javascripts/.keep +0 -0
  149. package/template/tsconfig.json +16 -0
  150. package/template/uno.config.ts +10 -0
package/dist/index.js ADDED
@@ -0,0 +1,1364 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import chalk9 from "chalk";
5
+ import yargs2 from "yargs-parser";
6
+
7
+ // src/commands/create/index.ts
8
+ import fs7 from "node:fs";
9
+ import path8 from "node:path";
10
+ import * as prompts7 from "@clack/prompts";
11
+ import chalk7 from "chalk";
12
+ import "yargs-parser";
13
+
14
+ // package.json
15
+ var package_default = {
16
+ name: "@tutorialkit-rb/cli",
17
+ version: "0.1.4",
18
+ description: "Interactive tutorials powered by WebContainer API",
19
+ author: "StackBlitz Inc.",
20
+ type: "module",
21
+ bugs: "https://github.com/Bakaface/tutorialkit.rb/issues",
22
+ homepage: "https://github.com/Bakaface/tutorialkit.rb",
23
+ license: "MIT",
24
+ repository: {
25
+ type: "git",
26
+ url: "git+https://github.com/Bakaface/tutorialkit.rb.git",
27
+ directory: "packages/cli"
28
+ },
29
+ bin: {
30
+ tutorialkit: "dist/index.js"
31
+ },
32
+ main: "./dist/index.js",
33
+ exports: {
34
+ ".": "./dist/index.js"
35
+ },
36
+ scripts: {
37
+ build: "node scripts/build.js",
38
+ "build-release": "node scripts/build-release.js",
39
+ test: "vitest --testTimeout=300000"
40
+ },
41
+ files: [
42
+ "dist",
43
+ "template",
44
+ "template/.gitignore"
45
+ ],
46
+ dependencies: {
47
+ "@babel/generator": "7.24.5",
48
+ "@babel/parser": "7.24.5",
49
+ "@babel/traverse": "7.24.5",
50
+ "@babel/types": "7.24.5",
51
+ "@clack/prompts": "^0.7.0",
52
+ chalk: "^5.3.0",
53
+ "detect-indent": "7.0.1",
54
+ execa: "^9.2.0",
55
+ ignore: "^5.3.1",
56
+ lookpath: "^1.2.2",
57
+ "which-pm": "2.2.0",
58
+ "yargs-parser": "^21.1.1"
59
+ },
60
+ devDependencies: {
61
+ "@types/babel__generator": "7.6.8",
62
+ "@types/babel__traverse": "7.20.5",
63
+ "@types/fs-extra": "^11.0.4",
64
+ "@types/node": "^20.14.6",
65
+ "@types/yargs-parser": "^21.0.3",
66
+ esbuild: "^0.20.2",
67
+ "esbuild-node-externals": "^1.13.1",
68
+ "fs-extra": "^11.2.0",
69
+ tempy: "^3.1.0",
70
+ vitest: "^3.0.5"
71
+ },
72
+ engines: {
73
+ node: ">=18.18.0"
74
+ }
75
+ };
76
+
77
+ // src/utils/messages.ts
78
+ import chalk2 from "chalk";
79
+
80
+ // src/utils/colors.ts
81
+ import chalk from "chalk";
82
+ var primaryBlue = chalk.bgHex("#0d6fe8");
83
+
84
+ // src/utils/messages.ts
85
+ function primaryLabel(text2) {
86
+ return primaryBlue(` ${chalk2.whiteBright(text2)} `);
87
+ }
88
+ function errorLabel(text2) {
89
+ return chalk2.bgRed(` ${chalk2.white(text2 ?? "ERROR")} `);
90
+ }
91
+ function warnLabel(text2) {
92
+ return chalk2.bgYellow(` ${chalk2.black(text2 ?? "WARN")} `);
93
+ }
94
+ function printHelp({ commandName, usage, tables, prolog, epilog }) {
95
+ const helpMessage = [];
96
+ let printNewline = false;
97
+ if (prolog) {
98
+ helpMessage.push(prolog);
99
+ printNewline = true;
100
+ }
101
+ if (usage) {
102
+ if (printNewline) {
103
+ helpMessage.push("");
104
+ }
105
+ printNewline = true;
106
+ const _usage = Array.isArray(usage) ? usage : [usage];
107
+ const label = "Usage:";
108
+ const indentation = " ".repeat(label.length + 1);
109
+ helpMessage.push(`${chalk2.bold.underline(label)} ${chalk2.green(commandName)} ${chalk2.bold(_usage[0])}`);
110
+ for (const usageLines of _usage.slice(1)) {
111
+ helpMessage.push(`${indentation}${chalk2.green(commandName)} ${chalk2.bold(usageLines)}`);
112
+ }
113
+ }
114
+ if (tables) {
115
+ let i = 0;
116
+ const tableEntries = Object.entries(tables);
117
+ if (tableEntries.length > 0 && printNewline) {
118
+ helpMessage.push("");
119
+ printNewline = true;
120
+ }
121
+ for (const [sectionTitle, tableRows] of tableEntries) {
122
+ const padding = Object.values(tableRows).reduce((maxLength, table) => {
123
+ const title = table[0];
124
+ if (title.length > maxLength) {
125
+ return title.length;
126
+ }
127
+ return maxLength;
128
+ }, 0);
129
+ helpMessage.push(chalk2.bold.underline(`${sectionTitle}:`));
130
+ for (const row of tableRows) {
131
+ const [command, description] = row;
132
+ helpMessage.push(` ${command.padEnd(padding, " ")} ${chalk2.dim(description)}`);
133
+ }
134
+ if (i++ < tableEntries.length - 1) {
135
+ helpMessage.push("");
136
+ }
137
+ }
138
+ }
139
+ if (epilog) {
140
+ if (printNewline) {
141
+ helpMessage.push("");
142
+ }
143
+ helpMessage.push(epilog);
144
+ }
145
+ console.log(helpMessage.join("\n"));
146
+ }
147
+
148
+ // src/utils/random.ts
149
+ function randomValueFromArray(array) {
150
+ return array[Math.floor(array.length * Math.random())];
151
+ }
152
+
153
+ // src/utils/words.ts
154
+ var adjectives = [
155
+ "aged",
156
+ "ancient",
157
+ "autumn",
158
+ "billowing",
159
+ "bitter",
160
+ "black",
161
+ "blue",
162
+ "bold",
163
+ "broad",
164
+ "broken",
165
+ "calm",
166
+ "cold",
167
+ "cool",
168
+ "crimson",
169
+ "curly",
170
+ "damp",
171
+ "dark",
172
+ "dawn",
173
+ "delicate",
174
+ "divine",
175
+ "dry",
176
+ "empty",
177
+ "falling",
178
+ "fancy",
179
+ "flat",
180
+ "floral",
181
+ "fragrant",
182
+ "frosty",
183
+ "gentle",
184
+ "green",
185
+ "hidden",
186
+ "holy",
187
+ "icy",
188
+ "jolly",
189
+ "late",
190
+ "lingering",
191
+ "little",
192
+ "lively",
193
+ "long",
194
+ "lucky",
195
+ "misty",
196
+ "morning",
197
+ "muddy",
198
+ "mute",
199
+ "nameless",
200
+ "noisy",
201
+ "odd",
202
+ "old",
203
+ "orange",
204
+ "patient",
205
+ "plain",
206
+ "polished",
207
+ "proud",
208
+ "purple",
209
+ "quiet",
210
+ "rapid",
211
+ "raspy",
212
+ "red",
213
+ "restless",
214
+ "rough",
215
+ "round",
216
+ "royal",
217
+ "shiny",
218
+ "shrill",
219
+ "shy",
220
+ "silent",
221
+ "small",
222
+ "snowy",
223
+ "soft",
224
+ "solitary",
225
+ "sparkling",
226
+ "spring",
227
+ "square",
228
+ "steep",
229
+ "still",
230
+ "summer",
231
+ "super",
232
+ "sweet",
233
+ "throbbing",
234
+ "tight",
235
+ "tiny",
236
+ "twilight",
237
+ "wandering",
238
+ "weathered",
239
+ "white",
240
+ "wild",
241
+ "winter",
242
+ "wispy",
243
+ "withered",
244
+ "yellow",
245
+ "young"
246
+ ];
247
+ var nouns = [
248
+ "art",
249
+ "band",
250
+ "bar",
251
+ "base",
252
+ "bird",
253
+ "block",
254
+ "boat",
255
+ "bonus",
256
+ "bread",
257
+ "breeze",
258
+ "brook",
259
+ "bush",
260
+ "butterfly",
261
+ "cake",
262
+ "cell",
263
+ "cherry",
264
+ "cloud",
265
+ "credit",
266
+ "darkness",
267
+ "dawn",
268
+ "dew",
269
+ "disk",
270
+ "dream",
271
+ "dust",
272
+ "feather",
273
+ "field",
274
+ "fire",
275
+ "firefly",
276
+ "flower",
277
+ "fog",
278
+ "forest",
279
+ "frog",
280
+ "frost",
281
+ "glade",
282
+ "glitter",
283
+ "grass",
284
+ "hall",
285
+ "hat",
286
+ "haze",
287
+ "heart",
288
+ "hill",
289
+ "king",
290
+ "lab",
291
+ "lake",
292
+ "leaf",
293
+ "limit",
294
+ "math",
295
+ "meadow",
296
+ "mode",
297
+ "moon",
298
+ "morning",
299
+ "mountain",
300
+ "mouse",
301
+ "mud",
302
+ "night",
303
+ "paper",
304
+ "pine",
305
+ "poetry",
306
+ "pond",
307
+ "queen",
308
+ "rain",
309
+ "recipe",
310
+ "resonance",
311
+ "rice",
312
+ "river",
313
+ "salad",
314
+ "scene",
315
+ "sea",
316
+ "shadow",
317
+ "shape",
318
+ "silence",
319
+ "sky",
320
+ "smoke",
321
+ "snow",
322
+ "snowflake",
323
+ "sound",
324
+ "star",
325
+ "sun",
326
+ "sun",
327
+ "sunset",
328
+ "surf",
329
+ "term",
330
+ "thunder",
331
+ "tooth",
332
+ "tree",
333
+ "truth",
334
+ "union",
335
+ "unit",
336
+ "violet",
337
+ "voice",
338
+ "water",
339
+ "waterfall",
340
+ "wave",
341
+ "wildflower",
342
+ "wind",
343
+ "wood"
344
+ ];
345
+
346
+ // src/utils/project.ts
347
+ function generateProjectName() {
348
+ const adjective = randomValueFromArray(adjectives);
349
+ const noun = randomValueFromArray(nouns);
350
+ return `${adjective}-${noun}`;
351
+ }
352
+
353
+ // src/utils/tasks.ts
354
+ import * as prompts from "@clack/prompts";
355
+ function assertNotCanceled(value, exitCode = 0) {
356
+ if (prompts.isCancel(value)) {
357
+ prompts.cancel("Command aborted");
358
+ console.log("Until next time!");
359
+ process.exit(exitCode);
360
+ }
361
+ }
362
+ async function runTask(task) {
363
+ if (task.disabled === true) {
364
+ return;
365
+ }
366
+ if (task.dryRun) {
367
+ prompts.log.warn(task.dryRunMessage ?? `Skipped '${task.title}'`);
368
+ return;
369
+ }
370
+ const spinner2 = prompts.spinner();
371
+ spinner2.start(task.title);
372
+ try {
373
+ const result = await task.task(spinner2.message);
374
+ spinner2.stop(result || task.title);
375
+ } catch (error) {
376
+ spinner2.stop(`${errorLabel()} ${error.message ?? "Task failed"}`, 1);
377
+ }
378
+ }
379
+
380
+ // src/utils/workspace-version.ts
381
+ function updateWorkspaceVersions(dependencies, version, filterDependency = allowAll) {
382
+ for (const dependency in dependencies) {
383
+ const depVersion = dependencies[dependency];
384
+ if (depVersion === "workspace:*" && filterDependency(dependency)) {
385
+ dependencies[dependency] = version;
386
+ }
387
+ }
388
+ }
389
+ function allowAll() {
390
+ return true;
391
+ }
392
+
393
+ // src/commands/create/enterprise.ts
394
+ import fs2 from "node:fs";
395
+ import path from "node:path";
396
+
397
+ // src/utils/astro-config.ts
398
+ import fs from "node:fs/promises";
399
+
400
+ // src/utils/babel.ts
401
+ import generator from "@babel/generator";
402
+ import parser from "@babel/parser";
403
+ import traverse from "@babel/traverse";
404
+ import * as t from "@babel/types";
405
+ var visit = traverse.default;
406
+ function generate(ast) {
407
+ const astToText = generator.default;
408
+ const { code } = astToText(ast);
409
+ return code;
410
+ }
411
+ var parse = (code) => parser.parse(code, { sourceType: "unambiguous", plugins: ["typescript"] });
412
+
413
+ // src/utils/astro-config.ts
414
+ async function parseAstroConfig(astroConfigPath) {
415
+ const source = await fs.readFile(astroConfigPath, { encoding: "utf-8" });
416
+ const result = parse(source);
417
+ if (!result) {
418
+ throw new Error("Unknown error parsing astro config");
419
+ }
420
+ if (result.errors.length > 0) {
421
+ throw new Error("Error parsing astro config: " + JSON.stringify(result.errors));
422
+ }
423
+ return result;
424
+ }
425
+ function generateAstroConfig(astroConfig) {
426
+ const defaultExport = "export default defineConfig";
427
+ let output = generate(astroConfig);
428
+ output = output.replace(defaultExport, `
429
+ ${defaultExport}`);
430
+ return output;
431
+ }
432
+ function replaceArgs(newTutorialKitArgs, ast) {
433
+ const integrationImport = "@tutorialkit-rb/astro";
434
+ let integrationId;
435
+ visit(ast, {
436
+ ImportDeclaration(path10) {
437
+ if (path10.node.source.value === integrationImport) {
438
+ const defaultImport = path10.node.specifiers.find((specifier) => specifier.type === "ImportDefaultSpecifier");
439
+ if (defaultImport) {
440
+ integrationId = defaultImport.local;
441
+ }
442
+ }
443
+ }
444
+ });
445
+ if (!integrationId) {
446
+ throw new Error(`Could not find import to '${integrationImport}'`);
447
+ }
448
+ visit(ast, {
449
+ ExportDefaultDeclaration(path10) {
450
+ if (!t.isCallExpression(path10.node.declaration)) {
451
+ return;
452
+ }
453
+ const configObject = path10.node.declaration.arguments[0];
454
+ if (!t.isObjectExpression(configObject)) {
455
+ throw new Error("TutorialKit is not part of the exported config");
456
+ }
457
+ const integrationsProp = configObject.properties.find((prop) => {
458
+ if (prop.type !== "ObjectProperty") {
459
+ return false;
460
+ }
461
+ if (prop.key.type === "Identifier") {
462
+ if (prop.key.name === "integrations") {
463
+ return true;
464
+ }
465
+ }
466
+ if (prop.key.type === "StringLiteral") {
467
+ if (prop.key.value === "integrations") {
468
+ return true;
469
+ }
470
+ }
471
+ return false;
472
+ });
473
+ if (integrationsProp.value.type !== "ArrayExpression") {
474
+ throw new Error("Unable to parse integrations in Astro config");
475
+ }
476
+ let integrationCall = integrationsProp.value.elements.find((expr) => {
477
+ return t.isCallExpression(expr) && t.isIdentifier(expr.callee) && expr.callee.name === integrationId.name;
478
+ });
479
+ if (!integrationCall) {
480
+ integrationCall = t.callExpression(integrationId, []);
481
+ integrationsProp.value.elements.push(integrationCall);
482
+ }
483
+ const integrationArgs = integrationCall.arguments;
484
+ if (integrationArgs.length === 0) {
485
+ const objectArgs = fromValue(newTutorialKitArgs);
486
+ if (objectArgs.properties.length > 0) {
487
+ integrationArgs.push(objectArgs);
488
+ }
489
+ return;
490
+ }
491
+ if (!t.isObjectExpression(integrationArgs[0])) {
492
+ throw new Error("Only updating an existing object literal as the config is supported");
493
+ }
494
+ updateObject(newTutorialKitArgs, integrationArgs[0]);
495
+ }
496
+ });
497
+ }
498
+ function updateObject(properties, object) {
499
+ if (typeof properties !== "object") {
500
+ return;
501
+ }
502
+ object ??= t.objectExpression([]);
503
+ for (const property in properties) {
504
+ const propertyInObject = object.properties.find((prop) => {
505
+ return prop.type === "ObjectProperty" && prop.key.type === "Identifier" && prop.key.name === property;
506
+ });
507
+ if (!propertyInObject) {
508
+ object.properties.push(t.objectProperty(t.identifier(property), fromValue(properties[property])));
509
+ } else {
510
+ if (typeof properties[property] === "object" && t.isObjectExpression(propertyInObject.value)) {
511
+ updateObject(properties[property], propertyInObject.value);
512
+ } else {
513
+ propertyInObject.value = fromValue(properties[property]);
514
+ }
515
+ }
516
+ }
517
+ }
518
+ function fromValue(value) {
519
+ if (value == null) {
520
+ return t.nullLiteral();
521
+ }
522
+ if (typeof value === "string") {
523
+ return t.stringLiteral(value);
524
+ }
525
+ if (typeof value === "number") {
526
+ return t.numericLiteral(value);
527
+ }
528
+ if (typeof value === "boolean") {
529
+ return t.booleanLiteral(value);
530
+ }
531
+ if (Array.isArray(value)) {
532
+ return t.arrayExpression(value.map(fromValue));
533
+ }
534
+ return t.objectExpression(
535
+ Object.keys(value).map((key) => t.objectProperty(t.identifier(key), fromValue(value[key])))
536
+ );
537
+ }
538
+
539
+ // src/commands/create/enterprise.ts
540
+ async function setupEnterpriseConfig(dest, flags) {
541
+ if (!flags.defaults && flags.enterprise === void 0) {
542
+ return;
543
+ }
544
+ let editorOrigin = flags.enterprise;
545
+ if (editorOrigin) {
546
+ const error = validateEditorOrigin(editorOrigin);
547
+ if (error) {
548
+ throw error;
549
+ }
550
+ editorOrigin = new URL(editorOrigin).origin;
551
+ }
552
+ const configPath = path.resolve(dest, "astro.config.ts");
553
+ if (!flags.dryRun && editorOrigin) {
554
+ const astroConfig = await parseAstroConfig(configPath);
555
+ replaceArgs(
556
+ {
557
+ enterprise: {
558
+ clientId: "wc_api",
559
+ editorOrigin,
560
+ scope: "turbo"
561
+ }
562
+ },
563
+ astroConfig
564
+ );
565
+ fs2.writeFileSync(configPath, generateAstroConfig(astroConfig));
566
+ }
567
+ }
568
+ function validateEditorOrigin(value) {
569
+ if (!value) {
570
+ return "Please provide an origin!";
571
+ }
572
+ try {
573
+ const url = new URL(value);
574
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
575
+ return "Please provide an origin starting with http:// or https://";
576
+ }
577
+ } catch {
578
+ return "Please provide a valid origin URL!";
579
+ }
580
+ }
581
+
582
+ // src/commands/create/gemfile-editing.ts
583
+ import path3 from "node:path";
584
+ import * as prompts2 from "@clack/prompts";
585
+ import chalk3 from "chalk";
586
+ import { execa } from "execa";
587
+
588
+ // src/commands/create/options.ts
589
+ import path2 from "node:path";
590
+ import { fileURLToPath } from "node:url";
591
+ var __dirname = path2.dirname(fileURLToPath(import.meta.url));
592
+ var templatePath = path2.resolve(__dirname, "../template");
593
+ var DEFAULT_VALUES = {
594
+ git: !process.env.CI,
595
+ install: true,
596
+ start: true,
597
+ dryRun: false,
598
+ force: false,
599
+ packageManager: "npm",
600
+ provider: "skip"
601
+ };
602
+ function readFlag(flags, flag) {
603
+ let value = flags[flag];
604
+ if (flags.defaults) {
605
+ value ??= DEFAULT_VALUES[flag];
606
+ }
607
+ return value;
608
+ }
609
+
610
+ // src/commands/create/gemfile-editing.ts
611
+ async function promptGemfileEditing(dest, flags) {
612
+ if (flags.defaults || flags.dryRun) {
613
+ return false;
614
+ }
615
+ prompts2.log.message(chalk3.bold.underline("Ruby Gems Configuration"));
616
+ prompts2.log.info(`Your Ruby application's gems are configured in ${chalk3.blue("ruby-wasm/Gemfile")}`);
617
+ const answer = await prompts2.confirm({
618
+ message: "Would you like to edit the Gemfile to add additional gems?",
619
+ initialValue: true
620
+ });
621
+ assertNotCanceled(answer);
622
+ if (answer) {
623
+ const gemfilePath = path3.resolve(dest, "ruby-wasm/Gemfile");
624
+ const editor = process.env.EDITOR;
625
+ if (editor) {
626
+ prompts2.log.message("");
627
+ prompts2.log.info(`Opening ${chalk3.blue("ruby-wasm/Gemfile")} in your editor...`);
628
+ try {
629
+ await execa(editor, [gemfilePath], { stdio: "inherit" });
630
+ prompts2.log.success("Great! Your Gemfile has been configured.");
631
+ return true;
632
+ } catch (error) {
633
+ prompts2.log.warn(`Failed to open editor: ${error.message}`);
634
+ prompts2.log.info("Falling back to manual editing instructions...");
635
+ }
636
+ }
637
+ prompts2.log.message("");
638
+ prompts2.log.step(`1. Open ${chalk3.blue(gemfilePath)} in your editor`);
639
+ prompts2.log.step(`2. Add any additional gems you need for your tutorial`);
640
+ prompts2.log.step(`3. Save the file and return here`);
641
+ prompts2.log.message("");
642
+ const ready = await prompts2.confirm({
643
+ message: "Have you finished editing the Gemfile?",
644
+ initialValue: true
645
+ });
646
+ assertNotCanceled(ready);
647
+ if (ready) {
648
+ prompts2.log.success("Great! Your Gemfile has been configured.");
649
+ return true;
650
+ }
651
+ }
652
+ return false;
653
+ }
654
+ function printRubyNextSteps(dest) {
655
+ let i = 0;
656
+ prompts2.log.message(chalk3.bold.underline("Next Steps"));
657
+ const steps = [
658
+ [`cd ${dest}`, "Navigate to project"],
659
+ ["npm run build:wasm", "Build Ruby WebAssembly with your gems"],
660
+ ["npm run dev", "Start development server"],
661
+ [, `Head over to ${chalk3.underline("http://localhost:4321")}`]
662
+ ];
663
+ for (const [command, text2] of steps) {
664
+ i++;
665
+ prompts2.log.step(`${i}. ${command ? `${chalk3.blue(command)} - ` : ""}${text2}`);
666
+ }
667
+ prompts2.log.message("");
668
+ prompts2.log.info("\u{1F4A1} The WASM build step may take several minutes the first time.");
669
+ prompts2.log.info("\u{1F4A1} Subsequent builds will be faster thanks to caching.");
670
+ }
671
+
672
+ // src/commands/create/generate-hosting-config.ts
673
+ import fs3 from "node:fs";
674
+ import path4 from "node:path";
675
+ import * as prompts3 from "@clack/prompts";
676
+ import chalk4 from "chalk";
677
+
678
+ // src/commands/create/hosting-config/_headers.txt?raw
679
+ var headers_default = "/*\n Cross-Origin-Embedder-Policy: require-corp\n Cross-Origin-Opener-Policy: same-origin\n";
680
+
681
+ // src/commands/create/hosting-config/netlify_toml.txt?raw
682
+ var netlify_toml_default = '[[headers]]\n for = "/*"\n [headers.values]\n Cross-Origin-Embedder-Policy = "require-corp"\n Cross-Origin-Opener-Policy = "same-origin"\n';
683
+
684
+ // src/commands/create/hosting-config/vercel.json?raw
685
+ var vercel_default = {
686
+ headers: [
687
+ {
688
+ source: "/(.*)",
689
+ headers: [
690
+ {
691
+ key: "Cross-Origin-Embedder-Policy",
692
+ value: "require-corp"
693
+ },
694
+ {
695
+ key: "Cross-Origin-Opener-Policy",
696
+ value: "same-origin"
697
+ }
698
+ ]
699
+ }
700
+ ]
701
+ };
702
+
703
+ // src/commands/create/generate-hosting-config.ts
704
+ async function generateHostingConfig(dest, flags) {
705
+ let provider = readFlag(flags, "provider");
706
+ if (provider === void 0) {
707
+ provider = await prompts3.select({
708
+ message: "Select hosting providers for automatic configuration:",
709
+ options: [
710
+ { value: "Vercel", label: "Vercel" },
711
+ { value: "Netlify", label: "Netlify" },
712
+ { value: "Cloudflare", label: "Cloudflare" },
713
+ { value: "skip", label: "Skip hosting configuration" }
714
+ ],
715
+ initialValue: DEFAULT_VALUES.provider
716
+ });
717
+ }
718
+ if (typeof provider !== "string") {
719
+ provider = "skip";
720
+ }
721
+ if (!provider || provider === "skip") {
722
+ prompts3.log.message(
723
+ `${chalk4.blue("hosting provider config [skip]")} You can configure hosting provider settings manually later.`
724
+ );
725
+ return provider;
726
+ }
727
+ prompts3.log.info(`${chalk4.blue("Hosting Configuration")} Setting up configuration for ${provider}`);
728
+ const resolvedDest = path4.resolve(dest);
729
+ if (!fs3.existsSync(resolvedDest)) {
730
+ fs3.mkdirSync(resolvedDest, { recursive: true });
731
+ }
732
+ let config;
733
+ let filename;
734
+ switch (provider.toLowerCase()) {
735
+ case "vercel": {
736
+ config = typeof vercel_default === "string" ? vercel_default : JSON.stringify(vercel_default, null, 2);
737
+ filename = "vercel.json";
738
+ break;
739
+ }
740
+ case "netlify": {
741
+ config = netlify_toml_default;
742
+ filename = "netlify.toml";
743
+ break;
744
+ }
745
+ case "cloudflare": {
746
+ config = headers_default;
747
+ filename = "_headers";
748
+ break;
749
+ }
750
+ }
751
+ if (config && filename) {
752
+ await runTask({
753
+ title: `Create hosting files for ${provider}`,
754
+ dryRun: flags.dryRun,
755
+ dryRunMessage: `${warnLabel("DRY RUN")} Skipped hosting provider config creation`,
756
+ task: async () => {
757
+ const filepath = path4.join(resolvedDest, filename);
758
+ fs3.writeFileSync(filepath, config);
759
+ return `Added ${filepath}`;
760
+ }
761
+ });
762
+ }
763
+ return provider;
764
+ }
765
+
766
+ // src/commands/create/git.ts
767
+ import fs4 from "node:fs";
768
+ import path5 from "node:path";
769
+ import * as prompts4 from "@clack/prompts";
770
+ import chalk5 from "chalk";
771
+
772
+ // src/utils/shell.ts
773
+ import { spawn } from "node:child_process";
774
+ async function runShellCommand(command, flags, opts = {}) {
775
+ let child;
776
+ let stdout = "";
777
+ let stderr = "";
778
+ try {
779
+ child = spawn(command, flags, {
780
+ cwd: opts.cwd,
781
+ shell: true,
782
+ stdio: opts.stdio,
783
+ timeout: opts.timeout
784
+ });
785
+ const done = new Promise((resolve, reject) => {
786
+ child.on("close", (code) => {
787
+ if (code !== 0) {
788
+ reject(code);
789
+ return;
790
+ }
791
+ resolve(code);
792
+ });
793
+ child.on("error", (code) => {
794
+ reject(code);
795
+ });
796
+ });
797
+ child.stdout?.setEncoding("utf8");
798
+ child.stderr?.setEncoding("utf8");
799
+ child.stdout?.on("data", (data) => {
800
+ stdout += data;
801
+ });
802
+ child.stderr?.on("data", (data) => {
803
+ stderr += data;
804
+ });
805
+ await done;
806
+ } catch (error) {
807
+ throw { stdout, stderr, exitCode: error };
808
+ }
809
+ return { stdout, stderr, exitCode: child.exitCode };
810
+ }
811
+
812
+ // src/commands/create/git.ts
813
+ async function initGitRepo(cwd, flags) {
814
+ let shouldInitGitRepo = readFlag(flags, "git");
815
+ if (shouldInitGitRepo === void 0) {
816
+ const answer = await prompts4.confirm({
817
+ message: "Initialize a new git repository?",
818
+ initialValue: DEFAULT_VALUES.git
819
+ });
820
+ assertNotCanceled(answer);
821
+ shouldInitGitRepo = answer;
822
+ }
823
+ if (shouldInitGitRepo) {
824
+ await runTask({
825
+ title: "Initializing git repository",
826
+ dryRun: flags.dryRun,
827
+ dryRunMessage: `${warnLabel("DRY RUN")} Skipped initializing git repository`,
828
+ task: async () => {
829
+ const message = await _initGitRepo(cwd);
830
+ return message ?? "Git repository initialized";
831
+ }
832
+ });
833
+ } else {
834
+ prompts4.log.message(`${chalk5.blue("git [skip]")} You can always run ${chalk5.yellow("git init")} manually.`);
835
+ }
836
+ }
837
+ async function _initGitRepo(cwd) {
838
+ if (fs4.existsSync(path5.join(cwd, ".git"))) {
839
+ return `${chalk5.cyan("Nice!")} Git has already been initialized`;
840
+ }
841
+ try {
842
+ await runShellCommand("git", ["init"], { cwd, stdio: "ignore" });
843
+ await runShellCommand("git", ["add", "-A"], { cwd, stdio: "ignore" });
844
+ await runShellCommand(
845
+ "git",
846
+ ["commit", "-m", `"feat: initial commit from ${package_default.name}"`, '--author="StackBlitz <hello@stackblitz.com>"'],
847
+ {
848
+ cwd,
849
+ stdio: "ignore"
850
+ }
851
+ );
852
+ return void 0;
853
+ } catch {
854
+ throw new Error("Failed to initialize local git repository");
855
+ }
856
+ }
857
+
858
+ // src/commands/create/package-manager.ts
859
+ import fs5 from "node:fs";
860
+ import path6 from "node:path";
861
+ import * as prompts5 from "@clack/prompts";
862
+ import chalk6 from "chalk";
863
+ import { lookpath } from "lookpath";
864
+ var LOCK_FILES = /* @__PURE__ */ new Map([
865
+ ["npm", "package-lock.json"],
866
+ ["pnpm", "pnpm-lock.yaml"],
867
+ ["yarn", "yarn.lock"]
868
+ ]);
869
+ async function selectPackageManager(cwd, flags) {
870
+ const packageManager = await resolvePackageManager(flags);
871
+ for (const [pkgManager, lockFile] of LOCK_FILES) {
872
+ if (pkgManager !== packageManager) {
873
+ fs5.rmSync(path6.join(cwd, lockFile), { force: true });
874
+ }
875
+ }
876
+ return packageManager;
877
+ }
878
+ async function resolvePackageManager(flags) {
879
+ if (flags.packageManager) {
880
+ if (await lookpath(String(flags.packageManager))) {
881
+ return flags.packageManager;
882
+ }
883
+ prompts5.log.warn(
884
+ `The specified package manager '${chalk6.yellow(flags.packageManager)}' doesn't seem to be installed!`
885
+ );
886
+ }
887
+ if (flags.defaults) {
888
+ return DEFAULT_VALUES.packageManager;
889
+ }
890
+ return await getPackageManager();
891
+ }
892
+ async function getPackageManager() {
893
+ const installedPackageManagers = await getInstalledPackageManagers();
894
+ let initialValue = process.env.npm_config_user_agent?.split("/")[0];
895
+ if (!installedPackageManagers.includes(initialValue)) {
896
+ initialValue = "npm";
897
+ }
898
+ const answer = await prompts5.select({
899
+ message: "What package manager should we use?",
900
+ initialValue,
901
+ options: [
902
+ { label: "npm", value: "npm" },
903
+ ...installedPackageManagers.map((pkgManager) => {
904
+ return { label: pkgManager, value: pkgManager };
905
+ })
906
+ ]
907
+ });
908
+ assertNotCanceled(answer);
909
+ return answer;
910
+ }
911
+ async function getInstalledPackageManagers() {
912
+ const packageManagers = [];
913
+ for (const pkgManager of ["yarn", "pnpm", "bun"]) {
914
+ try {
915
+ if (await lookpath(pkgManager)) {
916
+ packageManagers.push(pkgManager);
917
+ }
918
+ } catch {
919
+ }
920
+ }
921
+ return packageManagers;
922
+ }
923
+
924
+ // src/commands/create/template.ts
925
+ import fs6 from "node:fs";
926
+ import fsPromises from "node:fs/promises";
927
+ import path7 from "node:path";
928
+ import * as prompts6 from "@clack/prompts";
929
+ import ignore from "ignore";
930
+ async function copyTemplate(dest, flags) {
931
+ if (flags.dryRun) {
932
+ prompts6.log.warn(`${warnLabel("DRY RUN")} Skipped copying template`);
933
+ return;
934
+ }
935
+ const gitignore = ignore.default().add(readIgnoreFile());
936
+ const toCopy = [];
937
+ const folders = await fsPromises.readdir(templatePath);
938
+ for (const file of folders) {
939
+ if (gitignore.ignores(file)) {
940
+ continue;
941
+ }
942
+ toCopy.push(file);
943
+ }
944
+ for (const fileName of toCopy) {
945
+ const sourceFilePath = path7.join(templatePath, fileName);
946
+ const destFileName = fileName === ".npmignore" ? ".gitignore" : fileName;
947
+ const destFilePath = path7.join(dest, destFileName);
948
+ const stats = await fsPromises.stat(sourceFilePath);
949
+ if (stats.isDirectory()) {
950
+ await fsPromises.cp(sourceFilePath, destFilePath, { recursive: true });
951
+ } else if (stats.isFile()) {
952
+ await fsPromises.copyFile(sourceFilePath, destFilePath);
953
+ }
954
+ }
955
+ }
956
+ function readIgnoreFile() {
957
+ try {
958
+ return fs6.readFileSync(path7.resolve(templatePath, ".npmignore"), "utf8");
959
+ } catch {
960
+ return fs6.readFileSync(path7.resolve(templatePath, ".gitignore"), "utf8");
961
+ }
962
+ }
963
+
964
+ // src/commands/create/index.ts
965
+ var TUTORIALKIT_VERSION = package_default.version;
966
+ async function createTutorial(flags) {
967
+ if (flags._[1] === "help" || flags.help || flags.h) {
968
+ printHelp({
969
+ commandName: `${package_default.name} create`,
970
+ usage: "[name] [...options]",
971
+ tables: {
972
+ Options: [
973
+ ["--dir, -d", "The folder in which the tutorial gets created"],
974
+ ["--git, --no-git", `Initialize a local git repository (default ${chalk7.yellow(DEFAULT_VALUES.git)})`],
975
+ [
976
+ "--provider <name>, --no-provider",
977
+ `Select a hosting provider (default ${chalk7.yellow(DEFAULT_VALUES.provider)})`
978
+ ],
979
+ ["--dry-run", `Walk through steps without executing (default ${chalk7.yellow(DEFAULT_VALUES.dryRun)})`],
980
+ [
981
+ "--package-manager <name>, -p <name>",
982
+ `The package used to install dependencies (default ${chalk7.yellow(DEFAULT_VALUES.packageManager)})`
983
+ ],
984
+ [
985
+ "--enterprise <origin>, -e <origin>",
986
+ `The origin of your StackBlitz Enterprise instance (if not provided authentication is not turned on and your project will use ${chalk7.yellow("https://stackblitz.com")})`
987
+ ],
988
+ [
989
+ "--force",
990
+ `Overwrite existing files in the target directory without prompting (default ${chalk7.yellow(DEFAULT_VALUES.force)})`
991
+ ],
992
+ ["--defaults", "Skip all prompts and initialize the tutorial using the defaults"]
993
+ ]
994
+ }
995
+ });
996
+ return 0;
997
+ }
998
+ applyAliases(flags);
999
+ try {
1000
+ return _createTutorial(flags);
1001
+ } catch (error) {
1002
+ console.error(`${errorLabel()} Command failed`);
1003
+ if (error.stack) {
1004
+ console.error(`
1005
+ ${error.stack}`);
1006
+ }
1007
+ process.exit(1);
1008
+ }
1009
+ }
1010
+ async function _createTutorial(flags) {
1011
+ prompts7.intro(primaryLabel(package_default.name));
1012
+ let tutorialName = flags._[1] !== void 0 ? String(flags._[1]) : void 0;
1013
+ if (tutorialName === void 0) {
1014
+ const randomName = generateProjectName();
1015
+ if (flags.defaults) {
1016
+ tutorialName = randomName;
1017
+ } else {
1018
+ const answer = await prompts7.text({
1019
+ message: `What's the name of your tutorial?`,
1020
+ placeholder: randomName,
1021
+ validate: (value) => {
1022
+ if (!value) {
1023
+ return "Please provide a name!";
1024
+ }
1025
+ return void 0;
1026
+ }
1027
+ });
1028
+ assertNotCanceled(answer);
1029
+ tutorialName = answer;
1030
+ }
1031
+ }
1032
+ prompts7.log.info(`We'll call your tutorial ${chalk7.blue(tutorialName)}`);
1033
+ const dest = await getTutorialDirectory(tutorialName, flags);
1034
+ const resolvedDest = path8.resolve(process.cwd(), dest);
1035
+ prompts7.log.info(`Scaffolding tutorial in ${chalk7.blue(resolvedDest)}`);
1036
+ if (fs7.existsSync(resolvedDest) && !flags.force) {
1037
+ if (flags.defaults) {
1038
+ console.error(`
1039
+ ${errorLabel()} Failed to create tutorial. Directory already exists.`);
1040
+ process.exit(1);
1041
+ }
1042
+ let answer;
1043
+ if (fs7.readdirSync(resolvedDest).length > 0) {
1044
+ answer = await prompts7.confirm({
1045
+ message: `Directory is not empty. Continuing may overwrite existing files. Do you want to continue?`,
1046
+ initialValue: false
1047
+ });
1048
+ } else {
1049
+ answer = await prompts7.confirm({
1050
+ message: `Directory already exists. Continuing may overwrite existing files. Do you want to continue?`,
1051
+ initialValue: false
1052
+ });
1053
+ }
1054
+ assertNotCanceled(answer);
1055
+ if (!answer) {
1056
+ exitEarly();
1057
+ }
1058
+ } else {
1059
+ if (!flags.dryRun) {
1060
+ fs7.mkdirSync(resolvedDest, { recursive: true });
1061
+ }
1062
+ }
1063
+ await copyTemplate(resolvedDest, flags);
1064
+ const provider = await generateHostingConfig(resolvedDest, flags);
1065
+ updatePackageJson(resolvedDest, tutorialName, flags, provider);
1066
+ const selectedPackageManager = await selectPackageManager(resolvedDest, flags);
1067
+ updateReadme(resolvedDest, selectedPackageManager, flags);
1068
+ await setupEnterpriseConfig(resolvedDest, flags);
1069
+ await initGitRepo(resolvedDest, flags);
1070
+ prompts7.log.success(chalk7.green("Tutorial successfully created!"));
1071
+ await promptGemfileEditing(dest, flags);
1072
+ printRubyNextSteps(dest);
1073
+ prompts7.outro(`You're all set!`);
1074
+ console.log("Until next time \u{1F44B}");
1075
+ }
1076
+ async function getTutorialDirectory(tutorialName, flags) {
1077
+ const dir = flags.dir;
1078
+ if (dir) {
1079
+ return dir;
1080
+ }
1081
+ if (flags.defaults) {
1082
+ return `./${tutorialName}`;
1083
+ }
1084
+ const promptResult = await prompts7.text({
1085
+ message: "Where should we create your new tutorial?",
1086
+ initialValue: `./${tutorialName}`,
1087
+ placeholder: "./",
1088
+ validate(value) {
1089
+ if (!path8.isAbsolute(value) && !value.startsWith("./")) {
1090
+ return "Please provide an absolute or relative path!";
1091
+ }
1092
+ return void 0;
1093
+ }
1094
+ });
1095
+ assertNotCanceled(promptResult);
1096
+ return promptResult;
1097
+ }
1098
+ function updatePackageJson(dest, projectName, flags, provider) {
1099
+ if (flags.dryRun) {
1100
+ return;
1101
+ }
1102
+ const pkgPath = path8.resolve(dest, "package.json");
1103
+ const pkgJson = JSON.parse(fs7.readFileSync(pkgPath, "utf8"));
1104
+ pkgJson.name = projectName;
1105
+ updateWorkspaceVersions(pkgJson.dependencies, TUTORIALKIT_VERSION);
1106
+ updateWorkspaceVersions(pkgJson.devDependencies, TUTORIALKIT_VERSION);
1107
+ if (provider.toLowerCase() === "cloudflare") {
1108
+ pkgJson.scripts = pkgJson.scripts || {};
1109
+ pkgJson.scripts.postbuild = "cp _headers ./dist/";
1110
+ }
1111
+ fs7.writeFileSync(pkgPath, JSON.stringify(pkgJson, null, 2));
1112
+ try {
1113
+ const pkgLockPath = path8.resolve(dest, "package-lock.json");
1114
+ const pkgLockJson = JSON.parse(fs7.readFileSync(pkgLockPath, "utf8"));
1115
+ const defaultPackage = pkgLockJson.packages[""];
1116
+ pkgLockJson.name = projectName;
1117
+ if (defaultPackage) {
1118
+ defaultPackage.name = projectName;
1119
+ }
1120
+ fs7.writeFileSync(pkgLockPath, JSON.stringify(pkgLockJson, null, 2));
1121
+ } catch {
1122
+ }
1123
+ }
1124
+ function updateReadme(dest, packageManager, flags) {
1125
+ if (flags.dryRun) {
1126
+ return;
1127
+ }
1128
+ const readmePath = path8.resolve(dest, "README.md");
1129
+ let readme = fs7.readFileSync(readmePath, "utf8");
1130
+ readme = readme.replaceAll("<% pkgManager %>", packageManager ?? DEFAULT_VALUES.packageManager);
1131
+ fs7.writeFileSync(readmePath, readme);
1132
+ }
1133
+ function exitEarly(exitCode = 0) {
1134
+ prompts7.outro("Until next time!");
1135
+ process.exit(exitCode);
1136
+ }
1137
+ function applyAliases(flags) {
1138
+ if (flags.d) {
1139
+ flags.dir = flags.d;
1140
+ }
1141
+ if (flags.p) {
1142
+ flags.packageManager = flags.p;
1143
+ }
1144
+ if (flags.e) {
1145
+ flags.enterprise = flags.e;
1146
+ }
1147
+ }
1148
+
1149
+ // src/commands/eject/index.ts
1150
+ import fs8 from "node:fs";
1151
+ import path9 from "node:path";
1152
+ import * as prompts8 from "@clack/prompts";
1153
+ import chalk8 from "chalk";
1154
+ import detectIndent from "detect-indent";
1155
+ import { execa as execa2 } from "execa";
1156
+ import whichpm from "which-pm";
1157
+
1158
+ // src/commands/eject/options.ts
1159
+ var DEFAULT_VALUES2 = {
1160
+ force: false,
1161
+ defaults: false
1162
+ };
1163
+
1164
+ // src/commands/eject/index.ts
1165
+ var TUTORIALKIT_VERSION2 = package_default.version;
1166
+ var REQUIRED_DEPENDENCIES = [
1167
+ "@tutorialkit-rb/runtime",
1168
+ "@webcontainer/api",
1169
+ "nanostores",
1170
+ "@nanostores/react",
1171
+ "kleur",
1172
+ "@stackblitz/sdk",
1173
+ "fast-glob"
1174
+ ];
1175
+ function ejectRoutes(flags) {
1176
+ if (flags._[1] === "help" || flags.help || flags.h) {
1177
+ printHelp({
1178
+ commandName: `${package_default.name} eject`,
1179
+ usage: "[folder] [...options]",
1180
+ tables: {
1181
+ Options: [
1182
+ [
1183
+ "--force",
1184
+ `Overwrite existing files in the target directory without prompting (default ${chalk8.yellow(DEFAULT_VALUES2.force)})`
1185
+ ],
1186
+ ["--defaults", "Skip all the prompts and eject the routes using the defaults"]
1187
+ ]
1188
+ }
1189
+ });
1190
+ return 0;
1191
+ }
1192
+ try {
1193
+ return _eject(flags);
1194
+ } catch (error) {
1195
+ console.error(`${errorLabel()} Command failed`);
1196
+ if (error.stack) {
1197
+ console.error(`
1198
+ ${error.stack}`);
1199
+ }
1200
+ process.exit(1);
1201
+ }
1202
+ }
1203
+ async function _eject(flags) {
1204
+ let folderPath = flags._[1] !== void 0 ? String(flags._[1]) : void 0;
1205
+ if (folderPath === void 0) {
1206
+ folderPath = process.cwd();
1207
+ } else {
1208
+ folderPath = path9.resolve(process.cwd(), folderPath);
1209
+ }
1210
+ const { astroConfigPath, srcPath, pkgJsonPath, astroIntegrationPath, srcDestPath } = validateDestination(
1211
+ folderPath,
1212
+ flags.force
1213
+ );
1214
+ const astroConfig = await parseAstroConfig(astroConfigPath);
1215
+ replaceArgs({ defaultRoutes: false }, astroConfig);
1216
+ fs8.writeFileSync(astroConfigPath, generateAstroConfig(astroConfig));
1217
+ fs8.cpSync(srcPath, srcDestPath, { recursive: true });
1218
+ const pkgJsonContent = fs8.readFileSync(pkgJsonPath, "utf-8");
1219
+ const indent = detectIndent(pkgJsonContent).indent || " ";
1220
+ const pkgJson = JSON.parse(pkgJsonContent);
1221
+ const astroIntegrationPkgJson = JSON.parse(
1222
+ fs8.readFileSync(path9.join(astroIntegrationPath, "package.json"), "utf-8")
1223
+ );
1224
+ const newDependencies = [];
1225
+ for (const dep of REQUIRED_DEPENDENCIES) {
1226
+ if (!(dep in pkgJson.dependencies) && !(dep in pkgJson.devDependencies)) {
1227
+ pkgJson.dependencies[dep] = astroIntegrationPkgJson.dependencies[dep];
1228
+ newDependencies.push(dep);
1229
+ }
1230
+ }
1231
+ updateWorkspaceVersions(
1232
+ pkgJson.dependencies,
1233
+ TUTORIALKIT_VERSION2,
1234
+ (dependency) => REQUIRED_DEPENDENCIES.includes(dependency)
1235
+ );
1236
+ if (newDependencies.length > 0) {
1237
+ fs8.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, void 0, indent), { encoding: "utf-8" });
1238
+ console.log(
1239
+ primaryLabel("INFO"),
1240
+ `New dependencies added: ${newDependencies.join(", ")}. Install the new dependencies before proceeding.`
1241
+ );
1242
+ if (!flags.defaults) {
1243
+ const packageManager = (await whichpm(path9.dirname(pkgJsonPath))).name;
1244
+ const answer = await prompts8.confirm({
1245
+ message: `Do you want to install those dependencies now using ${chalk8.blue(packageManager)}?`
1246
+ });
1247
+ if (answer === true) {
1248
+ await execa2(packageManager, ["install"], { cwd: folderPath, stdio: "inherit" });
1249
+ }
1250
+ }
1251
+ }
1252
+ return 0;
1253
+ }
1254
+ function validateDestination(folder, force) {
1255
+ assertExists(folder);
1256
+ const pkgJsonPath = assertExists(path9.join(folder, "package.json"));
1257
+ const astroConfigPath = assertExists(path9.join(folder, "astro.config.ts"));
1258
+ const srcDestPath = assertExists(path9.join(folder, "src"));
1259
+ const astroIntegrationPath = assertExists(path9.resolve(folder, "node_modules", "@tutorialkit-rb", "astro"));
1260
+ const srcPath = path9.join(astroIntegrationPath, "dist", "default");
1261
+ if (!force) {
1262
+ walk(srcPath, (relativePath) => {
1263
+ const destination = path9.join(srcDestPath, relativePath);
1264
+ if (fs8.existsSync(destination)) {
1265
+ throw new Error(
1266
+ `Eject aborted because '${destination}' would be overwritten by this command. Use ${chalk8.yellow("--force")} to ignore this error.`
1267
+ );
1268
+ }
1269
+ });
1270
+ }
1271
+ return {
1272
+ astroConfigPath,
1273
+ astroIntegrationPath,
1274
+ pkgJsonPath,
1275
+ srcPath,
1276
+ srcDestPath
1277
+ };
1278
+ }
1279
+ function assertExists(filePath) {
1280
+ if (!fs8.existsSync(filePath)) {
1281
+ throw new Error(`${filePath} does not exists!`);
1282
+ }
1283
+ return filePath;
1284
+ }
1285
+ function walk(root, visit2) {
1286
+ function traverse2(folder, pathPrefix) {
1287
+ for (const filename of fs8.readdirSync(folder)) {
1288
+ const filePath = path9.join(folder, filename);
1289
+ const stat = fs8.statSync(filePath);
1290
+ const relativeFilePath = path9.join(pathPrefix, filename);
1291
+ if (stat.isDirectory()) {
1292
+ traverse2(filePath, relativeFilePath);
1293
+ } else {
1294
+ visit2(relativeFilePath);
1295
+ }
1296
+ }
1297
+ }
1298
+ traverse2(root, "");
1299
+ }
1300
+
1301
+ // src/index.ts
1302
+ var supportedCommands = /* @__PURE__ */ new Set(["version", "help", "create", "eject"]);
1303
+ cli();
1304
+ async function cli() {
1305
+ const flags = yargs2(process.argv.slice(2));
1306
+ const cmd = resolveCommand(flags);
1307
+ try {
1308
+ console.log("");
1309
+ const exitCode = await runCommand(cmd, flags);
1310
+ process.exit(exitCode || 0);
1311
+ } catch (error) {
1312
+ console.error(`${errorLabel()} ${error.message}`);
1313
+ process.exit(1);
1314
+ }
1315
+ }
1316
+ async function runCommand(cmd, flags) {
1317
+ switch (cmd) {
1318
+ case "version": {
1319
+ console.log(`${primaryLabel(package_default.name)} ${chalk9.green(`v${package_default.version}`)}`);
1320
+ return;
1321
+ }
1322
+ case "help": {
1323
+ printHelp({
1324
+ commandName: package_default.name,
1325
+ prolog: `${primaryLabel(package_default.name)} ${chalk9.green(`v${package_default.version}`)} Create tutorial apps powered by WebContainer API`,
1326
+ usage: ["[command] [...options]", "[ -h | --help | -v | --version ]"],
1327
+ tables: {
1328
+ Commands: [
1329
+ ["create", "Create new tutorial app"],
1330
+ [
1331
+ "eject",
1332
+ "Move all default pages and components into your project, providing full control over the Astro app"
1333
+ ],
1334
+ ["help", "Show this help message"]
1335
+ ]
1336
+ }
1337
+ });
1338
+ return;
1339
+ }
1340
+ case "create": {
1341
+ return createTutorial(flags);
1342
+ }
1343
+ case "eject": {
1344
+ return ejectRoutes(flags);
1345
+ }
1346
+ default: {
1347
+ console.error(`${errorLabel()} Unknown command ${chalk9.red(cmd)}`);
1348
+ return 1;
1349
+ }
1350
+ }
1351
+ }
1352
+ function resolveCommand(flags) {
1353
+ if (flags.version || flags.v) {
1354
+ return "version";
1355
+ }
1356
+ if (flags._[0] == null && (flags.help || flags.h)) {
1357
+ return "help";
1358
+ }
1359
+ const cmd = String(flags._.at(0));
1360
+ if (!supportedCommands.has(cmd)) {
1361
+ return "help";
1362
+ }
1363
+ return cmd;
1364
+ }