@sanity/runtime-cli 8.0.3 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -21
- package/dist/actions/sanity/examples.d.ts +26 -0
- package/dist/actions/sanity/examples.js +176 -0
- package/dist/commands/blueprints/add.d.ts +1 -0
- package/dist/commands/blueprints/add.js +6 -1
- package/dist/commands/blueprints/init.d.ts +1 -0
- package/dist/commands/blueprints/init.js +5 -0
- package/dist/cores/blueprints/add.d.ts +1 -0
- package/dist/cores/blueprints/add.js +62 -2
- package/dist/cores/blueprints/init.d.ts +1 -0
- package/dist/cores/blueprints/init.js +58 -7
- package/dist/cores/blueprints/plan.js +1 -1
- package/dist/utils/display/presenters.d.ts +1 -0
- package/dist/utils/display/presenters.js +7 -0
- package/dist/utils/other/github.d.ts +2 -0
- package/dist/utils/other/github.js +30 -0
- package/oclif.manifest.json +35 -2
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ $ npm install -g @sanity/runtime-cli
|
|
|
20
20
|
$ sanity-run COMMAND
|
|
21
21
|
running command...
|
|
22
22
|
$ sanity-run (--version)
|
|
23
|
-
@sanity/runtime-cli/8.0
|
|
23
|
+
@sanity/runtime-cli/8.1.0 linux-x64 node-v22.16.0
|
|
24
24
|
$ sanity-run --help [COMMAND]
|
|
25
25
|
USAGE
|
|
26
26
|
$ sanity-run COMMAND
|
|
@@ -52,15 +52,16 @@ Add a Resource to a Blueprint
|
|
|
52
52
|
|
|
53
53
|
```
|
|
54
54
|
USAGE
|
|
55
|
-
$ sanity-run blueprints add TYPE [--
|
|
56
|
-
|
|
55
|
+
$ sanity-run blueprints add TYPE [--example <value> | -n <value> | --fn-type document-publish | --fn-language
|
|
56
|
+
ts|js | --javascript | --fn-helpers | --fn-installer skip|npm|pnpm|yarn] [-i | ]
|
|
57
57
|
|
|
58
58
|
ARGUMENTS
|
|
59
|
-
TYPE (function) Type of
|
|
59
|
+
TYPE (function) Type of Resource to add (e.g. function)
|
|
60
60
|
|
|
61
61
|
FLAGS
|
|
62
62
|
-i, --install Shortcut for --fn-installer npm
|
|
63
63
|
-n, --name=<value> Name of the Resource to add
|
|
64
|
+
--example=<value> Example to use for the Resource
|
|
64
65
|
--[no-]fn-helpers Add helpers to the new Function
|
|
65
66
|
--fn-installer=<option> How to install the @sanity/functions helpers
|
|
66
67
|
<options: skip|npm|pnpm|yarn>
|
|
@@ -85,7 +86,7 @@ EXAMPLES
|
|
|
85
86
|
$ sanity-run blueprints add function --name my-function --fn-type document-publish --lang js
|
|
86
87
|
```
|
|
87
88
|
|
|
88
|
-
_See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v8.0
|
|
89
|
+
_See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v8.1.0/src/commands/blueprints/add.ts)_
|
|
89
90
|
|
|
90
91
|
## `sanity-run blueprints config`
|
|
91
92
|
|
|
@@ -116,7 +117,7 @@ EXAMPLES
|
|
|
116
117
|
$ sanity-run blueprints config --edit --project-id <projectId> --stack-id <stackId>
|
|
117
118
|
```
|
|
118
119
|
|
|
119
|
-
_See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v8.0
|
|
120
|
+
_See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v8.1.0/src/commands/blueprints/config.ts)_
|
|
120
121
|
|
|
121
122
|
## `sanity-run blueprints deploy`
|
|
122
123
|
|
|
@@ -138,7 +139,7 @@ EXAMPLES
|
|
|
138
139
|
$ sanity-run blueprints deploy --no-wait
|
|
139
140
|
```
|
|
140
141
|
|
|
141
|
-
_See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v8.0
|
|
142
|
+
_See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v8.1.0/src/commands/blueprints/deploy.ts)_
|
|
142
143
|
|
|
143
144
|
## `sanity-run blueprints destroy`
|
|
144
145
|
|
|
@@ -163,7 +164,7 @@ EXAMPLES
|
|
|
163
164
|
$ sanity-run blueprints destroy --stack-id <stackId> --project-id <projectId> --force --no-wait
|
|
164
165
|
```
|
|
165
166
|
|
|
166
|
-
_See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v8.0
|
|
167
|
+
_See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v8.1.0/src/commands/blueprints/destroy.ts)_
|
|
167
168
|
|
|
168
169
|
## `sanity-run blueprints info`
|
|
169
170
|
|
|
@@ -185,7 +186,7 @@ EXAMPLES
|
|
|
185
186
|
$ sanity-run blueprints info --stack-id <stackId>
|
|
186
187
|
```
|
|
187
188
|
|
|
188
|
-
_See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v8.0
|
|
189
|
+
_See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v8.1.0/src/commands/blueprints/info.ts)_
|
|
189
190
|
|
|
190
191
|
## `sanity-run blueprints init [DIR]`
|
|
191
192
|
|
|
@@ -193,8 +194,8 @@ Initialize a new Blueprint
|
|
|
193
194
|
|
|
194
195
|
```
|
|
195
196
|
USAGE
|
|
196
|
-
$ sanity-run blueprints init [DIR] [--dir <value>] [--blueprint-type json|js|ts
|
|
197
|
-
|
|
197
|
+
$ sanity-run blueprints init [DIR] [--dir <value>] [--example <value> | --blueprint-type json|js|ts | --stack-id
|
|
198
|
+
<value> | --stack-name <value>] [--project-id <value>]
|
|
198
199
|
|
|
199
200
|
ARGUMENTS
|
|
200
201
|
DIR Directory to create the Blueprint in
|
|
@@ -203,6 +204,7 @@ FLAGS
|
|
|
203
204
|
--blueprint-type=<option> Blueprint manifest type to use for the Blueprint
|
|
204
205
|
<options: json|js|ts>
|
|
205
206
|
--dir=<value> Directory to create the Blueprint in
|
|
207
|
+
--example=<value> Example to use for the Blueprint
|
|
206
208
|
--project-id=<value> Sanity Project ID to use for the Blueprint
|
|
207
209
|
--stack-id=<value> Existing Stack ID to use for the Blueprint
|
|
208
210
|
--stack-name=<value> Name to use for a NEW Stack
|
|
@@ -222,7 +224,7 @@ EXAMPLES
|
|
|
222
224
|
$ sanity-run blueprints init --blueprint-type <json|js|ts> --stack-name <stackName>
|
|
223
225
|
```
|
|
224
226
|
|
|
225
|
-
_See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v8.0
|
|
227
|
+
_See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v8.1.0/src/commands/blueprints/init.ts)_
|
|
226
228
|
|
|
227
229
|
## `sanity-run blueprints logs`
|
|
228
230
|
|
|
@@ -244,7 +246,7 @@ EXAMPLES
|
|
|
244
246
|
$ sanity-run blueprints logs --watch
|
|
245
247
|
```
|
|
246
248
|
|
|
247
|
-
_See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v8.0
|
|
249
|
+
_See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v8.1.0/src/commands/blueprints/logs.ts)_
|
|
248
250
|
|
|
249
251
|
## `sanity-run blueprints plan`
|
|
250
252
|
|
|
@@ -261,7 +263,7 @@ EXAMPLES
|
|
|
261
263
|
$ sanity-run blueprints plan
|
|
262
264
|
```
|
|
263
265
|
|
|
264
|
-
_See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v8.0
|
|
266
|
+
_See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v8.1.0/src/commands/blueprints/plan.ts)_
|
|
265
267
|
|
|
266
268
|
## `sanity-run blueprints stacks`
|
|
267
269
|
|
|
@@ -283,7 +285,7 @@ EXAMPLES
|
|
|
283
285
|
$ sanity-run blueprints stacks --project-id <projectId>
|
|
284
286
|
```
|
|
285
287
|
|
|
286
|
-
_See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v8.0
|
|
288
|
+
_See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v8.1.0/src/commands/blueprints/stacks.ts)_
|
|
287
289
|
|
|
288
290
|
## `sanity-run functions dev`
|
|
289
291
|
|
|
@@ -303,7 +305,7 @@ EXAMPLES
|
|
|
303
305
|
$ sanity-run functions dev --port 8974
|
|
304
306
|
```
|
|
305
307
|
|
|
306
|
-
_See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v8.0
|
|
308
|
+
_See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v8.1.0/src/commands/functions/dev.ts)_
|
|
307
309
|
|
|
308
310
|
## `sanity-run functions env add NAME KEY VALUE`
|
|
309
311
|
|
|
@@ -325,7 +327,7 @@ EXAMPLES
|
|
|
325
327
|
$ sanity-run functions env add MyFunction API_URL https://api.example.com/
|
|
326
328
|
```
|
|
327
329
|
|
|
328
|
-
_See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v8.0
|
|
330
|
+
_See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v8.1.0/src/commands/functions/env/add.ts)_
|
|
329
331
|
|
|
330
332
|
## `sanity-run functions env list NAME`
|
|
331
333
|
|
|
@@ -345,7 +347,7 @@ EXAMPLES
|
|
|
345
347
|
$ sanity-run functions env list MyFunction
|
|
346
348
|
```
|
|
347
349
|
|
|
348
|
-
_See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v8.0
|
|
350
|
+
_See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v8.1.0/src/commands/functions/env/list.ts)_
|
|
349
351
|
|
|
350
352
|
## `sanity-run functions env remove NAME KEY`
|
|
351
353
|
|
|
@@ -366,7 +368,7 @@ EXAMPLES
|
|
|
366
368
|
$ sanity-run functions env remove MyFunction API_URL
|
|
367
369
|
```
|
|
368
370
|
|
|
369
|
-
_See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v8.0
|
|
371
|
+
_See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v8.1.0/src/commands/functions/env/remove.ts)_
|
|
370
372
|
|
|
371
373
|
## `sanity-run functions logs NAME`
|
|
372
374
|
|
|
@@ -400,7 +402,7 @@ EXAMPLES
|
|
|
400
402
|
$ sanity-run functions logs <name> --delete
|
|
401
403
|
```
|
|
402
404
|
|
|
403
|
-
_See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v8.0
|
|
405
|
+
_See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v8.1.0/src/commands/functions/logs.ts)_
|
|
404
406
|
|
|
405
407
|
## `sanity-run functions test NAME`
|
|
406
408
|
|
|
@@ -433,7 +435,7 @@ EXAMPLES
|
|
|
433
435
|
$ sanity-run functions test <name> --data '{ "id": 1 }' --timeout 60
|
|
434
436
|
```
|
|
435
437
|
|
|
436
|
-
_See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v8.0
|
|
438
|
+
_See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v8.1.0/src/commands/functions/test.ts)_
|
|
437
439
|
|
|
438
440
|
## `sanity-run help [COMMAND]`
|
|
439
441
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const EXAMPLES_CACHE_DIR: string;
|
|
2
|
+
declare const EXAMPLE_TYPES: {
|
|
3
|
+
blueprint: string;
|
|
4
|
+
function: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function verifyExampleExists({ type, name, }: {
|
|
7
|
+
type: keyof typeof EXAMPLE_TYPES;
|
|
8
|
+
name: string;
|
|
9
|
+
}): Promise<boolean>;
|
|
10
|
+
/**
|
|
11
|
+
* Downloads an example from the examples repo and writes it to disk.
|
|
12
|
+
* @sideEffect Creates the example directory and writes the example to disk.
|
|
13
|
+
* @returns The example files and directory and the function config if it exists.
|
|
14
|
+
*/
|
|
15
|
+
export declare function writeExample({ ownerRepo, exampleType, exampleName, dir, }: {
|
|
16
|
+
ownerRepo?: string;
|
|
17
|
+
exampleType: keyof typeof EXAMPLE_TYPES;
|
|
18
|
+
exampleName: string;
|
|
19
|
+
dir?: string;
|
|
20
|
+
}): Promise<false | {
|
|
21
|
+
files: Record<string, string>;
|
|
22
|
+
dir: string;
|
|
23
|
+
instructions: string | null;
|
|
24
|
+
functionConfig: Record<string, unknown> | null;
|
|
25
|
+
}>;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { createReadStream, createWriteStream, existsSync, mkdirSync, statSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { pipeline } from 'node:stream/promises';
|
|
5
|
+
import { createGunzip } from 'node:zlib';
|
|
6
|
+
import { extract } from 'tar-stream';
|
|
7
|
+
import { gitHubRequest } from '../../utils/other/github.js';
|
|
8
|
+
export const EXAMPLES_CACHE_DIR = join(tmpdir(), 'sanity-examples');
|
|
9
|
+
const EXAMPLES_REPO = 'sanity-io/sanity';
|
|
10
|
+
const EXAMPLES_DIR = 'examples';
|
|
11
|
+
const BLUEPRINTS_DIR = `${EXAMPLES_DIR}/blueprints`;
|
|
12
|
+
const FUNCTIONS_DIR = `${EXAMPLES_DIR}/functions`;
|
|
13
|
+
const EXAMPLE_TYPES = {
|
|
14
|
+
blueprint: BLUEPRINTS_DIR,
|
|
15
|
+
function: FUNCTIONS_DIR,
|
|
16
|
+
};
|
|
17
|
+
export async function verifyExampleExists({ type, name, }) {
|
|
18
|
+
const examplePath = `${EXAMPLE_TYPES[type]}/${name}`;
|
|
19
|
+
const path = `/repos/${EXAMPLES_REPO}/contents/${examplePath}`;
|
|
20
|
+
const response = await gitHubRequest(path);
|
|
21
|
+
return response.ok;
|
|
22
|
+
}
|
|
23
|
+
async function downloadRepoArchive(ownerRepo, ref = 'main') {
|
|
24
|
+
// cache the archive in a temp directory
|
|
25
|
+
const cacheKey = `${ownerRepo.replace('/', '-')}-${ref}.tar.gz`;
|
|
26
|
+
const cacheDir = EXAMPLES_CACHE_DIR;
|
|
27
|
+
const cachePath = join(cacheDir, cacheKey);
|
|
28
|
+
if (existsSync(cachePath)) {
|
|
29
|
+
const stats = statSync(cachePath);
|
|
30
|
+
const ageMinutes = (Date.now() - stats.mtime.getTime()) / (1000 * 60);
|
|
31
|
+
if (ageMinutes < 5)
|
|
32
|
+
return createReadStream(cachePath);
|
|
33
|
+
}
|
|
34
|
+
const path = `/repos/${ownerRepo}/tarball/${ref}`;
|
|
35
|
+
try {
|
|
36
|
+
const response = await gitHubRequest(path);
|
|
37
|
+
if (!response.ok)
|
|
38
|
+
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
|
|
39
|
+
if (!response.body)
|
|
40
|
+
throw new Error('No response body received');
|
|
41
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
42
|
+
const cacheWriteStream = createWriteStream(cachePath);
|
|
43
|
+
try {
|
|
44
|
+
// converting web streams to node streams is awkward
|
|
45
|
+
await pipeline(response.body, cacheWriteStream);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.debug(error);
|
|
49
|
+
return response.body;
|
|
50
|
+
}
|
|
51
|
+
return createReadStream(cachePath);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error('❌ Error:', error instanceof Error ? error.message : 'Unknown error');
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function extractExampleFromArchive({ archiveStream, exampleType, exampleName, }) {
|
|
59
|
+
const fullPath = `${EXAMPLE_TYPES[exampleType]}/${exampleName}`;
|
|
60
|
+
const files = {};
|
|
61
|
+
let repoPrefix = null;
|
|
62
|
+
const extractStream = extract();
|
|
63
|
+
extractStream.on('entry', (header, stream, next) => {
|
|
64
|
+
const { name, type } = header;
|
|
65
|
+
// GitHub tarballs have a top-level directory like "repo-name-commit-hash/"
|
|
66
|
+
// detect it and strip it
|
|
67
|
+
if (!repoPrefix && name.includes('/'))
|
|
68
|
+
repoPrefix = name.split('/')[0];
|
|
69
|
+
// remove the repo prefix to get the actual file path
|
|
70
|
+
const cleanPath = repoPrefix ? name.replace(`${repoPrefix}/`, '') : name;
|
|
71
|
+
// check if this file is in our target example directory
|
|
72
|
+
if (cleanPath.startsWith(`${fullPath}/`) || cleanPath === fullPath) {
|
|
73
|
+
if (type === 'file') {
|
|
74
|
+
const chunks = [];
|
|
75
|
+
stream.on('data', (chunk) => chunks.push(chunk));
|
|
76
|
+
stream.on('end', () => {
|
|
77
|
+
const content = Buffer.concat(chunks).toString('utf8');
|
|
78
|
+
files[cleanPath.replace(`${fullPath}/`, '')] = content;
|
|
79
|
+
next();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
stream.resume(); // skip directories
|
|
84
|
+
next();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
stream.resume();
|
|
89
|
+
next();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
// pipes!
|
|
93
|
+
await pipeline(archiveStream, createGunzip(), extractStream);
|
|
94
|
+
if (Object.keys(files).length === 0)
|
|
95
|
+
return null;
|
|
96
|
+
return files;
|
|
97
|
+
}
|
|
98
|
+
function writeExampleToDisk(files, targetDir, exampleName) {
|
|
99
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
100
|
+
// Remove the example prefix from the file path for local storage
|
|
101
|
+
const localPath = filePath.startsWith(`${exampleName}/`)
|
|
102
|
+
? filePath.replace(`${exampleName}/`, '')
|
|
103
|
+
: filePath;
|
|
104
|
+
const fullPath = join(targetDir, localPath);
|
|
105
|
+
const dir = dirname(fullPath);
|
|
106
|
+
// Create directory if it doesn't exist
|
|
107
|
+
mkdirSync(dir, { recursive: true });
|
|
108
|
+
// Write file
|
|
109
|
+
writeFileSync(fullPath, content);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* @sideEffect Modifies the example files to remove the function config from the package.json.
|
|
114
|
+
* @returns The function config if it exists.
|
|
115
|
+
*/
|
|
116
|
+
function extractFunctionConfig(exampleFiles) {
|
|
117
|
+
const packageJson = exampleFiles['package.json'];
|
|
118
|
+
if (!packageJson)
|
|
119
|
+
return null;
|
|
120
|
+
let functionConfig = null;
|
|
121
|
+
try {
|
|
122
|
+
const packageJsonContent = JSON.parse(packageJson);
|
|
123
|
+
functionConfig = packageJsonContent.blueprintResourceItem;
|
|
124
|
+
packageJsonContent.blueprintResourceItem = undefined;
|
|
125
|
+
exampleFiles['package.json'] = JSON.stringify(packageJsonContent, null, 2);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
return functionConfig;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* @sideEffect Modifies the example files to remove the instructions from the package.json.
|
|
134
|
+
* @returns The instructions if they exist.
|
|
135
|
+
*/
|
|
136
|
+
function extractInstructions(exampleFiles) {
|
|
137
|
+
const packageJson = exampleFiles['package.json'];
|
|
138
|
+
if (!packageJson)
|
|
139
|
+
return null;
|
|
140
|
+
let instructions = null;
|
|
141
|
+
try {
|
|
142
|
+
const packageJsonContent = JSON.parse(packageJson);
|
|
143
|
+
instructions = packageJsonContent.exampleInstructions;
|
|
144
|
+
packageJsonContent.exampleInstructions = undefined;
|
|
145
|
+
exampleFiles['package.json'] = JSON.stringify(packageJsonContent, null, 2);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return instructions;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Downloads an example from the examples repo and writes it to disk.
|
|
154
|
+
* @sideEffect Creates the example directory and writes the example to disk.
|
|
155
|
+
* @returns The example files and directory and the function config if it exists.
|
|
156
|
+
*/
|
|
157
|
+
export async function writeExample({ ownerRepo = EXAMPLES_REPO, exampleType, exampleName, dir = './tmp', }) {
|
|
158
|
+
const archiveStream = await downloadRepoArchive(ownerRepo);
|
|
159
|
+
if (!archiveStream)
|
|
160
|
+
return false;
|
|
161
|
+
const exampleFiles = await extractExampleFromArchive({
|
|
162
|
+
archiveStream,
|
|
163
|
+
exampleType,
|
|
164
|
+
exampleName,
|
|
165
|
+
});
|
|
166
|
+
if (!exampleFiles)
|
|
167
|
+
return false;
|
|
168
|
+
let instructions = null;
|
|
169
|
+
instructions = extractInstructions(exampleFiles);
|
|
170
|
+
let functionConfig = null;
|
|
171
|
+
if (exampleType === 'function')
|
|
172
|
+
functionConfig = extractFunctionConfig(exampleFiles);
|
|
173
|
+
mkdirSync(dir, { recursive: true });
|
|
174
|
+
writeExampleToDisk(exampleFiles, dir, exampleName);
|
|
175
|
+
return { files: exampleFiles, dir, instructions, functionConfig };
|
|
176
|
+
}
|
|
@@ -6,6 +6,7 @@ export default class AddCommand extends BlueprintCommand<typeof AddCommand> {
|
|
|
6
6
|
type: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
7
|
};
|
|
8
8
|
static flags: {
|
|
9
|
+
example: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
10
|
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
'fn-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
12
|
'fn-language': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -12,12 +12,17 @@ export default class AddCommand extends BlueprintCommand {
|
|
|
12
12
|
];
|
|
13
13
|
static args = {
|
|
14
14
|
type: Args.string({
|
|
15
|
-
description: 'Type of
|
|
15
|
+
description: 'Type of Resource to add (e.g. function)',
|
|
16
16
|
options: ['function'],
|
|
17
17
|
required: true,
|
|
18
18
|
}),
|
|
19
19
|
};
|
|
20
20
|
static flags = {
|
|
21
|
+
example: Flags.string({
|
|
22
|
+
description: 'Example to use for the Resource',
|
|
23
|
+
aliases: ['recipe'],
|
|
24
|
+
exclusive: ['name', 'fn-type', 'fn-language', 'javascript', 'fn-helpers', 'fn-installer'], // set automatically
|
|
25
|
+
}),
|
|
21
26
|
name: Flags.string({
|
|
22
27
|
description: 'Name of the Resource to add',
|
|
23
28
|
char: 'n',
|
|
@@ -7,6 +7,7 @@ export default class InitCommand extends Command {
|
|
|
7
7
|
};
|
|
8
8
|
static flags: {
|
|
9
9
|
dir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
example: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
'blueprint-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
12
|
'project-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
13
|
'stack-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -19,6 +19,11 @@ export default class InitCommand extends Command {
|
|
|
19
19
|
dir: Flags.string({
|
|
20
20
|
description: 'Directory to create the Blueprint in',
|
|
21
21
|
}),
|
|
22
|
+
example: Flags.string({
|
|
23
|
+
description: 'Example to use for the Blueprint',
|
|
24
|
+
aliases: ['recipe'],
|
|
25
|
+
exclusive: ['blueprint-type', 'stack-id', 'stack-name'], // set automatically
|
|
26
|
+
}),
|
|
22
27
|
'blueprint-type': Flags.string({
|
|
23
28
|
description: 'Blueprint manifest type to use for the Blueprint',
|
|
24
29
|
options: ['json', 'js', 'ts'],
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
1
3
|
import { cwd } from 'node:process';
|
|
2
4
|
import { highlight } from 'cardinal';
|
|
3
5
|
import chalk from 'chalk';
|
|
4
6
|
import inquirer from 'inquirer';
|
|
5
7
|
import { createFunctionResource } from '../../actions/blueprints/resources.js';
|
|
8
|
+
import { verifyExampleExists, writeExample } from '../../actions/sanity/examples.js';
|
|
9
|
+
import { check, indent, warn } from '../../utils/display/presenters.js';
|
|
6
10
|
import { validateFunctionName } from '../../utils/validate/resource.js';
|
|
7
11
|
const FUNCTION_BLUEPRINT_RESOURCE_TEMPLATE = `
|
|
8
12
|
export default defineBlueprint({
|
|
@@ -14,9 +18,11 @@ const FUNCTION_BLUEPRINT_RESOURCE_TEMPLATE = `
|
|
|
14
18
|
})
|
|
15
19
|
`;
|
|
16
20
|
export async function blueprintAddCore(options) {
|
|
21
|
+
const root = cwd();
|
|
17
22
|
const { bin = 'sanity', log, blueprint, args, flags } = options;
|
|
23
|
+
const { blueprintFilePath } = blueprint.fileInfo;
|
|
18
24
|
const { type: resourceType } = args;
|
|
19
|
-
const { name: flagResourceName, 'fn-type': flagFnType, javascript: flagJs, 'fn-helpers': flagFnHelpers, install: flagI, 'fn-installer': flagFnInstaller, // can be 'skip'!
|
|
25
|
+
const { example: flagExample, name: flagResourceName, 'fn-type': flagFnType, javascript: flagJs, 'fn-helpers': flagFnHelpers, install: flagI, 'fn-installer': flagFnInstaller, // can be 'skip'!
|
|
20
26
|
} = flags;
|
|
21
27
|
let { language: flagFnLang } = flags;
|
|
22
28
|
flagFnLang = flagJs ? 'js' : flagFnLang;
|
|
@@ -26,6 +32,59 @@ export async function blueprintAddCore(options) {
|
|
|
26
32
|
error: `Unsupported Resource type: ${resourceType}`,
|
|
27
33
|
};
|
|
28
34
|
}
|
|
35
|
+
if (flagExample) {
|
|
36
|
+
// ! short circuit for examples
|
|
37
|
+
log(warn(`Example feature is experimental. Setting up "${flagExample}"...`)); // we need to...
|
|
38
|
+
// * 1. verify example exists in the recipes repo
|
|
39
|
+
const exampleExists = await verifyExampleExists({ type: 'function', name: flagExample });
|
|
40
|
+
if (!exampleExists) {
|
|
41
|
+
return { success: false, error: `Function example "${flagExample}" does not exist.` };
|
|
42
|
+
}
|
|
43
|
+
// * 2. download and write example to disk
|
|
44
|
+
// TODO: revisit path string handling; differs for fs operations vs. display
|
|
45
|
+
const exampleDir = join(dirname(blueprintFilePath), 'functions', flagExample);
|
|
46
|
+
if (existsSync(exampleDir)) {
|
|
47
|
+
return {
|
|
48
|
+
success: false,
|
|
49
|
+
error: `Directory "${exampleDir.replace(root, '')}" already exists.`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const addedExample = await writeExample({
|
|
53
|
+
exampleType: 'function',
|
|
54
|
+
exampleName: flagExample,
|
|
55
|
+
dir: exampleDir,
|
|
56
|
+
});
|
|
57
|
+
if (!addedExample) {
|
|
58
|
+
return { success: false, error: `Unable to download example "${flagExample}"` };
|
|
59
|
+
}
|
|
60
|
+
const { files, dir, instructions, functionConfig } = addedExample;
|
|
61
|
+
const newDir = dir.replace(root, '').replace(/^[/\\]+/, '');
|
|
62
|
+
for (const filePath of Object.keys(files)) {
|
|
63
|
+
log(check(`${chalk.bold('Created:')} ${newDir}/${filePath}`));
|
|
64
|
+
}
|
|
65
|
+
// * 3. print instructions
|
|
66
|
+
if (functionConfig) {
|
|
67
|
+
log('');
|
|
68
|
+
log(chalk.bold(`Add the following to ${blueprint.fileInfo.fileName}:`));
|
|
69
|
+
const configString = JSON.stringify(functionConfig, null, 2);
|
|
70
|
+
if (blueprint.fileInfo.extension === '.json') {
|
|
71
|
+
log(indent(highlight(configString)));
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// modify configString so it doesn't have quoted keys
|
|
75
|
+
const objectLiteral = configString.replace(/^(\s*)"([a-zA-Z_$][a-zA-Z0-9_$]*)":/gm, '$1$2:');
|
|
76
|
+
log(indent(highlight(`defineDocumentFunction(${objectLiteral})`)));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
log(warn('No Function config found in example.'));
|
|
81
|
+
}
|
|
82
|
+
if (instructions) {
|
|
83
|
+
log('');
|
|
84
|
+
log(instructions);
|
|
85
|
+
}
|
|
86
|
+
return { success: true };
|
|
87
|
+
}
|
|
29
88
|
if (flagI) {
|
|
30
89
|
if (flagFnInstaller) {
|
|
31
90
|
return {
|
|
@@ -90,13 +149,14 @@ export async function blueprintAddCore(options) {
|
|
|
90
149
|
if (installCommand)
|
|
91
150
|
log(`${chalk.magenta('Installing')} with ${installCommand}...`);
|
|
92
151
|
const { filePath, resourceAdded, resource } = await createFunctionResource({
|
|
152
|
+
blueprintFilePath,
|
|
93
153
|
name: fnName,
|
|
94
154
|
type: fnType,
|
|
95
155
|
lang: fnLang,
|
|
96
156
|
addHelpers,
|
|
97
157
|
installCommand,
|
|
98
158
|
});
|
|
99
|
-
log(`\nCreated function: ${filePath.replace(
|
|
159
|
+
log(`\nCreated function: ${filePath.replace(root, '')}`);
|
|
100
160
|
if (!resourceAdded) {
|
|
101
161
|
// print the resource definition for manual addition
|
|
102
162
|
log(`\n${chalk.bold('Add the Resource to your Blueprint:')}`);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
1
2
|
import { join } from 'node:path';
|
|
2
|
-
import { cwd } from 'node:process';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import inquirer from 'inquirer';
|
|
5
5
|
import { BLUEPRINT_CONFIG_FILE, BLUEPRINT_DIR, findBlueprintFile, writeBlueprintToDisk, writeConfigFile, writeGitignoreFile, writeOrUpdateNodeDependency, } from '../../actions/blueprints/blueprint.js';
|
|
6
6
|
import { createEmptyStack, getStack } from '../../actions/blueprints/stacks.js';
|
|
7
|
+
import { verifyExampleExists, writeExample } from '../../actions/sanity/examples.js';
|
|
7
8
|
import { getProject } from '../../actions/sanity/projects.js';
|
|
8
9
|
import { check, warn } from '../../utils/display/presenters.js';
|
|
9
10
|
import { promptForProject, promptForStackId } from '../../utils/display/prompt.js';
|
|
@@ -11,15 +12,66 @@ const LAUNCH_LIMIT_STACK_PER_PROJECT = true;
|
|
|
11
12
|
export async function blueprintInitCore(options) {
|
|
12
13
|
const { bin = 'sanity', log, token, args, flags } = options;
|
|
13
14
|
try {
|
|
14
|
-
const { 'blueprint-type': flagBlueprintType, 'project-id': flagProjectId, 'stack-id': flagStackId, 'stack-name': flagStackName,
|
|
15
|
+
const { dir: flagDir, example: flagExample, 'blueprint-type': flagBlueprintType, 'project-id': flagProjectId, 'stack-id': flagStackId, 'stack-name': flagStackName, } = flags;
|
|
15
16
|
const { dir: argDir } = args;
|
|
16
17
|
const providedDir = argDir || flagDir;
|
|
17
|
-
const
|
|
18
|
-
const dir = argDir || flagDir || here;
|
|
18
|
+
const dir = providedDir || '.';
|
|
19
19
|
const existingBlueprint = findBlueprintFile(dir);
|
|
20
20
|
if (existingBlueprint) {
|
|
21
21
|
return { success: false, error: 'Existing Blueprint found.' };
|
|
22
22
|
}
|
|
23
|
+
if (flagExample) {
|
|
24
|
+
// ! short circuit for examples
|
|
25
|
+
log(warn(`Example feature is experimental. Setting up "${flagExample}"...`));
|
|
26
|
+
// we need to...
|
|
27
|
+
// * 1. verify example exists in the recipes repo
|
|
28
|
+
const exampleExists = await verifyExampleExists({ type: 'blueprint', name: flagExample });
|
|
29
|
+
if (!exampleExists) {
|
|
30
|
+
return { success: false, error: `Blueprint example "${flagExample}" does not exist.` };
|
|
31
|
+
}
|
|
32
|
+
// * 2. get a projectId from the user
|
|
33
|
+
const projectId = flagProjectId || (await promptForProject({ token })).projectId;
|
|
34
|
+
// * 3. create empty stack with name from example name
|
|
35
|
+
const stack = await createEmptyStack({
|
|
36
|
+
token,
|
|
37
|
+
projectId,
|
|
38
|
+
name: `example-${flagExample}`,
|
|
39
|
+
projectBased: false,
|
|
40
|
+
});
|
|
41
|
+
// * 4. download and write example to disk
|
|
42
|
+
// take into account optional providedDir
|
|
43
|
+
const exampleDir = join(dir, providedDir ? '' : flagExample);
|
|
44
|
+
if (existsSync(exampleDir)) {
|
|
45
|
+
return { success: false, error: `Example directory "${exampleDir}" already exists.` };
|
|
46
|
+
}
|
|
47
|
+
const addedExample = await writeExample({
|
|
48
|
+
exampleType: 'blueprint',
|
|
49
|
+
exampleName: flagExample,
|
|
50
|
+
dir: exampleDir,
|
|
51
|
+
});
|
|
52
|
+
if (!addedExample) {
|
|
53
|
+
return { success: false, error: `Unable to download example "${flagExample}"` };
|
|
54
|
+
}
|
|
55
|
+
const { files, dir: newDir, instructions } = addedExample;
|
|
56
|
+
for (const filePath of Object.keys(files)) {
|
|
57
|
+
log(check(`${chalk.bold('Created:')} ${newDir}/${filePath}`));
|
|
58
|
+
}
|
|
59
|
+
const discoveredBlueprint = findBlueprintFile(exampleDir);
|
|
60
|
+
if (!discoveredBlueprint) {
|
|
61
|
+
return { success: false, error: 'Failed to find blueprint file.' };
|
|
62
|
+
}
|
|
63
|
+
const { blueprintFilePath } = discoveredBlueprint;
|
|
64
|
+
// * 5. write config file
|
|
65
|
+
writeConfigFile({ blueprintFilePath, projectId, stackId: stack.id });
|
|
66
|
+
log(check(`${chalk.bold('Configured:')} ${exampleDir}/${BLUEPRINT_DIR}/${BLUEPRINT_CONFIG_FILE}`));
|
|
67
|
+
// * 6. print next step
|
|
68
|
+
log(`\n Run "${chalk.bold.magenta(`cd ${exampleDir} && npm i`)}" and check out the README`);
|
|
69
|
+
if (instructions) {
|
|
70
|
+
log('');
|
|
71
|
+
log(instructions);
|
|
72
|
+
}
|
|
73
|
+
return { success: true };
|
|
74
|
+
}
|
|
23
75
|
const blueprintExtension = flagBlueprintType || (await promptForBlueprintType());
|
|
24
76
|
if (!blueprintExtension) {
|
|
25
77
|
return { success: false, error: 'Blueprint type is required.' };
|
|
@@ -77,12 +129,11 @@ export async function blueprintInitCore(options) {
|
|
|
77
129
|
}
|
|
78
130
|
const nextStepParts = [];
|
|
79
131
|
if (providedDir)
|
|
80
|
-
nextStepParts.push(`cd
|
|
132
|
+
nextStepParts.push(`cd ${providedDir}`);
|
|
81
133
|
if (blueprintExtension !== 'json')
|
|
82
134
|
nextStepParts.push('npm install');
|
|
83
135
|
nextStepParts.push(`${bin} blueprints --help`);
|
|
84
|
-
|
|
85
|
-
log(`\n ${nextStep} to get started`);
|
|
136
|
+
log(`\n Run "${chalk.bold.magenta(nextStepParts.join(' && '))}" to get started`);
|
|
86
137
|
return { success: true };
|
|
87
138
|
}
|
|
88
139
|
catch (error) {
|
|
@@ -19,6 +19,6 @@ export async function blueprintPlanCore(options) {
|
|
|
19
19
|
log(chalk.dim('No changes detected to live deployment'));
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
log(
|
|
22
|
+
log(`\n Run "${chalk.bold.magenta(`${bin} blueprints deploy`)}" to deploy these changes`);
|
|
23
23
|
return { success: true };
|
|
24
24
|
}
|
|
@@ -3,3 +3,4 @@ export declare function info(str: string): string;
|
|
|
3
3
|
export declare function warn(str: string): string;
|
|
4
4
|
export declare function severe(str: string): string;
|
|
5
5
|
export declare function niceId(id: string | undefined): string;
|
|
6
|
+
export declare function indent(str: string, spaces?: number): string;
|
|
@@ -16,3 +16,10 @@ export function niceId(id) {
|
|
|
16
16
|
return '';
|
|
17
17
|
return `<${chalk.yellow(id)}>`;
|
|
18
18
|
}
|
|
19
|
+
export function indent(str, spaces = 2) {
|
|
20
|
+
const pad = ' '.repeat(spaces);
|
|
21
|
+
return str
|
|
22
|
+
.split('\n')
|
|
23
|
+
.map((line) => (line.length > 0 ? pad + line : line))
|
|
24
|
+
.join('\n');
|
|
25
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// ! Making requests to the GitHub API will be rate limited at 60 requests per hour per IP address
|
|
2
|
+
// https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#primary-rate-limit-for-unauthenticated-users
|
|
3
|
+
export const GITHUB_API_URL = 'https://api.github.com';
|
|
4
|
+
export async function gitHubRequest(path) {
|
|
5
|
+
const response = await fetch(`${GITHUB_API_URL}${path}`, {
|
|
6
|
+
headers: { Accept: 'application/vnd.github.v3+json' },
|
|
7
|
+
});
|
|
8
|
+
if (response.ok) {
|
|
9
|
+
const remaining = Number(response.headers.get('X-RateLimit-Remaining'));
|
|
10
|
+
const limit = Number(response.headers.get('X-RateLimit-Limit'));
|
|
11
|
+
const reset = Number(response.headers.get('X-RateLimit-Reset'));
|
|
12
|
+
if (remaining && limit && reset) {
|
|
13
|
+
const percentRemaining = (remaining / limit) * 100;
|
|
14
|
+
const percentUsed = 100 - percentRemaining;
|
|
15
|
+
if (percentUsed > 85) {
|
|
16
|
+
// warn if near rate limit
|
|
17
|
+
console.warn(`Warning: You have used ${percentUsed.toFixed(2)}% of your GitHub API requests.`);
|
|
18
|
+
const resetTime = new Date(reset * 1000);
|
|
19
|
+
console.log(`Reset in ${Math.floor((resetTime.getTime() - Date.now()) / 60000)} minutes`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
// check for rate limit error
|
|
25
|
+
if (response.status === 403 || response.status === 429) {
|
|
26
|
+
console.error('GitHub API rate limit exceeded');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return response; // always return the response
|
|
30
|
+
}
|
package/oclif.manifest.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"aliases": [],
|
|
5
5
|
"args": {
|
|
6
6
|
"type": {
|
|
7
|
-
"description": "Type of
|
|
7
|
+
"description": "Type of Resource to add (e.g. function)",
|
|
8
8
|
"name": "type",
|
|
9
9
|
"options": [
|
|
10
10
|
"function"
|
|
@@ -21,6 +21,24 @@
|
|
|
21
21
|
"<%= config.bin %> <%= command.id %> function --name my-function --fn-type document-publish --lang js"
|
|
22
22
|
],
|
|
23
23
|
"flags": {
|
|
24
|
+
"example": {
|
|
25
|
+
"aliases": [
|
|
26
|
+
"recipe"
|
|
27
|
+
],
|
|
28
|
+
"description": "Example to use for the Resource",
|
|
29
|
+
"exclusive": [
|
|
30
|
+
"name",
|
|
31
|
+
"fn-type",
|
|
32
|
+
"fn-language",
|
|
33
|
+
"javascript",
|
|
34
|
+
"fn-helpers",
|
|
35
|
+
"fn-installer"
|
|
36
|
+
],
|
|
37
|
+
"name": "example",
|
|
38
|
+
"hasDynamicHelp": false,
|
|
39
|
+
"multiple": false,
|
|
40
|
+
"type": "option"
|
|
41
|
+
},
|
|
24
42
|
"name": {
|
|
25
43
|
"char": "n",
|
|
26
44
|
"description": "Name of the Resource to add",
|
|
@@ -351,6 +369,21 @@
|
|
|
351
369
|
"multiple": false,
|
|
352
370
|
"type": "option"
|
|
353
371
|
},
|
|
372
|
+
"example": {
|
|
373
|
+
"aliases": [
|
|
374
|
+
"recipe"
|
|
375
|
+
],
|
|
376
|
+
"description": "Example to use for the Blueprint",
|
|
377
|
+
"exclusive": [
|
|
378
|
+
"blueprint-type",
|
|
379
|
+
"stack-id",
|
|
380
|
+
"stack-name"
|
|
381
|
+
],
|
|
382
|
+
"name": "example",
|
|
383
|
+
"hasDynamicHelp": false,
|
|
384
|
+
"multiple": false,
|
|
385
|
+
"type": "option"
|
|
386
|
+
},
|
|
354
387
|
"blueprint-type": {
|
|
355
388
|
"aliases": [
|
|
356
389
|
"type"
|
|
@@ -838,5 +871,5 @@
|
|
|
838
871
|
]
|
|
839
872
|
}
|
|
840
873
|
},
|
|
841
|
-
"version": "8.0
|
|
874
|
+
"version": "8.1.0"
|
|
842
875
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/runtime-cli",
|
|
3
3
|
"description": "Sanity's Runtime CLI for Blueprints and Functions",
|
|
4
|
-
"version": "8.0
|
|
4
|
+
"version": "8.1.0",
|
|
5
5
|
"author": "Sanity Runtime Team",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -70,7 +70,8 @@
|
|
|
70
70
|
"pretest": "cd test/integration && npm install",
|
|
71
71
|
"test": "vitest run",
|
|
72
72
|
"posttest": "npm run lint",
|
|
73
|
-
"test:watch": "vitest"
|
|
73
|
+
"test:watch": "vitest",
|
|
74
|
+
"watch": "tsc --watch"
|
|
74
75
|
},
|
|
75
76
|
"dependencies": {
|
|
76
77
|
"@oclif/core": "^4.3.0",
|
|
@@ -86,6 +87,7 @@
|
|
|
86
87
|
"jiti": "^2.4.2",
|
|
87
88
|
"mime-types": "^3.0.1",
|
|
88
89
|
"ora": "^8.2.0",
|
|
90
|
+
"tar-stream": "^3.1.7",
|
|
89
91
|
"vite": "^6.3.5",
|
|
90
92
|
"vite-tsconfig-paths": "^5.1.4",
|
|
91
93
|
"ws": "^8.18.2",
|
|
@@ -105,6 +107,7 @@
|
|
|
105
107
|
"@types/cardinal": "^2.1.1",
|
|
106
108
|
"@types/mime-types": "^2.1.4",
|
|
107
109
|
"@types/node": "20",
|
|
110
|
+
"@types/tar-stream": "^3.1.4",
|
|
108
111
|
"@types/ws": "^8.18.1",
|
|
109
112
|
"codemirror": "^6.0.1",
|
|
110
113
|
"mentoss": "^0.11.0",
|