@peachlife/artisan 0.1.3 → 0.1.4
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/package.json +1 -1
- package/src/api/injection.mjs +27 -3
- package/src/cli/commands/test.mjs +1 -1
- package/src/core/config.mjs +30 -2
- package/src/core/container-manager.mjs +14 -1
package/package.json
CHANGED
package/src/api/injection.mjs
CHANGED
|
@@ -7,14 +7,30 @@ export {
|
|
|
7
7
|
expect,
|
|
8
8
|
} from "vitest";
|
|
9
9
|
|
|
10
|
-
import { test as vitestTest } from "vitest";
|
|
10
|
+
import { onTestFailed as vitestOnTestFailed, test as vitestTest } from "vitest";
|
|
11
11
|
import { DEFAULT_XDG_CONFIG_HOME } from "../core/constants.mjs";
|
|
12
12
|
import { ContainerManager } from "../core/container-manager.mjs";
|
|
13
13
|
|
|
14
|
-
export function createTestContext(
|
|
14
|
+
export function createTestContext(
|
|
15
|
+
container,
|
|
16
|
+
{ onTestFailed = vitestOnTestFailed } = {},
|
|
17
|
+
) {
|
|
18
|
+
let lastRun;
|
|
19
|
+
|
|
20
|
+
onTestFailed(() => {
|
|
21
|
+
if (!lastRun) return;
|
|
22
|
+
console.error("\n--- artisan: last run() output ---");
|
|
23
|
+
if (lastRun.stdout) console.error("stdout:", lastRun.stdout);
|
|
24
|
+
if (lastRun.stderr) console.error("stderr:", lastRun.stderr);
|
|
25
|
+
console.error("exitCode:", lastRun.exitCode);
|
|
26
|
+
console.error("---");
|
|
27
|
+
});
|
|
28
|
+
|
|
15
29
|
return {
|
|
16
30
|
async run(arguments_ = "", options = {}) {
|
|
17
|
-
|
|
31
|
+
const result = await container.exec(arguments_, options);
|
|
32
|
+
lastRun = result;
|
|
33
|
+
return result;
|
|
18
34
|
},
|
|
19
35
|
async copyFixture(localPath, containerPath) {
|
|
20
36
|
return container.copyFile(localPath, containerPath);
|
|
@@ -29,6 +45,11 @@ export function createTestContext(container) {
|
|
|
29
45
|
}
|
|
30
46
|
}
|
|
31
47
|
},
|
|
48
|
+
async shell(command, options = {}) {
|
|
49
|
+
const result = await container.shell(command, options);
|
|
50
|
+
lastRun = result;
|
|
51
|
+
return result;
|
|
52
|
+
},
|
|
32
53
|
};
|
|
33
54
|
}
|
|
34
55
|
|
|
@@ -81,6 +102,9 @@ function buildExtendedTest() {
|
|
|
81
102
|
)
|
|
82
103
|
.extend("setup", async ({ artisanContext }) =>
|
|
83
104
|
artisanContext.setup.bind(artisanContext),
|
|
105
|
+
)
|
|
106
|
+
.extend("shell", async ({ artisanContext }) =>
|
|
107
|
+
artisanContext.shell.bind(artisanContext),
|
|
84
108
|
);
|
|
85
109
|
}
|
|
86
110
|
|
|
@@ -77,13 +77,13 @@ function buildVitestArguments({ options, merged, testFiles }) {
|
|
|
77
77
|
value:
|
|
78
78
|
options.retries === undefined ? undefined : String(options.retries),
|
|
79
79
|
},
|
|
80
|
-
{ flag: "--verbose", value: options.verbose, boolean: true },
|
|
81
80
|
];
|
|
82
81
|
for (const { flag, value, boolean = false } of flagRules) {
|
|
83
82
|
if (!value) continue;
|
|
84
83
|
vitestArguments.push(flag);
|
|
85
84
|
if (!boolean) vitestArguments.push(value);
|
|
86
85
|
}
|
|
86
|
+
if (options.verbose) vitestArguments.push("--reporter", "verbose");
|
|
87
87
|
return vitestArguments;
|
|
88
88
|
}
|
|
89
89
|
|
package/src/core/config.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
3
|
import { UsageError } from "../utils/errors.mjs";
|
|
4
4
|
import { DEFAULT_DISTROS } from "./constants.mjs";
|
|
5
5
|
|
|
@@ -63,6 +63,14 @@ function validateConfigShape(config) {
|
|
|
63
63
|
if (config.artifact !== undefined && typeof config.artifact !== "string") {
|
|
64
64
|
throw new UsageError('"artifact" must be a string');
|
|
65
65
|
}
|
|
66
|
+
if (
|
|
67
|
+
typeof config.artifact === "string" &&
|
|
68
|
+
(isAbsolute(config.artifact) || config.artifact.startsWith("~"))
|
|
69
|
+
) {
|
|
70
|
+
throw new UsageError(
|
|
71
|
+
'"artifact" must be a repo-relative path (e.g. "./bin/mycli"), not an absolute or home-relative path',
|
|
72
|
+
);
|
|
73
|
+
}
|
|
66
74
|
if (config.distros !== undefined)
|
|
67
75
|
assertStringArray(config.distros, "distros");
|
|
68
76
|
if (
|
|
@@ -118,8 +126,28 @@ export async function readJsonConfig(configPath, { optional = false } = {}) {
|
|
|
118
126
|
}
|
|
119
127
|
}
|
|
120
128
|
|
|
129
|
+
async function fileExists(filePath) {
|
|
130
|
+
try {
|
|
131
|
+
await access(filePath);
|
|
132
|
+
return true;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (error?.code === "ENOENT") return false;
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
121
139
|
export async function loadConfig(cwd = process.cwd()) {
|
|
122
140
|
const configPath = join(cwd, "artisan.config.json");
|
|
141
|
+
const packageJsonPath = join(cwd, "package.json");
|
|
142
|
+
const [configExists, packageExists] = await Promise.all([
|
|
143
|
+
fileExists(configPath),
|
|
144
|
+
fileExists(packageJsonPath),
|
|
145
|
+
]);
|
|
146
|
+
if (!configExists && !packageExists) {
|
|
147
|
+
throw new UsageError(
|
|
148
|
+
"No artisan.config.json or package.json found — run from the project root",
|
|
149
|
+
);
|
|
150
|
+
}
|
|
123
151
|
const fileConfig = await readJsonConfig(configPath, { optional: true });
|
|
124
152
|
if (fileConfig.configs !== undefined) {
|
|
125
153
|
validateConfigs(fileConfig.configs);
|
|
@@ -8,6 +8,8 @@ import { BINARY_MOUNT_DIR } from "./constants.mjs";
|
|
|
8
8
|
const NO_CAP = 0;
|
|
9
9
|
const SH_C = ["sh", "-c"];
|
|
10
10
|
const ERR_NOT_STARTED = "Container not started";
|
|
11
|
+
// eslint-disable-next-line sonarjs/publicly-writable-directories
|
|
12
|
+
const DEFAULT_WORKDIR = "/tmp/work";
|
|
11
13
|
|
|
12
14
|
function parseChar(state, char) {
|
|
13
15
|
if (state.isEscaped) {
|
|
@@ -173,6 +175,17 @@ export class ContainerManager {
|
|
|
173
175
|
|
|
174
176
|
this.#container = await builder.start();
|
|
175
177
|
|
|
178
|
+
const workdirResult = await this.#execBounded(
|
|
179
|
+
[...SH_C, `mkdir -p ${DEFAULT_WORKDIR}`],
|
|
180
|
+
{},
|
|
181
|
+
NO_CAP,
|
|
182
|
+
);
|
|
183
|
+
if (workdirResult.exitCode !== 0) {
|
|
184
|
+
throw new DockerError(
|
|
185
|
+
`failed to initialize default workdir ${DEFAULT_WORKDIR}\n${workdirResult.stderr || workdirResult.stdout}`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
176
189
|
for (const command of this.#setupCommands) {
|
|
177
190
|
const result = await this.#execBounded([...SH_C, command], {}, NO_CAP);
|
|
178
191
|
if (result.exitCode !== 0) {
|
|
@@ -201,7 +214,7 @@ export class ContainerManager {
|
|
|
201
214
|
const {
|
|
202
215
|
env: environment = {},
|
|
203
216
|
timeout = NO_CAP,
|
|
204
|
-
cwd =
|
|
217
|
+
cwd = DEFAULT_WORKDIR,
|
|
205
218
|
} = options;
|
|
206
219
|
const command = [
|
|
207
220
|
this.#mountTarget,
|