creght-cli 0.7.6 → 0.9.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,91 +240,130 @@ 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
243
+ ## Open Preview
292
244
 
293
- Run local files and the online Creght editor against the same cloud realtime
294
- files:
245
+ Open the remote preview URL for a site in the browser:
295
246
 
296
247
  ```bash
297
- creght dev --site_id=<project_id>/<site_id> --dir=./mysite
248
+ creght preview --site_id=<project_id>/<site_id>
298
249
  ```
299
250
 
300
- For local backend or web development:
251
+ For local development:
301
252
 
302
253
  ```bash
303
- CREGHT_API_HOST=http://localhost:8433 creght dev --web=http://localhost:5173 --site_id=<project_id>/<site_id> --dir=./mysite
254
+ CREGHT_API_HOST=http://localhost:8433 creght preview --site_id=<project_id>/<site_id>
304
255
  ```
305
256
 
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.
257
+ ## Publish Site
310
258
 
311
- `dev` also starts a local Vite preview by default:
259
+ Publish a site:
312
260
 
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)
261
+ ```bash
262
+ creght publish --site_id=<project_id>/<site_id>
317
263
  ```
318
264
 
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:
265
+ With a publish note:
322
266
 
323
267
  ```bash
324
- creght dev --site_id=<project_id>/<site_id> --dir=./mysite --preview-port=5174
268
+ creght publish --site_id=<project_id>/<site_id> --note="Update homepage copy"
325
269
  ```
326
270
 
327
- Disable the local preview when you only want file sync:
271
+ For local development:
328
272
 
329
273
  ```bash
330
- creght dev --site_id=<project_id>/<site_id> --dir=./mysite --no-preview
274
+ CREGHT_API_HOST=http://localhost:8433 creght publish --site_id=<project_id>/<site_id>
331
275
  ```
332
276
 
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.
277
+ `publish` reports the version it created, so you can publish it again later:
337
278
 
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.
279
+ ```text
280
+ Published <project_id>/<site_id>
281
+ version 14 (id 458) is live on demo.creght.cn
282
+ ```
341
283
 
342
- ## Open Preview
284
+ ## Site Versions
343
285
 
344
- Open the remote preview URL for a site in the browser:
286
+ A site version is an immutable snapshot of the site's source files — the
287
+ platform equivalent of a git commit. Create one whenever a piece of work is
288
+ done, then publish whichever version you want the live site to serve.
289
+
290
+ Snapshots cover source files only. CMS content and the platform state under
291
+ `/platform/**` (CMS/form/table/auth definitions) are live, so publishing an
292
+ older version does not roll those back.
293
+
294
+ Inside a pulled workspace `--site_id` is optional; the version commands
295
+ discover `.creght/state.json` from the current directory or its parents, like
296
+ `pull`/`push` do.
297
+
298
+ Snapshot the current site source without publishing it:
345
299
 
346
300
  ```bash
347
- creght preview --site_id=<project_id>/<site_id>
301
+ creght version create --note="Add pricing page"
348
302
  ```
349
303
 
350
- For local development:
304
+ A version records the *remote* files, so local edits that were never pushed
305
+ would be missing from it. `create` therefore compares the workspace against the
306
+ site first and refuses to run while anything is unpushed:
351
307
 
352
- ```bash
353
- CREGHT_API_HOST=http://localhost:8433 creght preview --site_id=<project_id>/<site_id>
308
+ ```text
309
+ update /page/Index.tsx
310
+ 1 change is not pushed yet and would be missing from this version; run creght
311
+ push first, or pass --allow-dirty to snapshot the remote site as it is
354
312
  ```
355
313
 
356
- ## Publish Site
314
+ Run `creght push` first, or pass `--allow-dirty` to snapshot the remote site as
315
+ it is. The platform rejects a snapshot identical to the newest version, so
316
+ repeated `create` calls never pile up duplicates.
357
317
 
358
- Publish a site:
318
+ List versions, newest first:
359
319
 
360
320
  ```bash
361
- creght publish --site_id=<project_id>/<site_id>
321
+ creght version list
362
322
  ```
363
323
 
364
- With a publish note:
324
+ ```text
325
+ VERSION ID CREATED FROM NOTE
326
+ 12 456 2026-08-05 14:31 api_generate_version Add pricing page
327
+ * 11 455 2026-08-05 11:02 publish Fix nav
328
+ 10 442 2026-08-04 18:40 agent -
329
+ * live: version 11 (id 455), served by demo.creght.cn
330
+ pinned: www.example.com -> version 12 (id 456)
331
+ 1 change is pending on the site since the newest version; run creght version create to snapshot them
332
+ ```
333
+
334
+ `*` marks the version the live site serves. `VERSION` is the per-site number
335
+ that `version publish` takes; `ID` is the platform-wide version id. Use
336
+ `--limit=<n>` to shorten the list and `--json` to get the raw publish state,
337
+ including every version, the pinned domains, and the exact files that changed
338
+ since the newest version.
339
+
340
+ Make an existing version live, forward to a newer one or back to an older one:
365
341
 
366
342
  ```bash
