creght-cli 0.1.16
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 +495 -0
- package/bin/creght.js +30 -0
- package/package.json +58 -0
- package/scripts/install.js +27 -0
- package/scripts/prepare-npm-binaries.js +62 -0
- package/vendor/darwin-arm64/creght +0 -0
- package/vendor/darwin-x64/creght +0 -0
- package/vendor/linux-arm64/creght +0 -0
- package/vendor/linux-x64/creght +0 -0
- package/vendor/win32-arm64/creght.exe +0 -0
- package/vendor/win32-x64/creght.exe +0 -0
- package/vite/import-map.js +55 -0
- package/vite/index.d.ts +28 -0
- package/vite/index.js +546 -0
- package/vite/index.test.mjs +47 -0
- package/vite/package.json +3 -0
package/README.md
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
# Cregh CLI
|
|
2
|
+
|
|
3
|
+
Cregh CLI is a thin local bridge for syncing site code between a local directory and Cregh.
|
|
4
|
+
|
|
5
|
+
The CLI can also run a local Vite preview for pulled Cregh projects. Cregh remains responsible for cloud rendering, CMS, assets, and the realtime preview environment.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Using npm:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g creght-cli
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Build from source:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cd /Users/bysir/dev/bysir/creght-cli
|
|
19
|
+
go build -o creght ./cmd/creght
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Optional:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
mv ./creght /usr/local/bin/creght
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Login
|
|
29
|
+
|
|
30
|
+
For production:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
creght login
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For local development:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
CREGHT_API_HOST=http://localhost:8433 creght login --web=http://localhost:5173
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The command opens a browser authorization page. After authorization succeeds, the CLI stores the token in:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
~/Library/Application Support/creght/config.json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The config file contains the API host and CLI token.
|
|
49
|
+
|
|
50
|
+
When `--web` is omitted, the CLI uses `CREGHT_WEB_HOST` if set. For local API hosts such as `localhost` or `127.0.0.1`, it defaults to `http://localhost:5173`.
|
|
51
|
+
For production, the default API host and default web host are both `https://creght.cn`.
|
|
52
|
+
|
|
53
|
+
## Logout
|
|
54
|
+
|
|
55
|
+
Remove the saved CLI config:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
creght logout
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
This clears the saved token and any saved API host. The next command will use the production default unless you set `CREGHT_API_HOST`.
|
|
62
|
+
|
|
63
|
+
## List Projects
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
creght project list
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For local development:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
CREGHT_API_HOST=http://localhost:8433 creght project list
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Example output:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
project_id Project Name
|
|
79
|
+
project_id/site_id Site Name
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Use the `project_id/site_id` value with `pull`, `push`, and `sync`.
|
|
83
|
+
|
|
84
|
+
## Create Project
|
|
85
|
+
|
|
86
|
+
Create a new project:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
creght project create --name="My Project"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
For local development:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
CREGHT_API_HOST=http://localhost:8433 creght project create --name="My Project"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
You can also create from an existing project or template when the backend allows it:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
creght project create --name="My Project" --from_id=<project_id>
|
|
102
|
+
creght project create --name="My Project" --tpl_id=<template_id>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Pull Site Code
|
|
106
|
+
|
|
107
|
+
Download the current remote site files into a local directory:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
creght pull --site_id=<project_id>/<site_id> --dir=./mysite
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
For local development:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
CREGHT_API_HOST=http://localhost:8433 creght pull --site_id=<project_id>/<site_id> --dir=./mysite
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The command writes remote files such as `/page/...`, `/component/...`, and `creght.config.ts` into the target directory.
|
|
120
|
+
|
|
121
|
+
## Local Vite Preview
|
|
122
|
+
|
|
123
|
+
Cregh projects pulled by the CLI usually do not have their own `package.json`
|
|
124
|
+
or `node_modules`. The local preview plugin therefore uses Vite only for local
|
|
125
|
+
file serving and TSX transpilation; third-party packages continue to resolve
|
|
126
|
+
through the Cregh import map, matching the Web editor preview model. In
|
|
127
|
+
`creght dev`, the CLI loads the platform import map from server system info and
|
|
128
|
+
passes it to the Vite plugin; the plugin's local map is only a fallback.
|
|
129
|
+
|
|
130
|
+
Install Vite in the local project folder:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
cd ./mysite
|
|
134
|
+
npm init -y
|
|
135
|
+
npm install -D vite esbuild creght-cli
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Create `vite.config.mjs`:
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
import { defineConfig } from 'vite'
|
|
142
|
+
import creght from 'creght-cli/vite'
|
|
143
|
+
|
|
144
|
+
export default defineConfig({
|
|
145
|
+
plugins: [
|
|
146
|
+
creght({
|
|
147
|
+
apiHost: 'https://creght.cn',
|
|
148
|
+
projectId: '<project_id>',
|
|
149
|
+
// token: process.env.CREGHT_TOKEN,
|
|
150
|
+
}),
|
|
151
|
+
],
|
|
152
|
+
})
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Run it:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
npx vite --host 0.0.0.0
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The plugin maps `/page/Index.tsx` to `/`, `/page/About.tsx` to `/about`, starts
|
|
162
|
+
from the platform import map, merges `creght.config.ts` import-map entries,
|
|
163
|
+
loads `/index.css` through the Tailwind browser runtime, proxies local `/api/*`
|
|
164
|
+
requests to `apiHost`, calls page `getServerSideProps()` in the browser for a
|
|
165
|
+
preview-only first render, and uses Vite HMR to re-import the current page
|
|
166
|
+
module after local file changes without a full page reload.
|
|
167
|
+
|
|
168
|
+
## Push Local Changes
|
|
169
|
+
|
|
170
|
+
Push the current local directory snapshot to Cregh and exit:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
creght push --site_id=<project_id>/<site_id> --dir=./mysite
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
For local development:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
CREGHT_API_HOST=http://localhost:8433 creght push --site_id=<project_id>/<site_id> --dir=./mysite
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The CLI scans the local directory and calls the existing Cregh `site_action`
|
|
183
|
+
API to create or update remote files.
|
|
184
|
+
|
|
185
|
+
## Sync Local Changes
|
|
186
|
+
|
|
187
|
+
Run watch mode for a local directory:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
creght sync --site_id=<project_id>/<site_id> --dir=./mysite
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
For local development:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
CREGHT_API_HOST=http://localhost:8433 creght sync --site_id=<project_id>/<site_id> --dir=./mysite
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`sync` first pushes the current local snapshot, then keeps running and
|
|
200
|
+
automatically listens for local file changes. When a file is changed locally,
|
|
201
|
+
the CLI calls the existing Cregh `site_action` API and updates the remote site
|
|
202
|
+
in realtime. The command also prints the remote preview URL when available.
|
|
203
|
+
|
|
204
|
+
## Local Web Editor Bidirectional Sync
|
|
205
|
+
|
|
206
|
+
Run local files and the online Cregh editor against the same cloud realtime
|
|
207
|
+
files:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
creght dev --site_id=<project_id>/<site_id> --dir=./mysite
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
For local backend or web development:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
CREGHT_API_HOST=http://localhost:8433 creght dev --web=http://localhost:5173 --site_id=<project_id>/<site_id> --dir=./mysite
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The command prints the online Web editor URL, pushes local file changes to
|
|
220
|
+
Cregh, and listens to the existing WebSocket collaboration channel so editor
|
|
221
|
+
changes are written back to the local directory. MVP conflict handling is last
|
|
222
|
+
write wins.
|
|
223
|
+
|
|
224
|
+
`dev` also starts a local Vite preview by default:
|
|
225
|
+
|
|
226
|
+
```text
|
|
227
|
+
VITE v8.0.14 ready in 529 ms
|
|
228
|
+
➜ Local: http://localhost:5173/
|
|
229
|
+
Local Vite: started (preferred http://localhost:5173; use the Vite Local URL above)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Use `--preview-port` or `--preview-host` to change the preferred local preview
|
|
233
|
+
address. If that port is occupied, Vite uses its normal auto-port behavior and
|
|
234
|
+
prints the actual URL in the terminal:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
creght dev --site_id=<project_id>/<site_id> --dir=./mysite --preview-port=5174
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Disable the local preview when you only want file sync:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
creght dev --site_id=<project_id>/<site_id> --dir=./mysite --no-preview
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The preview uses the bundled `creght-cli/vite` plugin. If the site directory
|
|
247
|
+
has `node_modules/.bin/vite`, that local Vite is used; otherwise the CLI starts
|
|
248
|
+
a hidden temporary Vite runtime under `.creght/` and installs `vite` plus
|
|
249
|
+
`esbuild` there.
|
|
250
|
+
|
|
251
|
+
Local file changes are pushed through Vite HMR as a React root re-render. This
|
|
252
|
+
avoids a browser-level refresh, but it is not yet full React Fast Refresh and
|
|
253
|
+
does not guarantee component state preservation.
|
|
254
|
+
|
|
255
|
+
## Open Preview
|
|
256
|
+
|
|
257
|
+
Open the remote preview URL for a site in the browser:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
creght preview --site_id=<project_id>/<site_id>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
For local development:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
CREGHT_API_HOST=http://localhost:8433 creght preview --site_id=<project_id>/<site_id>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Publish Site
|
|
270
|
+
|
|
271
|
+
Publish a site:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
creght publish --site_id=<project_id>/<site_id>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
With a publish note:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
creght publish --site_id=<project_id>/<site_id> --note="Update homepage copy"
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
For local development:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
CREGHT_API_HOST=http://localhost:8433 creght publish --site_id=<project_id>/<site_id>
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Manage CMS Collections
|
|
290
|
+
|
|
291
|
+
List CMS collections:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
creght cms collections --site_id=<project_id>/<site_id>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Create a collection from a JSON Schema file:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
creght cms collection create --site_id=<project_id>/<site_id> --key=blogs --name="Blogs" --schema=./blogs.schema.json
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Update or delete by collection key or id:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
creght cms collection get --site_id=<project_id>/<site_id> --key=blogs
|
|
307
|
+
creght cms collection update --site_id=<project_id>/<site_id> --key=blogs --schema=./blogs.schema.json
|
|
308
|
+
creght cms collection delete --site_id=<project_id>/<site_id> --key=blogs
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
`--schema` can point to either a raw JSON Schema object or a full collection JSON object containing fields such as `key`, `name`, `desc`, and `json_schema`.
|
|
312
|
+
|
|
313
|
+
## Manage CMS Content
|
|
314
|
+
|
|
315
|
+
List, get, create, update, and delete content entries:
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
creght content list --site_id=<project_id>/<site_id> --collection=blogs
|
|
319
|
+
creght content get --site_id=<project_id>/<site_id> --collection=blogs --slug=hello-world
|
|
320
|
+
creght content create --site_id=<project_id>/<site_id> --collection=blogs --data=./content.json --slug=hello-world
|
|
321
|
+
creght content update --site_id=<project_id>/<site_id> --collection=blogs --id=<content_id> --data=./content.json
|
|
322
|
+
creght content delete --site_id=<project_id>/<site_id> --collection=blogs --id=<content_id>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
`--data` can point to either a plain CMS content body or a full content object. A plain content body may include a business field named `body`. The CLI treats JSON as a full content object only when it includes wrapper fields such as `id`, `slug`, `content_app_id`, `json_schema`, `status`, `sort`, or `tags`.
|
|
326
|
+
|
|
327
|
+
If your business JSON has a top-level `slug`, do not pass it as plain body JSON because `slug` is a content wrapper field. Either pass the slug as a flag and omit it from `--data`:
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
creght content create --site_id=<project_id>/<site_id> --collection=prompts --data=./content-body.json --slug=typography-v02
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Or use a full content object and put business fields under `body`:
|
|
334
|
+
|
|
335
|
+
```json
|
|
336
|
+
{
|
|
337
|
+
"slug": "typography-v02",
|
|
338
|
+
"body": {
|
|
339
|
+
"title": "Typography V.02",
|
|
340
|
+
"description": "100vh",
|
|
341
|
+
"tags": ["skill"]
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Manage Forms
|
|
347
|
+
|
|
348
|
+
List, create, update, and delete forms:
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
creght form list --site_id=<project_id>/<site_id>
|
|
352
|
+
creght form create --site_id=<project_id>/<site_id> --key=contact-form --name="Contact form" --schema=./contact.schema.json
|
|
353
|
+
creght form get --site_id=<project_id>/<site_id> --key=contact-form
|
|
354
|
+
creght form update --site_id=<project_id>/<site_id> --key=contact-form --schema=./contact.schema.json
|
|
355
|
+
creght form delete --site_id=<project_id>/<site_id> --key=contact-form
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Inspect and delete form submissions:
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
creght form logs --site_id=<project_id>/<site_id> --key=contact-form
|
|
362
|
+
creght form log get --site_id=<project_id>/<site_id> --key=contact-form --log_id=<log_id>
|
|
363
|
+
creght form log delete --site_id=<project_id>/<site_id> --key=contact-form --log_id=<log_id>
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Submit a form payload through the platform API:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
creght form submit --site_id=<project_id>/<site_id> --key=contact-form --data=./payload.json
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
After creating or changing CMS collections or forms, run `creght pull` again to refresh generated files such as `/types/cms.d.ts` and `/types/form.d.ts` before writing code that imports those types.
|
|
373
|
+
|
|
374
|
+
## Upload Assets
|
|
375
|
+
|
|
376
|
+
Upload a local file through the Cregh site asset flow:
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
creght upload --site_id=<project_id>/<site_id> --file=./image.png
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
The command prints the public file URL by default. Use `--json` to inspect the
|
|
383
|
+
full upload metadata, including `site_path`, a stable `/_assets/...` path that
|
|
384
|
+
can be used from Cregh site code:
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
creght upload --site_id=<project_id>/<site_id> --file=./image.png --json
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Optional flags:
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
creght upload --site_id=<project_id>/<site_id> --file=./image.png --name=hero.png --mimetype=image/png
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Push And Sync Boundary
|
|
397
|
+
|
|
398
|
+
The current MVP push/sync mode is one-way:
|
|
399
|
+
|
|
400
|
+
```text
|
|
401
|
+
local directory -> Cregh remote site
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
`push` fetches the remote file list to build the local path to remote file id
|
|
405
|
+
mapping, scans the local directory, uploads the current local snapshot, and then
|
|
406
|
+
exits.
|
|
407
|
+
|
|
408
|
+
`sync` is watch mode. It performs the same initial local snapshot push, then
|
|
409
|
+
keeps running and automatically listens for later local changes.
|
|
410
|
+
|
|
411
|
+
Neither command pulls Web editor changes back to the local directory while
|
|
412
|
+
running. If you edit the same site in the Web editor, run `pull` manually or
|
|
413
|
+
restart from a clean local copy before continuing.
|
|
414
|
+
|
|
415
|
+
Use a test project/site while validating the CLI. Do not run `push` or `sync`
|
|
416
|
+
against production content unless the local directory is intended to be the
|
|
417
|
+
source of truth.
|
|
418
|
+
|
|
419
|
+
## Commands
|
|
420
|
+
|
|
421
|
+
Cregh CLI is a local bridge for Cregh site code. It can authenticate with
|
|
422
|
+
Cregh, list projects and sites, pull remote site files into a local directory,
|
|
423
|
+
push local files back to Cregh, watch local files for realtime sync, open the
|
|
424
|
+
remote preview, and publish a site.
|
|
425
|
+
|
|
426
|
+
The CLI commands still use the Cregh backend and web app for the canonical
|
|
427
|
+
preview. The Vite plugin is a local development helper and intentionally does
|
|
428
|
+
not implement full production SSR.
|
|
429
|
+
|
|
430
|
+
```bash
|
|
431
|
+
creght login [--web=https://creght.cn]
|
|
432
|
+
creght logout
|
|
433
|
+
creght project list
|
|
434
|
+
creght pull --site_id=<project_id>/<site_id> --dir=./mysite
|
|
435
|
+
creght push --site_id=<project_id>/<site_id> --dir=./mysite
|
|
436
|
+
creght sync --site_id=<project_id>/<site_id> --dir=./mysite
|
|
437
|
+
creght dev --site_id=<project_id>/<site_id> --dir=./mysite [--web=https://creght.cn]
|
|
438
|
+
creght preview --site_id=<project_id>/<site_id>
|
|
439
|
+
creght publish --site_id=<project_id>/<site_id> [--note=<note>]
|
|
440
|
+
creght cms collections --site_id=<project_id>/<site_id>
|
|
441
|
+
creght cms collection create --site_id=<project_id>/<site_id> --key=<key> --name=<name> --schema=./schema.json
|
|
442
|
+
creght content list --site_id=<project_id>/<site_id> --collection=<key>
|
|
443
|
+
creght content create --site_id=<project_id>/<site_id> --collection=<key> --data=./content.json
|
|
444
|
+
creght form list --site_id=<project_id>/<site_id>
|
|
445
|
+
creght form create --site_id=<project_id>/<site_id> --key=<key> --name=<name> --schema=./schema.json
|
|
446
|
+
creght upload --site_id=<project_id>/<site_id> --file=./image.png
|
|
447
|
+
creght version
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Command meanings:
|
|
451
|
+
|
|
452
|
+
- `login`: Authenticate this machine with Cregh and save a CLI token.
|
|
453
|
+
- `logout`: Remove the saved CLI token and API host configuration.
|
|
454
|
+
- `project`: List available projects and sites. Use `project_id/site_id` with site commands. Also supports `project create`.
|
|
455
|
+
- `pull`: Download the current remote site files into a local directory.
|
|
456
|
+
- `push`: Push the current local directory snapshot to the remote site.
|
|
457
|
+
- `sync`: Watch mode; push the current snapshot, then keep listening for local changes.
|
|
458
|
+
- `dev`: Bidirectionally sync local files with cloud realtime files and the online Web editor.
|
|
459
|
+
- `preview`: Open the remote preview URL for a site in the browser.
|
|
460
|
+
- `publish`: Publish a site to make the current remote site version live.
|
|
461
|
+
- `cms`: Manage CMS collections.
|
|
462
|
+
- `content`: Manage CMS content entries.
|
|
463
|
+
- `form`: Manage forms and form submissions.
|
|
464
|
+
- `upload`: Upload a local file as a Cregh site asset and print its URL.
|
|
465
|
+
- `version`: Print the installed CLI version.
|
|
466
|
+
|
|
467
|
+
## Release
|
|
468
|
+
|
|
469
|
+
GitHub Releases are created by GitHub Actions when a tag matching `v*` is pushed.
|
|
470
|
+
The same workflow publishes the npm package `creght-cli`.
|
|
471
|
+
|
|
472
|
+
The release workflow builds binaries for:
|
|
473
|
+
|
|
474
|
+
- macOS: `darwin/amd64`, `darwin/arm64`
|
|
475
|
+
- Linux: `linux/amd64`, `linux/arm64`
|
|
476
|
+
- Windows: `windows/amd64`, `windows/arm64`
|
|
477
|
+
|
|
478
|
+
Create and push a release tag:
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
git tag v0.1.0
|
|
482
|
+
git push origin v0.1.0
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Before pushing a release tag, make sure `package.json` has the same version as the
|
|
486
|
+
tag without the leading `v`, and configure npm Trusted Publishing for `creght-cli`
|
|
487
|
+
with GitHub repository `creght/creght-cli` and workflow filename `release.yml`.
|
|
488
|
+
|
|
489
|
+
If this repository is mirrored to GitHub with a different remote name, push the tag to that remote:
|
|
490
|
+
|
|
491
|
+
```bash
|
|
492
|
+
git remote add github git@github.com:creght-dev/creght-cli.git
|
|
493
|
+
git push github main
|
|
494
|
+
git push github v0.1.0
|
|
495
|
+
```
|
package/bin/creght.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require("node:child_process");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
|
|
6
|
+
const platform = process.platform;
|
|
7
|
+
const arch = process.arch;
|
|
8
|
+
const exe = process.platform === "win32" ? "creght.exe" : "creght";
|
|
9
|
+
const binary = path.join(__dirname, "..", "vendor", `${platform}-${arch}`, exe);
|
|
10
|
+
|
|
11
|
+
const result = spawnSync(binary, process.argv.slice(2), {
|
|
12
|
+
stdio: "inherit",
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
if (result.error) {
|
|
16
|
+
if (result.error.code === "ENOENT") {
|
|
17
|
+
console.error(
|
|
18
|
+
"Cregh CLI binary is missing. Reinstall creght-cli and try again.",
|
|
19
|
+
);
|
|
20
|
+
} else {
|
|
21
|
+
console.error(result.error.message);
|
|
22
|
+
}
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (typeof result.status === "number") {
|
|
27
|
+
process.exit(result.status);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
process.exit(result.signal ? 1 : 0);
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "creght-cli",
|
|
3
|
+
"version": "0.1.16",
|
|
4
|
+
"description": "Cregh CLI for syncing local site code with Cregh.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/creght-dev/creght-cli#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/creght-dev/creght-cli.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/creght-dev/creght-cli/issues"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"creght": "bin/creght.js"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
"./vite": {
|
|
19
|
+
"types": "./vite/index.d.ts",
|
|
20
|
+
"import": "./vite/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"prepare:npm-binaries": "node scripts/prepare-npm-binaries.js",
|
|
25
|
+
"test:npm": "node scripts/install.js --check"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"bin/",
|
|
29
|
+
"scripts/",
|
|
30
|
+
"vite/",
|
|
31
|
+
"vendor/",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18"
|
|
36
|
+
},
|
|
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
|
+
"os": [
|
|
50
|
+
"darwin",
|
|
51
|
+
"linux",
|
|
52
|
+
"win32"
|
|
53
|
+
],
|
|
54
|
+
"cpu": [
|
|
55
|
+
"x64",
|
|
56
|
+
"arm64"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
|
|
6
|
+
const packageRoot = path.join(__dirname, "..");
|
|
7
|
+
const platform = process.platform;
|
|
8
|
+
const arch = process.arch;
|
|
9
|
+
const exe = platform === "win32" ? "creght.exe" : "creght";
|
|
10
|
+
const binary = path.join(packageRoot, "vendor", `${platform}-${arch}`, exe);
|
|
11
|
+
const checkOnly = process.argv.includes("--check");
|
|
12
|
+
|
|
13
|
+
if (checkOnly) {
|
|
14
|
+
const exists = fs.existsSync(binary);
|
|
15
|
+
console.log(`Cregh CLI binary path for this platform: ${binary}`);
|
|
16
|
+
if (!exists) {
|
|
17
|
+
console.log("Binary is not present in this local checkout. CI adds release binaries before npm publish.");
|
|
18
|
+
}
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!fs.existsSync(binary)) {
|
|
23
|
+
console.error(
|
|
24
|
+
`Cregh CLI binary is missing for ${platform}-${arch}. Reinstall creght-cli and try again.`,
|
|
25
|
+
);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const os = require("node:os");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const { spawnSync } = require("node:child_process");
|
|
7
|
+
|
|
8
|
+
const packageRoot = path.join(__dirname, "..");
|
|
9
|
+
const distDir = path.join(packageRoot, "dist");
|
|
10
|
+
const vendorDir = path.join(packageRoot, "vendor");
|
|
11
|
+
const version = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8")).version;
|
|
12
|
+
|
|
13
|
+
const targets = [
|
|
14
|
+
{ goos: "darwin", goarch: "amd64", npm: "darwin-x64", ext: "tar.gz", exe: "creght" },
|
|
15
|
+
{ goos: "darwin", goarch: "arm64", npm: "darwin-arm64", ext: "tar.gz", exe: "creght" },
|
|
16
|
+
{ goos: "linux", goarch: "amd64", npm: "linux-x64", ext: "tar.gz", exe: "creght" },
|
|
17
|
+
{ goos: "linux", goarch: "arm64", npm: "linux-arm64", ext: "tar.gz", exe: "creght" },
|
|
18
|
+
{ goos: "windows", goarch: "amd64", npm: "win32-x64", ext: "zip", exe: "creght.exe" },
|
|
19
|
+
{ goos: "windows", goarch: "arm64", npm: "win32-arm64", ext: "zip", exe: "creght.exe" },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
fs.rmSync(vendorDir, { recursive: true, force: true });
|
|
23
|
+
fs.mkdirSync(vendorDir, { recursive: true });
|
|
24
|
+
|
|
25
|
+
for (const target of targets) {
|
|
26
|
+
const archive = path.join(distDir, `creght_${version}_${target.goos}_${target.goarch}.${target.ext}`);
|
|
27
|
+
if (!fs.existsSync(archive)) {
|
|
28
|
+
throw new Error(`missing release archive: ${archive}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "creght-npm-"));
|
|
32
|
+
try {
|
|
33
|
+
extract(archive, tmpDir, target.ext);
|
|
34
|
+
const source = path.join(tmpDir, target.exe);
|
|
35
|
+
if (!fs.existsSync(source)) {
|
|
36
|
+
throw new Error(`archive did not contain ${target.exe}: ${archive}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const targetDir = path.join(vendorDir, target.npm);
|
|
40
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
41
|
+
const destination = path.join(targetDir, target.exe);
|
|
42
|
+
fs.copyFileSync(source, destination);
|
|
43
|
+
if (target.exe === "creght") {
|
|
44
|
+
fs.chmodSync(destination, 0o755);
|
|
45
|
+
}
|
|
46
|
+
} finally {
|
|
47
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function extract(archive, destination, ext) {
|
|
52
|
+
const command = ext === "zip" ? "unzip" : "tar";
|
|
53
|
+
const args = ext === "zip" ? ["-q", archive, "-d", destination] : ["-xzf", archive, "-C", destination];
|
|
54
|
+
const result = spawnSync(command, args, { stdio: "inherit" });
|
|
55
|
+
|
|
56
|
+
if (result.error) {
|
|
57
|
+
throw result.error;
|
|
58
|
+
}
|
|
59
|
+
if (result.status !== 0) {
|
|
60
|
+
throw new Error(`${command} exited with status ${result.status}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,55 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Plugin } from 'vite'
|
|
2
|
+
|
|
3
|
+
export type CreghVitePluginOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* Local Cregh project root. Defaults to Vite's root.
|
|
6
|
+
*/
|
|
7
|
+
root?: string
|
|
8
|
+
/**
|
|
9
|
+
* Cregh 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?: CreghVitePluginOptions): Plugin
|
|
28
|
+
export default creght
|
package/vite/index.js
ADDED
|
@@ -0,0 +1,546 @@
|
|
|
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('&', '&')
|
|
36
|
+
.replaceAll('<', '<')
|
|
37
|
+
.replaceAll('>', '>')
|
|
38
|
+
.replaceAll('"', '"')
|
|
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 Cregh 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(`Cregh 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('Cregh 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 Cregh 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 === '/' ? 'Cregh' : 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)}¶ms=${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 sendCreghUpdate = (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', sendCreghUpdate)
|
|
433
|
+
server.watcher.on('add', sendCreghUpdate)
|
|
434
|
+
server.watcher.on('unlink', sendCreghUpdate)
|
|
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
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
})
|