@sp-days-framework/create-sp-days 1.0.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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +318 -0
  3. package/bin/index.js +86 -0
  4. package/lib/index.d.ts +20 -0
  5. package/lib/index.js +454 -0
  6. package/package.json +41 -0
  7. package/sp-days-framework-create-sp-days-1.0.0.tgz +0 -0
  8. package/templates/addon-resources/resources/frontpage-collection/components/Columns.mdx +191 -0
  9. package/templates/addon-resources/resources/frontpage-collection/components/ContentBlock.mdx +126 -0
  10. package/templates/addon-resources/resources/frontpage-collection/components/CourseFeature.mdx +147 -0
  11. package/templates/addon-resources/resources/frontpage-collection/components/FancyHeader.mdx +76 -0
  12. package/templates/addon-resources/resources/frontpage-collection/components/GetStarted.mdx +222 -0
  13. package/templates/addon-resources/resources/frontpage-collection/components/HeroBanner.mdx +205 -0
  14. package/templates/addon-resources/resources/frontpage-collection/components/IconContainer.mdx +249 -0
  15. package/templates/addon-resources/resources/frontpage-collection/components/Iconify.mdx +228 -0
  16. package/templates/addon-resources/resources/frontpage-collection/components/_category_.yml +2 -0
  17. package/templates/addon-resources/resources/frontpage-collection/index.mdx +85 -0
  18. package/templates/addon-resources/resources/frontpage-collection/setup/index.mdx +185 -0
  19. package/templates/addon-resources/resources/index.mdx +35 -0
  20. package/templates/addon-resources/resources/interactive-tasks/creating-tasks.mdx +292 -0
  21. package/templates/addon-resources/resources/interactive-tasks/examples/_category_.yml +3 -0
  22. package/templates/addon-resources/resources/interactive-tasks/examples/advanced-usage.mdx +304 -0
  23. package/templates/addon-resources/resources/interactive-tasks/examples/basic-usage.mdx +128 -0
  24. package/templates/addon-resources/resources/interactive-tasks/index.mdx +93 -0
  25. package/templates/addon-resources/resources/interactive-tasks/setup/advanced-configuration.mdx +150 -0
  26. package/templates/addon-resources/resources/interactive-tasks/setup/index.mdx +174 -0
  27. package/templates/addon-resources/resources/interactive-tasks/task-progression.mdx +140 -0
  28. package/templates/addon-resources/resources/slidev-integration/index.mdx +106 -0
  29. package/templates/addon-resources/resources/slidev-integration/setup/advanced-configuration.mdx +144 -0
  30. package/templates/addon-resources/resources/slidev-integration/setup/index.mdx +200 -0
  31. package/templates/addon-resources/resources/sykehuspartner-theme/index.mdx +105 -0
  32. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/_category_.yml +2 -0
  33. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/content/_category_.yml +2 -0
  34. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/content/center.mdx +33 -0
  35. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/content/default.mdx +59 -0
  36. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/content/full.mdx +49 -0
  37. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/image/_category_.yml +2 -0
  38. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/image/image-left.mdx +37 -0
  39. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/image/image-right.mdx +37 -0
  40. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/image/image.mdx +56 -0
  41. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/multi-column/_category_.yml +2 -0
  42. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/multi-column/three-cols-header.mdx +49 -0
  43. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/multi-column/three-cols.mdx +47 -0
  44. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/multi-column/two-cols-header.mdx +43 -0
  45. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/multi-column/two-cols.mdx +38 -0
  46. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/section/_category_.yml +2 -0
  47. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/section/cover.mdx +43 -0
  48. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/section/end.mdx +33 -0
  49. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/section/intro.mdx +49 -0
  50. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/section/section.mdx +41 -0
  51. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/special/_category_.yml +2 -0
  52. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/special/about-me.mdx +92 -0
  53. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/special/fact.mdx +47 -0
  54. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/special/quote.mdx +27 -0
  55. package/templates/addon-resources/resources/sykehuspartner-theme/layouts/special/statement.mdx +28 -0
  56. package/templates/addon-resources/resources/sykehuspartner-theme/setup/advanced-configuration.mdx +79 -0
  57. package/templates/addon-resources/resources/sykehuspartner-theme/setup/index.mdx +104 -0
  58. package/templates/addon-resources/resources/sykehuspartner-theme/syntax-and-icons.mdx +89 -0
  59. package/templates/addon-slidev/package.json +54 -0
  60. package/templates/addon-slidev/slidev/creating-your-first-slidev.md +301 -0
  61. package/templates/addon-slidev/slidev/slidev-theme-sykehuspartner.md +403 -0
  62. package/templates/page-course/README.md +152 -0
  63. package/templates/page-course/course/index.mdx +7 -0
  64. package/templates/page-course/course/placeholder-advanced-usage.mdx +304 -0
  65. package/templates/page-course/course/placeholder-basic-usage.mdx +128 -0
  66. package/templates/page-course/docusaurus.config.ts +171 -0
  67. package/templates/page-course/example-github-pages.yml +66 -0
  68. package/templates/page-course/gitignore +20 -0
  69. package/templates/page-course/package.json +51 -0
  70. package/templates/page-course/src/css/sp-days-theme.scss +297 -0
  71. package/templates/page-course/src/pages/index.mdx +165 -0
  72. package/templates/page-course/static/.nojekyll +0 -0
  73. package/templates/page-course/static/img/favicon-navbar/github.svg +3 -0
  74. package/templates/page-course/static/img/favicon-navbar/sorost-logo-dark.svg +24 -0
  75. package/templates/page-course/static/img/favicon-navbar/sorost-logo-light.svg +24 -0
  76. package/templates/page-course/static/img/footer/sykehuspartner-dark.svg +37 -0
  77. package/templates/page-course/static/img/footer/sykehuspartner-light.svg +37 -0
  78. package/templates/page-course/static/img/sidebar/docusaurus.svg +17 -0
  79. package/templates/page-course/static/img/sidebar/slidev.svg +22 -0
  80. package/templates/page-course/static/img/sp-days-logo-color-dark.svg +70 -0
  81. package/templates/page-course/static/img/sp-days-logo-color-light.svg +70 -0
  82. package/templates/page-course/static/img/sp-days-logo-filled-invert.svg +11 -0
  83. package/templates/page-course/static/img/sp-days-logo-filled.svg +11 -0
  84. package/templates/page-course/static/img/sp-days-logo-outline.svg +19 -0
  85. package/templates/page-course/static/img/sp-days-plugin-color-dark.svg +57 -0
  86. package/templates/page-course/static/img/sp-days-plugin-color-light.svg +53 -0
  87. package/templates/page-course/tsconfig.json +8 -0