367
- creght publish --site_id=<project_id>/<site_id> --note="Update homepage copy"
343
+ creght version publish 12
344
+ creght version publish 12 --note="Roll back nav change"
368
345
  ```
369
346
 
370
- For local development:
347
+ Every domain follows it except domains pinned to a specific version.
348
+ Publishing the version already being served is a no-op that just refreshes its
349
+ caches.
350
+
351
+ Publishing a version changes only which snapshot the live site serves — it
352
+ restores nothing. The editable site workspace and your local files are
353
+ untouched, and `creght pull` still fetches the current workspace rather than the
354
+ published version's files. So after rolling production back to an older version,
355
+ the workspace still holds the newer source and the next `publish` would ship it
356
+ again; to revert the code itself, edit locally and push.
357
+
358
+ `12` is the per-site number from the `VERSION` column. To reach a version older
359
+ than the window `version list` returns, select it by id instead:
371
360
 
372
361
  ```bash
373
- CREGHT_API_HOST=http://localhost:8433 creght publish --site_id=<project_id>/<site_id>
362
+ creght version publish id:456
374
363
  ```
375
364
 
365
+ Bare `creght version` still prints the installed CLI version.
366
+
376
367
  ## Manage CMS Collections
377
368
 
378
369
  List CMS collections:
@@ -508,8 +499,7 @@ The workflow is the same as for any site file:
508
499
 
509
500
  1. Run `creght pull --site_id=<project_id>/<site_id> --dir=./mysite`.
510
501
  2. Edit or create files under `./mysite/backend/func/`.
511
- 3. Run `creght push --site_id=<project_id>/<site_id> --dir=./mysite`, or keep
512
- `creght dev` running.
502
+ 3. Run `creght push --site_id=<project_id>/<site_id> --dir=./mysite`.
513
503
 
514
504
  Examples:
515
505
 
@@ -552,7 +542,7 @@ The output matches the Func HTTP response protocol: successful runs print
552
542
  top-level `ok` execution wrapper.
553
543
 
554
544
  There are no `creght func list/get/create/update/delete` commands. Manage Func
555
- code by editing `backend/func` files and syncing them with pull/push/dev
545
+ code by editing `backend/func` files and syncing them with pull/push
556
546
  like any other site file; `func run` only runs sample input.
557
547
 
558
548
  ## Upload Assets
@@ -603,9 +593,11 @@ Creght, list projects and sites, pull remote site files into a local directory
603
593
  with three-way merge, push local files back to Creght, resolve conflicts, open
604
594
  the remote preview, and publish a site.
605
595
 
596
+ It can also manage site versions: immutable snapshots of a site's source files,
597
+ created and published like git commits.
598
+
606
599
  The CLI commands still use the Creght backend and web app for the canonical
607
- preview. The Vite plugin is a local development helper and intentionally does
608
- not implement full production SSR.
600
+ preview. The CLI does not render sites locally.
609
601
 
610
602
  ```bash
611
603
  creght login [--web=https://creght.cn]
@@ -614,7 +606,6 @@ creght project list
614
606
  creght pull --site_id=<project_id>/<site_id> --dir=./mysite
615
607
  creght push --site_id=<project_id>/<site_id> --dir=./mysite
616
608
  creght resolve --list
617
- creght dev --site_id=<project_id>/<site_id> --dir=./mysite [--web=https://creght.cn]
618
609
  creght preview --site_id=<project_id>/<site_id>
619
610
  creght publish --site_id=<project_id>/<site_id> [--note=<note>]
620
611
  creght cms collections --site_id=<project_id>/<site_id>
@@ -628,6 +619,9 @@ creght table record create --site_id=<project_id>/<site_id> --table=<key> --data
628
619
  creght func run --site_id=<project_id>/<site_id> --key=<key.method> --input=./input.json
629
620
  creght upload --site_id=<project_id>/<site_id> --file=./image.png
630
621
  creght version
622
+ creght version create [--note=<note>] [--allow-dirty]
623
+ creght version list [--limit=<n>] [--json]
624
+ creght version publish <version_no> [--note=<note>]
631
625
  ```
632
626
 
633
627
  Command meanings:
@@ -638,16 +632,15 @@ Command meanings:
638
632
  - `pull`: Download site files (including Func code under `backend/func/`) into a local workspace, three-way merging remote and local edits.
639
633
  - `push`: Push local workspace changes to the remote site/project after a three-way conflict check.
640
634
  - `resolve`: List files with conflict markers, or resolve one by keeping the local (`--ours`) or remote (`--theirs`) side.
641
- - `dev`: Bidirectionally sync local files with cloud realtime files and the online Web editor.
642
635
  - `preview`: Open the remote preview URL for a site in the browser.
643
- - `publish`: Publish a site to make the current remote site version live.
636
+ - `publish`: Snapshot the current remote site source into a new version and make it live in one step.
644
637
  - `cms`: Manage CMS collections.
645
638
  - `content`: Manage CMS content entries.
646
639
  - `form`: Manage forms and form submissions.
647
640
  - `table`: Manage project JSON tables and records used by Func.
648
- - `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.
641
+ - `func`: Run project Func backend code with sample input. Func code itself is edited as `backend/func` site files and synced with pull/push.
649
642
  - `upload`: Upload a local file as a Creght site asset and print its URL.
650
- - `version`: Print the installed CLI version.
643
+ - `version`: Print the installed CLI version. Subcommands manage site versions — immutable source snapshots: `version create` records one, `version list` shows them and which is live, `version publish` makes one live.
651
644
 
652
645
  ## Release
653
646
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "creght-cli",
3
- "version": "0.7.6",
3
+ "version": "0.9.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
- }