@celilo/e2e 0.6.0 → 0.6.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/config/dns/celilo.computer.zone +16 -0
- package/config/dns/iamtheinternet.org.zone +6 -4
- package/config/dns/knot-namecheap.conf +8 -0
- package/config/dns/knot-tld.conf +4 -0
- package/config/dns/lunacycle.net.zone +16 -0
- package/config/dns/net.zone +11 -0
- package/config/dns/org.zone +5 -0
- package/config/dns/root.zone +2 -0
- package/config/dns/templates/iamtheinternet.org.zone +16 -0
- package/config/dns/templates/lunacycle.net.zone +16 -0
- package/config/resolver/unbound.conf +10 -0
- package/config/routing/management-routes.sh +19 -0
- package/config/routing/resolver-internal-routes.sh +59 -0
- package/docker/Dockerfile.internal-resolver +16 -0
- package/docker/Dockerfile.isitup +37 -0
- package/docker/Dockerfile.registry +34 -0
- package/netapps/authentik.netapp +0 -0
- package/netapps/caddy.netapp +0 -0
- package/netapps/greenwave.netapp +0 -0
- package/netapps/homebridge.netapp +0 -0
- package/netapps/iptables.netapp +0 -0
- package/netapps/knot-unbound-internal.netapp +0 -0
- package/netapps/namecheap.netapp +0 -0
- package/netapps/technitium.netapp +0 -0
- package/netapps/test-ansible-output.netapp +0 -0
- package/package.json +2 -1
- package/simulators/isitup/Caddyfile +11 -0
- package/simulators/isitup/entrypoint.sh +47 -0
- package/simulators/isitup/server.ts +112 -0
- package/simulators/namecheap-ddns/server.ts +31 -2
- package/src/cli/build.ts +17 -3
- package/src/cli/command-registry.ts +3 -2
- package/src/cli/completion.ts +29 -5
- package/src/cli/index.ts +85 -15
- package/src/cli/module-discovery.ts +120 -0
- package/src/cli/scaffold.ts +4 -5
- package/src/container-manager.ts +358 -71
- package/src/docker-compose-generator.ts +119 -17
- package/src/parse-line.ts +14 -3
- package/src/runner.ts +122 -165
- package/src/simulator-ips.ts +42 -0
- package/src/types.ts +24 -0
|
@@ -23,12 +23,13 @@ export interface FlagDef {
|
|
|
23
23
|
export const COMMANDS: CommandDef[] = [
|
|
24
24
|
{
|
|
25
25
|
name: 'run',
|
|
26
|
-
description: 'Run e2e tests
|
|
26
|
+
description: 'Run e2e tests — pass a module path or pattern, or --all-modules',
|
|
27
27
|
flags: [
|
|
28
28
|
{ name: '--keep', description: 'Keep network running after tests' },
|
|
29
29
|
{ name: '--reuse', description: 'Reuse existing network if running' },
|
|
30
30
|
{ name: '--live', description: 'Use live (non-simulated) internet' },
|
|
31
31
|
{ name: '--published', description: 'Use published .netapp packages' },
|
|
32
|
+
{ name: '--all-modules', description: 'Run e2e tests for all modules with e2e/ directories' },
|
|
32
33
|
],
|
|
33
34
|
},
|
|
34
35
|
{
|
|
@@ -92,7 +93,7 @@ export const COMMANDS: CommandDef[] = [
|
|
|
92
93
|
|
|
93
94
|
export const UP_PRESETS = ['--caddy', '--full-stack', '--infrastructure'];
|
|
94
95
|
|
|
95
|
-
export const RUN_FLAGS = ['--keep', '--reuse', '--live', '--published'];
|
|
96
|
+
export const RUN_FLAGS = ['--keep', '--reuse', '--live', '--published', '--all-modules'];
|
|
96
97
|
|
|
97
98
|
export const DOWN_FLAGS = ['--keep', '--all'];
|
|
98
99
|
|
package/src/cli/completion.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
RUN_FLAGS,
|
|
11
11
|
UP_PRESETS,
|
|
12
12
|
} from './command-registry';
|
|
13
|
+
import { findAllTestNames } from './module-discovery';
|
|
13
14
|
|
|
14
15
|
export function getCompletions(words: string[], current: number): string[] {
|
|
15
16
|
const args = words[0] === 'cele2e' ? words.slice(1) : words;
|
|
@@ -26,10 +27,13 @@ export function getCompletions(words: string[], current: number): string[] {
|
|
|
26
27
|
|
|
27
28
|
if (command === 'run' && currentIndex >= 1) {
|
|
28
29
|
const used = new Set(args.slice(1));
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
const flags = RUN_FLAGS.filter((f) => !used.has(f));
|
|
31
|
+
const currentArg = args[currentIndex] || '';
|
|
32
|
+
if (currentArg.startsWith('--')) {
|
|
33
|
+
return filterSuggestions(flags, currentArg);
|
|
34
|
+
}
|
|
35
|
+
const testNames = findAllTestNames(process.cwd());
|
|
36
|
+
return filterSuggestions([...testNames, ...flags], currentArg);
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
if (command === 'up' && currentIndex === 1) {
|
|
@@ -87,6 +91,13 @@ export function generateZshCompletion(): string {
|
|
|
87
91
|
_cele2e() {
|
|
88
92
|
local curcontext="$curcontext" state line
|
|
89
93
|
typeset -A opt_args
|
|
94
|
+
# Capture the original command line — _arguments mutates words/CURRENT
|
|
95
|
+
# as it consumes arguments, but our dynamic completer needs the originals.
|
|
96
|
+
# zsh dynamic scoping makes these visible to nested functions.
|
|
97
|
+
local -a _cele2e_orig_words
|
|
98
|
+
local _cele2e_orig_current
|
|
99
|
+
_cele2e_orig_words=( "\${words[@]}" )
|
|
100
|
+
_cele2e_orig_current=$CURRENT
|
|
90
101
|
|
|
91
102
|
_arguments -C \\
|
|
92
103
|
'1: :_cele2e_commands' \\
|
|
@@ -101,7 +112,8 @@ _cele2e() {
|
|
|
101
112
|
'--reuse[Reuse existing network if running]' \\
|
|
102
113
|
'--live[Use live (non-simulated) internet]' \\
|
|
103
114
|
'--published[Use published .netapp packages]' \\
|
|
104
|
-
'
|
|
115
|
+
'--all-modules[Run e2e tests for all modules with e2e/ directories]' \\
|
|
116
|
+
'*::test name or module path:_cele2e_run_args'
|
|
105
117
|
;;
|
|
106
118
|
up)
|
|
107
119
|
_arguments '1: :_cele2e_up_presets'
|
|
@@ -166,6 +178,18 @@ _cele2e_completion_shells() {
|
|
|
166
178
|
_describe 'shell' shells
|
|
167
179
|
}
|
|
168
180
|
|
|
181
|
+
# Dynamic completion for 'cele2e run' positional args — calls the CLI to
|
|
182
|
+
# discover test names from modules under the current directory. Uses the
|
|
183
|
+
# original (un-stripped) words array captured by _cele2e.
|
|
184
|
+
_cele2e_run_args() {
|
|
185
|
+
local -a completions
|
|
186
|
+
local IFS=$'\\n'
|
|
187
|
+
completions=( \${(f)"$(cele2e --get-completions "\${_cele2e_orig_words[@]}" "$((_cele2e_orig_current - 1))" 2>/dev/null)"} )
|
|
188
|
+
if (( \${#completions[@]} > 0 )); then
|
|
189
|
+
_describe 'test' completions
|
|
190
|
+
fi
|
|
191
|
+
}
|
|
192
|
+
|
|
169
193
|
_cele2e "$@"
|
|
170
194
|
`;
|
|
171
195
|
}
|
package/src/cli/index.ts
CHANGED
|
@@ -17,21 +17,42 @@
|
|
|
17
17
|
|
|
18
18
|
import { spawnSync } from 'node:child_process';
|
|
19
19
|
import { existsSync, readFileSync, rmSync } from 'node:fs';
|
|
20
|
-
import { join, resolve } from 'node:path';
|
|
20
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
21
|
+
import { parse as parseYaml } from 'yaml';
|
|
21
22
|
import { runBuild } from './build';
|
|
22
23
|
import { runScaffold } from './scaffold';
|
|
23
24
|
import { generateBashCompletion, generateZshCompletion, getCompletions } from './completion';
|
|
25
|
+
import { findAllModules, findModulesWithMatchingTest } from './module-discovery';
|
|
24
26
|
|
|
25
27
|
const PKG_DIR = resolve(import.meta.dir, '../..');
|
|
26
28
|
const BIN_DIR = join(PKG_DIR, 'bin');
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
|
|
31
|
+
function readManifestTestsDir(manifestPath: string): string | undefined {
|
|
32
|
+
try {
|
|
33
|
+
const parsed = parseYaml(readFileSync(manifestPath, 'utf-8')) as { e2e?: { tests_dir?: string } };
|
|
34
|
+
return parsed?.e2e?.tests_dir;
|
|
35
|
+
} catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function detectTestDir(): { stateDir: string; testsPath: string } {
|
|
41
|
+
if (process.env.E2E_TEST_DIR) {
|
|
42
|
+
const stateDir = process.env.E2E_TEST_DIR;
|
|
43
|
+
return { stateDir, testsPath: join(stateDir, 'tests') };
|
|
44
|
+
}
|
|
30
45
|
const cwd = process.cwd();
|
|
31
|
-
|
|
32
|
-
if (existsSync(
|
|
33
|
-
|
|
34
|
-
|
|
46
|
+
const manifestPath = join(cwd, 'manifest.yml');
|
|
47
|
+
if (existsSync(manifestPath)) {
|
|
48
|
+
const testsDir = readManifestTestsDir(manifestPath);
|
|
49
|
+
if (testsDir) {
|
|
50
|
+
const testsPath = resolve(cwd, testsDir);
|
|
51
|
+
return { stateDir: dirname(testsPath), testsPath };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (existsSync(join(cwd, 'tests'))) return { stateDir: cwd, testsPath: join(cwd, 'tests') };
|
|
55
|
+
return { stateDir: cwd, testsPath: join(cwd, 'tests') };
|
|
35
56
|
}
|
|
36
57
|
|
|
37
58
|
function runScript(script: string, args: string[], env?: Record<string, string>): never {
|
|
@@ -82,8 +103,9 @@ Options for \`build-infra\`:
|
|
|
82
103
|
--skip-modules Skip .netapp packaging (only rebuild Docker images)
|
|
83
104
|
|
|
84
105
|
Examples:
|
|
85
|
-
cele2e run # run all tests
|
|
106
|
+
cele2e run # in a module: run its tests; outside: run all modules' tests
|
|
86
107
|
cele2e run deploy # run tests matching "deploy"
|
|
108
|
+
cele2e run caddy-direct-internet # from modules/, finds the matching module automatically
|
|
87
109
|
cele2e run --reuse deploy # reuse existing network
|
|
88
110
|
cele2e up --full-stack # interactive full-stack environment
|
|
89
111
|
cele2e shell caddy # debug running caddy container
|
|
@@ -95,7 +117,7 @@ Examples:
|
|
|
95
117
|
}
|
|
96
118
|
|
|
97
119
|
const [, , command, ...args] = process.argv;
|
|
98
|
-
const
|
|
120
|
+
const { stateDir, testsPath: detectedTestsPath } = detectTestDir();
|
|
99
121
|
|
|
100
122
|
// Handle --get-completions before routing (used by shell completion scripts)
|
|
101
123
|
if (command === '--get-completions') {
|
|
@@ -107,20 +129,68 @@ if (command === '--get-completions') {
|
|
|
107
129
|
}
|
|
108
130
|
|
|
109
131
|
switch (command) {
|
|
110
|
-
case 'run':
|
|
111
|
-
|
|
132
|
+
case 'run': {
|
|
133
|
+
// Check if any non-flag arg is a module path (has manifest.yml)
|
|
134
|
+
const modulePath = args
|
|
135
|
+
.filter((a) => !a.startsWith('--'))
|
|
136
|
+
.find((a) => existsSync(join(resolve(a), 'manifest.yml')));
|
|
137
|
+
|
|
138
|
+
if (modulePath) {
|
|
139
|
+
const absModulePath = resolve(modulePath);
|
|
140
|
+
runScript('e2e-run', args.filter((a) => a !== modulePath), {
|
|
141
|
+
E2E_TEST_DIR: stateDir,
|
|
142
|
+
E2E_MODULE_DIR: absModulePath,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// If cwd is not a module, walk sibling modules so the user can invoke
|
|
147
|
+
// `cele2e run` from infra/modules/ (or the repo root) without cd-ing in.
|
|
148
|
+
const cwdHasManifest = existsSync(join(process.cwd(), 'manifest.yml'));
|
|
149
|
+
const positionals = args.filter((a) => !a.startsWith('--'));
|
|
150
|
+
|
|
151
|
+
if (!cwdHasManifest) {
|
|
152
|
+
if (positionals.length === 1) {
|
|
153
|
+
const matches = findModulesWithMatchingTest(process.cwd(), positionals[0]);
|
|
154
|
+
if (matches.length === 1) {
|
|
155
|
+
runScript('e2e-run', args, {
|
|
156
|
+
E2E_TEST_DIR: stateDir,
|
|
157
|
+
E2E_MODULE_DIR: matches[0].moduleDir,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
if (matches.length > 1) {
|
|
161
|
+
console.error(`Multiple modules contain tests matching "${positionals[0]}":`);
|
|
162
|
+
for (const m of matches) {
|
|
163
|
+
console.error(` ${basename(m.moduleDir)}: ${m.matchedTests.join(', ')}`);
|
|
164
|
+
}
|
|
165
|
+
console.error('\nDisambiguate by running from inside the module directory or passing its path.');
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
} else if (positionals.length === 0) {
|
|
169
|
+
const allModules = findAllModules(process.cwd());
|
|
170
|
+
if (allModules.length > 0) {
|
|
171
|
+
runScript('e2e-run', args, {
|
|
172
|
+
E2E_TEST_DIR: stateDir,
|
|
173
|
+
E2E_MODULE_DIRS: allModules.join('\n'),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
runScript('e2e-run', args, { E2E_TEST_DIR: stateDir, E2E_TESTS_PATH: detectedTestsPath });
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
112
182
|
|
|
113
183
|
case 'up':
|
|
114
184
|
runScript('e2e-up', args);
|
|
115
185
|
|
|
116
186
|
case 'down':
|
|
117
|
-
runScript('e2e-down', args, { E2E_TEST_DIR:
|
|
187
|
+
runScript('e2e-down', args, { E2E_TEST_DIR: stateDir });
|
|
118
188
|
|
|
119
189
|
case 'shell':
|
|
120
190
|
runScript('e2e-shell', args);
|
|
121
191
|
|
|
122
192
|
case 'status': {
|
|
123
|
-
const persistentFile = join(
|
|
193
|
+
const persistentFile = join(stateDir, '.e2e-persistent.json');
|
|
124
194
|
let projectName: string | undefined;
|
|
125
195
|
if (existsSync(persistentFile)) {
|
|
126
196
|
try {
|
|
@@ -150,7 +220,7 @@ switch (command) {
|
|
|
150
220
|
}
|
|
151
221
|
|
|
152
222
|
case 'clear-timing': {
|
|
153
|
-
const timingFile = join(
|
|
223
|
+
const timingFile = join(stateDir, '.e2e-timing.json');
|
|
154
224
|
if (existsSync(timingFile)) {
|
|
155
225
|
rmSync(timingFile);
|
|
156
226
|
console.log(`Cleared timing data: ${timingFile}`);
|
|
@@ -166,7 +236,7 @@ switch (command) {
|
|
|
166
236
|
console.error('Error: scaffold requires a test name\n cele2e scaffold <name>');
|
|
167
237
|
process.exit(1);
|
|
168
238
|
}
|
|
169
|
-
runScaffold({
|
|
239
|
+
runScaffold({ testsPath: detectedTestsPath, name });
|
|
170
240
|
break;
|
|
171
241
|
}
|
|
172
242
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walks the cwd tree to find Celilo modules and their e2e tests.
|
|
3
|
+
*
|
|
4
|
+
* Used so that `cele2e run <test-name>` works from `infra/modules/` (or the
|
|
5
|
+
* repo root) without needing to cd into the module first, and so tab
|
|
6
|
+
* completion can suggest test names discovered across all modules.
|
|
7
|
+
*
|
|
8
|
+
* Scans direct subdirs and one level deeper — covers `modules/` (where each
|
|
9
|
+
* subdir is a module) and `infra/` (where `modules/<name>/` is one level
|
|
10
|
+
* deeper).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
14
|
+
import { join, resolve } from 'node:path';
|
|
15
|
+
import { parse as parseYaml } from 'yaml';
|
|
16
|
+
|
|
17
|
+
const MAX_DEPTH = 2;
|
|
18
|
+
|
|
19
|
+
function readManifestTestsDir(manifestPath: string): string | undefined {
|
|
20
|
+
try {
|
|
21
|
+
const parsed = parseYaml(readFileSync(manifestPath, 'utf-8')) as { e2e?: { tests_dir?: string } };
|
|
22
|
+
return parsed?.e2e?.tests_dir;
|
|
23
|
+
} catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function listTestNames(testsPath: string): string[] {
|
|
29
|
+
if (!existsSync(testsPath)) return [];
|
|
30
|
+
try {
|
|
31
|
+
return readdirSync(testsPath)
|
|
32
|
+
.filter((f) => f.endsWith('.test.ts'))
|
|
33
|
+
.map((f) => f.replace(/\.test\.ts$/, ''));
|
|
34
|
+
} catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isModule(dir: string): { manifestPath: string; testsPath: string } | null {
|
|
40
|
+
const manifestPath = join(dir, 'manifest.yml');
|
|
41
|
+
if (!existsSync(manifestPath)) return null;
|
|
42
|
+
// Match the runner's resolution: prefer e2e.tests_dir from the manifest,
|
|
43
|
+
// fall back to a conventional e2e/ directory at the module root.
|
|
44
|
+
const testsDir = readManifestTestsDir(manifestPath) ?? 'e2e';
|
|
45
|
+
return { manifestPath, testsPath: resolve(dir, testsDir) };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function walkModules(cwd: string, visit: (moduleDir: string, testsPath: string) => void): void {
|
|
49
|
+
function scan(dir: string, depth: number): void {
|
|
50
|
+
if (depth > MAX_DEPTH) return;
|
|
51
|
+
let entries: string[];
|
|
52
|
+
try {
|
|
53
|
+
entries = readdirSync(dir);
|
|
54
|
+
} catch {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
if (entry.startsWith('.') || entry === 'node_modules') continue;
|
|
59
|
+
const subdir = join(dir, entry);
|
|
60
|
+
try {
|
|
61
|
+
if (!statSync(subdir).isDirectory()) continue;
|
|
62
|
+
} catch {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const mod = isModule(subdir);
|
|
66
|
+
if (mod) {
|
|
67
|
+
visit(subdir, mod.testsPath);
|
|
68
|
+
} else if (depth < MAX_DEPTH) {
|
|
69
|
+
scan(subdir, depth + 1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
scan(cwd, 1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ModuleTestMatch {
|
|
77
|
+
moduleDir: string;
|
|
78
|
+
matchedTests: string[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Find modules under `cwd` whose e2e dir contains a test name including `pattern`.
|
|
83
|
+
* Used by `cele2e run <pattern>` when invoked outside any module dir.
|
|
84
|
+
*/
|
|
85
|
+
export function findModulesWithMatchingTest(cwd: string, pattern: string): ModuleTestMatch[] {
|
|
86
|
+
const results: ModuleTestMatch[] = [];
|
|
87
|
+
walkModules(cwd, (moduleDir, testsPath) => {
|
|
88
|
+
const matched = listTestNames(testsPath).filter((t) => t.includes(pattern));
|
|
89
|
+
if (matched.length > 0) results.push({ moduleDir, matchedTests: matched });
|
|
90
|
+
});
|
|
91
|
+
return results;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Collect all test names reachable from `cwd`. Includes tests in cwd itself
|
|
96
|
+
* (when cwd is a module) and tests in any module discovered under cwd.
|
|
97
|
+
*/
|
|
98
|
+
export function findAllTestNames(cwd: string): string[] {
|
|
99
|
+
const names = new Set<string>();
|
|
100
|
+
const cwdMod = isModule(cwd);
|
|
101
|
+
if (cwdMod) {
|
|
102
|
+
for (const n of listTestNames(cwdMod.testsPath)) names.add(n);
|
|
103
|
+
}
|
|
104
|
+
walkModules(cwd, (_moduleDir, testsPath) => {
|
|
105
|
+
for (const n of listTestNames(testsPath)) names.add(n);
|
|
106
|
+
});
|
|
107
|
+
return Array.from(names).sort();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Return the absolute paths of all modules discoverable under `cwd` that have
|
|
112
|
+
* an e2e tests directory containing at least one `*.test.ts` file.
|
|
113
|
+
*/
|
|
114
|
+
export function findAllModules(cwd: string): string[] {
|
|
115
|
+
const modules: string[] = [];
|
|
116
|
+
walkModules(cwd, (moduleDir, testsPath) => {
|
|
117
|
+
if (listTestNames(testsPath).length > 0) modules.push(moduleDir);
|
|
118
|
+
});
|
|
119
|
+
return modules.sort();
|
|
120
|
+
}
|
package/src/cli/scaffold.ts
CHANGED
|
@@ -10,7 +10,7 @@ const dim = '\x1b[2m';
|
|
|
10
10
|
const reset = '\x1b[0m';
|
|
11
11
|
|
|
12
12
|
interface ScaffoldOptions {
|
|
13
|
-
|
|
13
|
+
testsPath: string;
|
|
14
14
|
name: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -92,12 +92,11 @@ describe('${name}', () => {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
export function runScaffold(options: ScaffoldOptions): void {
|
|
95
|
-
const {
|
|
95
|
+
const { testsPath, name } = options;
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
mkdirSync(testsDir, { recursive: true });
|
|
97
|
+
mkdirSync(testsPath, { recursive: true });
|
|
99
98
|
|
|
100
|
-
const outPath = join(
|
|
99
|
+
const outPath = join(testsPath, `${name}.test.ts`);
|
|
101
100
|
if (existsSync(outPath)) {
|
|
102
101
|
console.error(`Error: ${outPath} already exists`);
|
|
103
102
|
process.exit(1);
|