creght-cli 0.7.5 → 0.8.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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Creght CLI is a thin local bridge for syncing site code between a local directory and Creght.
4
4
 
5
- The CLI can also run a local Vite preview for pulled Creght projects. Creght remains responsible for cloud rendering, CMS, assets, and the realtime preview environment.
5
+ Creght remains responsible for cloud rendering, CMS, assets, and the preview environment. The CLI does not render sites locally; use `creght preview` to open the canonical remote preview.
6
6
 
7
7
  ## Install
8
8
 
@@ -180,54 +180,6 @@ generated/*
180
180
  /scratch/
181
181
  ```
182
182
 
183
- ## Local Vite Preview
184
-
185
- Creght projects pulled by the CLI usually do not have their own `package.json`
186
- or `node_modules`. The local preview plugin therefore uses Vite only for local
187
- file serving and TSX transpilation; third-party packages continue to resolve
188
- through the Creght import map, matching the Web editor preview model. In
189
- `creght dev`, the CLI serves the workspace directory, loads the platform import
190
- map from server system info, and passes it to the Vite plugin; the plugin's
191
- local map is only a fallback.
192
-
193
- Install Vite in the local project folder:
194
-
195
- ```bash
196
- cd ./mysite
197
- npm init -y
198
- npm install -D vite esbuild creght-cli
199
- ```
200
-
201
- Create `vite.config.mjs`:
202
-
203
- ```js
204
- import { defineConfig } from 'vite'
205
- import creght from 'creght-cli/vite'
206
-
207
- export default defineConfig({
208
- plugins: [
209
- creght({
210
- apiHost: 'https://creght.cn',
211
- projectId: '<project_id>',
212
- // token: process.env.CREGHT_TOKEN,
213
- }),
214
- ],
215
- })
216
- ```
217
-
218
- Run it:
219
-
220
- ```bash
221
- npx vite --host 0.0.0.0
222
- ```
223
-
224
- The plugin maps `/page/Index.tsx` to `/`, `/page/About.tsx` to `/about`, starts
225
- from the platform import map, merges `creght.config.ts` import-map entries,
226
- loads `/index.css` through the Tailwind browser runtime, proxies local `/api/*`
227
- requests to `apiHost`, calls page `getServerSideProps()` in the browser for a
228
- preview-only first render, and uses Vite HMR to re-import the current page
229
- module after local file changes without a full page reload.
230
-
231
183
  ## Push Local Changes
232
184
 
233
185
  Push the current local directory snapshot to Creght and exit:
@@ -288,57 +240,6 @@ content is backed up under `.creght/backup/<timestamp>-*/`.
288
240
  `base_to_local_diff` / `base_to_remote_diff`, so agents can decide how to
289
241
  resolve without extra round-trips.
290
242
 
291
- ## Local Web Editor Bidirectional Sync
292
-
293
- Run local files and the online Creght editor against the same cloud realtime
294
- files:
295
-
296
- ```bash
297
- creght dev --site_id=<project_id>/<site_id> --dir=./mysite
298
- ```
299
-
300
- For local backend or web development:
301
-
302
- ```bash
303
- CREGHT_API_HOST=http://localhost:8433 creght dev --web=http://localhost:5173 --site_id=<project_id>/<site_id> --dir=./mysite
304
- ```
305
-
306
- The command prints the online Web editor URL, pushes local file changes to
307
- Creght, and listens to the existing WebSocket collaboration channel so editor
308
- changes are written back to the local directory. MVP conflict handling is last
309
- write wins.
310
-
311
- `dev` also starts a local Vite preview by default:
312
-
313
- ```text
314
- VITE v8.0.14 ready in 529 ms
315
- ➜ Local: http://localhost:5173/
316
- Local Vite: started (preferred http://localhost:5173; use the Vite Local URL above)
317
- ```
318
-
319
- Use `--preview-port` or `--preview-host` to change the preferred local preview
320
- address. If that port is occupied, Vite uses its normal auto-port behavior and
321
- prints the actual URL in the terminal:
322
-
323
- ```bash
324
- creght dev --site_id=<project_id>/<site_id> --dir=./mysite --preview-port=5174
325
- ```
326
-
327
- Disable the local preview when you only want file sync:
328
-
329
- ```bash
330
- creght dev --site_id=<project_id>/<site_id> --dir=./mysite --no-preview
331
- ```
332
-
333
- The preview uses the bundled `creght-cli/vite` plugin. If the site directory
334
- has `node_modules/.bin/vite`, that local Vite is used; otherwise the CLI starts
335
- a hidden temporary Vite runtime under `.creght/` and installs `vite` plus
336
- `esbuild` there.
337
-
338
- Local file changes are pushed through Vite HMR as a React root re-render. This
339
- avoids a browser-level refresh, but it is not yet full React Fast Refresh and
340
- does not guarantee component state preservation.
341
-
342
243
  ## Open Preview
343
244
 
344
245
  Open the remote preview URL for a site in the browser:
@@ -428,10 +329,14 @@ Top-level `slug` and `sort` are optional; the `--slug` / `--sort` flags override
428
329
 
429
330
  ```bash
430
331
  creght content create --site_id=<project_id>/<site_id> --collection=prompts --data=./content.json --slug=typography-v02
431
- creght content update --site_id=<project_id>/<site_id> --collection=prompts --id=<content_id> --data=./content.json --sort=15
332
+ creght content update --site_id=<project_id>/<site_id> --collection=prompts --id=<content_id> --sort=15
432
333
  ```
433
334
 
434
- `sort` controls the order editors see in the CMS list — bigger shows first, and `0` is a real value rather than "unset". When neither the flag nor the file sets it, the request omits `sort` entirely: `create` takes the platform default (appended last) and `update` leaves the entry's current sort alone. Use `content update --sort` to reorder; deleting and recreating an entry changes its id, and site versions do not snapshot CMS content, so that cannot be undone.
335
+ `update` applies a partial update, so `--data` is optional there pass `--slug` / `--sort` alone to rename or reorder without re-submitting the body, as in the command above. `create` still requires `--data`.
336
+
337
+ `sort` controls the order editors see in the CMS list, bigger first. Omitting it lets `create` append the entry last and leaves `update`'s current value alone. Use `content update --sort` to reorder; deleting and recreating an entry changes its id, and site versions do not snapshot CMS content, so that cannot be undone.
338
+
339
+ One asymmetry worth knowing: the platform reads a zero `sort` on **create** as "auto-assign, append last", so a literal `0` cannot be created — `create --sort=0` appends. `update --sort=0` does store a real 0.
435
340
 
436
341
  ## Manage Forms
437
342
 
@@ -487,8 +392,10 @@ creght table record delete --site_id=<project_id>/<site_id> --table=appointments
487
392
 
488
393
  `record update` sends a patch body to the backend. Existing fields are merged,
489
394
  and a `null` field value removes that field. Both `create` and `update` accept
490
- `--sort=<n>` with the same semantics as content sort: `0` is a real value, and
491
- omitting the flag leaves `sort` out of the request instead of zeroing it.
395
+ `--sort=<n>`; omitting the flag leaves `sort` out of the request instead of
396
+ zeroing it. Unlike content, a record's `sort` cannot be set to `0` the platform
397
+ ignores a zero sort on record update, so `--sort=0` is rejected there rather than
398
+ silently doing nothing, and on `create` it means "append last".
492
399
 
493
400
  ## Func Backend Code As Files
494
401
 
@@ -502,8 +409,7 @@ The workflow is the same as for any site file:
502
409
 
503
410
  1. Run `creght pull --site_id=<project_id>/<site_id> --dir=./mysite`.
504
411
  2. Edit or create files under `./mysite/backend/func/`.
505
- 3. Run `creght push --site_id=<project_id>/<site_id> --dir=./mysite`, or keep
506
- `creght dev` running.
412
+ 3. Run `creght push --site_id=<project_id>/<site_id> --dir=./mysite`.
507
413
 
508
414
  Examples:
509
415
 
@@ -546,7 +452,7 @@ The output matches the Func HTTP response protocol: successful runs print
546
452
  top-level `ok` execution wrapper.
547
453
 
548
454
  There are no `creght func list/get/create/update/delete` commands. Manage Func
549
- code by editing `backend/func` files and syncing them with pull/push/dev
455
+ code by editing `backend/func` files and syncing them with pull/push
550
456
  like any other site file; `func run` only runs sample input.
551
457
 
552
458
  ## Upload Assets
@@ -598,8 +504,7 @@ with three-way merge, push local files back to Creght, resolve conflicts, open
598
504
  the remote preview, and publish a site.
599
505
 
600
506
  The CLI commands still use the Creght backend and web app for the canonical
601
- preview. The Vite plugin is a local development helper and intentionally does
602
- not implement full production SSR.
507
+ preview. The CLI does not render sites locally.
603
508
 
604
509
  ```bash
605
510
  creght login [--web=https://creght.cn]
@@ -608,7 +513,6 @@ creght project list
608
513
  creght pull --site_id=<project_id>/<site_id> --dir=./mysite
609
514
  creght push --site_id=<project_id>/<site_id> --dir=./mysite
610
515
  creght resolve --list
611
- creght dev --site_id=<project_id>/<site_id> --dir=./mysite [--web=https://creght.cn]
612
516
  creght preview --site_id=<project_id>/<site_id>
613
517
  creght publish --site_id=<project_id>/<site_id> [--note=<note>]
614
518
  creght cms collections --site_id=<project_id>/<site_id>
@@ -632,14 +536,13 @@ Command meanings:
632
536
  - `pull`: Download site files (including Func code under `backend/func/`) into a local workspace, three-way merging remote and local edits.
633
537
  - `push`: Push local workspace changes to the remote site/project after a three-way conflict check.
634
538
  - `resolve`: List files with conflict markers, or resolve one by keeping the local (`--ours`) or remote (`--theirs`) side.
635
- - `dev`: Bidirectionally sync local files with cloud realtime files and the online Web editor.
636
539
  - `preview`: Open the remote preview URL for a site in the browser.
637
540
  - `publish`: Publish a site to make the current remote site version live.
638
541
  - `cms`: Manage CMS collections.
639
542
  - `content`: Manage CMS content entries.
640
543
  - `form`: Manage forms and form submissions.
641
544
  - `table`: Manage project JSON tables and records used by Func.
642
- - `func`: Run project Func backend code with sample input. Func code itself is edited as `backend/func` site files and synced with pull/push/dev.
545
+ - `func`: Run project Func backend code with sample input. Func code itself is edited as `backend/func` site files and synced with pull/push.
643
546
  - `upload`: Upload a local file as a Creght site asset and print its URL.
644
547
  - `version`: Print the installed CLI version.
645
548
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "creght-cli",
3
- "version": "0.7.5",
3
+ "version": "0.8.0",
4
4
  "description": "Creght CLI for syncing local site code with Creght.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/creght-dev/creght-cli#readme",
@@ -14,12 +14,6 @@
14
14
  "bin": {
15
15
  "creght": "bin/creght.js"
16
16
  },
17
- "exports": {
18
- "./vite": {
19
- "types": "./vite/index.d.ts",
20
- "import": "./vite/index.js"
21
- }
22
- },
23
17
  "scripts": {
24
18
  "prepare:npm-binaries": "node scripts/prepare-npm-binaries.js",
25
19
  "test:npm": "node scripts/install.js --check"
@@ -27,25 +21,12 @@
27
21
  "files": [
28
22
  "bin/",
29
23
  "scripts/",
30
- "vite/",
31
24
  "vendor/",
32
25
  "README.md"
33
26
  ],
34
27
  "engines": {
35
28
  "node": ">=18"
36
29
  },
37
- "peerDependencies": {
38
- "esbuild": ">=0.20",
39
- "vite": ">=5"
40
- },
41
- "peerDependenciesMeta": {
42
- "esbuild": {
43
- "optional": true
44
- },
45
- "vite": {
46
- "optional": true
47
- }
48
- },
49
30
  "os": [
50
31
  "darwin",
51
32
  "linux",
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,55 +0,0 @@
1
- function mergeExternalDeps(externalValue, requiredDeps) {
2
- try {
3
- externalValue = decodeURIComponent(externalValue)
4
- } catch {
5
- // Keep the original value and continue merging invalid encoded external lists.
6
- }
7
-
8
- const seen = new Set()
9
- const deps = externalValue
10
- .split(',')
11
- .map((dep) => dep.trim())
12
- .filter(Boolean)
13
- .filter((dep) => {
14
- if (seen.has(dep)) return false
15
- seen.add(dep)
16
- return true
17
- })
18
-
19
- for (const dep of requiredDeps) {
20
- if (seen.has(dep)) continue
21
- seen.add(dep)
22
- deps.push(dep)
23
- }
24
-
25
- return deps.join(',')
26
- }
27
-
28
- export function normalizeImportMapExternal(specifier, url) {
29
- if (!/^https?:\/\//i.test(url)) return url
30
- if (
31
- specifier === 'react' ||
32
- specifier.startsWith('react/') ||
33
- specifier === 'react-dom' ||
34
- specifier.startsWith('react-dom/')
35
- ) {
36
- return url
37
- }
38
-
39
- const requiredDeps = ['react', 'react-dom']
40
- const hashIndex = url.indexOf('#')
41
- const beforeHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url
42
- const hash = hashIndex >= 0 ? url.slice(hashIndex) : ''
43
- const hasPrefixSlash = specifier.endsWith('/') && beforeHash.endsWith('/')
44
- const externalTarget = hasPrefixSlash ? beforeHash.slice(0, -1) : beforeHash
45
- const externalSuffix = hasPrefixSlash ? '/' : ''
46
- const externalMatch = externalTarget.match(/([?&])external=([^&#]*)/)
47
-
48
- if (externalMatch && externalMatch.index !== undefined) {
49
- const start = externalMatch.index + externalMatch[1].length + 'external='.length
50
- const end = start + externalMatch[2].length
51
- return `${externalTarget.slice(0, start)}${mergeExternalDeps(externalMatch[2], requiredDeps)}${externalTarget.slice(end)}${externalSuffix}${hash}`
52
- }
53
-
54
- return `${externalTarget}${hasPrefixSlash || externalTarget.includes('?') ? '&' : '?'}external=${requiredDeps.join(',')}${externalSuffix}${hash}`
55
- }
package/vite/index.d.ts DELETED
@@ -1,28 +0,0 @@
1
- import type { Plugin } from 'vite'
2
-
3
- export type CreghtVitePluginOptions = {
4
- /**
5
- * Local Creght project root. Defaults to Vite's root.
6
- */
7
- root?: string
8
- /**
9
- * Creght API host used by the local /api proxy at runtime.
10
- */
11
- apiHost?: string
12
- /**
13
- * Project id for runtime CMS/form requests.
14
- */
15
- projectId?: string
16
- /**
17
- * Optional CLI auth token. When set, local /api proxy sends it as Bearer.
18
- */
19
- token?: string
20
- /**
21
- * Platform import map entries. `creght dev` fills this from server system info.
22
- * Manually configured entries override the built-in fallback when no server map is provided.
23
- */
24
- importMap?: Record<string, string>
25
- }
26
-
27
- export declare function creght(options?: CreghtVitePluginOptions): Plugin
28
- export default creght
package/vite/index.js DELETED
@@ -1,546 +0,0 @@
1
- import fs from 'node:fs/promises'
2
- import path from 'node:path'
3
- import { transform as esbuildTransform } from 'esbuild'
4
- import { normalizeImportMapExternal } from './import-map.js'
5
-
6
- const modulePrefix = '/@creght/module'
7
- const assetPrefix = '/@creght/asset'
8
- const runtimePrefix = '/@creght/runtime'
9
- const pageExts = ['.tsx', '.ts', '.jsx', '.js']
10
- const sourceExts = new Set([...pageExts, '.css'])
11
-
12
- const fallbackImportMap = {
13
- react: 'https://esm.talizen.com/react@19.2.4?dev',
14
- 'react/': 'https://esm.talizen.com/react@19.2.4&dev/',
15
- 'react-dom': 'https://esm.talizen.com/react-dom@19.2.4?dev',
16
- 'react-dom/': 'https://esm.talizen.com/react-dom@19.2.4&dev/',
17
- 'react-dom/client': 'https://esm.talizen.com/react-dom@19.2.4/client?dev',
18
- 'class-variance-authority': 'https://esm.talizen.com/class-variance-authority@0.7.1',
19
- clsx: 'https://esm.talizen.com/clsx@2.1.1',
20
- 'tailwind-merge': 'https://esm.talizen.com/tailwind-merge@3.5.0',
21
- '@radix-ui/react-slot': 'https://esm.talizen.com/@radix-ui/react-slot@1.2.4',
22
- 'lucide-react': 'https://esm.talizen.com/lucide-react@0.577.0?dev&external=react,react-dom',
23
- motion: 'https://esm.talizen.com/motion@12.38.0?dev',
24
- 'motion/react': 'https://esm.talizen.com/motion@12.38.0/react?dev&external=react,react-dom',
25
- 'framer-motion': 'https://esm.talizen.com/framer-motion@12.38.0?dev&external=react,react-dom',
26
- three: 'https://esm.talizen.com/three@0.167.1',
27
- 'three/': 'https://esm.talizen.com/three@0.167.1/',
28
- '@react-three/fiber': 'https://esm.talizen.com/@react-three/fiber@9.3.0?external=react,react-dom,three',
29
- '@react-three/drei': 'https://esm.talizen.com/@react-three/drei@10.7.4?external=react,react-dom,three,@react-three',
30
- talizen: 'https://esm.talizen.com/talizen@0.1.4',
31
- 'talizen/': 'https://esm.talizen.com/talizen@0.1.4/',
32
- }
33
-
34
- const escapeHtml = (s) => String(s)
35
- .replaceAll('&', '&amp;')
36
- .replaceAll('<', '&lt;')
37
- .replaceAll('>', '&gt;')
38
- .replaceAll('"', '&quot;')
39
-
40
- const jsonScript = (value) => JSON.stringify(value).replaceAll('<', '\\u003c')
41
-
42
- const transformWithEsbuild = (source, filename, options) =>
43
- esbuildTransform(source, { ...options, sourcefile: filename })
44
-
45
- const isRelativeSpecifier = (specifier) =>
46
- specifier.startsWith('./') || specifier.startsWith('../') || specifier.startsWith('/')
47
-
48
- const contentTypeForPath = (file) => {
49
- switch (path.extname(file).toLowerCase()) {
50
- case '.css':
51
- return 'text/css; charset=utf-8'
52
- case '.js':
53
- case '.mjs':
54
- return 'text/javascript; charset=utf-8'
55
- case '.json':
56
- return 'application/json; charset=utf-8'
57
- case '.svg':
58
- return 'image/svg+xml'
59
- case '.png':
60
- return 'image/png'
61
- case '.jpg':
62
- case '.jpeg':
63
- return 'image/jpeg'
64
- case '.gif':
65
- return 'image/gif'
66
- case '.webp':
67
- return 'image/webp'
68
- case '.woff':
69
- return 'font/woff'
70
- case '.woff2':
71
- return 'font/woff2'
72
- default:
73
- return 'application/octet-stream'
74
- }
75
- }
76
-
77
- const normalizeProjectPath = (value) => {
78
- const out = path.posix.normalize('/' + value.replaceAll(path.sep, '/').replace(/^\/+/, ''))
79
- if (out.startsWith('/../')) {
80
- throw new Error(`unsafe Creght path: ${value}`)
81
- }
82
- return out
83
- }
84
-
85
- async function exists(file) {
86
- try {
87
- await fs.access(file)
88
- return true
89
- } catch {
90
- return false
91
- }
92
- }
93
-
94
- async function resolveFile(projectRoot, projectPath) {
95
- const clean = normalizeProjectPath(projectPath)
96
- const abs = path.join(projectRoot, clean.slice(1))
97
- if (await exists(abs)) return { projectPath: clean, abs }
98
-
99
- for (const ext of pageExts) {
100
- if (await exists(abs + ext)) {
101
- return { projectPath: clean + ext, abs: abs + ext }
102
- }
103
- }
104
-
105
- for (const ext of pageExts) {
106
- const indexPath = path.join(abs, 'Index' + ext)
107
- if (await exists(indexPath)) {
108
- return { projectPath: normalizeProjectPath(path.posix.join(clean, 'Index' + ext)), abs: indexPath }
109
- }
110
- }
111
-
112
- throw new Error(`Creght module not found: ${projectPath}`)
113
- }
114
-
115
- async function readSiteConfig(projectRoot) {
116
- for (const name of ['creght.config.ts', 'creght.config.js', 'talizen.config.ts', 'talizen.config.js', 'folia.config.ts', 'folia.config.js']) {
117
- const abs = path.join(projectRoot, name)
118
- if (!(await exists(abs))) continue
119
-
120
- try {
121
- const source = await fs.readFile(abs, 'utf8')
122
- const transformed = await transformWithEsbuild(source, abs, {
123
- loader: name.endsWith('.ts') ? 'ts' : 'js',
124
- format: 'cjs',
125
- target: 'es2020',
126
- })
127
- const module = { exports: {} }
128
- const require = (specifier) => {
129
- if (specifier === 'creght' || specifier === 'creght/config' || specifier === 'talizen' || specifier === 'talizen/config') {
130
- return { defineConfig: (config) => config }
131
- }
132
- if (specifier.startsWith('creght/')) return {}
133
- if (specifier.startsWith('talizen/')) return {}
134
- throw new Error(`[creght.config] unsupported import: ${specifier}`)
135
- }
136
- const fn = new Function('module', 'exports', 'require', 'defineConfig', transformed.code)
137
- fn(module, module.exports, require, (config) => config)
138
- const value = module.exports?.default ?? module.exports
139
- return value && typeof value === 'object' ? value : {}
140
- } catch (err) {
141
- console.warn(`[creght vite] failed to evaluate ${name}:`, err)
142
- return {}
143
- }
144
- }
145
-
146
- return {}
147
- }
148
-
149
- async function readIndexCss(projectRoot, config) {
150
- if (typeof config.tailwindCss === 'string') return config.tailwindCss
151
-
152
- const cssPath = path.join(projectRoot, 'index.css')
153
- if (!(await exists(cssPath))) return ''
154
- return fs.readFile(cssPath, 'utf8')
155
- }
156
-
157
- async function buildImportMap(projectRoot, options) {
158
- const config = await readSiteConfig(projectRoot)
159
- const userImports = config.importMap?.imports || {}
160
- const imports = Object.keys(options.importMap || {}).length > 0
161
- ? { ...options.importMap }
162
- : { ...fallbackImportMap }
163
-
164
- for (const [specifier, url] of Object.entries(userImports)) {
165
- imports[specifier] = normalizeImportMapExternal(specifier, String(url))
166
- }
167
- for (const [specifier, url] of Object.entries(options.importMap || {})) {
168
- imports[specifier] = normalizeImportMapExternal(specifier, String(url))
169
- }
170
-
171
- return { config, imports }
172
- }
173
-
174
- async function listPages(projectRoot) {
175
- const pageRoot = path.join(projectRoot, 'page')
176
- if (!(await exists(pageRoot))) return []
177
-
178
- const out = []
179
- async function walk(dir) {
180
- const entries = await fs.readdir(dir, { withFileTypes: true })
181
- for (const entry of entries) {
182
- const abs = path.join(dir, entry.name)
183
- if (entry.isDirectory()) {
184
- await walk(abs)
185
- continue
186
- }
187
- const ext = path.extname(entry.name)
188
- if (!pageExts.includes(ext)) continue
189
- if (entry.name.includes('.canvas.')) continue
190
- const rel = normalizeProjectPath(path.relative(projectRoot, abs))
191
- out.push({ file: rel, route: routeFromPagePath(rel) })
192
- }
193
- }
194
- await walk(pageRoot)
195
- return out.sort((a, b) => a.route.localeCompare(b.route))
196
- }
197
-
198
- function routeFromPagePath(file) {
199
- const withoutExt = file.replace(/^\/page\//, '').replace(/\.[^.]+$/, '')
200
- if (withoutExt === 'Index') return '/'
201
- return '/' + withoutExt
202
- .replace(/\/Index$/, '')
203
- .split('/')
204
- .map((segment) => segment.startsWith('[') && segment.endsWith(']') ? `:${segment.slice(1, -1)}` : segment.toLowerCase())
205
- .join('/')
206
- }
207
-
208
- function matchRoute(routes, pathname) {
209
- const normalized = pathname !== '/' ? pathname.replace(/\/+$/, '') : '/'
210
- const exact = routes.find((r) => r.route === normalized)
211
- if (exact) return { ...exact, params: {} }
212
-
213
- for (const route of routes) {
214
- const routeParts = route.route.split('/').filter(Boolean)
215
- const pathParts = normalized.split('/').filter(Boolean)
216
- if (routeParts.length !== pathParts.length) continue
217
- const params = {}
218
- let ok = true
219
- for (let i = 0; i < routeParts.length; i++) {
220
- const part = routeParts[i]
221
- if (part.startsWith(':')) {
222
- params[part.slice(1)] = decodeURIComponent(pathParts[i])
223
- continue
224
- }
225
- if (part !== pathParts[i].toLowerCase()) {
226
- ok = false
227
- break
228
- }
229
- }
230
- if (ok) return { ...route, params }
231
- }
232
-
233
- return { ...(routes.find((r) => r.route === '/') || routes[0]), params: {} }
234
- }
235
-
236
- function rewriteModuleSpecifiers(code, importerPath) {
237
- const rewrite = (specifier) => {
238
- if (!isRelativeSpecifier(specifier)) return specifier
239
- const [rawPath, rawQuery = ''] = specifier.split('?')
240
- const importerDir = path.posix.dirname(importerPath)
241
- const resolved = normalizeProjectPath(path.posix.join(importerDir, rawPath))
242
- const target = rawQuery ? `${resolved}?${rawQuery}` : resolved
243
- return `${modulePrefix}?path=${encodeURIComponent(target)}`
244
- }
245
-
246
- return code
247
- .replace(/(from\s*["'])([^"']+)(["'])/g, (_, before, specifier, after) => `${before}${rewrite(specifier)}${after}`)
248
- .replace(/(import\s*["'])([^"']+)(["'])/g, (_, before, specifier, after) => `${before}${rewrite(specifier)}${after}`)
249
- .replace(/(import\s*\(\s*["'])([^"']+)(["']\s*\))/g, (_, before, specifier, after) => `${before}${rewrite(specifier)}${after}`)
250
- }
251
-
252
- async function transformProjectModule(projectRoot, projectPath) {
253
- const [pathWithoutQuery, query = ''] = projectPath.split('?')
254
- const { projectPath: resolvedProjectPath, abs } = await resolveFile(projectRoot, pathWithoutQuery)
255
- const ext = path.extname(abs).toLowerCase()
256
-
257
- if (query === 'raw') {
258
- const source = await fs.readFile(abs, 'utf8')
259
- return `export default ${JSON.stringify(source)};\n`
260
- }
261
- if (query === 'url') {
262
- return `export default ${JSON.stringify(`${assetPrefix}?path=${encodeURIComponent(resolvedProjectPath)}`)};\n`
263
- }
264
- if (!sourceExts.has(ext)) {
265
- return `export default ${JSON.stringify(`${assetPrefix}?path=${encodeURIComponent(resolvedProjectPath)}`)};\n`
266
- }
267
-
268
- const source = await fs.readFile(abs, 'utf8')
269
- if (abs.endsWith('.css')) {
270
- return `
271
- const css = ${JSON.stringify(source)};
272
- let style = document.querySelector('style[data-talizen-css-module="${resolvedProjectPath}"]');
273
- if (!style) {
274
- style = document.createElement('style');
275
- style.dataset.talizenCssModule = ${JSON.stringify(resolvedProjectPath)};
276
- document.head.appendChild(style);
277
- }
278
- style.textContent = css;
279
- export default css;
280
- `
281
- }
282
-
283
- const loader = abs.endsWith('.tsx') ? 'tsx' : abs.endsWith('.ts') ? 'ts' : abs.endsWith('.jsx') ? 'jsx' : 'js'
284
- const result = await transformWithEsbuild(source, abs, {
285
- loader,
286
- jsx: 'automatic',
287
- jsxDev: true,
288
- sourcemap: 'inline',
289
- target: 'es2020',
290
- define: {
291
- 'process.env.NODE_ENV': '"development"',
292
- 'process.env.RENDER_ENV': '"design"',
293
- 'process.env.RENDER_MODE': '"design"',
294
- },
295
- })
296
-
297
- return rewriteModuleSpecifiers(result.code, resolvedProjectPath)
298
- }
299
-
300
- function renderRuntimeScript(entryFile, params, options) {
301
- const projectId = options.projectId || ''
302
- const authHeaders = options.token ? { Authorization: `Bearer ${options.token}` } : {}
303
-
304
- return `
305
- import React from 'react';
306
- import { createRoot } from 'react-dom/client';
307
- import { createHotContext } from '/@vite/client';
308
-
309
- const creghtRuntimeHeaders = ${jsonScript(authHeaders)};
310
- const creghtRuntimeFetch = window.fetch.bind(window);
311
-
312
- window.TalizenConfig = {
313
- baseUrl: window.location.origin + '/api/u/v2/project/' + ${JSON.stringify(projectId)},
314
- headers: creghtRuntimeHeaders,
315
- fetch(input, init = {}) {
316
- const headers = new Headers(init.headers || {});
317
- for (const [key, value] of Object.entries(creghtRuntimeHeaders)) {
318
- if (!headers.has(key)) headers.set(key, value);
319
- }
320
- return creghtRuntimeFetch(input, { ...init, headers });
321
- },
322
- };
323
-
324
- const rootEl = document.getElementById('root');
325
- const root = createRoot(rootEl);
326
- let renderVersion = 0;
327
-
328
- async function loadPageModule() {
329
- return import(${JSON.stringify(`${modulePrefix}?path=${encodeURIComponent(entryFile)}`)} + '&t=' + Date.now());
330
- }
331
-
332
- async function renderPage() {
333
- const version = ++renderVersion;
334
- const pageModule = await loadPageModule();
335
- if (version !== renderVersion) return;
336
-
337
- const Page = pageModule.default || pageModule.App;
338
- let props = {};
339
-
340
- if (!Page) {
341
- throw new Error('Creght page has no default export: ${entryFile}');
342
- }
343
-
344
- if (typeof pageModule.getServerSideProps === 'function') {
345
- const result = await pageModule.getServerSideProps({
346
- query: Object.fromEntries(new URLSearchParams(window.location.search)),
347
- params: ${jsonScript(params)},
348
- });
349
- props = result && result.props ? result.props : {};
350
- }
351
-
352
- root.render(React.createElement(Page, props));
353
- }
354
-
355
- const hot = createHotContext(${JSON.stringify(`${runtimePrefix}?entry=${encodeURIComponent(entryFile)}`)});
356
-
357
- await renderPage();
358
-
359
- hot.on('creght:update', () => {
360
- renderPage().catch((err) => {
361
- console.error('[creght vite] hot update failed', err);
362
- });
363
- });
364
- `
365
- }
366
-
367
- async function renderHtml(projectRoot, pathname, options) {
368
- const routes = await listPages(projectRoot)
369
- if (routes.length === 0) {
370
- return `<!doctype html><div style="font:14px system-ui;padding:24px">No Creght pages found in <code>/page</code>.</div>`
371
- }
372
-
373
- const matched = matchRoute(routes, pathname)
374
- const { config, imports } = await buildImportMap(projectRoot, options)
375
- const indexCss = await readIndexCss(projectRoot, config)
376
- const customCode = config.customCode || {}
377
-
378
- return `<!doctype html>
379
- <html lang="en">
380
- <head>
381
- <meta charset="UTF-8">
382
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
383
- <title>${escapeHtml(matched.route === '/' ? 'Creght' : matched.route)}</title>
384
- <script type="importmap">${jsonScript({ imports })}</script>
385
- <script type="module" src="/@vite/client"></script>
386
- <style type="text/tailwindcss">${indexCss}</style>
387
- <script type="module" src="https://esm.talizen.com/@tailwindcss/browser@4"></script>
388
- ${customCode.head || ''}
389
- </head>
390
- <body>
391
- ${customCode.bodyStart || ''}
392
- <div id="root"></div>
393
- <script type="module" src="${runtimePrefix}?entry=${encodeURIComponent(matched.file)}&params=${encodeURIComponent(JSON.stringify(matched.params))}&t=${Date.now()}"></script>
394
- ${customCode.bodyEnd || customCode.body || ''}
395
- </body>
396
- </html>`
397
- }
398
-
399
- export function creght(options = {}) {
400
- let projectRoot = ''
401
- const apiHost = (options.apiHost || 'https://creght.cn').replace(/\/+$/, '')
402
-
403
- return {
404
- name: 'creght-local-preview',
405
- enforce: 'pre',
406
- configResolved(config) {
407
- projectRoot = path.resolve(options.root || config.root)
408
- },
409
- configureServer(server) {
410
- const hmrTimers = new Map()
411
- const sendCreghtUpdate = (file) => {
412
- if (!file.startsWith(projectRoot)) return
413
-
414
- const projectPath = normalizeProjectPath(path.relative(projectRoot, file))
415
- const existing = hmrTimers.get(projectPath)
416
- if (existing) clearTimeout(existing)
417
-
418
- hmrTimers.set(projectPath, setTimeout(() => {
419
- hmrTimers.delete(projectPath)
420
- server.ws.send({
421
- type: 'custom',
422
- event: 'creght:update',
423
- data: {
424
- file: projectPath,
425
- time: Date.now(),
426
- },
427
- })
428
- }, 80))
429
- }
430
-
431
- server.watcher.add(path.join(projectRoot, '**/*'))
432
- server.watcher.on('change', sendCreghtUpdate)
433
- server.watcher.on('add', sendCreghtUpdate)
434
- server.watcher.on('unlink', sendCreghtUpdate)
435
-
436
- server.middlewares.use(async (req, res, next) => {
437
- try {
438
- const url = new URL(req.url || '/', 'http://localhost')
439
-
440
- if (url.pathname.startsWith('/api/')) {
441
- const headers = new Headers()
442
- for (const [key, value] of Object.entries(req.headers)) {
443
- if (Array.isArray(value)) {
444
- headers.set(key, value.join(', '))
445
- } else if (value != null) {
446
- headers.set(key, value)
447
- }
448
- }
449
- headers.set('host', new URL(apiHost).host)
450
- if (options.token) {
451
- headers.delete('cookie')
452
- headers.set('authorization', `Bearer ${options.token}`)
453
- }
454
-
455
- const method = req.method || 'GET'
456
- const body = method === 'GET' || method === 'HEAD' ? undefined : req
457
- const upstream = await fetch(apiHost + url.pathname + url.search, {
458
- method,
459
- headers,
460
- body,
461
- // Required by Node fetch when the request body is a stream.
462
- duplex: body ? 'half' : undefined,
463
- })
464
-
465
- res.statusCode = upstream.status
466
- upstream.headers.forEach((value, key) => {
467
- const lowerKey = key.toLowerCase()
468
- if (lowerKey === 'content-encoding') return
469
- if (lowerKey === 'content-length') return
470
- if (lowerKey === 'transfer-encoding') return
471
- if (lowerKey === 'connection') return
472
- res.setHeader(key, value)
473
- })
474
- const data = Buffer.from(await upstream.arrayBuffer())
475
- res.end(data)
476
- return
477
- }
478
-
479
- if (url.pathname === modulePrefix) {
480
- const projectPath = url.searchParams.get('path')
481
- if (!projectPath) {
482
- res.statusCode = 400
483
- res.end('missing path')
484
- return
485
- }
486
- const code = await transformProjectModule(projectRoot, projectPath)
487
- res.setHeader('Content-Type', 'text/javascript; charset=utf-8')
488
- res.end(code)
489
- return
490
- }
491
-
492
- if (url.pathname === runtimePrefix) {
493
- const entry = url.searchParams.get('entry')
494
- const rawParams = url.searchParams.get('params') || '{}'
495
- if (!entry) {
496
- res.statusCode = 400
497
- res.end('missing entry')
498
- return
499
- }
500
- let params = {}
501
- try {
502
- params = JSON.parse(rawParams)
503
- } catch {
504
- params = {}
505
- }
506
- res.setHeader('Content-Type', 'text/javascript; charset=utf-8')
507
- res.end(renderRuntimeScript(entry, params, options))
508
- return
509
- }
510
-
511
- if (url.pathname === assetPrefix) {
512
- const projectPath = url.searchParams.get('path')
513
- if (!projectPath) {
514
- res.statusCode = 400
515
- res.end('missing path')
516
- return
517
- }
518
- const { abs } = await resolveFile(projectRoot, projectPath)
519
- res.setHeader('Content-Type', contentTypeForPath(abs))
520
- res.end(await fs.readFile(abs))
521
- return
522
- }
523
-
524
- if (req.method !== 'GET') {
525
- next()
526
- return
527
- }
528
-
529
- const accept = req.headers.accept || ''
530
- if (!accept.includes('text/html')) {
531
- next()
532
- return
533
- }
534
-
535
- const html = await renderHtml(projectRoot, url.pathname, options)
536
- res.setHeader('Content-Type', 'text/html; charset=utf-8')
537
- res.end(html)
538
- } catch (err) {
539
- next(err)
540
- }
541
- })
542
- },
543
- }
544
- }
545
-
546
- export default creght
@@ -1,47 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import { describe, it } from 'node:test'
3
-
4
- import { normalizeImportMapExternal } from './import-map.js'
5
-
6
- describe('normalizeImportMapExternal', () => {
7
- it('adds react and react-dom externals to regular import map entries', () => {
8
- assert.equal(
9
- normalizeImportMapExternal('framer-motion', 'https://esm.talizen.com/framer-motion'),
10
- 'https://esm.talizen.com/framer-motion?external=react,react-dom',
11
- )
12
- })
13
-
14
- it('merges missing externals without dropping existing entries', () => {
15
- assert.equal(
16
- normalizeImportMapExternal(
17
- '@react-three/drei',
18
- 'https://esm.talizen.com/@react-three/drei?external=three,react,@react-three/fiber',
19
- ),
20
- 'https://esm.talizen.com/@react-three/drei?external=three,react,@react-three/fiber,react-dom',
21
- )
22
- })
23
-
24
- it('inserts externals before the trailing slash for prefix import map entries', () => {
25
- assert.equal(
26
- normalizeImportMapExternal('talizen/', 'https://esm.talizen.com/talizen@0.1.4/'),
27
- 'https://esm.talizen.com/talizen@0.1.4&external=react,react-dom/',
28
- )
29
- })
30
-
31
- it('merges externals before the trailing slash for prefix entries', () => {
32
- assert.equal(
33
- normalizeImportMapExternal(
34
- '@kobalte/core/',
35
- 'https://esm.talizen.com/@kobalte/core&external=react/',
36
- ),
37
- 'https://esm.talizen.com/@kobalte/core&external=react,react-dom/',
38
- )
39
- })
40
-
41
- it('does not modify host React runtime specifiers', () => {
42
- assert.equal(
43
- normalizeImportMapExternal('react-dom/client', 'https://esm.talizen.com/react-dom@19/client?dev'),
44
- 'https://esm.talizen.com/react-dom@19/client?dev',
45
- )
46
- })
47
- })
package/vite/package.json DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "type": "module"
3
- }