javi-forge 1.10.0 → 1.10.1

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.
@@ -83,13 +83,13 @@ async function buildCICommands(stack, buildTool, projectDir) {
83
83
  case "java-gradle":
84
84
  return {
85
85
  lintCmd: "./gradlew spotlessCheck --no-daemon",
86
- compileCmd: "./gradlew clean classes testClasses --no-daemon && chown -R runner:runner build/ .gradle/ 2>/dev/null || true",
86
+ compileCmd: "./gradlew clean classes testClasses --no-daemon",
87
87
  testCmd: "./gradlew test --no-daemon",
88
88
  };
89
89
  case "java-maven":
90
90
  return {
91
91
  lintCmd: "./mvnw spotless:check",
92
- compileCmd: "./mvnw clean compile test-compile && chown -R runner:runner target/ .mvn/ 2>/dev/null || true",
92
+ compileCmd: "./mvnw clean compile test-compile",
93
93
  testCmd: "./mvnw test",
94
94
  };
95
95
  case "node": {
@@ -101,14 +101,14 @@ async function buildCICommands(stack, buildTool, projectDir) {
101
101
  catch {
102
102
  /* no package.json */
103
103
  }
104
- // Clean dist/ before build and chown after so tests (as runner) can access output.
105
- // Runs as root inside the container to handle host-owned output dirs.
104
+ // Clean dist/ before build so a stale directory never masks a broken
105
+ // build. The container runs as the host uid (see runInContainer,
106
+ // ENV-1), so output lands host-owned — no chown needed.
106
107
  const buildPrefix = "rm -rf dist/ && ";
107
- const buildSuffix = " && chown -R runner:runner dist/ 2>/dev/null || true";
108
108
  return {
109
109
  lintCmd: pkgContent.includes('"lint"') ? `${buildTool} run lint` : null,
110
110
  compileCmd: pkgContent.includes('"build"')
111
- ? `${buildPrefix}${buildTool} run build${buildSuffix}`
111
+ ? `${buildPrefix}${buildTool} run build`
112
112
  : null,
113
113
  testCmd: pkgContent.includes('"test"')
114
114
  ? `${buildTool} ${buildTool === "npm" ? "test" : "run test"}`
@@ -124,13 +124,13 @@ async function buildCICommands(stack, buildTool, projectDir) {
124
124
  case "go":
125
125
  return {
126
126
  lintCmd: "golangci-lint run",
127
- compileCmd: "go clean -cache && go build ./... && chown -R runner:runner . 2>/dev/null || true",
127
+ compileCmd: "go clean -cache && go build ./...",
128
128
  testCmd: "go test ./...",
129
129
  };
130
130
  case "rust":
131
131
  return {
132
132
  lintCmd: "cargo clippy -- -D warnings",
133
- compileCmd: "cargo clean && cargo build && chown -R runner:runner target/ 2>/dev/null || true",
133
+ compileCmd: "cargo clean && cargo build",
134
134
  testCmd: "cargo test",
135
135
  };
136
136
  default:
@@ -565,7 +565,6 @@ async function runRunner(runner, ctx) {
565
565
  id: "compile",
566
566
  label: "Compile",
567
567
  cmds: runner.compileCmds,
568
- user: "root",
569
568
  skip: false,
570
569
  },
571
570
  {
@@ -189,6 +189,20 @@ export async function runInContainer(options) {
189
189
  // detection happens on this path — ever.
190
190
  const imageName = image;
191
191
  const isInteractive = process.stdin.isTTY && stream;
192
+ // ENV-1: match the container process to the HOST user. The images bake a
193
+ // `runner` user whose uid depends on the base (1001 on node:22-slim, where
194
+ // uid 1000 is already `node`). When that uid differs from the host uid,
195
+ // everything the container writes to the bind-mounted workspace (dist/,
196
+ // node_modules/.vite-temp, build output) lands owned by the wrong user,
197
+ // and the host's local vitest then fails with EACCES. Running as the host
198
+ // uid:gid makes artifacts host-owned — no chown dance, ever. An explicit
199
+ // `user` override still wins (e.g. a caller that needs root). getuid/getgid
200
+ // are undefined on non-POSIX platforms (Windows); there we omit the flag
201
+ // and keep the image default.
202
+ const uid = process.getuid?.();
203
+ const gid = process.getgid?.();
204
+ const runAsUser = user ??
205
+ (uid !== undefined && gid !== undefined ? `${uid}:${gid}` : undefined);
192
206
  // Use --mount instead of -v: the -v form parses the value as a single
193
207
  // "src:dst[:opt]" colon-separated string, which breaks (and could be
194
208
  // hijacked) when projectDir itself contains a colon. --mount takes
@@ -201,7 +215,7 @@ export async function runInContainer(options) {
201
215
  "30",
202
216
  "--entrypoint",
203
217
  "",
204
- ...(user ? ["--user", user] : []),
218
+ ...(runAsUser ? ["--user", runAsUser] : []),
205
219
  "--mount",
206
220
  `type=bind,source=${projectDir},target=/home/runner/work`,
207
221
  "-e",
@@ -237,6 +251,11 @@ export async function runInContainer(options) {
237
251
  */
238
252
  export async function openShell(projectDir, image) {
239
253
  const imageName = image;
254
+ // ENV-1: run the interactive shell as the host user too, so anything
255
+ // written from the debug shell stays host-owned. See runInContainer.
256
+ const uid = process.getuid?.();
257
+ const gid = process.getgid?.();
258
+ const runAsUser = uid !== undefined && gid !== undefined ? `${uid}:${gid}` : undefined;
240
259
  await new Promise((resolve, reject) => {
241
260
  const proc = spawn("docker", [
242
261
  "run",
@@ -244,6 +263,7 @@ export async function openShell(projectDir, image) {
244
263
  "-it",
245
264
  "--entrypoint",
246
265
  "",
266
+ ...(runAsUser ? ["--user", runAsUser] : []),
247
267
  // --mount is colon-safe; see runInContainer for the rationale.
248
268
  "--mount",
249
269
  `type=bind,source=${projectDir},target=/home/runner/work`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javi-forge",
3
- "version": "1.10.0",
3
+ "version": "1.10.1",
4
4
  "description": "Project scaffolding and AI-ready CI bootstrap",
5
5
  "type": "module",
6
6
  "bin": {