bsmnt 0.6.3 → 0.7.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 (91) hide show
  1. package/dist/application/add-integration/index.d.ts.map +1 -1
  2. package/dist/application/add-integration/index.js +30 -4
  3. package/dist/application/add-integration/index.js.map +1 -1
  4. package/dist/application/shared/node-version.d.ts +8 -0
  5. package/dist/application/shared/node-version.d.ts.map +1 -0
  6. package/dist/application/shared/node-version.js +31 -0
  7. package/dist/application/shared/node-version.js.map +1 -0
  8. package/dist/application/shared/strip-ansi.d.ts +3 -0
  9. package/dist/application/shared/strip-ansi.d.ts.map +1 -0
  10. package/dist/application/shared/strip-ansi.js +7 -0
  11. package/dist/application/shared/strip-ansi.js.map +1 -0
  12. package/dist/core/create/execute-plan.d.ts +3 -0
  13. package/dist/core/create/execute-plan.d.ts.map +1 -1
  14. package/dist/core/create/execute-plan.js +3 -0
  15. package/dist/core/create/execute-plan.js.map +1 -1
  16. package/dist/domain/cms.d.ts +2 -0
  17. package/dist/domain/cms.d.ts.map +1 -1
  18. package/dist/domain/cms.js +1 -0
  19. package/dist/domain/cms.js.map +1 -1
  20. package/dist/domain/forms.d.ts +2 -2
  21. package/dist/domain/forms.js +1 -1
  22. package/dist/domain/forms.js.map +1 -1
  23. package/dist/domain/integration.d.ts +7 -3
  24. package/dist/domain/integration.d.ts.map +1 -1
  25. package/dist/domain/integration.js.map +1 -1
  26. package/dist/domain/skills.d.ts +26 -8
  27. package/dist/domain/skills.d.ts.map +1 -1
  28. package/dist/domain/skills.js +30 -9
  29. package/dist/domain/skills.js.map +1 -1
  30. package/dist/index.js +132 -16
  31. package/dist/index.js.map +1 -1
  32. package/dist/infrastructure/create/executor.d.ts.map +1 -1
  33. package/dist/infrastructure/create/executor.js +2 -1
  34. package/dist/infrastructure/create/executor.js.map +1 -1
  35. package/dist/infrastructure/create/init-git-repo.d.ts +11 -7
  36. package/dist/infrastructure/create/init-git-repo.d.ts.map +1 -1
  37. package/dist/infrastructure/create/init-git-repo.js +21 -18
  38. package/dist/infrastructure/create/init-git-repo.js.map +1 -1
  39. package/dist/infrastructure/create/sanity-auth.d.ts +11 -0
  40. package/dist/infrastructure/create/sanity-auth.d.ts.map +1 -0
  41. package/dist/infrastructure/create/sanity-auth.js +72 -0
  42. package/dist/infrastructure/create/sanity-auth.js.map +1 -0
  43. package/dist/infrastructure/create/setup-agent.d.ts +4 -3
  44. package/dist/infrastructure/create/setup-agent.d.ts.map +1 -1
  45. package/dist/infrastructure/create/setup-agent.js +51 -30
  46. package/dist/infrastructure/create/setup-agent.js.map +1 -1
  47. package/dist/infrastructure/create/setup-dotenvx.js +1 -1
  48. package/dist/infrastructure/create/setup-dotenvx.js.map +1 -1
  49. package/dist/infrastructure/create/setup-remote.d.ts +14 -0
  50. package/dist/infrastructure/create/setup-remote.d.ts.map +1 -0
  51. package/dist/infrastructure/create/setup-remote.js +139 -0
  52. package/dist/infrastructure/create/setup-remote.js.map +1 -0
  53. package/dist/infrastructure/create/setup-sanity.d.ts +3 -2
  54. package/dist/infrastructure/create/setup-sanity.d.ts.map +1 -1
  55. package/dist/infrastructure/create/setup-sanity.js +34 -49
  56. package/dist/infrastructure/create/setup-sanity.js.map +1 -1
  57. package/dist/modules/features/env/dotenvx/config.d.ts.map +1 -1
  58. package/dist/modules/features/env/dotenvx/config.js +6 -0
  59. package/dist/modules/features/env/dotenvx/config.js.map +1 -1
  60. package/dist/paths.d.ts +0 -2
  61. package/dist/paths.d.ts.map +1 -1
  62. package/dist/paths.js +0 -2
  63. package/dist/paths.js.map +1 -1
  64. package/package.json +3 -3
  65. package/src/agent-skills/hubspot-pagebuilder-block/SKILL.md +160 -0
  66. package/src/agent-skills/rss-feed/SKILL.md +158 -0
  67. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/sanity.config.ts +3 -0
  68. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.config.ts +3 -0
  69. package/src/modules/features/cms/sanity-pagebuilder/files/lib/scripts/sanity-typegen.ts +16 -25
  70. package/src/modules/features/env/dotenvx/files/push-keys.mjs +7 -7
  71. package/src/modules/features/env/dotenvx/files/save-key.mjs +27 -12
  72. package/src/modules/features/env/dotenvx/files/setup-remote.ts +182 -0
  73. package/src/modules/features/forms/hubspot/files/_sanity-adapter/form-block.ts +60 -0
  74. package/src/modules/features/forms/hubspot/files/_sanity-adapter/form-field.ts +30 -0
  75. package/src/modules/features/forms/hubspot/files/_sanity-adapter/form-select-input.tsx +169 -0
  76. package/src/modules/features/forms/hubspot/files/lib/integrations/hubspot/README.md +114 -0
  77. package/src/modules/features/forms/hubspot/files/lib/integrations/hubspot/embed.tsx +165 -0
  78. package/src/modules/features/forms/hubspot/files/lib/integrations/hubspot/form-selection.ts +40 -0
  79. package/src/modules/features/forms/hubspot/files/lib/integrations/hubspot/form.tsx +29 -0
  80. package/src/modules/features/forms/hubspot/files/lib/integrations/hubspot/forms.ts +142 -0
  81. package/src/modules/features/forms/hubspot/files/lib/integrations/hubspot/hubspot-form.css +634 -0
  82. package/src/modules/features/forms/hubspot/files/lib/integrations/hubspot/skeleton.tsx +24 -0
  83. package/src/templates/next-default/README.md +1 -0
  84. package/src/templates/next-default/knip.json +9 -1
  85. package/src/templates/next-experiments/README.md +1 -0
  86. package/src/templates/next-experiments/knip.json +9 -1
  87. package/src/templates/next-pagebuilder/README.md +1 -0
  88. package/src/templates/next-pagebuilder/knip.json +7 -2
  89. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +1197 -176
  90. package/src/templates/next-webgl/README.md +1 -0
  91. package/src/templates/next-webgl/knip.json +9 -1
