jsrepo 1.0.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.
@@ -0,0 +1,218 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { cancel, confirm, isCancel, outro, spinner } from '@clack/prompts';
4
+ import color from 'chalk';
5
+ import { Command, program } from 'commander';
6
+ import { diffLines } from 'diff';
7
+ import * as v from 'valibot';
8
+ import { context } from '..';
9
+ import { getConfig } from '../config';
10
+ import { OUTPUT_FILE } from '../utils';
11
+ import { type Block, isTestFile } from '../utils/build';
12
+ import { formatDiff } from '../utils/diff';
13
+ import { getInstalledBlocks } from '../utils/get-installed-blocks';
14
+ import { getWatermark } from '../utils/get-watermark';
15
+ import * as gitProviders from '../utils/git-providers';
16
+ import { languages } from '../utils/language-support';
17
+ import { intro } from '../utils/prompts';
18
+
19
+ const L = color.gray('│');
20
+
21
+ const schema = v.object({
22
+ allow: v.boolean(),
23
+ expand: v.boolean(),
24
+ maxUnchanged: v.number(),
25
+ repo: v.optional(v.string()),
26
+ });
27
+
28
+ type Options = v.InferInput<typeof schema>;
29
+
30
+ const diff = new Command('diff')
31
+ .description('Compares local blocks to the blocks in the provided repository.')
32
+ .option('-A, --allow', 'Allow jsrepo to download code from the provided repo.', false)
33
+ .option('-E, --expand', 'Expands the diff so you see everything.', false)
34
+ .option('--repo <repo>', 'Repository to download the blocks from.')
35
+ .option(
36
+ '--max-unchanged <number>',
37
+ 'Maximum unchanged lines that will show without being collapsed.',
38
+ (val) => Number.parseInt(val), // this is such a dumb api thing
39
+ 3
40
+ )
41
+ .action(async (opts) => {
42
+ const options = v.parse(schema, opts);
43
+
44
+ await _diff(options);
45
+ });
46
+
47
+ type RemoteBlock = Block & { sourceRepo: gitProviders.Info };
48
+
49
+ const _diff = async (options: Options) => {
50
+ intro(context.package.version);
51
+
52
+ const loading = spinner();
53
+
54
+ const config = getConfig().match(
55
+ (val) => val,
56
+ (err) => program.error(color.red(err))
57
+ );
58
+
59
+ const blocksMap: Map<string, RemoteBlock> = new Map();
60
+
61
+ let repoPaths = config.repos;
62
+
63
+ // we just want to override all others if supplied via the CLI
64
+ if (options.repo) repoPaths = [options.repo];
65
+
66
+ if (!options.allow && options.repo) {
67
+ const result = await confirm({
68
+ message: `Allow ${color.cyan('jsrepo')} to download and run code from ${color.cyan(options.repo)}?`,
69
+ initialValue: true,
70
+ });
71
+
72
+ if (isCancel(result) || !result) {
73
+ cancel('Canceled!');
74
+ process.exit(0);
75
+ }
76
+ }
77
+
78
+ loading.start(`Fetching blocks from ${color.cyan(repoPaths.join(', '))}`);
79
+
80
+ for (const repo of repoPaths) {
81
+ const providerInfo: gitProviders.Info = (await gitProviders.getProviderInfo(repo)).match(
82
+ (info) => info,
83
+ (err) => program.error(color.red(err))
84
+ );
85
+
86
+ const manifestUrl = await providerInfo.provider.resolveRaw(providerInfo, OUTPUT_FILE);
87
+
88
+ const categories = (await gitProviders.getManifest(manifestUrl)).match(
89
+ (val) => val,
90
+ (err) => program.error(color.red(err))
91
+ );
92
+
93
+ for (const category of categories) {
94
+ for (const block of category.blocks) {
95
+ blocksMap.set(
96
+ `${providerInfo.name}/${providerInfo.owner}/${providerInfo.repoName}/${category.name}/${block.name}`,
97
+ {
98
+ ...block,
99
+ sourceRepo: providerInfo,
100
+ }
101
+ );
102
+ }
103
+ }
104
+ }
105
+
106
+ loading.stop(`Retrieved blocks from ${color.cyan(repoPaths.join(', '))}`);
107
+
108
+ const installedBlocks = getInstalledBlocks(blocksMap, config);
109
+
110
+ for (const blockSpecifier of installedBlocks) {
111
+ let found = false;
112
+
113
+ for (const repo of repoPaths) {
114
+ // we unwrap because we already checked this
115
+ const providerInfo = (await gitProviders.getProviderInfo(repo)).unwrap();
116
+
117
+ const fullSpecifier = `${providerInfo.name}/${providerInfo.owner}/${providerInfo.repoName}/${blockSpecifier.specifier}`;
118
+
119
+ const block = blocksMap.get(fullSpecifier);
120
+
121
+ if (block === undefined) continue;
122
+
123
+ found = true;
124
+
125
+ process.stdout.write(`${L}\n`);
126
+
127
+ process.stdout.write(`${L} ${fullSpecifier}\n`);
128
+
129
+ fullSpecifier;
130
+
131
+ for (const file of block.files) {
132
+ // skip test files if not included
133
+ if (!config.includeTests && isTestFile(file)) continue;
134
+
135
+ process.stdout.write(`${L}\n`);
136
+
137
+ const sourcePath = path.join(block.directory, file);
138
+
139
+ const rawUrl = await providerInfo.provider.resolveRaw(providerInfo, sourcePath);
140
+
141
+ const response = await fetch(rawUrl);
142
+
143
+ if (!response.ok) {
144
+ program.error(color.red(`There was an error trying to get ${fullSpecifier}`));
145
+ }
146
+
147
+ let remoteContent = await response.text();
148
+
149
+ let localPath = path.join(config.path, block.category, file);
150
+ if (block.subdirectory) {
151
+ localPath = path.join(config.path, block.category, block.name, file);
152
+ }
153
+
154
+ let fileContent = '';
155
+ if (fs.existsSync(localPath)) {
156
+ fileContent = fs.readFileSync(localPath).toString();
157
+ }
158
+
159
+ if (config.watermark) {
160
+ const lang = languages.find((lang) => lang.matches(sourcePath));
161
+
162
+ if (lang) {
163
+ const watermark = getWatermark(context.package.version, repo);
164
+
165
+ const comment = lang.comment(watermark);
166
+
167
+ remoteContent = `${comment}\n\n${remoteContent}`;
168
+ }
169
+ }
170
+
171
+ const changes = diffLines(fileContent, remoteContent);
172
+
173
+ const from = path
174
+ .join(
175
+ `${providerInfo.name}/${providerInfo.owner}/${providerInfo.repoName}`,
176
+ sourcePath
177
+ )
178
+ .replaceAll('\\', '/');
179
+
180
+ const to = localPath.replaceAll('\\', '/');
181
+
182
+ const formattedDiff = formatDiff({
183
+ from,
184
+ to,
185
+ changes,
186
+ expand: options.expand,
187
+ maxUnchanged: options.maxUnchanged,
188
+ colorAdded: color.greenBright,
189
+ colorRemoved: color.redBright,
190
+ prefix: () => `${L} `,
191
+ onUnchanged: ({ from, to, prefix }) =>
192
+ `${prefix?.() ?? ''}${color.cyan(from)} → ${color.gray(to)} ${color.gray('(unchanged)')}\n`,
193
+ intro: ({ from, to, changes, prefix }) => {
194
+ const totalChanges = changes.filter((a) => a.added).length;
195
+
196
+ return `${prefix?.() ?? ''}${color.cyan(from)} → ${color.gray(to)} (${totalChanges} change${
197
+ totalChanges === 1 ? '' : 's'
198
+ })\n${prefix?.() ?? ''}\n`;
199
+ },
200
+ });
201
+
202
+ process.stdout.write(formattedDiff);
203
+ }
204
+
205
+ break;
206
+ }
207
+
208
+ if (!found) {
209
+ program.error(
210
+ color.red(`Invalid block! ${color.bold(blockSpecifier)} does not exist!`)
211
+ );
212
+ }
213
+ }
214
+
215
+ outro(color.green('All done!'));
216
+ };
217
+
218
+ export { diff };
@@ -0,0 +1,7 @@
1
+ import { add } from './add';
2
+ import { build } from './build';
3
+ import { diff } from './diff';
4
+ import { init } from './init';
5
+ import { test } from './test';
6
+
7
+ export { add, init, test, build, diff };
@@ -0,0 +1,117 @@
1
+ import fs from 'node:fs';
2
+ import { cancel, confirm, isCancel, outro, spinner, text } from '@clack/prompts';
3
+ import color from 'chalk';
4
+ import { Command } from 'commander';
5
+ import * as v from 'valibot';
6
+ import { context } from '..';
7
+ import { CONFIG_NAME, type Config, getConfig } from '../config';
8
+ import { intro } from '../utils/prompts';
9
+
10
+ const schema = v.object({
11
+ path: v.optional(v.string()),
12
+ tests: v.optional(v.boolean()),
13
+ repos: v.optional(v.array(v.string())),
14
+ watermark: v.boolean(),
15
+ });
16
+
17
+ type Options = v.InferInput<typeof schema>;
18
+
19
+ const init = new Command('init')
20
+ .description('Initializes the configuration file')
21
+ .option('--path <path>', 'Path to install the blocks')
22
+ .option('--repos [repos...]', 'Repository to install the blocks from')
23
+ .option(
24
+ '--no-watermark',
25
+ 'Will not add a watermark to each file upon adding it to your project.'
26
+ )
27
+ .option('--tests', 'Will include tests along with the functions.')
28
+ .action(async (opts) => {
29
+ const options = v.parse(schema, opts);
30
+
31
+ await _init(options);
32
+ });
33
+
34
+ const _init = async (options: Options) => {
35
+ intro(context.package.version);
36
+
37
+ const initialConfig = getConfig();
38
+
39
+ const loading = spinner();
40
+
41
+ if (!options.path) {
42
+ const result = await text({
43
+ message: 'Where should we add the blocks?',
44
+ validate(value) {
45
+ if (value.trim() === '') return 'Please provide a value';
46
+ },
47
+ initialValue: initialConfig.isOk() ? initialConfig.unwrap().path : 'src/blocks',
48
+ });
49
+
50
+ if (isCancel(result)) {
51
+ cancel('Canceled!');
52
+ process.exit(0);
53
+ }
54
+
55
+ options.path = result;
56
+ }
57
+
58
+ if (!options.repos) {
59
+ options.repos = initialConfig.isOk() ? initialConfig.unwrap().repos : [];
60
+
61
+ while (true) {
62
+ if (options.repos.length > 0) {
63
+ const confirmResult = await confirm({
64
+ message: 'Add another repo?',
65
+ initialValue: false,
66
+ });
67
+
68
+ if (isCancel(confirmResult)) {
69
+ cancel('Canceled!');
70
+ process.exit(0);
71
+ }
72
+
73
+ if (!confirmResult) break;
74
+ }
75
+
76
+ const result = await text({
77
+ message: 'Where should we download the blocks from?',
78
+ placeholder: 'github/ieedan/std',
79
+ validate: (val) => {
80
+ if (!val.startsWith('https://github.com') && !val.startsWith('github/')) {
81
+ return `Must be a ${color.bold('GitHub')} repository!`;
82
+ }
83
+ },
84
+ });
85
+
86
+ if (isCancel(result)) {
87
+ cancel('Canceled!');
88
+ process.exit(0);
89
+ }
90
+
91
+ options.repos.push(result);
92
+ }
93
+ }
94
+
95
+ const config: Config = {
96
+ $schema: `https://unpkg.com/jsrepo@${context.package.version}/schema.json`,
97
+ repos: options.repos,
98
+ path: options.path,
99
+ includeTests:
100
+ initialConfig.isOk() && options.tests === undefined
101
+ ? initialConfig.unwrap().includeTests
102
+ : (options.tests ?? false),
103
+ watermark: options.watermark,
104
+ };
105
+
106
+ loading.start(`Writing config to \`${CONFIG_NAME}\``);
107
+
108
+ fs.writeFileSync(CONFIG_NAME, `${JSON.stringify(config, null, '\t')}\n`);
109
+
110
+ fs.mkdirSync(config.path, { recursive: true });
111
+
112
+ loading.stop(`Wrote config to \`${CONFIG_NAME}\`.`);
113
+
114
+ outro(color.green('All done!'));
115
+ };
116
+
117
+ export { init };
@@ -0,0 +1,369 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { cancel, confirm, isCancel, outro, spinner } from '@clack/prompts';
4
+ import color from 'chalk';
5
+ import { Argument, Command, program } from 'commander';
6
+ import { execa } from 'execa';
7
+ import { resolveCommand } from 'package-manager-detector/commands';
8
+ import { detect } from 'package-manager-detector/detect';
9
+ import { Project } from 'ts-morph';
10
+ import * as v from 'valibot';
11
+ import { context } from '..';
12
+ import { getConfig } from '../config';
13
+ import { INFO } from '../utils';
14
+ import { type Block, categorySchema, isTestFile } from '../utils/build';
15
+ import { getInstalledBlocks } from '../utils/get-installed-blocks';
16
+ import * as gitProviders from '../utils/git-providers';
17
+ import { OUTPUT_FILE } from '../utils/index';
18
+ import { intro } from '../utils/prompts';
19
+
20
+ const schema = v.object({
21
+ debug: v.boolean(),
22
+ verbose: v.boolean(),
23
+ repo: v.optional(v.string()),
24
+ allow: v.boolean(),
25
+ });
26
+
27
+ type Options = v.InferInput<typeof schema>;
28
+
29
+ const test = new Command('test')
30
+ .description('Tests blocks against most recent tests')
31
+ .addArgument(new Argument('[blocks...]', 'Whichever blocks you want to test.').default([]))
32
+ .option('--verbose', 'Include debug logs.', false)
33
+ .option('-A, --allow', 'Allow jsrepo to download code from the provided repo.', false)
34
+ .option('--repo <repo>', 'Repository to download the blocks from')
35
+ .option('--debug', 'Leaves the temp test file around for debugging upon failure.', false)
36
+ .action(async (blockNames, opts) => {
37
+ const options = v.parse(schema, opts);
38
+
39
+ await _test(blockNames, options);
40
+ });
41
+
42
+ type RemoteBlock = Block & { sourceRepo: gitProviders.Info };
43
+
44
+ const _test = async (blockNames: string[], options: Options) => {
45
+ intro(context.package.version);
46
+
47
+ const verbose = (msg: string) => {
48
+ if (options.verbose) {
49
+ console.info(`${INFO} ${msg}`);
50
+ }
51
+ };
52
+
53
+ verbose(`Attempting to test ${JSON.stringify(blockNames)}`);
54
+
55
+ const config = getConfig().match(
56
+ (val) => val,
57
+ (err) => program.error(color.red(err))
58
+ );
59
+
60
+ const loading = spinner();
61
+
62
+ const blocksMap: Map<string, RemoteBlock> = new Map();
63
+
64
+ let repoPaths = config.repos;
65
+
66
+ // we just want to override all others if supplied via the CLI
67
+ if (options.repo) repoPaths = [options.repo];
68
+
69
+ if (!options.allow && options.repo) {
70
+ const result = await confirm({
71
+ message: `Allow ${color.cyan('jsrepo')} to download and run code from ${color.cyan(options.repo)}?`,
72
+ initialValue: true,
73
+ });
74
+
75
+ if (isCancel(result) || !result) {
76
+ cancel('Canceled!');
77
+ process.exit(0);
78
+ }
79
+ }
80
+
81
+ verbose(`Fetching blocks from ${color.cyan(repoPaths.join(', '))}`);
82
+
83
+ if (!options.verbose) loading.start(`Fetching blocks from ${color.cyan(repoPaths.join(', '))}`);
84
+
85
+ for (const repo of repoPaths) {
86
+ const providerInfo: gitProviders.Info = (await gitProviders.getProviderInfo(repo)).match(
87
+ (info) => info,
88
+ (err) => program.error(color.red(err))
89
+ );
90
+
91
+ const manifestUrl = await providerInfo.provider.resolveRaw(providerInfo, OUTPUT_FILE);
92
+
93
+ verbose(`Got info for provider ${color.cyan(providerInfo.name)}`);
94
+
95
+ const response = await fetch(manifestUrl);
96
+
97
+ if (!response.ok) {
98
+ if (!options.verbose) loading.stop(`Error fetching ${color.cyan(manifestUrl.href)}`);
99
+ program.error(
100
+ color.red(
101
+ `There was an error fetching the \`${OUTPUT_FILE}\` from the repository ${color.cyan(
102
+ repo
103
+ )} make sure the target repository has a \`${OUTPUT_FILE}\` in its root?`
104
+ )
105
+ );
106
+ }
107
+
108
+ const categories = v.parse(v.array(categorySchema), await response.json());
109
+
110
+ for (const category of categories) {
111
+ for (const block of category.blocks) {
112
+ // blocks will override each other
113
+ blocksMap.set(
114
+ `${providerInfo.name}/${providerInfo.owner}/${providerInfo.repoName}/${category.name}/${block.name}`,
115
+ {
116
+ ...block,
117
+ sourceRepo: providerInfo,
118
+ }
119
+ );
120
+ }
121
+ }
122
+ }
123
+
124
+ verbose(`Retrieved blocks from ${color.cyan(repoPaths.join(', '))}`);
125
+
126
+ if (!options.verbose) loading.stop(`Retrieved blocks from ${color.cyan(repoPaths.join(', '))}`);
127
+
128
+ const tempTestDirectory = `blocks-tests-temp-${Date.now()}`;
129
+
130
+ verbose(`Trying to create the temp directory ${color.bold(tempTestDirectory)}.`);
131
+
132
+ fs.mkdirSync(tempTestDirectory, { recursive: true });
133
+
134
+ const cleanUp = () => {
135
+ fs.rmSync(tempTestDirectory, { recursive: true, force: true });
136
+ };
137
+
138
+ const installedBlocks = getInstalledBlocks(blocksMap, config).map((val) => val.specifier);
139
+
140
+ let testingBlocks = blockNames;
141
+
142
+ // in the case that we want to test all files
143
+ if (blockNames.length === 0) {
144
+ testingBlocks = installedBlocks;
145
+ }
146
+
147
+ if (testingBlocks.length === 0) {
148
+ cleanUp();
149
+ program.error(color.red('There were no blocks found in your project!'));
150
+ }
151
+
152
+ const testingBlocksMapped: { name: string; block: RemoteBlock }[] = [];
153
+
154
+ for (const blockSpecifier of testingBlocks) {
155
+ let block: RemoteBlock | undefined = undefined;
156
+
157
+ // if the block starts with github (or another provider) we know it has been resolved
158
+ if (!blockSpecifier.startsWith('github')) {
159
+ for (const repo of repoPaths) {
160
+ // we unwrap because we already checked this
161
+ const providerInfo = (await gitProviders.getProviderInfo(repo)).unwrap();
162
+
163
+ const tempBlock = blocksMap.get(
164
+ `${providerInfo.name}/${providerInfo.owner}/${providerInfo.repoName}/${blockSpecifier}`
165
+ );
166
+
167
+ if (tempBlock === undefined) continue;
168
+
169
+ block = tempBlock;
170
+
171
+ break;
172
+ }
173
+ } else {
174
+ if (repoPaths.length === 0) {
175
+ const [providerName, owner, repoName, ...rest] = blockSpecifier.split('/');
176
+
177
+ let repo: string;
178
+ // if rest is greater than 2 it isn't the block specifier so it is part of the path
179
+ if (rest.length > 2) {
180
+ repo = `${providerName}/${owner}/${repoName}/${rest.join('/')}`;
181
+ } else {
182
+ repo = `${providerName}/${owner}/${repoName}`;
183
+ }
184
+
185
+ const providerInfo = (await gitProviders.getProviderInfo(repo)).match(
186
+ (val) => val,
187
+ (err) => program.error(color.red(err))
188
+ );
189
+
190
+ const manifestUrl = await providerInfo.provider.resolveRaw(
191
+ providerInfo,
192
+ OUTPUT_FILE
193
+ );
194
+
195
+ const categories = (await gitProviders.getManifest(manifestUrl)).match(
196
+ (val) => val,
197
+ (err) => program.error(color.red(err))
198
+ );
199
+
200
+ for (const category of categories) {
201
+ for (const block of category.blocks) {
202
+ blocksMap.set(
203
+ `${providerInfo.name}/${providerInfo.owner}/${providerInfo.repoName}/${category.name}/${block.name}`,
204
+ {
205
+ ...block,
206
+ sourceRepo: providerInfo,
207
+ }
208
+ );
209
+ }
210
+ }
211
+ }
212
+
213
+ block = blocksMap.get(blockSpecifier);
214
+ }
215
+
216
+ if (!block) {
217
+ program.error(
218
+ color.red(`Invalid block! ${color.bold(blockSpecifier)} does not exist!`)
219
+ );
220
+ }
221
+
222
+ testingBlocksMapped.push({ name: blockSpecifier, block });
223
+ }
224
+
225
+ for (const { name: specifier, block } of testingBlocksMapped) {
226
+ const providerInfo = block.sourceRepo;
227
+
228
+ if (!options.verbose) {
229
+ loading.start(`Setting up test file for ${color.cyan(specifier)}`);
230
+ }
231
+
232
+ if (!block.tests) {
233
+ loading.stop(`No tests found for ${color.cyan(specifier)}`);
234
+ continue;
235
+ }
236
+
237
+ const getSourceFile = async (filePath: string) => {
238
+ const rawUrl = await providerInfo.provider.resolveRaw(providerInfo, filePath);
239
+
240
+ const response = await fetch(rawUrl);
241
+
242
+ if (!response.ok) {
243
+ loading.stop(color.red(`Error fetching ${color.bold(rawUrl.href)}`));
244
+ program.error(color.red(`There was an error trying to get ${specifier}`));
245
+ }
246
+
247
+ return await response.text();
248
+ };
249
+
250
+ verbose(`Downloading and copying test files for ${specifier}`);
251
+
252
+ const testFiles: string[] = [];
253
+
254
+ for (const testFile of block.files.filter((file) => isTestFile(file))) {
255
+ const content = await getSourceFile(path.join(block.directory, testFile));
256
+
257
+ const destPath = path.join(tempTestDirectory, testFile);
258
+
259
+ fs.writeFileSync(destPath, content);
260
+
261
+ testFiles.push(destPath);
262
+ }
263
+
264
+ const directory = path.join(config.path, block.category);
265
+ let blockFilePath = path.join(directory, `${block.name}`);
266
+
267
+ blockFilePath = blockFilePath.replaceAll('\\', '/');
268
+
269
+ verbose(`${color.bold(specifier)} file path is ${color.bold(blockFilePath)}`);
270
+
271
+ const project = new Project();
272
+
273
+ // resolve imports for the block
274
+ for (const file of testFiles) {
275
+ verbose(`Opening test file ${file}`);
276
+
277
+ const tempFile = project.addSourceFileAtPath(file);
278
+
279
+ for (const importDeclaration of tempFile.getImportDeclarations()) {
280
+ const moduleSpecifier = importDeclaration.getModuleSpecifierValue();
281
+
282
+ let newModuleSpecifier: string | undefined = undefined;
283
+
284
+ // if the module is relative resolve it relative to the new path of the tests
285
+ if (moduleSpecifier.startsWith('.')) {
286
+ if (block.subdirectory) {
287
+ newModuleSpecifier = path.join(
288
+ '../',
289
+ config.path,
290
+ block.category,
291
+ block.name,
292
+ moduleSpecifier
293
+ );
294
+ } else {
295
+ newModuleSpecifier = path.join(
296
+ '../',
297
+ config.path,
298
+ block.category,
299
+ moduleSpecifier
300
+ );
301
+ }
302
+ }
303
+
304
+ if (newModuleSpecifier) {
305
+ // we need to add the replace so that paths are correctly translated on windows
306
+ importDeclaration.setModuleSpecifier(newModuleSpecifier.replaceAll(/\\/g, '/'));
307
+ }
308
+ }
309
+ }
310
+
311
+ project.saveSync();
312
+
313
+ verbose(`Completed ${color.cyan.bold(specifier)} test file`);
314
+
315
+ if (!options.verbose) {
316
+ loading.stop(`Completed setup for ${color.bold(specifier)}`);
317
+ }
318
+ }
319
+
320
+ verbose('Beginning testing');
321
+
322
+ const pm = await detect({ cwd: process.cwd() });
323
+
324
+ if (pm == null) {
325
+ program.error(color.red('Could not detect package manager'));
326
+ }
327
+
328
+ const resolved = resolveCommand(pm.agent, 'execute', ['vitest', 'run', tempTestDirectory]);
329
+
330
+ if (resolved == null) {
331
+ program.error(color.red(`Could not resolve add command for '${pm.agent}'.`));
332
+ }
333
+
334
+ const { command, args } = resolved;
335
+
336
+ const testCommand = `${command} ${args.join(' ')}`;
337
+
338
+ const testingProcess = execa({
339
+ cwd: process.cwd(),
340
+ stdio: ['ignore', 'pipe', 'pipe'],
341
+ })`${testCommand}`;
342
+
343
+ const handler = (data: string) => console.info(data.toString());
344
+
345
+ testingProcess.stdout.on('data', handler);
346
+ testingProcess.stderr.on('data', handler);
347
+
348
+ try {
349
+ await testingProcess;
350
+
351
+ cleanUp();
352
+
353
+ outro(color.green('All done!'));
354
+ } catch (err) {
355
+ if (options.debug) {
356
+ console.info(
357
+ `${color.bold('--debug')} flag provided. Skipping cleanup. Run '${color.bold(
358
+ testCommand
359
+ )}' to retry tests.\n`
360
+ );
361
+ } else {
362
+ cleanUp();
363
+ }
364
+
365
+ program.error(color.red(`Tests failed! Error ${err}`));
366
+ }
367
+ };
368
+
369
+ export { test };