package/lib/index.js ADDED
@@ -0,0 +1,454 @@
1
+ import fs from "fs-extra";
2
+ import { fileURLToPath } from "url";
3
+ import path from "path";
4
+ import { logger } from "@docusaurus/logger";
5
+ import execa from "execa";
6
+ import prompts from "prompts";
7
+ import supportsColor from "supports-color";
8
+ // Only used in the rare, rare case of running globally installed create +
9
+ // using --skip-install. We need a default name to show the tip text
10
+ const defaultPackageManager = "npm";
11
+ const lockfileNames = {
12
+ npm: "package-lock.json",
13
+ yarn: "yarn.lock",
14
+ pnpm: "pnpm-lock.yaml",
15
+ bun: "bun.lockb",
16
+ };
17
+ const packageManagers = Object.keys(lockfileNames);
18
+ async function findPackageManagerFromLockFile(rootDir) {
19
+ for (const packageManager of packageManagers) {
20
+ const lockFilePath = path.join(rootDir, lockfileNames[packageManager]);
21
+ if (await fs.pathExists(lockFilePath)) {
22
+ return packageManager;
23
+ }
24
+ }
25
+ return undefined;
26
+ }
27
+ function findPackageManagerFromUserAgent() {
28
+ return packageManagers.find((packageManager) => process.env.npm_config_user_agent?.startsWith(packageManager));
29
+ }
30
+ async function askForPackageManagerChoice() {
31
+ const hasYarn = (await execa.command("yarn --version")).exitCode === 0;
32
+ const hasPnpm = (await execa.command("pnpm --version")).exitCode === 0;
33
+ const hasBun = (await execa.command("bun --version")).exitCode === 0;
34
+ if (!hasYarn && !hasPnpm && !hasBun) {
35
+ return "npm";
36
+ }
37
+ const choices = ["npm", hasYarn && "yarn", hasPnpm && "pnpm", hasBun && "bun"]
38
+ .filter((p) => Boolean(p))
39
+ .map((p) => ({ title: p, value: p }));
40
+ return ((await prompts({
41
+ type: "select",
42
+ name: "packageManager",
43
+ message: "Select a package manager...",
44
+ choices,
45
+ }, {
46
+ onCancel() {
47
+ logger.info `Falling back to name=${defaultPackageManager}`;
48
+ },
49
+ })).packageManager ?? defaultPackageManager);
50
+ }
51
+ async function getPackageManager(dest, { packageManager, skipInstall }) {
52
+ if (packageManager && !packageManagers.includes(packageManager)) {
53
+ throw new Error(`Invalid package manager choice ${packageManager}. Must be one of ${packageManagers.join(", ")}`);
54
+ }
55
+ return (packageManager ??
56
+ (await findPackageManagerFromLockFile(dest)) ??
57
+ findPackageManagerFromUserAgent() ??
58
+ (skipInstall ? defaultPackageManager : askForPackageManagerChoice()));
59
+ }
60
+ const templatesDir = fileURLToPath(new URL("../templates", import.meta.url));
61
+ async function getSiteName(reqName, rootDir) {
62
+ async function validateSiteName(siteName) {
63
+ if (!siteName) {
64
+ return "A site name is required.";
65
+ }
66
+ const dest = path.resolve(rootDir, siteName);
67
+ if (await fs.pathExists(dest)) {
68
+ return logger.interpolate `Directory already exists at path=${dest}!`;
69
+ }
70
+ return true;
71
+ }
72
+ if (reqName) {
73
+ const res = await validateSiteName(reqName);
74
+ if (typeof res === "string") {
75
+ throw new Error(res);
76
+ }
77
+ return reqName;
78
+ }
79
+ const { siteName } = (await prompts({
80
+ type: "text",
81
+ name: "siteName",
82
+ message: "What should we name your site directory?",
83
+ initial: "docs",
84
+ validate: validateSiteName,
85
+ }, {
86
+ onCancel() {
87
+ logger.error("A site name is required.");
88
+ process.exit(1);
89
+ },
90
+ }));
91
+ return siteName;
92
+ }
93
+ async function getProjectConfig(siteName, cliOptions) {
94
+ // Get configuration from prompts or CLI options
95
+ const defaultOrg = "helse-sorost";
96
+ const defaultRepo = "example-repo";
97
+ const defaultTitle = "SP Days Template";
98
+ const defaultTagline = "Empowered by Docusaurus and Slidev";
99
+ let organizationName;
100
+ let repositoryName;
101
+ let title;
102
+ let tagline;
103
+ // Check if ANY configuration options were provided via CLI
104
+ const hasCliConfig = !!(cliOptions.name ||
105
+ cliOptions.org ||
106
+ cliOptions.repo ||
107
+ cliOptions.title ||
108
+ cliOptions.tagline);
109
+ if (hasCliConfig) {
110
+ // Use CLI options with defaults
111
+ organizationName = cliOptions.org ?? defaultOrg;
112
+ repositoryName = cliOptions.repo ?? cliOptions.name ?? defaultRepo;
113
+ title = cliOptions.title ?? defaultTitle;
114
+ tagline = cliOptions.tagline ?? defaultTagline;
115
+ }
116
+ else {
117
+ // Interactive prompts
118
+ const answers = await prompts([
119
+ {
120
+ type: "text",
121
+ name: "organizationName",
122
+ message: "What is your GitHub organization?",
123
+ initial: defaultOrg,
124
+ },
125
+ {
126
+ type: "text",
127
+ name: "repositoryName",
128
+ message: "What is your repository name?",
129
+ initial: defaultRepo,
130
+ },
131
+ {
132
+ type: "text",
133
+ name: "title",
134
+ message: "What is your site title?",
135
+ initial: defaultTitle,
136
+ },
137
+ {
138
+ type: "text",
139
+ name: "tagline",
140
+ message: "What is your site tagline?",
141
+ initial: defaultTagline,
142
+ },
143
+ ], {
144
+ onCancel() {
145
+ logger.error("Configuration is required.");
146
+ process.exit(1);
147
+ },
148
+ });
149
+ // Ensure we have values - use defaults if prompts were somehow bypassed
150
+ organizationName = answers.organizationName || defaultOrg;
151
+ repositoryName = answers.repositoryName || defaultRepo;
152
+ title = answers.title || defaultTitle;
153
+ tagline = answers.tagline || defaultTagline;
154
+ }
155
+ // Get addon selections
156
+ let addonSlidev;
157
+ let addonResources;
158
+ if (hasCliConfig) {
159
+ // If using CLI mode for config, also use CLI mode for addons (default to false if not specified)
160
+ addonSlidev = cliOptions.addonSlidev ?? false;
161
+ addonResources = cliOptions.addonResources ?? false;
162
+ }
163
+ else if (cliOptions.addonSlidev !== undefined ||
164
+ cliOptions.addonResources !== undefined) {
165
+ // Addon flags provided without config flags
166
+ addonSlidev = cliOptions.addonSlidev ?? false;
167
+ addonResources = cliOptions.addonResources ?? false;
168
+ }
169
+ else {
170
+ // Interactive mode - prompt with default yes
171
+ const addonAnswers = await prompts([
172
+ {
173
+ type: "confirm",
174
+ name: "addonSlidev",
175
+ message: 'Do you wish to add Slidev Integration?\n This will create a "./slidev" directory and add the necessary packages.',
176
+ initial: true,
177
+ },
178
+ {
179
+ type: "confirm",
180
+ name: "addonResources",
181
+ message: 'Do you wish to add a Resources page?\n This will add a "./resources" directory and add comprehensive resources examples.',
182
+ initial: true,
183
+ },
184
+ ], {
185
+ onCancel() {
186
+ logger.error("Addon selection is required.");
187
+ process.exit(1);
188
+ },
189
+ });
190
+ addonSlidev = addonAnswers.addonSlidev ?? true;
191
+ addonResources = addonAnswers.addonResources ?? true;
192
+ }
193
+ return {
194
+ directoryName: siteName,
195
+ projectName: repositoryName,
196
+ organizationName,
197
+ repositoryName,
198
+ title,
199
+ tagline,
200
+ addonSlidev,
201
+ addonResources,
202
+ gitRepositoryUrl: `https://github.com/${organizationName}/${repositoryName}`,
203
+ githubPagesUrl: `https://${organizationName}.github.io`,
204
+ baseUrl: "/",
205
+ };
206
+ }
207
+ async function copyBaseTemplate(dest) {
208
+ const baseTemplatePath = path.join(templatesDir, "page-course");
209
+ await fs.copy(baseTemplatePath, dest);
210
+ logger.success("Base template copied");
211
+ }
212
+ async function installAddon(dest, addonName) {
213
+ const addonPath = path.join(templatesDir, addonName);
214
+ if (addonName === "addon-slidev") {
215
+ // Copy slidev directory
216
+ const slidevSrc = path.join(addonPath, "slidev");
217
+ const slidevDest = path.join(dest, "slidev");
218
+ await fs.copy(slidevSrc, slidevDest);
219
+ // Overwrite package.json with the one that includes Slidev dependencies
220
+ const pkgSrc = path.join(addonPath, "package.json");
221
+ const pkgDest = path.join(dest, "package.json");
222
+ await fs.copy(pkgSrc, pkgDest, { overwrite: true });
223
+ logger.success("Slidev addon installed");
224
+ }
225
+ else if (addonName === "addon-resources") {
226
+ // Copy resources directory
227
+ const resourcesSrc = path.join(addonPath, "resources");
228
+ const resourcesDest = path.join(dest, "resources");
229
+ await fs.copy(resourcesSrc, resourcesDest);
230
+ logger.success("Resources addon installed");
231
+ }
232
+ }
233
+ function removeConditionalSections(content, removeMarkers) {
234
+ let result = content;
235
+ for (const marker of removeMarkers) {
236
+ const startMarker = `// CONDITIONAL: ${marker} (START)`;
237
+ const endMarker = `// CONDITIONAL: ${marker} (END)`;
238
+ // Remove everything between start and end markers, including the markers
239
+ const regex = new RegExp(`[ \\t]*${startMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${endMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "g");
240
+ result = result.replace(regex, "");
241
+ }
242
+ return result;
243
+ }
244
+ function removeConditionalMarkers(content, keepMarkers) {
245
+ let result = content;
246
+ for (const marker of keepMarkers) {
247
+ const startMarker = `// CONDITIONAL: ${marker} (START)`;
248
+ const endMarker = `// CONDITIONAL: ${marker} (END)`;
249
+ // Remove just the markers, keeping the content between them
250
+ const startRegex = new RegExp(`[ \\t]*${startMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "g");
251
+ const endRegex = new RegExp(`[ \\t]*${endMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "g");
252
+ result = result.replace(startRegex, "");
253
+ result = result.replace(endRegex, "");
254
+ }
255
+ return result;
256
+ }
257
+ async function processConditionalMarkers(dest, config) {
258
+ const markersToRemove = [];
259
+ const markersToKeep = [];
260
+ if (!config.addonSlidev) {
261
+ markersToRemove.push("addon-slidev");
262
+ }
263
+ else {
264
+ markersToKeep.push("addon-slidev");
265
+ }
266
+ if (!config.addonResources) {
267
+ markersToRemove.push("addon-resources");
268
+ }
269
+ else {
270
+ markersToKeep.push("addon-resources");
271
+ }
272
+ // Process docusaurus.config.ts
273
+ const configPath = path.join(dest, "docusaurus.config.ts");
274
+ let configContent = await fs.readFile(configPath, "utf-8");
275
+ // First remove sections for disabled addons
276
+ configContent = removeConditionalSections(configContent, markersToRemove);
277
+ // Then remove just the markers for enabled addons
278
+ configContent = removeConditionalMarkers(configContent, markersToKeep);
279
+ await fs.writeFile(configPath, configContent);
280
+ // Process index.mdx
281
+ const indexMdxPath = path.join(dest, "src", "pages", "index.mdx");
282
+ if (await fs.pathExists(indexMdxPath)) {
283
+ let indexContent = await fs.readFile(indexMdxPath, "utf-8");
284
+ // First remove sections for disabled addons
285
+ indexContent = removeConditionalSections(indexContent, markersToRemove);
286
+ // Then remove just the markers for enabled addons
287
+ indexContent = removeConditionalMarkers(indexContent, markersToKeep);
288
+ await fs.writeFile(indexMdxPath, indexContent);
289
+ }
290
+ logger.success("Configuration processed");
291
+ }
292
+ async function processTemplateVariables(dest, config) {
293
+ // Process docusaurus.config.ts
294
+ const configPath = path.join(dest, "docusaurus.config.ts");
295
+ if (await fs.pathExists(configPath)) {
296
+ let content = await fs.readFile(configPath, "utf-8");
297
+ // Replace template placeholders
298
+ content = content
299
+ .replace(/<%= organizationName %>/g, config.organizationName)
300
+ .replace(/<%= projectName %>/g, config.projectName)
301
+ .replace(/<%= gitRepositoryUrl %>/g, config.gitRepositoryUrl)
302
+ .replace(/<%= title %>/g, config.title)
303
+ .replace(/<%= tagline %>/g, config.tagline);
304
+ await fs.writeFile(configPath, content);
305
+ }
306
+ // Process package.json
307
+ const pkgPath = path.join(dest, "package.json");
308
+ if (await fs.pathExists(pkgPath)) {
309
+ let content = await fs.readFile(pkgPath, "utf-8");
310
+ content = content.replace(/<%= projectName %>/g, config.projectName);
311
+ await fs.writeFile(pkgPath, content);
312
+ }
313
+ // Process README.md with conditional sections
314
+ const readmePath = path.join(dest, "README.md");
315
+ if (await fs.pathExists(readmePath)) {
316
+ let content = await fs.readFile(readmePath, "utf-8");
317
+ // Replace variables
318
+ content = content
319
+ .replace(/<%= title %>/g, config.title)
320
+ .replace(/<%= tagline %>/g, config.tagline)
321
+ .replace(/<%= directoryName %>/g, config.directoryName)
322
+ .replace(/<%= organizationName %>/g, config.organizationName)
323
+ .replace(/<%= repositoryName %>/g, config.repositoryName)
324
+ .replace(/<%= gitRepositoryUrl %>/g, config.gitRepositoryUrl);
325
+ // Process conditional sections for addons
326
+ content = processReadmeConditionals(content, config);
327
+ await fs.writeFile(readmePath, content);
328
+ }
329
+ }
330
+ function processReadmeConditionals(content, config) {
331
+ let result = content;
332
+ // Process Slidev conditional sections
333
+ if (!config.addonSlidev) {
334
+ // Remove sections between <% if (addonSlidev) { %> and <% } %>
335
+ result = result.replace(/<% if \(addonSlidev\) \{ %>[\s\S]*?<% } %>/g, "");
336
+ }
337
+ else {
338
+ // Remove the conditional markers but keep the content
339
+ result = result.replace(/<% if \(addonSlidev\) \{ %>\n?/g, "");
340
+ result = result.replace(/<% } %>\n?/g, "");
341
+ }
342
+ // Process Resources conditional sections
343
+ if (!config.addonResources) {
344
+ result = result.replace(/<% if \(addonResources\) \{ %>[\s\S]*?<% } %>/g, "");
345
+ }
346
+ else {
347
+ result = result.replace(/<% if \(addonResources\) \{ %>\n?/g, "");
348
+ result = result.replace(/<% } %>\n?/g, "");
349
+ }
350
+ return result;
351
+ }
352
+ async function updatePkg(pkgPath, obj) {
353
+ const pkg = (await fs.readJSON(pkgPath));
354
+ const newPkg = Object.assign(pkg, obj);
355
+ await fs.outputFile(pkgPath, `${JSON.stringify(newPkg, null, 2)}\n`);
356
+ }
357
+ function toKebabCase(str) {
358
+ return str
359
+ .replace(/([a-z])([A-Z])/g, "$1-$2")
360
+ .replace(/[\s_]+/g, "-")
361
+ .toLowerCase();
362
+ }
363
+ export default async function init(rootDir, reqName, cliOptions = {}) {
364
+ const siteName = await getSiteName(reqName, rootDir);
365
+ const dest = path.resolve(rootDir, siteName);
366
+ const config = await getProjectConfig(siteName, cliOptions);
367
+ logger.info("Creating a new SP-Days site...");
368
+ // Copy base template
369
+ await copyBaseTemplate(dest);
370
+ // Install addons if requested
371
+ if (config.addonSlidev) {
372
+ await installAddon(dest, "addon-slidev");
373
+ }
374
+ if (config.addonResources) {
375
+ await installAddon(dest, "addon-resources");
376
+ }
377
+ // Process conditional markers in docusaurus.config.ts
378
+ await processConditionalMarkers(dest, config);
379
+ // Process template variables
380
+ await processTemplateVariables(dest, config);
381
+ // Update package.json info.
382
+ try {
383
+ await updatePkg(path.join(dest, "package.json"), {
384
+ name: toKebabCase(config.projectName),
385
+ version: "0.0.0",
386
+ private: true,
387
+ });
388
+ }
389
+ catch (err) {
390
+ logger.error("Failed to update package.json.");
391
+ throw err;
392
+ }
393
+ // We need to rename the gitignore file to .gitignore
394
+ if (!(await fs.pathExists(path.join(dest, ".gitignore"))) &&
395
+ (await fs.pathExists(path.join(dest, "gitignore")))) {
396
+ await fs.move(path.join(dest, "gitignore"), path.join(dest, ".gitignore"));
397
+ }
398
+ if (await fs.pathExists(path.join(dest, "gitignore"))) {
399
+ await fs.remove(path.join(dest, "gitignore"));
400
+ }
401
+ // Display the most elegant way to cd.
402
+ const cdpath = path.relative(".", dest);
403
+ const pkgManager = await getPackageManager(dest, cliOptions);
404
+ if (!cliOptions.skipInstall) {
405
+ process.chdir(dest);
406
+ logger.info `Installing dependencies with name=${pkgManager}...`;
407
+ if ((await execa.command(pkgManager === "yarn"
408
+ ? "yarn"
409
+ : pkgManager === "bun"
410
+ ? "bun install"
411
+ : `${pkgManager} install --color always`, {
412
+ env: {
413
+ ...process.env,
414
+ // Force coloring the output
415
+ ...(supportsColor.stdout ? { FORCE_COLOR: "1" } : {}),
416
+ },
417
+ })).exitCode !== 0) {
418
+ logger.error("Dependency installation failed.");
419
+ logger.info `The site directory has already been created, and you can retry by typing:
420
+
421
+ code=${`cd ${cdpath}`}
422
+ code=${`${pkgManager} install`}`;
423
+ process.exit(0);
424
+ }
425
+ logger.success("Dependencies installed");
426
+ }
427
+ const useNpm = pkgManager === "npm";
428
+ const useBun = pkgManager === "bun";
429
+ const useRunCommand = useNpm || useBun;
430
+ logger.success `Created name=${config.projectName} at path=${cdpath}.`;
431
+ logger.info `Inside that directory, you can run several commands:
432
+
433
+ code=${`${pkgManager} start`}
434
+ Starts the development server.
435
+
436
+ code=${`${pkgManager} ${useRunCommand ? "run " : ""}build`}
437
+ Bundles your website into static files for production.
438
+
439
+ code=${`${pkgManager} ${useRunCommand ? "run " : ""}serve`}
440
+ Serves the built website locally.${config.addonSlidev
441
+ ? `
442
+
443
+ code=${`${pkgManager} ${useRunCommand ? "run " : ""}slidev`}
444
+ Launches Slidev presentation server.`
445
+ : ""}
446
+
447
+ We suggest that you begin by typing:
448
+
449
+ code=${`cd ${cdpath}`}
450
+ code=${`${pkgManager} start`}
451
+
452
+ Happy days! 📚
453
+ `;
454
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@sp-days-framework/create-sp-days",
3
+ "version": "1.0.0",
4
+ "description": "Scaffolding tool for creating SP-Days course websites built on Docusaurus and Slidev.",
5
+ "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/helse-sorost/sp-days-framework",
9
+ "directory": "create-sp-days"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc --build",
13
+ "watch": "tsc --build --watch"
14
+ },
15
+ "bin": "bin/index.js",
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "license": "MIT",
20
+ "dependencies": {
21
+ "@docusaurus/logger": "3.9.2",
22
+ "@docusaurus/types": "3.9.2",
23
+ "commander": "^5.1.0",
24
+ "execa": "5.1.1",
25
+ "fs-extra": "^11.1.1",
26
+ "prompts": "^2.4.2",
27
+ "semver": "^7.5.4",
28
+ "supports-color": "^9.4.0",
29
+ "tslib": "^2.6.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/fs-extra": "^11.0.4",
33
+ "@types/prompts": "^2.4.9",
34
+ "@types/semver": "^7.5.6",
35
+ "@types/supports-color": "^8.1.1",
36
+ "typescript": "^5.3.3"
37
+ },
38
+ "engines": {
39
+ "node": ">=20.0"
40
+ }
41
+ }
@@ -0,0 +1,191 @@
1
+ import Tabs from "@theme/Tabs";
2
+ import TabItem from "@theme/TabItem";
3
+ import {
4
+ Columns,
5
+ Left,
6
+ Right,
7
+ Block,
8
+ } from "@sp-days-framework/docusaurus-frontpage-collection";
9
+
10
+ # Columns Section
11
+
12
+ Create responsive two-column layouts with customizable ratios.
13
+
14
+ <div className="preview">
15
+ <div className="container" style={{ width: '-webkit-fill-available'}}>
16
+ <Columns leftRatio="60">
17
+ <Left>
18
+ ### Main Content
19
+
20
+ This is a live example of the Columns component. The left column takes up 60% of the width, while the right column takes up 40%.
21
+
22
+ The columns will automatically stack on smaller screens for better mobile experience.
23
+ </Left>
24
+ <Right>
25
+ <Block>
26
+ ### Quick Stats
27
+
28
+ - **Responsive**: Stacks on mobile
29
+ - **Flexible**: Custom ratios
30
+ - **Equal height**: Automatic
31
+ </Block>
32
+ </Right>
33
+ </Columns>
34
+ </div>
35
+ </div>
36
+
37
+ ## Features
38
+
39
+ - Flexible column ratios
40
+ - Equal height option
41
+ - Automatic text wrapping
42
+
43
+ ## Import
44
+
45
+ <Tabs>
46
+ <TabItem value="individual" label="Individual import" default>
47
+ ```mdx
48
+ import {
49
+ Columns,
50
+ Left,
51
+ Right,
52
+ } from "@sp-days-framework/docusaurus-frontpage-collection";
53
+ ```
54
+ </TabItem>
55
+ <TabItem value="default" label="Default import">
56
+ ```mdx
57
+ import Columns, { Left, Right } from "@sp-days-framework/docusaurus-frontpage-collection/lib/components/Columns";
58
+ ```
59
+ </TabItem>
60
+ </Tabs>
61
+
62
+ ## Basic Usage
63
+
64
+ ```mdx
65
+ <Columns>
66
+ <Left>
67
+ Left column content
68
+ </Left>
69
+ <Right>
70
+ Right column content
71
+ </Right>
72
+ </Columns>
73
+ ```
74
+
75
+ ## Props
76
+
77
+ ### Columns
78
+
79
+ | Prop | Type | Default | Description |
80
+ | ------------- | ----------- | --------- | ---------------------------------------- |
81
+ | `leftRatio` | `string` | `'50'` | Width percentage for left column |
82
+ | `equalHeight` | `boolean` | `true` | Whether columns should have equal height |
83
+ | `minWidth` | `string` | `'700px'` | Minimum width before stacking |
84
+ | `noStretch` | `boolean` | `false` | Prevent columns from stretching |
85
+ | `children` | `ReactNode` | required | Left and Right components |
86
+
87
+ ### Left / Right
88
+
89
+ | Prop | Type | Description |
90
+ | ---------- | ----------- | ----------------------------- |
91
+ | `children` | `ReactNode` | Column content (supports MDX) |
92
+
93
+ ## Examples
94
+
95
+ ### 50/50 Split
96
+
97
+ ```mdx
98
+ <Columns>
99
+ <Left>
100
+ ## Left Side
101
+
102
+ Content for the left column
103
+ </Left>
104
+ <Right>
105
+ ## Right Side
106
+
107
+ Content for the right column
108
+ </Right>
109
+ </Columns>
110
+ ```
111
+
112
+ ### 60/40 Split
113
+
114
+ ```mdx
115
+ <Columns leftRatio="60">
116
+ <Left>
117
+ ## Main Content (60%)
118
+
119
+ This column takes up 60% of the width
120
+ </Left>
121
+ <Right>
122
+ ## Sidebar (40%)
123
+
124
+ This column takes up 40% of the width
125
+ </Right>
126
+ </Columns>
127
+ ```
128
+
129
+ ### 70/30 Split
130
+
131
+ ```mdx
132
+ <Columns leftRatio="70">
133
+ <Left>
134
+ ## Primary Content
135
+
136
+ Larger column for main content
137
+ </Left>
138
+ <Right>
139
+ ### Quick Stats
140
+
141
+ - Duration: 4 weeks
142
+ - Effort: 10 hrs/week
143
+ </Right>
144
+ </Columns>
145
+ ```
146
+
147
+ ### With Block Component
148
+
149
+ ```mdx
150
+ import {
151
+ Columns,
152
+ Left,
153
+ Right,
154
+ Block,
155
+ } from "@sp-days-framework/docusaurus-frontpage-collection";
156
+
157
+ <Columns leftRatio="65">
158
+ <Left>
159
+ ## Course Overview
160
+
161
+ Detailed description of the course...
162
+ </Left>
163
+ <Right>
164
+ <Block>
165
+ ### Info
166
+
167
+ - **Level**: Intermediate
168
+ - **Duration**: 6 weeks
169
+ - **Certificate**: Yes
170
+ </Block>
171
+ </Right>
172
+ </Columns>
173
+ ```
174
+
175
+ ## Responsive Behavior
176
+
177
+ The columns automatically stack vertically when the container is too narrow:
178
+
179
+ - **> 900px**: Full two-column layout
180
+ - **750px - 900px**: Reduced gap between columns
181
+ - **600px - 750px**: Further adjusted spacing
182
+ - **< 600px**: Stacks vertically
183
+
184
+ ## Content Wrapping
185
+
186
+ Text and content automatically wrap within columns:
187
+
188
+ - Long words break appropriately
189
+ - Tables become scrollable
190
+ - Images scale to fit
191
+ - Code blocks wrap or scroll