mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
fix(devcontainer): set persist-credentials:false on CI checkouts + prettier
- zizmor `artipacked` (CodeQL/GitHub Advanced Security) flagged both actions/checkout steps in ci-devcontainer.yml: checkout defaults to persist-credentials:true, leaving GITHUB_TOKEN in .git/config where it can leak into uploaded artifacts. Both jobs are read-only (run tests / build smoke, never push), so persist-credentials:false is correct — matches the repo convention in codeql.yml / ci-tests.yml. - Ran prettier 3.8.0 over the new .cjs modules + test (single-quote/style normalization to match the repo). JSON/YAML were already compliant; README is in .prettierignore; .sh has no prettier parser. Behavior unchanged — 12/12 transform unit tests still pass.
This commit is contained in:
parent
1008b0dcf9
commit
1a2c480847
4 changed files with 92 additions and 102 deletions
|
|
@ -14,17 +14,17 @@
|
|||
// test harness). DISABLE_AUTOUPDATER=1 (containerEnv) already neutralizes
|
||||
// runtime updates; this purely silences the doctor mismatch + native probe.
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
const fs = require("fs");
|
||||
const fs = require('fs');
|
||||
|
||||
// Host binary-management / machine-install fields — never valid for an
|
||||
// `npm install -g` container. Stripping lets Claude auto-detect npm-global.
|
||||
const MACHINE_FIELDS = [
|
||||
"installMethod",
|
||||
"autoUpdates",
|
||||
"autoUpdatesProtectedForNative",
|
||||
"shiftEnterKeyBindingInstalled",
|
||||
'installMethod',
|
||||
'autoUpdates',
|
||||
'autoUpdatesProtectedForNative',
|
||||
'shiftEnterKeyBindingInstalled',
|
||||
];
|
||||
|
||||
// Pure transform: take whatever the host file parsed to and return the
|
||||
|
|
@ -34,7 +34,7 @@ const MACHINE_FIELDS = [
|
|||
// hasCompletedOnboarding assignment, and re-trigger onboarding every rebuild).
|
||||
function sanitizeClaudeConfig(parsed) {
|
||||
let cfg = parsed;
|
||||
if (cfg === null || typeof cfg !== "object" || Array.isArray(cfg)) {
|
||||
if (cfg === null || typeof cfg !== 'object' || Array.isArray(cfg)) {
|
||||
cfg = {};
|
||||
}
|
||||
for (const k of MACHINE_FIELDS) {
|
||||
|
|
@ -47,7 +47,7 @@ function sanitizeClaudeConfig(parsed) {
|
|||
function readHostConfig(src) {
|
||||
try {
|
||||
if (fs.existsSync(src) && fs.statSync(src).size > 0) {
|
||||
return JSON.parse(fs.readFileSync(src, "utf8"));
|
||||
return JSON.parse(fs.readFileSync(src, 'utf8'));
|
||||
}
|
||||
} catch {
|
||||
// Malformed/unreadable host file — fall back to an empty config so the
|
||||
|
|
@ -57,16 +57,14 @@ function readHostConfig(src) {
|
|||
}
|
||||
|
||||
function main() {
|
||||
const src = process.argv[2] || "/host/.claude.json";
|
||||
const dst = process.argv[3] || "/home/node/.claude.json";
|
||||
const src = process.argv[2] || '/host/.claude.json';
|
||||
const dst = process.argv[3] || '/home/node/.claude.json';
|
||||
const cfg = sanitizeClaudeConfig(readHostConfig(src));
|
||||
try {
|
||||
fs.writeFileSync(dst, JSON.stringify(cfg, null, 2));
|
||||
fs.chmodSync(dst, 0o644);
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`[post-create] ERROR: failed to seed ${dst}: ${err && err.message}`,
|
||||
);
|
||||
console.error(`[post-create] ERROR: failed to seed ${dst}: ${err && err.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,18 +13,16 @@
|
|||
// Extracted from a post-create.sh heredoc so the regex + deep rewrite are
|
||||
// lintable and unit-tested (the regex has had path-handling bugs before).
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Match an absolute path that contains `<sep>.<cli><sep>plugins<sep><rest>`
|
||||
// where <sep> is `/` or `\`. Anchored at start; the lazy `.*?` consumes the
|
||||
// home prefix up to the FIRST `.<cli>/plugins` segment.
|
||||
function buildRe(cliName) {
|
||||
return new RegExp(
|
||||
`^(?:[A-Za-z]:)?[\\\\/].*?[\\\\/]\\.${cliName}[\\\\/]plugins[\\\\/](.*)$`,
|
||||
);
|
||||
return new RegExp(`^(?:[A-Za-z]:)?[\\\\/].*?[\\\\/]\\.${cliName}[\\\\/]plugins[\\\\/](.*)$`);
|
||||
}
|
||||
|
||||
// Recursively rewrite every string value in `obj` that matches `re`,
|
||||
|
|
@ -32,33 +30,29 @@ function buildRe(cliName) {
|
|||
// Windows backslashes to forward slashes.
|
||||
function rewriteDeep(obj, re, ctr) {
|
||||
if (Array.isArray(obj)) return obj.map((v) => rewriteDeep(v, re, ctr));
|
||||
if (obj && typeof obj === "object") {
|
||||
if (obj && typeof obj === 'object') {
|
||||
const out = {};
|
||||
for (const [k, v] of Object.entries(obj)) out[k] = rewriteDeep(v, re, ctr);
|
||||
return out;
|
||||
}
|
||||
if (typeof obj === "string") {
|
||||
return obj.replace(re, (_, rest) => `${ctr}/${rest.replace(/\\/g, "/")}`);
|
||||
if (typeof obj === 'string') {
|
||||
return obj.replace(re, (_, rest) => `${ctr}/${rest.replace(/\\/g, '/')}`);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
const REGISTRIES = [
|
||||
{
|
||||
cli: "claude",
|
||||
host: "/host/.claude/plugins",
|
||||
ctr: "/home/node/.claude/plugins",
|
||||
files: [
|
||||
"known_marketplaces.json",
|
||||
"installed_plugins.json",
|
||||
"plugin-catalog-cache.json",
|
||||
],
|
||||
cli: 'claude',
|
||||
host: '/host/.claude/plugins',
|
||||
ctr: '/home/node/.claude/plugins',
|
||||
files: ['known_marketplaces.json', 'installed_plugins.json', 'plugin-catalog-cache.json'],
|
||||
},
|
||||
{
|
||||
cli: "cursor",
|
||||
host: "/host/.cursor/plugins",
|
||||
ctr: "/home/node/.cursor/plugins",
|
||||
files: ["installed_plugins.json"],
|
||||
cli: 'cursor',
|
||||
host: '/host/.cursor/plugins',
|
||||
ctr: '/home/node/.cursor/plugins',
|
||||
files: ['installed_plugins.json'],
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -68,9 +62,7 @@ function translate(registries) {
|
|||
try {
|
||||
fs.mkdirSync(reg.ctr, { recursive: true });
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`[post-create] ERROR: failed to create ${reg.ctr}: ${err && err.message}`,
|
||||
);
|
||||
console.error(`[post-create] ERROR: failed to create ${reg.ctr}: ${err && err.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
for (const name of reg.files) {
|
||||
|
|
@ -79,19 +71,14 @@ function translate(registries) {
|
|||
if (!fs.existsSync(src) || fs.statSync(src).size === 0) continue;
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(fs.readFileSync(src, "utf8"));
|
||||
data = JSON.parse(fs.readFileSync(src, 'utf8'));
|
||||
} catch {
|
||||
continue; // skip a malformed host registry rather than abort
|
||||
}
|
||||
try {
|
||||
fs.writeFileSync(
|
||||
dst,
|
||||
JSON.stringify(rewriteDeep(data, re, reg.ctr), null, 2),
|
||||
);
|
||||
fs.writeFileSync(dst, JSON.stringify(rewriteDeep(data, re, reg.ctr), null, 2));
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`[post-create] ERROR: failed to write ${dst}: ${err && err.message}`,
|
||||
);
|
||||
console.error(`[post-create] ERROR: failed to write ${dst}: ${err && err.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,121 +8,118 @@
|
|||
// Run with the built-in Node test runner (no deps):
|
||||
// node --test .devcontainer/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const {
|
||||
buildRe,
|
||||
rewriteDeep,
|
||||
} = require("./translate-plugin-registries.cjs");
|
||||
const { sanitizeClaudeConfig } = require("./seed-claude-config.cjs");
|
||||
const { buildRe, rewriteDeep } = require('./translate-plugin-registries.cjs');
|
||||
const { sanitizeClaudeConfig } = require('./seed-claude-config.cjs');
|
||||
|
||||
const CLAUDE = "/home/node/.claude/plugins";
|
||||
const CURSOR = "/home/node/.cursor/plugins";
|
||||
const CLAUDE = '/home/node/.claude/plugins';
|
||||
const CURSOR = '/home/node/.cursor/plugins';
|
||||
|
||||
function rw(value, cli, ctr) {
|
||||
return rewriteDeep(value, buildRe(cli), ctr);
|
||||
}
|
||||
|
||||
test("claude: Windows backslash absolute path -> container path", () => {
|
||||
test('claude: Windows backslash absolute path -> container path', () => {
|
||||
assert.equal(
|
||||
rw("C:\\Users\\gergo\\.claude\\plugins\\cache\\x\\1.0", "claude", CLAUDE),
|
||||
"/home/node/.claude/plugins/cache/x/1.0",
|
||||
rw('C:\\Users\\gergo\\.claude\\plugins\\cache\\x\\1.0', 'claude', CLAUDE),
|
||||
'/home/node/.claude/plugins/cache/x/1.0',
|
||||
);
|
||||
});
|
||||
|
||||
test("claude: Windows forward-slash absolute path -> container path", () => {
|
||||
test('claude: Windows forward-slash absolute path -> container path', () => {
|
||||
assert.equal(
|
||||
rw("C:/Users/gergo/.claude/plugins/marketplaces/m", "claude", CLAUDE),
|
||||
"/home/node/.claude/plugins/marketplaces/m",
|
||||
rw('C:/Users/gergo/.claude/plugins/marketplaces/m', 'claude', CLAUDE),
|
||||
'/home/node/.claude/plugins/marketplaces/m',
|
||||
);
|
||||
});
|
||||
|
||||
test("claude: macOS POSIX path -> container path", () => {
|
||||
test('claude: macOS POSIX path -> container path', () => {
|
||||
assert.equal(
|
||||
rw("/Users/alice/.claude/plugins/marketplaces/m", "claude", CLAUDE),
|
||||
"/home/node/.claude/plugins/marketplaces/m",
|
||||
rw('/Users/alice/.claude/plugins/marketplaces/m', 'claude', CLAUDE),
|
||||
'/home/node/.claude/plugins/marketplaces/m',
|
||||
);
|
||||
});
|
||||
|
||||
test("claude: Linux POSIX path -> container path", () => {
|
||||
test('claude: Linux POSIX path -> container path', () => {
|
||||
assert.equal(
|
||||
rw("/home/bob/.claude/plugins/cache/foo", "claude", CLAUDE),
|
||||
"/home/node/.claude/plugins/cache/foo",
|
||||
rw('/home/bob/.claude/plugins/cache/foo', 'claude', CLAUDE),
|
||||
'/home/node/.claude/plugins/cache/foo',
|
||||
);
|
||||
});
|
||||
|
||||
test("cursor: Windows path -> container cursor path", () => {
|
||||
test('cursor: Windows path -> container cursor path', () => {
|
||||
assert.equal(
|
||||
rw("C:\\Users\\gergo\\.cursor\\plugins\\local\\myplug", "cursor", CURSOR),
|
||||
"/home/node/.cursor/plugins/local/myplug",
|
||||
rw('C:\\Users\\gergo\\.cursor\\plugins\\local\\myplug', 'cursor', CURSOR),
|
||||
'/home/node/.cursor/plugins/local/myplug',
|
||||
);
|
||||
});
|
||||
|
||||
test("cross-CLI isolation: claude regex leaves a .cursor path untouched", () => {
|
||||
const input = "C:\\Users\\g\\.cursor\\plugins\\x";
|
||||
assert.equal(rw(input, "claude", CLAUDE), input);
|
||||
test('cross-CLI isolation: claude regex leaves a .cursor path untouched', () => {
|
||||
const input = 'C:\\Users\\g\\.cursor\\plugins\\x';
|
||||
assert.equal(rw(input, 'claude', CLAUDE), input);
|
||||
});
|
||||
|
||||
test("non-path strings pass through unchanged", () => {
|
||||
assert.equal(rw("not-a-path", "claude", CLAUDE), "not-a-path");
|
||||
assert.equal(rw("https://github.com/EveryInc/x.git", "claude", CLAUDE), "https://github.com/EveryInc/x.git");
|
||||
test('non-path strings pass through unchanged', () => {
|
||||
assert.equal(rw('not-a-path', 'claude', CLAUDE), 'not-a-path');
|
||||
assert.equal(
|
||||
rw('https://github.com/EveryInc/x.git', 'claude', CLAUDE),
|
||||
'https://github.com/EveryInc/x.git',
|
||||
);
|
||||
});
|
||||
|
||||
test("non-string scalars pass through unchanged", () => {
|
||||
assert.equal(rw(42, "claude", CLAUDE), 42);
|
||||
assert.equal(rw(null, "claude", CLAUDE), null);
|
||||
assert.equal(rw(true, "claude", CLAUDE), true);
|
||||
test('non-string scalars pass through unchanged', () => {
|
||||
assert.equal(rw(42, 'claude', CLAUDE), 42);
|
||||
assert.equal(rw(null, 'claude', CLAUDE), null);
|
||||
assert.equal(rw(true, 'claude', CLAUDE), true);
|
||||
});
|
||||
|
||||
test("nested objects/arrays are rewritten deeply", () => {
|
||||
test('nested objects/arrays are rewritten deeply', () => {
|
||||
const input = {
|
||||
"compound-engineering@m": [
|
||||
{ installPath: "C:\\Users\\g\\.claude\\plugins\\cache\\ce\\3.9.2", version: "3.9.2" },
|
||||
'compound-engineering@m': [
|
||||
{ installPath: 'C:\\Users\\g\\.claude\\plugins\\cache\\ce\\3.9.2', version: '3.9.2' },
|
||||
],
|
||||
nested: { installLocation: "/Users/g/.claude/plugins/marketplaces/m" },
|
||||
nested: { installLocation: '/Users/g/.claude/plugins/marketplaces/m' },
|
||||
};
|
||||
const out = rw(input, "claude", CLAUDE);
|
||||
const out = rw(input, 'claude', CLAUDE);
|
||||
assert.equal(
|
||||
out["compound-engineering@m"][0].installPath,
|
||||
"/home/node/.claude/plugins/cache/ce/3.9.2",
|
||||
);
|
||||
assert.equal(out["compound-engineering@m"][0].version, "3.9.2");
|
||||
assert.equal(
|
||||
out.nested.installLocation,
|
||||
"/home/node/.claude/plugins/marketplaces/m",
|
||||
out['compound-engineering@m'][0].installPath,
|
||||
'/home/node/.claude/plugins/cache/ce/3.9.2',
|
||||
);
|
||||
assert.equal(out['compound-engineering@m'][0].version, '3.9.2');
|
||||
assert.equal(out.nested.installLocation, '/home/node/.claude/plugins/marketplaces/m');
|
||||
});
|
||||
|
||||
test("sanitizeClaudeConfig: strips machine fields, forces hasCompletedOnboarding", () => {
|
||||
test('sanitizeClaudeConfig: strips machine fields, forces hasCompletedOnboarding', () => {
|
||||
const out = sanitizeClaudeConfig({
|
||||
installMethod: "native",
|
||||
installMethod: 'native',
|
||||
autoUpdates: false,
|
||||
autoUpdatesProtectedForNative: true,
|
||||
shiftEnterKeyBindingInstalled: true,
|
||||
userID: "abc",
|
||||
oauthAccount: { emailAddress: "x@y.z" },
|
||||
userID: 'abc',
|
||||
oauthAccount: { emailAddress: 'x@y.z' },
|
||||
});
|
||||
assert.equal(out.installMethod, undefined);
|
||||
assert.equal(out.autoUpdates, undefined);
|
||||
assert.equal(out.autoUpdatesProtectedForNative, undefined);
|
||||
assert.equal(out.shiftEnterKeyBindingInstalled, undefined);
|
||||
assert.equal(out.userID, "abc");
|
||||
assert.equal(out.oauthAccount.emailAddress, "x@y.z");
|
||||
assert.equal(out.userID, 'abc');
|
||||
assert.equal(out.oauthAccount.emailAddress, 'x@y.z');
|
||||
assert.equal(out.hasCompletedOnboarding, true);
|
||||
});
|
||||
|
||||
test("sanitizeClaudeConfig: non-object inputs become a valid onboarding-bearing object", () => {
|
||||
for (const bad of [42, "x", null, ["a"], true]) {
|
||||
test('sanitizeClaudeConfig: non-object inputs become a valid onboarding-bearing object', () => {
|
||||
for (const bad of [42, 'x', null, ['a'], true]) {
|
||||
const out = sanitizeClaudeConfig(bad);
|
||||
assert.equal(typeof out, "object");
|
||||
assert.equal(typeof out, 'object');
|
||||
assert.equal(Array.isArray(out), false);
|
||||
assert.equal(out.hasCompletedOnboarding, true);
|
||||
}
|
||||
});
|
||||
|
||||
test("sanitizeClaudeConfig: empty object still gets hasCompletedOnboarding", () => {
|
||||
test('sanitizeClaudeConfig: empty object still gets hasCompletedOnboarding', () => {
|
||||
assert.deepEqual(sanitizeClaudeConfig({}), { hasCompletedOnboarding: true });
|
||||
});
|
||||
|
|
|
|||
8
.github/workflows/ci-devcontainer.yml
vendored
8
.github/workflows/ci-devcontainer.yml
vendored
|
|
@ -31,7 +31,11 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
# persist-credentials: false — read-only job (tests + syntax checks),
|
||||
# never pushes; keeps GITHUB_TOKEN out of .git/config (zizmor artipacked).
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 22
|
||||
|
|
@ -47,7 +51,11 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
# persist-credentials: false — read-only build smoke, never pushes;
|
||||
# keeps GITHUB_TOKEN out of .git/config (zizmor artipacked).
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 22
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue