@kedge/cli 0.11.0 → 0.11.2
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 +2 -1
- package/kedge-manifest.json +35 -30
- package/package.json +7 -7
- package/src/main.ts +66 -92
- package/src/smoke.ts +2 -1
- package/src/usage.ts +110 -0
package/README.md
CHANGED
|
@@ -18,7 +18,8 @@ its dependencies, run as `pnpm kedge <command>`.
|
|
|
18
18
|
| `kedge smoke --url <url>` | A test order, placed; run again, the same order |
|
|
19
19
|
| `kedge upgrade --to <version>` | Every `@kedge` package to a release, with its codemods |
|
|
20
20
|
|
|
21
|
-
**For agents.**
|
|
21
|
+
**For agents.** `kedge <command> --help` prints that command's usage and runs
|
|
22
|
+
nothing; `kedge --help` lists them all. Exit `0` done, `1` refused or failed (the reason on stderr), `2`
|
|
22
23
|
usage, `3` a person must act: the last line of stderr starts `for you:` and is one
|
|
23
24
|
sentence to pass on. Run the same command again after they do; every command is
|
|
24
25
|
safe to repeat. With `--json`, stdout is one object, `{"ok": true, ...}`,
|
package/kedge-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.11.
|
|
3
|
-
"upgradesFrom": "0.
|
|
2
|
+
"version": "0.11.2",
|
|
3
|
+
"upgradesFrom": "0.11.1",
|
|
4
4
|
"doStateFormat": 1,
|
|
5
5
|
"migrations": {
|
|
6
6
|
"0000_schema.sql": {
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
"@kedge/core": {
|
|
149
149
|
"LICENSE": "5068e060c465b99dee8b5d7ccfecc51f395affd63d6b9651a2d31e71e3a0d9fb",
|
|
150
150
|
"README.md": "5cffa13dcc33f4c3c39adb020a8c589722a88ed904033de1f16385a8a3e67236",
|
|
151
|
-
"package.json": "
|
|
151
|
+
"package.json": "a4f59f1f10ef6963204a9d9412436d70298ff646ce3189a28cc6302dab3cb3be",
|
|
152
152
|
"src/access/audit.ts": "374bf28051543e2cfc3a92f6dd3b27525562eb76d12d0628951da855323be120",
|
|
153
153
|
"src/access/context.ts": "bf4d50e4473ebe99aea6e6d673b0006595bb5bcf47e9f34c094fa4d9c6fea5e7",
|
|
154
154
|
"src/access/permissions.ts": "0e71620cf6dcddd414bdba4634986366f6c37c8d94f419c4fdd289131b5771da",
|
|
@@ -228,7 +228,7 @@
|
|
|
228
228
|
"migrations/0029_cli_session.sql": "a883b970c2820a84ac3e0c65530681df488ecc0546ebf29a43bc9a5778e8c1f9",
|
|
229
229
|
"migrations/0030_credential_ref.sql": "b06cee4958d222a93d0af1b6a57e260beb98a92a9a1b2ba4c7a895fc40b79201",
|
|
230
230
|
"migrations/0031_cli_session_origin.sql": "96fbe4ae12064ff4dbd599d8d0939b43b541c7177a9f12dfb4abfe54cdc9f6c9",
|
|
231
|
-
"package.json": "
|
|
231
|
+
"package.json": "3e81875595c648c2b5f4977139758dfd0a99b44473e0804dde99482aa8a457a6",
|
|
232
232
|
"src/access-sql.ts": "51481f129f4614ba770218526048042aa0d118274ce7a737e0584037ea59dff0",
|
|
233
233
|
"src/access.ts": "2a384d4b6f22bc0109aa2f247f628543bc0a80a936416de6be56b693328dce9c",
|
|
234
234
|
"src/admin.ts": "694d508e2bb51eda9134d163b049240688d5fdcd6783a66e2ae18bf9da21d140",
|
|
@@ -270,7 +270,7 @@
|
|
|
270
270
|
"@kedge/server": {
|
|
271
271
|
"LICENSE": "5068e060c465b99dee8b5d7ccfecc51f395affd63d6b9651a2d31e71e3a0d9fb",
|
|
272
272
|
"README.md": "1fb1bcd08cc11851accb343d6df724383706cf9324d58785d097431476524b26",
|
|
273
|
-
"package.json": "
|
|
273
|
+
"package.json": "440af1e0f33133f8b72fce7cb82901e763b2ddec6a849010198066b5e1d9b979",
|
|
274
274
|
"src/access.ts": "6544a7b385b7d3391d0a996f1bc867d17741ddd63a0d48e0f0b1d37adcea78b4",
|
|
275
275
|
"src/admin-routes.ts": "9c1f13283f1b11709c0d27512205d9ead59a9042a4df36fa1bec344184ad7d0e",
|
|
276
276
|
"src/admin-shell.ts": "3b94e14e3cd8303fd7f3153a65ce7c3339203b58f74656c40839587ca996a7c3",
|
|
@@ -293,7 +293,7 @@
|
|
|
293
293
|
"@kedge/runtime-cf": {
|
|
294
294
|
"LICENSE": "5068e060c465b99dee8b5d7ccfecc51f395affd63d6b9651a2d31e71e3a0d9fb",
|
|
295
295
|
"README.md": "b678c333e82364c5e9349bb1aa4f64d1479678893b471ddc0c4ea41b769adef9",
|
|
296
|
-
"package.json": "
|
|
296
|
+
"package.json": "0cfd3894a386bc1a44dd43f8fb0db8f1bd96253216f8b6ec3bbadc7916353079",
|
|
297
297
|
"src/admin.ts": "2c005857fe2928b4e82777590f13f7a83e26edfa30b24f5463ee0c80932b2ca0",
|
|
298
298
|
"src/cart-do.ts": "d7be874e440d41b716a96607341b82b0992a9da1656456322fca3634f6be8529",
|
|
299
299
|
"src/clock-do.ts": "8d68b0ba01d16763fe7d13feee71d315bbc1ebbee7e5f8694a9d186c4ccab3a5",
|
|
@@ -320,13 +320,13 @@
|
|
|
320
320
|
"@kedge/plugin-sdk": {
|
|
321
321
|
"LICENSE": "5068e060c465b99dee8b5d7ccfecc51f395affd63d6b9651a2d31e71e3a0d9fb",
|
|
322
322
|
"README.md": "685a9eede5493ce08535b0525cf30b1290d48ba9e75b3662239fb5b6e08d3baa",
|
|
323
|
-
"package.json": "
|
|
323
|
+
"package.json": "7c742e41c40ff88a78815effa8c5be04c85d7c805c044a981ce91cb8450da757",
|
|
324
324
|
"src/index.ts": "cb2aa938e2065f6ca5f01a470b421ec0ca20599f149114051823e2510a76d11b"
|
|
325
325
|
},
|
|
326
326
|
"@kedge/testing": {
|
|
327
327
|
"LICENSE": "5068e060c465b99dee8b5d7ccfecc51f395affd63d6b9651a2d31e71e3a0d9fb",
|
|
328
328
|
"README.md": "94da183c9b4fdb9617dbe93cfb169b81ce4bd6ed7c9a364d6d6ad3e62578d9cc",
|
|
329
|
-
"package.json": "
|
|
329
|
+
"package.json": "dd9ca56e06a63c714bb178ede39a9ba07ae05c7342b348dc8ee3af92a62c89a7",
|
|
330
330
|
"src/budgets.ts": "4ffb780a7543d1d2c7c7e5dca7a2d4cdb363e3b0ee2801a928d928da8088e525",
|
|
331
331
|
"src/http.ts": "59c4bb9d3df6885333103a05cddde0d03cc70008a416c99905422fbead034ee7",
|
|
332
332
|
"src/index.ts": "c3705394753e6c6ecf3abfd19a3b1e672c61c33bda4ff8f2d54ece10f506b5cd",
|
|
@@ -350,18 +350,22 @@
|
|
|
350
350
|
"extending/tests.md": "8e2e68427f7e830b0750aebef889846c3285fbf4bdcfb0342947adeb1b896574",
|
|
351
351
|
"extending/timers.md": "6da7fc0a8eee314df7158df16e5670d194842d5de452032ba8be414b5dac4e7e",
|
|
352
352
|
"extending/tracks.md": "3d85cd54a5060c35fe70b3bbea848c16ffd0a4e2e21e590b104408690a721ac6",
|
|
353
|
-
"index.json": "
|
|
354
|
-
"package.json": "
|
|
353
|
+
"index.json": "eb1c33142a381537964add5f42f12380509a44ec312fd2e124f232d3d507441b",
|
|
354
|
+
"package.json": "7f96281f71ab2b2d110fd36cb454c4dfa7c2313d96626ba83f9677b18df6d50e",
|
|
355
355
|
"reference/api.md": "114830d766ab3b81a5181f7b07b10012ac5ee15898c87fa9515c71cec60ec374",
|
|
356
356
|
"reference/errors.md": "c4fa0dcb4d79d5a4888d7fc3ee525a045b21d3cb3ac158f5172dbb02880979f2",
|
|
357
|
+
"reference/events.md": "3b8fc6330b5fca2212d8ffc7c9edcdffc7872ed1f8e114930801c210b7f5fa70",
|
|
358
|
+
"reference/orders.md": "8fd6d5d3ee7dd4f9f44cf8b0131f9eb0cf60bb3b5d0b9a7696cf89d0f7a29012",
|
|
357
359
|
"skills/kedge-extend/SKILL.md": "7ce215445e99d87e401b7f2669c184f3fa263b75aabced2cdf5eefb398043013",
|
|
358
360
|
"skills/kedge-plugin/SKILL.md": "21af3f112cfcdf2a1cd2eb8e9148a0558a4aebf29b1a057cb5ece09af8116d99",
|
|
359
361
|
"skills/kedge-preview-deploy/SKILL.md": "5dfe9c4aeb18f26d811b7bee24b3b50df4ec63dffcccf905320ae9f045d83be6",
|
|
360
|
-
"skills/kedge-start/SKILL.md": "
|
|
362
|
+
"skills/kedge-start/SKILL.md": "408055af695678394089fb4f05386ebe21f94b44d7747cef2f179736f147c362",
|
|
361
363
|
"skills/kedge-upgrade/SKILL.md": "97cba305676a22696d6d4e25d2c5e823b47fc0be04b12813d17b9dfd3c5a6c2e",
|
|
362
364
|
"upgrading/0.10.0.md": "169efc96cdf80af167df6607c0db4d977e29c135858ba0c61f53d50d9facc676",
|
|
363
365
|
"upgrading/0.10.1.md": "d47ddc49f1724e0f3be55a7136d75cbcd8f0bf1128cc7ed2aa6e17cf884ac893",
|
|
364
366
|
"upgrading/0.11.0.md": "fb23a255a2316e3c27860080e3ec17cde546885cc85f744b963a76b737570aeb",
|
|
367
|
+
"upgrading/0.11.1.md": "62311682707de91687cd3400c772eb056e8ea8a017a6f00b38ec26eafe6744b7",
|
|
368
|
+
"upgrading/0.11.2.md": "181b2b574847774cc7ad985f918b3f1806d205afaba06c7ffc8a2011f03dce03",
|
|
365
369
|
"upgrading/0.8.0.md": "b0ddd26993048ea1fdd0e76fb2707060cc4032ffb199a691278020361be95cf8",
|
|
366
370
|
"upgrading/0.9.0.md": "d632d6143636351289684dd51dd51eb0be3e76975c4a42d65acdcee04549d21d",
|
|
367
371
|
"upgrading/codemods/add-admin-assets.mjs": "fa5d069a3489889dcc095b309020d0e773004b3a39544457747491963492f0a1",
|
|
@@ -375,31 +379,31 @@
|
|
|
375
379
|
"LICENSE": "5068e060c465b99dee8b5d7ccfecc51f395affd63d6b9651a2d31e71e3a0d9fb",
|
|
376
380
|
"README.md": "1ce1a6d2a16995f6342c9bcc40ddbac0e2bc35cdcdf1ad24efe28aa5b85d79a3",
|
|
377
381
|
"dist/_headers": "b1d979b2fdf27af6c0f41fdaee420f2a2b192f28dc855fec7491f8ffa685c9ba",
|
|
378
|
-
"dist/app/assets/account-
|
|
382
|
+
"dist/app/assets/account-GJ460eh6.js": "a21fe5c2caf4dc65e68574bdba7f68ab67e0b4ced3ec17c830047c1a3a23a4c1",
|
|
379
383
|
"dist/app/assets/card-CH9fCW35.js": "995ee7034e88909a04b07d559a7f520fb08df647e960606f54f5ab88c7201a3f",
|
|
380
|
-
"dist/app/assets/display-
|
|
381
|
-
"dist/app/assets/home-
|
|
382
|
-
"dist/app/assets/index-
|
|
383
|
-
"dist/app/assets/index-
|
|
384
|
-
"dist/app/assets/invite-
|
|
385
|
-
"dist/app/assets/resource-detail-
|
|
386
|
-
"dist/app/assets/resource-form-
|
|
387
|
-
"dist/app/assets/resource-list-
|
|
388
|
-
"dist/app/assets/setup-
|
|
389
|
-
"dist/app/assets/use-command-
|
|
390
|
-
"dist/app/assets/work-lists-
|
|
384
|
+
"dist/app/assets/display-DBOMyq_B.js": "6a38291191c4c33fd8307d78b8f87c420e281a065965bda99eed31ebb15657a2",
|
|
385
|
+
"dist/app/assets/home-SVbjuY56.js": "4729b17cd7fc2db51792bea9ad4ff8168e877a40df7c8d839d3b7491bad80f55",
|
|
386
|
+
"dist/app/assets/index-A6QB_53Q.css": "69a091853faa15ae161af250c27e0a9ac1d4546553b9854339c419368cd340a5",
|
|
387
|
+
"dist/app/assets/index-Bxm9wGkx.js": "2b9b9aac4810d8f08e0e3b47223666e1181f5b28bcd7c128b8deb140de75d6e0",
|
|
388
|
+
"dist/app/assets/invite-D54MWzuf.js": "629732b1e09605bb5419c80d4d60829ae95ccc40a5e464530376e7979be7208d",
|
|
389
|
+
"dist/app/assets/resource-detail-Dekov70L.js": "e349522daeca2894f3b7bf87b83d2e78d49abdfc74321a8862f8540a3207180e",
|
|
390
|
+
"dist/app/assets/resource-form-C9T8aoRr.js": "2799113487483a205798ff6f9808309c506acf09296b3b325c7990900cf19110",
|
|
391
|
+
"dist/app/assets/resource-list-DJU-gknY.js": "37df28dc64c86d4fb9d1fa6e42c1f808d958f86b6c8e9ab885964f4104134c9a",
|
|
392
|
+
"dist/app/assets/setup-DBmP0maP.js": "0edeae3bfbf2095d22de7b87a6f24846d53a7294fb6cb67654b3231ecc36d1a2",
|
|
393
|
+
"dist/app/assets/use-command-C-SbE8V0.js": "22a76a3e79165954e450b57db20edbdbb371e066942bd5a5e3ac4321d9235114",
|
|
394
|
+
"dist/app/assets/work-lists-DEL5FPck.js": "d1932cb198e5f3be38c52de67b77636526d897da1ce5a42c40db65da099547c0",
|
|
391
395
|
"dist/app/boot.js": "3099141f8935b0c2f15a041f205a673c977a9c8bfc7aa30f23a44b44a69bdb1d",
|
|
392
|
-
"dist/app/build.json": "
|
|
393
|
-
"dist/app/index.html": "
|
|
396
|
+
"dist/app/build.json": "0fc2dcaa057dd9fc871cdc5f6bc7bae013165b7cacdc44a3b2f99d38caf8ce9d",
|
|
397
|
+
"dist/app/index.html": "4aae15c67b20300637c1d0a8cf445ecce83d560d5325625fc186ba7442a11085",
|
|
394
398
|
"dist/app/licenses.txt": "7a2bb91cbbc5e4201b72d4d95b2a4d7458f42e80a0d05b01398c9e93f5764f62",
|
|
395
|
-
"package.json": "
|
|
399
|
+
"package.json": "049e3b8cb2fc25044417895237975f23a9f7ae8a7863166224815e8e8f74faa5",
|
|
396
400
|
"slots.json": "3252f255e1628537056317d74be5fd2db2fb7e283cd6e8d3f2d49e6dad4b15e5"
|
|
397
401
|
},
|
|
398
402
|
"@kedge/cli": {
|
|
399
403
|
"LICENSE": "5068e060c465b99dee8b5d7ccfecc51f395affd63d6b9651a2d31e71e3a0d9fb",
|
|
400
|
-
"README.md": "
|
|
404
|
+
"README.md": "8b6cbd9f74ea766b082cefa0afbd241bbfda3c10e9c7c7ce8b3d728790d1933f",
|
|
401
405
|
"bin/kedge.mjs": "8279a888842a6ee6c0ae9e26fd4380b97892648ca94979fc69d505ff8af6aa9f",
|
|
402
|
-
"package.json": "
|
|
406
|
+
"package.json": "c63256ac55804475bb3ad8d699834c5dd4bf16aa04ab1f6661abd27b4d5fd194",
|
|
403
407
|
"src/cf.ts": "bf87715c96490278e9679c87818ad7962a7f66c7b38af3895a9f8323d06d7a2c",
|
|
404
408
|
"src/check.ts": "273ff4b59d717aea33e166e4d88775f190fa826cc3432642a2ccfa2cad12ddd6",
|
|
405
409
|
"src/cli-session.ts": "dbff2b69ac8fd50c62ebfc082a4d8344561afe5ceb9b389494d1010a7b34d883",
|
|
@@ -414,7 +418,7 @@
|
|
|
414
418
|
"src/invariants.ts": "177446f4144571345c6872f816a076dc88f5dc92dc0503281456f3a2164229ae",
|
|
415
419
|
"src/keys-save.ts": "682e27974af1e0aba6127e70f73b99a848dae7baf31a6db1584d68d64f027581",
|
|
416
420
|
"src/local-state.ts": "bcfc69f6eb1b3ca134f428e27fe7dd38340755a0a37565587b5c88dfd99cae83",
|
|
417
|
-
"src/main.ts": "
|
|
421
|
+
"src/main.ts": "e94c402236e6edc34338bf92f324c8072d2d61273a79bfd5c3a0449bf7b9e757",
|
|
418
422
|
"src/manifest.ts": "9d52eafd735f9e3d8978588f2b9fb712e17d0e78106170fdb8cd373f39920c87",
|
|
419
423
|
"src/n1.ts": "17b26eb4114dba819282c44c67ef8bf5fbfcc854674133bee5d87bd250ebdc39",
|
|
420
424
|
"src/ops.ts": "c7958879d1d3dba8f3f959c2dfd1cff0554b7a56d4dc91a3df2f7b2ccf893eaa",
|
|
@@ -423,7 +427,7 @@
|
|
|
423
427
|
"src/plugin-lint.ts": "b716e02ee5a2ebaac1ef0bee709d5417fa800aa7ab2b1d3ded1607739091584d",
|
|
424
428
|
"src/preview.ts": "8d2f4105f62877d4e71a14bcde77356528d084a8aec0fd672afc99e2035a0911",
|
|
425
429
|
"src/schema.ts": "d0b2b8b1bbb7a9ea1a53e092563ac62f0e98c8c1b27a5172d977e2f1c63453d1",
|
|
426
|
-
"src/smoke.ts": "
|
|
430
|
+
"src/smoke.ts": "89051ae975a306aec53cac2e6bd7859c646aa5c3051f5e165a9f072de1e11fbf",
|
|
427
431
|
"src/sqlite-verify.ts": "1181960dfbbf5b94bd376c024a69d71aaa76bd69c6b92359713acf665e89c3b5",
|
|
428
432
|
"src/store-api.ts": "146e0aa31041bc9fa8bbf8e87a38e1b9153bb3b6e9c47aa3c3a1549ffbfa6f17",
|
|
429
433
|
"src/store.ts": "d6b76378ce7ddb2d90b0cf4a44ae599ed5fbc51b2336f2b4f8fe643975706a13",
|
|
@@ -432,6 +436,7 @@
|
|
|
432
436
|
"src/tz.ts": "5b83e5071d6ce2bdc4d300e7e045be3bc628a239053dcf360a6d3ef11dea9a79",
|
|
433
437
|
"src/units.ts": "66b6373828802a58d64bf71e8a0e2308afd49fc5f08d0acd816ff539b7309213",
|
|
434
438
|
"src/upgrade.ts": "06bdb77a35834e92091f92e52612ea2336735df78fd9508c33cfe33789276a36",
|
|
439
|
+
"src/usage.ts": "435a202e3e958a10c28c400fe5e645c829b5d58ff00183dab0d1143f138f4c17",
|
|
435
440
|
"src/worker-config.ts": "5b4cd682f0430f8239108d72a05ac8586da85639a9ad6a67d4169e5e90237136"
|
|
436
441
|
}
|
|
437
442
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kedge/cli",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,16 +28,16 @@
|
|
|
28
28
|
"yaml": "2.9.1",
|
|
29
29
|
"@babel/parser": "7.29.9",
|
|
30
30
|
"wrangler": "4.152.0",
|
|
31
|
-
"@kedge/
|
|
32
|
-
"@kedge/
|
|
33
|
-
"@kedge/testing": "0.11.
|
|
34
|
-
"@kedge/runtime-cf": "0.11.
|
|
31
|
+
"@kedge/db": "0.11.2",
|
|
32
|
+
"@kedge/core": "0.11.2",
|
|
33
|
+
"@kedge/testing": "0.11.2",
|
|
34
|
+
"@kedge/runtime-cf": "0.11.2"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "24.19.2",
|
|
38
38
|
"@cloudflare/workers-types": "5.20261024.1",
|
|
39
|
-
"@kedge/plugin-
|
|
40
|
-
"@kedge/plugin-
|
|
39
|
+
"@kedge/plugin-loyalty": "0.0.0",
|
|
40
|
+
"@kedge/plugin-handling-fee": "0.0.0"
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
43
|
"typecheck": "tsc --noEmit -p .",
|
package/src/main.ts
CHANGED
|
@@ -44,66 +44,9 @@ import { emit, withJsonOutput } from "./output";
|
|
|
44
44
|
import { composedTexts, schemaOf } from "./schema";
|
|
45
45
|
import { storeNames } from "./store";
|
|
46
46
|
import { compileStoreZone } from "./tz";
|
|
47
|
+
import { JSON_COMMANDS, synopsisOf, USAGE, usageOf } from "./usage";
|
|
47
48
|
import { strictlyPublic } from "./worker-config";
|
|
48
49
|
|
|
49
|
-
const USAGE = `usage: kedge generate [--check] [--supersede <op-id>]
|
|
50
|
-
kedge check [--fast]
|
|
51
|
-
kedge doctor [--json]
|
|
52
|
-
kedge dev
|
|
53
|
-
kedge migrate [--remote]
|
|
54
|
-
kedge deploy [--maintenance-window <from>/<to>] [--var K=V]... [--adopt <name>]...
|
|
55
|
-
[--resume | --take-over <op-id> --confirm-stopped | --abort-cutover | --supersede <op-id>]
|
|
56
|
-
[--resolve-intent <id> --not-executed] [--record-active --reason <what>]
|
|
57
|
-
[--adopt-existing --from-release <version>] [--allow-older]
|
|
58
|
-
kedge preview [--name N] [--var K=V]... [--fresh] [--secret-env NAME]...
|
|
59
|
-
kedge preview build [--name N] | kedge preview apply <plan.tar|-> [--store S] [--name N]
|
|
60
|
-
kedge preview destroy [--name N] [--sweep]
|
|
61
|
-
kedge eject <unit> --reason "<why>" | kedge eject --rebase <unit>
|
|
62
|
-
kedge upgrade --to <version> [--from-packs <dir>]
|
|
63
|
-
kedge doctor --core-diff [--upstream <manifest>] [--strict]
|
|
64
|
-
kedge secret put <NAME> | kedge secret delete <NAME>
|
|
65
|
-
kedge restore --list
|
|
66
|
-
kedge seed --sample apparel --url <url> [--key <sk_…> | --session <token>] [--live]
|
|
67
|
-
kedge smoke --url <url> [--run <name>] [--live]
|
|
68
|
-
kedge keys create (--publishable [--origin <url>]... | --secret --grant <permission>...)
|
|
69
|
-
--url <url> (--session <token> | --sign-in <email> [--answers-stdin] | --save) [--name <name>]
|
|
70
|
-
kedge staff invite-owner <email> [--database DB] [--remote] [--url <url>] [--config wrangler.jsonc]
|
|
71
|
-
kedge staff recover <email> [--database DB] [--remote] [--config wrangler.jsonc]
|
|
72
|
-
kedge keys create --publishable --name <name> [--origin <origin>]... [--pepper-version N]
|
|
73
|
-
[--database DB] [--remote] [--config wrangler.jsonc]
|
|
74
|
-
|
|
75
|
-
generate compose core, plugin and local migrations into migrations/
|
|
76
|
-
and update kedge.lock.json
|
|
77
|
-
generate --check fail if generate would write anything (for CI)
|
|
78
|
-
dev fresh local secrets once, local migrations, then wrangler dev
|
|
79
|
-
migrate local migrations; --remote applies N−1 files to production, refusing a cutover
|
|
80
|
-
deploy migrations before the Worker, under an operation in <store>-ops (§6)
|
|
81
|
-
preview a separate Worker with its own D1, queue and Durable Objects, capture
|
|
82
|
-
sinks for every effect, seeded, then test/scenarios/*.http.ts against it
|
|
83
|
-
eject copy one unit of core into src/overrides, its upstream hash recorded
|
|
84
|
-
upgrade every @kedge package to a release one supported step on, its codemods,
|
|
85
|
-
UPGRADE-TODO.md, then generate and check; nothing is committed
|
|
86
|
-
restore --list the restore points; restoring waits for P1.18
|
|
87
|
-
smoke a test order on one of this store's own URLs: a cart, completion and
|
|
88
|
-
placement, resumed by its --run name until it has its number
|
|
89
|
-
check typecheck, generate --check, doctor, then the store's tests;
|
|
90
|
-
--fast stops before the tests (the Stop hook's)
|
|
91
|
-
doctor the store's offline checks: lock, installed core, error map,
|
|
92
|
-
invariant catalog, code lint, wrangler.jsonc
|
|
93
|
-
staff invite-owner the first Owner's one-time invite (only while no active Owner exists)
|
|
94
|
-
staff recover reset a sole Owner's factors and print a reenrol invite
|
|
95
|
-
keys create a storefront and its first publishable key; reads the pepper from
|
|
96
|
-
KEDGE_KEY_PEPPER_V<N> in the environment (N defaults to 1)
|
|
97
|
-
|
|
98
|
-
The staff and keys commands run one audited batch through wrangler d1 execute
|
|
99
|
-
(--local unless --remote) and print the secret once.
|
|
100
|
-
|
|
101
|
-
deploy, preview, seed, staff and keys take --json: one object on stdout when they
|
|
102
|
-
end ({"ok": true, ...}, or {"ok": false, "error"} / {"ok": false, "handoff"}),
|
|
103
|
-
everything else on stderr. Exit 3 means a person must act: the last line of stderr
|
|
104
|
-
starts "for you:". --sign-in asks only a terminal; --answers-stdin reads one line
|
|
105
|
-
per question from a pipe, waiting 30 seconds for each.`;
|
|
106
|
-
|
|
107
50
|
/**
|
|
108
51
|
* The store's origin for the invite link (p0.9 §3.5): `--url`, or with
|
|
109
52
|
* `--remote` the Worker's workers.dev URL. Null when neither is known, and then
|
|
@@ -148,9 +91,10 @@ async function bootstrap(
|
|
|
148
91
|
if (o.json) (o.emit ?? emit)(r);
|
|
149
92
|
};
|
|
150
93
|
// A usage error is an end too: under --json it's an object, never an empty stdout (codex code r8).
|
|
94
|
+
// The sentence says what's wrong, and the command's own usage follows it (p0.11.1 row 3).
|
|
151
95
|
const usage = (message: string) => {
|
|
152
|
-
console.error(message);
|
|
153
|
-
result({ ok: false, error: message
|
|
96
|
+
console.error(`${message}\n\n${usageOf(command)}`);
|
|
97
|
+
result({ ok: false, error: message });
|
|
154
98
|
return 2;
|
|
155
99
|
};
|
|
156
100
|
// Wrangler's own words are on stderr already; the object says only that the batch didn't finish.
|
|
@@ -158,6 +102,26 @@ async function bootstrap(
|
|
|
158
102
|
result({ ok: false, error: `wrangler d1 execute exited ${status}` });
|
|
159
103
|
return status;
|
|
160
104
|
};
|
|
105
|
+
// The command line first, before credentials, the account or any batch (p0.11.1 §2): a mistake
|
|
106
|
+
// in it is the agent's to fix, exit 2, never a credentials handoff that wouldn't fix it (codex r1).
|
|
107
|
+
const [first, target] = args;
|
|
108
|
+
// A flag first is no subcommand: `kedge keys --url …` needs `create`, not an unknown `--url`.
|
|
109
|
+
const sub = first?.startsWith("-") ? undefined : first;
|
|
110
|
+
const email = sub && target && !target.startsWith("--") ? target : undefined;
|
|
111
|
+
if (command === "staff") {
|
|
112
|
+
if (sub !== "invite-owner" && sub !== "recover")
|
|
113
|
+
return usage(sub ? `unknown command: kedge staff ${sub}` : "kedge staff needs invite-owner or recover");
|
|
114
|
+
if (!email) return usage(`staff ${sub} needs the Owner's email: ${synopsisOf(`staff ${sub}`)[0]?.trim()}`);
|
|
115
|
+
} else {
|
|
116
|
+
if (sub !== "create") return usage(sub ? `unknown command: kedge keys ${sub}` : "kedge keys needs create");
|
|
117
|
+
if (!args.includes("--publishable"))
|
|
118
|
+
return usage("keys create needs --publishable, or --url and a sign-in to create a key through the store's API");
|
|
119
|
+
}
|
|
120
|
+
const name = flag(args, "--name");
|
|
121
|
+
const pepperVersion = Number(flag(args, "--pepper-version") ?? "1");
|
|
122
|
+
if (command === "keys" && !name) return usage("keys create needs --name");
|
|
123
|
+
if (command === "keys" && (!Number.isSafeInteger(pepperVersion) || pepperVersion < 1))
|
|
124
|
+
return usage("--pepper-version takes a whole number from 1");
|
|
161
125
|
const config = flag(args, "--config");
|
|
162
126
|
const remote = args.includes("--remote");
|
|
163
127
|
// Resolved before any write, as every remote command does (p0.10 §4): no credentials is the
|
|
@@ -185,43 +149,35 @@ async function bootstrap(
|
|
|
185
149
|
: {}),
|
|
186
150
|
};
|
|
187
151
|
const now = Date.now();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const b = sub === "invite-owner" ? await ownerInviteSql(target, now) : await recoverSql(target, now);
|
|
152
|
+
if (command === "staff" && email) {
|
|
153
|
+
const b = sub === "invite-owner" ? await ownerInviteSql(email, now) : await recoverSql(email, now);
|
|
191
154
|
const status = execute(b, opts);
|
|
192
155
|
if (status !== 0) return failed(status);
|
|
193
156
|
const expires = new Date(b.expiresAt).toISOString();
|
|
194
|
-
console.log(`invite ${b.inviteId} for ${
|
|
157
|
+
console.log(`invite ${b.inviteId} for ${email}, valid until ${expires}`);
|
|
195
158
|
console.log(`accept with POST /auth/admin/invites/accept and this token (shown once):\n${b.token}`);
|
|
196
159
|
const origin = await inviteOrigin(args, opts.remote, cwd, env, cf);
|
|
197
160
|
// The token rides in the fragment, which no server log or Referer carries (p0.9 §3.5).
|
|
198
161
|
const link = origin ? `${origin}/app/invite#${b.token}` : null;
|
|
199
162
|
if (link) console.log(`or open the admin's invite page:\n${link}`);
|
|
200
|
-
result({ ok: true, invite: b.inviteId, email
|
|
163
|
+
result({ ok: true, invite: b.inviteId, email, expires_at: expires, token: b.token, link });
|
|
201
164
|
return 0;
|
|
202
165
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (status !== 0) return failed(status);
|
|
219
|
-
console.log(`storefront ${b.storefrontId}, key ${b.keyId}`);
|
|
220
|
-
console.log(`publishable key (send as x-kedge-key; shown once):\n${b.key}`);
|
|
221
|
-
result({ ok: true, storefront: b.storefrontId, key_id: b.keyId, key: b.key });
|
|
222
|
-
return 0;
|
|
223
|
-
}
|
|
224
|
-
return usage(USAGE);
|
|
166
|
+
// keys create --publishable, its name and pepper version checked above.
|
|
167
|
+
const origins = args.flatMap((a, i) => (a === "--origin" && args[i + 1] ? [args[i + 1] as string] : []));
|
|
168
|
+
const pepper = env[`KEDGE_KEY_PEPPER_V${pepperVersion}`];
|
|
169
|
+
// The pepper lives in the merchant's own vault, or only in the Worker: theirs to hand over (p0.10 §5).
|
|
170
|
+
if (!pepper)
|
|
171
|
+
throw new NeedsPerson(
|
|
172
|
+
`set KEDGE_KEY_PEPPER_V${pepperVersion} to the Worker's pepper secret, or create the key with --url and --sign-in, which keeps the pepper in the Worker`,
|
|
173
|
+
);
|
|
174
|
+
const b = await publishableKeySql({ name: name as string, origins, pepperVersion, pepper }, now);
|
|
175
|
+
const status = execute(b, opts);
|
|
176
|
+
if (status !== 0) return failed(status);
|
|
177
|
+
console.log(`storefront ${b.storefrontId}, key ${b.keyId}`);
|
|
178
|
+
console.log(`publishable key (send as x-kedge-key; shown once):\n${b.key}`);
|
|
179
|
+
result({ ok: true, storefront: b.storefrontId, key_id: b.keyId, key: b.key });
|
|
180
|
+
return 0;
|
|
225
181
|
}
|
|
226
182
|
|
|
227
183
|
const INDEX_START = "<!-- kedge:docs-index:start -->";
|
|
@@ -297,9 +253,6 @@ interface RunDeps {
|
|
|
297
253
|
emit?: (result: Record<string, unknown>) => void;
|
|
298
254
|
}
|
|
299
255
|
|
|
300
|
-
/** The commands an agent reads the result of (p0.10 §5); doctor has its own `--json`. */
|
|
301
|
-
const JSON_COMMANDS = new Set(["deploy", "preview", "staff", "keys", "seed", "smoke"]);
|
|
302
|
-
|
|
303
256
|
async function run(argv: string[], cwd: string, deps: RunDeps & { json: boolean }): Promise<number> {
|
|
304
257
|
const [command, ...flags] = argv;
|
|
305
258
|
const out = {
|
|
@@ -307,7 +260,9 @@ async function run(argv: string[], cwd: string, deps: RunDeps & { json: boolean
|
|
|
307
260
|
...(deps.emit ? { emit: deps.emit } : {}),
|
|
308
261
|
...(deps.execute ? { execute: deps.execute } : {}),
|
|
309
262
|
};
|
|
310
|
-
|
|
263
|
+
// Only `keys create` goes over HTTP: anything else is bootstrap's usage error, before a sign-in
|
|
264
|
+
// or a request, so a mistyped subcommand never creates a key (codex code r2).
|
|
265
|
+
if (command === "keys" && flags[0] === "create" && flag(flags, "--url")) {
|
|
311
266
|
const d0: CommandDeps = { cwd, env: deps.env ?? process.env, coreDir: coreMigrationsDir(), ...out };
|
|
312
267
|
return keysOverHttp(flags, d0);
|
|
313
268
|
}
|
|
@@ -410,7 +365,7 @@ async function run(argv: string[], cwd: string, deps: RunDeps & { json: boolean
|
|
|
410
365
|
doctor: (c: string) => main(["doctor"], c),
|
|
411
366
|
});
|
|
412
367
|
if (command !== "generate") {
|
|
413
|
-
console.error(USAGE);
|
|
368
|
+
console.error(command ? `unknown command: ${command}\n\n${USAGE}` : USAGE);
|
|
414
369
|
return 2;
|
|
415
370
|
}
|
|
416
371
|
const check = flags.includes("--check");
|
|
@@ -470,6 +425,8 @@ async function run(argv: string[], cwd: string, deps: RunDeps & { json: boolean
|
|
|
470
425
|
}
|
|
471
426
|
}
|
|
472
427
|
|
|
428
|
+
const HELP = new Set(["--help", "-h"]);
|
|
429
|
+
|
|
473
430
|
/**
|
|
474
431
|
* The CLI. A handoff from any command ends it with exit 3 and one sentence for
|
|
475
432
|
* whoever runs it to pass on (p0.10 §5).
|
|
@@ -477,6 +434,23 @@ async function run(argv: string[], cwd: string, deps: RunDeps & { json: boolean
|
|
|
477
434
|
export async function main(argv: string[], cwd: string, deps: RunDeps = {}): Promise<number> {
|
|
478
435
|
const [command, ...flags] = argv;
|
|
479
436
|
const json = flags.includes("--json") && JSON_COMMANDS.has(command ?? "");
|
|
437
|
+
// Help runs nothing, whatever else is on the line: an agent asks a command for --help
|
|
438
|
+
// before running it, and `kedge dev --help` that starts the server blocks it until its
|
|
439
|
+
// tool times out (p0.11.1 row 2). One check here, so no command can forget it.
|
|
440
|
+
if (command === "help" || (command && HELP.has(command)) || flags.some((f) => HELP.has(f))) {
|
|
441
|
+
const of = command === "help" || HELP.has(command ?? "") ? flags.find((f) => !f.startsWith("-")) : command;
|
|
442
|
+
const text = of ? usageOf(of) : USAGE;
|
|
443
|
+
if (text === null) {
|
|
444
|
+
console.error(`unknown command: ${of}\n\n${USAGE}`);
|
|
445
|
+
return 2;
|
|
446
|
+
}
|
|
447
|
+
// One object under --json when the command it's for takes --json, `kedge help <command>` too, and
|
|
448
|
+
// doctor, whose --json is its own (codex code r1, r2).
|
|
449
|
+
if (flags.includes("--json") && (!of || of === "doctor" || JSON_COMMANDS.has(of)))
|
|
450
|
+
(deps.emit ?? emit)({ ok: true, usage: text });
|
|
451
|
+
else console.log(text);
|
|
452
|
+
return 0;
|
|
453
|
+
}
|
|
480
454
|
return withJsonOutput(json, async () => {
|
|
481
455
|
try {
|
|
482
456
|
return await run(argv, cwd, { ...deps, json });
|
package/src/smoke.ts
CHANGED
|
@@ -207,7 +207,8 @@ async function run(db: DatabaseSync, target: StoreTarget, name: string, d: Smoke
|
|
|
207
207
|
`the order is ${status}${number ? ` (${number})` : ", with no number"}, so it isn't placed`,
|
|
208
208
|
);
|
|
209
209
|
set({ number });
|
|
210
|
-
|
|
210
|
+
// An open order's expires_at reads like a hold that lapses, and agents told merchants so (p0.11.1 row 8).
|
|
211
|
+
d.log(`order ${number} is open (smoke run ${name}): placed and unpaid, its unit reserved until it's fulfilled or canceled`);
|
|
211
212
|
return { run: name, order_id: orderId, number, status };
|
|
212
213
|
}
|
|
213
214
|
|
package/src/usage.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// The `kedge` command's usage, the one home for it (p0.11.1 §2): `kedge --help`
|
|
2
|
+
// prints it all, and `kedge <command> --help` the parts for that command.
|
|
3
|
+
|
|
4
|
+
const SYNOPSIS = `usage: kedge generate [--check] [--supersede <op-id>]
|
|
5
|
+
kedge check [--fast]
|
|
6
|
+
kedge doctor [--remote] [--json]
|
|
7
|
+
kedge dev
|
|
8
|
+
kedge migrate [--remote]
|
|
9
|
+
kedge deploy [--maintenance-window <from>/<to>] [--var K=V]... [--adopt <name>]...
|
|
10
|
+
[--resume | --take-over <op-id> --confirm-stopped | --abort-cutover | --supersede <op-id>]
|
|
11
|
+
[--resolve-intent <id> --not-executed] [--record-active --reason <what>]
|
|
12
|
+
[--adopt-existing --from-release <version>] [--allow-older]
|
|
13
|
+
kedge preview [--name N] [--var K=V]... [--fresh] [--secret-env NAME]...
|
|
14
|
+
kedge preview build [--name N] | kedge preview apply <plan.tar|-> [--store S] [--name N]
|
|
15
|
+
kedge preview destroy [--name N] [--sweep]
|
|
16
|
+
kedge eject <unit> --reason "<why>" | kedge eject --rebase <unit>
|
|
17
|
+
kedge upgrade --to <version> [--from-packs <dir>]
|
|
18
|
+
kedge doctor --core-diff [--upstream <manifest>] [--strict]
|
|
19
|
+
kedge secret put <NAME> | kedge secret delete <NAME>
|
|
20
|
+
kedge restore --list
|
|
21
|
+
kedge seed --sample apparel --url <url> [--key <sk_…> | --session <token>] [--live]
|
|
22
|
+
kedge smoke --url <url> [--run <name>] [--live]
|
|
23
|
+
kedge keys create (--publishable [--origin <url>]... | --secret --grant <permission>...)
|
|
24
|
+
--url <url> (--session <token> | --sign-in <email> [--answers-stdin] | --save) [--name <name>]
|
|
25
|
+
kedge staff invite-owner <email> [--database DB] [--remote] [--url <url>] [--config wrangler.jsonc]
|
|
26
|
+
kedge staff recover <email> [--database DB] [--remote] [--config wrangler.jsonc]
|
|
27
|
+
kedge keys create --publishable --name <name> [--origin <origin>]... [--pepper-version N]
|
|
28
|
+
[--database DB] [--remote] [--config wrangler.jsonc]`;
|
|
29
|
+
|
|
30
|
+
const DESCRIPTIONS = ` generate compose core, plugin and local migrations into migrations/
|
|
31
|
+
and update kedge.lock.json
|
|
32
|
+
generate --check fail if generate would write anything (for CI)
|
|
33
|
+
dev fresh local secrets once, local migrations, then wrangler dev
|
|
34
|
+
migrate local migrations; --remote applies N−1 files to production, refusing a cutover
|
|
35
|
+
deploy migrations before the Worker, under an operation in <store>-ops (§6)
|
|
36
|
+
preview a separate Worker with its own D1, queue and Durable Objects, capture
|
|
37
|
+
sinks for every effect, seeded, then test/scenarios/*.http.ts against it
|
|
38
|
+
secret put or delete one of the Worker's secrets; put reads the value from
|
|
39
|
+
stdin, 32 bytes or more
|
|
40
|
+
eject copy one unit of core into src/overrides, its upstream hash recorded
|
|
41
|
+
upgrade every @kedge package to a release one supported step on, its codemods,
|
|
42
|
+
UPGRADE-TODO.md, then generate and check; nothing is committed
|
|
43
|
+
restore --list the restore points; restoring waits for P1.18
|
|
44
|
+
seed a sample catalogue (--sample apparel) on one of this store's own URLs,
|
|
45
|
+
through its admin API
|
|
46
|
+
smoke a test order on one of this store's own URLs: a cart, completion and
|
|
47
|
+
placement, resumed by its --run name until it has its number
|
|
48
|
+
check typecheck, generate --check, doctor, then the store's tests;
|
|
49
|
+
--fast stops before the tests (the Stop hook's)
|
|
50
|
+
doctor the store's offline checks: lock, installed core, error map,
|
|
51
|
+
invariant catalog, code lint, wrangler.jsonc
|
|
52
|
+
staff invite-owner the first Owner's one-time invite (only while no active Owner exists)
|
|
53
|
+
staff recover reset a sole Owner's factors and print a reenrol invite
|
|
54
|
+
keys create a storefront and its first publishable key, or a secret key. With
|
|
55
|
+
--url, through the store's own API, signed in as staff: the pepper
|
|
56
|
+
stays in the Worker. Without it, --publishable is one D1 batch that
|
|
57
|
+
reads the pepper from KEDGE_KEY_PEPPER_V<N> (N defaults to 1)`;
|
|
58
|
+
|
|
59
|
+
/** The commands an agent reads the result of (p0.10 §5); doctor has its own `--json`. */
|
|
60
|
+
export const JSON_COMMANDS = new Set(["deploy", "preview", "staff", "keys", "seed", "smoke"]);
|
|
61
|
+
|
|
62
|
+
const BOOTSTRAP_NOTE = ` The staff commands, and keys create without --url, run one audited batch through
|
|
63
|
+
wrangler d1 execute (--local unless --remote) and print the secret once.`;
|
|
64
|
+
|
|
65
|
+
const JSON_NOTE = ` deploy, preview, seed, smoke, staff and keys take --json: one object on stdout when
|
|
66
|
+
they end ({"ok": true, ...}, or {"ok": false, "error"} / {"ok": false, "handoff"}),
|
|
67
|
+
everything else on stderr. Exit 3 means a person must act: the last line of stderr
|
|
68
|
+
starts "for you:". --sign-in asks only a terminal; --answers-stdin reads one line
|
|
69
|
+
per question from a pipe, waiting 30 seconds for each.`;
|
|
70
|
+
|
|
71
|
+
export const USAGE = [SYNOPSIS, DESCRIPTIONS, BOOTSTRAP_NOTE, JSON_NOTE].join("\n\n");
|
|
72
|
+
|
|
73
|
+
/** Entries of a block: an entry is its first line and the lines indented past it. */
|
|
74
|
+
function entries(text: string, starts: (line: string) => boolean): string[][] {
|
|
75
|
+
const out: string[][] = [];
|
|
76
|
+
for (const line of text.split("\n")) {
|
|
77
|
+
if (starts(line)) out.push([line]);
|
|
78
|
+
else out.at(-1)?.push(line);
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Whether a line's words begin with `words` (`kedge staff` names `kedge staff recover`, never `kedge staffs`). */
|
|
84
|
+
const names = (line: string, words: string) => line === words || line.startsWith(`${words} `);
|
|
85
|
+
|
|
86
|
+
/** The synopsis entries for a command or subcommand (`staff`, `staff invite-owner`), one string each. */
|
|
87
|
+
export function synopsisOf(words: string): string[] {
|
|
88
|
+
return entries(SYNOPSIS, (l) => /^(usage: | {7})kedge /.test(l))
|
|
89
|
+
.filter((e) => names((e[0] as string).replace(/^(usage: | {7})/, ""), `kedge ${words}`))
|
|
90
|
+
.map((e) => e.join("\n"));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* A command's help: its synopsis, its descriptions and the notes that apply to
|
|
95
|
+
* it, or null for a command that isn't one. Every command `run()` dispatches has
|
|
96
|
+
* a synopsis and a description, which a test holds.
|
|
97
|
+
*/
|
|
98
|
+
export function usageOf(command: string): string | null {
|
|
99
|
+
const synopsis = synopsisOf(command).map((e) => e.replace(/^(usage: | {7})/, " "));
|
|
100
|
+
if (!synopsis.length) return null;
|
|
101
|
+
const descriptions = entries(DESCRIPTIONS, (l) => /^ {2}\S/.test(l))
|
|
102
|
+
.filter((e) => names((e[0] as string).trim(), command))
|
|
103
|
+
.map((e) => e.join("\n"));
|
|
104
|
+
return [
|
|
105
|
+
`usage: ${synopsis.join("\n").slice(7)}`,
|
|
106
|
+
descriptions.join("\n"),
|
|
107
|
+
...(command === "staff" || command === "keys" ? [BOOTSTRAP_NOTE] : []),
|
|
108
|
+
...(JSON_COMMANDS.has(command) ? [JSON_NOTE] : []),
|
|
109
|
+
].join("\n\n");
|
|
110
|
+
}
|