pocketbomb-dynamite 0.2.0 → 0.3.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.
Files changed (41) hide show
  1. package/README.md +24 -1
  2. package/dist/cli/check.cjs +160 -0
  3. package/dist/cli/check.cjs.map +1 -0
  4. package/dist/cli/check.d.cts +21 -0
  5. package/dist/cli/check.d.ts +21 -0
  6. package/dist/cli/check.js +157 -0
  7. package/dist/cli/check.js.map +1 -0
  8. package/dist/cli/codegen.cjs +2 -1
  9. package/dist/cli/codegen.cjs.map +1 -1
  10. package/dist/cli/codegen.js +3 -2
  11. package/dist/cli/codegen.js.map +1 -1
  12. package/dist/cli/index.cjs +32 -0
  13. package/dist/cli/index.cjs.map +1 -1
  14. package/dist/cli/index.d.cts +2 -1
  15. package/dist/cli/index.d.ts +2 -1
  16. package/dist/cli/index.js +32 -1
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/customer-account/customer-account.cjs +55 -0
  19. package/dist/customer-account/customer-account.cjs.map +1 -1
  20. package/dist/customer-account/customer-account.d.cts +33 -0
  21. package/dist/customer-account/customer-account.d.ts +33 -0
  22. package/dist/customer-account/customer-account.js +52 -1
  23. package/dist/customer-account/customer-account.js.map +1 -1
  24. package/dist/customer-account/index.cjs +4 -0
  25. package/dist/customer-account/index.d.cts +2 -2
  26. package/dist/customer-account/index.d.ts +2 -2
  27. package/dist/customer-account/index.js +1 -1
  28. package/dist/react/hooks.cjs +37 -5
  29. package/dist/react/hooks.cjs.map +1 -1
  30. package/dist/react/hooks.d.cts +12 -2
  31. package/dist/react/hooks.d.ts +12 -2
  32. package/dist/react/hooks.js +37 -5
  33. package/dist/react/hooks.js.map +1 -1
  34. package/dist/react/index.d.cts +2 -2
  35. package/dist/react/index.d.ts +2 -2
  36. package/dist/ts-plugin/index.d.cts +2 -1
  37. package/dist/vite/index.cjs +6 -2
  38. package/dist/vite/index.cjs.map +1 -1
  39. package/dist/vite/index.js +6 -2
  40. package/dist/vite/index.js.map +1 -1
  41. package/package.json +7 -3
@@ -1,6 +1,32 @@
1
+ const require_customer_account = require("../customer-account/customer-account.cjs");
1
2
  const require_context = require("./context.cjs");
2
3
  let react = require("react");
3
4
  // src/react/hooks.ts
