Skip to content

Commit 94d3fab

Browse files
committed
all examples are copied by default
1 parent 560fc17 commit 94d3fab

3 files changed

Lines changed: 70 additions & 6 deletions

File tree

README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ By default it generates a clean starter workspace with:
77
- a minimal React Router prototype shell
88
- shared `api`, `ui`, `msw`, and `mocks` packages
99
- Turborepo, TypeScript, ESLint, Prettier, and Antora wiring
10+
- every example template copied into `examples/<name>/`
1011

1112
The scaffold no longer ships a demo application in the base workspace.
1213

@@ -16,14 +17,20 @@ The scaffold no longer ships a demo application in the base workspace.
1617
npm create tinker-stack@latest
1718
```
1819

19-
To include an independent example template:
20+
To generate the base workspace without any example folders:
21+
22+
```bash
23+
npm create tinker-stack@latest -- --no-examples
24+
```
25+
26+
To generate only specific example templates:
2027

2128
```bash
2229
npm create tinker-stack@latest -- --example react-router
2330
```
2431

25-
The selected examples are copied into `examples/<name>/` inside the generated project. They are
26-
self-contained and can be deleted without affecting the main workspace.
32+
By default, all available examples are copied into `examples/<name>/` inside the generated project.
33+
They are self-contained and can be deleted without affecting the main workspace.
2734

2835
## Example templates
2936

create/index.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ function parseCliArgs(argv = process.argv.slice(2)) {
1717
debug: undefined,
1818
install: undefined,
1919
examples: [],
20+
noExamples: false,
2021
withExample: false
2122
};
2223
const positionals = [];
@@ -48,6 +49,11 @@ function parseCliArgs(argv = process.argv.slice(2)) {
4849
continue;
4950
}
5051

52+
if (arg === '--no-examples') {
53+
options.noExamples = true;
54+
continue;
55+
}
56+
5157
if (arg === '--cwd') {
5258
const value = argv[i + 1];
5359
if (value && !value.startsWith('-')) {
@@ -129,16 +135,19 @@ function normalizeOptions(input = {}) {
129135
? path.resolve(resolvedCwd, targetDir)
130136
: undefined;
131137

132-
const examples = [
138+
const explicitExamples = [
133139
...normalizeExampleNames(opts.examples),
134140
...normalizeExampleNames(opts.example),
135141
...normalizeExampleNames(cli.examples)
136142
];
137143

138144
if (opts.withExample === true || cli.withExample) {
139-
examples.push(DEFAULT_EXAMPLE);
145+
explicitExamples.push(DEFAULT_EXAMPLE);
140146
}
141147

148+
const noExamples = Boolean(opts.noExamples ?? cli.noExamples);
149+
const hasExplicitExamples = explicitExamples.length > 0;
150+
const examples = hasExplicitExamples ? explicitExamples : noExamples ? [] : getDefaultExamples();
142151
const normalizedExamples = [...new Set(examples)];
143152

144153
return {
@@ -149,10 +158,23 @@ function normalizeOptions(input = {}) {
149158
cwd: resolvedCwd,
150159
templateDir, // always use the built-in template
151160
exampleTemplatesDir,
161+
noExamples,
152162
examples: normalizedExamples
153163
};
154164
}
155165

166+
function getDefaultExamples() {
167+
try {
168+
return fs
169+
.readdirSync(exampleTemplatesDir, { withFileTypes: true })
170+
.filter(entry => entry.isDirectory())
171+
.map(entry => entry.name)
172+
.sort();
173+
} catch {
174+
return [];
175+
}
176+
}
177+
156178
async function main(...args) {
157179
const opts = normalizeOptions(args[0]);
158180

tests/generator.test.mjs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ async function runCommand(cmd, args, options = {}) {
2222

2323
describe('create-tinker-stack generator', () => {
2424
test(
25-
'scaffolds project and passes build checks',
25+
'scaffolds project with all examples by default and passes build checks',
2626
async () => {
2727
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'tinker-stack-'));
2828
const appFolder = 'demo-app';
2929
const projectDir = path.join(tempRoot, appFolder);
30+
const exampleDir = path.join(projectDir, 'examples', 'react-router');
3031

3132
try {
3233
await runCommand('node', [CLI_ENTRY, appFolder], {
@@ -46,6 +47,10 @@ describe('create-tinker-stack generator', () => {
4647

4748
const readme = await readFile(path.join(projectDir, 'README.md'), 'utf8');
4849
expect(readme).toContain('Demo Title');
50+
const examplePackageJson = JSON.parse(
51+
await readFile(path.join(exampleDir, 'package.json'), 'utf8')
52+
);
53+
expect(examplePackageJson.scripts).toHaveProperty('build');
4954

5055
await runCommand('npm', ['install'], { cwd: projectDir });
5156
await runCommand('npm', ['run', 'typecheck'], { cwd: projectDir });
@@ -101,6 +106,36 @@ describe('create-tinker-stack generator', () => {
101106
5 * 60 * 1000
102107
);
103108

109+
test(
110+
'skips copying example templates when --no-examples is passed',
111+
async () => {
112+
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'tinker-stack-'));
113+
const appFolder = 'demo-app';
114+
const projectDir = path.join(tempRoot, appFolder);
115+
116+
try {
117+
await runCommand('node', [CLI_ENTRY, '--no-examples', appFolder], {
118+
env: {
119+
...process.env,
120+
CI: '1',
121+
SKIP_SETUP: '1',
122+
SKIP_FORMAT: '1'
123+
},
124+
cwd: tempRoot
125+
});
126+
127+
const packageJson = JSON.parse(await readFile(path.join(projectDir, 'package.json'), 'utf8'));
128+
expect(packageJson.name).toBe('demo-title');
129+
await expect(readFile(path.join(projectDir, 'examples', 'react-router', 'package.json'), 'utf8'))
130+
.rejects
131+
.toMatchObject({ code: 'ENOENT' });
132+
} finally {
133+
await rm(tempRoot, { recursive: true, force: true });
134+
}
135+
},
136+
5 * 60 * 1000
137+
);
138+
104139
test(
105140
'scaffolds into the current working directory when no target is provided',
106141
async () => {

0 commit comments

Comments
 (0)