@stackwright/hooks-registry 0.1.0-alpha.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/dist/index.d.mts +103 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +97 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +65 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +37 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffold Hook Types
|
|
3
|
+
*
|
|
4
|
+
* Hooks allow Pro packages to extend scaffold with:
|
|
5
|
+
* - Enterprise license injection
|
|
6
|
+
* - Custom MCP server configuration
|
|
7
|
+
* - Additional project setup
|
|
8
|
+
* - Post-install verification
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Hook types representing lifecycle points in scaffolding
|
|
12
|
+
*/
|
|
13
|
+
type ScaffoldHookType = 'preScaffold' | 'preInstall' | 'postInstall' | 'postScaffold';
|
|
14
|
+
/**
|
|
15
|
+
* A single scaffold hook
|
|
16
|
+
*/
|
|
17
|
+
interface ScaffoldHook {
|
|
18
|
+
/** Lifecycle point when hook runs */
|
|
19
|
+
type: ScaffoldHookType;
|
|
20
|
+
/** Unique name for the hook */
|
|
21
|
+
name: string;
|
|
22
|
+
/** Lower priority = runs first (default: 50) */
|
|
23
|
+
priority?: number;
|
|
24
|
+
/** If true, hook failure fails entire scaffold (default: false) */
|
|
25
|
+
critical?: boolean;
|
|
26
|
+
/** Hook handler function */
|
|
27
|
+
handler: (context: ScaffoldHookContext) => Promise<void> | void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Context passed to all hooks
|
|
31
|
+
*/
|
|
32
|
+
interface ScaffoldHookContext {
|
|
33
|
+
/** Target directory for the new project */
|
|
34
|
+
targetDir: string;
|
|
35
|
+
/** Project name */
|
|
36
|
+
projectName: string;
|
|
37
|
+
/** Site title */
|
|
38
|
+
siteTitle: string;
|
|
39
|
+
/** Theme ID */
|
|
40
|
+
themeId: string;
|
|
41
|
+
/** Mutable package.json - hooks can add dependencies */
|
|
42
|
+
packageJson: Record<string, any>;
|
|
43
|
+
/** Mutable .code-puppy.json config - hooks can add MCP servers */
|
|
44
|
+
codePuppyConfig?: Record<string, any>;
|
|
45
|
+
/** Dependency mode: workspace:* or versioned */
|
|
46
|
+
dependencyMode: 'workspace' | 'standalone';
|
|
47
|
+
/** Pages that will be created */
|
|
48
|
+
pages?: string[];
|
|
49
|
+
/** Whether to install dependencies automatically */
|
|
50
|
+
install?: boolean;
|
|
51
|
+
/** Additional properties hooks can add */
|
|
52
|
+
[key: string]: any;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Scaffold Hook Registry
|
|
57
|
+
*
|
|
58
|
+
* Singleton registry using Symbol.for() to survive module boundary crossings.
|
|
59
|
+
* This allows the registry to work correctly even when multiple copies of this
|
|
60
|
+
* package are loaded by different parts of the monorepo.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Register a hook to run during scaffolding
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* import { registerScaffoldHook } from '@stackwright/hooks-registry';
|
|
68
|
+
*
|
|
69
|
+
* registerScaffoldHook({
|
|
70
|
+
* type: 'preInstall',
|
|
71
|
+
* name: 'enterprise-license',
|
|
72
|
+
* priority: 10,
|
|
73
|
+
* handler: addEnterpriseLicense,
|
|
74
|
+
* });
|
|
75
|
+
*/
|
|
76
|
+
declare function registerScaffoldHook(hook: ScaffoldHook): void;
|
|
77
|
+
/**
|
|
78
|
+
* Get all registered hooks, sorted by priority
|
|
79
|
+
*/
|
|
80
|
+
declare function getScaffoldHooks(): ReadonlyArray<ScaffoldHook>;
|
|
81
|
+
/**
|
|
82
|
+
* Get hooks for a specific lifecycle point, sorted by priority
|
|
83
|
+
*/
|
|
84
|
+
declare function getScaffoldHooksForType(type: ScaffoldHookType): ReadonlyArray<ScaffoldHook>;
|
|
85
|
+
/**
|
|
86
|
+
* Clear all registered hooks
|
|
87
|
+
*/
|
|
88
|
+
declare function clearScaffoldHooks(): void;
|
|
89
|
+
/**
|
|
90
|
+
* Reset registry for testing isolation.
|
|
91
|
+
* Combines clearing hooks with resetting global state.
|
|
92
|
+
*/
|
|
93
|
+
declare function resetForTesting(): void;
|
|
94
|
+
/**
|
|
95
|
+
* Run all hooks of a given type
|
|
96
|
+
*
|
|
97
|
+
* @param type - Lifecycle point
|
|
98
|
+
* @param context - Context passed to hooks
|
|
99
|
+
* @throws If a critical hook fails
|
|
100
|
+
*/
|
|
101
|
+
declare function runScaffoldHooks(type: ScaffoldHookType, context: ScaffoldHookContext): Promise<void>;
|
|
102
|
+
|
|
103
|
+
export { type ScaffoldHook, type ScaffoldHookContext, type ScaffoldHookType, clearScaffoldHooks, getScaffoldHooks, getScaffoldHooksForType, registerScaffoldHook, resetForTesting, runScaffoldHooks };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffold Hook Types
|
|
3
|
+
*
|
|
4
|
+
* Hooks allow Pro packages to extend scaffold with:
|
|
5
|
+
* - Enterprise license injection
|
|
6
|
+
* - Custom MCP server configuration
|
|
7
|
+
* - Additional project setup
|
|
8
|
+
* - Post-install verification
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Hook types representing lifecycle points in scaffolding
|
|
12
|
+
*/
|
|
13
|
+
type ScaffoldHookType = 'preScaffold' | 'preInstall' | 'postInstall' | 'postScaffold';
|
|
14
|
+
/**
|
|
15
|
+
* A single scaffold hook
|
|
16
|
+
*/
|
|
17
|
+
interface ScaffoldHook {
|
|
18
|
+
/** Lifecycle point when hook runs */
|
|
19
|
+
type: ScaffoldHookType;
|
|
20
|
+
/** Unique name for the hook */
|
|
21
|
+
name: string;
|
|
22
|
+
/** Lower priority = runs first (default: 50) */
|
|
23
|
+
priority?: number;
|
|
24
|
+
/** If true, hook failure fails entire scaffold (default: false) */
|
|
25
|
+
critical?: boolean;
|
|
26
|
+
/** Hook handler function */
|
|
27
|
+
handler: (context: ScaffoldHookContext) => Promise<void> | void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Context passed to all hooks
|
|
31
|
+
*/
|
|
32
|
+
interface ScaffoldHookContext {
|
|
33
|
+
/** Target directory for the new project */
|
|
34
|
+
targetDir: string;
|
|
35
|
+
/** Project name */
|
|
36
|
+
projectName: string;
|
|
37
|
+
/** Site title */
|
|
38
|
+
siteTitle: string;
|
|
39
|
+
/** Theme ID */
|
|
40
|
+
themeId: string;
|
|
41
|
+
/** Mutable package.json - hooks can add dependencies */
|
|
42
|
+
packageJson: Record<string, any>;
|
|
43
|
+
/** Mutable .code-puppy.json config - hooks can add MCP servers */
|
|
44
|
+
codePuppyConfig?: Record<string, any>;
|
|
45
|
+
/** Dependency mode: workspace:* or versioned */
|
|
46
|
+
dependencyMode: 'workspace' | 'standalone';
|
|
47
|
+
/** Pages that will be created */
|
|
48
|
+
pages?: string[];
|
|
49
|
+
/** Whether to install dependencies automatically */
|
|
50
|
+
install?: boolean;
|
|
51
|
+
/** Additional properties hooks can add */
|
|
52
|
+
[key: string]: any;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Scaffold Hook Registry
|
|
57
|
+
*
|
|
58
|
+
* Singleton registry using Symbol.for() to survive module boundary crossings.
|
|
59
|
+
* This allows the registry to work correctly even when multiple copies of this
|
|
60
|
+
* package are loaded by different parts of the monorepo.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Register a hook to run during scaffolding
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* import { registerScaffoldHook } from '@stackwright/hooks-registry';
|
|
68
|
+
*
|
|
69
|
+
* registerScaffoldHook({
|
|
70
|
+
* type: 'preInstall',
|
|
71
|
+
* name: 'enterprise-license',
|
|
72
|
+
* priority: 10,
|
|
73
|
+
* handler: addEnterpriseLicense,
|
|
74
|
+
* });
|
|
75
|
+
*/
|
|
76
|
+
declare function registerScaffoldHook(hook: ScaffoldHook): void;
|
|
77
|
+
/**
|
|
78
|
+
* Get all registered hooks, sorted by priority
|
|
79
|
+
*/
|
|
80
|
+
declare function getScaffoldHooks(): ReadonlyArray<ScaffoldHook>;
|
|
81
|
+
/**
|
|
82
|
+
* Get hooks for a specific lifecycle point, sorted by priority
|
|
83
|
+
*/
|
|
84
|
+
declare function getScaffoldHooksForType(type: ScaffoldHookType): ReadonlyArray<ScaffoldHook>;
|
|
85
|
+
/**
|
|
86
|
+
* Clear all registered hooks
|
|
87
|
+
*/
|
|
88
|
+
declare function clearScaffoldHooks(): void;
|
|
89
|
+
/**
|
|
90
|
+
* Reset registry for testing isolation.
|
|
91
|
+
* Combines clearing hooks with resetting global state.
|
|
92
|
+
*/
|
|
93
|
+
declare function resetForTesting(): void;
|
|
94
|
+
/**
|
|
95
|
+
* Run all hooks of a given type
|
|
96
|
+
*
|
|
97
|
+
* @param type - Lifecycle point
|
|
98
|
+
* @param context - Context passed to hooks
|
|
99
|
+
* @throws If a critical hook fails
|
|
100
|
+
*/
|
|
101
|
+
declare function runScaffoldHooks(type: ScaffoldHookType, context: ScaffoldHookContext): Promise<void>;
|
|
102
|
+
|
|
103
|
+
export { type ScaffoldHook, type ScaffoldHookContext, type ScaffoldHookType, clearScaffoldHooks, getScaffoldHooks, getScaffoldHooksForType, registerScaffoldHook, resetForTesting, runScaffoldHooks };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
clearScaffoldHooks: () => clearScaffoldHooks,
|
|
24
|
+
getScaffoldHooks: () => getScaffoldHooks,
|
|
25
|
+
getScaffoldHooksForType: () => getScaffoldHooksForType,
|
|
26
|
+
registerScaffoldHook: () => registerScaffoldHook,
|
|
27
|
+
resetForTesting: () => resetForTesting,
|
|
28
|
+
runScaffoldHooks: () => runScaffoldHooks
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/registry.ts
|
|
33
|
+
var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("@stackwright/hooks-registry:hooks");
|
|
34
|
+
function getRegistry() {
|
|
35
|
+
const global = globalThis;
|
|
36
|
+
if (!global[REGISTRY_KEY]) {
|
|
37
|
+
global[REGISTRY_KEY] = /* @__PURE__ */ new Map();
|
|
38
|
+
}
|
|
39
|
+
return global[REGISTRY_KEY];
|
|
40
|
+
}
|
|
41
|
+
function registerScaffoldHook(hook) {
|
|
42
|
+
const registry = getRegistry();
|
|
43
|
+
registry.set(hook.name, {
|
|
44
|
+
type: hook.type,
|
|
45
|
+
name: hook.name,
|
|
46
|
+
priority: hook.priority ?? 50,
|
|
47
|
+
critical: hook.critical ?? false,
|
|
48
|
+
handler: hook.handler
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function getScaffoldHooks() {
|
|
52
|
+
const registry = getRegistry();
|
|
53
|
+
const entries = Array.from(registry.values());
|
|
54
|
+
return entries.sort((a, b) => a.priority - b.priority).map((entry) => ({
|
|
55
|
+
type: entry.type,
|
|
56
|
+
name: entry.name,
|
|
57
|
+
priority: entry.priority,
|
|
58
|
+
critical: entry.critical,
|
|
59
|
+
handler: entry.handler
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
function getScaffoldHooksForType(type) {
|
|
63
|
+
return getScaffoldHooks().filter((h) => h.type === type);
|
|
64
|
+
}
|
|
65
|
+
function clearScaffoldHooks() {
|
|
66
|
+
getRegistry().clear();
|
|
67
|
+
}
|
|
68
|
+
function resetForTesting() {
|
|
69
|
+
clearScaffoldHooks();
|
|
70
|
+
}
|
|
71
|
+
async function runScaffoldHooks(type, context) {
|
|
72
|
+
const relevantHooks = getScaffoldHooksForType(type);
|
|
73
|
+
for (const hook of relevantHooks) {
|
|
74
|
+
try {
|
|
75
|
+
await hook.handler(context);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (hook.critical) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Critical scaffold hook "${hook.name}" failed: ${error.message}`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
console.warn(
|
|
83
|
+
`[Scaffold Hook] Non-critical hook "${hook.name}" failed: ${error.message}`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
89
|
+
0 && (module.exports = {
|
|
90
|
+
clearScaffoldHooks,
|
|
91
|
+
getScaffoldHooks,
|
|
92
|
+
getScaffoldHooksForType,
|
|
93
|
+
registerScaffoldHook,
|
|
94
|
+
resetForTesting,
|
|
95
|
+
runScaffoldHooks
|
|
96
|
+
});
|
|
97
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/registry.ts"],"sourcesContent":["/**\n * @stackwright/hooks-registry\n *\n * Singleton registry for scaffold hooks.\n * Uses Symbol.for() to survive module boundary crossings between different package copies.\n */\n\n// Types re-export\nexport type { ScaffoldHook, ScaffoldHookType, ScaffoldHookContext } from './hooks';\n\n// Registry functions\nexport {\n registerScaffoldHook,\n getScaffoldHooks,\n getScaffoldHooksForType,\n clearScaffoldHooks,\n resetForTesting,\n runScaffoldHooks,\n} from './registry';\n","/**\n * Scaffold Hook Registry\n *\n * Singleton registry using Symbol.for() to survive module boundary crossings.\n * This allows the registry to work correctly even when multiple copies of this\n * package are loaded by different parts of the monorepo.\n */\n\nimport type { ScaffoldHook, ScaffoldHookType, ScaffoldHookContext } from './hooks';\n\n/**\n * Singleton key for cross-module registry sharing\n */\nconst REGISTRY_KEY = Symbol.for('@stackwright/hooks-registry:hooks');\n\n/**\n * Get or create the shared registry\n * Uses Symbol.for() so multiple module instances share the same storage\n */\nfunction getRegistry(): Map<string, HookEntry> {\n const global = globalThis as typeof globalThis & {\n [REGISTRY_KEY]?: Map<string, HookEntry>;\n };\n if (!global[REGISTRY_KEY]) {\n global[REGISTRY_KEY] = new Map();\n }\n return global[REGISTRY_KEY]!;\n}\n\n/**\n * Hook entry stored in registry\n */\ninterface HookEntry {\n type: ScaffoldHookType;\n name: string;\n priority: number;\n critical: boolean;\n handler: (context: any) => Promise<void> | void;\n}\n\n/**\n * Register a hook to run during scaffolding\n *\n * @example\n * import { registerScaffoldHook } from '@stackwright/hooks-registry';\n *\n * registerScaffoldHook({\n * type: 'preInstall',\n * name: 'enterprise-license',\n * priority: 10,\n * handler: addEnterpriseLicense,\n * });\n */\nexport function registerScaffoldHook(hook: ScaffoldHook): void {\n const registry = getRegistry();\n registry.set(hook.name, {\n type: hook.type,\n name: hook.name,\n priority: hook.priority ?? 50,\n critical: hook.critical ?? false,\n handler: hook.handler,\n });\n}\n\n/**\n * Get all registered hooks, sorted by priority\n */\nexport function getScaffoldHooks(): ReadonlyArray<ScaffoldHook> {\n const registry = getRegistry();\n const entries = Array.from(registry.values());\n return entries\n .sort((a, b) => a.priority - b.priority)\n .map((entry) => ({\n type: entry.type,\n name: entry.name,\n priority: entry.priority,\n critical: entry.critical,\n handler: entry.handler,\n }));\n}\n\n/**\n * Get hooks for a specific lifecycle point, sorted by priority\n */\nexport function getScaffoldHooksForType(type: ScaffoldHookType): ReadonlyArray<ScaffoldHook> {\n return getScaffoldHooks().filter((h) => h.type === type);\n}\n\n/**\n * Clear all registered hooks\n */\nexport function clearScaffoldHooks(): void {\n getRegistry().clear();\n}\n\n/**\n * Reset registry for testing isolation.\n * Combines clearing hooks with resetting global state.\n */\nexport function resetForTesting(): void {\n clearScaffoldHooks();\n}\n\n/**\n * Run all hooks of a given type\n *\n * @param type - Lifecycle point\n * @param context - Context passed to hooks\n * @throws If a critical hook fails\n */\nexport async function runScaffoldHooks(\n type: ScaffoldHookType,\n context: ScaffoldHookContext\n): Promise<void> {\n const relevantHooks = getScaffoldHooksForType(type);\n\n for (const hook of relevantHooks) {\n try {\n await hook.handler(context);\n } catch (error) {\n if (hook.critical) {\n throw new Error(\n `Critical scaffold hook \"${hook.name}\" failed: ${(error as Error).message}`\n );\n }\n // Non-critical: warn but continue\n console.warn(\n `[Scaffold Hook] Non-critical hook \"${hook.name}\" failed: ${(error as Error).message}`\n );\n }\n }\n}\n\n// Re-export types from hooks.ts\nexport type { ScaffoldHook, ScaffoldHookType, ScaffoldHookContext } from './hooks';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACaA,IAAM,eAAe,uBAAO,IAAI,mCAAmC;AAMnE,SAAS,cAAsC;AAC7C,QAAM,SAAS;AAGf,MAAI,CAAC,OAAO,YAAY,GAAG;AACzB,WAAO,YAAY,IAAI,oBAAI,IAAI;AAAA,EACjC;AACA,SAAO,OAAO,YAAY;AAC5B;AA0BO,SAAS,qBAAqB,MAA0B;AAC7D,QAAM,WAAW,YAAY;AAC7B,WAAS,IAAI,KAAK,MAAM;AAAA,IACtB,MAAM,KAAK;AAAA,IACX,MAAM,KAAK;AAAA,IACX,UAAU,KAAK,YAAY;AAAA,IAC3B,UAAU,KAAK,YAAY;AAAA,IAC3B,SAAS,KAAK;AAAA,EAChB,CAAC;AACH;AAKO,SAAS,mBAAgD;AAC9D,QAAM,WAAW,YAAY;AAC7B,QAAM,UAAU,MAAM,KAAK,SAAS,OAAO,CAAC;AAC5C,SAAO,QACJ,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,EACtC,IAAI,CAAC,WAAW;AAAA,IACf,MAAM,MAAM;AAAA,IACZ,MAAM,MAAM;AAAA,IACZ,UAAU,MAAM;AAAA,IAChB,UAAU,MAAM;AAAA,IAChB,SAAS,MAAM;AAAA,EACjB,EAAE;AACN;AAKO,SAAS,wBAAwB,MAAqD;AAC3F,SAAO,iBAAiB,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,IAAI;AACzD;AAKO,SAAS,qBAA2B;AACzC,cAAY,EAAE,MAAM;AACtB;AAMO,SAAS,kBAAwB;AACtC,qBAAmB;AACrB;AASA,eAAsB,iBACpB,MACA,SACe;AACf,QAAM,gBAAgB,wBAAwB,IAAI;AAElD,aAAW,QAAQ,eAAe;AAChC,QAAI;AACF,YAAM,KAAK,QAAQ,OAAO;AAAA,IAC5B,SAAS,OAAO;AACd,UAAI,KAAK,UAAU;AACjB,cAAM,IAAI;AAAA,UACR,2BAA2B,KAAK,IAAI,aAAc,MAAgB,OAAO;AAAA,QAC3E;AAAA,MACF;AAEA,cAAQ;AAAA,QACN,sCAAsC,KAAK,IAAI,aAAc,MAAgB,OAAO;AAAA,MACtF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// src/registry.ts
|
|
2
|
+
var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("@stackwright/hooks-registry:hooks");
|
|
3
|
+
function getRegistry() {
|
|
4
|
+
const global = globalThis;
|
|
5
|
+
if (!global[REGISTRY_KEY]) {
|
|
6
|
+
global[REGISTRY_KEY] = /* @__PURE__ */ new Map();
|
|
7
|
+
}
|
|
8
|
+
return global[REGISTRY_KEY];
|
|
9
|
+
}
|
|
10
|
+
function registerScaffoldHook(hook) {
|
|
11
|
+
const registry = getRegistry();
|
|
12
|
+
registry.set(hook.name, {
|
|
13
|
+
type: hook.type,
|
|
14
|
+
name: hook.name,
|
|
15
|
+
priority: hook.priority ?? 50,
|
|
16
|
+
critical: hook.critical ?? false,
|
|
17
|
+
handler: hook.handler
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function getScaffoldHooks() {
|
|
21
|
+
const registry = getRegistry();
|
|
22
|
+
const entries = Array.from(registry.values());
|
|
23
|
+
return entries.sort((a, b) => a.priority - b.priority).map((entry) => ({
|
|
24
|
+
type: entry.type,
|
|
25
|
+
name: entry.name,
|
|
26
|
+
priority: entry.priority,
|
|
27
|
+
critical: entry.critical,
|
|
28
|
+
handler: entry.handler
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
function getScaffoldHooksForType(type) {
|
|
32
|
+
return getScaffoldHooks().filter((h) => h.type === type);
|
|
33
|
+
}
|
|
34
|
+
function clearScaffoldHooks() {
|
|
35
|
+
getRegistry().clear();
|
|
36
|
+
}
|
|
37
|
+
function resetForTesting() {
|
|
38
|
+
clearScaffoldHooks();
|
|
39
|
+
}
|
|
40
|
+
async function runScaffoldHooks(type, context) {
|
|
41
|
+
const relevantHooks = getScaffoldHooksForType(type);
|
|
42
|
+
for (const hook of relevantHooks) {
|
|
43
|
+
try {
|
|
44
|
+
await hook.handler(context);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (hook.critical) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Critical scaffold hook "${hook.name}" failed: ${error.message}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
console.warn(
|
|
52
|
+
`[Scaffold Hook] Non-critical hook "${hook.name}" failed: ${error.message}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
clearScaffoldHooks,
|
|
59
|
+
getScaffoldHooks,
|
|
60
|
+
getScaffoldHooksForType,
|
|
61
|
+
registerScaffoldHook,
|
|
62
|
+
resetForTesting,
|
|
63
|
+
runScaffoldHooks
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/registry.ts"],"sourcesContent":["/**\n * Scaffold Hook Registry\n *\n * Singleton registry using Symbol.for() to survive module boundary crossings.\n * This allows the registry to work correctly even when multiple copies of this\n * package are loaded by different parts of the monorepo.\n */\n\nimport type { ScaffoldHook, ScaffoldHookType, ScaffoldHookContext } from './hooks';\n\n/**\n * Singleton key for cross-module registry sharing\n */\nconst REGISTRY_KEY = Symbol.for('@stackwright/hooks-registry:hooks');\n\n/**\n * Get or create the shared registry\n * Uses Symbol.for() so multiple module instances share the same storage\n */\nfunction getRegistry(): Map<string, HookEntry> {\n const global = globalThis as typeof globalThis & {\n [REGISTRY_KEY]?: Map<string, HookEntry>;\n };\n if (!global[REGISTRY_KEY]) {\n global[REGISTRY_KEY] = new Map();\n }\n return global[REGISTRY_KEY]!;\n}\n\n/**\n * Hook entry stored in registry\n */\ninterface HookEntry {\n type: ScaffoldHookType;\n name: string;\n priority: number;\n critical: boolean;\n handler: (context: any) => Promise<void> | void;\n}\n\n/**\n * Register a hook to run during scaffolding\n *\n * @example\n * import { registerScaffoldHook } from '@stackwright/hooks-registry';\n *\n * registerScaffoldHook({\n * type: 'preInstall',\n * name: 'enterprise-license',\n * priority: 10,\n * handler: addEnterpriseLicense,\n * });\n */\nexport function registerScaffoldHook(hook: ScaffoldHook): void {\n const registry = getRegistry();\n registry.set(hook.name, {\n type: hook.type,\n name: hook.name,\n priority: hook.priority ?? 50,\n critical: hook.critical ?? false,\n handler: hook.handler,\n });\n}\n\n/**\n * Get all registered hooks, sorted by priority\n */\nexport function getScaffoldHooks(): ReadonlyArray<ScaffoldHook> {\n const registry = getRegistry();\n const entries = Array.from(registry.values());\n return entries\n .sort((a, b) => a.priority - b.priority)\n .map((entry) => ({\n type: entry.type,\n name: entry.name,\n priority: entry.priority,\n critical: entry.critical,\n handler: entry.handler,\n }));\n}\n\n/**\n * Get hooks for a specific lifecycle point, sorted by priority\n */\nexport function getScaffoldHooksForType(type: ScaffoldHookType): ReadonlyArray<ScaffoldHook> {\n return getScaffoldHooks().filter((h) => h.type === type);\n}\n\n/**\n * Clear all registered hooks\n */\nexport function clearScaffoldHooks(): void {\n getRegistry().clear();\n}\n\n/**\n * Reset registry for testing isolation.\n * Combines clearing hooks with resetting global state.\n */\nexport function resetForTesting(): void {\n clearScaffoldHooks();\n}\n\n/**\n * Run all hooks of a given type\n *\n * @param type - Lifecycle point\n * @param context - Context passed to hooks\n * @throws If a critical hook fails\n */\nexport async function runScaffoldHooks(\n type: ScaffoldHookType,\n context: ScaffoldHookContext\n): Promise<void> {\n const relevantHooks = getScaffoldHooksForType(type);\n\n for (const hook of relevantHooks) {\n try {\n await hook.handler(context);\n } catch (error) {\n if (hook.critical) {\n throw new Error(\n `Critical scaffold hook \"${hook.name}\" failed: ${(error as Error).message}`\n );\n }\n // Non-critical: warn but continue\n console.warn(\n `[Scaffold Hook] Non-critical hook \"${hook.name}\" failed: ${(error as Error).message}`\n );\n }\n }\n}\n\n// Re-export types from hooks.ts\nexport type { ScaffoldHook, ScaffoldHookType, ScaffoldHookContext } from './hooks';\n"],"mappings":";AAaA,IAAM,eAAe,uBAAO,IAAI,mCAAmC;AAMnE,SAAS,cAAsC;AAC7C,QAAM,SAAS;AAGf,MAAI,CAAC,OAAO,YAAY,GAAG;AACzB,WAAO,YAAY,IAAI,oBAAI,IAAI;AAAA,EACjC;AACA,SAAO,OAAO,YAAY;AAC5B;AA0BO,SAAS,qBAAqB,MAA0B;AAC7D,QAAM,WAAW,YAAY;AAC7B,WAAS,IAAI,KAAK,MAAM;AAAA,IACtB,MAAM,KAAK;AAAA,IACX,MAAM,KAAK;AAAA,IACX,UAAU,KAAK,YAAY;AAAA,IAC3B,UAAU,KAAK,YAAY;AAAA,IAC3B,SAAS,KAAK;AAAA,EAChB,CAAC;AACH;AAKO,SAAS,mBAAgD;AAC9D,QAAM,WAAW,YAAY;AAC7B,QAAM,UAAU,MAAM,KAAK,SAAS,OAAO,CAAC;AAC5C,SAAO,QACJ,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,EACtC,IAAI,CAAC,WAAW;AAAA,IACf,MAAM,MAAM;AAAA,IACZ,MAAM,MAAM;AAAA,IACZ,UAAU,MAAM;AAAA,IAChB,UAAU,MAAM;AAAA,IAChB,SAAS,MAAM;AAAA,EACjB,EAAE;AACN;AAKO,SAAS,wBAAwB,MAAqD;AAC3F,SAAO,iBAAiB,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,IAAI;AACzD;AAKO,SAAS,qBAA2B;AACzC,cAAY,EAAE,MAAM;AACtB;AAMO,SAAS,kBAAwB;AACtC,qBAAmB;AACrB;AASA,eAAsB,iBACpB,MACA,SACe;AACf,QAAM,gBAAgB,wBAAwB,IAAI;AAElD,aAAW,QAAQ,eAAe;AAChC,QAAI;AACF,YAAM,KAAK,QAAQ,OAAO;AAAA,IAC5B,SAAS,OAAO;AACd,UAAI,KAAK,UAAU;AACjB,cAAM,IAAI;AAAA,UACR,2BAA2B,KAAK,IAAI,aAAc,MAAgB,OAAO;AAAA,QAC3E;AAAA,MACF;AAEA,cAAQ;AAAA,QACN,sCAAsC,KAAK,IAAI,aAAc,MAAgB,OAAO;AAAA,MACtF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stackwright/hooks-registry",
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"description": "Singleton registry for scaffold hooks - survives module boundary crossings",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/Per-Aspera-LLC/stackwright"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"module": "./dist/index.mjs",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.mjs",
|
|
17
|
+
"require": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.0.0",
|
|
25
|
+
"tsup": "^8.5.0",
|
|
26
|
+
"typescript": "^5.8.0",
|
|
27
|
+
"vitest": "4.1.2"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=20.0.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup",
|
|
34
|
+
"dev": "tsup --watch",
|
|
35
|
+
"test": "vitest run"
|
|
36
|
+
}
|
|
37
|
+
}
|