5
+ function rejectRouteOptions(options) {
6
+ const names = Object.keys(options).filter((name) => options[name] !== void 0);
7
+ if (names.length) throw new Error("useCustomer(): " + names.join(", ") + " can't be sent through an account route URL; set them in the route loader instead.");
8
+ }
9
+ function routeLoginOptions(options = {}) {
10
+ const { returnTo, prompt, maxAge, loginHint, ...rest } = options;
11
+ rejectRouteOptions(rest);
12
+ return {
13
+ returnTo,
14
+ prompt,
15
+ maxAge,
16
+ loginHint
17
+ };
18
+ }
19
+ function accountLoginOptions(options) {
20
+ if (options?.returnTo === void 0) return options;
21
+ const { returnTo, ...rest } = options;
22
+ return {
23
+ ...rest,
24
+ state: {
25
+ ...rest.state,
26
+ returnTo
27
+ }
28
+ };
29
+ }
4
30
  /**
5
31
  * Returns the customer account state and actions.
6
32
  *
@@ -45,10 +71,10 @@ function useCustomer() {
45
71
  }, []);
46
72
  const login = (0, react.useCallback)((options) => run(async () => {
47
73
  if (customerRoutes) {
48
- navigate(customerRoutes.login);
74
+ navigate(require_customer_account.loginRouteURL(customerRoutes.login, routeLoginOptions(options)));
49
75
  return null;
50
76
  }
51
- return customerClient ? customerClient.login(options) : null;
77
+ return customerClient ? customerClient.login(accountLoginOptions(options)) : null;
52
78
  }), [
53
79
  run,
54
80
  customerRoutes,
@@ -57,10 +83,14 @@ function useCustomer() {
57
83
  ]);
58
84
  const signup = (0, react.useCallback)((options) => run(async () => {
59
85
  if (customerRoutes) {
60
- navigate(customerRoutes.signup || customerRoutes.login);
86
+ const routeOptions = routeLoginOptions(options);
87
+ navigate(customerRoutes.signup ? require_customer_account.loginRouteURL(customerRoutes.signup, routeOptions) : require_customer_account.loginRouteURL(customerRoutes.login, {
88
+ ...routeOptions,
89
+ prompt: "create"
90
+ }));
61
91
  return null;
62
92
  }
63
- return customerClient ? customerClient.signup(options) : null;
93
+ return customerClient ? customerClient.signup(accountLoginOptions(options)) : null;
64
94
  }), [
65
95
  run,
66
96
  customerRoutes,
@@ -69,7 +99,9 @@ function useCustomer() {
69
99
  ]);
70
100
  const logout = (0, react.useCallback)((options) => run(async () => {
71
101
  if (customerRoutes) {
72
- navigate(customerRoutes.logout);
102
+ const { postLogoutRedirectUri, ...rest } = options || {};
103
+ rejectRouteOptions(rest);
104
+ navigate(require_customer_account.logoutRouteURL(customerRoutes.logout, { postLogoutRedirectUri }));
73
105
  return;
74
106
  }
75
107
  await customerClient?.logout(options);
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.cjs","names":["useDynamiteContext","useState","useCallback","useRef"],"sources":["../../src/react/hooks.ts"],"sourcesContent":["import type {\n AccountCallbackResult,\n AccountLoginOptions,\n AccountLogoutOptions,\n AuthRecord,\n} from \"pocketbomb-primer\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { StorefrontQueryOptions } from \"../client/storefront\";\nimport type { DocumentInput, ResultOf, VariablesOf } from \"../graphql/graphql\";\nimport { useDynamiteContext } from \"./context\";\n\nexport interface CustomerState {\n /** The signed in customer record (null if signed out). */\n customer: AuthRecord;\n isLoggedIn: boolean;\n /** Whether a login/logout/restore is in progress. */\n loading: boolean;\n /** The last account error (e.g. a failed callback). */\n error: Error | null;\n /** Starts the login (server route navigation, browser redirect or mobile auth session). */\n login(options?: AccountLoginOptions): Promise<AccountCallbackResult | null>;\n /** Starts the signup (`prompt=create`). */\n signup(options?: Omit<AccountLoginOptions, \"prompt\">): Promise<AccountCallbackResult | null>;\n /** Signs out (server route navigation or the account logout). */\n logout(options?: AccountLogoutOptions): Promise<void>;\n}\n\n/**\n * Returns the customer account state and actions.\n *\n * - server rendered apps (`customer.routes`): the customer comes from `initialCustomer` and\n * the actions navigate to the account routes\n * - browser apps (customer client options): the state follows the account auth store; on\n * mount the session is restored and a pending authorization response is handled\n */\nexport function useCustomer(): CustomerState {\n const { customerClient, customerRoutes, initialCustomer, navigate } = useDynamiteContext();\n\n // the first render always uses the server value (hydration safe); the browser account\n // store state is applied by the effect\n const [customer, setCustomer] = useState<AuthRecord>(initialCustomer);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n if (!customerClient) {\n setCustomer(initialCustomer);\n return;\n }\n\n const store = customerClient.client.authStore;\n const unsubscribe = store.onChange((_, record) => setCustomer(record), true);\n\n let active = true;\n setLoading(true);\n (async () => {\n if (customerClient.account.isCallbackURL()) {\n await customerClient.handleCallback();\n }\n await customerClient.account.restore();\n })()\n .catch((err) => active && setError(err))\n .finally(() => active && setLoading(false));\n\n return () => {\n active = false;\n unsubscribe();\n };\n }, [customerClient, initialCustomer]);\n\n const run = useCallback(async <T>(fn: () => Promise<T>): Promise<T> => {\n setLoading(true);\n setError(null);\n try {\n return await fn();\n } catch (err) {\n setError(err as Error);\n throw err;\n } finally {\n setLoading(false);\n }\n }, []);\n\n const login = useCallback(\n (options?: AccountLoginOptions) =>\n run(async () => {\n if (customerRoutes) {\n navigate(customerRoutes.login);\n return null;\n }\n return customerClient ? customerClient.login(options) : null;\n }),\n [run, customerRoutes, customerClient, navigate],\n );\n\n const signup = useCallback(\n (options?: Omit<AccountLoginOptions, \"prompt\">) =>\n run(async () => {\n if (customerRoutes) {\n navigate(customerRoutes.signup || customerRoutes.login);\n return null;\n }\n return customerClient ? customerClient.signup(options) : null;\n }),\n [run, customerRoutes, customerClient, navigate],\n );\n\n const logout = useCallback(\n (options?: AccountLogoutOptions) =>\n run(async () => {\n if (customerRoutes) {\n navigate(customerRoutes.logout);\n return;\n }\n await customerClient?.logout(options);\n }),\n [run, customerRoutes, customerClient, navigate],\n );\n\n return {\n customer,\n isLoggedIn: !!customer,\n loading,\n error,\n login,\n signup,\n logout,\n };\n}\n\nexport interface UseStorefrontQueryOptions<\n Doc extends DocumentInput,\n> extends StorefrontQueryOptions<VariablesOf<Doc>> {\n /** The data rendered by the server (no request on mount). */\n initialData?: ResultOf<Doc>;\n /** Skip the request. */\n skip?: boolean;\n}\n\nexport interface StorefrontQueryState<Data> {\n data: Data | undefined;\n error: Error | null;\n loading: boolean;\n refetch(): Promise<void>;\n}\n\n/**\n * Runs a storefront GraphQL query in the browser (public key) and re-runs it when the\n * variables change. The server rendered `initialData` is used for the first render.\n */\nexport function useStorefrontQuery<Doc extends DocumentInput>(\n document: Doc,\n options: UseStorefrontQueryOptions<Doc> = {},\n): StorefrontQueryState<ResultOf<Doc>> {\n const storefront = useDynamiteContext().storefront;\n const { initialData, skip, ...queryOptions } = options;\n\n const variablesKey = JSON.stringify(queryOptions.variables ?? null);\n const [data, setData] = useState<ResultOf<Doc> | undefined>(initialData);\n const [error, setError] = useState<Error | null>(null);\n const [loading, setLoading] = useState(!skip && initialData === undefined);\n\n // the initial data matches the initial variables (no refetch on hydration)\n const fetchedKey = useRef<string | null>(initialData !== undefined ? variablesKey : null);\n const optionsRef = useRef(queryOptions);\n optionsRef.current = queryOptions;\n\n const fetchData = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n setData(await storefront.query(document, optionsRef.current));\n } catch (err) {\n setError(err as Error);\n } finally {\n setLoading(false);\n }\n }, [storefront, document]);\n\n useEffect(() => {\n if (skip || fetchedKey.current === variablesKey) {\n return;\n }\n fetchedKey.current = variablesKey;\n void fetchData();\n }, [skip, variablesKey, fetchData]);\n\n return { data, error, loading, refetch: fetchData };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"hooks.cjs","names":["useDynamiteContext","useState","useCallback","loginRouteURL","logoutRouteURL","useRef"],"sources":["../../src/react/hooks.ts"],"sourcesContent":["import type {\n AccountCallbackResult,\n AccountLoginOptions,\n AccountLogoutOptions,\n AuthRecord,\n} from \"pocketbomb-primer\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { StorefrontQueryOptions } from \"../client/storefront\";\nimport type { DocumentInput, ResultOf, VariablesOf } from \"../graphql/graphql\";\nimport {\n loginRouteURL,\n logoutRouteURL,\n type LoginRouteOptions,\n} from \"../customer-account/customer-account\";\nimport { useDynamiteContext } from \"./context\";\n\n/**\n * The login options of {@link useCustomer}. `returnTo` is the page to return to after the\n * login (the account routes validate it with `isSafeReturnTo`).\n *\n * With the account routes of a server rendered app the options travel in the route URL, so only\n * `returnTo`, `prompt`, `maxAge` and `loginHint` are supported: the route decides everything else.\n */\nexport interface CustomerLoginOptions extends AccountLoginOptions {\n returnTo?: string;\n}\n\nfunction rejectRouteOptions(options: Record<string, unknown>): void {\n const names = Object.keys(options).filter((name) => options[name] !== undefined);\n if (names.length) {\n throw new Error(\n \"useCustomer(): \" +\n names.join(\", \") +\n \" can't be sent through an account route URL; set them in the route loader instead.\",\n );\n }\n}\n\nfunction routeLoginOptions(options: CustomerLoginOptions = {}): LoginRouteOptions {\n const { returnTo, prompt, maxAge, loginHint, ...rest } = options;\n rejectRouteOptions(rest);\n return { returnTo, prompt, maxAge, loginHint };\n}\n\nfunction accountLoginOptions(options?: CustomerLoginOptions): AccountLoginOptions | undefined {\n if (options?.returnTo === undefined) {\n return options;\n }\n const { returnTo, ...rest } = options;\n return { ...rest, state: { ...rest.state, returnTo } };\n}\n\nexport interface CustomerState {\n /** The signed in customer record (null if signed out). */\n customer: AuthRecord;\n isLoggedIn: boolean;\n /** Whether a login/logout/restore is in progress. */\n loading: boolean;\n /** The last account error (e.g. a failed callback). */\n error: Error | null;\n /** Starts the login (server route navigation, browser redirect or mobile auth session). */\n login(options?: CustomerLoginOptions): Promise<AccountCallbackResult | null>;\n /** Starts the signup (`prompt=create`). */\n signup(options?: Omit<CustomerLoginOptions, \"prompt\">): Promise<AccountCallbackResult | null>;\n /** Signs out (server route navigation or the account logout). */\n logout(options?: AccountLogoutOptions): Promise<void>;\n}\n\n/**\n * Returns the customer account state and actions.\n *\n * - server rendered apps (`customer.routes`): the customer comes from `initialCustomer` and\n * the actions navigate to the account routes\n * - browser apps (customer client options): the state follows the account auth store; on\n * mount the session is restored and a pending authorization response is handled\n */\nexport function useCustomer(): CustomerState {\n const { customerClient, customerRoutes, initialCustomer, navigate } = useDynamiteContext();\n\n // the first render always uses the server value (hydration safe); the browser account\n // store state is applied by the effect\n const [customer, setCustomer] = useState<AuthRecord>(initialCustomer);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n if (!customerClient) {\n setCustomer(initialCustomer);\n return;\n }\n\n const store = customerClient.client.authStore;\n const unsubscribe = store.onChange((_, record) => setCustomer(record), true);\n\n let active = true;\n setLoading(true);\n (async () => {\n if (customerClient.account.isCallbackURL()) {\n await customerClient.handleCallback();\n }\n await customerClient.account.restore();\n })()\n .catch((err) => active && setError(err))\n .finally(() => active && setLoading(false));\n\n return () => {\n active = false;\n unsubscribe();\n };\n }, [customerClient, initialCustomer]);\n\n const run = useCallback(async <T>(fn: () => Promise<T>): Promise<T> => {\n setLoading(true);\n setError(null);\n try {\n return await fn();\n } catch (err) {\n setError(err as Error);\n throw err;\n } finally {\n setLoading(false);\n }\n }, []);\n\n const login = useCallback(\n (options?: CustomerLoginOptions) =>\n run(async () => {\n if (customerRoutes) {\n navigate(loginRouteURL(customerRoutes.login, routeLoginOptions(options)));\n return null;\n }\n return customerClient ? customerClient.login(accountLoginOptions(options)) : null;\n }),\n [run, customerRoutes, customerClient, navigate],\n );\n\n const signup = useCallback(\n (options?: Omit<CustomerLoginOptions, \"prompt\">) =>\n run(async () => {\n if (customerRoutes) {\n const routeOptions = routeLoginOptions(options);\n navigate(\n customerRoutes.signup\n ? loginRouteURL(customerRoutes.signup, routeOptions)\n : loginRouteURL(customerRoutes.login, { ...routeOptions, prompt: \"create\" }),\n );\n return null;\n }\n return customerClient ? customerClient.signup(accountLoginOptions(options)) : null;\n }),\n [run, customerRoutes, customerClient, navigate],\n );\n\n const logout = useCallback(\n (options?: AccountLogoutOptions) =>\n run(async () => {\n if (customerRoutes) {\n const { postLogoutRedirectUri, ...rest } = options || {};\n rejectRouteOptions(rest);\n navigate(logoutRouteURL(customerRoutes.logout, { postLogoutRedirectUri }));\n return;\n }\n await customerClient?.logout(options);\n }),\n [run, customerRoutes, customerClient, navigate],\n );\n\n return {\n customer,\n isLoggedIn: !!customer,\n loading,\n error,\n login,\n signup,\n logout,\n };\n}\n\nexport interface UseStorefrontQueryOptions<\n Doc extends DocumentInput,\n> extends StorefrontQueryOptions<VariablesOf<Doc>> {\n /** The data rendered by the server (no request on mount). */\n initialData?: ResultOf<Doc>;\n /** Skip the request. */\n skip?: boolean;\n}\n\nexport interface StorefrontQueryState<Data> {\n data: Data | undefined;\n error: Error | null;\n loading: boolean;\n refetch(): Promise<void>;\n}\n\n/**\n * Runs a storefront GraphQL query in the browser (public key) and re-runs it when the\n * variables change. The server rendered `initialData` is used for the first render.\n */\nexport function useStorefrontQuery<Doc extends DocumentInput>(\n document: Doc,\n options: UseStorefrontQueryOptions<Doc> = {},\n): StorefrontQueryState<ResultOf<Doc>> {\n const storefront = useDynamiteContext().storefront;\n const { initialData, skip, ...queryOptions } = options;\n\n const variablesKey = JSON.stringify(queryOptions.variables ?? null);\n const [data, setData] = useState<ResultOf<Doc> | undefined>(initialData);\n const [error, setError] = useState<Error | null>(null);\n const [loading, setLoading] = useState(!skip && initialData === undefined);\n\n // the initial data matches the initial variables (no refetch on hydration)\n const fetchedKey = useRef<string | null>(initialData !== undefined ? variablesKey : null);\n const optionsRef = useRef(queryOptions);\n optionsRef.current = queryOptions;\n\n const fetchData = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n setData(await storefront.query(document, optionsRef.current));\n } catch (err) {\n setError(err as Error);\n } finally {\n setLoading(false);\n }\n }, [storefront, document]);\n\n useEffect(() => {\n if (skip || fetchedKey.current === variablesKey) {\n return;\n }\n fetchedKey.current = variablesKey;\n void fetchData();\n }, [skip, variablesKey, fetchData]);\n\n return { data, error, loading, refetch: fetchData };\n}\n"],"mappings":"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"}
@@ -1,6 +1,16 @@
1
1
  import { DocumentInput, ResultOf, VariablesOf } from "../graphql/graphql.cjs";
2
2
  import { StorefrontQueryOptions } from "../client/storefront.cjs";
3
3
  import { AccountCallbackResult, AccountLoginOptions, AccountLogoutOptions, AuthRecord } from "pocketbomb-primer";
