@woodsportal/hubspot-kit 1.0.42-dev.0 → 1.0.42-dev.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -1
- package/dist/{chunk-WHYTQEZX.js → chunk-4UFWXPQG.js} +2 -2
- package/dist/{chunk-4GOJPOW3.js → chunk-GXYUGXF5.js} +82 -23
- package/dist/chunk-GXYUGXF5.js.map +1 -0
- package/dist/cli.js +61 -26
- package/dist/cli.js.map +1 -1
- package/dist/{exec-VY6BGWF3.js → exec-NS4GKXZS.js} +2 -2
- package/dist/{scaffold-page-lifecycle-EHMPWPPE.js → scaffold-page-lifecycle-53AWWGFR.js} +3 -3
- package/examples/greenfield/module-hybrid-cdn/README.md +2 -1
- package/examples/greenfield/module-hybrid-cdn-auth-full/README.md +2 -1
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/components/starter-login.tsx +2 -6
- package/examples/greenfield/module-hybrid-cdn-auth-full/src/features/auth/sso-callback.ts +6 -0
- package/examples/greenfield/module-hybrid-cdn-auth-sso/README.md +2 -1
- package/examples/greenfield/module-hybrid-cdn-auth-sso/src/features/auth/sso-callback.ts +6 -0
- package/examples/greenfield/module-hybrid-cdn-auth-starter/README.md +2 -1
- package/examples/greenfield/module-hybrid-cdn-auth-starter-sidebar/README.md +2 -1
- package/package.json +3 -2
- package/scripts/build-cdn.mjs +6 -5
- package/scripts/dist-cli-pack-smoke.test.mjs +79 -0
- package/scripts/ensure-wphs-workspace.mjs +7 -2
- package/scripts/resolve-hubspot-cli.mjs +51 -0
- package/scripts/resolve-vite-config.mjs +2 -3
- package/scripts/resolve-vite-spawn.mjs +48 -0
- package/scripts/run-vite-build.mjs +5 -6
- package/scripts/watch-hubspot.mjs +11 -13
- package/templates/module-hybrid-cdn/README.md +2 -1
- package/templates/module-monolith/README.md +2 -1
- package/templates/scaffold/auth/_full/src/features/auth/components/starter-login.tsx +79 -0
- package/templates/scaffold/auth/_shared/src/app.tsx +8 -3
- package/templates/scaffold/auth/_shared/src/features/auth/components/starter-login.tsx +10 -3
- package/templates/scaffold/auth/_shared/src/integrations/woodsportal-sdk.ts +1 -0
- package/templates/scaffold/auth/_sso/src/features/auth/components/starter-login.tsx +10 -3
- package/templates/scaffold/auth/_sso/src/features/auth/sso-callback.ts +6 -0
- package/dist/chunk-4GOJPOW3.js.map +0 -1
- package/dist/ensure-wphs-workspace-5S3GTO6K.js +0 -234
- package/dist/ensure-wphs-workspace-5S3GTO6K.js.map +0 -1
- /package/dist/{chunk-WHYTQEZX.js.map → chunk-4UFWXPQG.js.map} +0 -0
- /package/dist/{exec-VY6BGWF3.js.map → exec-NS4GKXZS.js.map} +0 -0
- /package/dist/{scaffold-page-lifecycle-EHMPWPPE.js.map → scaffold-page-lifecycle-53AWWGFR.js.map} +0 -0
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
runNodeScript,
|
|
6
6
|
runNodeScriptAsync,
|
|
7
7
|
runNodeScriptLongRunning
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-GXYUGXF5.js";
|
|
9
9
|
export {
|
|
10
10
|
runCommand,
|
|
11
11
|
runCommandAsync,
|
|
@@ -13,4 +13,4 @@ export {
|
|
|
13
13
|
runNodeScriptAsync,
|
|
14
14
|
runNodeScriptLongRunning
|
|
15
15
|
};
|
|
16
|
-
//# sourceMappingURL=exec-
|
|
16
|
+
//# sourceMappingURL=exec-NS4GKXZS.js.map
|
|
@@ -8,11 +8,11 @@ import {
|
|
|
8
8
|
resolveProjectNavConfig,
|
|
9
9
|
slugToCamelCase,
|
|
10
10
|
slugToPascalCase
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-4UFWXPQG.js";
|
|
12
12
|
import {
|
|
13
13
|
ExitCode,
|
|
14
14
|
WpHsError
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-GXYUGXF5.js";
|
|
16
16
|
|
|
17
17
|
// src/lib/scaffold-page-lifecycle.ts
|
|
18
18
|
import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
@@ -191,4 +191,4 @@ export {
|
|
|
191
191
|
listScaffoldedPages,
|
|
192
192
|
removeScaffoldedPage
|
|
193
193
|
};
|
|
194
|
-
//# sourceMappingURL=scaffold-page-lifecycle-
|
|
194
|
+
//# sourceMappingURL=scaffold-page-lifecycle-53AWWGFR.js.map
|
|
@@ -6,7 +6,7 @@ import { Form, FormField } from '@/components/ui/Form'
|
|
|
6
6
|
import { Input } from '@/components/ui/Input'
|
|
7
7
|
import { SsoLoginButton } from '@/components/ui/auth/SsoLoginButton'
|
|
8
8
|
import { api } from '@/integrations/woodsportal-sdk'
|
|
9
|
-
import { handleAuthLoginResponse
|
|
9
|
+
import { handleAuthLoginResponse } from '@/features/auth/login-session-bridge'
|
|
10
10
|
import { resolveAuthLoginErrorMessage } from '@/features/auth/resolve-auth-login-error-message'
|
|
11
11
|
import {
|
|
12
12
|
getStoredLoginEmail,
|
|
@@ -28,11 +28,7 @@ export function StarterLogin() {
|
|
|
28
28
|
const response = await login({ username: email.trim(), password })
|
|
29
29
|
const result = await handleAuthLoginResponse(response, router)
|
|
30
30
|
if (!result.success) {
|
|
31
|
-
|
|
32
|
-
toast.error(MFA_REQUIRED_SCAFFOLD_MESSAGE)
|
|
33
|
-
} else {
|
|
34
|
-
toast.error('Login failed — no access token returned.')
|
|
35
|
-
}
|
|
31
|
+
toast.error('Login failed — no access token returned.')
|
|
36
32
|
return
|
|
37
33
|
}
|
|
38
34
|
if (!result.mfaRequired) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Routes } from '@/config/routes'
|
|
2
2
|
import { readHashPathname } from '@/features/auth/auth-route-paths'
|
|
3
3
|
import { normalizeRouterPath } from '@/utils/normalize-router-path'
|
|
4
|
+
import { WP_STORAGE_KEYS } from 'woodsportal-client-sdk'
|
|
4
5
|
|
|
5
6
|
export type SsoCallbackParams = {
|
|
6
7
|
code: string
|
|
@@ -60,6 +61,11 @@ export function clearPreHashQueryParams(): void {
|
|
|
60
61
|
window.history.replaceState(null, '', `${window.location.pathname}${window.location.hash}`)
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
/** Legacy sessionStorage inflight — clear on boot so stale keys never block exchange. */
|
|
65
|
+
export function clearLegacySsoCallbackInFlight(): void {
|
|
66
|
+
sessionStorage.removeItem(`${WP_STORAGE_KEYS.c.sso.cb}:inflight`)
|
|
67
|
+
}
|
|
68
|
+
|
|
63
69
|
export function isSsoCallbackAlreadyProcessed(key: string): boolean {
|
|
64
70
|
return processedKeys.has(key)
|
|
65
71
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Routes } from '@/config/routes'
|
|
2
2
|
import { readHashPathname } from '@/features/auth/auth-route-paths'
|
|
3
3
|
import { normalizeRouterPath } from '@/utils/normalize-router-path'
|
|
4
|
+
import { WP_STORAGE_KEYS } from 'woodsportal-client-sdk'
|
|
4
5
|
|
|
5
6
|
export type SsoCallbackParams = {
|
|
6
7
|
code: string
|
|
@@ -60,6 +61,11 @@ export function clearPreHashQueryParams(): void {
|
|
|
60
61
|
window.history.replaceState(null, '', `${window.location.pathname}${window.location.hash}`)
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
/** Legacy sessionStorage inflight — clear on boot so stale keys never block exchange. */
|
|
65
|
+
export function clearLegacySsoCallbackInFlight(): void {
|
|
66
|
+
sessionStorage.removeItem(`${WP_STORAGE_KEYS.c.sso.cb}:inflight`)
|
|
67
|
+
}
|
|
68
|
+
|
|
63
69
|
export function isSsoCallbackAlreadyProcessed(key: string): boolean {
|
|
64
70
|
return processedKeys.has(key)
|
|
65
71
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@woodsportal/hubspot-kit",
|
|
3
|
-
"version": "1.0.42-dev.
|
|
3
|
+
"version": "1.0.42-dev.2",
|
|
4
4
|
"description": "WoodsCLI — HubSpot CMS dev kit by WoodsPortal. wp CLI, Vite presets, module-config overlay, and theme/module build pipelines.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -53,8 +53,9 @@
|
|
|
53
53
|
"typecheck": "tsc --noEmit",
|
|
54
54
|
"test": "node --import tsx --test 'src/lib/**/*.test.ts' 'src/cli/**/*.test.ts' 'src/schema/**/*.test.ts' 'src/vite/**/*.test.ts' 'scripts/portal-build-id.test.mjs' 'scripts/resolve-consumer-dep.test.mjs' 'scripts/wp-cli-smoke.test.mjs' 'scripts/vite/ensure-dev-kit-shims.test.mjs' 'scripts/vite/consumer-react-aliases.test.mjs' 'scripts/vite/resolve-from-project-root.test.mjs' && vitest run src/dev-module-config/__tests__/normalize-repeater-items.test.ts src/dev-module-config/__tests__/module-config-transform.test.ts src/dev-module-config/__tests__/flatten-visible-fields.test.ts",
|
|
55
55
|
"test:examples": "node examples/smoke/run-matrix.mjs --tier static",
|
|
56
|
+
"test:dist-pack": "node --test scripts/dist-cli-pack-smoke.test.mjs",
|
|
56
57
|
"lint": "eslint src",
|
|
57
|
-
"verify": "npm run typecheck && npm run test && npm run test:examples && npm run build",
|
|
58
|
+
"verify": "npm run typecheck && npm run test && npm run test:examples && npm run build && npm run test:dist-pack",
|
|
58
59
|
"prepublishOnly": "npm run verify"
|
|
59
60
|
},
|
|
60
61
|
"engines": {
|
package/scripts/build-cdn.mjs
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Build CDN vendor + app IIFE chunks into dist/cdn/
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import { resolve, dirname } from 'node:path'
|
|
5
|
+
import { resolve } from 'node:path'
|
|
7
6
|
import { fileURLToPath } from 'node:url'
|
|
7
|
+
|
|
8
8
|
import { findProjectRoot } from './project-root.mjs'
|
|
9
9
|
|
|
10
10
|
import { envWithPortalBuildId } from './resolve-portal-build-id.mjs'
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
portalSubprocessStdio,
|
|
18
18
|
portalViteLogArgs,
|
|
19
19
|
} from './portal-subprocess-stdio.mjs'
|
|
20
|
+
import { spawnViteSync } from './resolve-vite-spawn.mjs'
|
|
20
21
|
|
|
21
22
|
const root = findProjectRoot()
|
|
22
23
|
|
|
@@ -31,9 +32,9 @@ const env = envForPortalBuild(mode, root, {
|
|
|
31
32
|
})
|
|
32
33
|
|
|
33
34
|
function runWithEnv(config, buildMode) {
|
|
34
|
-
const result =
|
|
35
|
-
|
|
36
|
-
['
|
|
35
|
+
const result = spawnViteSync(
|
|
36
|
+
root,
|
|
37
|
+
['build', '--config', config, '--mode', buildMode, ...portalViteLogArgs()],
|
|
37
38
|
{ cwd: root, stdio: portalSubprocessStdio(), env, encoding: 'utf8' },
|
|
38
39
|
)
|
|
39
40
|
exitOnSpawnFailure(result)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { spawnSync } from 'node:child_process'
|
|
3
|
+
import { existsSync, mkdtempSync, rmSync } from 'node:fs'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { join, resolve } from 'node:path'
|
|
6
|
+
import { test } from 'node:test'
|
|
7
|
+
|
|
8
|
+
import { copyFixture, kitRoot } from './cli-smoke-helpers.mjs'
|
|
9
|
+
|
|
10
|
+
const distCli = join(kitRoot, 'dist/cli.js')
|
|
11
|
+
|
|
12
|
+
test('dist cli: doctor scaffolds .wphs without SyntaxError', () => {
|
|
13
|
+
if (!existsSync(distCli)) {
|
|
14
|
+
console.log('skip: dist/cli.js missing — run npm run build first')
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const dir = mkdtempSync(join(tmpdir(), 'wphs-dist-pack-'))
|
|
19
|
+
try {
|
|
20
|
+
copyFixture('greenfield/module-hybrid-cdn', dir)
|
|
21
|
+
const result = spawnSync(
|
|
22
|
+
process.execPath,
|
|
23
|
+
[distCli, '--project', dir, '--ci', '-y', 'doctor'],
|
|
24
|
+
{
|
|
25
|
+
cwd: kitRoot,
|
|
26
|
+
encoding: 'utf8',
|
|
27
|
+
env: { ...process.env, NO_COLOR: '1', WP_SKIP_VERSION_CHECK: '1' },
|
|
28
|
+
},
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const combined = `${result.stdout}\n${result.stderr}`
|
|
32
|
+
assert.doesNotMatch(combined, /Invalid or unexpected token/)
|
|
33
|
+
assert.ok(
|
|
34
|
+
result.status === 0 || result.status === 2,
|
|
35
|
+
`unexpected exit ${result.status}: ${combined}`,
|
|
36
|
+
)
|
|
37
|
+
assert.ok(existsSync(resolve(dir, '.wphs', 'config')), 'expected .wphs/config after doctor')
|
|
38
|
+
} finally {
|
|
39
|
+
rmSync(dir, { recursive: true, force: true })
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('dist cli: init dry-run completes without SyntaxError', () => {
|
|
44
|
+
if (!existsSync(distCli)) {
|
|
45
|
+
console.log('skip: dist/cli.js missing — run npm run build first')
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const targetDir = mkdtempSync(join(tmpdir(), 'wphs-dist-init-'))
|
|
50
|
+
try {
|
|
51
|
+
const result = spawnSync(
|
|
52
|
+
process.execPath,
|
|
53
|
+
[
|
|
54
|
+
distCli,
|
|
55
|
+
'--ci',
|
|
56
|
+
'-y',
|
|
57
|
+
'--dry-run',
|
|
58
|
+
'init',
|
|
59
|
+
'-t',
|
|
60
|
+
'module-hybrid-cdn',
|
|
61
|
+
'-n',
|
|
62
|
+
'dist-init-smoke',
|
|
63
|
+
'--directory',
|
|
64
|
+
targetDir,
|
|
65
|
+
],
|
|
66
|
+
{
|
|
67
|
+
cwd: kitRoot,
|
|
68
|
+
encoding: 'utf8',
|
|
69
|
+
env: { ...process.env, NO_COLOR: '1', WP_SKIP_VERSION_CHECK: '1' },
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
const combined = `${result.stdout}\n${result.stderr}`
|
|
74
|
+
assert.doesNotMatch(combined, /Invalid or unexpected token/)
|
|
75
|
+
assert.equal(result.status, 0, combined)
|
|
76
|
+
} finally {
|
|
77
|
+
rmSync(targetDir, { recursive: true, force: true })
|
|
78
|
+
}
|
|
79
|
+
})
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
/**
|
|
3
2
|
* Scaffold WoodsCLI `.wphs/` workspace + kit symlinks.
|
|
4
3
|
*/
|
|
5
4
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
5
|
+
import { resolve } from 'node:path'
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
6
7
|
|
|
7
8
|
import { findProjectRoot, getKitRoot } from './project-root.mjs'
|
|
8
9
|
import { wphsPaths, wphsWorkspaceSubdirs } from './wphs-paths.mjs'
|
|
@@ -48,7 +49,11 @@ export function ensureWphsWorkspace(projectRoot, kitRoot) {
|
|
|
48
49
|
return paths
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
const invokedDirectly =
|
|
53
|
+
process.argv[1] &&
|
|
54
|
+
resolve(fileURLToPath(import.meta.url)) === resolve(process.argv[1])
|
|
55
|
+
|
|
56
|
+
if (invokedDirectly) {
|
|
52
57
|
const paths = ensureWphsWorkspace()
|
|
53
58
|
console.log(`[wphs] workspace ready at ${paths.workspaceRoot}`)
|
|
54
59
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { platform } from 'node:os'
|
|
3
|
+
import { resolve } from 'node:path'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} [projectRoot]
|
|
7
|
+
* @returns {{ command: string, prefixArgs: string[], shell?: boolean }}
|
|
8
|
+
*/
|
|
9
|
+
export function resolveHubSpotCliSpawn(projectRoot) {
|
|
10
|
+
const roots = []
|
|
11
|
+
if (projectRoot) roots.push(projectRoot)
|
|
12
|
+
if (!roots.includes(process.cwd())) roots.push(process.cwd())
|
|
13
|
+
|
|
14
|
+
for (const root of roots) {
|
|
15
|
+
const binDir = resolve(root, 'node_modules', '.bin')
|
|
16
|
+
if (platform() === 'win32') {
|
|
17
|
+
const cmd = resolve(binDir, 'hs.cmd')
|
|
18
|
+
if (existsSync(cmd)) return { command: cmd, prefixArgs: [], shell: false }
|
|
19
|
+
}
|
|
20
|
+
const shim = resolve(binDir, 'hs')
|
|
21
|
+
if (existsSync(shim)) return { command: shim, prefixArgs: [], shell: false }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (platform() === 'win32') {
|
|
25
|
+
return { command: 'hs', prefixArgs: [], shell: true }
|
|
26
|
+
}
|
|
27
|
+
return { command: 'hs', prefixArgs: [], shell: false }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} projectRoot
|
|
32
|
+
* @param {string | undefined} kitCliPath
|
|
33
|
+
*/
|
|
34
|
+
export function resolveWpBinSpawn(projectRoot, kitCliPath) {
|
|
35
|
+
if (kitCliPath && existsSync(kitCliPath)) {
|
|
36
|
+
return { command: process.execPath, prefixArgs: [kitCliPath], shell: false }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const binDir = resolve(projectRoot, 'node_modules', '.bin')
|
|
40
|
+
if (platform() === 'win32') {
|
|
41
|
+
const cmd = resolve(binDir, 'wp.cmd')
|
|
42
|
+
if (existsSync(cmd)) return { command: cmd, prefixArgs: [], shell: false }
|
|
43
|
+
}
|
|
44
|
+
const shim = resolve(binDir, 'wp')
|
|
45
|
+
if (existsSync(shim)) return { command: shim, prefixArgs: [], shell: false }
|
|
46
|
+
|
|
47
|
+
if (platform() === 'win32') {
|
|
48
|
+
return { command: 'wp', prefixArgs: [], shell: true }
|
|
49
|
+
}
|
|
50
|
+
return { command: 'wp', prefixArgs: [], shell: false }
|
|
51
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Resolve a Vite config path: prefer project root, fall back to kit vite presets.
|
|
3
3
|
*/
|
|
4
4
|
import { existsSync } from 'node:fs'
|
|
5
|
-
import { basename, resolve } from 'node:path'
|
|
5
|
+
import { basename, resolve, isAbsolute } from 'node:path'
|
|
6
6
|
|
|
7
7
|
import { findProjectRoot, getKitRoot } from './project-root.mjs'
|
|
8
8
|
|
|
@@ -11,8 +11,7 @@ import { findProjectRoot, getKitRoot } from './project-root.mjs'
|
|
|
11
11
|
* @param {string} [projectRoot]
|
|
12
12
|
*/
|
|
13
13
|
export function resolveViteConfig(configArg, projectRoot = findProjectRoot()) {
|
|
14
|
-
const
|
|
15
|
-
const localPath = isAbsolute ? configArg : resolve(projectRoot, configArg)
|
|
14
|
+
const localPath = isAbsolute(configArg) ? configArg : resolve(projectRoot, configArg)
|
|
16
15
|
if (existsSync(localPath)) {
|
|
17
16
|
return localPath
|
|
18
17
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { platform } from 'node:os'
|
|
4
|
+
import { resolve } from 'node:path'
|
|
5
|
+
|
|
6
|
+
const VITE_JS_SEGMENTS = ['node_modules', 'vite', 'bin', 'vite.js']
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} projectRoot
|
|
10
|
+
* @returns {{ command: string, prefixArgs: string[], shell?: boolean } | null}
|
|
11
|
+
*/
|
|
12
|
+
export function resolveViteSpawn(projectRoot) {
|
|
13
|
+
const viteJs = resolve(projectRoot, ...VITE_JS_SEGMENTS)
|
|
14
|
+
if (existsSync(viteJs)) {
|
|
15
|
+
return { command: process.execPath, prefixArgs: [viteJs], shell: false }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const binDir = resolve(projectRoot, 'node_modules', '.bin')
|
|
19
|
+
if (platform() === 'win32') {
|
|
20
|
+
const winCmd = resolve(binDir, 'vite.cmd')
|
|
21
|
+
if (existsSync(winCmd)) {
|
|
22
|
+
return { command: winCmd, prefixArgs: [], shell: false }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const posixShim = resolve(binDir, 'vite')
|
|
27
|
+
if (existsSync(posixShim)) {
|
|
28
|
+
return { command: posixShim, prefixArgs: [], shell: false }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} projectRoot
|
|
36
|
+
* @param {string[]} viteArgs
|
|
37
|
+
* @param {import('node:child_process').SpawnSyncOptions} options
|
|
38
|
+
*/
|
|
39
|
+
export function spawnViteSync(projectRoot, viteArgs, options) {
|
|
40
|
+
const spawn = resolveViteSpawn(projectRoot)
|
|
41
|
+
if (!spawn) {
|
|
42
|
+
throw new Error('vite not installed — run npm install or yarn install in the project root')
|
|
43
|
+
}
|
|
44
|
+
return spawnSync(spawn.command, [...spawn.prefixArgs, ...viteArgs], {
|
|
45
|
+
...options,
|
|
46
|
+
shell: spawn.shell ?? false,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Usage: node scripts/run-vite-build.mjs <vite-config.mjs> [--local | --dev | --stg | --prod]
|
|
6
6
|
*/
|
|
7
|
-
import { spawnSync } from 'node:child_process'
|
|
8
|
-
|
|
9
7
|
import { defaultDatadogFlag, envForPortalBuild } from './portal-vite-env.mjs'
|
|
10
8
|
import { resolvePortalViteMode } from './portal-env-mode.mjs'
|
|
11
9
|
import { findProjectRoot } from './project-root.mjs'
|
|
@@ -15,6 +13,7 @@ import {
|
|
|
15
13
|
portalSubprocessStdio,
|
|
16
14
|
portalViteLogArgs,
|
|
17
15
|
} from './portal-subprocess-stdio.mjs'
|
|
16
|
+
import { spawnViteSync } from './resolve-vite-spawn.mjs'
|
|
18
17
|
|
|
19
18
|
const root = findProjectRoot()
|
|
20
19
|
const configArg = process.argv[2]
|
|
@@ -32,10 +31,10 @@ const env = envForPortalBuild(mode, root, {
|
|
|
32
31
|
WP_HS_PROJECT_ROOT: root,
|
|
33
32
|
})
|
|
34
33
|
|
|
35
|
-
const result =
|
|
36
|
-
|
|
37
|
-
['
|
|
38
|
-
{ cwd: root, stdio: portalSubprocessStdio(), env,
|
|
34
|
+
const result = spawnViteSync(
|
|
35
|
+
root,
|
|
36
|
+
['build', '--config', config, '--mode', mode, ...portalViteLogArgs()],
|
|
37
|
+
{ cwd: root, stdio: portalSubprocessStdio(), env, encoding: 'utf8' },
|
|
39
38
|
)
|
|
40
39
|
|
|
41
40
|
exitOnSpawnFailure(result)
|
|
@@ -8,6 +8,7 @@ import { dirname, resolve } from 'node:path'
|
|
|
8
8
|
import { fileURLToPath } from 'node:url'
|
|
9
9
|
|
|
10
10
|
import { moduleOutDir, moduleUploadPath, readWoodsportalConfig, themeUploadPath } from './read-woodsportal-config.mjs'
|
|
11
|
+
import { resolveHubSpotCliSpawn, resolveWpBinSpawn } from './resolve-hubspot-cli.mjs'
|
|
11
12
|
|
|
12
13
|
const kitScriptsDir = dirname(fileURLToPath(import.meta.url))
|
|
13
14
|
const kitRoot = resolve(kitScriptsDir, '..')
|
|
@@ -87,23 +88,15 @@ process.on('SIGTERM', () => shutdown('SIGTERM'))
|
|
|
87
88
|
function runWpBuild() {
|
|
88
89
|
const env = { ...process.env, WP_HS_PROJECT_ROOT: root }
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (existsSync(kitCli)) {
|
|
94
|
-
args = [kitCli, 'build', '--tier', tier, '--quiet']
|
|
95
|
-
} else {
|
|
96
|
-
const localWp = resolve(root, 'node_modules', '.bin', 'wp')
|
|
97
|
-
command = existsSync(localWp) ? localWp : 'wp'
|
|
98
|
-
args = ['build', '--tier', tier, '--quiet']
|
|
99
|
-
}
|
|
91
|
+
const wpSpawn = resolveWpBinSpawn(root, existsSync(kitCli) ? kitCli : undefined)
|
|
92
|
+
const args = [...wpSpawn.prefixArgs, 'build', '--tier', tier, '--quiet']
|
|
100
93
|
|
|
101
94
|
log(`Running wp build --tier ${tier} …`)
|
|
102
|
-
const result = spawnSync(command, args, {
|
|
95
|
+
const result = spawnSync(wpSpawn.command, args, {
|
|
103
96
|
cwd: root,
|
|
104
97
|
stdio: 'inherit',
|
|
105
98
|
env,
|
|
106
|
-
shell:
|
|
99
|
+
shell: wpSpawn.shell ?? false,
|
|
107
100
|
})
|
|
108
101
|
if (result.status !== 0) {
|
|
109
102
|
process.exit(result.status ?? 1)
|
|
@@ -122,7 +115,12 @@ function startCmsWatch() {
|
|
|
122
115
|
const src = isTheme ? theme.srcDir : hubspotModulePath
|
|
123
116
|
const target = isTheme ? theme.uploadPath : hubspotUploadTarget
|
|
124
117
|
log(`Starting hs cms watch ${src} → ${target}`)
|
|
125
|
-
|
|
118
|
+
const hsSpawn = resolveHubSpotCliSpawn(root)
|
|
119
|
+
cmsChild = spawn(hsSpawn.command, [...hsSpawn.prefixArgs, 'cms', 'watch', src, target], {
|
|
120
|
+
cwd: root,
|
|
121
|
+
stdio: 'inherit',
|
|
122
|
+
shell: hsSpawn.shell ?? false,
|
|
123
|
+
})
|
|
126
124
|
cmsChild.on('exit', (code) => {
|
|
127
125
|
if (shuttingDown) return
|
|
128
126
|
process.exit(code ?? 0)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { useRouter } from '@tanstack/react-router'
|
|
3
|
+
import { toast } from 'sonner'
|
|
4
|
+
import { Button } from '@/components/ui/Button'
|
|
5
|
+
import { Form, FormField } from '@/components/ui/Form'
|
|
6
|
+
import { Input } from '@/components/ui/Input'
|
|
7
|
+
import { SsoLoginButton } from '@/components/ui/auth/SsoLoginButton'
|
|
8
|
+
import { api } from '@/integrations/woodsportal-sdk'
|
|
9
|
+
import { handleAuthLoginResponse } from '@/features/auth/login-session-bridge'
|
|
10
|
+
import { resolveAuthLoginErrorMessage } from '@/features/auth/resolve-auth-login-error-message'
|
|
11
|
+
import {
|
|
12
|
+
getStoredLoginEmail,
|
|
13
|
+
setStoredLoginEmail,
|
|
14
|
+
} from '@/features/auth/login-email-storage'
|
|
15
|
+
|
|
16
|
+
export function StarterLogin() {
|
|
17
|
+
const router = useRouter()
|
|
18
|
+
const [email, setEmail] = useState(getStoredLoginEmail() ?? '')
|
|
19
|
+
const [password, setPassword] = useState('')
|
|
20
|
+
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
21
|
+
|
|
22
|
+
const onSubmit = async (event: React.FormEvent) => {
|
|
23
|
+
event.preventDefault()
|
|
24
|
+
setIsSubmitting(true)
|
|
25
|
+
try {
|
|
26
|
+
setStoredLoginEmail(email)
|
|
27
|
+
const { login } = api.auth.login({})
|
|
28
|
+
const response = await login({ username: email.trim(), password })
|
|
29
|
+
const result = await handleAuthLoginResponse(response, router)
|
|
30
|
+
if (!result.success) {
|
|
31
|
+
toast.error('Login failed — no access token returned.')
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
if (!result.mfaRequired) {
|
|
35
|
+
toast.success('Signed in')
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
toast.error(resolveAuthLoginErrorMessage(error))
|
|
39
|
+
} finally {
|
|
40
|
+
setIsSubmitting(false)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="w-full max-w-md rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
|
|
46
|
+
<h1 className="mb-2 text-xl font-semibold text-gray-900">Sign in</h1>
|
|
47
|
+
<p className="mb-6 text-sm text-gray-600">Use your portal email and password or SSO.</p>
|
|
48
|
+
<Form onSubmit={onSubmit}>
|
|
49
|
+
<FormField label="Email">
|
|
50
|
+
<Input
|
|
51
|
+
type="email"
|
|
52
|
+
autoComplete="email"
|
|
53
|
+
required
|
|
54
|
+
value={email}
|
|
55
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
56
|
+
/>
|
|
57
|
+
</FormField>
|
|
58
|
+
<FormField label="Password">
|
|
59
|
+
<Input
|
|
60
|
+
type="password"
|
|
61
|
+
autoComplete="current-password"
|
|
62
|
+
required
|
|
63
|
+
value={password}
|
|
64
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
65
|
+
/>
|
|
66
|
+
</FormField>
|
|
67
|
+
<Button type="submit" className="w-full" disabled={isSubmitting}>
|
|
68
|
+
{isSubmitting ? 'Signing in…' : 'Sign in'}
|
|
69
|
+
</Button>
|
|
70
|
+
</Form>
|
|
71
|
+
<div className="my-4 flex items-center gap-3 text-xs text-gray-500">
|
|
72
|
+
<span className="h-px flex-1 bg-gray-200" />
|
|
73
|
+
or
|
|
74
|
+
<span className="h-px flex-1 bg-gray-200" />
|
|
75
|
+
</div>
|
|
76
|
+
<SsoLoginButton />
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react'
|
|
|
2
2
|
import { RouterProvider } from '@tanstack/react-router'
|
|
3
3
|
import { installCrossTabSessionListener } from 'woodsportal-client-sdk'
|
|
4
4
|
|
|
5
|
-
import { api } from '@/integrations/woodsportal-sdk'
|
|
5
|
+
import { api, isSessionRefreshDeferredError } from '@/integrations/woodsportal-sdk'
|
|
6
6
|
import * as TanStackQueryProvider from '@/integrations/tanstack-query/root-provider'
|
|
7
7
|
import { env } from '@/env'
|
|
8
8
|
import Loader from '@/components/ui/loader/loader'
|
|
@@ -123,7 +123,10 @@ export function App({ router }: AppProps) {
|
|
|
123
123
|
|
|
124
124
|
try {
|
|
125
125
|
await session.refreshSession({ force: true })
|
|
126
|
-
} catch {
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (isSessionRefreshDeferredError(error)) {
|
|
128
|
+
return
|
|
129
|
+
}
|
|
127
130
|
if (!inCmsEditor) {
|
|
128
131
|
await teardownAndRedirectToLogin()
|
|
129
132
|
}
|
|
@@ -147,7 +150,9 @@ export function App({ router }: AppProps) {
|
|
|
147
150
|
}),
|
|
148
151
|
])
|
|
149
152
|
} catch (error) {
|
|
150
|
-
if (
|
|
153
|
+
if (isSessionRefreshDeferredError(error)) {
|
|
154
|
+
// API temporarily unavailable — keep session cookies intact.
|
|
155
|
+
} else if (!inCmsEditor && skipCurrentPublicPath()) {
|
|
151
156
|
await teardownAndRedirectToLogin()
|
|
152
157
|
} else if (error instanceof Error && error.message !== 'bootstrap_timeout') {
|
|
153
158
|
throw error
|