@tothalex/nulljs 0.0.48 → 0.0.54

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 (42) hide show
  1. package/package.json +25 -32
  2. package/scripts/install-server.js +0 -199
  3. package/src/commands/api.ts +0 -16
  4. package/src/commands/auth.ts +0 -54
  5. package/src/commands/create.ts +0 -43
  6. package/src/commands/deploy.ts +0 -160
  7. package/src/commands/dev/function/index.ts +0 -221
  8. package/src/commands/dev/function/utils.ts +0 -99
  9. package/src/commands/dev/index.tsx +0 -126
  10. package/src/commands/dev/logging-manager.ts +0 -87
  11. package/src/commands/dev/server/index.ts +0 -48
  12. package/src/commands/dev/server/utils.ts +0 -37
  13. package/src/commands/dev/ui/components/scroll-area.tsx +0 -141
  14. package/src/commands/dev/ui/components/tab-bar.tsx +0 -67
  15. package/src/commands/dev/ui/index.tsx +0 -71
  16. package/src/commands/dev/ui/logging-context.tsx +0 -76
  17. package/src/commands/dev/ui/tabs/functions-tab.tsx +0 -35
  18. package/src/commands/dev/ui/tabs/server-tab.tsx +0 -36
  19. package/src/commands/dev/ui/tabs/vite-tab.tsx +0 -35
  20. package/src/commands/dev/ui/use-logging.tsx +0 -34
  21. package/src/commands/dev/vite/index.ts +0 -54
  22. package/src/commands/dev/vite/utils.ts +0 -71
  23. package/src/commands/host.ts +0 -339
  24. package/src/commands/index.ts +0 -8
  25. package/src/commands/profile.ts +0 -189
  26. package/src/commands/secret.ts +0 -79
  27. package/src/index.ts +0 -346
  28. package/src/lib/api.ts +0 -189
  29. package/src/lib/bundle/external.ts +0 -23
  30. package/src/lib/bundle/function/index.ts +0 -46
  31. package/src/lib/bundle/index.ts +0 -2
  32. package/src/lib/bundle/react/index.ts +0 -2
  33. package/src/lib/bundle/react/spa.ts +0 -77
  34. package/src/lib/bundle/react/ssr/client.ts +0 -93
  35. package/src/lib/bundle/react/ssr/config.ts +0 -77
  36. package/src/lib/bundle/react/ssr/index.ts +0 -4
  37. package/src/lib/bundle/react/ssr/props.ts +0 -71
  38. package/src/lib/bundle/react/ssr/server.ts +0 -83
  39. package/src/lib/bundle/types.ts +0 -4
  40. package/src/lib/config.ts +0 -347
  41. package/src/lib/deployment.ts +0 -244
  42. package/src/lib/update-server.ts +0 -262
