Skip to content

Commit 39c575d

Browse files
committed
feat(create): add add-on dev watch mode
1 parent 5fbf262 commit 39c575d

File tree

4 files changed

+82
-2
lines changed

4 files changed

+82
-2
lines changed

docs/creating-add-ons.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ tanstack add-on init
2626
# 5. Compile
2727
tanstack add-on compile
2828

29+
# Optional: continuously rebuild while authoring
30+
tanstack add-on dev
31+
2932
# 6. Test locally
3033
npx serve .add-on -l 9080
3134
tanstack create test --add-ons http://localhost:9080/info.json
@@ -193,6 +196,8 @@ npx serve .add-on -l 9080
193196
tanstack create test-app --add-ons http://localhost:9080/info.json
194197
```
195198

199+
If you are actively editing templates, run `tanstack add-on dev` to auto-refresh `.add-on` and `add-on.json` on file changes.
200+
196201
### Publishing Tips
197202

198203
- Keep add-on source in git (not just compiled output).

packages/cli/src/cli.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
SUPPORTED_PACKAGE_MANAGERS,
1010
addToApp,
1111
compileAddOn,
12+
devAddOn,
1213
compileStarter,
1314
createApp,
1415
createSerializedOptions,
@@ -697,6 +698,14 @@ Remove your node_modules directory and package lock file and re-install.`,
697698
.action(async () => {
698699
await compileAddOn(environment)
699700
})
701+
addOnCommand
702+
.command('dev')
703+
.description(
704+
'Watch project files and continuously refresh .add-on and add-on.json',
705+
)
706+
.action(async () => {
707+
await devAddOn(environment)
708+
})
700709

701710
// === STARTER SUBCOMMAND ===
702711
const starterCommand = program.command('starter')

packages/create/src/custom-add-ons/add-on.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { readFile } from 'node:fs/promises'
1+
import { readFile, watch } from 'node:fs/promises'
22
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
33
import { basename, dirname, resolve } from 'node:path'
44

@@ -36,6 +36,22 @@ const COMPILED_FILE = 'add-on.json'
3636

3737
const ASSETS_DIR = 'assets'
3838

39+
const ADD_ON_DEV_IGNORE_PREFIXES = [
40+
'.add-on/',
41+
'.git/',
42+
'node_modules/',
43+
'.turbo/',
44+
'dist/',
45+
]
46+
47+
function shouldIgnoreDevPath(path: string) {
48+
const normalized = path.replace(/\\/g, '/')
49+
if (normalized === COMPILED_FILE) return true
50+
return ADD_ON_DEV_IGNORE_PREFIXES.some((prefix) =>
51+
normalized.startsWith(prefix),
52+
)
53+
}
54+
3955
export function camelCase(str: string) {
4056
return str
4157
.split(/(\.|-|\/)/)
@@ -218,6 +234,56 @@ export async function initAddOn(environment: Environment) {
218234
await compileAddOn(environment)
219235
}
220236

237+
export async function devAddOn(environment: Environment) {
238+
await initAddOn(environment)
239+
240+
environment.info(
241+
'Add-on dev mode is running.',
242+
'Watching project files and recompiling add-on output on changes. Press Ctrl+C to stop.',
243+
)
244+
245+
const abortController = new AbortController()
246+
let debounceTimeout: ReturnType<typeof setTimeout> | undefined
247+
248+
const rerun = async () => {
249+
try {
250+
await updateAddOnInfo(environment)
251+
await compileAddOn(environment)
252+
environment.info('Add-on updated.', 'Compiled add-on.json and refreshed .add-on')
253+
} catch (error) {
254+
const message = error instanceof Error ? error.message : String(error)
255+
environment.error('Failed to rebuild add-on.', message)
256+
}
257+
}
258+
259+
process.once('SIGINT', () => {
260+
abortController.abort()
261+
})
262+
263+
try {
264+
for await (const event of watch(process.cwd(), {
265+
recursive: true,
266+
signal: abortController.signal,
267+
})) {
268+
const file = event.filename?.toString()
269+
if (!file || shouldIgnoreDevPath(file)) continue
270+
271+
if (debounceTimeout) {
272+
clearTimeout(debounceTimeout)
273+
}
274+
275+
debounceTimeout = setTimeout(() => {
276+
void rerun()
277+
}, 200)
278+
}
279+
} catch (error) {
280+
const message = error instanceof Error ? error.message : String(error)
281+
if (!message.includes('aborted')) {
282+
throw error
283+
}
284+
}
285+
}
286+
221287
export async function loadRemoteAddOn(url: string): Promise<AddOn> {
222288
const response = await fetch(url)
223289
const jsonContent = await response.json()

packages/create/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export {
6161
export { formatCommand, handleSpecialURL } from './utils.js'
6262

6363
export { initStarter, compileStarter } from './custom-add-ons/starter.js'
64-
export { initAddOn, compileAddOn } from './custom-add-ons/add-on.js'
64+
export { initAddOn, compileAddOn, devAddOn } from './custom-add-ons/add-on.js'
6565
export {
6666
createAppOptionsFromPersisted,
6767
createSerializedOptionsFromPersisted,

0 commit comments

Comments
 (0)