@united-robotics/cli 0.4.7 → 0.4.8
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/dist/index.js +67 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,6 +11,8 @@ var configDirName = ".united-robotics";
|
|
|
11
11
|
var globalConfigDir = join(homedir(), configDirName);
|
|
12
12
|
var globalConfigPath = join(globalConfigDir, configFileName);
|
|
13
13
|
var defaultApiUrl = process.env.UR_API_URL ?? "https://united-robotics.rollersoft.com.au";
|
|
14
|
+
var defaultGitName = "exis[ai]";
|
|
15
|
+
var defaultGitEmail = "gotexis@gmail.com";
|
|
14
16
|
function load() {
|
|
15
17
|
const localPath = findLocalConfigPath(process.cwd());
|
|
16
18
|
for (const path of [process.env.UR_CONFIG, localPath, globalConfigPath].filter(Boolean)) {
|
|
@@ -58,7 +60,7 @@ program.command("team").argument("<cmd>").action(async (cmd) => {
|
|
|
58
60
|
var project = program.command("project");
|
|
59
61
|
project.command("ls").option("--team <teamId>").action(async (opts) => console.log(JSON.stringify(await api(`/api/projects${opts.team ? `?team=${encodeURIComponent(opts.team)}` : ""}`), null, 2)));
|
|
60
62
|
project.command("show").argument("<projectId>").action(async (projectId) => console.log(JSON.stringify({ projectId, repos: await projectRepos(projectId) }, null, 2)));
|
|
61
|
-
project.command("clone").argument("<projectId>").option("--dest <path>").option("--in-place", "clone into the current directory so AGENTS.md and workspace files land at the project root").option("--no-submodules", "clone primary repo only").option("--name <name>", "Git user.name for Vercel-compatible commits",
|
|
63
|
+
project.command("clone").argument("<projectId>").option("--dest <path>").option("--in-place", "clone into the current directory so AGENTS.md and workspace files land at the project root").option("--no-submodules", "clone primary repo only").option("--name <name>", "Git user.name for Vercel-compatible commits", defaultGitName).option("--email <email>", "Git user.email for Vercel-compatible commits", defaultGitEmail).action(async (projectId, opts) => {
|
|
62
64
|
const repos = await projectRepos(projectId);
|
|
63
65
|
const primary = repos.find((r) => r.kind === "primary") ?? repos[0];
|
|
64
66
|
if (!primary) throw new Error("No repo found");
|
|
@@ -90,7 +92,7 @@ project.command("clone").argument("<projectId>").option("--dest <path>").option(
|
|
|
90
92
|
credentials.cleanup();
|
|
91
93
|
}
|
|
92
94
|
});
|
|
93
|
-
project.command("init").argument("<projectId>").option("--cwd <path>", "workspace path", process.cwd()).option("--name <name>", "Git user.name for Vercel-compatible commits",
|
|
95
|
+
project.command("init").argument("<projectId>").option("--cwd <path>", "workspace path", process.cwd()).option("--name <name>", "Git user.name for Vercel-compatible commits", defaultGitName).option("--email <email>", "Git user.email for Vercel-compatible commits", defaultGitEmail).action(async (projectId, opts) => {
|
|
94
96
|
const token = await projectGithubToken(projectId);
|
|
95
97
|
const credentials = createGitAskpass(token.token);
|
|
96
98
|
try {
|
|
@@ -107,7 +109,7 @@ program.command("repo").argument("<cmd>").argument("<projectId>").action(async (
|
|
|
107
109
|
console.log(JSON.stringify(await projectRepos(projectId), null, 2));
|
|
108
110
|
});
|
|
109
111
|
var git = program.command("git").description("Git helpers for tenant workspaces");
|
|
110
|
-
git.command("identity").option("--name <name>", "Git user.name",
|
|
112
|
+
git.command("identity").option("--name <name>", "Git user.name", defaultGitName).option("--email <email>", "Git user.email", defaultGitEmail).option("--global", "write global git config instead of current repo").option("--recursive", "also apply to initialized submodules").action((opts) => {
|
|
111
113
|
if (opts.global) {
|
|
112
114
|
runGit(["config", "--global", "user.name", opts.name], { cwd: process.cwd() });
|
|
113
115
|
runGit(["config", "--global", "user.email", opts.email], { cwd: process.cwd() });
|
|
@@ -194,6 +196,67 @@ function initProjectWorktree(worktree, projectId, name, email, env = process.env
|
|
|
194
196
|
function configureGitIdentity(cwd, name, email, env = process.env) {
|
|
195
197
|
runGit(["config", "--local", "user.name", name], { cwd, env });
|
|
196
198
|
runGit(["config", "--local", "user.email", email], { cwd, env });
|
|
199
|
+
installIdentityHooks(cwd, name, email);
|
|
200
|
+
}
|
|
201
|
+
function installIdentityHooks(cwd, name, email) {
|
|
202
|
+
const gitDir = resolveGitDir(cwd);
|
|
203
|
+
if (!gitDir) return;
|
|
204
|
+
const hooksDir = join(gitDir, "hooks");
|
|
205
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
206
|
+
const expected = `${name} <${email}>`;
|
|
207
|
+
const preCommit = `#!/bin/sh
|
|
208
|
+
set -eu
|
|
209
|
+
expected_name='${shellSingleQuoteSafe(name)}'
|
|
210
|
+
expected_email='${shellSingleQuoteSafe(email)}'
|
|
211
|
+
actual_name="\${GIT_AUTHOR_NAME:-$(git config --get user.name || true)}"
|
|
212
|
+
actual_email="\${GIT_AUTHOR_EMAIL:-$(git config --get user.email || true)}"
|
|
213
|
+
committer_name="\${GIT_COMMITTER_NAME:-$(git config --get user.name || true)}"
|
|
214
|
+
committer_email="\${GIT_COMMITTER_EMAIL:-$(git config --get user.email || true)}"
|
|
215
|
+
if [ "$actual_name" != "$expected_name" ] || [ "$actual_email" != "$expected_email" ] || [ "$committer_name" != "$expected_name" ] || [ "$committer_email" != "$expected_email" ]; then
|
|
216
|
+
echo "United Robotics identity guard: commits must use ${expected}." >&2
|
|
217
|
+
echo "Run: ur git identity --recursive" >&2
|
|
218
|
+
exit 1
|
|
219
|
+
fi
|
|
220
|
+
`;
|
|
221
|
+
const prePush = `#!/bin/sh
|
|
222
|
+
set -eu
|
|
223
|
+
expected='${shellSingleQuoteSafe(expected)}'
|
|
224
|
+
while read local_ref local_sha remote_ref remote_sha; do
|
|
225
|
+
case "$local_sha" in
|
|
226
|
+
0000000000000000000000000000000000000000) continue ;;
|
|
227
|
+
esac
|
|
228
|
+
if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then
|
|
229
|
+
range="$local_sha"
|
|
230
|
+
else
|
|
231
|
+
range="$remote_sha..$local_sha"
|
|
232
|
+
fi
|
|
233
|
+
bad=$(git log --format='%H%x09%an <%ae>%x09%cn <%ce>' "$range" -- 2>/dev/null | awk -F ' ' -v e="$expected" '$2 != e || $3 != e { print; exit }')
|
|
234
|
+
if [ -n "$bad" ]; then
|
|
235
|
+
echo "United Robotics identity guard: refusing push with non-standard author/committer." >&2
|
|
236
|
+
echo "$bad" >&2
|
|
237
|
+
echo "Expected author and committer: $expected" >&2
|
|
238
|
+
echo "Fix by recommitting/amending after: ur git identity --recursive" >&2
|
|
239
|
+
exit 1
|
|
240
|
+
fi
|
|
241
|
+
done
|
|
242
|
+
`;
|
|
243
|
+
writeExecutableHook(join(hooksDir, "pre-commit"), preCommit);
|
|
244
|
+
writeExecutableHook(join(hooksDir, "pre-push"), prePush);
|
|
245
|
+
}
|
|
246
|
+
function resolveGitDir(cwd) {
|
|
247
|
+
const result = runGit(["rev-parse", "--git-dir"], { cwd, allowFailure: true });
|
|
248
|
+
if (result.status !== 0) return null;
|
|
249
|
+
const value = result.stdout.trim();
|
|
250
|
+
if (!value) return null;
|
|
251
|
+
return value.startsWith("/") ? value : join(cwd, value);
|
|
252
|
+
}
|
|
253
|
+
function writeExecutableHook(path, content) {
|
|
254
|
+
writeFileSync(path, content, { mode: 493 });
|
|
255
|
+
chmodSync(path, 493);
|
|
256
|
+
}
|
|
257
|
+
function listInitializedSubmodules(worktree, env = process.env) {
|
|
258
|
+
const result = runGit(["submodule", "status", "--recursive"], { cwd: worktree, env, allowFailure: true });
|
|
259
|
+
return result.stdout.split(/\r?\n/).map((line) => line.trim().split(/\s+/)[1]).filter((value) => Boolean(value));
|
|
197
260
|
}
|
|
198
261
|
function configureGitCredentialHelper(cwd, projectId, env = process.env) {
|
|
199
262
|
const helper = `!ur git credential-helper --project ${shellSingleQuoteSafe(projectId)}`;
|
|
@@ -206,6 +269,7 @@ function configureGitCredentialHelper(cwd, projectId, env = process.env) {
|
|
|
206
269
|
function configureSubmoduleIdentities(worktree, name, email, env = process.env) {
|
|
207
270
|
const result = runGit(["submodule", "foreach", "--recursive", `git config --local user.name '${shellSingleQuoteSafe(name)}' && git config --local user.email '${shellSingleQuoteSafe(email)}'`], { cwd: worktree, env, allowFailure: true });
|
|
208
271
|
if (result.status !== 0) console.warn("Warning: could not apply git identity to all submodules. Run `ur git identity --recursive` after submodules are initialized.");
|
|
272
|
+
for (const submodule of listInitializedSubmodules(worktree, env)) installIdentityHooks(join(worktree, submodule), name, email);
|
|
209
273
|
}
|
|
210
274
|
function configureSubmoduleCredentialHelpers(worktree, projectId, env = process.env) {
|
|
211
275
|
const helper = `!ur git credential-helper --project ${shellSingleQuoteSafe(projectId)}`;
|