package/src/index.ts DELETED
@@ -1,346 +0,0 @@
1
- #! /usr/bin/env bun
2
-
3
- import yargs from 'yargs'
4
- import { hideBin } from 'yargs/helpers'
5
-
6
- import {
7
- deploy,
8
- auth,
9
- createSecret,
10
- listSecretKeys,
11
- setApiUrl,
12
- create,
13
- createSecretsFromFile,
14
- dev,
15
- host,
16
- unhost,
17
- listProfilesCommand,
18
- createProfile,
19
- useProfile,
20
- showProfile,
21
- removeProfile,
22
- copyProfile
23
- } from './commands'
24
- import chalk from 'chalk'
25
- import { getActiveProfile, listProfiles, profileExists, loadProfile } from './lib/config'
26
-
27
- const showWelcome = () => {
28
- console.log(chalk.bold.blue('\nšŸš€ NullJS CLI'))
29
- console.log(chalk.gray('─'.repeat(40)))
30
-
31
- // Show current profile
32
- const activeProfile = getActiveProfile()
33
- if (activeProfile && profileExists(activeProfile)) {
34
- const config = loadProfile(activeProfile)
35
- console.log(chalk.bold(`\nšŸ“‹ Current Profile: ${chalk.green(activeProfile)}`))
36
-
37
- if (config.api) {
38
- console.log(` ${chalk.blue('API URL:')} ${config.api}`)
39
- } else {
40
- console.log(` ${chalk.blue('API URL:')} ${chalk.red('(not configured)')}`)
41
- }
42
-
43
- if (config.key?.public && config.key?.private) {
44
- console.log(` ${chalk.blue('Authentication:')} ${chalk.green('āœ“ configured')}`)
45
- } else {
46
- console.log(` ${chalk.blue('Authentication:')} ${chalk.red('āœ— not configured')}`)
47
- }
48
- } else {
49
- console.log(chalk.yellow('\nāš ļø No active profile selected'))
50
- const profiles = listProfiles()
51
- if (profiles.length > 0) {
52
- console.log(chalk.gray(' Available profiles:'))
53
- profiles.forEach((profile) => console.log(chalk.gray(` - ${profile}`)))
54
- console.log(chalk.gray(' Switch to a profile: nulljs profile use <name>'))
55
- } else {
56
- console.log(chalk.gray(' Create your first profile: nulljs profile create <name>'))
57
- }
58
- }
59
-
60
- // Show available commands
61
- console.log(chalk.bold('\nšŸ“š Available Commands:'))
62
- console.log(chalk.blue(' nulljs create <name>') + chalk.gray(' Create a new project'))
63
- console.log(chalk.blue(' nulljs deploy [path]') + chalk.gray(' Deploy your project'))
64
- console.log(chalk.blue(' nulljs dev [path]') + chalk.gray(' Start development mode'))
65
- console.log(
66
- chalk.blue(' nulljs auth') + chalk.gray(' Generate authentication keys')
67
- )
68
- console.log(chalk.blue(' nulljs config set-api <url>') + chalk.gray(' Set API URL'))
69
- console.log(chalk.blue(' nulljs secret <action>') + chalk.gray(' Manage secrets'))
70
- console.log(chalk.blue(' nulljs profile <action>') + chalk.gray(' Manage profiles'))
71
- console.log(
72
- chalk.blue(' nulljs host [cloud-path]') + chalk.gray(' Set up production hosting')
73
- )
74
- console.log(
75
- chalk.blue(' nulljs host --update') + chalk.gray(' Update server binary and restart service')
76
- )
77
- console.log(chalk.blue(' nulljs unhost') + chalk.gray(' Remove production hosting'))
78
-
79
- console.log(chalk.gray('\nšŸ’” Quick Start:'))
80
- if (!activeProfile) {
81
- console.log(chalk.gray(' 1. nulljs profile create my-profile'))
82
- console.log(chalk.gray(' 2. nulljs auth'))
83
- console.log(chalk.gray(' 3. nulljs config set-api <your-api-url>'))
84
- console.log(chalk.gray(' 4. nulljs create my-project'))
85
- } else {
86
- console.log(chalk.gray(' • nulljs create my-project (create a new project)'))
87
- console.log(chalk.gray(' • nulljs dev (start development)'))
88
- console.log(chalk.gray(' • nulljs deploy (deploy to production)'))
89
- }
90
-
91
- console.log(chalk.gray('\n Run "nulljs --help" for detailed usage\n'))
92
- }
93
-
94
- const parser = yargs(hideBin(process.argv))
95
- .scriptName('nulljs')
96
- .usage('Usage: $0 <command> [options]')
97
- .command(
98
- 'create <name>',
99
- 'Create a nulljs project with <name>',
100
- (yargs) =>
101
- yargs.positional('name', {
102
- description: 'Project name',
103
- type: 'string',
104
- demandOption: true
105
- }),
106
- (argv) => create(argv.name)
107
- )
108
- .command(
109
- 'deploy [path]',
110
- 'Deploy a project to the platform',
111
- (yargs) =>
112
- yargs.positional('path', {
113
- description:
114
- 'Path to the project directory or file (optional, defaults to current directory)',
115
- type: 'string',
116
- demandOption: false
117
- }),
118
- (argv) => deploy(argv.path)
119
- )
120
- .command(
121
- 'dev [path]',
122
- 'Start dev mode with Vite server and function watching',
123
- (yargs) =>
124
- yargs.positional('path', {
125
- description: 'Path to the src directory (defaults to ./src)',
126
- type: 'string',
127
- default: './src'
128
- }),
129
- (argv) => dev(argv.path)
130
- )
131
- .command(
132
- 'host [cloud-path]',
133
- 'Set up production hosting with systemd service (Linux only)',
134
- (yargs) =>
135
- yargs
136
- .positional('cloud-path', {
137
- description: 'Path to cloud data directory (defaults to ~/.nulljs)',
138
- type: 'string',
139
- demandOption: false
140
- })
141
- .option('update', {
142
- description: 'Update the server binary to the latest version and restart the service',
143
- type: 'boolean',
144
- default: false
145
- }),
146
- (argv) => host(argv['cloud-path'], { update: argv.update })
147
- )
148
- .command(
149
- 'unhost',
150
- 'Remove production hosting setup (Linux only)',
151
- (yargs) =>
152
- yargs.option('keep-data', {
153
- description: 'Keep the cloud data directory',
154
- type: 'boolean',
155
- default: false
156
- }),
157
- (argv) => unhost({ keepData: argv['keep-data'] })
158
- )
159
- .command(
160
- 'auth',
161
- 'Generate or regenerate authentication keys',
162
- (yargs) =>
163
- yargs.option('profile', {
164
- description: 'Save keys to a specific profile',
165
- type: 'string',
166
- demandOption: false
167
- }),
168
- (argv) => auth(argv.profile)
169
- )
170
- .command(
171
- 'config',
172
- 'Configuration management',
173
- (yargs) =>
174
- yargs
175
- .command(
176
- 'set-api <url>',
177
- 'Set the API URL for the platform',
178
- (yargs) =>
179
- yargs
180
- .positional('url', {
181
- description: 'API URL to save in configuration',
182
- type: 'string',
183
- demandOption: true
184
- })
185
- .option('profile', {
186
- description: 'Save to a specific profile',
187
- type: 'string',
188
- demandOption: false
189
- }),
190
- (argv) => setApiUrl(argv.url, argv.profile)
191
- )
192
- .demandCommand(1, 'You need to specify a config action'),
193
- () => {}
194
- )
195
- .command(
196
- 'secret <action>',
197
- 'Secret management operations',
198
- (yargs) =>
199
- yargs
200
- .command(
201
- 'create',
202
- 'Create a new secret',
203
- (yargs) =>
204
- yargs
205
- .option('key', {
206
- description: 'Key name for the secret',
207
- type: 'string',
208
- demandOption: false
209
- })
210
- .option('value', {
211
- description: 'Value for the secret',
212
- type: 'string',
213
- demandOption: false
214
- })
215
- .option('file', {
216
- description: 'Path to file containing the secret value',
217
- type: 'string',
218
- demandOption: false
219
- })
220
- .check((argv) => {
221
- // Either file is provided OR both key and value are provided
222
- if (argv.file && (argv.key || argv.value)) {
223
- throw new Error('Cannot use --file with --key or --value options')
224
- }
225
- if (!argv.file && (!argv.key || !argv.value)) {
226
- throw new Error('Must provide either --file OR both --key and --value')
227
- }
228
- return true
229
- }),
230
- (argv) => {
231
- if (argv.file) {
232
- createSecretsFromFile({
233
- filePath: argv.file
234
- })
235
- } else {
236
- createSecret({ key: argv.key!, value: argv.value! })
237
- }
238
- }
239
- )
240
- .command('list', 'List all available secret keys', {}, () => listSecretKeys())
241
- .demandCommand(1, 'You need to specify a secret action'),
242
- () => {}
243
- )
244
- .command(
245
- 'profile <action>',
246
- 'Profile management operations',
247
- (yargs) =>
248
- yargs
249
- .command('list', 'List all available profiles', {}, () => listProfilesCommand())
250
- .command(
251
- 'create <name>',
252
- 'Create a new profile',
253
- (yargs) =>
254
- yargs.positional('name', {
255
- description: 'Profile name',
256
- type: 'string',
257
- demandOption: true
258
- }),
259
- (argv) => createProfile(argv.name)
260
- )
261
- .command(
262
- 'use <name>',
263
- 'Switch to a profile',
264
- (yargs) =>
265
- yargs.positional('name', {
266
- description: 'Profile name to switch to',
267
- type: 'string',
268
- demandOption: true
269
- }),
270
- (argv) => useProfile(argv.name)
271
- )
272
- .command(
273
- 'show [name]',
274
- 'Show profile configuration',
275
- (yargs) =>
276
- yargs.positional('name', {
277
- description: 'Profile name (defaults to active profile)',
278
- type: 'string',
279
- demandOption: false
280
- }),
281
- (argv) => showProfile(argv.name)
282
- )
283
- .command(
284
- 'remove <name>',
285
- 'Remove a profile',
286
- (yargs) =>
287
- yargs.positional('name', {
288
- description: 'Profile name to remove',
289
- type: 'string',
290
- demandOption: true
291
- }),
292
- (argv) => removeProfile(argv.name)
293
- )
294
- .command(
295
- 'copy <from> <to>',
296
- 'Copy a profile to a new profile',
297
- (yargs) =>
298
- yargs
299
- .positional('from', {
300
- description: 'Source profile name',
301
- type: 'string',
302
- demandOption: true
303
- })
304
- .positional('to', {
305
- description: 'Destination profile name',
306
- type: 'string',
307
- demandOption: true
308
- }),
309
- (argv) => copyProfile(argv.from, argv.to)
310
- )
311
- .demandCommand(1, 'You need to specify a profile action'),
312
- () => {}
313
- )
314
- .help('h')
315
- .alias('h', 'help')
316
- .version('0.0.3')
317
- .alias('v', 'version')
318
- .example('$0 create my-project', 'Create project called my-project')
319
- .example('$0 deploy ./my-project', 'Deploy a project from the current directory')
320
- .example('$0 dev', 'Start dev mode for current project (./src)')
321
- .example('$0 dev ./src', 'Start dev mode for specific src directory')
322
- .example('$0 host', 'Set up production hosting with default cloud path (~/.nulljs)')
323
- .example('$0 host /var/nulljs', 'Set up production hosting with custom cloud path')
324
- .example('$0 host --update', 'Update server binary to latest version and restart service')
325
- .example('$0 unhost', 'Remove production hosting (removes service and data)')
326
- .example('$0 unhost --keep-data', 'Remove hosting but keep cloud data')
327
- .example('$0 auth', 'Generate authentication keys')
328
- .example('$0 config set-api https://api.example.com', 'Set the API URL')
329
- .example('$0 secret create --key MY_KEY --value "secret value"', 'Create a new secret')
330
- .example('$0 secret create --file secrets.json', 'Create secrets from a file')
331
- .example('$0 profile list', 'List all available profiles')
332
- .example('$0 profile create development', 'Create a new profile called development')
333
- .example('$0 profile use production', 'Switch to the production profile')
334
- .example('$0 profile show', 'Show the current active profile configuration')
335
- .example('$0 profile copy development staging', 'Copy development profile to staging')
336
- .epilogue('For more information, visit: https://your-docs-url.com')
337
-
338
- // Parse arguments and show welcome screen if no command is provided
339
- async function main() {
340
- const argv = await parser.parse()
341
- if (argv._.length === 0) {
342
- showWelcome()
343
- }
344
- }
345
-
346
- main().catch(console.error)
package/src/lib/api.ts DELETED
@@ -1,189 +0,0 @@
1
- import { lookup } from 'mime-types'
2
- import type { Deployment } from './deployment'
3
- import chalk from 'chalk'
4
- import { API, loadPrivateKey } from './config'
5
-
6
- const formDataToString = async (formData: FormData): Promise<string> => {
7
- const entries: string[] = []
8
-
9
- const sortedEntries: [string, FormDataEntryValue][] = Array.from(formData.entries()).sort(
10
- ([a], [b]) => a.localeCompare(b)
11
- )
12
-
13
- for (const [key, value] of sortedEntries) {
14
- if (value instanceof Blob) {
15
- entries.push(`${key}:${value.size}:${value.type}`)
16
- }
17
- }
18
-
19
- return entries.join('|')
20
- }
21
-
22
- export const createDeployment = async (deployment: Deployment, logger: any = console) => {
23
- const form = new FormData()
24
-
25
- if (deployment.type === 'react') {
26
- for (const asset of deployment.assets) {
27
- const mime = lookup(asset.fileName)
28
-
29
- if (!mime) {
30
- throw new Error(chalk.yellow(`Couldn't get the mime type for ${asset.fileName}`))
31
- }
32
-
33
- const buffer = Buffer.from(asset.code)
34
- const blob = new Blob([buffer], { type: mime })
35
-
36
- form.append(asset.fileName, blob)
37
- }
38
-
39
- logger.log(chalk.yellow('Deploying ') + chalk.bgYellow.black(deployment.name))
40
- deployment.assets.forEach((asset) => {
41
- logger.log(`-> ${chalk.blue(asset.fileName)}`)
42
- })
43
-
44
- const privateKey = await loadPrivateKey()
45
-
46
- const sign = await crypto.subtle.sign(
47
- 'Ed25519',
48
- privateKey,
49
- Buffer.from(await formDataToString(form))
50
- )
51
-
52
- const signature = btoa(String.fromCharCode(...new Uint8Array(sign)))
53
-
54
- const response = await fetch(`${API}/deployment/react`, {
55
- headers: {
56
- Authorization: signature
57
- },
58
- method: 'POST',
59
- body: form
60
- })
61
-
62
- if (response.status !== 200) {
63
- throw new Error(
64
- `${chalk.red('Deployment failed')} ${chalk.bgRed.black(response.status)}: ${chalk.red(await response.text())}`
65
- )
66
- }
67
-
68
- logger.log(chalk.green('Deployed ') + chalk.bgGreen.black(deployment.name))
69
- }
70
-
71
- if (deployment.type === 'function') {
72
- const handler = deployment.assets.find((asset) => asset.fileName === 'handler.js')
73
-
74
- if (!handler) {
75
- throw new Error(chalk.yellow(`Handler not found for ${deployment.name}`))
76
- }
77
-
78
- const mime = lookup(handler.fileName)
79
-
80
- if (!mime) {
81
- throw new Error(chalk.yellow(`Couldn't get the mime type for ${handler.fileName}`))
82
- }
83
-
84
- const buffer = Buffer.from(handler.code)
85
- const blob = new Blob([buffer], { type: mime })
86
-
87
- form.append(handler.fileName, blob)
88
-
89
- logger.log(chalk.yellow('Deploying ') + chalk.bgYellow.black(deployment.name))
90
- logger.log(`-> ${chalk.blue(handler.fileName)}`)
91
-
92
- const privateKey = await loadPrivateKey()
93
-
94
- const sign = await crypto.subtle.sign(
95
- 'Ed25519',
96
- privateKey,
97
- Buffer.from(await formDataToString(form))
98
- )
99
-
100
- const signature = btoa(String.fromCharCode(...new Uint8Array(sign)))
101
-
102
- const response = await fetch(`${API}/deployment`, {
103
- headers: {
104
- authorization: signature
105
- },
106
- method: 'POST',
107
- body: form
108
- })
109
-
110
- if (response.status !== 200) {
111
- throw new Error(
112
- `${chalk.red('Deployment failed')} ${chalk.bgRed.black(response.status)}: ${chalk.red(await response.text())}`
113
- )
114
- }
115
-
116
- logger.log(chalk.green('Deployed ') + chalk.bgGreen.black(deployment.name))
117
- }
118
- }
119
-
120
- const createSignatureHeader = async (props: { method: string; url: string; body?: string }) => {
121
- const timestamp = new Date().toISOString()
122
- const privateKey = await loadPrivateKey()
123
-
124
- const raw = `${props.method}-${props.url}-${timestamp}${props.body ? '-' + props.body : ''}`
125
-
126
- const sign = await crypto.subtle.sign('Ed25519', privateKey, Buffer.from(raw))
127
- const signature = btoa(String.fromCharCode(...new Uint8Array(sign)))
128
-
129
- const header: { authorization: string; 'x-time': string; 'x-body'?: string } = {
130
- authorization: signature,
131
- 'x-time': timestamp
132
- }
133
-
134
- if (props.body) {
135
- header['x-body'] = btoa(props.body)
136
- }
137
-
138
- return header
139
- }
140
-
141
- export const fetchSecretKeys = async (): Promise<string[]> => {
142
- const path = `${API}/secrets`
143
-
144
- const headers = await createSignatureHeader({
145
- method: 'GET',
146
- url: path
147
- })
148
-
149
- const response = await fetch(path, {
150
- headers,
151
- method: 'GET'
152
- })
153
-
154
- if (response.status !== 200) {
155
- console.log(chalk.red('Request failed'))
156
- console.log(`${chalk.bgRed.black(response.status)}: ${chalk.red(await response.text())}`)
157
- return []
158
- }
159
-
160
- const data = await response.json()
161
-
162
- return data
163
- }
164
-
165
- export const postSecret = async (props: { key: string; value: string }) => {
166
- const path = `${API}/secrets`
167
- const body = JSON.stringify(props)
168
-
169
- const headers = await createSignatureHeader({
170
- method: 'POST',
171
- url: path,
172
- body
173
- })
174
-
175
- const response = await fetch(path, {
176
- method: 'POST',
177
- headers: {
178
- 'Content-Type': 'application/json',
179
- ...headers
180
- },
181
- body
182
- })
183
-
184
- if (response.status !== 200) {
185
- throw new Error(
186
- `${chalk.red('Request failed')} ${chalk.bgRed.black(response.status)}: ${chalk.red(await response.text())}`
187
- )
188
- }
189
- }
@@ -1,23 +0,0 @@
1
- export const external = [
2
- 'cloud/cache',
3
- 'cloud/event',
4
- 'cloud',
5
- 'cloud/postgres',
6
- 'cloud/secret',
7
- 'cloud/uuid',
8
- 'assert',
9
- 'buffer',
10
- 'crypto',
11
- 'dns',
12
- 'events',
13
- 'net',
14
- 'os',
15
- 'process',
16
- 'stream/web',
17
- 'string_decoder',
18
- 'timers',
19
- 'tty',
20
- 'url',
21
- 'util',
22
- 'zlib'
23
- ]
@@ -1,46 +0,0 @@
1
- import type { InlineConfig, Plugin, UserConfig } from 'vite'
2
- import { external } from '../external'
3
- import type { PluginOptions } from '../types'
4
-
5
- const jsFunction = ({ filePath }: PluginOptions): Plugin => {
6
- return {
7
- name: 'nulljs-function-plugin',
8
- apply: 'build',
9
- config: async (config: UserConfig, { command }) => {
10
- if (command !== 'build') {
11
- return config
12
- }
13
-
14
- return {
15
- build: {
16
- rollupOptions: {
17
- input: {
18
- handler: filePath
19
- },
20
- external,
21
- output: {
22
- // preserveModules: false,
23
- entryFileNames: '[name].js'
24
- },
25
- preserveEntrySignatures: 'strict'
26
- }
27
- }
28
- }
29
- }
30
- }
31
- }
32
-
33
- export const functionConfig = (filePath: string): InlineConfig => {
34
- return {
35
- logLevel: 'error',
36
- plugins: [
37
- jsFunction({
38
- filePath
39
- })
40
- ],
41
- build: {
42
- outDir: '/tmp',
43
- minify: false
44
- }
45
- }
46
- }
@@ -1,2 +0,0 @@
1
- export * from './react'
2
- export * from './function'
@@ -1,2 +0,0 @@
1
- export * from './ssr'
2
- export * from './spa'
@@ -1,77 +0,0 @@
1
- import { basename, extname } from 'path'
2
- import type { InlineConfig, Plugin, UserConfig } from 'vite'
3
- import react from '@vitejs/plugin-react'
4
- import tailwindcss from '@tailwindcss/vite'
5
-
6
- import type { PluginOptions } from '../types'
7
-
8
- const jsSpa = ({ filePath }: PluginOptions): Plugin => {
9
- const entry = basename(filePath, extname(filePath))
10
- const virtualPrefix = `virtual:ssr/${entry}.tsx`
11
-
12
- return {
13
- name: 'nulljs-ssr-client-plugin',
14
- apply: 'build',
15
- config: async (config: UserConfig, { command }) => {
16
- if (command !== 'build') {
17
- return config
18
- }
19
-
20
- return {
21
- build: {
22
- rollupOptions: {
23
- input: {
24
- [entry]: virtualPrefix
25
- },
26
- external: ['cloud'],
27
- output: {
28
- entryFileNames: '[name].js'
29
- }
30
- }
31
- },
32
- plugins: [react()]
33
- }
34
- },
35
-
36
- resolveId: (id: string) => {
37
- if (id === virtualPrefix) {
38
- return id
39
- }
40
- return null
41
- },
42
-
43
- load: (id: string) => {
44
- if (id === virtualPrefix) {
45
- const script = `
46
- import { StrictMode } from 'react'
47
- import { createRoot } from 'react-dom/client'
48
-
49
- import { Page } from "${filePath.replace(/\\/g, '\\\\')}";
50
-
51
- createRoot(document.getElementById('root')!).render(
52
- <StrictMode>
53
- <Page />
54
- </StrictMode>
55
- )`
56
-
57
- return script
58
- }
59
- return null
60
- }
61
- }
62
- }
63
-
64
- export const spaClientConfig = (filePath: string): InlineConfig => {
65
- return {
66
- logLevel: 'error',
67
- plugins: [
68
- jsSpa({
69
- filePath
70
- }),
71
- tailwindcss()
72
- ],
73
- build: {
74
- outDir: '/tmp'
75
- }
76
- }
77
- }