@@ -0,0 +1,165 @@
1
+ "use client"
2
+
3
+ import { useCallback, useEffect, useId, useRef, useState } from "react"
4
+ import { HubSpotFormSkeleton } from "./skeleton"
5
+ import "./hubspot-form.css"
6
+
7
+ const HUBSPOT_FORMS_SCRIPT_SRC = "https://js.hsforms.net/forms/embed/v2.js"
8
+ // A form that hasn't rendered within this window is treated as failed.
9
+ const FORM_READY_TIMEOUT_MS = 12_000
10
+
11
+ let hubspotFormsScriptPromise: Promise<void> | null = null
12
+
13
+ type HubSpotFormCreateOptions = {
14
+ portalId: string
15
+ formId: string
16
+ region: string
17
+ target: string
18
+ cssRequired?: string
19
+ css?: string
20
+ onFormReady?: (form: HTMLFormElement) => void
21
+ }
22
+
23
+ declare global {
24
+ interface Window {
25
+ hbspt?: {
26
+ forms?: {
27
+ create: (options: HubSpotFormCreateOptions) => void
28
+ }
29
+ }
30
+ }
31
+ }
32
+
33
+ const loadHubSpotFormsScript = () => {
34
+ if (typeof window === "undefined") return Promise.resolve()
35
+ if (window.hbspt?.forms) return Promise.resolve()
36
+ if (hubspotFormsScriptPromise) return hubspotFormsScriptPromise
37
+
38
+ hubspotFormsScriptPromise = new Promise((resolve, reject) => {
39
+ // Drop the dead tag on error so a later mount refetches instead of awaiting
40
+ // a `load` that already fired.
41
+ const fail = (script: HTMLScriptElement) => {
42
+ hubspotFormsScriptPromise = null
43
+ script.remove()
44
+ reject(new Error("Failed to load the HubSpot forms script."))
45
+ }
46
+
47
+ // An existing tag means a load is in flight (a finished one set window.hbspt,
48
+ // caught above) — attach to its events.
49
+ const existing = document.querySelector<HTMLScriptElement>(
50
+ `script[src="${HUBSPOT_FORMS_SCRIPT_SRC}"]`
51
+ )
52
+ if (existing) {
53
+ existing.addEventListener("load", () => resolve(), { once: true })
54
+ existing.addEventListener("error", () => fail(existing), { once: true })
55
+ return
56
+ }
57
+
58
+ const script = document.createElement("script")
59
+ script.src = HUBSPOT_FORMS_SCRIPT_SRC
60
+ script.async = true
61
+ script.charset = "utf-8"
62
+ script.onload = () => resolve()
63
+ script.onerror = () => fail(script)
64
+ document.head.appendChild(script)
65
+ })
66
+
67
+ return hubspotFormsScriptPromise
68
+ }
69
+
70
+ type HubSpotFormEmbedProps = {
71
+ portalId: string
72
+ formId: string
73
+ region: string
74
+ }
75
+
76
+ export function HubSpotFormEmbed({
77
+ portalId,
78
+ formId,
79
+ region,
80
+ }: HubSpotFormEmbedProps) {
81
+ // Sanitize useId() (it can contain `:`) for use as a DOM id / selector.
82
+ const containerId = `hubspot-form-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`
83
+ const containerRef = useRef<HTMLDivElement | null>(null)
84
+ const readyTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
85
+ const unmountedRef = useRef(false)
86
+ const [hasError, setHasError] = useState(false)
87
+ const [isReady, setIsReady] = useState(false)
88
+
89
+ const createForm = useCallback(() => {
90
+ const target = containerRef.current
91
+ const formsApi = window.hbspt?.forms
92
+ if (!(target && formsApi)) {
93
+ setHasError(true)
94
+ return
95
+ }
96
+
97
+ setIsReady(false)
98
+ target.innerHTML = ""
99
+
100
+ // create() is fire-and-forget: a bad portal/region or archived form never
101
+ // calls onFormReady, so fall back to the error state on timeout.
102
+ if (readyTimeoutRef.current) clearTimeout(readyTimeoutRef.current)
103
+ readyTimeoutRef.current = setTimeout(() => {
104
+ if (unmountedRef.current) return
105
+ target.innerHTML = ""
106
+ setHasError(true)
107
+ }, FORM_READY_TIMEOUT_MS)
108
+
109
+ // cssRequired/css are emptied so HubSpot doesn't inject its own stylesheet —
110
+ // the form is styled by hubspot-form.css instead. Everything else (submit,
111
+ // validation, the post-submit thank-you message) is HubSpot's default.
112
+ formsApi.create({
113
+ portalId,
114
+ formId,
115
+ region,
116
+ target: `#${containerId}`,
117
+ cssRequired: "",
118
+ css: "",
119
+ onFormReady: () => {
120
+ if (unmountedRef.current) return
121
+ if (readyTimeoutRef.current) clearTimeout(readyTimeoutRef.current)
122
+ setIsReady(true)
123
+ setHasError(false)
124
+ },
125
+ })
126
+ }, [containerId, formId, portalId, region])
127
+
128
+ useEffect(() => {
129
+ unmountedRef.current = false
130
+ setHasError(false)
131
+
132
+ void loadHubSpotFormsScript()
133
+ .then(() => {
134
+ if (unmountedRef.current) return
135
+ createForm()
136
+ })
137
+ .catch((error: unknown) => {
138
+ if (unmountedRef.current) return
139
+ console.error("Failed to mount HubSpot form", error)
140
+ setHasError(true)
141
+ })
142
+
143
+ return () => {
144
+ unmountedRef.current = true
145
+ if (readyTimeoutRef.current) clearTimeout(readyTimeoutRef.current)
146
+ if (containerRef.current) containerRef.current.innerHTML = ""
147
+ }
148
+ }, [createForm])
149
+
150
+ const showSkeleton = !(isReady || hasError)
151
+
152
+ return (
153
+ <div className="hubspot-form-wrapper">
154
+ <div className="hubspot-form-embed" id={containerId} ref={containerRef} />
155
+
156
+ {showSkeleton ? <HubSpotFormSkeleton /> : null}
157
+
158
+ {hasError ? (
159
+ <p className="hubspot-form-error">
160
+ We couldn&apos;t load this form right now.
161
+ </p>
162
+ ) : null}
163
+ </div>
164
+ )
165
+ }
@@ -0,0 +1,40 @@
1
+ export type HubSpotFormSelection = {
2
+ name: string
3
+ formId: string
4
+ portalId: string
5
+ region: string
6
+ }
7
+
8
+ export function decodeHubSpotFormSelection(
9
+ value?: string | null
10
+ ): HubSpotFormSelection | null {
11
+ if (!value) return null
12
+
13
+ try {
14
+ const parsed: unknown = JSON.parse(value)
15
+
16
+ if (typeof parsed !== "object" || parsed === null) return null
17
+
18
+ const { name, formId, portalId, region } = parsed as Record<string, unknown>
19
+
20
+ if (
21
+ typeof name !== "string" ||
22
+ typeof formId !== "string" ||
23
+ typeof portalId !== "string" ||
24
+ typeof region !== "string"
25
+ ) {
26
+ return null
27
+ }
28
+
29
+ // Fields that drive hbspt.forms.create must be non-empty (name is cosmetic).
30
+ if (!(formId.trim() && portalId.trim() && region.trim())) return null
31
+
32
+ return { name, formId, portalId, region }
33
+ } catch {
34
+ return null
35
+ }
36
+ }
37
+
38
+ export function getHubSpotFormSelectionLabel(selection: HubSpotFormSelection) {
39
+ return `${selection.name} (${selection.formId})`
40
+ }
@@ -0,0 +1,29 @@
1
+ import { HubSpotFormEmbed } from "./embed"
2
+ import "./hubspot-form.css"
3
+
4
+ type HubSpotFormProps = {
5
+ portalId: string
6
+ formId: string
7
+ region: string
8
+ className?: string | undefined
9
+ }
10
+
11
+ /**
12
+ * Renders a HubSpot form, fully styled and decoupled from any CMS. Pass the
13
+ * portal/form/region directly, or decode them from a stored selection with
14
+ * `decodeHubSpotFormSelection`.
15
+ */
16
+ export function HubSpotForm({
17
+ portalId,
18
+ formId,
19
+ region,
20
+ className,
21
+ }: HubSpotFormProps) {
22
+ const classes = ["hubspot-form-card", className].filter(Boolean).join(" ")
23
+
24
+ return (
25
+ <div className={classes}>
26
+ <HubSpotFormEmbed formId={formId} portalId={portalId} region={region} />
27
+ </div>
28
+ )
29
+ }
@@ -0,0 +1,142 @@
1
+ "use server"
2
+
3
+ import {
4
+ getHubSpotFormSelectionLabel,
5
+ type HubSpotFormSelection,
6
+ } from "./form-selection"
7
+
8
+ type HubSpotAccountDetailsResponse = {
9
+ portalId?: number | string
10
+ dataHostingLocation?: string
11
+ }
12
+
13
+ type HubSpotApiForm = {
14
+ id?: string
15
+ name?: string
16
+ archived?: boolean
17
+ displayOptions?: {
18
+ renderRawHtml?: boolean
19
+ }
20
+ }
21
+
22
+ type HubSpotFormsResponse = {
23
+ results?: HubSpotApiForm[]
24
+ paging?: { next?: { after?: string } }
25
+ }
26
+
27
+ export type HubSpotFormOption = {
28
+ label: string
29
+ value: string
30
+ /** False when HubSpot will iframe-embed (Free portals); used only as a hint. */
31
+ renderRawHtml: boolean | null
32
+ }
33
+
34
+ const HUBSPOT_API_BASE_URL = "https://api.hubapi.com"
35
+
36
+ async function fetchHubSpotJson<T>(path: string, accessToken: string) {
37
+ const response = await fetch(`${HUBSPOT_API_BASE_URL}${path}`, {
38
+ headers: {
39
+ Authorization: `Bearer ${accessToken}`,
40
+ Accept: "application/json",
41
+ },
42
+ cache: "no-store",
43
+ })
44
+
45
+ if (!response.ok) {
46
+ // Keep the full body for the server log, but throw a clean status so the
47
+ // caller-facing message never leaks HubSpot's raw response.
48
+ const body = await response.text()
49
+ console.error(
50
+ `HubSpot request failed (${response.status}) ${path}: ${body}`
51
+ )
52
+ throw new Error(`HubSpot request failed (${response.status}).`)
53
+ }
54
+
55
+ return (await response.json()) as T
56
+ }
57
+
58
+ async function fetchHubSpotForms(accessToken: string) {
59
+ const forms: HubSpotApiForm[] = []
60
+ let after: string | undefined
61
+
62
+ do {
63
+ const searchParams = new URLSearchParams({
64
+ formTypes: "hubspot",
65
+ limit: "100",
66
+ })
67
+ if (after) searchParams.set("after", after)
68
+
69
+ const response = await fetchHubSpotJson<HubSpotFormsResponse>(
70
+ `/marketing/v3/forms?${searchParams.toString()}`,
71
+ accessToken
72
+ )
73
+
74
+ forms.push(...(response.results ?? []))
75
+ after = response.paging?.next?.after
76
+ } while (after)
77
+
78
+ return forms
79
+ }
80
+
81
+ /**
82
+ * Lists every published HubSpot form in the connected portal as a flat list of
83
+ * options. Each option's `value` is the JSON-encoded selection consumed by
84
+ * `decodeHubSpotFormSelection` and ultimately by `<HubSpotForm />`.
85
+ *
86
+ * Requires a private-app token in `HUBSPOT_ACCESS_TOKEN` with the `forms` and
87
+ * `account-info.security.read` scopes.
88
+ */
89
+ export async function getHubSpotFormOptions(): Promise<{
90
+ forms: HubSpotFormOption[]
91
+ error: string | null
92
+ }> {
93
+ const accessToken = process.env.HUBSPOT_ACCESS_TOKEN
94
+
95
+ if (!accessToken) {
96
+ return { forms: [], error: "Missing HUBSPOT_ACCESS_TOKEN." }
97
+ }
98
+
99
+ try {
100
+ const [accountDetails, forms] = await Promise.all([
101
+ fetchHubSpotJson<HubSpotAccountDetailsResponse>(
102
+ "/account-info/v3/details",
103
+ accessToken
104
+ ),
105
+ fetchHubSpotForms(accessToken),
106
+ ])
107
+
108
+ const portalId = accountDetails.portalId
109
+ if (!portalId) {
110
+ return {
111
+ forms: [],
112
+ error: "HubSpot account details did not include a portal ID.",
113
+ }
114
+ }
115
+
116
+ const region = accountDetails.dataHostingLocation || "na1"
117
+
118
+ const options = forms
119
+ .filter((form) => form.id && form.name && !form.archived)
120
+ .map<HubSpotFormOption>((form) => {
121
+ const selection: HubSpotFormSelection = {
122
+ name: form.name as string,
123
+ formId: form.id as string,
124
+ portalId: String(portalId),
125
+ region,
126
+ }
127
+ return {
128
+ label: getHubSpotFormSelectionLabel(selection),
129
+ value: JSON.stringify(selection),
130
+ renderRawHtml: form.displayOptions?.renderRawHtml ?? null,
131
+ }
132
+ })
133
+ .sort((a, b) => a.label.localeCompare(b.label))
134
+
135
+ return { forms: options, error: null }
136
+ } catch (error) {
137
+ return {
138
+ forms: [],
139
+ error: error instanceof Error ? error.message : "Unable to load forms.",
140
+ }
141
+ }
142
+ }