hereya-cli 0.100.1 → 0.100.3

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.
@@ -90,7 +90,15 @@ export declare function resolveGithubAppMarkers(resolved: Record<string, string>
90
90
  /**
91
91
  * Walk a resolved env map, find the first value that looks like an HTTPS
92
92
  * GitHub URL (with or without `.git` suffix and trailing slash), and return
93
- * `['<owner>/<repo>']`. Returns `undefined` if no GitHub URL is found.
93
+ * `['<repo>']`. Returns `undefined` if no GitHub URL is found.
94
+ *
95
+ * The bare repo name (no owner prefix) is what GitHub's installation-token
96
+ * endpoint (`POST /app/installations/{id}/access_tokens`) expects in its
97
+ * `repositories` body field: it scopes by repo name within the installation
98
+ * account, since an installation is already tied to a single account. Octokit
99
+ * forwards `repositoryNames` verbatim to that field, so passing `owner/repo`
100
+ * causes the mint to fail with "There is at least one repository that does
101
+ * not exist or is not accessible to the parent installation".
94
102
  *
95
103
  * Used by `resolveGithubAppMarkers` to scope minted installation tokens to a
96
104
  * single repository instead of granting installation-wide access.
@@ -83,7 +83,15 @@ export async function resolveGithubAppMarkers(resolved, input, providers) {
83
83
  /**
84
84
  * Walk a resolved env map, find the first value that looks like an HTTPS
85
85
  * GitHub URL (with or without `.git` suffix and trailing slash), and return
86
- * `['<owner>/<repo>']`. Returns `undefined` if no GitHub URL is found.
86
+ * `['<repo>']`. Returns `undefined` if no GitHub URL is found.
87
+ *
88
+ * The bare repo name (no owner prefix) is what GitHub's installation-token
89
+ * endpoint (`POST /app/installations/{id}/access_tokens`) expects in its
90
+ * `repositories` body field: it scopes by repo name within the installation
91
+ * account, since an installation is already tied to a single account. Octokit
92
+ * forwards `repositoryNames` verbatim to that field, so passing `owner/repo`
93
+ * causes the mint to fail with "There is at least one repository that does
94
+ * not exist or is not accessible to the parent installation".
87
95
  *
88
96
  * Used by `resolveGithubAppMarkers` to scope minted installation tokens to a
89
97
  * single repository instead of granting installation-wide access.
@@ -95,7 +103,7 @@ export function extractGithubRepoFromPeers(resolved) {
95
103
  continue;
96
104
  const match = githubUrlRegex.exec(value);
97
105
  if (match) {
98
- return [`${match[1]}/${match[2]}`];
106
+ return [match[2]];
99
107
  }
100
108
  }
101
109
  return undefined;
@@ -1,5 +1,5 @@
1
- import { spawn } from 'node:child_process';
2
- import { runShell } from './shell.js';
1
+ import spawn from 'cross-spawn';
2
+ import { buildGitCredentialHelperValue, runShell } from './shell.js';
3
3
  export const gitUtils = {
4
4
  /**
5
5
  * Clones a git repository using the hereya credential helper.
@@ -104,7 +104,7 @@ export async function cloneWithCredentialHelper(input) {
104
104
  const { gitUrl, hereyaBinPath, password, targetDir, username } = input;
105
105
  // Build the credential helper config
106
106
  // The ! prefix tells git to treat it as a shell command
107
- const credHelper = `!${hereyaBinPath} credential-helper`;
107
+ const credHelper = buildGitCredentialHelperValue(hereyaBinPath);
108
108
  return new Promise((resolve) => {
109
109
  const env = { ...process.env };
110
110
  if (username)
@@ -140,7 +140,7 @@ export async function cloneWithCredentialHelper(input) {
140
140
  }
141
141
  export async function setupCredentialHelper(input) {
142
142
  const { hereyaBinPath, projectDir } = input;
143
- const credHelper = `!${hereyaBinPath} credential-helper`;
143
+ const credHelper = buildGitCredentialHelperValue(hereyaBinPath);
144
144
  const runGitConfig = (args) => new Promise((resolve, reject) => {
145
145
  const child = spawn('git', args, { cwd: projectDir, stdio: 'pipe' });
146
146
  child.on('close', (code) => {
@@ -39,3 +39,23 @@ export declare const shellUtils: {
39
39
  runShell: typeof runShell;
40
40
  };
41
41
  export declare function delay(ms: number): Promise<unknown> | undefined;
42
+ /**
43
+ * Builds a shell-safe command string that invokes the hereya CLI. Suitable
44
+ * for embedding in git credential-helper config strings, SSH ProxyCommand
45
+ * lines, and anywhere else an external tool re-parses our command through
46
+ * a POSIX shell or cmd.exe.
47
+ *
48
+ * - If `binPath` ends in `.js`, prepend the absolute path of the current
49
+ * Node executable (`process.execPath`) so the helper works without
50
+ * depending on `.js` being executable.
51
+ * - Path-like tokens are normalized to forward slashes and wrapped in
52
+ * double quotes to survive spaces and Windows backslashes.
53
+ * - Args are emitted bare unless they contain whitespace, quotes, or
54
+ * backslashes.
55
+ */
56
+ export declare function buildHereyaShellCommand(binPath: string, args: string[]): string;
57
+ /**
58
+ * Builds the value for git's `credential.helper` config when the helper
59
+ * is a shell command. Git requires a leading `!` for shell-command helpers.
60
+ */
61
+ export declare function buildGitCredentialHelperValue(binPath: string, subcommand?: string): string;
package/dist/lib/shell.js CHANGED
@@ -106,3 +106,58 @@ export function delay(ms) {
106
106
  setTimeout(resolve, ms);
107
107
  });
