@puredesktop/create-app 1.0.0-beta.1
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/bin/create-puredesktop-app.mjs +164 -0
- package/package.json +21 -0
- package/template/README.md +30 -0
- package/template/gitignore.tpl +5 -0
- package/template/index.html +12 -0
- package/template/package.json +25 -0
- package/template/plugin.json +19 -0
- package/template/src/App.tsx +49 -0
- package/template/src/bridge/platformBridge.ts +31 -0
- package/template/src/components/AppShell.tsx +31 -0
- package/template/src/constants.ts +1 -0
- package/template/src/hooks/useAppBoot.ts +45 -0
- package/template/src/main.tsx +9 -0
- package/template/src/types.ts +5 -0
- package/template/tsconfig.json +15 -0
- package/template/vite-env.d.ts +1 -0
- package/template/vite.config.js +28 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Scaffold an external PureScience Desktop plugin app.
|
|
4
|
+
*
|
|
5
|
+
* Published as @puredesktop/create-app → npm create @puredesktop/app@latest
|
|
6
|
+
*/
|
|
7
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
8
|
+
import { dirname, join, resolve } from 'node:path'
|
|
9
|
+
import { fileURLToPath } from 'node:url'
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
12
|
+
const TEMPLATE_DIR = join(__dirname, '..', 'template')
|
|
13
|
+
const DEFAULT_UI = '@puredesktop/puredesktop-ui-bridge'
|
|
14
|
+
const DEFAULT_UI_VERSION = '1.0.0-beta.1'
|
|
15
|
+
|
|
16
|
+
function usage() {
|
|
17
|
+
console.log(`Usage: npm create @puredesktop/app@latest [dir] [options]
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
--name <title> Display name (default: derived from dir)
|
|
21
|
+
--slug <slug> App slug for settings/bridge (default: dir name)
|
|
22
|
+
--port <number> Dev server port in plugin.json (default: 5300)
|
|
23
|
+
--ui <spec> UI package spec (default: ${DEFAULT_UI}@${DEFAULT_UI_VERSION})
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
npm create @puredesktop/app@latest my-tool
|
|
27
|
+
npm create @puredesktop/app@latest my-tool -- --name "My Tool" --slug mytool --port 5310
|
|
28
|
+
`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function uiDependencySpec(spec) {
|
|
32
|
+
if (spec.startsWith('file:')) {
|
|
33
|
+
return {
|
|
34
|
+
name: '@puredesktop/puredesktop-ui-bridge',
|
|
35
|
+
version: spec,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const at = spec.lastIndexOf('@')
|
|
39
|
+
if (at > 1) {
|
|
40
|
+
return { name: spec.slice(0, at), version: spec.slice(at + 1) }
|
|
41
|
+
}
|
|
42
|
+
return { name: spec, version: DEFAULT_UI_VERSION }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseArgs(argv) {
|
|
46
|
+
const positional = []
|
|
47
|
+
const options = {
|
|
48
|
+
name: '',
|
|
49
|
+
slug: '',
|
|
50
|
+
port: 5300,
|
|
51
|
+
ui: `${DEFAULT_UI}@${DEFAULT_UI_VERSION}`,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
55
|
+
const arg = argv[i]
|
|
56
|
+
if (arg === '--help' || arg === '-h') {
|
|
57
|
+
usage()
|
|
58
|
+
process.exit(0)
|
|
59
|
+
}
|
|
60
|
+
if (arg === '--name') {
|
|
61
|
+
options.name = argv[++i] ?? ''
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
64
|
+
if (arg === '--slug') {
|
|
65
|
+
options.slug = argv[++i] ?? ''
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
if (arg === '--port') {
|
|
69
|
+
options.port = Number(argv[++i])
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
if (arg === '--ui') {
|
|
73
|
+
options.ui = argv[++i] ?? options.ui
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
if (arg.startsWith('-')) {
|
|
77
|
+
console.error(`Unknown option: ${arg}`)
|
|
78
|
+
usage()
|
|
79
|
+
process.exit(1)
|
|
80
|
+
}
|
|
81
|
+
positional.push(arg)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!Number.isInteger(options.port) || options.port <= 0) {
|
|
85
|
+
console.error('--port must be a positive integer')
|
|
86
|
+
process.exit(1)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { targetDir: positional[0] ?? 'puredesktop-app', options }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function slugify(value) {
|
|
93
|
+
return value
|
|
94
|
+
.trim()
|
|
95
|
+
.toLowerCase()
|
|
96
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
97
|
+
.replace(/^-+|-+$/g, '')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function titleCaseFromSlug(slug) {
|
|
101
|
+
return slug
|
|
102
|
+
.split('-')
|
|
103
|
+
.filter(Boolean)
|
|
104
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
105
|
+
.join(' ')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function copyTemplate(srcDir, destDir, vars) {
|
|
109
|
+
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
|
|
110
|
+
const srcPath = join(srcDir, entry.name)
|
|
111
|
+
const destName = entry.name.replace(/\.tpl$/u, '')
|
|
112
|
+
const destPath = join(destDir, destName)
|
|
113
|
+
if (entry.isDirectory()) {
|
|
114
|
+
mkdirSync(destPath, { recursive: true })
|
|
115
|
+
copyTemplate(srcPath, destPath, vars)
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
const raw = readFileSync(srcPath, 'utf8')
|
|
119
|
+
const rendered = raw.replace(/\{\{(\w+)\}\}/g, (_match, key) => vars[key] ?? '')
|
|
120
|
+
writeFileSync(destPath, rendered)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const { targetDir, options } = parseArgs(process.argv.slice(2))
|
|
125
|
+
const uiDep = uiDependencySpec(options.ui)
|
|
126
|
+
const dirName = targetDir
|
|
127
|
+
const slug = options.slug || slugify(dirName) || 'my-app'
|
|
128
|
+
const title = options.name || titleCaseFromSlug(slug) || 'My App'
|
|
129
|
+
const pluginId = slug
|
|
130
|
+
const outDir = resolve(process.cwd(), dirName)
|
|
131
|
+
|
|
132
|
+
if (existsSync(outDir)) {
|
|
133
|
+
console.error(`Target already exists: ${outDir}`)
|
|
134
|
+
process.exit(1)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
mkdirSync(outDir, { recursive: true })
|
|
138
|
+
|
|
139
|
+
const vars = {
|
|
140
|
+
APP_TITLE: title,
|
|
141
|
+
APP_SLUG: slug,
|
|
142
|
+
APP_NAME: slug,
|
|
143
|
+
PLUGIN_ID: pluginId,
|
|
144
|
+
APP_PORT: String(options.port),
|
|
145
|
+
UI_PACKAGE_NAME: uiDep.name,
|
|
146
|
+
UI_PACKAGE_VERSION: uiDep.version,
|
|
147
|
+
PACKAGE_NAME: `@puredesktop/${slug}`,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
copyTemplate(TEMPLATE_DIR, outDir, vars)
|
|
151
|
+
|
|
152
|
+
console.log(`
|
|
153
|
+
Created ${title} at ${outDir}
|
|
154
|
+
|
|
155
|
+
cd ${dirName}
|
|
156
|
+
npm install
|
|
157
|
+
npm run dev
|
|
158
|
+
|
|
159
|
+
Register in PureScience Desktop (File → Register App…) with:
|
|
160
|
+
entrypoint: http://localhost:${options.port}
|
|
161
|
+
permissions: network (+ settings if you use app settings)
|
|
162
|
+
|
|
163
|
+
Build your UI in src/components/AppShell.tsx.
|
|
164
|
+
`)
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@puredesktop/create-app",
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
|
+
"description": "Scaffold a PureScience Desktop plugin app (Vite + bridge SDK)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-app": "./bin/create-puredesktop-app.mjs",
|
|
8
|
+
"create-puredesktop-app": "./bin/create-puredesktop-app.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"template"
|
|
13
|
+
],
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "restricted",
|
|
16
|
+
"registry": "https://registry.npmjs.org"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# {{APP_TITLE}}
|
|
2
|
+
|
|
3
|
+
Vite + React plugin app for **PureScience Desktop**, scaffolded with `@puredesktop/create-app`.
|
|
4
|
+
|
|
5
|
+
## Dev in the shell
|
|
6
|
+
|
|
7
|
+
1. `npm install`
|
|
8
|
+
2. `npm run dev` — serves at `http://localhost:{{APP_PORT}}` (must match `plugin.json`)
|
|
9
|
+
3. In PureScience Desktop: **File → Register App…**
|
|
10
|
+
- Entrypoint URL: `http://localhost:{{APP_PORT}}`
|
|
11
|
+
- Permissions: **Network**, **Settings**
|
|
12
|
+
|
|
13
|
+
## Dev in the browser (optional)
|
|
14
|
+
|
|
15
|
+
Open the Vite URL directly. The app runs in standalone dev mode without the shell bridge.
|
|
16
|
+
|
|
17
|
+
## Build & deploy
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm run build
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Deploy the `dist/` folder to HTTPS, then register that URL in PureScience Desktop.
|
|
24
|
+
|
|
25
|
+
## Project layout
|
|
26
|
+
|
|
27
|
+
- `src/App.tsx` — bridge + boot gates, always wraps `AppFrame`
|
|
28
|
+
- `src/components/AppShell.tsx` — your UI starts here
|
|
29
|
+
- `src/bridge/platformBridge.ts` — only file that calls `bridge.call`
|
|
30
|
+
- `plugin.json` — catalog metadata and dev entrypoint port
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{APP_TITLE}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PACKAGE_NAME}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"{{UI_PACKAGE_NAME}}": "{{UI_PACKAGE_VERSION}}",
|
|
13
|
+
"react": "^19.1.0",
|
|
14
|
+
"react-dom": "^19.1.0",
|
|
15
|
+
"styled-components": "^6.1.18"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@swc/plugin-styled-components": "^12.9.0",
|
|
19
|
+
"@types/react": "^19.1.6",
|
|
20
|
+
"@types/react-dom": "^19.1.5",
|
|
21
|
+
"@vitejs/plugin-react-swc": "^3.10.2",
|
|
22
|
+
"typescript": "^5.8.3",
|
|
23
|
+
"vite": "^6.3.5"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "{{PLUGIN_ID}}",
|
|
4
|
+
"name": "{{APP_TITLE}}",
|
|
5
|
+
"permissions": ["network", "settings"],
|
|
6
|
+
"entrypoint": {
|
|
7
|
+
"kind": "dev-url",
|
|
8
|
+
"url": "http://localhost:{{APP_PORT}}"
|
|
9
|
+
},
|
|
10
|
+
"app": {
|
|
11
|
+
"id": "plugin.{{APP_SLUG}}",
|
|
12
|
+
"slug": "{{APP_SLUG}}",
|
|
13
|
+
"name": "{{APP_TITLE}}",
|
|
14
|
+
"navigationLabel": "{{APP_TITLE}}",
|
|
15
|
+
"productName": "pure.{{APP_SLUG}}",
|
|
16
|
+
"kind": "{{APP_SLUG}}",
|
|
17
|
+
"description": "{{APP_TITLE}} — built with PureScience Desktop."
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { AppFrame } from '@puredesktop/puredesktop-ui-bridge/components/common/containers/AppFrame'
|
|
2
|
+
import { EmptyState } from '@puredesktop/puredesktop-ui-bridge/components/common/feedback/EmptyState'
|
|
3
|
+
import { usePlatformBridge } from '@puredesktop/puredesktop-ui-bridge/bridge/react/usePlatformBridge'
|
|
4
|
+
import { AppShell } from './components/AppShell'
|
|
5
|
+
import { isStandaloneDevMode } from './bridge/platformBridge'
|
|
6
|
+
import { useAppBoot } from './hooks/useAppBoot'
|
|
7
|
+
|
|
8
|
+
export function App(): React.ReactElement {
|
|
9
|
+
const { error: bridgeError, ready } = usePlatformBridge()
|
|
10
|
+
const standaloneDev = isStandaloneDevMode()
|
|
11
|
+
const bootReady = ready || standaloneDev
|
|
12
|
+
const { boot, bootError, booting } = useAppBoot(bootReady)
|
|
13
|
+
|
|
14
|
+
if (bridgeError && !standaloneDev) {
|
|
15
|
+
return (
|
|
16
|
+
<AppFrame>
|
|
17
|
+
<EmptyState
|
|
18
|
+
tone="error"
|
|
19
|
+
title="Bridge unavailable"
|
|
20
|
+
message={bridgeError.message}
|
|
21
|
+
/>
|
|
22
|
+
</AppFrame>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!bootReady || !boot) {
|
|
27
|
+
return (
|
|
28
|
+
<AppFrame>
|
|
29
|
+
<EmptyState
|
|
30
|
+
tone={bootError ? 'error' : 'neutral'}
|
|
31
|
+
title={bootError ? 'Boot failed' : '{{APP_TITLE}}'}
|
|
32
|
+
message={
|
|
33
|
+
bootError
|
|
34
|
+
? bootError.message
|
|
35
|
+
: booting
|
|
36
|
+
? 'Loading…'
|
|
37
|
+
: 'Waiting for PureScience shell bridge…'
|
|
38
|
+
}
|
|
39
|
+
/>
|
|
40
|
+
</AppFrame>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<AppFrame>
|
|
46
|
+
<AppShell settings={boot.settings} />
|
|
47
|
+
</AppFrame>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { bridge } from '@puredesktop/puredesktop-ui-bridge/bridge/client'
|
|
2
|
+
import { PLATFORM_BRIDGE_METHODS } from '@puredesktop/puredesktop-ui-bridge/bridge/methods'
|
|
3
|
+
import { APP_SLUG } from '../constants'
|
|
4
|
+
import type { AppSettings } from '../types'
|
|
5
|
+
|
|
6
|
+
export { bridge }
|
|
7
|
+
|
|
8
|
+
const STANDALONE_SETTINGS_KEY = 'puredesktop:{{APP_SLUG}}:settings'
|
|
9
|
+
|
|
10
|
+
export function isStandaloneDevMode(): boolean {
|
|
11
|
+
return import.meta.env.DEV && window.parent === window
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function readStandaloneSettings(): AppSettings {
|
|
15
|
+
try {
|
|
16
|
+
const raw = window.localStorage.getItem(STANDALONE_SETTINGS_KEY)
|
|
17
|
+
return raw ? (JSON.parse(raw) as AppSettings) : {}
|
|
18
|
+
} catch {
|
|
19
|
+
return {}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function fetchAppSettings(): Promise<AppSettings> {
|
|
24
|
+
if (isStandaloneDevMode()) {
|
|
25
|
+
return readStandaloneSettings()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return bridge.call<AppSettings>(PLATFORM_BRIDGE_METHODS.SETTINGS_APP_GET, [
|
|
29
|
+
APP_SLUG,
|
|
30
|
+
])
|
|
31
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import styled from 'styled-components'
|
|
2
|
+
import { Heading } from '@puredesktop/puredesktop-ui-bridge/components/common/typography/Heading'
|
|
3
|
+
import { Text } from '@puredesktop/puredesktop-ui-bridge/components/common/typography/Text'
|
|
4
|
+
import type { AppSettings } from '../types'
|
|
5
|
+
|
|
6
|
+
const Root = styled.div`
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
gap: 12px;
|
|
10
|
+
padding: 32px;
|
|
11
|
+
max-width: 720px;
|
|
12
|
+
`
|
|
13
|
+
|
|
14
|
+
export interface AppShellProps {
|
|
15
|
+
settings: AppSettings
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function AppShell({ settings }: AppShellProps): React.ReactElement {
|
|
19
|
+
return (
|
|
20
|
+
<Root>
|
|
21
|
+
<Heading level={1}>{{APP_TITLE}}</Heading>
|
|
22
|
+
<Text>
|
|
23
|
+
Start building in <code>src/components/AppShell.tsx</code>. Bridge boot
|
|
24
|
+
is wired — AppFrame, settings load, and standalone browser dev all work.
|
|
25
|
+
</Text>
|
|
26
|
+
{Object.keys(settings).length > 0 ? (
|
|
27
|
+
<Text meta>Loaded {Object.keys(settings).length} setting key(s).</Text>
|
|
28
|
+
) : null}
|
|
29
|
+
</Root>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const APP_SLUG = '{{APP_SLUG}}'
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { fetchAppSettings } from '../bridge/platformBridge'
|
|
3
|
+
import type { AppBootState } from '../types'
|
|
4
|
+
|
|
5
|
+
interface UseAppBootResult {
|
|
6
|
+
boot: AppBootState | null
|
|
7
|
+
bootError: Error | null
|
|
8
|
+
booting: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useAppBoot(ready: boolean): UseAppBootResult {
|
|
12
|
+
const [boot, setBoot] = useState<AppBootState | null>(null)
|
|
13
|
+
const [bootError, setBootError] = useState<Error | null>(null)
|
|
14
|
+
const [booting, setBooting] = useState(false)
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!ready) return
|
|
18
|
+
|
|
19
|
+
let cancelled = false
|
|
20
|
+
|
|
21
|
+
async function load() {
|
|
22
|
+
setBooting(true)
|
|
23
|
+
setBootError(null)
|
|
24
|
+
try {
|
|
25
|
+
const settings = await fetchAppSettings()
|
|
26
|
+
if (cancelled) return
|
|
27
|
+
setBoot({ settings })
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (cancelled) return
|
|
30
|
+
setBoot(null)
|
|
31
|
+
setBootError(error instanceof Error ? error : new Error(String(error)))
|
|
32
|
+
} finally {
|
|
33
|
+
if (!cancelled) setBooting(false)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
void load()
|
|
38
|
+
|
|
39
|
+
return () => {
|
|
40
|
+
cancelled = true
|
|
41
|
+
}
|
|
42
|
+
}, [ready])
|
|
43
|
+
|
|
44
|
+
return { boot, bootError, booting }
|
|
45
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "Bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"isolatedModules": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*.ts", "src/**/*.tsx", "vite-env.d.ts"]
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import react from '@vitejs/plugin-react-swc'
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
import { dirname, join } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { defineConfig } from 'vite'
|
|
6
|
+
|
|
7
|
+
/** Keep Vite port in sync with plugin.json entrypoint.url. */
|
|
8
|
+
function devServerFromManifest(metaUrl) {
|
|
9
|
+
const appDir = dirname(fileURLToPath(metaUrl))
|
|
10
|
+
const manifest = JSON.parse(readFileSync(join(appDir, 'plugin.json'), 'utf8'))
|
|
11
|
+
const url = new URL(manifest.entrypoint.url)
|
|
12
|
+
return {
|
|
13
|
+
host: url.hostname,
|
|
14
|
+
port: Number(url.port),
|
|
15
|
+
strictPort: true,
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default defineConfig({
|
|
20
|
+
plugins: [
|
|
21
|
+
react({
|
|
22
|
+
plugins: [
|
|
23
|
+
['@swc/plugin-styled-components', { displayName: true, fileName: true }],
|
|
24
|
+
],
|
|
25
|
+
}),
|
|
26
|
+
],
|
|
27
|
+
server: devServerFromManifest(import.meta.url),
|
|
28
|
+
})
|