codex-multi-auth 0.1.0 → 0.1.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/LICENSE +1 -1
- package/README.md +223 -98
- package/assets/readme-hero.svg +31 -31
- package/config/README.md +30 -87
- package/config/{opencode-legacy.json → codex-legacy.json} +571 -571
- package/config/{opencode-modern.json → codex-modern.json} +241 -239
- package/config/{minimal-opencode.json → minimal-codex.json} +15 -13
- package/config/schema/config.schema.json +21 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -20
- package/dist/index.js.map +1 -1
- package/dist/lib/accounts.d.ts +1 -1
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/auth/auth.d.ts +1 -1
- package/dist/lib/auth/auth.js +1 -1
- package/dist/lib/cli.d.ts +1 -1
- package/dist/lib/cli.js +4 -4
- package/dist/lib/cli.js.map +1 -1
- package/dist/lib/codex-manager/settings-hub.d.ts +6 -0
- package/dist/lib/codex-manager/settings-hub.d.ts.map +1 -0
- package/dist/lib/codex-manager/settings-hub.js +1786 -0
- package/dist/lib/codex-manager/settings-hub.js.map +1 -0
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +5 -1637
- package/dist/lib/codex-manager.js.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +20 -11
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/context-overflow.d.ts +1 -1
- package/dist/lib/context-overflow.js +11 -11
- package/dist/lib/oauth-success.html +1 -1
- package/dist/lib/prompts/codex-host-bridge.d.ts +19 -0
- package/dist/lib/prompts/codex-host-bridge.d.ts.map +1 -0
- package/dist/lib/prompts/{codex-opencode-bridge.js → codex-host-bridge.js} +143 -143
- package/dist/lib/prompts/codex-host-bridge.js.map +1 -0
- package/dist/lib/prompts/codex.d.ts +2 -2
- package/dist/lib/prompts/codex.d.ts.map +1 -1
- package/dist/lib/prompts/codex.js +3 -3
- package/dist/lib/prompts/host-codex-prompt.d.ts +25 -0
- package/dist/lib/prompts/host-codex-prompt.d.ts.map +1 -0
- package/dist/lib/prompts/{opencode-codex.js → host-codex-prompt.js} +136 -47
- package/dist/lib/prompts/host-codex-prompt.js.map +1 -0
- package/dist/lib/recovery/storage.d.ts +2 -2
- package/dist/lib/recovery/storage.js +2 -2
- package/dist/lib/recovery/types.d.ts +1 -1
- package/dist/lib/recovery/types.js +1 -1
- package/dist/lib/recovery.d.ts +1 -1
- package/dist/lib/recovery.d.ts.map +1 -1
- package/dist/lib/recovery.js +1 -4
- package/dist/lib/recovery.js.map +1 -1
- package/dist/lib/request/fetch-helpers.d.ts +3 -3
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +6 -2
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/request/helpers/input-utils.d.ts +2 -2
- package/dist/lib/request/helpers/input-utils.d.ts.map +1 -1
- package/dist/lib/request/helpers/input-utils.js +14 -14
- package/dist/lib/request/helpers/input-utils.js.map +1 -1
- package/dist/lib/request/helpers/model-map.d.ts +2 -2
- package/dist/lib/request/helpers/model-map.js +2 -2
- package/dist/lib/request/request-transformer.d.ts +12 -12
- package/dist/lib/request/request-transformer.d.ts.map +1 -1
- package/dist/lib/request/request-transformer.js +23 -24
- package/dist/lib/request/request-transformer.js.map +1 -1
- package/dist/lib/runtime-paths.d.ts +4 -4
- package/dist/lib/runtime-paths.d.ts.map +1 -1
- package/dist/lib/runtime-paths.js +57 -13
- package/dist/lib/runtime-paths.js.map +1 -1
- package/dist/lib/storage/paths.d.ts +11 -0
- package/dist/lib/storage/paths.d.ts.map +1 -1
- package/dist/lib/storage/paths.js +167 -5
- package/dist/lib/storage/paths.js.map +1 -1
- package/dist/lib/storage.d.ts +1 -0
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +290 -81
- package/dist/lib/storage.js.map +1 -1
- package/dist/lib/tools/hashline-tools.d.ts +1 -1
- package/dist/lib/tools/hashline-tools.d.ts.map +1 -1
- package/dist/lib/tools/hashline-tools.js +1 -1
- package/dist/lib/tools/hashline-tools.js.map +1 -1
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/ui/copy.d.ts +6 -6
- package/dist/lib/ui/copy.js +6 -6
- package/dist/lib/ui/copy.js.map +1 -1
- package/node_modules/@codex-ai/plugin/dist/index.d.ts +2 -0
- package/node_modules/@codex-ai/plugin/dist/index.js +2 -0
- package/node_modules/@codex-ai/plugin/dist/tool.d.ts +42 -0
- package/node_modules/@codex-ai/plugin/dist/tool.js +29 -0
- package/node_modules/@codex-ai/plugin/package.json +9 -0
- package/package.json +30 -16
- package/scripts/audit-dev-allowlist.js +128 -128
- package/scripts/bench-format/{opencode.mjs → codex-host.mjs} +206 -205
- package/scripts/bench-format/hashline-v2.mjs +642 -642
- package/scripts/bench-format/models.mjs +111 -105
- package/scripts/bench-format/render.mjs +496 -496
- package/scripts/bench-format/stats.mjs +54 -54
- package/scripts/bench-format/tasks.mjs +151 -151
- package/scripts/benchmark-edit-formats.mjs +1162 -1161
- package/scripts/benchmark-render-dashboard.mjs +49 -49
- package/scripts/codex.js +40 -2
- package/scripts/copy-oauth-success.js +37 -37
- package/scripts/install-codex-auth-utils.js +49 -0
- package/scripts/{install-opencode-codex-auth.js → install-codex-auth.js} +220 -193
- package/scripts/repo-hygiene.js +320 -0
- package/scripts/test-all-models.sh +7 -7
- package/scripts/test-model-matrix.js +475 -423
- package/scripts/validate-model-map.sh +7 -7
- package/vendor/codex-ai-plugin/dist/index.d.ts +2 -0
- package/vendor/codex-ai-plugin/dist/index.js +2 -0
- package/vendor/codex-ai-plugin/dist/tool.d.ts +42 -0
- package/vendor/codex-ai-plugin/dist/tool.js +29 -0
- package/vendor/codex-ai-plugin/package.json +9 -0
- package/vendor/codex-ai-sdk/dist/index.d.ts +4 -0
- package/vendor/codex-ai-sdk/dist/index.js +2 -0
- package/vendor/codex-ai-sdk/package.json +8 -0
- package/assets/opencode-logo-ornate-dark.svg +0 -18
- package/dist/lib/prompts/codex-opencode-bridge.d.ts +0 -19
- package/dist/lib/prompts/codex-opencode-bridge.d.ts.map +0 -1
- package/dist/lib/prompts/codex-opencode-bridge.js.map +0 -1
- package/dist/lib/prompts/opencode-codex.d.ts +0 -25
- package/dist/lib/prompts/opencode-codex.d.ts.map +0 -1
- package/dist/lib/prompts/opencode-codex.js.map +0 -1
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { promises as fs } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Retry-capable fs.rm for Windows EBUSY/EPERM/ENOTEMPTY errors.
|
|
10
|
+
* Uses linear backoff (attempt * 50ms) with up to 5 retries.
|
|
11
|
+
*/
|
|
12
|
+
async function removeWithRetry(targetPath, options) {
|
|
13
|
+
const retryableCodes = new Set(["ENOTEMPTY", "EPERM", "EBUSY"]);
|
|
14
|
+
const maxAttempts = 6;
|
|
15
|
+
|
|
16
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
17
|
+
try {
|
|
18
|
+
await fs.rm(targetPath, options);
|
|
19
|
+
return;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
22
|
+
const shouldRetry = code !== undefined && retryableCodes.has(code);
|
|
23
|
+
if (!shouldRetry || attempt === maxAttempts) {
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
await new Promise((resolve) => setTimeout(resolve, attempt * 50));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const ROOT_TEMP_PATTERN = /^\.?tmp/i;
|
|
32
|
+
const GENERATED_DIRS = new Set([
|
|
33
|
+
"coverage",
|
|
34
|
+
"dist",
|
|
35
|
+
"node_modules",
|
|
36
|
+
".omx",
|
|
37
|
+
".opencode",
|
|
38
|
+
".sisyphus",
|
|
39
|
+
]);
|
|
40
|
+
const TRACKED_SCRATCH_FILES = new Set([
|
|
41
|
+
"task_plan.md",
|
|
42
|
+
"findings.md",
|
|
43
|
+
"progress.md",
|
|
44
|
+
"test-results.md",
|
|
45
|
+
]);
|
|
46
|
+
const TRAVERSAL_SKIP_DIRS = new Set([
|
|
47
|
+
".git",
|
|
48
|
+
"node_modules",
|
|
49
|
+
"dist",
|
|
50
|
+
"coverage",
|
|
51
|
+
"vendor",
|
|
52
|
+
".omx",
|
|
53
|
+
".opencode",
|
|
54
|
+
".sisyphus",
|
|
55
|
+
".history",
|
|
56
|
+
]);
|
|
57
|
+
const REQUIRED_GITIGNORE_PATTERNS = [
|
|
58
|
+
"tmp",
|
|
59
|
+
".tmp",
|
|
60
|
+
"coverage/",
|
|
61
|
+
"dist/",
|
|
62
|
+
"node_modules/",
|
|
63
|
+
".omx/",
|
|
64
|
+
".opencode/",
|
|
65
|
+
".sisyphus/",
|
|
66
|
+
"task_plan.md",
|
|
67
|
+
"findings.md",
|
|
68
|
+
"progress.md",
|
|
69
|
+
"test-results.md",
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
function parseArgs(argv) {
|
|
73
|
+
const args = {
|
|
74
|
+
command: "",
|
|
75
|
+
mode: "safe",
|
|
76
|
+
root: process.cwd(),
|
|
77
|
+
dryRun: false,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
if (argv.length === 0) {
|
|
81
|
+
throw new Error("Usage: repo-hygiene.js <clean|check> [--mode aggressive] [--root path] [--dry-run]");
|
|
82
|
+
}
|
|
83
|
+
args.command = argv[0] ?? "";
|
|
84
|
+
|
|
85
|
+
for (let i = 1; i < argv.length; i += 1) {
|
|
86
|
+
const arg = argv[i];
|
|
87
|
+
if (arg === "--mode") {
|
|
88
|
+
const value = argv[i + 1];
|
|
89
|
+
if (!value || value.startsWith("--")) {
|
|
90
|
+
throw new Error("--mode requires a value");
|
|
91
|
+
}
|
|
92
|
+
args.mode = value;
|
|
93
|
+
i += 1;
|
|
94
|
+
} else if (arg === "--root") {
|
|
95
|
+
const value = argv[i + 1];
|
|
96
|
+
if (!value || value.startsWith("--")) {
|
|
97
|
+
throw new Error("--root requires a value");
|
|
98
|
+
}
|
|
99
|
+
args.root = value;
|
|
100
|
+
i += 1;
|
|
101
|
+
} else if (arg === "--dry-run") {
|
|
102
|
+
args.dryRun = true;
|
|
103
|
+
} else if (arg.startsWith("--")) {
|
|
104
|
+
throw new Error(`Unknown flag: ${arg}`);
|
|
105
|
+
} else {
|
|
106
|
+
throw new Error(`Unexpected positional argument: ${arg}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!args.root) {
|
|
111
|
+
throw new Error("--root requires a value");
|
|
112
|
+
}
|
|
113
|
+
if (args.command === "clean" && args.mode !== "aggressive") {
|
|
114
|
+
throw new Error('Only "--mode aggressive" is allowed for clean');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return args;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function ensureRepoRoot(rootPath) {
|
|
121
|
+
const resolved = path.resolve(rootPath);
|
|
122
|
+
const gitDir = path.join(resolved, ".git");
|
|
123
|
+
try {
|
|
124
|
+
await fs.stat(gitDir);
|
|
125
|
+
return resolved;
|
|
126
|
+
} catch {
|
|
127
|
+
throw new Error(`Root path is not a git working tree: ${resolved}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isDeletionCandidate(name, isDirectory) {
|
|
132
|
+
if (TRACKED_SCRATCH_FILES.has(name)) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
if (isDirectory && GENERATED_DIRS.has(name)) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
return ROOT_TEMP_PATTERN.test(name);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function isProtectedName(name) {
|
|
142
|
+
return [
|
|
143
|
+
".git",
|
|
144
|
+
"lib",
|
|
145
|
+
"test",
|
|
146
|
+
"docs",
|
|
147
|
+
"scripts",
|
|
148
|
+
"config",
|
|
149
|
+
"assets",
|
|
150
|
+
"vendor",
|
|
151
|
+
].includes(name);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function isDeepTempCandidate(name, isDirectory) {
|
|
155
|
+
if (isDirectory && (name === "tmp" || ROOT_TEMP_PATTERN.test(name))) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
if (!isDirectory && (name.startsWith(".tmp") || name.endsWith(".tmp"))) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function collectNestedTempCandidates(rootPath, dirPath, addCandidate) {
|
|
165
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
166
|
+
for (const entry of entries) {
|
|
167
|
+
const entryPath = path.join(dirPath, entry.name);
|
|
168
|
+
if (entry.isDirectory() && TRAVERSAL_SKIP_DIRS.has(entry.name)) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (isDeepTempCandidate(entry.name, entry.isDirectory())) {
|
|
172
|
+
addCandidate(
|
|
173
|
+
path.relative(rootPath, entryPath).replaceAll("\\", "/"),
|
|
174
|
+
entryPath,
|
|
175
|
+
entry.isDirectory(),
|
|
176
|
+
);
|
|
177
|
+
if (entry.isDirectory()) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (entry.isDirectory()) {
|
|
182
|
+
await collectNestedTempCandidates(rootPath, entryPath, addCandidate);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function collectCandidates(rootPath) {
|
|
188
|
+
const rootEntries = await fs.readdir(rootPath, { withFileTypes: true });
|
|
189
|
+
const candidates = [];
|
|
190
|
+
const candidatePaths = new Set();
|
|
191
|
+
const addCandidate = (name, candidatePath, isDirectory) => {
|
|
192
|
+
if (candidatePaths.has(candidatePath)) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
candidatePaths.add(candidatePath);
|
|
196
|
+
candidates.push({ name, path: candidatePath, isDirectory });
|
|
197
|
+
};
|
|
198
|
+
for (const entry of rootEntries) {
|
|
199
|
+
if (isProtectedName(entry.name)) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (!isDeletionCandidate(entry.name, entry.isDirectory())) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
addCandidate(entry.name, path.join(rootPath, entry.name), entry.isDirectory());
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
await collectNestedTempCandidates(rootPath, rootPath, addCandidate);
|
|
209
|
+
return candidates;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function clean(rootPath, dryRun) {
|
|
213
|
+
const candidates = await collectCandidates(rootPath);
|
|
214
|
+
if (candidates.length === 0) {
|
|
215
|
+
console.log("repo-hygiene: no cleanup candidates found");
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for (const candidate of candidates) {
|
|
220
|
+
const relative = path.relative(rootPath, candidate.path);
|
|
221
|
+
if (!relative || relative.includes("..")) {
|
|
222
|
+
throw new Error(`Refusing to delete non-root candidate: ${candidate.path}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (dryRun) {
|
|
226
|
+
console.log(`[dry-run] delete ${candidate.name}`);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
await removeWithRetry(candidate.path, { recursive: true, force: true });
|
|
230
|
+
console.log(`deleted ${candidate.name}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function getTrackedPaths(rootPath) {
|
|
235
|
+
try {
|
|
236
|
+
const output = execFileSync("git", ["ls-files"], {
|
|
237
|
+
cwd: rootPath,
|
|
238
|
+
encoding: "utf-8",
|
|
239
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
240
|
+
});
|
|
241
|
+
return output.split(/\r?\n/).filter(Boolean);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
throw new Error(
|
|
244
|
+
`git ls-files failed in getTrackedPaths: ${error instanceof Error ? error.message : String(error)}`,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function check(rootPath) {
|
|
250
|
+
let hasError = false;
|
|
251
|
+
|
|
252
|
+
const tracked = getTrackedPaths(rootPath);
|
|
253
|
+
const trackedViolations = new Set();
|
|
254
|
+
for (const trackedPath of tracked) {
|
|
255
|
+
const normalized = trackedPath.replaceAll("\\", "/");
|
|
256
|
+
const basename = normalized.split("/").pop() ?? normalized;
|
|
257
|
+
if (TRACKED_SCRATCH_FILES.has(basename)) {
|
|
258
|
+
trackedViolations.add(normalized);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (trackedViolations.size > 0) {
|
|
263
|
+
hasError = true;
|
|
264
|
+
console.error("repo-hygiene check failed: tracked scratch files present:");
|
|
265
|
+
for (const item of [...trackedViolations].sort()) {
|
|
266
|
+
console.error(` - ${item}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const gitignorePath = path.join(rootPath, ".gitignore");
|
|
271
|
+
let gitignore = "";
|
|
272
|
+
try {
|
|
273
|
+
gitignore = await fs.readFile(gitignorePath, "utf-8");
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.warn(
|
|
276
|
+
`repo-hygiene warning: unable to read .gitignore at ${gitignorePath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
const gitignoreLines = new Set(
|
|
280
|
+
gitignore
|
|
281
|
+
.split(/\r?\n/)
|
|
282
|
+
.map((line) => line.trim())
|
|
283
|
+
.filter((line) => line.length > 0 && !line.startsWith("#")),
|
|
284
|
+
);
|
|
285
|
+
const missingPatterns = REQUIRED_GITIGNORE_PATTERNS.filter((pattern) => !gitignoreLines.has(pattern));
|
|
286
|
+
if (missingPatterns.length > 0) {
|
|
287
|
+
hasError = true;
|
|
288
|
+
console.error("repo-hygiene check failed: missing required .gitignore patterns:");
|
|
289
|
+
for (const pattern of missingPatterns) {
|
|
290
|
+
console.error(` - ${pattern}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (hasError) {
|
|
295
|
+
process.exitCode = 1;
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
console.log("repo-hygiene check passed");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function main() {
|
|
302
|
+
const args = parseArgs(process.argv.slice(2));
|
|
303
|
+
const rootPath = await ensureRepoRoot(args.root);
|
|
304
|
+
|
|
305
|
+
if (args.command === "clean") {
|
|
306
|
+
await clean(rootPath, args.dryRun);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (args.command === "check") {
|
|
310
|
+
await check(rootPath);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
throw new Error(`Unknown command: ${args.command}`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
main().catch((error) => {
|
|
318
|
+
console.error(`repo-hygiene error: ${error instanceof Error ? error.message : String(error)}`);
|
|
319
|
+
process.exitCode = 1;
|
|
320
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
set -euo pipefail
|
|
4
|
-
|
|
5
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
-
node "${SCRIPT_DIR}/test-model-matrix.js" "$@"
|
|
7
|
-
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
node "${SCRIPT_DIR}/test-model-matrix.js" "$@"
|
|
7
|
+
|