@poveste/shared 0.3.3 → 0.5.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/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # @poveste/shared
2
+
3
+ Shared utilities used across the Poveste packages.
4
+
5
+ Internal to [Poveste](https://github.com/poveste-dev/poveste): it is installed as a
6
+ dependency of `poveste` and there is nothing to install or configure directly. Install
7
+ [`poveste`](https://www.npmjs.com/package/poveste) instead.
8
+
9
+ [Documentation](https://poveste.dev)
package/dist/setup.d.ts CHANGED
@@ -9,5 +9,5 @@ export type SetupModule = Record<string, unknown> | undefined;
9
9
  * consumer build. Passing the namespace through a call forces it to be
10
10
  * materialized, so the lookup happens at runtime where it belongs.
11
11
  */
12
- export declare function getSetupHook<T>(mod: SetupModule, name: string): T | undefined;
12
+ export declare function getSetupHook<T>(mod: SetupModule, name: string | string[]): T | undefined;
13
13
  //# sourceMappingURL=setup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;AAE7D;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAG7E"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;AAE7D;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAgBxF"}
package/dist/setup.js CHANGED
@@ -9,6 +9,15 @@
9
9
  * materialized, so the lookup happens at runtime where it belongs.
10
10
  */
11
11
  export function getSetupHook(mod, name) {
12
- const hook = mod?.[name];
13
- return typeof hook === 'function' ? hook : undefined;
12
+ const names = typeof name === 'string' ? [name] : name;
13
+ const present = names.filter(candidate => typeof mod?.[candidate] === 'function');
14
+ // Earlier names win, so a plugin lists its established hook first and a newer
15
+ // alias after it: an existing setup file keeps the exact behaviour it had.
16
+ // Running both instead would apply the same setup twice, which for Vue means
17
+ // a second `app.use()` for every plugin the user registers.
18
+ if (present.length > 1) {
19
+ console.warn(`[poveste] Setup file exports ${present.length} interchangeable setup hooks (${present.join(', ')}). `
20
+ + `Only ${present[0]} runs. Keep one — they are aliases, not separate hooks.`);
21
+ }
22
+ return present.length > 0 ? mod[present[0]] : undefined;
14
23
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@poveste/shared",
3
3
  "type": "module",
4
- "version": "0.3.3",
4
+ "version": "0.5.0",
5
5
  "description": "Shared utilities for Poveste",
6
6
  "author": {
7
7
  "name": "Sorin Gitlan"
@@ -34,7 +34,7 @@
34
34
  "pathe": "^1.1.2",
35
35
  "picocolors": "^1.1.1",
36
36
  "unhead": "^3.1.0",
37
- "@poveste/vendors": "^0.3.3"
37
+ "@poveste/vendors": "^0.5.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "typescript": "5.6.3",
@@ -0,0 +1,60 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest'
2
+ import { getSetupHook } from '../setup.js'
3
+
4
+ afterEach(() => {
5
+ vi.restoreAllMocks()
6
+ })
7
+
8
+ describe('getSetupHook', () => {
9
+ it('reads a hook by a single name', () => {
10
+ const hook = () => {}
11
+ expect(getSetupHook({ setupVanilla: hook }, 'setupVanilla')).toBe(hook)
12
+ })
13
+
14
+ it('ignores a non-function export of the right name', () => {
15
+ expect(getSetupHook({ setupVanilla: 'not a function' }, 'setupVanilla')).toBeUndefined()
16
+ expect(getSetupHook({ setupVanilla: undefined }, 'setupVanilla')).toBeUndefined()
17
+ })
18
+
19
+ it('tolerates a missing module', () => {
20
+ expect(getSetupHook(undefined, 'setupVanilla')).toBeUndefined()
21
+ })
22
+
23
+ it.each([
24
+ ['the established name', 'setupVue3'],
25
+ ['the unnumbered alias', 'setupVue'],
26
+ ])('finds a hook exported under %s', (_label, name) => {
27
+ const hook = () => {}
28
+ expect(getSetupHook({ [name]: hook }, ['setupVue3', 'setupVue'])).toBe(hook)
29
+ })
30
+
31
+ it('returns undefined when none of the names are exported', () => {
32
+ expect(getSetupHook({ setupSvelte5: () => {} }, ['setupVue3', 'setupVue'])).toBeUndefined()
33
+ })
34
+
35
+ describe('when a setup file exports more than one alias', () => {
36
+ // The whole point of the alias is that existing files keep working, so the
37
+ // established name has to win regardless of the order they are declared in.
38
+ it('runs the earlier name in the list, not the first declared', () => {
39
+ const legacy = () => {}
40
+ const renamed = () => {}
41
+ expect(getSetupHook({ setupVue: renamed, setupVue3: legacy }, ['setupVue3', 'setupVue'])).toBe(legacy)
42
+ })
43
+
44
+ it('warns, naming which one actually runs', () => {
45
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
46
+ getSetupHook({ setupVue3: () => {}, setupVue: () => {} }, ['setupVue3', 'setupVue'])
47
+
48
+ expect(warn).toHaveBeenCalledOnce()
49
+ const message = warn.mock.calls[0][0] as string
50
+ expect(message).toContain('setupVue3, setupVue')
51
+ expect(message).toContain('Only setupVue3 runs')
52
+ })
53
+
54
+ it('stays quiet when only one is exported', () => {
55
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
56
+ getSetupHook({ setupVue: () => {} }, ['setupVue3', 'setupVue'])
57
+ expect(warn).not.toHaveBeenCalled()
58
+ })
59
+ })
60
+ })
package/src/setup.ts CHANGED
@@ -10,7 +10,20 @@ export type SetupModule = Record<string, unknown> | undefined
10
10
  * consumer build. Passing the namespace through a call forces it to be
11
11
  * materialized, so the lookup happens at runtime where it belongs.
12
12
  */
13
- export function getSetupHook<T>(mod: SetupModule, name: string): T | undefined {
14
- const hook = mod?.[name]
15
- return typeof hook === 'function' ? hook as T : undefined
13
+ export function getSetupHook<T>(mod: SetupModule, name: string | string[]): T | undefined {
14
+ const names = typeof name === 'string' ? [name] : name
15
+ const present = names.filter(candidate => typeof mod?.[candidate] === 'function')
16
+
17
+ // Earlier names win, so a plugin lists its established hook first and a newer
18
+ // alias after it: an existing setup file keeps the exact behaviour it had.
19
+ // Running both instead would apply the same setup twice, which for Vue means
20
+ // a second `app.use()` for every plugin the user registers.
21
+ if (present.length > 1) {
22
+ console.warn(
23
+ `[poveste] Setup file exports ${present.length} interchangeable setup hooks (${present.join(', ')}). `
24
+ + `Only ${present[0]} runs. Keep one — they are aliases, not separate hooks.`,
25
+ )
26
+ }
27
+
28
+ return present.length > 0 ? mod![present[0]] as T : undefined
16
29
  }