@kedge/cli 0.10.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/kedge-manifest.json +43 -33
- package/package.json +5 -5
- package/src/check.ts +4 -2
- package/src/commands.ts +63 -16
- package/src/d1-batch.ts +3 -1
- package/src/keys-save.ts +49 -4
- package/src/local-state.ts +25 -13
- package/src/main.ts +21 -3
- package/src/output.ts +18 -9
- package/src/preview.ts +3 -1
- package/src/smoke.ts +29 -21
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# @kedge/cli
|
|
2
|
+
|
|
3
|
+
`kedge`, the command line of a [Kedge](https://www.npmjs.com/package/create-kedge)
|
|
4
|
+
store. A new store comes from `npm create kedge@latest`; this package is one of
|
|
5
|
+
its dependencies, run as `pnpm kedge <command>`.
|
|
6
|
+
|
|
7
|
+
| Command | Does |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `kedge dev` | The store on this machine, its admin at `/app/` |
|
|
10
|
+
| `kedge check` | Typecheck, `generate --check`, `doctor`, then the store's tests |
|
|
11
|
+
| `kedge doctor [--remote]` | The store's checks; `--remote` adds its Cloudflare credentials and resources |
|
|
12
|
+
| `kedge generate` | Compose migrations and the lock from core, plugins and `local/` |
|
|
13
|
+
| `kedge preview` | An isolated copy on Cloudflare, with `test/scenarios` run against it |
|
|
14
|
+
| `kedge deploy` | Production: migrations first, then the Worker, under one recorded operation |
|
|
15
|
+
| `kedge staff invite-owner <email>` | The first Owner's invite link (`--remote` for Cloudflare) |
|
|
16
|
+
| `kedge keys create … --save` | A storefront or secret key, saved to the store's env file |
|
|
17
|
+
| `kedge seed --sample apparel --url <url>` | Sample products |
|
|
18
|
+
| `kedge smoke --url <url>` | A test order, placed; run again, the same order |
|
|
19
|
+
| `kedge upgrade --to <version>` | Every `@kedge` package to a release, with its codemods |
|
|
20
|
+
|
|
21
|
+
**For agents.** Exit `0` done, `1` refused or failed (the reason on stderr), `2`
|
|
22
|
+
usage, `3` a person must act: the last line of stderr starts `for you:` and is one
|
|
23
|
+
sentence to pass on. Run the same command again after they do; every command is
|
|
24
|
+
safe to repeat. With `--json`, stdout is one object, `{"ok": true, ...}`,
|
|
25
|
+
`{"ok": false, "error"}` or `{"ok": false, "handoff"}`.
|
|
26
|
+
|
|
27
|
+
Cloudflare credentials come from the environment, then the store's `.env.local`,
|
|
28
|
+
then `wrangler login`; `CLOUDFLARE_API_BASE_URL` is honoured for every call.
|
package/kedge-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.10.
|
|
3
|
-
"upgradesFrom": "0.
|
|
2
|
+
"version": "0.10.1",
|
|
3
|
+
"upgradesFrom": "0.10.0",
|
|
4
4
|
"doStateFormat": 1,
|
|
5
5
|
"migrations": {
|
|
6
6
|
"0000_schema.sql": {
|
|
@@ -143,7 +143,8 @@
|
|
|
143
143
|
"packages": {
|
|
144
144
|
"@kedge/core": {
|
|
145
145
|
"LICENSE": "5068e060c465b99dee8b5d7ccfecc51f395affd63d6b9651a2d31e71e3a0d9fb",
|
|
146
|
-
"
|
|
146
|
+
"README.md": "5cffa13dcc33f4c3c39adb020a8c589722a88ed904033de1f16385a8a3e67236",
|
|
147
|
+
"package.json": "9593f2661eb8c3b199da8260de0439e85a6a5bbd53c5f79904df19a9767b76f3",
|
|
147
148
|
"src/access/audit.ts": "374bf28051543e2cfc3a92f6dd3b27525562eb76d12d0628951da855323be120",
|
|
148
149
|
"src/access/context.ts": "bf4d50e4473ebe99aea6e6d673b0006595bb5bcf47e9f34c094fa4d9c6fea5e7",
|
|
149
150
|
"src/access/permissions.ts": "0e71620cf6dcddd414bdba4634986366f6c37c8d94f419c4fdd289131b5771da",
|
|
@@ -190,6 +191,7 @@
|
|
|
190
191
|
},
|
|
191
192
|
"@kedge/db": {
|
|
192
193
|
"LICENSE": "5068e060c465b99dee8b5d7ccfecc51f395affd63d6b9651a2d31e71e3a0d9fb",
|
|
194
|
+
"README.md": "02526e2c786cd6e421e6bbd3689cac5fe8641578092baf07902cd65d6d102569",
|
|
193
195
|
"migrations/0000_schema.sql": "31d99f65263f43480cc25e249197a6555e33166e28785cf0859b7cfd2bed53ad",
|
|
194
196
|
"migrations/0001_triggers.sql": "61f5a9a0aec224dc5b69778a4143f3476d5c814350979bfbbb9547269bd688f7",
|
|
195
197
|
"migrations/0002_stock_bounds.sql": "4e4b63d22aacceb196a2a54caadee9619d392b83b2eb82a3396871f0538d914b",
|
|
@@ -221,7 +223,7 @@
|
|
|
221
223
|
"migrations/0028_tracks_entities.sql": "bbdad5c12570bc528abd5b294fa4277b780940fcf773ea67403e9b2324321c12",
|
|
222
224
|
"migrations/0029_cli_session.sql": "a883b970c2820a84ac3e0c65530681df488ecc0546ebf29a43bc9a5778e8c1f9",
|
|
223
225
|
"migrations/0030_credential_ref.sql": "b06cee4958d222a93d0af1b6a57e260beb98a92a9a1b2ba4c7a895fc40b79201",
|
|
224
|
-
"package.json": "
|
|
226
|
+
"package.json": "311c0d8fa2fb339c4e8bda31d005e68233c5fd1f83ab116af5921e9cccd55792",
|
|
225
227
|
"src/access-sql.ts": "51481f129f4614ba770218526048042aa0d118274ce7a737e0584037ea59dff0",
|
|
226
228
|
"src/access.ts": "933e0fcaed1d0aca624df7556f3aab7368aa33793619b29e3ad10f610842db44",
|
|
227
229
|
"src/admin.ts": "694d508e2bb51eda9134d163b049240688d5fdcd6783a66e2ae18bf9da21d140",
|
|
@@ -262,7 +264,8 @@
|
|
|
262
264
|
},
|
|
263
265
|
"@kedge/server": {
|
|
264
266
|
"LICENSE": "5068e060c465b99dee8b5d7ccfecc51f395affd63d6b9651a2d31e71e3a0d9fb",
|
|
265
|
-
"
|
|
267
|
+
"README.md": "1fb1bcd08cc11851accb343d6df724383706cf9324d58785d097431476524b26",
|
|
268
|
+
"package.json": "44dc6665b926a512f039e1b58b22530ba2300d39761e9adc7766f3e41b5832e2",
|
|
266
269
|
"src/access.ts": "224341803cb2448ab9458c4757035abc7344c0748e261ffcf18a6b072c3ebfa0",
|
|
267
270
|
"src/admin-routes.ts": "9c1f13283f1b11709c0d27512205d9ead59a9042a4df36fa1bec344184ad7d0e",
|
|
268
271
|
"src/admin-shell.ts": "3b94e14e3cd8303fd7f3153a65ce7c3339203b58f74656c40839587ca996a7c3",
|
|
@@ -284,7 +287,8 @@
|
|
|
284
287
|
},
|
|
285
288
|
"@kedge/runtime-cf": {
|
|
286
289
|
"LICENSE": "5068e060c465b99dee8b5d7ccfecc51f395affd63d6b9651a2d31e71e3a0d9fb",
|
|
287
|
-
"
|
|
290
|
+
"README.md": "b678c333e82364c5e9349bb1aa4f64d1479678893b471ddc0c4ea41b769adef9",
|
|
291
|
+
"package.json": "0ee37754d1a8d0d663927c7504193befbc302c0ff63e0609eb6729e9371860db",
|
|
288
292
|
"src/admin.ts": "2c005857fe2928b4e82777590f13f7a83e26edfa30b24f5463ee0c80932b2ca0",
|
|
289
293
|
"src/cart-do.ts": "d7be874e440d41b716a96607341b82b0992a9da1656456322fca3634f6be8529",
|
|
290
294
|
"src/clock-do.ts": "8d68b0ba01d16763fe7d13feee71d315bbc1ebbee7e5f8694a9d186c4ccab3a5",
|
|
@@ -310,12 +314,14 @@
|
|
|
310
314
|
},
|
|
311
315
|
"@kedge/plugin-sdk": {
|
|
312
316
|
"LICENSE": "5068e060c465b99dee8b5d7ccfecc51f395affd63d6b9651a2d31e71e3a0d9fb",
|
|
313
|
-
"
|
|
317
|
+
"README.md": "685a9eede5493ce08535b0525cf30b1290d48ba9e75b3662239fb5b6e08d3baa",
|
|
318
|
+
"package.json": "c0ed2f482fdac69c049eb76eee53f5ee1ad9918dbd33714515e09a91ac214fd0",
|
|
314
319
|
"src/index.ts": "cb2aa938e2065f6ca5f01a470b421ec0ca20599f149114051823e2510a76d11b"
|
|
315
320
|
},
|
|
316
321
|
"@kedge/testing": {
|
|
317
322
|
"LICENSE": "5068e060c465b99dee8b5d7ccfecc51f395affd63d6b9651a2d31e71e3a0d9fb",
|
|
318
|
-
"
|
|
323
|
+
"README.md": "94da183c9b4fdb9617dbe93cfb169b81ce4bd6ed7c9a364d6d6ad3e62578d9cc",
|
|
324
|
+
"package.json": "1cd6b1b4d32fb197fc6a11ba1c7531e20e56cfb58245cb70a38937579442a77e",
|
|
319
325
|
"src/budgets.ts": "4ffb780a7543d1d2c7c7e5dca7a2d4cdb363e3b0ee2801a928d928da8088e525",
|
|
320
326
|
"src/http.ts": "59c4bb9d3df6885333103a05cddde0d03cc70008a416c99905422fbead034ee7",
|
|
321
327
|
"src/index.ts": "c3705394753e6c6ecf3abfd19a3b1e672c61c33bda4ff8f2d54ece10f506b5cd",
|
|
@@ -327,6 +333,7 @@
|
|
|
327
333
|
},
|
|
328
334
|
"@kedge/docs": {
|
|
329
335
|
"LICENSE": "5068e060c465b99dee8b5d7ccfecc51f395affd63d6b9651a2d31e71e3a0d9fb",
|
|
336
|
+
"README.md": "e131075a2958e014a2c2dcaa33882d04deeadedd52d2f692a04fe7d1af669905",
|
|
330
337
|
"extending/admin.md": "4514c38097dc5442b1ec6761c337689dd1f0c76b1320184c1417c4bb34704464",
|
|
331
338
|
"extending/contributions.md": "2f970bd64f1e131a408c494e867060907e8d2c83b9f89efd02ae4618aa49be5b",
|
|
332
339
|
"extending/custom-fields.md": "ccdd0d83b8c54d72992b43cad3a7742c42b65009b863b44c71006ad7255fb900",
|
|
@@ -339,15 +346,16 @@
|
|
|
339
346
|
"extending/timers.md": "6da7fc0a8eee314df7158df16e5670d194842d5de452032ba8be414b5dac4e7e",
|
|
340
347
|
"extending/tracks.md": "3d85cd54a5060c35fe70b3bbea848c16ffd0a4e2e21e590b104408690a721ac6",
|
|
341
348
|
"index.json": "4d0f56f56cd470c78e93c9b26ce251ee89fdb624dfd20febb1fc411b398182e9",
|
|
342
|
-
"package.json": "
|
|
349
|
+
"package.json": "df6d4c464cc5256ad388a943d959d4792373379148fdf235f25a5e759bcfc7a9",
|
|
343
350
|
"reference/api.md": "114830d766ab3b81a5181f7b07b10012ac5ee15898c87fa9515c71cec60ec374",
|
|
344
351
|
"reference/errors.md": "c4fa0dcb4d79d5a4888d7fc3ee525a045b21d3cb3ac158f5172dbb02880979f2",
|
|
345
352
|
"skills/kedge-extend/SKILL.md": "7ce215445e99d87e401b7f2669c184f3fa263b75aabced2cdf5eefb398043013",
|
|
346
353
|
"skills/kedge-plugin/SKILL.md": "21af3f112cfcdf2a1cd2eb8e9148a0558a4aebf29b1a057cb5ece09af8116d99",
|
|
347
354
|
"skills/kedge-preview-deploy/SKILL.md": "4b794f84b8f639aff97f4d5e28c44ec8b44de00f6b7b5dc25500dff722891480",
|
|
348
|
-
"skills/kedge-start/SKILL.md": "
|
|
355
|
+
"skills/kedge-start/SKILL.md": "04eed3cc12d66eaf1531b26671b0e90d44fd4eb81b394bb00d3e6a4e8c97e3c5",
|
|
349
356
|
"skills/kedge-upgrade/SKILL.md": "97cba305676a22696d6d4e25d2c5e823b47fc0be04b12813d17b9dfd3c5a6c2e",
|
|
350
357
|
"upgrading/0.10.0.md": "169efc96cdf80af167df6607c0db4d977e29c135858ba0c61f53d50d9facc676",
|
|
358
|
+
"upgrading/0.10.1.md": "d47ddc49f1724e0f3be55a7136d75cbcd8f0bf1128cc7ed2aa6e17cf884ac893",
|
|
351
359
|
"upgrading/0.8.0.md": "b0ddd26993048ea1fdd0e76fb2707060cc4032ffb199a691278020361be95cf8",
|
|
352
360
|
"upgrading/0.9.0.md": "d632d6143636351289684dd51dd51eb0be3e76975c4a42d65acdcee04549d21d",
|
|
353
361
|
"upgrading/codemods/add-admin-assets.mjs": "fa5d069a3489889dcc095b309020d0e773004b3a39544457747491963492f0a1",
|
|
@@ -355,55 +363,57 @@
|
|
|
355
363
|
},
|
|
356
364
|
"@kedge/admin": {
|
|
357
365
|
"LICENSE": "5068e060c465b99dee8b5d7ccfecc51f395affd63d6b9651a2d31e71e3a0d9fb",
|
|
366
|
+
"README.md": "1ce1a6d2a16995f6342c9bcc40ddbac0e2bc35cdcdf1ad24efe28aa5b85d79a3",
|
|
358
367
|
"dist/_headers": "b1d979b2fdf27af6c0f41fdaee420f2a2b192f28dc855fec7491f8ffa685c9ba",
|
|
359
|
-
"dist/app/assets/account-
|
|
368
|
+
"dist/app/assets/account-pLgh_XLx.js": "cee500d077ab53d4607358d76d2f3d12e5b8f3fe0950023b104bbd32094e55d4",
|
|
360
369
|
"dist/app/assets/card-CH9fCW35.js": "995ee7034e88909a04b07d559a7f520fb08df647e960606f54f5ab88c7201a3f",
|
|
361
370
|
"dist/app/assets/display-CLAPxMXW.js": "4f4e029a0f62756c966c2da476c9f62b7bc59b521c84fa2af86b0f1e2ce51729",
|
|
362
|
-
"dist/app/assets/home-
|
|
371
|
+
"dist/app/assets/home-DrmfIxH8.js": "87154c23f458a95e797eca4189411a42b5ba6640a6fdc23270130c429940be87",
|
|
363
372
|
"dist/app/assets/index-BA_54h5V.css": "0d7763f516591b37628f3bd5cad733d786783e78a092ff4ad74f0a4045394c73",
|
|
364
|
-
"dist/app/assets/index-
|
|
365
|
-
"dist/app/assets/invite-
|
|
366
|
-
"dist/app/assets/resource-detail-
|
|
367
|
-
"dist/app/assets/resource-form-
|
|
368
|
-
"dist/app/assets/resource-list-
|
|
369
|
-
"dist/app/assets/setup-
|
|
370
|
-
"dist/app/assets/use-command-
|
|
371
|
-
"dist/app/assets/work-lists-
|
|
373
|
+
"dist/app/assets/index-CJIaoAxE.js": "e99daee039bd6873e4cf73eea6dce911eb01578926279223f1b538ef3302da74",
|
|
374
|
+
"dist/app/assets/invite-DtAspOzb.js": "aa238d84ae5c5c16b95337e5056ac1f59d37f87e2645f949ced15b94e892f205",
|
|
375
|
+
"dist/app/assets/resource-detail-Du1RdgVZ.js": "ebf6435a529b8675c118335900725e2ea91ec5125c52f3dd4c6dc278cf2c2f10",
|
|
376
|
+
"dist/app/assets/resource-form-EmoEfjrj.js": "1f8826d42e5fa918f0b23754f773a3307b37e178441d3b02885dfba7e5fff08e",
|
|
377
|
+
"dist/app/assets/resource-list-VLCxpKZl.js": "8b42f06d949b02f28dd30a2511b189b69f564b90ba803984015d348f80078287",
|
|
378
|
+
"dist/app/assets/setup-BiUpBePR.js": "d45e13f5eced59f064eb86e9dcb78661163f780d22c99770f0b1094fb4619e1b",
|
|
379
|
+
"dist/app/assets/use-command-CB6a5Dop.js": "de5ed878a74f8086d8db82597f0bae2fcd5181d06123834596bc03ec9600e042",
|
|
380
|
+
"dist/app/assets/work-lists-C3wqs0gp.js": "4b3a49ed9b62997fe0f9320b26c97b6b5fbb36bd46abb46a40b751b07a513f2d",
|
|
372
381
|
"dist/app/boot.js": "3099141f8935b0c2f15a041f205a673c977a9c8bfc7aa30f23a44b44a69bdb1d",
|
|
373
|
-
"dist/app/build.json": "
|
|
374
|
-
"dist/app/index.html": "
|
|
382
|
+
"dist/app/build.json": "ee4c28bdd6ba1b787b8f22d07f962cb81ceedba95e8f034138715b4088e8663c",
|
|
383
|
+
"dist/app/index.html": "7ede70cce6abcff8c1225ce961885a540206462300c5897a23f3cba80c7ee8db",
|
|
375
384
|
"dist/app/licenses.txt": "7a2bb91cbbc5e4201b72d4d95b2a4d7458f42e80a0d05b01398c9e93f5764f62",
|
|
376
|
-
"package.json": "
|
|
385
|
+
"package.json": "742f602ed4dce5f800505a9e9e9996240ab19462182c1213c96e5e0db54e922f",
|
|
377
386
|
"slots.json": "3252f255e1628537056317d74be5fd2db2fb7e283cd6e8d3f2d49e6dad4b15e5"
|
|
378
387
|
},
|
|
379
388
|
"@kedge/cli": {
|
|
380
389
|
"LICENSE": "5068e060c465b99dee8b5d7ccfecc51f395affd63d6b9651a2d31e71e3a0d9fb",
|
|
390
|
+
"README.md": "642be70aa940d01572dc5e91482353b3ecb9c24d4558f3014a4b93fb62a909fd",
|
|
381
391
|
"bin/kedge.mjs": "8279a888842a6ee6c0ae9e26fd4380b97892648ca94979fc69d505ff8af6aa9f",
|
|
382
|
-
"package.json": "
|
|
392
|
+
"package.json": "318bca59c7980a066581995a1f4cd22d59fbb652dd6603f7a45bb88453030618",
|
|
383
393
|
"src/cf.ts": "af5a7e281f628f07c46426c819da35b96d2662f275a196073b511966aa1786e6",
|
|
384
|
-
"src/check.ts": "
|
|
394
|
+
"src/check.ts": "273ff4b59d717aea33e166e4d88775f190fa826cc3432642a2ccfa2cad12ddd6",
|
|
385
395
|
"src/cli-session.ts": "2e666d1e64f1f3997b5923dc8803e8ddb84cc05c61593d22cbae19b148e69768",
|
|
386
396
|
"src/code-lint.ts": "a696fb8ce00a10da783c1d514a1afa1dd9306a9e04819ae36580c592b651a544",
|
|
387
|
-
"src/commands.ts": "
|
|
397
|
+
"src/commands.ts": "3f1d6ba7b1c68aa6125eb409c172b50688c1b77d8306b24c655f37487f008cf6",
|
|
388
398
|
"src/compose.ts": "bc67b22f814e54c0c27db88d776b135d3c7b26b55702f678e1f0253aaf50c943",
|
|
389
|
-
"src/d1-batch.ts": "
|
|
399
|
+
"src/d1-batch.ts": "dd4dd69778ccf47970e9fc80637b6655ccd94bffa96a6ff7de3dfa92dd668662",
|
|
390
400
|
"src/deploy.ts": "574c4338231bdc1cc7bfd2024e2e37689b2d75b2a3b822889a46a1fb9bba25e8",
|
|
391
401
|
"src/doctor.ts": "17db6b809e9e71ddb10bee6aa009c1f86beff3b5476629ec4ff8626e8c776b9f",
|
|
392
402
|
"src/fork.ts": "f3d9878f58098a563bf2abc76f9e3207514e81779d144831d2e0259c8f455656",
|
|
393
403
|
"src/index.ts": "1f1427679dce8ce228bbce9fcc9290382dd2027c5f2c25bf6a712de74d9793ab",
|
|
394
404
|
"src/invariants.ts": "177446f4144571345c6872f816a076dc88f5dc92dc0503281456f3a2164229ae",
|
|
395
|
-
"src/keys-save.ts": "
|
|
396
|
-
"src/local-state.ts": "
|
|
397
|
-
"src/main.ts": "
|
|
405
|
+
"src/keys-save.ts": "682e27974af1e0aba6127e70f73b99a848dae7baf31a6db1584d68d64f027581",
|
|
406
|
+
"src/local-state.ts": "bcfc69f6eb1b3ca134f428e27fe7dd38340755a0a37565587b5c88dfd99cae83",
|
|
407
|
+
"src/main.ts": "f0b3e2c4b30f9b1a525f5be092a31c6c6435b6be16c14124e379343e52ccfbbd",
|
|
398
408
|
"src/manifest.ts": "9d52eafd735f9e3d8978588f2b9fb712e17d0e78106170fdb8cd373f39920c87",
|
|
399
409
|
"src/n1.ts": "17b26eb4114dba819282c44c67ef8bf5fbfcc854674133bee5d87bd250ebdc39",
|
|
400
410
|
"src/ops.ts": "9eab0317f1f6b480b07498621f9d6b47fd8a22f24e417b83555b7fe0a4ef5649",
|
|
401
411
|
"src/order.ts": "faba43ade99bd2f966cbc40c3c5d780e6f47a8df06c3e45cc16629ac82d5407e",
|
|
402
|
-
"src/output.ts": "
|
|
412
|
+
"src/output.ts": "d599d8752695efc8b56919914f2fb7fc27b5f420d0c5f5d6df03b8416a404184",
|
|
403
413
|
"src/plugin-lint.ts": "b716e02ee5a2ebaac1ef0bee709d5417fa800aa7ab2b1d3ded1607739091584d",
|
|
404
|
-
"src/preview.ts": "
|
|
414
|
+
"src/preview.ts": "48e83edc7282053eef9be43644c96d885499605ff5305ac6c0c0adc10a171022",
|
|
405
415
|
"src/schema.ts": "d0b2b8b1bbb7a9ea1a53e092563ac62f0e98c8c1b27a5172d977e2f1c63453d1",
|
|
406
|
-
"src/smoke.ts": "
|
|
416
|
+
"src/smoke.ts": "dd722d21d319db8c75a826f799721ae371761497bb3524bf980249a05d94940c",
|
|
407
417
|
"src/sqlite-verify.ts": "1181960dfbbf5b94bd376c024a69d71aaa76bd69c6b92359713acf665e89c3b5",
|
|
408
418
|
"src/store-api.ts": "72a7ee732ba8b531c703df5880d701a305a9c1552a5a3fa631c8ea464d72c8ff",
|
|
409
419
|
"src/store.ts": "fcee4e1838b289c0a5875f63fd83610e46173edd8d189827ce6aa94723b97635",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kedge/cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"yaml": "2.9.1",
|
|
29
29
|
"@babel/parser": "7.29.9",
|
|
30
30
|
"wrangler": "4.152.0",
|
|
31
|
-
"@kedge/core": "0.10.
|
|
32
|
-
"@kedge/db": "0.10.
|
|
33
|
-
"@kedge/testing": "0.10.
|
|
34
|
-
"@kedge/runtime-cf": "0.10.
|
|
31
|
+
"@kedge/core": "0.10.1",
|
|
32
|
+
"@kedge/db": "0.10.1",
|
|
33
|
+
"@kedge/testing": "0.10.1",
|
|
34
|
+
"@kedge/runtime-cf": "0.10.1"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "24.19.2",
|
package/src/check.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { spawnSync } from "node:child_process";
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
7
|
import { join } from "node:path";
|
|
8
|
-
import { childStdio } from "./output";
|
|
8
|
+
import { childFailed, childStdio } from "./output";
|
|
9
9
|
|
|
10
10
|
export interface CheckInput {
|
|
11
11
|
cwd: string;
|
|
@@ -22,7 +22,9 @@ const runBin = (bin: string, args: string[], cwd: string): number => {
|
|
|
22
22
|
console.error(`${bin} isn't installed in this store: run pnpm install`);
|
|
23
23
|
return 1;
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
const r = spawnSync(path, args, { cwd, stdio: childStdio(), maxBuffer: 1 << 26 });
|
|
26
|
+
childFailed(r);
|
|
27
|
+
return r.status ?? 1;
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
export async function check(input: CheckInput): Promise<number> {
|
package/src/commands.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// acting on production.
|
|
7
7
|
import { spawnSync } from "node:child_process";
|
|
8
8
|
import { existsSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { createServer } from "node:net";
|
|
9
10
|
import { join, resolve } from "node:path";
|
|
10
11
|
import { pathToFileURL } from "node:url";
|
|
11
12
|
import { isPermission, type KedgeConfig } from "@kedge/core";
|
|
@@ -41,7 +42,7 @@ import { childStdio, emit } from "./output";
|
|
|
41
42
|
import { needsLive, smoke } from "./smoke";
|
|
42
43
|
import { git, storeNames } from "./store";
|
|
43
44
|
import { storeRequest } from "./store-api";
|
|
44
|
-
import { resolveTarget, type StoreTarget } from "./target";
|
|
45
|
+
import { devPort, resolveTarget, type StoreTarget } from "./target";
|
|
45
46
|
import { compileStoreZone } from "./tz";
|
|
46
47
|
|
|
47
48
|
export interface CommandDeps {
|
|
@@ -302,14 +303,8 @@ function adminUrl(cwd: string, url: string): string | null {
|
|
|
302
303
|
}
|
|
303
304
|
|
|
304
305
|
export async function migrateCommand(args: readonly string[], d: CommandDeps): Promise<number> {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const r = spawnSync(process.execPath, [wranglerBin(), "d1", "migrations", "apply", "DB", "--local"], {
|
|
308
|
-
cwd: d.cwd,
|
|
309
|
-
stdio: childStdio(),
|
|
310
|
-
});
|
|
311
|
-
return r.status ?? 1;
|
|
312
|
-
}
|
|
306
|
+
// Local: the dev database, through Wrangler's own bookkeeping.
|
|
307
|
+
if (!args.includes("--remote")) return migrateLocal(d);
|
|
313
308
|
return refusals(d, async () => (await deployer(d)).migrateRemote());
|
|
314
309
|
}
|
|
315
310
|
|
|
@@ -743,7 +738,41 @@ export function devArgs(cwd: string): string[] {
|
|
|
743
738
|
return id ? ["--define", `KEDGE_ADMIN_BUILD:${JSON.stringify(id)}`] : [];
|
|
744
739
|
}
|
|
745
740
|
|
|
746
|
-
|
|
741
|
+
/**
|
|
742
|
+
* The local database's migrations, through Wrangler's own bookkeeping, in one
|
|
743
|
+
* line: Wrangler prints its whole status table again after each migration,
|
|
744
|
+
* thousands of lines for a new store (p0.10.1 §1, 8). Its full output is shown
|
|
745
|
+
* only when it fails.
|
|
746
|
+
*/
|
|
747
|
+
export function migrateLocal(d: Pick<CommandDeps, "cwd" | "log" | "error">): number {
|
|
748
|
+
const r = spawnSync(process.execPath, [wranglerBin(), "d1", "migrations", "apply", "DB", "--local"], {
|
|
749
|
+
cwd: d.cwd,
|
|
750
|
+
encoding: "utf8",
|
|
751
|
+
env: { ...process.env, WRANGLER_SEND_METRICS: "false" },
|
|
752
|
+
maxBuffer: 1 << 26,
|
|
753
|
+
});
|
|
754
|
+
const out = `${r.stdout ?? ""}${r.stderr ?? ""}`;
|
|
755
|
+
if (r.status !== 0) {
|
|
756
|
+
(d.error ?? console.error)(out.trim().split("\n").slice(-40).join("\n"));
|
|
757
|
+
return r.status ?? 1;
|
|
758
|
+
}
|
|
759
|
+
const n = new Set(out.match(/\b\d{4}_[a-z0-9_]+\.sql\b/g) ?? []).size;
|
|
760
|
+
(d.log ?? console.log)(
|
|
761
|
+
n ? `applied ${n} migration${n === 1 ? "" : "s"} to the local database` : "the local database is up to date",
|
|
762
|
+
);
|
|
763
|
+
return 0;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/** Whether something already listens on the port: Wrangler would otherwise move to another, silently. */
|
|
767
|
+
function portTaken(port: number): Promise<boolean> {
|
|
768
|
+
return new Promise((resolve) => {
|
|
769
|
+
const server = createServer();
|
|
770
|
+
server.once("error", () => resolve(true));
|
|
771
|
+
server.listen(port, "127.0.0.1", () => server.close(() => resolve(false)));
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
export async function devCommand(d: CommandDeps): Promise<number> {
|
|
747
776
|
const vars = join(d.cwd, ".dev.vars");
|
|
748
777
|
if (!existsSync(vars)) {
|
|
749
778
|
const lines = [
|
|
@@ -759,11 +788,16 @@ export function devCommand(d: CommandDeps): number {
|
|
|
759
788
|
});
|
|
760
789
|
(d.log ?? console.log)("wrote .dev.vars with fresh local secrets");
|
|
761
790
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
791
|
+
// One port for dev, its invite link and the CLI's local target (p0.10.1 §3): taken is a refusal, never a move.
|
|
792
|
+
const port = devPort(d.cwd);
|
|
793
|
+
if (await portTaken(port)) {
|
|
794
|
+
(d.error ?? console.error)(
|
|
795
|
+
`port ${port} is in use: stop what's on it, or set "dev": { "port": <another> } in wrangler.jsonc`,
|
|
796
|
+
);
|
|
797
|
+
return 1;
|
|
798
|
+
}
|
|
799
|
+
const migrated = migrateLocal(d);
|
|
800
|
+
if (migrated !== 0) return migrated;
|
|
767
801
|
let extra: string[];
|
|
768
802
|
try {
|
|
769
803
|
extra = devArgs(d.cwd);
|
|
@@ -771,7 +805,20 @@ export function devCommand(d: CommandDeps): number {
|
|
|
771
805
|
(d.log ?? console.log)((e as Error).message);
|
|
772
806
|
return 1;
|
|
773
807
|
}
|
|
774
|
-
|
|
808
|
+
const origin = `http://localhost:${port}`;
|
|
809
|
+
// Where the store and its admin are, and how a person signs in to a new store's admin (p0.10.1 §1, 5).
|
|
810
|
+
(d.log ?? console.log)(
|
|
811
|
+
[
|
|
812
|
+
`the store: ${origin} its admin: ${origin}/app/`,
|
|
813
|
+
"no one can sign in to the admin until there's an Owner: pnpm kedge staff invite-owner <your email>",
|
|
814
|
+
].join("\n"),
|
|
815
|
+
);
|
|
816
|
+
return (
|
|
817
|
+
spawnSync(process.execPath, [wranglerBin(), "dev", "--port", String(port), ...extra], {
|
|
818
|
+
cwd: d.cwd,
|
|
819
|
+
stdio: "inherit",
|
|
820
|
+
}).status ?? 0
|
|
821
|
+
);
|
|
775
822
|
}
|
|
776
823
|
|
|
777
824
|
export const removeIfExists = (p: string) => rmSync(p, { recursive: true, force: true });
|
package/src/d1-batch.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { tmpdir } from "node:os";
|
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import type { CliBatch } from "@kedge/db";
|
|
9
9
|
import { wranglerBin } from "./cf";
|
|
10
|
-
import { childStdio } from "./output";
|
|
10
|
+
import { childFailed, childStdio } from "./output";
|
|
11
11
|
|
|
12
12
|
/** Runs one CLI batch against the store's D1; tests replace it. */
|
|
13
13
|
export type Executor = (
|
|
@@ -56,8 +56,10 @@ export const wranglerExecute: Executor = (b, opts) => {
|
|
|
56
56
|
cwd: opts.cwd,
|
|
57
57
|
stdio: childStdio(),
|
|
58
58
|
env: bootstrapEnv(process.env, dir, opts.env),
|
|
59
|
+
maxBuffer: 1 << 26,
|
|
59
60
|
},
|
|
60
61
|
);
|
|
62
|
+
childFailed(r);
|
|
61
63
|
return r.status ?? 1;
|
|
62
64
|
} finally {
|
|
63
65
|
// The file holds no secret (only digests), but it needn't outlive the run.
|
package/src/keys-save.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { DatabaseSync } from "node:sqlite";
|
|
|
11
11
|
import { NeedsPerson } from "./cf";
|
|
12
12
|
import { OLDER_KEDGE, type SessionDeps, withCliSession } from "./cli-session";
|
|
13
13
|
import { DeployRefused } from "./deploy";
|
|
14
|
-
import { type RefRecord, readEnvLocal, saveEnv, stateDb, withStoreLock } from "./local-state";
|
|
14
|
+
import { ENV_FILE, type RefRecord, readEnvLocal, saveEnv, stateDb, withStoreLock } from "./local-state";
|
|
15
15
|
import { storeRequest } from "./store-api";
|
|
16
16
|
import type { StoreTarget } from "./target";
|
|
17
17
|
|
|
@@ -85,7 +85,10 @@ async function save(db: DatabaseSync, target: StoreTarget, wanted: Wanted, d: Sa
|
|
|
85
85
|
// digest, never by a label beside it: a key with no such ref, or one for another store, would be sent
|
|
86
86
|
// to the wrong store or relabelled as this one's (codex code r11, r13). So any such key, of either
|
|
87
87
|
// kind, is the person's to remove before anything is saved beside it.
|
|
88
|
-
|
|
88
|
+
moveLocalBundle(db, d);
|
|
89
|
+
// Each store's keys in its own file (p0.10.1 §4): kedge dev's apart from Cloudflare's.
|
|
90
|
+
const envFile = target.kind === "local" ? ENV_FILE.local : ENV_FILE.production;
|
|
91
|
+
const env = readEnvLocal(d.cwd, envFile);
|
|
89
92
|
const all = db.prepare("SELECT * FROM credential_ref").all() as unknown as RefRecord[];
|
|
90
93
|
const foreign: string[] = [];
|
|
91
94
|
for (const name of [ENV.publishable, ENV.secret]) {
|
|
@@ -104,7 +107,7 @@ async function save(db: DatabaseSync, target: StoreTarget, wanted: Wanted, d: Sa
|
|
|
104
107
|
if (sf && !all.some((r) => r.store === store && r.storefront_id === sf)) foreign.push(ENV.storefront);
|
|
105
108
|
if (foreign.length)
|
|
106
109
|
throw new NeedsPerson(
|
|
107
|
-
|
|
110
|
+
`${envFile} holds ${foreign.join(", ")} that this checkout didn't make for this store: remove ${foreign.length === 1 ? "that line" : "those lines"}, or save this store's keys in its own checkout, then run this again`,
|
|
108
111
|
);
|
|
109
112
|
const saved = env[envName];
|
|
110
113
|
if (saved && (await works(target, wanted, saved, d))) {
|
|
@@ -172,7 +175,7 @@ async function save(db: DatabaseSync, target: StoreTarget, wanted: Wanted, d: Sa
|
|
|
172
175
|
// 4. Saved before anything else happens, then marked saved.
|
|
173
176
|
const values: Record<string, string> = { [ENV.store]: store, [envName]: made.key };
|
|
174
177
|
if (made.storefront_id) values[ENV.storefront] = made.storefront_id;
|
|
175
|
-
saveEnv(d.cwd, values);
|
|
178
|
+
saveEnv(d.cwd, values, envFile);
|
|
176
179
|
set(ref, { state: "saved" });
|
|
177
180
|
d.log(`saved ${Object.keys(values).join(", ")} to .env.local`);
|
|
178
181
|
return values;
|
|
@@ -254,6 +257,48 @@ export function badOrigins(origins: readonly string[]): string[] {
|
|
|
254
257
|
});
|
|
255
258
|
}
|
|
256
259
|
|
|
260
|
+
/**
|
|
261
|
+
* A 0.10.0 checkout saved `kedge dev`'s keys in `.env.local`, where they'd block
|
|
262
|
+
* Cloudflare's (p0.10.1 §4). Under the lock, before anything else, the bundle
|
|
263
|
+
* proven local moves to `.env.development.local`: each key whose digest matches
|
|
264
|
+
* a ref recorded for a `local/…` store, the storefront a local ref recorded, and
|
|
265
|
+
* a `local/…` store marker. Nothing unproven moves. The destination is written
|
|
266
|
+
* first and read back, then only names it now holds are removed from
|
|
267
|
+
* `.env.local`; a run stopped between the two finishes the removal next time.
|
|
268
|
+
* A destination already holding one of those names with another value is a handoff.
|
|
269
|
+
*/
|
|
270
|
+
function moveLocalBundle(db: DatabaseSync, d: SaveDeps): void {
|
|
271
|
+
const prod = readEnvLocal(d.cwd, ENV_FILE.production);
|
|
272
|
+
const refs = (db.prepare("SELECT * FROM credential_ref").all() as unknown as RefRecord[]).filter((r) =>
|
|
273
|
+
r.store.startsWith("local/"),
|
|
274
|
+
);
|
|
275
|
+
const moving: Record<string, string> = {};
|
|
276
|
+
for (const name of [ENV.publishable, ENV.secret]) {
|
|
277
|
+
const value = prod[name];
|
|
278
|
+
if (value && refs.some((r) => r.env_name === name && r.key_digest === keyDigest(value))) moving[name] = value;
|
|
279
|
+
}
|
|
280
|
+
const sf = prod[ENV.storefront];
|
|
281
|
+
if (sf && refs.some((r) => r.storefront_id === sf)) moving[ENV.storefront] = sf;
|
|
282
|
+
if (prod[ENV.store]?.startsWith("local/")) moving[ENV.store] = prod[ENV.store] as string;
|
|
283
|
+
const names = Object.keys(moving);
|
|
284
|
+
if (!names.length) return;
|
|
285
|
+
const dev = readEnvLocal(d.cwd, ENV_FILE.local);
|
|
286
|
+
const clash = names.filter((n) => dev[n] !== undefined && dev[n] !== moving[n]);
|
|
287
|
+
if (clash.length)
|
|
288
|
+
throw new NeedsPerson(
|
|
289
|
+
`${ENV_FILE.production} holds kedge dev's ${clash.join(", ")}, and ${ENV_FILE.local} holds others: keep one, remove the other line, then run this again`,
|
|
290
|
+
);
|
|
291
|
+
saveEnv(d.cwd, moving, ENV_FILE.local);
|
|
292
|
+
const landed = readEnvLocal(d.cwd, ENV_FILE.local);
|
|
293
|
+
saveEnv(
|
|
294
|
+
d.cwd,
|
|
295
|
+
{},
|
|
296
|
+
ENV_FILE.production,
|
|
297
|
+
names.filter((n) => landed[n] === moving[n]),
|
|
298
|
+
);
|
|
299
|
+
d.log(`moved kedge dev's ${names.join(", ")} from ${ENV_FILE.production} to ${ENV_FILE.local}`);
|
|
300
|
+
}
|
|
301
|
+
|
|
257
302
|
/**
|
|
258
303
|
* Whether a saved key still does its job (§6.2): a publishable key passes the
|
|
259
304
|
* storefront gate; a secret key holds its grants. Only a definite answer
|
package/src/local-state.ts
CHANGED
|
@@ -77,24 +77,36 @@ export function stateDb(cwd: string): DatabaseSync {
|
|
|
77
77
|
return db;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/** Where a store's saved keys go (p0.10.1 §4): `kedge dev`'s apart from Cloudflare's, so neither blocks the other. */
|
|
81
|
+
export const ENV_FILE = { production: ".env.local", local: ".env.development.local" } as const;
|
|
82
|
+
|
|
80
83
|
/**
|
|
81
|
-
*
|
|
84
|
+
* Rewrites an env file, keeping every line it doesn't touch: `values` set
|
|
85
|
+
* (replaced in place, or added at the end) and `remove` dropped. A temporary
|
|
82
86
|
* file in the same directory, mode 0600, renamed over it, so a crash leaves
|
|
83
87
|
* the old file or the new one, never half of either.
|
|
84
88
|
*/
|
|
85
|
-
export function saveEnv(
|
|
86
|
-
|
|
89
|
+
export function saveEnv(
|
|
90
|
+
cwd: string,
|
|
91
|
+
values: Record<string, string>,
|
|
92
|
+
name: string = ENV_FILE.production,
|
|
93
|
+
remove: readonly string[] = [],
|
|
94
|
+
): void {
|
|
95
|
+
const file = join(cwd, name);
|
|
87
96
|
const lines = existsSync(file) ? readFileSync(file, "utf8").split("\n") : [];
|
|
88
97
|
const left = new Set(Object.keys(values));
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return
|
|
98
|
+
const drop = new Set(remove);
|
|
99
|
+
const out = lines.flatMap((line) => {
|
|
100
|
+
const key = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(line)?.[1];
|
|
101
|
+
if (!key) return [line];
|
|
102
|
+
if (drop.has(key)) return [];
|
|
103
|
+
if (!(key in values)) return [line];
|
|
104
|
+
left.delete(key);
|
|
105
|
+
return [`${key}=${values[key]}`];
|
|
94
106
|
});
|
|
95
107
|
while (out.length && out.at(-1) === "") out.pop();
|
|
96
|
-
for (const
|
|
97
|
-
const tmp = join(cwd,
|
|
108
|
+
for (const key of left) out.push(`${key}=${values[key]}`);
|
|
109
|
+
const tmp = join(cwd, `${name}.${process.pid}.tmp`);
|
|
98
110
|
try {
|
|
99
111
|
writeFileSync(tmp, `${out.join("\n")}\n`, { mode: 0o600 });
|
|
100
112
|
renameSync(tmp, file);
|
|
@@ -103,8 +115,8 @@ export function saveEnv(cwd: string, values: Record<string, string>): void {
|
|
|
103
115
|
}
|
|
104
116
|
}
|
|
105
117
|
|
|
106
|
-
/** `.env.local`
|
|
107
|
-
export function readEnvLocal(cwd: string): Record<string, string> {
|
|
108
|
-
const file = join(cwd,
|
|
118
|
+
/** An env file's values (`.env.local` by default), as the credential resolver reads them. */
|
|
119
|
+
export function readEnvLocal(cwd: string, name: string = ENV_FILE.production): Record<string, string> {
|
|
120
|
+
const file = join(cwd, name);
|
|
109
121
|
return existsSync(file) ? (parseEnv(readFileSync(file, "utf8")) as Record<string, string>) : {};
|
|
110
122
|
}
|
package/src/main.ts
CHANGED
|
@@ -118,7 +118,15 @@ async function inviteOrigin(
|
|
|
118
118
|
): Promise<string | null> {
|
|
119
119
|
const url = flag(args, "--url");
|
|
120
120
|
if (url) return new URL(url).origin;
|
|
121
|
-
|
|
121
|
+
// Local: kedge dev's own origin, the port it binds (p0.10.1 §1, 5; §3).
|
|
122
|
+
if (!remote) {
|
|
123
|
+
try {
|
|
124
|
+
const { devPort } = await import("./target");
|
|
125
|
+
return `http://localhost:${devPort(cwd)}`;
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
122
130
|
try {
|
|
123
131
|
const worker = storeNames(cwd).names.worker;
|
|
124
132
|
return `https://${worker}.${await (cf ?? (await cloudflareFor(env, cwd))).workersSubdomain()}.workers.dev`;
|
|
@@ -372,8 +380,17 @@ async function run(argv: string[], cwd: string, deps: RunDeps & { json: boolean
|
|
|
372
380
|
else results.push(...(await doctorRemote({ cwd, cf, lock, logpushJobs: () => cf.logpushJobs() })));
|
|
373
381
|
}
|
|
374
382
|
}
|
|
375
|
-
console.log(flags.includes("--json") ? JSON.stringify({ checks: results }, null, 2) : formatReport(results));
|
|
376
383
|
const handoff = results.find((r) => r.handoff)?.handoff;
|
|
384
|
+
// The same envelope as every --json command (p0.10.1 §1, 7): ok, the checks, and a handoff when there is one.
|
|
385
|
+
console.log(
|
|
386
|
+
flags.includes("--json")
|
|
387
|
+
? JSON.stringify({
|
|
388
|
+
ok: !results.some((r) => r.status === "fail"),
|
|
389
|
+
checks: results,
|
|
390
|
+
...(handoff ? { handoff } : {}),
|
|
391
|
+
})
|
|
392
|
+
: formatReport(results),
|
|
393
|
+
);
|
|
377
394
|
if (handoff) {
|
|
378
395
|
console.error(`for you: ${handoff}`);
|
|
379
396
|
return 3;
|
|
@@ -463,7 +480,8 @@ export async function main(argv: string[], cwd: string, deps: RunDeps = {}): Pro
|
|
|
463
480
|
const f = failureOf(e);
|
|
464
481
|
if (!f) throw e;
|
|
465
482
|
console.error(f.line);
|
|
466
|
-
|
|
483
|
+
// doctor keeps its own --json report, and a failure past it is still one object (p0.10.1, codex code r20).
|
|
484
|
+
if (json || (command === "doctor" && flags.includes("--json"))) (deps.emit ?? emit)(f.result);
|
|
467
485
|
return f.code;
|
|
468
486
|
}
|
|
469
487
|
});
|
package/src/output.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// `--json` (p0.10 §5): stdout carries one object when the command ends, and
|
|
2
|
-
// nothing else, so an agent parses it whole.
|
|
3
|
-
//
|
|
4
|
-
// child the CLI starts (Wrangler, the store's
|
|
5
|
-
//
|
|
6
|
-
|
|
2
|
+
// nothing else, so an agent parses it whole. stderr carries only what the
|
|
3
|
+
// agent must read: a refusal, a handoff's `for you:` line, or a child's failure.
|
|
4
|
+
// Progress is dropped, and every child the CLI starts (Wrangler, the store's
|
|
5
|
+
// checks and scenarios) is captured and shown, its last lines on stderr, only
|
|
6
|
+
// when it fails: an agent's tool shows both streams as one, so anything chatty
|
|
7
|
+
// there reads as part of the answer (p0.10.1 §1, 7).
|
|
8
|
+
import type { SpawnSyncReturns, StdioOptions } from "node:child_process";
|
|
7
9
|
|
|
8
10
|
let reserved = false;
|
|
9
11
|
|
|
@@ -13,8 +15,8 @@ export async function withJsonOutput<T>(on: boolean, work: () => Promise<T>): Pr
|
|
|
13
15
|
const log = console.log;
|
|
14
16
|
const info = console.info;
|
|
15
17
|
reserved = true;
|
|
16
|
-
console.log =
|
|
17
|
-
console.info =
|
|
18
|
+
console.log = () => {};
|
|
19
|
+
console.info = () => {};
|
|
18
20
|
try {
|
|
19
21
|
return await work();
|
|
20
22
|
} finally {
|
|
@@ -29,7 +31,14 @@ export function emit(result: Record<string, unknown>): void {
|
|
|
29
31
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
/** A child's stdio: inherited,
|
|
34
|
+
/** A child's stdio: inherited, or under `--json` captured, for `childFailed` to show if it fails. */
|
|
33
35
|
export function childStdio(): StdioOptions {
|
|
34
|
-
return reserved ? ["
|
|
36
|
+
return reserved ? ["ignore", "pipe", "pipe"] : "inherit";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Under `--json`, a captured child that failed shows its last lines on stderr; inherited output was already seen. */
|
|
40
|
+
export function childFailed(r: Pick<SpawnSyncReturns<string | Buffer>, "status" | "stdout" | "stderr">): void {
|
|
41
|
+
if (!reserved || r.status === 0) return;
|
|
42
|
+
const out = `${r.stdout?.toString() ?? ""}${r.stderr?.toString() ?? ""}`.trim();
|
|
43
|
+
if (out) process.stderr.write(`${out.split("\n").slice(-40).join("\n")}\n`);
|
|
35
44
|
}
|
package/src/preview.ts
CHANGED
|
@@ -33,7 +33,7 @@ import type { Lock } from "./compose";
|
|
|
33
33
|
import { CORE_SECRETS, DeployRefused, openOps, stageAssets } from "./deploy";
|
|
34
34
|
import { assetsShape, declaredClasses, readWrangler } from "./doctor";
|
|
35
35
|
import { LostOwnership, OperationHeld, type OpsStore } from "./ops";
|
|
36
|
-
import { childStdio } from "./output";
|
|
36
|
+
import { childFailed, childStdio } from "./output";
|
|
37
37
|
import { hashTree, type StoreNames, sha256, type WranglerJson } from "./store";
|
|
38
38
|
import { readTar, type TarEntry, TarRefused, writeTar } from "./tar";
|
|
39
39
|
|
|
@@ -1018,7 +1018,9 @@ export function runScenarios(cwd: string, credentials: StoreCredentials, env: No
|
|
|
1018
1018
|
cwd,
|
|
1019
1019
|
stdio: childStdio(),
|
|
1020
1020
|
env: { ...env, KEDGE_PREVIEW_CREDENTIALS: JSON.stringify(credentials) },
|
|
1021
|
+
maxBuffer: 1 << 26,
|
|
1021
1022
|
});
|
|
1023
|
+
childFailed(r);
|
|
1022
1024
|
return r.status ?? 1;
|
|
1023
1025
|
} finally {
|
|
1024
1026
|
rmSync(tmp, { recursive: true, force: true });
|
package/src/smoke.ts
CHANGED
|
@@ -47,11 +47,25 @@ export interface SmokeResult {
|
|
|
47
47
|
status: string;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
const
|
|
51
|
-
db.exec(`CREATE TABLE IF NOT EXISTS smoke_run (
|
|
52
|
-
name TEXT PRIMARY KEY, store TEXT NOT NULL, storefront_id TEXT NOT NULL,
|
|
50
|
+
const COLUMNS = `store TEXT NOT NULL, name TEXT NOT NULL, storefront_id TEXT NOT NULL,
|
|
53
51
|
cart_id TEXT, cart_token TEXT, completion TEXT, order_id TEXT, order_token TEXT, number TEXT, abandoned TEXT,
|
|
54
|
-
created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
|
|
52
|
+
created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL`;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Runs keyed by store and name (p0.10.1 §5): `first` on kedge dev and `first` on
|
|
56
|
+
* Cloudflare are two runs. A 0.10.0 checkout's `smoke_run`, keyed by name alone,
|
|
57
|
+
* is copied in whole the first time, every recorded moment with it, so a run it
|
|
58
|
+
* stopped resumes; the old table is left as it was.
|
|
59
|
+
*/
|
|
60
|
+
function table(db: DatabaseSync) {
|
|
61
|
+
db.exec(`CREATE TABLE IF NOT EXISTS smoke_runs (${COLUMNS}, PRIMARY KEY (store, name))`);
|
|
62
|
+
const legacy = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'smoke_run'").get();
|
|
63
|
+
if (legacy)
|
|
64
|
+
db.exec(`INSERT OR IGNORE INTO smoke_runs (store, name, storefront_id, cart_id, cart_token, completion, order_id,
|
|
65
|
+
order_token, number, abandoned, created_at, updated_at)
|
|
66
|
+
SELECT store, name, storefront_id, cart_id, cart_token, completion, order_id, order_token, number, abandoned,
|
|
67
|
+
created_at, updated_at FROM smoke_run`);
|
|
68
|
+
}
|
|
55
69
|
|
|
56
70
|
/** The smoke run `name` on `target`: resumed, or answered again once it has its number. */
|
|
57
71
|
export async function smoke(target: StoreTarget, name: string, d: SmokeDeps): Promise<SmokeResult> {
|
|
@@ -69,20 +83,18 @@ export async function smoke(target: StoreTarget, name: string, d: SmokeDeps): Pr
|
|
|
69
83
|
async function run(db: DatabaseSync, target: StoreTarget, name: string, d: SmokeDeps): Promise<SmokeResult> {
|
|
70
84
|
const now = d.now ?? Date.now;
|
|
71
85
|
const store = identityOf(target);
|
|
72
|
-
const get = () =>
|
|
86
|
+
const get = () =>
|
|
87
|
+
db.prepare("SELECT * FROM smoke_runs WHERE store = ? AND name = ?").get(store, name) as unknown as Run | undefined;
|
|
73
88
|
const set = (a: Partial<Run>) => {
|
|
74
89
|
const cols = Object.keys(a);
|
|
75
|
-
db.prepare(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
name,
|
|
79
|
-
);
|
|
90
|
+
db.prepare(
|
|
91
|
+
`UPDATE smoke_runs SET ${cols.map((c) => `${c} = ?`).join(", ")}, updated_at = ? WHERE store = ? AND name = ?`,
|
|
92
|
+
).run(...cols.map((c) => (a as Record<string, string | null>)[c] ?? null), now(), store, name);
|
|
80
93
|
};
|
|
81
94
|
|
|
82
95
|
let r = get();
|
|
83
96
|
// A finished run answers its order again, sending nothing: a retried command can't place a second one.
|
|
84
97
|
if (r?.number && r.order_id) {
|
|
85
|
-
if (r.store !== store) throw new DeployRefused(`smoke run ${name} belongs to another store (${r.store})`);
|
|
86
98
|
return { run: name, order_id: r.order_id, number: r.number, status: "open" };
|
|
87
99
|
}
|
|
88
100
|
if (r?.abandoned)
|
|
@@ -106,19 +118,15 @@ async function run(db: DatabaseSync, target: StoreTarget, name: string, d: Smoke
|
|
|
106
118
|
|
|
107
119
|
if (!r) {
|
|
108
120
|
const t = now();
|
|
109
|
-
db.prepare(
|
|
110
|
-
name,
|
|
111
|
-
|
|
112
|
-
storefront,
|
|
113
|
-
t,
|
|
114
|
-
t,
|
|
115
|
-
);
|
|
121
|
+
db.prepare(
|
|
122
|
+
"INSERT INTO smoke_runs (store, name, storefront_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
|
123
|
+
).run(store, name, storefront, t, t);
|
|
116
124
|
r = get() as Run;
|
|
117
125
|
}
|
|
118
|
-
// A cart admits only its own storefront's key: a record for another
|
|
119
|
-
if (r.
|
|
126
|
+
// A cart admits only its own storefront's key: a record for another storefront is refused, not reused.
|
|
127
|
+
if (r.storefront_id !== storefront)
|
|
120
128
|
throw new DeployRefused(
|
|
121
|
-
`smoke run ${name} was made for
|
|
129
|
+
`smoke run ${name} was made for storefront ${r.storefront_id}: start another with --run <new name>`,
|
|
122
130
|
);
|
|
123
131
|
|
|
124
132
|
const shop = { "x-kedge-key": key };
|