@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.
- package/LICENSE +21 -0
- package/README.md +318 -0
- package/bin/index.js +86 -0
- package/lib/index.d.ts +20 -0
- package/lib/index.js +454 -0
- package/package.json +41 -0
- package/sp-days-framework-create-sp-days-1.0.0.tgz +0 -0
- package/templates/addon-resources/resources/frontpage-collection/components/Columns.mdx +191 -0
- package/templates/addon-resources/resources/frontpage-collection/components/ContentBlock.mdx +126 -0
- package/templates/addon-resources/resources/frontpage-collection/components/CourseFeature.mdx +147 -0
- package/templates/addon-resources/resources/frontpage-collection/components/FancyHeader.mdx +76 -0
- package/templates/addon-resources/resources/frontpage-collection/components/GetStarted.mdx +222 -0
- package/templates/addon-resources/resources/frontpage-collection/components/HeroBanner.mdx +205 -0
- package/templates/addon-resources/resources/frontpage-collection/components/IconContainer.mdx +249 -0
- package/templates/addon-resources/resources/frontpage-collection/components/Iconify.mdx +228 -0
- package/templates/addon-resources/resources/frontpage-collection/components/_category_.yml +2 -0
- package/templates/addon-resources/resources/frontpage-collection/index.mdx +85 -0
- package/templates/addon-resources/resources/frontpage-collection/setup/index.mdx +185 -0
- package/templates/addon-resources/resources/index.mdx +35 -0
- package/templates/addon-resources/resources/interactive-tasks/creating-tasks.mdx +292 -0
- package/templates/addon-resources/resources/interactive-tasks/examples/_category_.yml +3 -0
- package/templates/addon-resources/resources/interactive-tasks/examples/advanced-usage.mdx +304 -0
- package/templates/addon-resources/resources/interactive-tasks/examples/basic-usage.mdx +128 -0
- package/templates/addon-resources/resources/interactive-tasks/index.mdx +93 -0
- package/templates/addon-resources/resources/interactive-tasks/setup/advanced-configuration.mdx +150 -0
- package/templates/addon-resources/resources/interactive-tasks/setup/index.mdx +174 -0
- package/templates/addon-resources/resources/interactive-tasks/task-progression.mdx +140 -0
- package/templates/addon-resources/resources/slidev-integration/index.mdx +106 -0
- package/templates/addon-resources/resources/slidev-integration/setup/advanced-configuration.mdx +144 -0
- package/templates/addon-resources/resources/slidev-integration/setup/index.mdx +200 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/index.mdx +105 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/_category_.yml +2 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/content/_category_.yml +2 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/content/center.mdx +33 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/content/default.mdx +59 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/content/full.mdx +49 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/image/_category_.yml +2 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/image/image-left.mdx +37 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/image/image-right.mdx +37 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/image/image.mdx +56 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/multi-column/_category_.yml +2 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/multi-column/three-cols-header.mdx +49 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/multi-column/three-cols.mdx +47 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/multi-column/two-cols-header.mdx +43 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/multi-column/two-cols.mdx +38 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/section/_category_.yml +2 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/section/cover.mdx +43 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/section/end.mdx +33 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/section/intro.mdx +49 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/section/section.mdx +41 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/special/_category_.yml +2 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/special/about-me.mdx +92 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/special/fact.mdx +47 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/special/quote.mdx +27 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/layouts/special/statement.mdx +28 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/setup/advanced-configuration.mdx +79 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/setup/index.mdx +104 -0
- package/templates/addon-resources/resources/sykehuspartner-theme/syntax-and-icons.mdx +89 -0
- package/templates/addon-slidev/package.json +54 -0
- package/templates/addon-slidev/slidev/creating-your-first-slidev.md +301 -0
- package/templates/addon-slidev/slidev/slidev-theme-sykehuspartner.md +403 -0
- package/templates/page-course/README.md +152 -0
- package/templates/page-course/course/index.mdx +7 -0
- package/templates/page-course/course/placeholder-advanced-usage.mdx +304 -0
- package/templates/page-course/course/placeholder-basic-usage.mdx +128 -0
- package/templates/page-course/docusaurus.config.ts +171 -0
- package/templates/page-course/example-github-pages.yml +66 -0
- package/templates/page-course/gitignore +20 -0
- package/templates/page-course/package.json +51 -0
- package/templates/page-course/src/css/sp-days-theme.scss +297 -0
- package/templates/page-course/src/pages/index.mdx +165 -0
- package/templates/page-course/static/.nojekyll +0 -0
- package/templates/page-course/static/img/favicon-navbar/github.svg +3 -0
- package/templates/page-course/static/img/favicon-navbar/sorost-logo-dark.svg +24 -0
- package/templates/page-course/static/img/favicon-navbar/sorost-logo-light.svg +24 -0
- package/templates/page-course/static/img/footer/sykehuspartner-dark.svg +37 -0
- package/templates/page-course/static/img/footer/sykehuspartner-light.svg +37 -0
- package/templates/page-course/static/img/sidebar/docusaurus.svg +17 -0
- package/templates/page-course/static/img/sidebar/slidev.svg +22 -0
- package/templates/page-course/static/img/sp-days-logo-color-dark.svg +70 -0
- package/templates/page-course/static/img/sp-days-logo-color-light.svg +70 -0
- package/templates/page-course/static/img/sp-days-logo-filled-invert.svg +11 -0
- package/templates/page-course/static/img/sp-days-logo-filled.svg +11 -0
- package/templates/page-course/static/img/sp-days-logo-outline.svg +19 -0
- package/templates/page-course/static/img/sp-days-plugin-color-dark.svg +57 -0
- package/templates/page-course/static/img/sp-days-plugin-color-light.svg +53 -0
- 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
|
+
}
|
|
Binary file
|
|
@@ -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
|