Skip to content

Commit 3352dde

Browse files
authored
Merge pull request #11 from stackbit/example-support
v0.1.1: Examples monorepo
2 parents 10b2ae8 + c22417d commit 3352dde

File tree

5 files changed

+166
-41
lines changed

5 files changed

+166
-41
lines changed

README.md

+16-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ To see a full list of options use the `--help` flag:
1717
1818
Options:
1919
--version Show version number [boolean]
20-
-s, --starter Choose a starter [choices: "nextjs", "ts-nextjs"]
20+
-s, --starter Choose a starter [choices: "nextjs", "ts-nextjs"]
21+
-e, --example Start from an example
22+
[choices: "algolia-search", "dynamic-app", "sb-countdown", "sb-typist"]
2123
--help Show help [boolean]
2224
```
2325

@@ -31,6 +33,18 @@ npx create-stackbit-app --starter ts-nextjs
3133

3234
If no starter option is provided, [the default starter](https://github.com/stackbit-themes/nextjs-starter) is used.
3335

36+
### Starting from an Example (🧪 Experimental)
37+
38+
Use the `--example` option to start a project from an example. Run the command with the `--help` flag to see a full list of available starters.
39+
40+
```txt
41+
npx create-stackbit-app --example algolia-search
42+
```
43+
44+
This will create a new project matching the name of the example, unless overridden (see below). [See here for a full list of starters](https://github.com/stackbit-themes/stackbit-examples).
45+
46+
_Note: This is an experimental feature. Please [report issues](https://github.com/stackbit/create-stackbit-app/issues/new)._
47+
3448
### Setting Project Directory
3549

3650
Pass a directory name as the only argument when running the command. For example, if you wanted your directory to be name `my-site`, the command would look something like this:
@@ -39,7 +53,7 @@ Pass a directory name as the only argument when running the command. For example
3953
npx create-stackbit-app my-site
4054
```
4155

42-
If no name is provided, the directory will be `my-stackbit-site-[id]`, where `[id]` is a randomly-generated string used to avoid directory conflicts.
56+
If no name is provided, the directory will be `my-stackbit-site` for starters or will match the name of the example if starting from an example. If the directory already exists, a timestamp value will be appended to the directory name to ensure uniqueness.
4357

4458
## Adding Stackbit to Existing Projects
4559

config.js

+6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ const starters = [
99
},
1010
];
1111

12+
const examples = {
13+
repoUrl: "https://github.com/stackbit-themes/stackbit-examples",
14+
directories: ["algolia-search", "dynamic-app", "sb-countdown", "sb-typist"],
15+
};
16+
1217
export default {
1318
defaults: { dirName: "my-stackbit-site", starter: starters[0] },
19+
examples,
1420
starters,
1521
};

index.js

+141-18
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import chalk from "chalk";
44
import { exec } from "child_process";
55
import fs from "fs";
6-
import { nanoid } from "nanoid";
76
import path from "path";
87
import readline from "readline";
98
import util from "util";
@@ -27,6 +26,55 @@ function prompt(question, defaultAnswer) {
2726
});
2827
}
2928

29+
function getDirName(defaultDirName) {
30+
let dirName = args._[0] ?? defaultDirName;
31+
if (fs.existsSync(dirName)) dirName += `-${timestamp}`;
32+
return dirName;
33+
}
34+
35+
async function installDependencies(dirName) {
36+
console.log(`Installing dependencies ...`);
37+
await run(`cd ${dirName} && npm install`);
38+
}
39+
40+
async function initGit(dirName) {
41+
console.log(`Setting up Git ...`);
42+
await run(`rm -rf ${dirName}/.git`);
43+
await run(
44+
`cd ${dirName} && git init && git add . && git commit -m "New Stackbit project"`
45+
);
46+
}
47+
48+
/**
49+
* Given a version string, compare it to a control version. Returns:
50+
*
51+
* -1: version is less than (older) than control
52+
* 0: version and control are identical
53+
* 1: version is greater than (newer) than control
54+
*
55+
* @param {string} version Version string that is being compared
56+
* @param {string} control Version that is being compared against
57+
*/
58+
function compareVersion(version, control) {
59+
// References
60+
let returnValue = 0;
61+
// Return 0 if the versions match.
62+
if (version === control) return returnValue;
63+
// Break the versions into arrays of integers.
64+
const getVersionParts = (str) => str.split(".").map((v) => parseInt(v));
65+
const versionParts = getVersionParts(version);
66+
const controlParts = getVersionParts(control);
67+
// Loop and compare each item.
68+
controlParts.every((controlPart, idx) => {
69+
// If the versions are equal at this part, we move on to the next part.
70+
if (versionParts[idx] === controlPart) return true;
71+
// Otherwise, set the return value, then break out of the loop.
72+
returnValue = versionParts[idx] > controlPart ? 1 : -1;
73+
return false;
74+
});
75+
return returnValue;
76+
}
77+
3078
/* --- Parse CLI Arguments */
3179

3280
const args = yargs(hideBin(process.argv))
@@ -35,6 +83,11 @@ const args = yargs(hideBin(process.argv))
3583
describe: "Choose a starter",
3684
choices: config.starters.map((s) => s.name),
3785
})
86+
.option("example", {
87+
alias: "e",
88+
describe: "Start from an example",
89+
choices: config.examples.directories,
90+
})
3891
.help()
3992
.parse();
4093

