Skip to content

Commit 9673322

Browse files
authored
chore: watch mode (#141)
1 parent c75ef40 commit 9673322

File tree

17 files changed

+357
-98
lines changed

17 files changed

+357
-98
lines changed

.github/commit-convention.md

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
## Git Commit Message Convention
2+
3+
> This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).
4+
5+
#### TL;DR:
6+
7+
Messages must be matched by the following regex:
8+
9+
<!-- prettier-ignore -->
10+
```js
11+
/^(revert: )?(feat|fix|docs|dx|refactor|perf|test|workflow|build|ci|chore|types|wip|release|deps)(\(.+\))?: .{1,50}/
12+
```
13+
14+
#### Examples
15+
16+
Appears under "Features" header, `dev` subheader:
17+
18+
```
19+
feat(dev): add 'comments' option
20+
```
21+
22+
Appears under "Bug Fixes" header, `dev` subheader, with a link to issue #28:
23+
24+
```
25+
fix(dev): fix dev error
26+
27+
close #28
28+
```
29+
30+
Appears under "Performance Improvements" header, and under "Breaking Changes" with the breaking change explanation:
31+
32+
```
33+
perf(build): remove 'foo' option
34+
35+
BREAKING CHANGE: The 'foo' option has been removed.
36+
```
37+
38+
The following commit and commit `667ecc1` do not appear in the changelog if they are under the same release. If not, the revert commit appears under the "Reverts" header.
39+
40+
```
41+
revert: feat(compiler): add 'comments' option
42+
43+
This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
44+
```
45+
46+
### Full Message Format
47+
48+
A commit message consists of a **header**, **body** and **footer**. The header has a **type**, **scope** and **subject**:
49+
50+
```
51+
<type>(<scope>): <subject>
52+
<BLANK LINE>
53+
<body>
54+
<BLANK LINE>
55+
<footer>
56+
```
57+
58+
The **header** is mandatory and the **scope** of the header is optional.
59+
60+
### Revert
61+
62+
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body, it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
63+
64+
### Type
65+
66+
If the prefix is `feat`, `fix` or `perf`, it will appear in the changelog. However, if there is any [BREAKING CHANGE](#footer), the commit will always appear in the changelog.
67+
68+
Other prefixes are up to your discretion. Suggested prefixes are `docs`, `chore`, `style`, `refactor`, and `test` for non-changelog related tasks.
69+
70+
### Scope
71+
72+
The scope could be anything specifying the place of the commit change. For example `dev`, `build`, `workflow`, `cli` etc...
73+
74+
### Subject
75+
76+
The subject contains a succinct description of the change:
77+
78+
- use the imperative, present tense: "change" not "changed" nor "changes"
79+
- don't capitalize the first letter
80+
- no dot (.) at the end
81+
82+
### Body
83+
84+
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
85+
The body should include the motivation for the change and contrast this with previous behavior.
86+
87+
### Footer
88+
89+
The footer should contain any information about **Breaking Changes** and is also the place to
90+
reference GitHub issues that this commit **Closes**.
91+
92+
**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.

CONTRIBUTING.md

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# TutorialKit Contributing Guide
2+
3+
Hi! We are really excited that you are interested in contributing to TutorialKit. Before submitting your contribution, please make sure to take a moment and read through the following guide:
4+
5+
## Repo Setup
6+
7+
The TutorialKit repo is a monorepo using pnpm workspaces. The package manager used to install and link dependencies must be [pnpm](https://pnpm.io/).
8+
9+
To develop and test packages:
10+
11+
1. Clone this repository and navigate into the cloned directory.
12+
13+
```
14+
git clone [email protected]:stackblitz/tutorialkit.git
15+
cd tutorialkit
16+
```
17+
18+
2. Run `pnpm install` in project's root folder
19+
20+
3. Run `pnpm run build` to build sources
21+
22+
4. Run `pnpm run dev` to build sources in watch mode
23+
- Development environment starts in http://localhost:4321/
24+
25+
5. Run `pnpm run test` to run core tests
26+
27+
## Testing TutorialKit against external packages
28+
29+
You may wish to test your locally-modified copy of TutorialKit against another package that is using it. For pnpm, after building TutorialKit, you can use [`pnpm.overrides`](https://pnpm.io/package_json#pnpmoverrides). Please note that `pnpm.overrides` must be specified in the root `package.json` and you must first list the package as a dependency in the root `package.json`:
30+
31+
```json
32+
{
33+
"pnpm": {
34+
"overrides": {
35+
"@tutorialkit/astro": "file:../tutorialkit/packages/astro",
36+
"@tutorialkit/components-react": "file:../tutorialkit/packages/components/react",
37+
"@tutorialkit/runtime": "file:../tutorialkit/packages/runtime",
38+
"@tutorialkit/theme": "file:../tutorialkit/packages/theme",
39+
"@tutorialkit/types": "file:../tutorialkit/packages/types"
40+
}
41+
}
42+
}
43+
```
44+
45+
And re-run `pnpm install` to link the package.
46+
47+
## Pull Request Guidelines
48+
49+
- Checkout a topic branch from a base branch, e.g. `main`, and merge back against that branch.
50+
51+
- If adding a new feature:
52+
53+
- Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it.
54+
- Add accompanying test case.
55+
56+
- If fixing a bug:
57+
58+
- If you are resolving a special issue, add `(fix #xxxx[,#xxxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `fix: update entities encoding/decoding (fix #3899)`.
59+
- Provide a detailed description of the bug in the PR. Live demo preferred.
60+
- Add appropriate test coverage if applicable.
61+
62+
- It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging.
63+
64+
- Make sure tests pass!
65+
66+
- Commit messages must follow the [commit message convention](./.github/commit-convention.md) so that changelogs can be automatically generated.

README.md

+4-21
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
<img src="media/logo.svg" alt="tutorialkit-logo" width="440px" height="120px" />
55
</picture>
66
<br>
7-
TutorialKit by <a href="https://stackblitz.com">StackBlitz</a> enables you to create interactive coding tutorials effortlessly, boosting the adoption of
8-
your framework, UI library or design system.
7+
TutorialKit by <a href="https://stackblitz.com">StackBlitz</a> enables you to create interactive coding tutorials effortlessly, boosting the adoption of
8+
your framework, UI library or design system.
99
</p>
1010

1111
<p align="center">
@@ -34,26 +34,9 @@ Read our documentation on [tutorialkit.dev](https://tutorialkit.dev/guides/about
3434
- Install [Node.js](https://nodejs.org/en) v18.18 or above.
3535
- Install [pnpm](https://pnpm.io/).
3636

37-
### Set up
37+
### Contribution
3838

39-
Clone this repository and navigate into the cloned directory.
40-
41-
```
42-
git clone [email protected]:stackblitz/tutorialkit.git
43-
cd tutorialkit
44-
```
45-
46-
TutorialKit uses [pnpm workspaces](https://pnpm.io/workspaces). Just run the install command in the root of the project.
47-
48-
```
49-
pnpm install
50-
```
51-
52-
You can now start the development template of TutorialKit by running:
53-
54-
```
55-
pnpm template:dev
56-
```
39+
See [Contributing Guide](https://github.com/stackblitz/tutorialkit/blob/main/CONTRIBUTING.md).
5740

5841
## Community
5942

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"private": true,
33
"scripts": {
44
"build": "pnpm run --filter=@tutorialkit/* --filter=tutorialkit --filter=create-tutorial build",
5+
"dev": "TUTORIALKIT_DEV=true pnpm -r --parallel --stream --filter='./packages/**' run dev",
56
"changelog": "./scripts/changelog.mjs",
67
"clean": "./scripts/clean.sh",
78
"prepare": "is-ci || husky install",

packages/astro/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
],
2727
"scripts": {
2828
"build": "node ./scripts/build.js",
29+
"dev": "node ./scripts/build.js --watch",
2930
"test": "vitest"
3031
},
3132
"dependencies": {

packages/astro/scripts/build.js

+76-31
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,85 @@
11
import assert from 'node:assert';
2-
import { existsSync } from 'node:fs';
2+
import path from 'node:path';
3+
import { existsSync, rmSync, copyFileSync } from 'node:fs';
34
import { cp, rm } from 'node:fs/promises';
45
import { execa } from 'execa';
6+
import chokidar from 'chokidar';
57
import esbuild from 'esbuild';
6-
import glob from 'fast-glob';
78
import { nodeExternalsPlugin } from 'esbuild-node-externals';
89

9-
// clean dist
10-
await rm('dist', { recursive: true, force: true });
11-
12-
// only do typechecking and emit the type declarations with tsc
13-
execa('tsc', ['--emitDeclarationOnly', '--project', './tsconfig.build.json'], {
14-
stdio: 'inherit',
15-
preferLocal: true,
16-
});
17-
18-
// build with esbuild
19-
await esbuild.build({
20-
entryPoints: ['src/index.ts'],
21-
bundle: true,
22-
tsconfig: './tsconfig.build.json',
23-
platform: 'node',
24-
format: 'esm',
25-
outdir: 'dist',
26-
define: {
27-
'process.env.TUTORIALKIT_DEV': JSON.stringify(process.env.TUTORIALKIT_DEV ?? null),
28-
},
29-
plugins: [nodeExternalsPlugin()],
30-
});
31-
32-
if (existsSync('./dist/default')) {
33-
assert.fail('TypeScript transpiled the default folder, it means that the tsconfig has an issue');
10+
const isWatch = process.argv.includes('--watch');
11+
12+
if (!isWatch) {
13+
// clean dist
14+
await rm('dist', { recursive: true, force: true });
15+
}
16+
17+
await generateTypes();
18+
await buildJS();
19+
await copyDefaultFolder();
20+
21+
async function generateTypes() {
22+
// only do typechecking and emit the type declarations with tsc
23+
const args = [
24+
'--emitDeclarationOnly',
25+
'--project',
26+
'./tsconfig.build.json',
27+
isWatch && '--watch',
28+
'--preserveWatchOutput',
29+
].filter((s) => !!s);
30+
31+
const promise = execa('tsc', args, { stdio: 'inherit', preferLocal: true });
32+
33+
if (!isWatch && existsSync('./dist/default')) {
34+
await promise;
35+
assert.fail('TypeScript transpiled the default folder, it means that the tsconfig has an issue');
36+
}
3437
}
3538

36-
// copy default folder unmodified
37-
await cp('./src/default', './dist/default', { recursive: true });
39+
async function buildJS() {
40+
const context = await esbuild.context({
41+
entryPoints: ['src/index.ts'],
42+
bundle: true,
43+
tsconfig: './tsconfig.build.json',
44+
platform: 'node',
45+
format: 'esm',
46+
outdir: 'dist',
47+
define: {
48+
'process.env.TUTORIALKIT_DEV': JSON.stringify(process.env.TUTORIALKIT_DEV ?? null),
49+
},
50+
plugins: [nodeExternalsPlugin()],
51+
});
3852

39-
// remove test files
40-
await glob('./dist/default/**/*.spec.ts').then((testFiles) => Promise.all(testFiles.map((testFile) => rm(testFile))));
53+
if (isWatch) {
54+
context.watch();
55+
} else {
56+
await context.rebuild();
57+
await context.dispose();
58+
}
59+
}
60+
61+
async function copyDefaultFolder() {
62+
const src = './src/default';
63+
const dist = './dist/default';
64+
await rm(dist, { recursive: true, force: true });
65+
66+
// copy default folder unmodified, without test files
67+
await cp(src, dist, {
68+
recursive: true,
69+
filter: (filename) => !filename.endsWith('.spec.ts'),
70+
});
71+
72+
if (isWatch) {
73+
chokidar.watch(src).on('all', (event, filePath, stats) => {
74+
if (stats?.isDirectory() !== true) {
75+
const target = path.join(dist, path.relative(src, filePath));
76+
77+
if (event === 'unlink') {
78+
rmSync(target);
79+
} else {
80+
copyFileSync(filePath, target);
81+
}
82+
}
83+
});
84+
}
85+
}

packages/cli/scripts/build.js

+28-16
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,34 @@ import esbuild from 'esbuild';
22
import { nodeExternalsPlugin } from 'esbuild-node-externals';
33
import fs from 'node:fs';
44
import { distFolder, outDir } from './_constants.js';
5-
import { success } from './logger.js';
65

7-
fs.rmSync(distFolder, { recursive: true, force: true });
6+
const isWatch = process.argv.includes('--watch');
87

9-
esbuild.build({
10-
entryPoints: ['src/index.ts'],
11-
minify: false,
12-
bundle: true,
13-
platform: 'node',
14-
target: 'node18',
15-
format: 'esm',
16-
outdir: outDir,
17-
define: {
18-
'process.env.TUTORIALKIT_TEMPLATE_PATH': JSON.stringify(process.env.TUTORIALKIT_TEMPLATE_PATH ?? null),
19-
},
20-
plugins: [nodeExternalsPlugin()],
21-
});
8+
if (!isWatch) {
9+
fs.rmSync(distFolder, { recursive: true, force: true });
10+
}
2211

23-
success('Build finished');
12+
await buildJS();
13+
14+
async function buildJS() {
15+
const context = await esbuild.context({
16+
entryPoints: ['src/index.ts'],
17+
minify: false,
18+
bundle: true,
19+
platform: 'node',
20+
target: 'node18',
21+
format: 'esm',
22+
outdir: outDir,
23+
define: {
24+
'process.env.TUTORIALKIT_TEMPLATE_PATH': JSON.stringify(process.env.TUTORIALKIT_TEMPLATE_PATH ?? null),
25+
},
26+
plugins: [nodeExternalsPlugin()],
27+
});
28+
29+
if (isWatch) {
30+
context.watch();
31+
} else {
32+
await context.rebuild();
33+
await context.dispose();
34+
}
35+
}

0 commit comments

Comments
 (0)