108
108
  }
109
+ /**
110
+ * Quote a path-like token so it survives re-parsing by a POSIX shell or
111
+ * cmd.exe. Backslashes are normalized to forward slashes (Windows OpenSSH
112
+ * and Git for Windows' msys bash both accept forward-slash absolute paths
113
+ * like `C:/Users/…`). Embedded double quotes are escaped as `\"`.
114
+ */
115
+ function quotePath(p) {
116
+ const normalized = p.replaceAll('\\', '/');
117
+ const escaped = normalized.replaceAll('"', String.raw `\"`);
118
+ return `"${escaped}"`;
119
+ }
120
+ /**
121
+ * Quote a plain CLI argument. Bare args stay bare unless they contain a
122
+ * space, a double quote, or a backslash — keeping the macOS/Linux output
123
+ * close to today's (e.g. `devenv ssh-proxy -w my-ws` stays bare).
124
+ */
125
+ function quoteArg(arg) {
126
+ if (arg === '' || /[\s"\\]/.test(arg)) {
127
+ const escaped = arg.replaceAll('\\', String.raw `\\`).replaceAll('"', String.raw `\"`);
128
+ return `"${escaped}"`;
129
+ }
130
+ return arg;
131
+ }
132
+ /**
133
+ * Builds a shell-safe command string that invokes the hereya CLI. Suitable
134
+ * for embedding in git credential-helper config strings, SSH ProxyCommand
135
+ * lines, and anywhere else an external tool re-parses our command through
136
+ * a POSIX shell or cmd.exe.
137
+ *
138
+ * - If `binPath` ends in `.js`, prepend the absolute path of the current
139
+ * Node executable (`process.execPath`) so the helper works without
140
+ * depending on `.js` being executable.
141
+ * - Path-like tokens are normalized to forward slashes and wrapped in
142
+ * double quotes to survive spaces and Windows backslashes.
143
+ * - Args are emitted bare unless they contain whitespace, quotes, or
144
+ * backslashes.
145
+ */
146
+ export function buildHereyaShellCommand(binPath, args) {
147
+ const tokens = [];
148
+ if (binPath.toLowerCase().endsWith('.js')) {
149
+ tokens.push(quotePath(process.execPath));
150
+ }
151
+ tokens.push(quotePath(binPath));
152
+ for (const arg of args) {
153
+ tokens.push(quoteArg(arg));
154
+ }
155
+ return tokens.join(' ');
156
+ }
157
+ /**
158
+ * Builds the value for git's `credential.helper` config when the helper
159
+ * is a shell command. Git requires a leading `!` for shell-command helpers.
160
+ */
161
+ export function buildGitCredentialHelperValue(binPath, subcommand = 'credential-helper') {
162
+ return '!' + buildHereyaShellCommand(binPath, [subcommand]);
163
+ }