4
+ /**
5
+ * The login options of {@link useCustomer}. `returnTo` is the page to return to after the
6
+ * login (the account routes validate it with `isSafeReturnTo`).
7
+ *
8
+ * With the account routes of a server rendered app the options travel in the route URL, so only
9
+ * `returnTo`, `prompt`, `maxAge` and `loginHint` are supported: the route decides everything else.
10
+ */
11
+ export interface CustomerLoginOptions extends AccountLoginOptions {
12
+ returnTo?: string;
13
+ }
4
14
  export interface CustomerState {
5
15
  /** The signed in customer record (null if signed out). */
6
16
  customer: AuthRecord;
@@ -10,9 +20,9 @@ export interface CustomerState {
10
20
  /** The last account error (e.g. a failed callback). */
11
21
  error: Error | null;
12
22
  /** Starts the login (server route navigation, browser redirect or mobile auth session). */
13
- login(options?: AccountLoginOptions): Promise<AccountCallbackResult | null>;
23
+ login(options?: CustomerLoginOptions): Promise<AccountCallbackResult | null>;
14
24
  /** Starts the signup (`prompt=create`). */
15
- signup(options?: Omit<AccountLoginOptions, "prompt">): Promise<AccountCallbackResult | null>;
25
+ signup(options?: Omit<CustomerLoginOptions, "prompt">): Promise<AccountCallbackResult | null>;
16
26
  /** Signs out (server route navigation or the account logout). */
17
27
  logout(options?: AccountLogoutOptions): Promise<void>;
18
28
  }
@@ -1,6 +1,16 @@
1
1
  import { DocumentInput, ResultOf, VariablesOf } from "../graphql/graphql.js";
2
2
  import { StorefrontQueryOptions } from "../client/storefront.js";
3
3
  import { AccountCallbackResult, AccountLoginOptions, AccountLogoutOptions, AuthRecord } from "pocketbomb-primer";
4
+ /**
5
+ * The login options of {@link useCustomer}. `returnTo` is the page to return to after the
6
+ * login (the account routes validate it with `isSafeReturnTo`).
7
+ *
8
+ * With the account routes of a server rendered app the options travel in the route URL, so only
9
+ * `returnTo`, `prompt`, `maxAge` and `loginHint` are supported: the route decides everything else.
10
+ */
11
+ export interface CustomerLoginOptions extends AccountLoginOptions {
12
+ returnTo?: string;
13
+ }
4
14
  export interface CustomerState {
5
15
  /** The signed in customer record (null if signed out). */
6
16
  customer: AuthRecord;
@@ -10,9 +20,9 @@ export interface CustomerState {
10
20
  /** The last account error (e.g. a failed callback). */
11
21
  error: Error | null;
12
22
  /** Starts the login (server route navigation, browser redirect or mobile auth session). */
13
- login(options?: AccountLoginOptions): Promise<AccountCallbackResult | null>;
23
+ login(options?: CustomerLoginOptions): Promise<AccountCallbackResult | null>;
14
24
  /** Starts the signup (`prompt=create`). */
15
- signup(options?: Omit<AccountLoginOptions, "prompt">): Promise<AccountCallbackResult | null>;
25
+ signup(options?: Omit<CustomerLoginOptions, "prompt">): Promise<AccountCallbackResult | null>;
16
26
  /** Signs out (server route navigation or the account logout). */
17
27
  logout(options?: AccountLogoutOptions): Promise<void>;
18
28
  }
@@ -1,6 +1,32 @@
1
+ import { loginRouteURL, logoutRouteURL } from "../customer-account/customer-account.js";
1
2
  import { useDynamiteContext } from "./context.js";
2
3
  import { useCallback, useEffect, useRef, useState } from "react";
3
4
  // src/react/hooks.ts
5
+ function rejectRouteOptions(options) {
6
+ const names = Object.keys(options).filter((name) => options[name] !== void 0);
7
+ if (names.length) throw new Error("useCustomer(): " + names.join(", ") + " can't be sent through an account route URL; set them in the route loader instead.");
8
+ }
9
+ function routeLoginOptions(options = {}) {
10
+ const { returnTo, prompt, maxAge, loginHint, ...rest } = options;
11
+ rejectRouteOptions(rest);
12
+ return {
13
+ returnTo,
14
+ prompt,
15
+ maxAge,
16
+ loginHint
17
+ };
18
+ }
19
+ function accountLoginOptions(options) {
20
+ if (options?.returnTo === void 0) return options;
21
+ const { returnTo, ...rest } = options;
22
+ return {
23
+ ...rest,
24
+ state: {
25
+ ...rest.state,
26
+ returnTo
27
+ }
28
+ };
29
+ }
4
30
  /**
5
31
  * Returns the customer account state and actions.
6
32
  *
@@ -45,10 +71,10 @@ function useCustomer() {
45
71
  }, []);
46
72
  const login = useCallback((options) => run(async () => {
47
73
  if (customerRoutes) {
48
- navigate(customerRoutes.login);
74
+ navigate(loginRouteURL(customerRoutes.login, routeLoginOptions(options)));
49
75
  return null;
50
76
  }
51
- return customerClient ? customerClient.login(options) : null;
77
+ return customerClient ? customerClient.login(accountLoginOptions(options)) : null;
52
78
  }), [
53
79
  run,
54
80
  customerRoutes,
@@ -57,10 +83,14 @@ function useCustomer() {
57
83
  ]);
58
84
  const signup = useCallback((options) => run(async () => {
59
85
  if (customerRoutes) {
60
- navigate(customerRoutes.signup || customerRoutes.login);
86
+ const routeOptions = routeLoginOptions(options);
87
+ navigate(customerRoutes.signup ? loginRouteURL(customerRoutes.signup, routeOptions) : loginRouteURL(customerRoutes.login, {
88
+ ...routeOptions,
89
+ prompt: "create"
90
+ }));
61
91
  return null;
62
92
  }
63
- return customerClient ? customerClient.signup(options) : null;
93
+ return customerClient ? customerClient.signup(accountLoginOptions(options)) : null;
64
94
  }), [
65
95
  run,
66
96
  customerRoutes,
@@ -69,7 +99,9 @@ function useCustomer() {
69
99
  ]);
70
100
  const logout = useCallback((options) => run(async () => {
71
101
  if (customerRoutes) {
72
- navigate(customerRoutes.logout);
102
+ const { postLogoutRedirectUri, ...rest } = options || {};
103
+ rejectRouteOptions(rest);
104
+ navigate(logoutRouteURL(customerRoutes.logout, { postLogoutRedirectUri }));
73
105
  return;
74
106
  }
75
107
  await customerClient?.logout(options);
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.js","names":[],"sources":["../../src/react/hooks.ts"],"sourcesContent":["import type {\n AccountCallbackResult,\n AccountLoginOptions,\n AccountLogoutOptions,\n AuthRecord,\n} from \"pocketbomb-primer\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { StorefrontQueryOptions } from \"../client/storefront\";\nimport type { DocumentInput, ResultOf, VariablesOf } from \"../graphql/graphql\";\nimport { useDynamiteContext } from \"./context\";\n\nexport interface CustomerState {\n /** The signed in customer record (null if signed out). */\n customer: AuthRecord;\n isLoggedIn: boolean;\n /** Whether a login/logout/restore is in progress. */\n loading: boolean;\n /** The last account error (e.g. a failed callback). */\n error: Error | null;\n /** Starts the login (server route navigation, browser redirect or mobile auth session). */\n login(options?: AccountLoginOptions): Promise<AccountCallbackResult | null>;\n /** Starts the signup (`prompt=create`). */\n signup(options?: Omit<AccountLoginOptions, \"prompt\">): Promise<AccountCallbackResult | null>;\n /** Signs out (server route navigation or the account logout). */\n logout(options?: AccountLogoutOptions): Promise<void>;\n}\n\n/**\n * Returns the customer account state and actions.\n *\n * - server rendered apps (`customer.routes`): the customer comes from `initialCustomer` and\n * the actions navigate to the account routes\n * - browser apps (customer client options): the state follows the account auth store; on\n * mount the session is restored and a pending authorization response is handled\n */\nexport function useCustomer(): CustomerState {\n const { customerClient, customerRoutes, initialCustomer, navigate } = useDynamiteContext();\n\n // the first render always uses the server value (hydration safe); the browser account\n // store state is applied by the effect\n const [customer, setCustomer] = useState<AuthRecord>(initialCustomer);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n if (!customerClient) {\n setCustomer(initialCustomer);\n return;\n }\n\n const store = customerClient.client.authStore;\n const unsubscribe = store.onChange((_, record) => setCustomer(record), true);\n\n let active = true;\n setLoading(true);\n (async () => {\n if (customerClient.account.isCallbackURL()) {\n await customerClient.handleCallback();\n }\n await customerClient.account.restore();\n })()\n .catch((err) => active && setError(err))\n .finally(() => active && setLoading(false));\n\n return () => {\n active = false;\n unsubscribe();\n };\n }, [customerClient, initialCustomer]);\n\n const run = useCallback(async <T>(fn: () => Promise<T>): Promise<T> => {\n setLoading(true);\n setError(null);\n try {\n return await fn();\n } catch (err) {\n setError(err as Error);\n throw err;\n } finally {\n setLoading(false);\n }\n }, []);\n\n const login = useCallback(\n (options?: AccountLoginOptions) =>\n run(async () => {\n if (customerRoutes) {\n navigate(customerRoutes.login);\n return null;\n }\n return customerClient ? customerClient.login(options) : null;\n }),\n [run, customerRoutes, customerClient, navigate],\n );\n\n const signup = useCallback(\n (options?: Omit<AccountLoginOptions, \"prompt\">) =>\n run(async () => {\n if (customerRoutes) {\n navigate(customerRoutes.signup || customerRoutes.login);\n return null;\n }\n return customerClient ? customerClient.signup(options) : null;\n }),\n [run, customerRoutes, customerClient, navigate],\n );\n\n const logout = useCallback(\n (options?: AccountLogoutOptions) =>\n run(async () => {\n if (customerRoutes) {\n navigate(customerRoutes.logout);\n return;\n }\n await customerClient?.logout(options);\n }),\n [run, customerRoutes, customerClient, navigate],\n );\n\n return {\n customer,\n isLoggedIn: !!customer,\n loading,\n error,\n login,\n signup,\n logout,\n };\n}\n\nexport interface UseStorefrontQueryOptions<\n Doc extends DocumentInput,\n> extends StorefrontQueryOptions<VariablesOf<Doc>> {\n /** The data rendered by the server (no request on mount). */\n initialData?: ResultOf<Doc>;\n /** Skip the request. */\n skip?: boolean;\n}\n\nexport interface StorefrontQueryState<Data> {\n data: Data | undefined;\n error: Error | null;\n loading: boolean;\n refetch(): Promise<void>;\n}\n\n/**\n * Runs a storefront GraphQL query in the browser (public key) and re-runs it when the\n * variables change. The server rendered `initialData` is used for the first render.\n */\nexport function useStorefrontQuery<Doc extends DocumentInput>(\n document: Doc,\n options: UseStorefrontQueryOptions<Doc> = {},\n): StorefrontQueryState<ResultOf<Doc>> {\n const storefront = useDynamiteContext().storefront;\n const { initialData, skip, ...queryOptions } = options;\n\n const variablesKey = JSON.stringify(queryOptions.variables ?? null);\n const [data, setData] = useState<ResultOf<Doc> | undefined>(initialData);\n const [error, setError] = useState<Error | null>(null);\n const [loading, setLoading] = useState(!skip && initialData === undefined);\n\n // the initial data matches the initial variables (no refetch on hydration)\n const fetchedKey = useRef<string | null>(initialData !== undefined ? variablesKey : null);\n const optionsRef = useRef(queryOptions);\n optionsRef.current = queryOptions;\n\n const fetchData = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n setData(await storefront.query(document, optionsRef.current));\n } catch (err) {\n setError(err as Error);\n } finally {\n setLoading(false);\n }\n }, [storefront, document]);\n\n useEffect(() => {\n if (skip || fetchedKey.current === variablesKey) {\n return;\n }\n fetchedKey.current = variablesKey;\n void fetchData();\n }, [skip, variablesKey, fetchData]);\n\n return { data, error, loading, refetch: fetchData };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"hooks.js","names":[],"sources":["../../src/react/hooks.ts"],"sourcesContent":["import type {\n AccountCallbackResult,\n AccountLoginOptions,\n AccountLogoutOptions,\n AuthRecord,\n} from \"pocketbomb-primer\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { StorefrontQueryOptions } from \"../client/storefront\";\nimport type { DocumentInput, ResultOf, VariablesOf } from \"../graphql/graphql\";\nimport {\n loginRouteURL,\n logoutRouteURL,\n type LoginRouteOptions,\n} from \"../customer-account/customer-account\";\nimport { useDynamiteContext } from \"./context\";\n\n/**\n * The login options of {@link useCustomer}. `returnTo` is the page to return to after the\n * login (the account routes validate it with `isSafeReturnTo`).\n *\n * With the account routes of a server rendered app the options travel in the route URL, so only\n * `returnTo`, `prompt`, `maxAge` and `loginHint` are supported: the route decides everything else.\n */\nexport interface CustomerLoginOptions extends AccountLoginOptions {\n returnTo?: string;\n}\n\nfunction rejectRouteOptions(options: Record<string, unknown>): void {\n const names = Object.keys(options).filter((name) => options[name] !== undefined);\n if (names.length) {\n throw new Error(\n \"useCustomer(): \" +\n names.join(\", \") +\n \" can't be sent through an account route URL; set them in the route loader instead.\",\n );\n }\n}\n\nfunction routeLoginOptions(options: CustomerLoginOptions = {}): LoginRouteOptions {\n const { returnTo, prompt, maxAge, loginHint, ...rest } = options;\n rejectRouteOptions(rest);\n return { returnTo, prompt, maxAge, loginHint };\n}\n\nfunction accountLoginOptions(options?: CustomerLoginOptions): AccountLoginOptions | undefined {\n if (options?.returnTo === undefined) {\n return options;\n }\n const { returnTo, ...rest } = options;\n return { ...rest, state: { ...rest.state, returnTo } };\n}\n\nexport interface CustomerState {\n /** The signed in customer record (null if signed out). */\n customer: AuthRecord;\n isLoggedIn: boolean;\n /** Whether a login/logout/restore is in progress. */\n loading: boolean;\n /** The last account error (e.g. a failed callback). */\n error: Error | null;\n /** Starts the login (server route navigation, browser redirect or mobile auth session). */\n login(options?: CustomerLoginOptions): Promise<AccountCallbackResult | null>;\n /** Starts the signup (`prompt=create`). */\n signup(options?: Omit<CustomerLoginOptions, \"prompt\">): Promise<AccountCallbackResult | null>;\n /** Signs out (server route navigation or the account logout). */\n logout(options?: AccountLogoutOptions): Promise<void>;\n}\n\n/**\n * Returns the customer account state and actions.\n *\n * - server rendered apps (`customer.routes`): the customer comes from `initialCustomer` and\n * the actions navigate to the account routes\n * - browser apps (customer client options): the state follows the account auth store; on\n * mount the session is restored and a pending authorization response is handled\n */\nexport function useCustomer(): CustomerState {\n const { customerClient, customerRoutes, initialCustomer, navigate } = useDynamiteContext();\n\n // the first render always uses the server value (hydration safe); the browser account\n // store state is applied by the effect\n const [customer, setCustomer] = useState<AuthRecord>(initialCustomer);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n if (!customerClient) {\n setCustomer(initialCustomer);\n return;\n }\n\n const store = customerClient.client.authStore;\n const unsubscribe = store.onChange((_, record) => setCustomer(record), true);\n\n let active = true;\n setLoading(true);\n (async () => {\n if (customerClient.account.isCallbackURL()) {\n await customerClient.handleCallback();\n }\n await customerClient.account.restore();\n })()\n .catch((err) => active && setError(err))\n .finally(() => active && setLoading(false));\n\n return () => {\n active = false;\n unsubscribe();\n };\n }, [customerClient, initialCustomer]);\n\n const run = useCallback(async <T>(fn: () => Promise<T>): Promise<T> => {\n setLoading(true);\n setError(null);\n try {\n return await fn();\n } catch (err) {\n setError(err as Error);\n throw err;\n } finally {\n setLoading(false);\n }\n }, []);\n\n const login = useCallback(\n (options?: CustomerLoginOptions) =>\n run(async () => {\n if (customerRoutes) {\n navigate(loginRouteURL(customerRoutes.login, routeLoginOptions(options)));\n return null;\n }\n return customerClient ? customerClient.login(accountLoginOptions(options)) : null;\n }),\n [run, customerRoutes, customerClient, navigate],\n );\n\n const signup = useCallback(\n (options?: Omit<CustomerLoginOptions, \"prompt\">) =>\n run(async () => {\n if (customerRoutes) {\n const routeOptions = routeLoginOptions(options);\n navigate(\n customerRoutes.signup\n ? loginRouteURL(customerRoutes.signup, routeOptions)\n : loginRouteURL(customerRoutes.login, { ...routeOptions, prompt: \"create\" }),\n );\n return null;\n }\n return customerClient ? customerClient.signup(accountLoginOptions(options)) : null;\n }),\n [run, customerRoutes, customerClient, navigate],\n );\n\n const logout = useCallback(\n (options?: AccountLogoutOptions) =>\n run(async () => {\n if (customerRoutes) {\n const { postLogoutRedirectUri, ...rest } = options || {};\n rejectRouteOptions(rest);\n navigate(logoutRouteURL(customerRoutes.logout, { postLogoutRedirectUri }));\n return;\n }\n await customerClient?.logout(options);\n }),\n [run, customerRoutes, customerClient, navigate],\n );\n\n return {\n customer,\n isLoggedIn: !!customer,\n loading,\n error,\n login,\n signup,\n logout,\n };\n}\n\nexport interface UseStorefrontQueryOptions<\n Doc extends DocumentInput,\n> extends StorefrontQueryOptions<VariablesOf<Doc>> {\n /** The data rendered by the server (no request on mount). */\n initialData?: ResultOf<Doc>;\n /** Skip the request. */\n skip?: boolean;\n}\n\nexport interface StorefrontQueryState<Data> {\n data: Data | undefined;\n error: Error | null;\n loading: boolean;\n refetch(): Promise<void>;\n}\n\n/**\n * Runs a storefront GraphQL query in the browser (public key) and re-runs it when the\n * variables change. The server rendered `initialData` is used for the first render.\n */\nexport function useStorefrontQuery<Doc extends DocumentInput>(\n document: Doc,\n options: UseStorefrontQueryOptions<Doc> = {},\n): StorefrontQueryState<ResultOf<Doc>> {\n const storefront = useDynamiteContext().storefront;\n const { initialData, skip, ...queryOptions } = options;\n\n const variablesKey = JSON.stringify(queryOptions.variables ?? null);\n const [data, setData] = useState<ResultOf<Doc> | undefined>(initialData);\n const [error, setError] = useState<Error | null>(null);\n const [loading, setLoading] = useState(!skip && initialData === undefined);\n\n // the initial data matches the initial variables (no refetch on hydration)\n const fetchedKey = useRef<string | null>(initialData !== undefined ? variablesKey : null);\n const optionsRef = useRef(queryOptions);\n optionsRef.current = queryOptions;\n\n const fetchData = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n setData(await storefront.query(document, optionsRef.current));\n } catch (err) {\n setError(err as Error);\n } finally {\n setLoading(false);\n }\n }, [storefront, document]);\n\n useEffect(() => {\n if (skip || fetchedKey.current === variablesKey) {\n return;\n }\n fetchedKey.current = variablesKey;\n void fetchData();\n }, [skip, variablesKey, fetchData]);\n\n return { data, error, loading, refetch: fetchData };\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
1
  import { AccountCallbackResult, AccountLoginOptions, CustomerRoutes, DynamiteContextValue, DynamiteProvider, DynamiteProviderProps, useDynamiteContext, useStorefront } from "./context.cjs";
2
- import { CustomerState, StorefrontQueryState, UseStorefrontQueryOptions, useCustomer, useStorefrontQuery } from "./hooks.cjs";
2
+ import { CustomerLoginOptions, CustomerState, StorefrontQueryState, UseStorefrontQueryOptions, useCustomer, useStorefrontQuery } from "./hooks.cjs";
3
3
  import { FileImage, FileImageProps, PAGINATION_CURSOR_PARAM, PAGINATION_DIRECTION_PARAM, Pagination, PaginationProps, PaginationRenderProps, getPaginationURL, getPaginationVariables } from "./components.cjs";
4
- export { type AccountCallbackResult, type AccountLoginOptions, CustomerRoutes, CustomerState, DynamiteContextValue, DynamiteProvider, DynamiteProviderProps, FileImage, FileImageProps, PAGINATION_CURSOR_PARAM, PAGINATION_DIRECTION_PARAM, Pagination, PaginationProps, PaginationRenderProps, StorefrontQueryState, UseStorefrontQueryOptions, getPaginationURL, getPaginationVariables, useCustomer, useDynamiteContext, useStorefront, useStorefrontQuery };
4
+ export { type AccountCallbackResult, type AccountLoginOptions, CustomerLoginOptions, CustomerRoutes, CustomerState, DynamiteContextValue, DynamiteProvider, DynamiteProviderProps, FileImage, FileImageProps, PAGINATION_CURSOR_PARAM, PAGINATION_DIRECTION_PARAM, Pagination, PaginationProps, PaginationRenderProps, StorefrontQueryState, UseStorefrontQueryOptions, getPaginationURL, getPaginationVariables, useCustomer, useDynamiteContext, useStorefront, useStorefrontQuery };
@@ -1,4 +1,4 @@
1
1
  import { AccountCallbackResult, AccountLoginOptions, CustomerRoutes, DynamiteContextValue, DynamiteProvider, DynamiteProviderProps, useDynamiteContext, useStorefront } from "./context.js";
2
- import { CustomerState, StorefrontQueryState, UseStorefrontQueryOptions, useCustomer, useStorefrontQuery } from "./hooks.js";
2
+ import { CustomerLoginOptions, CustomerState, StorefrontQueryState, UseStorefrontQueryOptions, useCustomer, useStorefrontQuery } from "./hooks.js";
3
3
  import { FileImage, FileImageProps, PAGINATION_CURSOR_PARAM, PAGINATION_DIRECTION_PARAM, Pagination, PaginationProps, PaginationRenderProps, getPaginationURL, getPaginationVariables } from "./components.js";
4
- export { type AccountCallbackResult, type AccountLoginOptions, CustomerRoutes, CustomerState, DynamiteContextValue, DynamiteProvider, DynamiteProviderProps, FileImage, FileImageProps, PAGINATION_CURSOR_PARAM, PAGINATION_DIRECTION_PARAM, Pagination, PaginationProps, PaginationRenderProps, StorefrontQueryState, UseStorefrontQueryOptions, getPaginationURL, getPaginationVariables, useCustomer, useDynamiteContext, useStorefront, useStorefrontQuery };
4
+ export { type AccountCallbackResult, type AccountLoginOptions, CustomerLoginOptions, CustomerRoutes, CustomerState, DynamiteContextValue, DynamiteProvider, DynamiteProviderProps, FileImage, FileImageProps, PAGINATION_CURSOR_PARAM, PAGINATION_DIRECTION_PARAM, Pagination, PaginationProps, PaginationRenderProps, StorefrontQueryState, UseStorefrontQueryOptions, getPaginationURL, getPaginationVariables, useCustomer, useDynamiteContext, useStorefront, useStorefrontQuery };
@@ -1,3 +1,4 @@
1
- declare const init: (void 0).PluginModuleFactory;
1
+ import ts from "typescript/lib/tsserverlibrary";
2
+ declare const init: ts.server.PluginModuleFactory;
2
3
  export = init;
3
4
  //# sourceMappingURL=index.d.cts.map
@@ -64,7 +64,8 @@ function dynamite(options = {}) {
64
64
  env = pocketbombEnv(configEnv.mode, root);
65
65
  for (const key in env) process.env[key] ??= env[key];
66
66
  fusePending = isDev && !!options.fuse && !env.POCKETBOMB_URL;
67
- if (!proxyEnabled()) return;
67
+ const optimizeDeps = { include: ["pocketbomb-dynamite/graphql", "pocketbomb-dynamite/react"] };
68
+ if (!proxyEnabled()) return { optimizeDeps };
68
69
  const proxy = {};
69
70
  for (const path of PROXIED_PATHS) proxy[path] = {
70
71
  target: env.POCKETBOMB_URL || "http://127.0.0.1:9",
@@ -72,7 +73,10 @@ function dynamite(options = {}) {
72
73
  ws: true,
73
74
  configure: (server) => proxies.push(server)
74
75
  };
75
- return { server: { proxy } };
76
+ return {
77
+ optimizeDeps,
78
+ server: { proxy }
79
+ };
76
80
  },
77
81
  configResolved(config) {
78
82
  resolved = config;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["loadEnv","codegen","watchCodegen"],"sources":["../../src/vite/index.ts"],"sourcesContent":["// pocketbomb-dynamite/vite\n//\n// The Vite plugin of the PocketBomb apps:\n// - env: POCKETBOMB_URL and POCKETBOMB_PUBLIC_KEY are exposed to the client code as\n// `import.meta.env.*`; the private key stays server-only (`process.env`) and the client\n// bundles are checked to never contain it\n// - dev proxy of /api, /oauth2 and /.well-known to the PocketBomb server (same-origin dev)\n// - the GraphQL codegen on start and on schema changes (dev)\n// - fuse: a local PocketBomb server started with the dev server\nimport type { Connect, Plugin, ResolvedConfig } from \"vite\";\nimport { loadEnv } from \"vite\";\nimport type { Fuse, FuseOptions } from \"pocketbomb-fuse/node\";\n\ntype ProxyOptions = {\n target: string;\n changeOrigin: boolean;\n ws: boolean;\n configure: (proxy: any) => void;\n};\nimport { codegen, watchCodegen, type GenerateOptions } from \"../cli/codegen\";\n\nexport const PUBLIC_ENV_KEYS = [\"POCKETBOMB_URL\", \"POCKETBOMB_PUBLIC_KEY\"] as const;\nexport const PRIVATE_KEY_ENV = \"POCKETBOMB_PRIVATE_KEY\";\nexport const PROXIED_PATHS = [\"/api\", \"/oauth2\", \"/.well-known\"] as const;\n\nconst PUBLIC_ENV_PATTERN = /\\bimport\\.meta\\.env\\.(POCKETBOMB_URL|POCKETBOMB_PUBLIC_KEY)\\b/g;\n\nexport interface DynamitePluginOptions {\n /**\n * Start a local PocketBomb server with fuse for the dev server\n * (skipped when POCKETBOMB_URL is set).\n */\n fuse?: boolean | FuseOptions;\n\n /**\n * Proxy /api, /oauth2 and /.well-known to the PocketBomb server in dev (default to true);\n * the client `POCKETBOMB_URL` is then \"/\" (same origin).\n */\n proxy?: boolean;\n\n /**\n * Run the GraphQL codegen on start and on schema changes in dev (default to true when\n * a public key is available).\n */\n codegen?: boolean | (GenerateOptions & { interval?: number });\n}\n\nfunction isSecretExposedByPrefix(prefixes: string | Array<string> | undefined): boolean {\n const list = Array.isArray(prefixes) ? prefixes : [prefixes || \"VITE_\"];\n return list.some((prefix) => prefix !== \"\" && PRIVATE_KEY_ENV.startsWith(prefix));\n}\n\n/**\n * Returns the PocketBomb variables of the mode env files (`.env*`) and of `process.env`\n * (the latter wins).\n */\nexport function pocketbombEnv(mode: string, root: string): Record<string, string> {\n const fromFiles = loadEnv(mode, root, \"POCKETBOMB_\");\n const result: Record<string, string> = { ...fromFiles };\n for (const key in process.env) {\n if (key.startsWith(\"POCKETBOMB_\") && process.env[key]) {\n result[key] = process.env[key]!;\n }\n }\n return result;\n}\n\n/**\n * The PocketBomb Vite plugin (see the module docs).\n */\nexport function dynamite(options: DynamitePluginOptions = {}): Plugin {\n let env: Record<string, string> = {};\n let fuse: Fuse | null = null;\n let isDev = false;\n let stopCodegen = () => {};\n let resolved: ResolvedConfig | null = null;\n let fusePending = false;\n const proxies: Array<{ options: { target?: string } }> = [];\n\n const proxyEnabled = () =>\n isDev && options.proxy !== false && (!!env.POCKETBOMB_URL || fusePending);\n\n const startCodegen = async () => {\n const codegenOptions = options.codegen;\n if (codegenOptions === false || !env.POCKETBOMB_URL || !env.POCKETBOMB_PUBLIC_KEY) {\n return;\n }\n\n const extra = typeof codegenOptions === \"object\" ? codegenOptions : {};\n const base = {\n url: env.POCKETBOMB_URL,\n publicKey: env.POCKETBOMB_PUBLIC_KEY,\n cwd: resolved?.root,\n ...extra,\n };\n\n try {\n await codegen(base);\n } catch (err) {\n resolved?.logger.warn(\"[dynamite] codegen: \" + String((err as Error)?.message || err));\n }\n\n stopCodegen = watchCodegen({\n ...base,\n onChange: (result) => resolved?.logger.info(\"[dynamite] codegen: \" + result.envFile),\n onError: (err) =>\n resolved?.logger.warn(\"[dynamite] codegen: \" + String((err as Error)?.message || err)),\n });\n };\n\n const plugin: Plugin = {\n name: \"pocketbomb:dynamite\",\n\n config(config, configEnv) {\n isDev = configEnv.command === \"serve\";\n const root = config.root || process.cwd();\n\n if (isSecretExposedByPrefix(config.envPrefix)) {\n throw new Error(\n \"[dynamite] the Vite envPrefix exposes \" +\n PRIVATE_KEY_ENV +\n \" to the client code; use a narrower prefix.\",\n );\n }\n\n env = pocketbombEnv(configEnv.mode, root);\n // make the env files available to the server code too\n for (const key in env) {\n process.env[key] ??= env[key];\n }\n\n // fuse is started with the first dev request (not by the build/typegen helper servers)\n fusePending = isDev && !!options.fuse && !env.POCKETBOMB_URL;\n\n if (!proxyEnabled()) {\n return;\n }\n\n const proxy: Record<string, ProxyOptions> = {};\n for (const path of PROXIED_PATHS) {\n proxy[path] = {\n // the target of a pending fuse is set once it runs\n target: env.POCKETBOMB_URL || \"http://127.0.0.1:9\",\n // the Host header is preserved so that the same-origin redirect URIs are accepted\n changeOrigin: false,\n ws: true,\n configure: (server: { options: { target?: string } }) => proxies.push(server),\n };\n }\n\n return { server: { proxy } };\n },\n\n configResolved(config) {\n resolved = config;\n },\n\n configureServer(server) {\n let ready: Promise<void> | null = null;\n\n const start = async () => {\n if (fusePending) {\n fusePending = false;\n const { startFuse } = await import(\"pocketbomb-fuse/node\");\n fuse = await startFuse(options.fuse === true ? {} : (options.fuse as FuseOptions));\n Object.assign(process.env, fuse.env);\n env = { ...env, ...fuse.env };\n server.config.logger.info(\"[dynamite] fuse: PocketBomb running on \" + fuse.url);\n }\n\n for (const proxy of proxies) {\n proxy.options.target = env.POCKETBOMB_URL;\n }\n\n await startCodegen();\n };\n\n // the dev runtime (fuse, codegen) starts with the first request\n const ensureStarted: Connect.NextHandleFunction = (_req, _res, next) => {\n ready ??= start();\n ready.then(() => next(), next);\n };\n server.middlewares.use(ensureStarted);\n },\n\n // note: the public env is inlined by this transform (in dev Vite shares the `define` of\n // `import.meta.env.*` between the client and the SSR modules): the client gets the browser\n // URL (\"/\" with the dev proxy) and the SSR modules the absolute URL\n transform(code, id) {\n if (id.includes(\"/node_modules/\")) {\n return;\n }\n\n const isClient = this.environment?.name === \"client\";\n const privateKey = env[PRIVATE_KEY_ENV];\n if (\n isClient &&\n ((privateKey && code.includes(privateKey)) ||\n code.includes(\"import.meta.env.\" + PRIVATE_KEY_ENV))\n ) {\n this.error(\n \"[dynamite] \" +\n id +\n \" uses the PocketBomb private key in client code; it must stay server-only.\",\n );\n }\n\n if (!code.includes(\"import.meta.env.POCKETBOMB_\")) {\n return;\n }\n\n const values: Record<string, string> = {\n POCKETBOMB_URL: isClient && proxyEnabled() ? \"/\" : env.POCKETBOMB_URL || \"\",\n POCKETBOMB_PUBLIC_KEY: env.POCKETBOMB_PUBLIC_KEY || \"\",\n };\n\n return {\n code: code.replace(PUBLIC_ENV_PATTERN, (_, key: string) => JSON.stringify(values[key])),\n map: null,\n };\n },\n\n generateBundle(_, bundle) {\n if (this.environment?.name !== \"client\") {\n return;\n }\n\n const privateKey = env[PRIVATE_KEY_ENV];\n if (!privateKey) {\n return;\n }\n\n for (const file in bundle) {\n const item = bundle[file];\n const content =\n item.type === \"chunk\"\n ? item.code\n : typeof item.source === \"string\"\n ? item.source\n : new TextDecoder().decode(item.source);\n if (content.includes(privateKey)) {\n this.error(\n \"[dynamite] the client asset \" + file + \" contains the PocketBomb private key.\",\n );\n }\n }\n },\n\n async buildEnd() {\n stopCodegen();\n stopCodegen = () => {};\n\n if (fuse) {\n const stopping = fuse;\n fuse = null;\n for (const key in stopping.env) {\n if (process.env[key] === stopping.env[key]) {\n delete process.env[key];\n }\n }\n await stopping.stop();\n }\n },\n };\n\n return plugin;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["loadEnv","codegen","watchCodegen"],"sources":["../../src/vite/index.ts"],"sourcesContent":["// pocketbomb-dynamite/vite\n//\n// The Vite plugin of the PocketBomb apps:\n// - env: POCKETBOMB_URL and POCKETBOMB_PUBLIC_KEY are exposed to the client code as\n// `import.meta.env.*`; the private key stays server-only (`process.env`) and the client\n// bundles are checked to never contain it\n// - dev proxy of /api, /oauth2 and /.well-known to the PocketBomb server (same-origin dev)\n// - the GraphQL codegen on start and on schema changes (dev)\n// - fuse: a local PocketBomb server started with the dev server\nimport type { Connect, Plugin, ResolvedConfig } from \"vite\";\nimport { loadEnv } from \"vite\";\nimport type { Fuse, FuseOptions } from \"pocketbomb-fuse/node\";\n\ntype ProxyOptions = {\n target: string;\n changeOrigin: boolean;\n ws: boolean;\n configure: (proxy: any) => void;\n};\nimport { codegen, watchCodegen, type GenerateOptions } from \"../cli/codegen\";\n\nexport const PUBLIC_ENV_KEYS = [\"POCKETBOMB_URL\", \"POCKETBOMB_PUBLIC_KEY\"] as const;\nexport const PRIVATE_KEY_ENV = \"POCKETBOMB_PRIVATE_KEY\";\nexport const PROXIED_PATHS = [\"/api\", \"/oauth2\", \"/.well-known\"] as const;\n\nconst PUBLIC_ENV_PATTERN = /\\bimport\\.meta\\.env\\.(POCKETBOMB_URL|POCKETBOMB_PUBLIC_KEY)\\b/g;\n\nexport interface DynamitePluginOptions {\n /**\n * Start a local PocketBomb server with fuse for the dev server\n * (skipped when POCKETBOMB_URL is set).\n */\n fuse?: boolean | FuseOptions;\n\n /**\n * Proxy /api, /oauth2 and /.well-known to the PocketBomb server in dev (default to true);\n * the client `POCKETBOMB_URL` is then \"/\" (same origin).\n */\n proxy?: boolean;\n\n /**\n * Run the GraphQL codegen on start and on schema changes in dev (default to true when\n * a public key is available).\n */\n codegen?: boolean | (GenerateOptions & { interval?: number });\n}\n\nfunction isSecretExposedByPrefix(prefixes: string | Array<string> | undefined): boolean {\n const list = Array.isArray(prefixes) ? prefixes : [prefixes || \"VITE_\"];\n return list.some((prefix) => prefix !== \"\" && PRIVATE_KEY_ENV.startsWith(prefix));\n}\n\n/**\n * Returns the PocketBomb variables of the mode env files (`.env*`) and of `process.env`\n * (the latter wins).\n */\nexport function pocketbombEnv(mode: string, root: string): Record<string, string> {\n const fromFiles = loadEnv(mode, root, \"POCKETBOMB_\");\n const result: Record<string, string> = { ...fromFiles };\n for (const key in process.env) {\n if (key.startsWith(\"POCKETBOMB_\") && process.env[key]) {\n result[key] = process.env[key]!;\n }\n }\n return result;\n}\n\n/**\n * The PocketBomb Vite plugin (see the module docs).\n */\nexport function dynamite(options: DynamitePluginOptions = {}): Plugin {\n let env: Record<string, string> = {};\n let fuse: Fuse | null = null;\n let isDev = false;\n let stopCodegen = () => {};\n let resolved: ResolvedConfig | null = null;\n let fusePending = false;\n const proxies: Array<{ options: { target?: string } }> = [];\n\n const proxyEnabled = () =>\n isDev && options.proxy !== false && (!!env.POCKETBOMB_URL || fusePending);\n\n const startCodegen = async () => {\n const codegenOptions = options.codegen;\n if (codegenOptions === false || !env.POCKETBOMB_URL || !env.POCKETBOMB_PUBLIC_KEY) {\n return;\n }\n\n const extra = typeof codegenOptions === \"object\" ? codegenOptions : {};\n const base = {\n url: env.POCKETBOMB_URL,\n publicKey: env.POCKETBOMB_PUBLIC_KEY,\n cwd: resolved?.root,\n ...extra,\n };\n\n try {\n await codegen(base);\n } catch (err) {\n resolved?.logger.warn(\"[dynamite] codegen: \" + String((err as Error)?.message || err));\n }\n\n stopCodegen = watchCodegen({\n ...base,\n onChange: (result) => resolved?.logger.info(\"[dynamite] codegen: \" + result.envFile),\n onError: (err) =>\n resolved?.logger.warn(\"[dynamite] codegen: \" + String((err as Error)?.message || err)),\n });\n };\n\n const plugin: Plugin = {\n name: \"pocketbomb:dynamite\",\n\n config(config, configEnv) {\n isDev = configEnv.command === \"serve\";\n const root = config.root || process.cwd();\n\n if (isSecretExposedByPrefix(config.envPrefix)) {\n throw new Error(\n \"[dynamite] the Vite envPrefix exposes \" +\n PRIVATE_KEY_ENV +\n \" to the client code; use a narrower prefix.\",\n );\n }\n\n env = pocketbombEnv(configEnv.mode, root);\n // make the env files available to the server code too\n for (const key in env) {\n process.env[key] ??= env[key];\n }\n\n // fuse is started with the first dev request (not by the build/typegen helper servers)\n fusePending = isDev && !!options.fuse && !env.POCKETBOMB_URL;\n\n // discovered late they make a first visit reload the page halfway through\n const optimizeDeps = {\n include: [\"pocketbomb-dynamite/graphql\", \"pocketbomb-dynamite/react\"],\n };\n\n if (!proxyEnabled()) {\n return { optimizeDeps };\n }\n\n const proxy: Record<string, ProxyOptions> = {};\n for (const path of PROXIED_PATHS) {\n proxy[path] = {\n // the target of a pending fuse is set once it runs\n target: env.POCKETBOMB_URL || \"http://127.0.0.1:9\",\n // the Host header is preserved so that the same-origin redirect URIs are accepted\n changeOrigin: false,\n ws: true,\n configure: (server: { options: { target?: string } }) => proxies.push(server),\n };\n }\n\n return { optimizeDeps, server: { proxy } };\n },\n\n configResolved(config) {\n resolved = config;\n },\n\n configureServer(server) {\n let ready: Promise<void> | null = null;\n\n const start = async () => {\n if (fusePending) {\n fusePending = false;\n const { startFuse } = await import(\"pocketbomb-fuse/node\");\n fuse = await startFuse(options.fuse === true ? {} : (options.fuse as FuseOptions));\n Object.assign(process.env, fuse.env);\n env = { ...env, ...fuse.env };\n server.config.logger.info(\"[dynamite] fuse: PocketBomb running on \" + fuse.url);\n }\n\n for (const proxy of proxies) {\n proxy.options.target = env.POCKETBOMB_URL;\n }\n\n await startCodegen();\n };\n\n // the dev runtime (fuse, codegen) starts with the first request\n const ensureStarted: Connect.NextHandleFunction = (_req, _res, next) => {\n ready ??= start();\n ready.then(() => next(), next);\n };\n server.middlewares.use(ensureStarted);\n },\n\n // note: the public env is inlined by this transform (in dev Vite shares the `define` of\n // `import.meta.env.*` between the client and the SSR modules): the client gets the browser\n // URL (\"/\" with the dev proxy) and the SSR modules the absolute URL\n transform(code, id) {\n if (id.includes(\"/node_modules/\")) {\n return;\n }\n\n const isClient = this.environment?.name === \"client\";\n const privateKey = env[PRIVATE_KEY_ENV];\n if (\n isClient &&\n ((privateKey && code.includes(privateKey)) ||\n code.includes(\"import.meta.env.\" + PRIVATE_KEY_ENV))\n ) {\n this.error(\n \"[dynamite] \" +\n id +\n \" uses the PocketBomb private key in client code; it must stay server-only.\",\n );\n }\n\n if (!code.includes(\"import.meta.env.POCKETBOMB_\")) {\n return;\n }\n\n const values: Record<string, string> = {\n POCKETBOMB_URL: isClient && proxyEnabled() ? \"/\" : env.POCKETBOMB_URL || \"\",\n POCKETBOMB_PUBLIC_KEY: env.POCKETBOMB_PUBLIC_KEY || \"\",\n };\n\n return {\n code: code.replace(PUBLIC_ENV_PATTERN, (_, key: string) => JSON.stringify(values[key])),\n map: null,\n };\n },\n\n generateBundle(_, bundle) {\n if (this.environment?.name !== \"client\") {\n return;\n }\n\n const privateKey = env[PRIVATE_KEY_ENV];\n if (!privateKey) {\n return;\n }\n\n for (const file in bundle) {\n const item = bundle[file];\n const content =\n item.type === \"chunk\"\n ? item.code\n : typeof item.source === \"string\"\n ? item.source\n : new TextDecoder().decode(item.source);\n if (content.includes(privateKey)) {\n this.error(\n \"[dynamite] the client asset \" + file + \" contains the PocketBomb private key.\",\n );\n }\n }\n },\n\n async buildEnd() {\n stopCodegen();\n stopCodegen = () => {};\n\n if (fuse) {\n const stopping = fuse;\n fuse = null;\n for (const key in stopping.env) {\n if (process.env[key] === stopping.env[key]) {\n delete process.env[key];\n }\n }\n await stopping.stop();\n }\n },\n };\n\n return plugin;\n}\n"],"mappings":"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"}
@@ -63,7 +63,8 @@ function dynamite(options = {}) {
63
63
  env = pocketbombEnv(configEnv.mode, root);
64
64
  for (const key in env) process.env[key] ??= env[key];
65
65
  fusePending = isDev && !!options.fuse && !env.POCKETBOMB_URL;
66
- if (!proxyEnabled()) return;
66
+ const optimizeDeps = { include: ["pocketbomb-dynamite/graphql", "pocketbomb-dynamite/react"] };
67
+ if (!proxyEnabled()) return { optimizeDeps };
67
68
  const proxy = {};
68
69
  for (const path of PROXIED_PATHS) proxy[path] = {
69
70
  target: env.POCKETBOMB_URL || "http://127.0.0.1:9",
@@ -71,7 +72,10 @@ function dynamite(options = {}) {
71
72
  ws: true,
72
73
  configure: (server) => proxies.push(server)
73
74
  };
74
- return { server: { proxy } };
75
+ return {
76
+ optimizeDeps,
77
+ server: { proxy }
78
+ };
75
79
  },
76
80
  configResolved(config) {
77
81
  resolved = config;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/vite/index.ts"],"sourcesContent":["// pocketbomb-dynamite/vite\n//\n// The Vite plugin of the PocketBomb apps:\n// - env: POCKETBOMB_URL and POCKETBOMB_PUBLIC_KEY are exposed to the client code as\n// `import.meta.env.*`; the private key stays server-only (`process.env`) and the client\n// bundles are checked to never contain it\n// - dev proxy of /api, /oauth2 and /.well-known to the PocketBomb server (same-origin dev)\n// - the GraphQL codegen on start and on schema changes (dev)\n// - fuse: a local PocketBomb server started with the dev server\nimport type { Connect, Plugin, ResolvedConfig } from \"vite\";\nimport { loadEnv } from \"vite\";\nimport type { Fuse, FuseOptions } from \"pocketbomb-fuse/node\";\n\ntype ProxyOptions = {\n target: string;\n changeOrigin: boolean;\n ws: boolean;\n configure: (proxy: any) => void;\n};\nimport { codegen, watchCodegen, type GenerateOptions } from \"../cli/codegen\";\n\nexport const PUBLIC_ENV_KEYS = [\"POCKETBOMB_URL\", \"POCKETBOMB_PUBLIC_KEY\"] as const;\nexport const PRIVATE_KEY_ENV = \"POCKETBOMB_PRIVATE_KEY\";\nexport const PROXIED_PATHS = [\"/api\", \"/oauth2\", \"/.well-known\"] as const;\n\nconst PUBLIC_ENV_PATTERN = /\\bimport\\.meta\\.env\\.(POCKETBOMB_URL|POCKETBOMB_PUBLIC_KEY)\\b/g;\n\nexport interface DynamitePluginOptions {\n /**\n * Start a local PocketBomb server with fuse for the dev server\n * (skipped when POCKETBOMB_URL is set).\n */\n fuse?: boolean | FuseOptions;\n\n /**\n * Proxy /api, /oauth2 and /.well-known to the PocketBomb server in dev (default to true);\n * the client `POCKETBOMB_URL` is then \"/\" (same origin).\n */\n proxy?: boolean;\n\n /**\n * Run the GraphQL codegen on start and on schema changes in dev (default to true when\n * a public key is available).\n */\n codegen?: boolean | (GenerateOptions & { interval?: number });\n}\n\nfunction isSecretExposedByPrefix(prefixes: string | Array<string> | undefined): boolean {\n const list = Array.isArray(prefixes) ? prefixes : [prefixes || \"VITE_\"];\n return list.some((prefix) => prefix !== \"\" && PRIVATE_KEY_ENV.startsWith(prefix));\n}\n\n/**\n * Returns the PocketBomb variables of the mode env files (`.env*`) and of `process.env`\n * (the latter wins).\n */\nexport function pocketbombEnv(mode: string, root: string): Record<string, string> {\n const fromFiles = loadEnv(mode, root, \"POCKETBOMB_\");\n const result: Record<string, string> = { ...fromFiles };\n for (const key in process.env) {\n if (key.startsWith(\"POCKETBOMB_\") && process.env[key]) {\n result[key] = process.env[key]!;\n }\n }\n return result;\n}\n\n/**\n * The PocketBomb Vite plugin (see the module docs).\n */\nexport function dynamite(options: DynamitePluginOptions = {}): Plugin {\n let env: Record<string, string> = {};\n let fuse: Fuse | null = null;\n let isDev = false;\n let stopCodegen = () => {};\n let resolved: ResolvedConfig | null = null;\n let fusePending = false;\n const proxies: Array<{ options: { target?: string } }> = [];\n\n const proxyEnabled = () =>\n isDev && options.proxy !== false && (!!env.POCKETBOMB_URL || fusePending);\n\n const startCodegen = async () => {\n const codegenOptions = options.codegen;\n if (codegenOptions === false || !env.POCKETBOMB_URL || !env.POCKETBOMB_PUBLIC_KEY) {\n return;\n }\n\n const extra = typeof codegenOptions === \"object\" ? codegenOptions : {};\n const base = {\n url: env.POCKETBOMB_URL,\n publicKey: env.POCKETBOMB_PUBLIC_KEY,\n cwd: resolved?.root,\n ...extra,\n };\n\n try {\n await codegen(base);\n } catch (err) {\n resolved?.logger.warn(\"[dynamite] codegen: \" + String((err as Error)?.message || err));\n }\n\n stopCodegen = watchCodegen({\n ...base,\n onChange: (result) => resolved?.logger.info(\"[dynamite] codegen: \" + result.envFile),\n onError: (err) =>\n resolved?.logger.warn(\"[dynamite] codegen: \" + String((err as Error)?.message || err)),\n });\n };\n\n const plugin: Plugin = {\n name: \"pocketbomb:dynamite\",\n\n config(config, configEnv) {\n isDev = configEnv.command === \"serve\";\n const root = config.root || process.cwd();\n\n if (isSecretExposedByPrefix(config.envPrefix)) {\n throw new Error(\n \"[dynamite] the Vite envPrefix exposes \" +\n PRIVATE_KEY_ENV +\n \" to the client code; use a narrower prefix.\",\n );\n }\n\n env = pocketbombEnv(configEnv.mode, root);\n // make the env files available to the server code too\n for (const key in env) {\n process.env[key] ??= env[key];\n }\n\n // fuse is started with the first dev request (not by the build/typegen helper servers)\n fusePending = isDev && !!options.fuse && !env.POCKETBOMB_URL;\n\n if (!proxyEnabled()) {\n return;\n }\n\n const proxy: Record<string, ProxyOptions> = {};\n for (const path of PROXIED_PATHS) {\n proxy[path] = {\n // the target of a pending fuse is set once it runs\n target: env.POCKETBOMB_URL || \"http://127.0.0.1:9\",\n // the Host header is preserved so that the same-origin redirect URIs are accepted\n changeOrigin: false,\n ws: true,\n configure: (server: { options: { target?: string } }) => proxies.push(server),\n };\n }\n\n return { server: { proxy } };\n },\n\n configResolved(config) {\n resolved = config;\n },\n\n configureServer(server) {\n let ready: Promise<void> | null = null;\n\n const start = async () => {\n if (fusePending) {\n fusePending = false;\n const { startFuse } = await import(\"pocketbomb-fuse/node\");\n fuse = await startFuse(options.fuse === true ? {} : (options.fuse as FuseOptions));\n Object.assign(process.env, fuse.env);\n env = { ...env, ...fuse.env };\n server.config.logger.info(\"[dynamite] fuse: PocketBomb running on \" + fuse.url);\n }\n\n for (const proxy of proxies) {\n proxy.options.target = env.POCKETBOMB_URL;\n }\n\n await startCodegen();\n };\n\n // the dev runtime (fuse, codegen) starts with the first request\n const ensureStarted: Connect.NextHandleFunction = (_req, _res, next) => {\n ready ??= start();\n ready.then(() => next(), next);\n };\n server.middlewares.use(ensureStarted);\n },\n\n // note: the public env is inlined by this transform (in dev Vite shares the `define` of\n // `import.meta.env.*` between the client and the SSR modules): the client gets the browser\n // URL (\"/\" with the dev proxy) and the SSR modules the absolute URL\n transform(code, id) {\n if (id.includes(\"/node_modules/\")) {\n return;\n }\n\n const isClient = this.environment?.name === \"client\";\n const privateKey = env[PRIVATE_KEY_ENV];\n if (\n isClient &&\n ((privateKey && code.includes(privateKey)) ||\n code.includes(\"import.meta.env.\" + PRIVATE_KEY_ENV))\n ) {\n this.error(\n \"[dynamite] \" +\n id +\n \" uses the PocketBomb private key in client code; it must stay server-only.\",\n );\n }\n\n if (!code.includes(\"import.meta.env.POCKETBOMB_\")) {\n return;\n }\n\n const values: Record<string, string> = {\n POCKETBOMB_URL: isClient && proxyEnabled() ? \"/\" : env.POCKETBOMB_URL || \"\",\n POCKETBOMB_PUBLIC_KEY: env.POCKETBOMB_PUBLIC_KEY || \"\",\n };\n\n return {\n code: code.replace(PUBLIC_ENV_PATTERN, (_, key: string) => JSON.stringify(values[key])),\n map: null,\n };\n },\n\n generateBundle(_, bundle) {\n if (this.environment?.name !== \"client\") {\n return;\n }\n\n const privateKey = env[PRIVATE_KEY_ENV];\n if (!privateKey) {\n return;\n }\n\n for (const file in bundle) {\n const item = bundle[file];\n const content =\n item.type === \"chunk\"\n ? item.code\n : typeof item.source === \"string\"\n ? item.source\n : new TextDecoder().decode(item.source);\n if (content.includes(privateKey)) {\n this.error(\n \"[dynamite] the client asset \" + file + \" contains the PocketBomb private key.\",\n );\n }\n }\n },\n\n async buildEnd() {\n stopCodegen();\n stopCodegen = () => {};\n\n if (fuse) {\n const stopping = fuse;\n fuse = null;\n for (const key in stopping.env) {\n if (process.env[key] === stopping.env[key]) {\n delete process.env[key];\n }\n }\n await stopping.stop();\n }\n },\n };\n\n return plugin;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/vite/index.ts"],"sourcesContent":["// pocketbomb-dynamite/vite\n//\n// The Vite plugin of the PocketBomb apps:\n// - env: POCKETBOMB_URL and POCKETBOMB_PUBLIC_KEY are exposed to the client code as\n// `import.meta.env.*`; the private key stays server-only (`process.env`) and the client\n// bundles are checked to never contain it\n// - dev proxy of /api, /oauth2 and /.well-known to the PocketBomb server (same-origin dev)\n// - the GraphQL codegen on start and on schema changes (dev)\n// - fuse: a local PocketBomb server started with the dev server\nimport type { Connect, Plugin, ResolvedConfig } from \"vite\";\nimport { loadEnv } from \"vite\";\nimport type { Fuse, FuseOptions } from \"pocketbomb-fuse/node\";\n\ntype ProxyOptions = {\n target: string;\n changeOrigin: boolean;\n ws: boolean;\n configure: (proxy: any) => void;\n};\nimport { codegen, watchCodegen, type GenerateOptions } from \"../cli/codegen\";\n\nexport const PUBLIC_ENV_KEYS = [\"POCKETBOMB_URL\", \"POCKETBOMB_PUBLIC_KEY\"] as const;\nexport const PRIVATE_KEY_ENV = \"POCKETBOMB_PRIVATE_KEY\";\nexport const PROXIED_PATHS = [\"/api\", \"/oauth2\", \"/.well-known\"] as const;\n\nconst PUBLIC_ENV_PATTERN = /\\bimport\\.meta\\.env\\.(POCKETBOMB_URL|POCKETBOMB_PUBLIC_KEY)\\b/g;\n\nexport interface DynamitePluginOptions {\n /**\n * Start a local PocketBomb server with fuse for the dev server\n * (skipped when POCKETBOMB_URL is set).\n */\n fuse?: boolean | FuseOptions;\n\n /**\n * Proxy /api, /oauth2 and /.well-known to the PocketBomb server in dev (default to true);\n * the client `POCKETBOMB_URL` is then \"/\" (same origin).\n */\n proxy?: boolean;\n\n /**\n * Run the GraphQL codegen on start and on schema changes in dev (default to true when\n * a public key is available).\n */\n codegen?: boolean | (GenerateOptions & { interval?: number });\n}\n\nfunction isSecretExposedByPrefix(prefixes: string | Array<string> | undefined): boolean {\n const list = Array.isArray(prefixes) ? prefixes : [prefixes || \"VITE_\"];\n return list.some((prefix) => prefix !== \"\" && PRIVATE_KEY_ENV.startsWith(prefix));\n}\n\n/**\n * Returns the PocketBomb variables of the mode env files (`.env*`) and of `process.env`\n * (the latter wins).\n */\nexport function pocketbombEnv(mode: string, root: string): Record<string, string> {\n const fromFiles = loadEnv(mode, root, \"POCKETBOMB_\");\n const result: Record<string, string> = { ...fromFiles };\n for (const key in process.env) {\n if (key.startsWith(\"POCKETBOMB_\") && process.env[key]) {\n result[key] = process.env[key]!;\n }\n }\n return result;\n}\n\n/**\n * The PocketBomb Vite plugin (see the module docs).\n */\nexport function dynamite(options: DynamitePluginOptions = {}): Plugin {\n let env: Record<string, string> = {};\n let fuse: Fuse | null = null;\n let isDev = false;\n let stopCodegen = () => {};\n let resolved: ResolvedConfig | null = null;\n let fusePending = false;\n const proxies: Array<{ options: { target?: string } }> = [];\n\n const proxyEnabled = () =>\n isDev && options.proxy !== false && (!!env.POCKETBOMB_URL || fusePending);\n\n const startCodegen = async () => {\n const codegenOptions = options.codegen;\n if (codegenOptions === false || !env.POCKETBOMB_URL || !env.POCKETBOMB_PUBLIC_KEY) {\n return;\n }\n\n const extra = typeof codegenOptions === \"object\" ? codegenOptions : {};\n const base = {\n url: env.POCKETBOMB_URL,\n publicKey: env.POCKETBOMB_PUBLIC_KEY,\n cwd: resolved?.root,\n ...extra,\n };\n\n try {\n await codegen(base);\n } catch (err) {\n resolved?.logger.warn(\"[dynamite] codegen: \" + String((err as Error)?.message || err));\n }\n\n stopCodegen = watchCodegen({\n ...base,\n onChange: (result) => resolved?.logger.info(\"[dynamite] codegen: \" + result.envFile),\n onError: (err) =>\n resolved?.logger.warn(\"[dynamite] codegen: \" + String((err as Error)?.message || err)),\n });\n };\n\n const plugin: Plugin = {\n name: \"pocketbomb:dynamite\",\n\n config(config, configEnv) {\n isDev = configEnv.command === \"serve\";\n const root = config.root || process.cwd();\n\n if (isSecretExposedByPrefix(config.envPrefix)) {\n throw new Error(\n \"[dynamite] the Vite envPrefix exposes \" +\n PRIVATE_KEY_ENV +\n \" to the client code; use a narrower prefix.\",\n );\n }\n\n env = pocketbombEnv(configEnv.mode, root);\n // make the env files available to the server code too\n for (const key in env) {\n process.env[key] ??= env[key];\n }\n\n // fuse is started with the first dev request (not by the build/typegen helper servers)\n fusePending = isDev && !!options.fuse && !env.POCKETBOMB_URL;\n\n // discovered late they make a first visit reload the page halfway through\n const optimizeDeps = {\n include: [\"pocketbomb-dynamite/graphql\", \"pocketbomb-dynamite/react\"],\n };\n\n if (!proxyEnabled()) {\n return { optimizeDeps };\n }\n\n const proxy: Record<string, ProxyOptions> = {};\n for (const path of PROXIED_PATHS) {\n proxy[path] = {\n // the target of a pending fuse is set once it runs\n target: env.POCKETBOMB_URL || \"http://127.0.0.1:9\",\n // the Host header is preserved so that the same-origin redirect URIs are accepted\n changeOrigin: false,\n ws: true,\n configure: (server: { options: { target?: string } }) => proxies.push(server),\n };\n }\n\n return { optimizeDeps, server: { proxy } };\n },\n\n configResolved(config) {\n resolved = config;\n },\n\n configureServer(server) {\n let ready: Promise<void> | null = null;\n\n const start = async () => {\n if (fusePending) {\n fusePending = false;\n const { startFuse } = await import(\"pocketbomb-fuse/node\");\n fuse = await startFuse(options.fuse === true ? {} : (options.fuse as FuseOptions));\n Object.assign(process.env, fuse.env);\n env = { ...env, ...fuse.env };\n server.config.logger.info(\"[dynamite] fuse: PocketBomb running on \" + fuse.url);\n }\n\n for (const proxy of proxies) {\n proxy.options.target = env.POCKETBOMB_URL;\n }\n\n await startCodegen();\n };\n\n // the dev runtime (fuse, codegen) starts with the first request\n const ensureStarted: Connect.NextHandleFunction = (_req, _res, next) => {\n ready ??= start();\n ready.then(() => next(), next);\n };\n server.middlewares.use(ensureStarted);\n },\n\n // note: the public env is inlined by this transform (in dev Vite shares the `define` of\n // `import.meta.env.*` between the client and the SSR modules): the client gets the browser\n // URL (\"/\" with the dev proxy) and the SSR modules the absolute URL\n transform(code, id) {\n if (id.includes(\"/node_modules/\")) {\n return;\n }\n\n const isClient = this.environment?.name === \"client\";\n const privateKey = env[PRIVATE_KEY_ENV];\n if (\n isClient &&\n ((privateKey && code.includes(privateKey)) ||\n code.includes(\"import.meta.env.\" + PRIVATE_KEY_ENV))\n ) {\n this.error(\n \"[dynamite] \" +\n id +\n \" uses the PocketBomb private key in client code; it must stay server-only.\",\n );\n }\n\n if (!code.includes(\"import.meta.env.POCKETBOMB_\")) {\n return;\n }\n\n const values: Record<string, string> = {\n POCKETBOMB_URL: isClient && proxyEnabled() ? \"/\" : env.POCKETBOMB_URL || \"\",\n POCKETBOMB_PUBLIC_KEY: env.POCKETBOMB_PUBLIC_KEY || \"\",\n };\n\n return {\n code: code.replace(PUBLIC_ENV_PATTERN, (_, key: string) => JSON.stringify(values[key])),\n map: null,\n };\n },\n\n generateBundle(_, bundle) {\n if (this.environment?.name !== \"client\") {\n return;\n }\n\n const privateKey = env[PRIVATE_KEY_ENV];\n if (!privateKey) {\n return;\n }\n\n for (const file in bundle) {\n const item = bundle[file];\n const content =\n item.type === \"chunk\"\n ? item.code\n : typeof item.source === \"string\"\n ? item.source\n : new TextDecoder().decode(item.source);\n if (content.includes(privateKey)) {\n this.error(\n \"[dynamite] the client asset \" + file + \" contains the PocketBomb private key.\",\n );\n }\n }\n },\n\n async buildEnd() {\n stopCodegen();\n stopCodegen = () => {};\n\n if (fuse) {\n const stopping = fuse;\n fuse = null;\n for (const key in stopping.env) {\n if (process.env[key] === stopping.env[key]) {\n delete process.env[key];\n }\n }\n await stopping.stop();\n }\n },\n };\n\n return plugin;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pocketbomb-dynamite",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "PocketBomb storefront framework: request context (buyer IP, cookie session, cache), storefront and customer account clients, gql.tada GraphQL, React bindings, Vite plugin, TypeScript plugin and the dynamite CLI (codegen, dev).",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -90,8 +90,8 @@
90
90
  "@gql.tada/internal": "1.2.2",
91
91
  "gql.tada": "^1.11.4",
92
92
  "graphql": "^16.11.0",
93
- "pocketbomb-fuse": "^0.2.0",
94
- "pocketbomb-primer": "^0.2.0"
93
+ "pocketbomb-fuse": "^0.2.1",
94
+ "pocketbomb-primer": "^0.3.0"
95
95
  },
96
96
  "devDependencies": {
97
97
  "@types/node": "^24.0.0",
@@ -107,12 +107,16 @@
107
107
  },
108
108
  "peerDependencies": {
109
109
  "react": ">=18",
110
+ "typescript": ">=5",
110
111
  "vite": ">=6"
111
112
  },
112
113
  "peerDependenciesMeta": {
113
114
  "react": {
114
115
  "optional": true
115
116
  },
117
+ "typescript": {
118
+ "optional": true
119
+ },
116
120
  "vite": {
117
121
  "optional": true
118
122
  }