vue-micro-router 1.0.5 → 1.0.8

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/README.md CHANGED
@@ -333,26 +333,37 @@ const { title, message, onConfirm } = useMicroState<Attrs>({
333
333
  **Step 2:** Run code generation:
334
334
 
335
335
  ```bash
336
- bun run gen:types
336
+ npx vue-micro-router-gen # auto-detects src/ or app/, outputs src/vue-micro-router.d.ts
337
+ npx vue-micro-router-gen -d src # explicit scan directory
338
+ npx vue-micro-router-gen -o types/router.d.ts # custom output path
337
339
  ```
338
340
 
339
- This scans plugin files, finds components with `export interface Attrs`, and generates `vue-micro-router.d.ts` with all type mappings.
341
+ The script:
342
+ - Scans **all `.ts` files** for `defineFeaturePlugin()` calls (any folder structure)
343
+ - Resolves `@/`, `~/`, `#/` path aliases automatically
344
+ - Finds `.vue` components with `export interface Attrs`
345
+ - Generates `vue-micro-router.d.ts` with Register + AttrsMap augmentations
340
346
 
341
- **Step 3:** Typed everywhere no extra work:
347
+ **Step 3:** Include in tsconfig and enjoy typed everywhere:
348
+
349
+ ```jsonc
350
+ // tsconfig.json — add the generated file
351
+ { "include": ["src/**/*.ts", "src/**/*.vue", "src/vue-micro-router.d.ts"] }
352
+ ```
342
353
 
343
354
  ```ts
344
355
  push('profile', { userId: 42, username: 'Danh' }); // ✅ typed, autocomplete
345
356
  push('profile'); // ❌ TS error: missing required props
346
- push('home'); // ✅ OK (no Attrs defined → optional)
357
+ push('profile', { meta: { title: 'Hi' } }); // ✅ optional fields can be skipped
358
+ push('home'); // ✅ OK (no Attrs → untyped)
347
359
  openDialog('confirm', { title: 'Sure?' }); // ✅ typed
348
- stepWisePush('profile', { userId: 1, username: 'A' }); // ✅ typed
349
360
  ```
350
361
 
351
362
  **Rules:**
352
363
  - Required fields in `Attrs` → must pass in `push()` / `openDialog()`
353
- - Optional fields (`?`) → can omit
364
+ - Optional fields (`?`) → can skip entirely, including the whole props arg
354
365
  - Routes without `export interface Attrs` → `push()` accepts any `Record<string, unknown>`
355
- - Re-run `bun run gen:types` after adding/changing `Attrs` interfaces
366
+ - Re-run `npx vue-micro-router-gen` after adding/changing `Attrs` interfaces
356
367
 
357
368
  ### State Serialization
358
369
 
@@ -604,12 +615,19 @@ Includes page slide/fade transitions, dialog animations, control fade transition
604
615
  ## Development
605
616
 
606
617
  ```bash
607
- bun run lint # ESLint check
608
- bun run lint:fix # Auto-fix lint issues
609
- bun test # Run all tests
610
- bun run typecheck # TypeScript strict check
611
- bun run build # Build package
618
+ bun run lint # ESLint check
619
+ bun run lint:fix # Auto-fix lint issues
620
+ bun test # Run all tests
621
+ bun run typecheck # TypeScript strict check
622
+ bun run build # Build package
623
+ bun run gen:types # Generate vue-micro-router.d.ts (local dev)
612
624
  bun run dev:example # Run example app
625
+ bun run publish:npm # Bump version + build + publish + tag + release
626
+ ```
627
+
628
+ For consumers:
629
+ ```bash
630
+ npx vue-micro-router-gen # Generate type augmentations in consumer project
613
631
  ```
614
632
 
615
633
  ## API Reference
@@ -12,7 +12,7 @@
12
12
  * npx vue-micro-router-gen -d src # scan specific dir
13
13
  * npx vue-micro-router-gen --help
14
14
  */
15
- import { readFileSync, writeFileSync, existsSync, readdirSync } from 'fs';
15
+ import { readFileSync, writeFileSync, existsSync, readdirSync, watch as fsWatch } from 'fs';
16
16
  import { resolve, relative, dirname, join, extname } from 'path';
17
17
 
18
18
  const CWD = process.cwd();
@@ -23,10 +23,12 @@ const MODULE_NAME = 'vue-micro-router';
23
23
  const args = process.argv.slice(2);
24
24
  let outputFile = '';
25
25
  let scanDir = '';
26
+ let watchMode = false;
26
27
 
27
28
  for (let i = 0; i < args.length; i++) {
28
29
  if (args[i] === '-o' || args[i] === '--output') outputFile = args[++i];
29
30
  else if (args[i] === '-d' || args[i] === '--dir') scanDir = args[++i];
31
+ else if (args[i] === '-w' || args[i] === '--watch') watchMode = true;
30
32
  else if (args[i] === '--help' || args[i] === '-h') {
31
33
  console.log(`
32
34
  vue-micro-router-gen — Auto-generate typed augmentations
@@ -36,6 +38,7 @@ Usage: npx vue-micro-router-gen [options]
36
38
  Options:
37
39
  -o, --output <file> Output file (auto: src/vue-micro-router.d.ts or ./vue-micro-router.d.ts)
38
40
  -d, --dir <dir> Directory to scan (auto-detects src/, app/, or cwd)
41
+ -w, --watch Watch .vue/.ts files and regenerate on change
39
42
  -h, --help Show this help
40
43
 
41
44
  How it works:
@@ -200,85 +203,106 @@ function hasAttrs(filePath) {
200
203
  } catch { return false; }
201
204
  }
202
205
 
203
- // ── Main ────────────────────────────────────────────────────────────────────
206
+ // ── Generate ────────────────────────────────────────────────────────────────
204
207
 
205
- const plugins = findPlugins();
206
- if (plugins.length === 0) {
207
- console.log('\nNo defineFeaturePlugin() calls found.');
208
- console.log('Tip: use -d <dir> to specify scan directory');
209
- process.exit(0);
210
- }
208
+ function generate() {
209
+ const plugins = findPlugins();
210
+ if (plugins.length === 0) {
211
+ console.log('No defineFeaturePlugin() calls found.');
212
+ return false;
213
+ }
211
214
 
212
- console.log(`\nPlugins: ${plugins.length}`);
213
- plugins.forEach(p => console.log(` ${p.relPath} → ${p.exportName}`));
215
+ console.log(`Plugins: ${plugins.length}`);
216
+ plugins.forEach(p => console.log(` ${p.relPath} → ${p.exportName}`));
214
217
 
215
- // Extract all entries from plugins
216
- const allEntries = plugins.flatMap(p => extractEntries(p.absPath));
218
+ const allEntries = plugins.flatMap(p => extractEntries(p.absPath));
217
219
 
218
- // Separate by kind and filter for Attrs
219
- const typed = { route: [], dialog: [], control: [] };
220
- const untyped = { route: 0, dialog: 0, control: 0 };
220
+ const typed = { route: [], dialog: [], control: [] };
221
+ const untyped = { route: 0, dialog: 0, control: 0 };
221
222
 
222
- for (const entry of allEntries) {
223
- if (entry.componentFile && hasAttrs(entry.componentFile)) {
224
- typed[entry.kind].push(entry);
225
- } else {
226
- untyped[entry.kind]++;
223
+ for (const entry of allEntries) {
224
+ if (entry.componentFile && hasAttrs(entry.componentFile)) {
225
+ typed[entry.kind].push(entry);
226
+ } else {
227
+ untyped[entry.kind]++;
228
+ }
227
229
  }
228
- }
229
-
230
- console.log(`Routes: ${typed.route.length} typed / ${untyped.route} untyped`);
231
- console.log(`Dialogs: ${typed.dialog.length} typed / ${untyped.dialog} untyped`);
232
- console.log(`Controls: ${typed.control.length} typed / ${untyped.control} untyped`);
233
230
 
234
- // ── Generate ────────────────────────────────────────────────────────────────
231
+ console.log(`Routes: ${typed.route.length} typed / ${untyped.route} untyped`);
232
+ console.log(`Dialogs: ${typed.dialog.length} typed / ${untyped.dialog} untyped`);
233
+ console.log(`Controls: ${typed.control.length} typed / ${untyped.control} untyped`);
235
234
 
236
- const outDir = dirname(resolve(CWD, outputFile));
237
- const lines = [
238
- '/* Auto-generated by vue-micro-router-gen. DO NOT EDIT. */',
239
- '/* Regenerate: npx vue-micro-router-gen */',
240
- '',
241
- ];
242
-
243
- // Import plugins
244
- for (const p of plugins) {
245
- const rel = relative(outDir, resolve(CWD, p.relPath)).replace(/\\/g, '/');
246
- const imp = rel.startsWith('.') ? rel : `./${rel}`;
247
- lines.push(`import type { ${p.exportName} } from '${imp.replace(/\.ts$/, '')}';`);
248
- }
235
+ const outDir = dirname(resolve(CWD, outputFile));
236
+ const lines = [
237
+ '/* Auto-generated by vue-micro-router-gen. DO NOT EDIT. */',
238
+ '/* Regenerate: npx vue-micro-router-gen */',
239
+ '',
240
+ ];
249
241
 
250
- // Import Attrs
251
- for (const [kind, entries] of Object.entries(typed)) {
252
- for (const e of entries) {
253
- const alias = `${kind}_${e.key.replace(/[^a-zA-Z0-9]/g, '_')}`;
254
- const rel = relative(outDir, resolve(CWD, e.componentFile)).replace(/\\/g, '/');
242
+ for (const p of plugins) {
243
+ const rel = relative(outDir, resolve(CWD, p.relPath)).replace(/\\/g, '/');
255
244
  const imp = rel.startsWith('.') ? rel : `./${rel}`;
256
- lines.push(`import type { Attrs as ${alias} } from '${imp}';`);
245
+ lines.push(`import type { ${p.exportName} } from '${imp.replace(/\.ts$/, '')}';`);
257
246
  }
258
- }
259
-
260
- lines.push('');
261
- lines.push(`declare module '${MODULE_NAME}' {`);
262
247
 
263
- // Register
264
- if (plugins.length > 0) {
265
- lines.push(' interface Register {');
266
- lines.push(` plugin: ${plugins.map(p => `typeof ${p.exportName}`).join(' | ')};`);
267
- lines.push(' }');
268
- }
269
-
270
- // AttrsMap
271
- for (const [mapName, kind] of [['RouteAttrsMap', 'route'], ['DialogAttrsMap', 'dialog'], ['ControlAttrsMap', 'control']]) {
272
- if (typed[kind].length > 0) {
273
- lines.push(` interface ${mapName} {`);
274
- for (const e of typed[kind]) {
248
+ for (const [kind, entries] of Object.entries(typed)) {
249
+ for (const e of entries) {
275
250
  const alias = `${kind}_${e.key.replace(/[^a-zA-Z0-9]/g, '_')}`;
276
- lines.push(` '${e.key}': ${alias};`);
251
+ const rel = relative(outDir, resolve(CWD, e.componentFile)).replace(/\\/g, '/');
252
+ const imp = rel.startsWith('.') ? rel : `./${rel}`;
253
+ lines.push(`import type { Attrs as ${alias} } from '${imp}';`);
277
254
  }
255
+ }
256
+
257
+ lines.push('');
258
+ lines.push(`declare module '${MODULE_NAME}' {`);
259
+
260
+ if (plugins.length > 0) {
261
+ lines.push(' interface Register {');
262
+ lines.push(` plugin: ${plugins.map(p => `typeof ${p.exportName}`).join(' | ')};`);
278
263
  lines.push(' }');
279
264
  }
265
+
266
+ for (const [mapName, kind] of [['RouteAttrsMap', 'route'], ['DialogAttrsMap', 'dialog'], ['ControlAttrsMap', 'control']]) {
267
+ if (typed[kind].length > 0) {
268
+ lines.push(` interface ${mapName} {`);
269
+ for (const e of typed[kind]) {
270
+ const alias = `${kind}_${e.key.replace(/[^a-zA-Z0-9]/g, '_')}`;
271
+ lines.push(` '${e.key}': ${alias};`);
272
+ }
273
+ lines.push(' }');
274
+ }
275
+ }
276
+
277
+ lines.push('}', '');
278
+ writeFileSync(resolve(CWD, outputFile), lines.join('\n'));
279
+ console.log(`✅ ${outputFile}`);
280
+ return true;
280
281
  }
281
282
 
282
- lines.push('}', '');
283
- writeFileSync(resolve(CWD, outputFile), lines.join('\n'));
284
- console.log(`\n✅ ${outputFile}`);
283
+ // ── Run ─────────────────────────────────────────────────────────────────────
284
+
285
+ generate();
286
+
287
+ if (watchMode) {
288
+ console.log(`\n👀 Watching ${scanDir}/ for .vue/.ts changes...\n`);
289
+
290
+ let debounce = null;
291
+ const regenerate = () => {
292
+ clearTimeout(debounce);
293
+ debounce = setTimeout(() => {
294
+ console.log(`\n[${new Date().toLocaleTimeString()}] Change detected, regenerating...`);
295
+ generate();
296
+ }, 300);
297
+ };
298
+
299
+ const outputBasename = outputFile.split('/').pop();
300
+
301
+ fsWatch(SCAN_PATH, { recursive: true }, (event, filename) => {
302
+ if (!filename) return;
303
+ // Skip output file to prevent infinite loop
304
+ if (filename.endsWith(outputBasename)) return;
305
+ const ext = extname(filename);
306
+ if (ext === '.vue' || ext === '.ts') regenerate();
307
+ });
308
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-micro-router",
3
- "version": "1.0.5",
3
+ "version": "1.0.8",
4
4
  "type": "module",
5
5
  "description": "Mobile-app-style navigation for Vue 3 — animated page stacks, modal dialogs, HUD controls. No URL routing.",
6
6
  "author": {