@remcostoeten/use-shortcut 1.3.0 → 2.0.0
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/CHANGELOG.md +8 -0
- package/README.md +33 -313
- package/dist/cli/index.mjs +88 -216
- package/dist/index.d.mts +39 -68
- package/dist/index.d.ts +39 -68
- package/dist/index.js +350 -361
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +351 -354
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
- package/src/__tests__/features.test.ts +43 -12
- package/src/builder.ts +37 -476
- package/src/constants.ts +59 -13
- package/src/formatter.ts +37 -22
- package/src/hook.ts +150 -31
- package/src/index.ts +1 -12
- package/src/parser.ts +6 -3
- package/src/runtime/binding.ts +136 -0
- package/src/runtime/conflicts.ts +43 -0
- package/src/runtime/debug.ts +6 -0
- package/src/runtime/guards.ts +82 -0
- package/src/runtime/keys.ts +63 -0
- package/src/runtime/listener.ts +142 -0
- package/src/runtime/recording.ts +39 -0
- package/src/runtime/types.ts +48 -0
- package/src/types.ts +16 -19
package/dist/cli/index.mjs
CHANGED
|
@@ -1,35 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
5
|
-
import { join, dirname } from "path";
|
|
6
|
-
import { fileURLToPath } from "url";
|
|
7
|
-
|
|
8
|
-
// cli/templates.ts
|
|
9
|
-
function getArchitectureTemplates(framework) {
|
|
10
|
-
const integrationSection = framework === "next" ? `## Next.js Integration
|
|
11
|
-
|
|
12
|
-
1. Create a client provider wrapper at \`app/shortcut-provider.tsx\` and render \`<ShortcutProvider />\` there.
|
|
13
|
-
2. Render that provider inside \`app/layout.tsx\` around your app shell.
|
|
14
|
-
3. Keep page/server components pure; shortcut handlers stay in client components.
|
|
15
|
-
` : `## React Integration
|
|
16
|
-
|
|
17
|
-
1. Wrap your app root (for example in \`main.tsx\`) with \`<ShortcutProvider />\`.
|
|
18
|
-
2. Keep handlers in a top-level client component and pass them to the provider via \`handlers\`.
|
|
19
|
-
3. Use \`useShortcutManager()\` inside feature components to toggle scopes and bindings.
|
|
20
|
-
`;
|
|
21
|
-
return {
|
|
22
|
-
"scopes.ts": `export const shortcutScopes = ["global", "navigation", "editor", "modal"] as const
|
|
2
|
+
import{existsSync as h,mkdirSync as f,readFileSync as w,writeFileSync as x}from"fs";import{join as l,dirname as B}from"path";import{fileURLToPath as I}from"url";function m(t){return{"scopes.ts":`/** App-defined scope catalog used by the scaffolded provider. */
|
|
3
|
+
export const shortcutScopes = ["global", "navigation", "editor", "modal"] as const
|
|
23
4
|
|
|
24
5
|
export type ShortcutScope = (typeof shortcutScopes)[number]
|
|
25
6
|
|
|
7
|
+
/** Default active scopes at provider boot. */
|
|
26
8
|
export const defaultActiveScopes: ShortcutScope[] = ["global", "navigation"]
|
|
27
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Normalizes a scope input into an array form.
|
|
12
|
+
*
|
|
13
|
+
* @param scopes - One or many scope names
|
|
14
|
+
* @returns Array of scope names
|
|
15
|
+
*/
|
|
28
16
|
export function normalizeScopes(scopes: ShortcutScope | ShortcutScope[]): ShortcutScope[] {
|
|
29
17
|
return Array.isArray(scopes) ? scopes : [scopes]
|
|
30
18
|
}
|
|
31
|
-
`,
|
|
32
|
-
"registry.ts": `import type { HandlerOptions } from "@remcostoeten/use-shortcut"
|
|
19
|
+
`,"registry.ts":`import type { HandlerOptions } from "@remcostoeten/use-shortcut"
|
|
33
20
|
import type { ShortcutScope } from "./scopes"
|
|
34
21
|
|
|
35
22
|
export type ShortcutDefinition = {
|
|
@@ -64,8 +51,7 @@ export const shortcutRegistry = {
|
|
|
64
51
|
|
|
65
52
|
export type ShortcutActionId = keyof typeof shortcutRegistry
|
|
66
53
|
export type ShortcutBindings = Record<ShortcutActionId, string | string[]>
|
|
67
|
-
`,
|
|
68
|
-
"types.ts": `import type { ShortcutActionId, ShortcutBindings } from "./registry"
|
|
54
|
+
`,"types.ts":`import type { ShortcutActionId, ShortcutBindings } from "./registry"
|
|
69
55
|
import type { ShortcutScope } from "./scopes"
|
|
70
56
|
|
|
71
57
|
export type ShortcutHandlers = Record<ShortcutActionId, (event: KeyboardEvent) => void>
|
|
@@ -96,12 +82,16 @@ export type ShortcutContextValue = {
|
|
|
96
82
|
actions: ShortcutActions
|
|
97
83
|
meta: ShortcutMeta
|
|
98
84
|
}
|
|
99
|
-
`,
|
|
100
|
-
"runtime.ts": `import type { ShortcutMap } from "@remcostoeten/use-shortcut"
|
|
85
|
+
`,"runtime.ts":`import type { ShortcutMap } from "@remcostoeten/use-shortcut"
|
|
101
86
|
import { shortcutRegistry, type ShortcutActionId, type ShortcutBindings } from "./registry"
|
|
102
87
|
import type { ShortcutHandlers } from "./types"
|
|
103
88
|
|
|
104
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Returns a fresh binding object seeded from \`shortcutRegistry\` defaults.
|
|
91
|
+
*
|
|
92
|
+
* @returns Default shortcut bindings for every registered action
|
|
93
|
+
*/
|
|
94
|
+
export function createDefaultShortcutBindings(): ShortcutBindings {
|
|
105
95
|
const bindings = {} as ShortcutBindings
|
|
106
96
|
|
|
107
97
|
for (const actionId of Object.keys(shortcutRegistry) as ShortcutActionId[]) {
|
|
@@ -111,7 +101,15 @@ export function createDefaultBindings(): ShortcutBindings {
|
|
|
111
101
|
return bindings
|
|
112
102
|
}
|
|
113
103
|
|
|
114
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Builds a \`ShortcutMap\` by combining current bindings with action handlers.
|
|
106
|
+
* Actions without handlers are skipped.
|
|
107
|
+
*
|
|
108
|
+
* @param bindings - Current binding state (defaults + user overrides)
|
|
109
|
+
* @param handlers - Runtime action handlers from the consuming app
|
|
110
|
+
* @returns Runtime shortcut map consumable by \`registerShortcutMap\`
|
|
111
|
+
*/
|
|
112
|
+
export function buildShortcutMap(bindings: ShortcutBindings, handlers: ShortcutHandlers): ShortcutMap {
|
|
115
113
|
const map: ShortcutMap = {}
|
|
116
114
|
|
|
117
115
|
for (const actionId of Object.keys(shortcutRegistry) as ShortcutActionId[]) {
|
|
@@ -135,16 +133,22 @@ export function createShortcutMap(bindings: ShortcutBindings, handlers: Shortcut
|
|
|
135
133
|
|
|
136
134
|
return map
|
|
137
135
|
}
|
|
138
|
-
`,
|
|
139
|
-
"storage.ts": `import type { ShortcutActionId, ShortcutBindings } from "./registry"
|
|
136
|
+
`,"storage.ts":`import type { ShortcutActionId, ShortcutBindings } from "./registry"
|
|
140
137
|
|
|
138
|
+
/** Default localStorage key used by the scaffolded shortcut provider. */
|
|
141
139
|
export const DEFAULT_SHORTCUT_STORAGE_KEY = "app-shortcut-bindings"
|
|
142
140
|
|
|
143
|
-
function
|
|
141
|
+
function _isBindingValue(value: unknown): value is string | string[] {
|
|
144
142
|
if (typeof value === "string") return true
|
|
145
143
|
return Array.isArray(value) && value.every((entry) => typeof entry === "string")
|
|
146
144
|
}
|
|
147
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Loads persisted shortcut binding overrides from localStorage.
|
|
148
|
+
*
|
|
149
|
+
* @param storageKey - Storage key namespace
|
|
150
|
+
* @returns Partial binding overrides keyed by action id
|
|
151
|
+
*/
|
|
148
152
|
export function loadShortcutBindings(storageKey: string): Partial<ShortcutBindings> {
|
|
149
153
|
if (typeof window === "undefined") return {}
|
|
150
154
|
|
|
@@ -158,7 +162,7 @@ export function loadShortcutBindings(storageKey: string): Partial<ShortcutBindin
|
|
|
158
162
|
const result: Partial<ShortcutBindings> = {}
|
|
159
163
|
|
|
160
164
|
for (const [actionId, value] of Object.entries(parsed as Record<string, unknown>)) {
|
|
161
|
-
if (!
|
|
165
|
+
if (!_isBindingValue(value)) continue
|
|
162
166
|
result[actionId as ShortcutActionId] = value
|
|
163
167
|
}
|
|
164
168
|
|
|
@@ -168,6 +172,12 @@ export function loadShortcutBindings(storageKey: string): Partial<ShortcutBindin
|
|
|
168
172
|
}
|
|
169
173
|
}
|
|
170
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Persists all shortcut bindings to localStorage.
|
|
177
|
+
*
|
|
178
|
+
* @param storageKey - Storage key namespace
|
|
179
|
+
* @param bindings - Full current binding set
|
|
180
|
+
*/
|
|
171
181
|
export function saveShortcutBindings(storageKey: string, bindings: ShortcutBindings): void {
|
|
172
182
|
if (typeof window === "undefined") return
|
|
173
183
|
|
|
@@ -178,6 +188,11 @@ export function saveShortcutBindings(storageKey: string, bindings: ShortcutBindi
|
|
|
178
188
|
}
|
|
179
189
|
}
|
|
180
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Removes persisted shortcut bindings from localStorage.
|
|
193
|
+
*
|
|
194
|
+
* @param storageKey - Storage key namespace
|
|
195
|
+
*/
|
|
181
196
|
export function clearShortcutBindings(storageKey: string): void {
|
|
182
197
|
if (typeof window === "undefined") return
|
|
183
198
|
|
|
@@ -187,8 +202,7 @@ export function clearShortcutBindings(storageKey: string): void {
|
|
|
187
202
|
// Ignore quota/security errors.
|
|
188
203
|
}
|
|
189
204
|
}
|
|
190
|
-
`,
|
|
191
|
-
"provider.tsx": `"use client"
|
|
205
|
+
`,"provider.tsx":`"use client"
|
|
192
206
|
|
|
193
207
|
import {
|
|
194
208
|
createContext,
|
|
@@ -201,7 +215,7 @@ import {
|
|
|
201
215
|
} from "react"
|
|
202
216
|
import { registerShortcutMap, useShortcut, type UseShortcutOptions } from "@remcostoeten/use-shortcut"
|
|
203
217
|
import { shortcutRegistry, type ShortcutActionId, type ShortcutBindings } from "./registry"
|
|
204
|
-
import {
|
|
218
|
+
import { buildShortcutMap, createDefaultShortcutBindings } from "./runtime"
|
|
205
219
|
import { defaultActiveScopes, normalizeScopes, type ShortcutScope } from "./scopes"
|
|
206
220
|
import {
|
|
207
221
|
DEFAULT_SHORTCUT_STORAGE_KEY,
|
|
@@ -211,9 +225,9 @@ import {
|
|
|
211
225
|
} from "./storage"
|
|
212
226
|
import type { ShortcutContextValue, ShortcutHandlers } from "./types"
|
|
213
227
|
|
|
214
|
-
const
|
|
228
|
+
const _ShortcutContext = createContext<ShortcutContextValue | null>(null)
|
|
215
229
|
|
|
216
|
-
function
|
|
230
|
+
function _mergeBindings(defaultBindings: ShortcutBindings, persisted: Partial<ShortcutBindings>): ShortcutBindings {
|
|
217
231
|
const merged = { ...defaultBindings }
|
|
218
232
|
|
|
219
233
|
for (const actionId of Object.keys(defaultBindings) as ShortcutActionId[]) {
|
|
@@ -226,17 +240,17 @@ function mergeBindings(defaultBindings: ShortcutBindings, persisted: Partial<Sho
|
|
|
226
240
|
return merged
|
|
227
241
|
}
|
|
228
242
|
|
|
229
|
-
function
|
|
243
|
+
function _isSameBinding(a: string | string[], b: string | string[]): boolean {
|
|
230
244
|
const left = Array.isArray(a) ? a.join("|") : a
|
|
231
245
|
const right = Array.isArray(b) ? b.join("|") : b
|
|
232
246
|
return left === right
|
|
233
247
|
}
|
|
234
248
|
|
|
235
|
-
function
|
|
236
|
-
const defaults =
|
|
249
|
+
function _hasBindingOverrides(bindings: ShortcutBindings): boolean {
|
|
250
|
+
const defaults = createDefaultShortcutBindings()
|
|
237
251
|
|
|
238
252
|
for (const actionId of Object.keys(defaults) as ShortcutActionId[]) {
|
|
239
|
-
if (!
|
|
253
|
+
if (!_isSameBinding(bindings[actionId], defaults[actionId])) {
|
|
240
254
|
return true
|
|
241
255
|
}
|
|
242
256
|
}
|
|
@@ -244,6 +258,7 @@ function hasBindingOverrides(bindings: ShortcutBindings): boolean {
|
|
|
244
258
|
return false
|
|
245
259
|
}
|
|
246
260
|
|
|
261
|
+
/** Props for the scaffolded \`ShortcutProvider\`. */
|
|
247
262
|
export type ShortcutProviderProps = {
|
|
248
263
|
children: ReactNode
|
|
249
264
|
handlers: ShortcutHandlers
|
|
@@ -254,6 +269,9 @@ export type ShortcutProviderProps = {
|
|
|
254
269
|
shortcutOptions?: Omit<UseShortcutOptions, "activeScopes" | "disabled">
|
|
255
270
|
}
|
|
256
271
|
|
|
272
|
+
/**
|
|
273
|
+
* App-level provider that binds action handlers, scope state, and optional binding persistence.
|
|
274
|
+
*/
|
|
257
275
|
export function ShortcutProvider({
|
|
258
276
|
children,
|
|
259
277
|
handlers,
|
|
@@ -265,7 +283,7 @@ export function ShortcutProvider({
|
|
|
265
283
|
}: ShortcutProviderProps) {
|
|
266
284
|
const [activeScopes, setActiveScopes] = useState<ShortcutScope[]>(initialScopes)
|
|
267
285
|
const [enabled, setEnabled] = useState(initialEnabled)
|
|
268
|
-
const [bindings, setBindings] = useState<ShortcutBindings>(() =>
|
|
286
|
+
const [bindings, setBindings] = useState<ShortcutBindings>(() => createDefaultShortcutBindings())
|
|
269
287
|
|
|
270
288
|
useEffect(() => {
|
|
271
289
|
setEnabled(initialEnabled)
|
|
@@ -275,7 +293,7 @@ export function ShortcutProvider({
|
|
|
275
293
|
if (!persistBindings) return
|
|
276
294
|
|
|
277
295
|
const persisted = loadShortcutBindings(storageKey)
|
|
278
|
-
setBindings((current) =>
|
|
296
|
+
setBindings((current) => _mergeBindings(current, persisted))
|
|
279
297
|
}, [persistBindings, storageKey])
|
|
280
298
|
|
|
281
299
|
useEffect(() => {
|
|
@@ -283,7 +301,7 @@ export function ShortcutProvider({
|
|
|
283
301
|
saveShortcutBindings(storageKey, bindings)
|
|
284
302
|
}, [bindings, persistBindings, storageKey])
|
|
285
303
|
|
|
286
|
-
const shortcutMap = useMemo(() =>
|
|
304
|
+
const shortcutMap = useMemo(() => buildShortcutMap(bindings, handlers), [bindings, handlers])
|
|
287
305
|
|
|
288
306
|
const $ = useShortcut({
|
|
289
307
|
...shortcutOptions,
|
|
@@ -331,7 +349,7 @@ export function ShortcutProvider({
|
|
|
331
349
|
}, [])
|
|
332
350
|
|
|
333
351
|
const resetBindings = useCallback(() => {
|
|
334
|
-
setBindings(
|
|
352
|
+
setBindings(createDefaultShortcutBindings())
|
|
335
353
|
if (persistBindings) clearShortcutBindings(storageKey)
|
|
336
354
|
}, [persistBindings, storageKey])
|
|
337
355
|
|
|
@@ -352,7 +370,7 @@ export function ShortcutProvider({
|
|
|
352
370
|
setEnabled,
|
|
353
371
|
},
|
|
354
372
|
meta: {
|
|
355
|
-
hasBindingOverrides:
|
|
373
|
+
hasBindingOverrides: _hasBindingOverrides(bindings),
|
|
356
374
|
availableActions: Object.keys(shortcutRegistry) as ShortcutActionId[],
|
|
357
375
|
},
|
|
358
376
|
}),
|
|
@@ -369,11 +387,14 @@ export function ShortcutProvider({
|
|
|
369
387
|
],
|
|
370
388
|
)
|
|
371
389
|
|
|
372
|
-
return <
|
|
390
|
+
return <_ShortcutContext.Provider value={contextValue}>{children}</_ShortcutContext.Provider>
|
|
373
391
|
}
|
|
374
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Reads the shortcut manager context exposed by \`ShortcutProvider\`.
|
|
395
|
+
*/
|
|
375
396
|
export function useShortcutManager(): ShortcutContextValue {
|
|
376
|
-
const context = useContext(
|
|
397
|
+
const context = useContext(_ShortcutContext)
|
|
377
398
|
|
|
378
399
|
if (!context) {
|
|
379
400
|
throw new Error("useShortcutManager must be used within <ShortcutProvider>")
|
|
@@ -381,8 +402,7 @@ export function useShortcutManager(): ShortcutContextValue {
|
|
|
381
402
|
|
|
382
403
|
return context
|
|
383
404
|
}
|
|
384
|
-
`,
|
|
385
|
-
"index.ts": `export { ShortcutProvider, useShortcutManager, type ShortcutProviderProps } from "./provider"
|
|
405
|
+
`,"index.ts":`export { ShortcutProvider, useShortcutManager, type ShortcutProviderProps } from "./provider"
|
|
386
406
|
export { shortcutRegistry, type ShortcutActionId, type ShortcutBindings } from "./registry"
|
|
387
407
|
export { defaultActiveScopes, shortcutScopes, type ShortcutScope } from "./scopes"
|
|
388
408
|
export type {
|
|
@@ -392,8 +412,7 @@ export type {
|
|
|
392
412
|
ShortcutMeta,
|
|
393
413
|
ShortcutState,
|
|
394
414
|
} from "./types"
|
|
395
|
-
`,
|
|
396
|
-
"README.md": `# Shortcut Architecture Scaffold
|
|
415
|
+
`,"README.md":`# Shortcut Architecture Scaffold
|
|
397
416
|
|
|
398
417
|
This folder was generated by \`use-shortcut scaffold\`.
|
|
399
418
|
|
|
@@ -412,172 +431,25 @@ It follows a scalable architecture with a strict split between:
|
|
|
412
431
|
3. Optionally expose a user-configurable key in your settings UI through \`useShortcutManager().actions.setBinding\`.
|
|
413
432
|
4. Activate scopes from feature boundaries (for example editor route enters \`editor\` scope).
|
|
414
433
|
|
|
415
|
-
${
|
|
434
|
+
${t==="next"?"## Next.js Integration\n\n1. Create a client provider wrapper at `app/shortcut-provider.tsx` and render `<ShortcutProvider />` there.\n2. Render that provider inside `app/layout.tsx` around your app shell.\n3. Keep page/server components pure; shortcut handlers stay in client components.\n":"## React Integration\n\n1. Wrap your app root (for example in `main.tsx`) with `<ShortcutProvider />`.\n2. Keep handlers in a top-level client component and pass them to the provider via `handlers`.\n3. Use `useShortcutManager()` inside feature components to toggle scopes and bindings.\n"}
|
|
416
435
|
## Rules For Scale
|
|
417
436
|
|
|
418
437
|
- Keep handlers side-effect focused and feature-owned; keep the registry declarative.
|
|
419
438
|
- Use scopes instead of conditionals in handlers.
|
|
420
439
|
- Persist only key bindings, not executable handlers.
|
|
421
440
|
- Treat \`registry.ts\` as your architectural contract.
|
|
422
|
-
`
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
green
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
};
|
|
437
|
-
function log(message, color = COLORS.reset) {
|
|
438
|
-
console.log(`${color}${message}${COLORS.reset}`);
|
|
439
|
-
}
|
|
440
|
-
function getSrcPath() {
|
|
441
|
-
return join(__dirname, "..", "src");
|
|
442
|
-
}
|
|
443
|
-
function getCopyDestPath(targetDir) {
|
|
444
|
-
return join(process.cwd(), targetDir, "use-shortcut");
|
|
445
|
-
}
|
|
446
|
-
function getScaffoldDestPath(targetDir, dir) {
|
|
447
|
-
return join(process.cwd(), targetDir, dir);
|
|
448
|
-
}
|
|
449
|
-
function getFlagValue(args, flag, fallback) {
|
|
450
|
-
const flagIndex = args.indexOf(flag);
|
|
451
|
-
if (flagIndex === -1) return fallback;
|
|
452
|
-
const value = args[flagIndex + 1];
|
|
453
|
-
if (!value || value.startsWith("--")) return fallback;
|
|
454
|
-
return value;
|
|
455
|
-
}
|
|
456
|
-
function hasFlag(args, flag) {
|
|
457
|
-
return args.includes(flag);
|
|
458
|
-
}
|
|
459
|
-
var CORE_FILES = [
|
|
460
|
-
"index.ts",
|
|
461
|
-
"hook.ts",
|
|
462
|
-
"builder.ts",
|
|
463
|
-
"types.ts",
|
|
464
|
-
"parser.ts",
|
|
465
|
-
"constants.ts",
|
|
466
|
-
"formatter.ts"
|
|
467
|
-
];
|
|
468
|
-
function init(targetDir = "hooks", force = false) {
|
|
469
|
-
const srcPath = getSrcPath();
|
|
470
|
-
const destPath = getCopyDestPath(targetDir);
|
|
471
|
-
log("\nuse-shortcut CLI\n", COLORS.cyan);
|
|
472
|
-
if (existsSync(destPath) && !force) {
|
|
473
|
-
log(`Directory already exists: ${destPath}`, COLORS.yellow);
|
|
474
|
-
log("Use --force to overwrite existing files\n", COLORS.dim);
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
|
-
mkdirSync(destPath, { recursive: true });
|
|
478
|
-
let written = 0;
|
|
479
|
-
for (const file of CORE_FILES) {
|
|
480
|
-
const srcFile = join(srcPath, file);
|
|
481
|
-
const destFile = join(destPath, file);
|
|
482
|
-
if (!existsSync(srcFile)) {
|
|
483
|
-
log(`Source file not found: ${file}`, COLORS.yellow);
|
|
484
|
-
continue;
|
|
485
|
-
}
|
|
486
|
-
const content = readFileSync(srcFile, "utf-8");
|
|
487
|
-
writeFileSync(destFile, content);
|
|
488
|
-
written += 1;
|
|
489
|
-
log(` wrote ${file}`, COLORS.green);
|
|
490
|
-
}
|
|
491
|
-
log(`
|
|
492
|
-
Copied ${written} files to:`, COLORS.green);
|
|
493
|
-
log(` ${destPath}
|
|
494
|
-
`, COLORS.dim);
|
|
495
|
-
log("Usage:", COLORS.cyan);
|
|
496
|
-
log(` import { useShortcut } from "@/${targetDir}/use-shortcut"`, COLORS.dim);
|
|
497
|
-
log(" const $ = useShortcut()", COLORS.dim);
|
|
498
|
-
log(' $.mod.key("k").on(() => console.log("Search"))\n', COLORS.dim);
|
|
499
|
-
}
|
|
500
|
-
function scaffoldArchitecture(framework, targetDir = "src", dir = "shortcuts", force = false) {
|
|
501
|
-
const destPath = getScaffoldDestPath(targetDir, dir);
|
|
502
|
-
const templates = getArchitectureTemplates(framework);
|
|
503
|
-
log("\nuse-shortcut CLI\n", COLORS.cyan);
|
|
504
|
-
log(`Scaffolding ${framework} architecture in ${destPath}
|
|
505
|
-
`, COLORS.dim);
|
|
506
|
-
mkdirSync(destPath, { recursive: true });
|
|
507
|
-
let written = 0;
|
|
508
|
-
let skipped = 0;
|
|
509
|
-
for (const [file, content] of Object.entries(templates)) {
|
|
510
|
-
const outputPath = join(destPath, file);
|
|
511
|
-
if (existsSync(outputPath) && !force) {
|
|
512
|
-
skipped += 1;
|
|
513
|
-
log(` skipped ${file} (already exists)`, COLORS.yellow);
|
|
514
|
-
continue;
|
|
515
|
-
}
|
|
516
|
-
writeFileSync(outputPath, content);
|
|
517
|
-
written += 1;
|
|
518
|
-
log(` wrote ${file}`, COLORS.green);
|
|
519
|
-
}
|
|
520
|
-
log("", COLORS.reset);
|
|
521
|
-
log(`Architecture scaffold complete: ${written} written, ${skipped} skipped.`, COLORS.green);
|
|
522
|
-
log(`Location: ${destPath}
|
|
523
|
-
`, COLORS.dim);
|
|
524
|
-
log("Next steps:", COLORS.cyan);
|
|
525
|
-
log(` 1. Open ${join(targetDir, dir, "registry.ts")} and define your action catalog`, COLORS.dim);
|
|
526
|
-
log(` 2. Wire app handlers into <ShortcutProvider handlers={...} />`, COLORS.dim);
|
|
527
|
-
log(` 3. Toggle scopes from feature boundaries via useShortcutManager()`, COLORS.dim);
|
|
528
|
-
log(` 4. Optionally expose setBinding/resetBinding in your settings UI
|
|
529
|
-
`, COLORS.dim);
|
|
530
|
-
}
|
|
531
|
-
function printHelp() {
|
|
532
|
-
log("\nuse-shortcut CLI\n", COLORS.cyan);
|
|
533
|
-
log("Commands:", COLORS.yellow);
|
|
534
|
-
log(" init [--target hooks] [--force]", COLORS.dim);
|
|
535
|
-
log(" Copy source files into your project (shadcn-style).", COLORS.dim);
|
|
536
|
-
log("", COLORS.dim);
|
|
537
|
-
log(" scaffold [--framework next|react] [--target src] [--dir shortcuts] [--force]", COLORS.dim);
|
|
538
|
-
log(" Generate a scalable app shortcut architecture.", COLORS.dim);
|
|
539
|
-
log("", COLORS.dim);
|
|
540
|
-
log(" init --architecture", COLORS.dim);
|
|
541
|
-
log(" Alias for scaffold with defaults.\n", COLORS.dim);
|
|
542
|
-
}
|
|
543
|
-
function parseFramework(value) {
|
|
544
|
-
if (value === "next" || value === "react") {
|
|
545
|
-
return value;
|
|
546
|
-
}
|
|
547
|
-
log(`Invalid framework: ${value}. Expected "next" or "react".`, COLORS.red);
|
|
548
|
-
process.exit(1);
|
|
549
|
-
}
|
|
550
|
-
function main() {
|
|
551
|
-
const args = process.argv.slice(2);
|
|
552
|
-
const command = args[0];
|
|
553
|
-
const isHelp = !command || command === "--help" || command === "-h" || command === "help";
|
|
554
|
-
if (isHelp) {
|
|
555
|
-
printHelp();
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
if (command === "init") {
|
|
559
|
-
if (hasFlag(args, "--architecture") || hasFlag(args, "--app") || hasFlag(args, "--scaffold")) {
|
|
560
|
-
const framework = parseFramework(getFlagValue(args, "--framework", "next"));
|
|
561
|
-
const targetDir2 = getFlagValue(args, "--target", "src");
|
|
562
|
-
const dir = getFlagValue(args, "--dir", "shortcuts");
|
|
563
|
-
const force2 = hasFlag(args, "--force");
|
|
564
|
-
scaffoldArchitecture(framework, targetDir2, dir, force2);
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
const targetDir = getFlagValue(args, "--target", "hooks");
|
|
568
|
-
const force = hasFlag(args, "--force");
|
|
569
|
-
init(targetDir, force);
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
if (command === "scaffold" || command === "architecture") {
|
|
573
|
-
const framework = parseFramework(getFlagValue(args, "--framework", "next"));
|
|
574
|
-
const targetDir = getFlagValue(args, "--target", "src");
|
|
575
|
-
const dir = getFlagValue(args, "--dir", "shortcuts");
|
|
576
|
-
const force = hasFlag(args, "--force");
|
|
577
|
-
scaffoldArchitecture(framework, targetDir, dir, force);
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
printHelp();
|
|
581
|
-
process.exit(1);
|
|
582
|
-
}
|
|
583
|
-
main();
|
|
441
|
+
`}}var k=I(import.meta.url),_=B(k),e={reset:"\x1B[0m",green:"\x1B[32m",cyan:"\x1B[36m",yellow:"\x1B[33m",dim:"\x1B[2m",red:"\x1B[31m"};function r(t,o=e.reset){console.log(`${o}${t}${e.reset}`)}function O(){return l(_,"..","src")}function R(t){return l(process.cwd(),t,"use-shortcut")}function C(t,o){return l(process.cwd(),t,o)}function p(t,o,a){let s=t.indexOf(o);if(s===-1)return a;let n=t[s+1];return!n||n.startsWith("--")?a:n}function g(t,o){return t.includes(o)}var P=["index.ts","hook.ts","builder.ts","types.ts","parser.ts","constants.ts","formatter.ts","runtime/types.ts","runtime/binding.ts","runtime/conflicts.ts","runtime/debug.ts","runtime/guards.ts","runtime/keys.ts","runtime/listener.ts","runtime/recording.ts"];function E(t="hooks",o=!1){let a=O(),s=R(t);if(r(`
|
|
442
|
+
use-shortcut CLI
|
|
443
|
+
`,e.cyan),h(s)&&!o){r(`Directory already exists: ${s}`,e.yellow),r(`Use --force to overwrite existing files
|
|
444
|
+
`,e.dim);return}f(s,{recursive:!0});let n=0;for(let i of P){let c=l(a,i),d=l(s,i);if(f(B(d),{recursive:!0}),!h(c)){r(`Source file not found: ${i}`,e.yellow);continue}let u=w(c,"utf-8");x(d,u),n+=1,r(` wrote ${i}`,e.green)}r(`
|
|
445
|
+
Copied ${n} files to:`,e.green),r(` ${s}
|
|
446
|
+
`,e.dim),r("Usage:",e.cyan),r(` import { useShortcut } from "@/${t}/use-shortcut"`,e.dim),r(" const $ = useShortcut()",e.dim),r(` $.mod.key("k").on(() => console.log("Search"))
|
|
447
|
+
`,e.dim)}function y(t,o="src",a="shortcuts",s=!1){let n=C(o,a),i=m(t);r(`
|
|
448
|
+
use-shortcut CLI
|
|
449
|
+
`,e.cyan),r(`Scaffolding ${t} architecture in ${n}
|
|
450
|
+
`,e.dim),f(n,{recursive:!0});let c=0,d=0;for(let[u,A]of Object.entries(i)){let S=l(n,u);if(h(S)&&!s){d+=1,r(` skipped ${u} (already exists)`,e.yellow);continue}x(S,A),c+=1,r(` wrote ${u}`,e.green)}r("",e.reset),r(`Architecture scaffold complete: ${c} written, ${d} skipped.`,e.green),r(`Location: ${n}
|
|
451
|
+
`,e.dim),r("Next steps:",e.cyan),r(` 1. Open ${l(o,a,"registry.ts")} and define your action catalog`,e.dim),r(" 2. Wire app handlers into <ShortcutProvider handlers={...} />",e.dim),r(" 3. Toggle scopes from feature boundaries via useShortcutManager()",e.dim),r(` 4. Optionally expose setBinding/resetBinding in your settings UI
|
|
452
|
+
`,e.dim)}function b(){r(`
|
|
453
|
+
use-shortcut CLI
|
|
454
|
+
`,e.cyan),r("Commands:",e.yellow),r(" init [--target hooks] [--force]",e.dim),r(" Copy source files into your project (shadcn-style).",e.dim),r("",e.dim),r(" scaffold [--framework next|react] [--target src] [--dir shortcuts] [--force]",e.dim),r(" Generate a scalable app shortcut architecture.",e.dim),r("",e.dim),r(" init --architecture",e.dim),r(` Alias for scaffold with defaults.
|
|
455
|
+
`,e.dim)}function v(t){if(t==="next"||t==="react")return t;r(`Invalid framework: ${t}. Expected "next" or "react".`,e.red),process.exit(1)}function K(){let t=process.argv.slice(2),o=t[0];if(!o||o==="--help"||o==="-h"||o==="help"){b();return}if(o==="init"){if(g(t,"--architecture")||g(t,"--app")||g(t,"--scaffold")){let i=v(p(t,"--framework","next")),c=p(t,"--target","src"),d=p(t,"--dir","shortcuts"),u=g(t,"--force");y(i,c,d,u);return}let s=p(t,"--target","hooks"),n=g(t,"--force");E(s,n);return}if(o==="scaffold"||o==="architecture"){let s=v(p(t,"--framework","next")),n=p(t,"--target","src"),i=p(t,"--dir","shortcuts"),c=g(t,"--force");y(s,n,i,c);return}b(),process.exit(1)}K();
|