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