@@ -43,27 +96,24 @@ const args = yargs(hideBin(process.argv))
4396
const starter = config.starters.find(
4497
(s) => s.name === (args.starter ?? config.defaults.starter.name)
4598
);
46-
const dirName =
47-
args._[0] ?? `${config.defaults.dirName}-${nanoid(8).toLowerCase()}`;
4899

49-
/* --- New Project --- */
100+
// Current time in seconds.
101+
const timestamp = Math.round(new Date().getTime() / 1000);
102+
103+
/* --- New Project from Starter --- */
50104

51105
async function cloneStarter() {
106+
// Set references
107+
const dirName = getDirName(config.defaults.dirName);
108+
52109
// Clone repo
53110
const cloneCommand = `git clone --depth=1 ${starter.repoUrl} ${dirName}`;
54111
console.log(`\nCreating new project in ${dirName} ...`);
55112
await run(cloneCommand);
56113

57-
// Install dependencies
58-
console.log(`Installing dependencies ...`);
59-
await run(`cd ${dirName} && npm install`);
60-
61-
// Set up git
62-
console.log(`Setting up Git ...`);
63-
await run(`rm -rf ${dirName}/.git`);
64-
await run(
65-
`cd ${dirName} && git init && git add . && git commit -m "New Stackbit project"`
66-
);
114+
// Project Setup
115+
await installDependencies(dirName);
116+
await initGit(dirName);
67117

68118
// Output next steps:
69119
console.log(`
@@ -75,6 +125,66 @@ Follow the instructions for getting Started here:
75125
`);
76126
}
77127

128+
/* --- New Project from Example --- */
129+
130+
async function cloneExample() {
131+
const gitResult = await run("git --version");
132+
const gitVersionMatch = gitResult.stdout.match(/\d+\.\d+\.\d+/);
133+
if (!gitVersionMatch || !gitVersionMatch[0]) {
134+
console.error(
135+
`Cannot determine git version, which is required for starting from an example.`,
136+
`\nPlease report this:`,
137+
chalk.underline(
138+
"https://github.com/stackbit/create-stackbit-app/issues/new"
139+
)
140+
);
141+
process.exit(1);
142+
}
143+
const minGitVersion = "2.25.0";
144+
if (compareVersion(gitVersionMatch[0], minGitVersion) < 0) {
145+
console.error(
146+
`Starting from an example requires git version ${minGitVersion} or later.`,
147+
"Please upgrade"
148+
);
149+
process.exit(1);
150+
}
151+
152+
const dirName = getDirName(args.example);
153+
const tmpDir = `__tmp${timestamp}__`;
154+
console.log(`\nCreating new project in ${dirName} ...`);
155+
156+
try {
157+
// Sparse clone the monorepo.
158+
await run(
159+
`git clone --depth 1 --filter=blob:none --sparse ${config.examples.repoUrl} ${tmpDir}`
160+
);
161+
// Checkout just the example dir.
162+
await run(`cd ${tmpDir} && git sparse-checkout set ${args.example}`);
163+
// Copy out into a new directory within current working directory.
164+
await run(`cp -R ${tmpDir}/${args.example} ${dirName}`);
165+
// Delete the clone.
166+
await run(`rm -rf ${tmpDir}`);
167+
168+
// Project Setup
169+
await installDependencies(dirName);
170+
await initGit(dirName);
171+
} catch (err) {
172+
console.error(err);
173+
if (fs.existsSync(dirName)) await run(`rm -rf ${dirName}`);
174+
if (fs.existsSync(tmpDir)) await run(`rm -rf ${tmpDir}`);
175+
process.exit(1);
176+
}
177+
178+
// Output next steps:
179+
console.log(`
180+
🎉 ${chalk.bold("Your example project is ready!")} 🎉
181+
182+
Follow the instructions and learn more about the example here:
183+
184+
${config.examples.repoUrl}/tree/main/${args.example}#readme
185+
`);
186+
}
187+
78188
/* --- Existing Project --- */
79189

80190
async function integrateStackbit() {
@@ -96,9 +206,22 @@ Visit the following URL to learn more about the integration process:
96206

97207
/* --- Run --- */
98208

99-
const packageJsonFilePath = path.join(process.cwd(), "package.json");
100-
const hasPackageJson = fs.existsSync(packageJsonFilePath);
101-
const runFunc = hasPackageJson ? integrateStackbit : cloneStarter;
102-
await runFunc();
209+
async function doCreate() {
210+
// If the current directory has a package.json file, we assume we're in an
211+
// active project, and will not create a new project.
212+
const packageJsonFilePath = path.join(process.cwd(), "package.json");
213+
if (fs.existsSync(packageJsonFilePath)) return integrateStackbit();
214+
// If both starter and example were specified, throw an error message.
215+
if (args.starter && args.example) {
216+
console.error("[ERROR] Cannot specify a starter and an example.");
217+
process.exit(1);
218+
}
219+
// Start from an example if specified.
220+
if (args.example) return cloneExample();
221+
// Otherwise, use a starter, which falls back to the default if not set.
222+
return cloneStarter();
223+
}
224+
225+
await doCreate();
103226

104227
rl.close();

package-lock.json

+2-19
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "create-stackbit-app",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
44
"description": "Create a new Stackbit site, or add Stackbit to an existing site.",
55
"main": "index.js",
66
"scripts": {
@@ -15,7 +15,6 @@
1515
"type": "module",
1616
"dependencies": {
1717
"chalk": "^5.0.0",
18-
"nanoid": "^3.3.4",
1918
"yargs": "^17.3.1"
2019
}
2120
}

0 commit comments

Comments
 (0)