hereya-cli 0.87.0 → 0.89.0
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/README.md +111 -163
- package/dist/backend/cloud/cloud-backend.d.ts +2 -1
- package/dist/backend/cloud/cloud-backend.js +36 -0
- package/dist/backend/common.d.ts +17 -0
- package/dist/backend/index.js +0 -24
- package/dist/commands/app/deploy/index.d.ts +0 -2
- package/dist/commands/app/deploy/index.js +0 -17
- package/dist/commands/app/deployments/index.d.ts +0 -4
- package/dist/commands/app/deployments/index.js +1 -20
- package/dist/commands/app/destroy/index.d.ts +0 -2
- package/dist/commands/app/destroy/index.js +0 -17
- package/dist/commands/app/env/index.d.ts +0 -2
- package/dist/commands/app/env/index.js +0 -17
- package/dist/commands/app/list/index.d.ts +0 -4
- package/dist/commands/app/list/index.js +1 -20
- package/dist/commands/app/status/index.d.ts +0 -2
- package/dist/commands/app/status/index.js +0 -17
- package/dist/commands/clone/index.js +2 -2
- package/dist/commands/deploy/index.d.ts +0 -2
- package/dist/commands/deploy/index.js +56 -69
- package/dist/commands/import-repo/index.d.ts +15 -0
- package/dist/commands/import-repo/index.js +111 -0
- package/dist/commands/init/index.d.ts +0 -2
- package/dist/commands/init/index.js +10 -70
- package/dist/commands/login/index.js +0 -5
- package/dist/commands/publish/index.d.ts +0 -2
- package/dist/commands/publish/index.js +0 -17
- package/dist/commands/run/index.d.ts +0 -2
- package/dist/commands/run/index.js +0 -17
- package/dist/commands/search/index.d.ts +0 -2
- package/dist/commands/search/index.js +21 -38
- package/dist/commands/undeploy/index.d.ts +0 -2
- package/dist/commands/undeploy/index.js +14 -27
- package/dist/commands/up/index.d.ts +0 -2
- package/dist/commands/up/index.js +1 -17
- package/dist/commands/workspace/executor/install/index.d.ts +0 -2
- package/dist/commands/workspace/executor/install/index.js +1 -17
- package/dist/commands/workspace/executor/token/index.d.ts +0 -2
- package/dist/commands/workspace/executor/token/index.js +0 -17
- package/dist/commands/workspace/executor/uninstall/index.d.ts +0 -2
- package/dist/commands/workspace/executor/uninstall/index.js +1 -17
- package/dist/executor/context.js +15 -12
- package/dist/lib/clone-and-configure.d.ts +43 -0
- package/dist/lib/clone-and-configure.js +77 -0
- package/dist/lib/hereya-token.js +11 -10
- package/dist/lib/package/index.js +24 -20
- package/oclif.manifest.json +145 -254
- package/package.json +1 -1
- package/dist/commands/git/index.d.ts +0 -12
- package/dist/commands/git/index.js +0 -116
- package/dist/lib/active-cloud.d.ts +0 -31
- package/dist/lib/active-cloud.js +0 -55
- package/dist/lib/ephemeral-token.d.ts +0 -45
- package/dist/lib/ephemeral-token.js +0 -89
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
2
|
import { CloudBackend } from '../../../../backend/cloud/cloud-backend.js';
|
|
3
3
|
import { getBackend } from '../../../../backend/index.js';
|
|
4
|
-
import { withEphemeralToken } from '../../../../lib/ephemeral-token.js';
|
|
5
|
-
import { getDefaultLogger } from '../../../../lib/log.js';
|
|
6
4
|
export default class WorkspaceExecutorToken extends Command {
|
|
7
5
|
static description = 'Generate a workspace-scoped executor token for the remote executor';
|
|
8
6
|
static flags = {
|
|
9
|
-
token: Flags.string({
|
|
10
|
-
description: 'Ephemeral cloud access token used for this invocation only. Held in memory; never written to the keychain or `~/.hereya/secrets/`. Takes precedence over the HEREYA_TOKEN environment variable.',
|
|
11
|
-
required: false,
|
|
12
|
-
}),
|
|
13
7
|
workspace: Flags.string({
|
|
14
8
|
char: 'w',
|
|
15
9
|
description: 'name of the workspace',
|
|
@@ -17,17 +11,6 @@ export default class WorkspaceExecutorToken extends Command {
|
|
|
17
11
|
}),
|
|
18
12
|
};
|
|
19
13
|
async run() {
|
|
20
|
-
const { flags } = await this.parse(WorkspaceExecutorToken);
|
|
21
|
-
// Precedence: --token flag > HEREYA_TOKEN env var > keychain.
|
|
22
|
-
if (flags.token) {
|
|
23
|
-
if (process.env.HEREYA_TOKEN) {
|
|
24
|
-
getDefaultLogger().debug('[workspace executor token] --token flag overrides HEREYA_TOKEN environment variable.');
|
|
25
|
-
}
|
|
26
|
-
return withEphemeralToken(flags.token, () => this.runInner());
|
|
27
|
-
}
|
|
28
|
-
return this.runInner();
|
|
29
|
-
}
|
|
30
|
-
async runInner() {
|
|
31
14
|
const { flags } = await this.parse(WorkspaceExecutorToken);
|
|
32
15
|
const backend = await getBackend();
|
|
33
16
|
if (!(backend instanceof CloudBackend)) {
|
|
@@ -3,9 +3,7 @@ export default class WorkspaceExecutorUninstall extends Command {
|
|
|
3
3
|
static description: string;
|
|
4
4
|
static flags: {
|
|
5
5
|
debug: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
6
|
-
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
6
|
workspace: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
7
|
};
|
|
9
8
|
run(): Promise<void>;
|
|
10
|
-
private runInner;
|
|
11
9
|
}
|
|
@@ -3,18 +3,13 @@ import { Listr, ListrLogger, ListrLogLevels } from 'listr2';
|
|
|
3
3
|
import { CloudBackend } from '../../../../backend/cloud/cloud-backend.js';
|
|
4
4
|
import { getBackend } from '../../../../backend/index.js';
|
|
5
5
|
import { getExecutor } from '../../../../executor/index.js';
|
|
6
|
-
import {
|
|
7
|
-
import { getDefaultLogger, getLogger, getLogPath, isDebug, setDebug } from '../../../../lib/log.js';
|
|
6
|
+
import { getLogger, getLogPath, isDebug, setDebug } from '../../../../lib/log.js';
|
|
8
7
|
import { delay } from '../../../../lib/shell.js';
|
|
9
8
|
const DEFAULT_EXECUTOR_PACKAGE = 'hereya/remote-executor-aws';
|
|
10
9
|
export default class WorkspaceExecutorUninstall extends Command {
|
|
11
10
|
static description = 'Uninstall the remote executor from a workspace';
|
|
12
11
|
static flags = {
|
|
13
12
|
debug: Flags.boolean({ default: false, description: 'enable debug mode' }),
|
|
14
|
-
token: Flags.string({
|
|
15
|
-
description: 'Ephemeral cloud access token used for this invocation only. Held in memory; never written to the keychain or `~/.hereya/secrets/`. Takes precedence over the HEREYA_TOKEN environment variable.',
|
|
16
|
-
required: false,
|
|
17
|
-
}),
|
|
18
13
|
workspace: Flags.string({
|
|
19
14
|
char: 'w',
|
|
20
15
|
description: 'name of the workspace',
|
|
@@ -22,17 +17,6 @@ export default class WorkspaceExecutorUninstall extends Command {
|
|
|
22
17
|
}),
|
|
23
18
|
};
|
|
24
19
|
async run() {
|
|
25
|
-
const { flags } = await this.parse(WorkspaceExecutorUninstall);
|
|
26
|
-
// Precedence: --token flag > HEREYA_TOKEN env var > keychain.
|
|
27
|
-
if (flags.token) {
|
|
28
|
-
if (process.env.HEREYA_TOKEN) {
|
|
29
|
-
getDefaultLogger().debug('[workspace executor uninstall] --token flag overrides HEREYA_TOKEN environment variable.');
|
|
30
|
-
}
|
|
31
|
-
return withEphemeralToken(flags.token, () => this.runInner());
|
|
32
|
-
}
|
|
33
|
-
return this.runInner();
|
|
34
|
-
}
|
|
35
|
-
async runInner() {
|
|
36
20
|
const { flags } = await this.parse(WorkspaceExecutorUninstall);
|
|
37
21
|
setDebug(flags.debug);
|
|
38
22
|
const myLogger = new ListrLogger({ useIcons: false });
|
package/dist/executor/context.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { getCloudCredentials, loadBackendConfig } from '../backend/config.js';
|
|
2
|
+
import { BackendType, getBackend } from '../backend/index.js';
|
|
3
3
|
import { resolveWorkspaceName } from '../lib/workspace-utils.js';
|
|
4
4
|
import { getExecutor } from './index.js';
|
|
5
5
|
export async function getExecutorForWorkspace(workspaceName, project) {
|
|
6
6
|
if (!workspaceName) {
|
|
7
7
|
return getExecutor();
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (!auth) {
|
|
9
|
+
const backendConfig = await loadBackendConfig();
|
|
10
|
+
if (backendConfig.current !== BackendType.Cloud) {
|
|
11
|
+
return getExecutor();
|
|
12
|
+
}
|
|
13
|
+
if (!backendConfig.cloud) {
|
|
15
14
|
return getExecutor();
|
|
16
15
|
}
|
|
17
16
|
const resolvedName = resolveWorkspaceName(workspaceName, project);
|
|
@@ -24,12 +23,16 @@ export async function getExecutorForWorkspace(workspaceName, project) {
|
|
|
24
23
|
if (!workspace.hasExecutor) {
|
|
25
24
|
return getExecutor();
|
|
26
25
|
}
|
|
26
|
+
const credentials = await getCloudCredentials(backendConfig.cloud.clientId);
|
|
27
|
+
if (!credentials) {
|
|
28
|
+
return getExecutor();
|
|
29
|
+
}
|
|
27
30
|
return getExecutor({
|
|
28
31
|
cloudConfig: {
|
|
29
|
-
accessToken:
|
|
30
|
-
clientId:
|
|
31
|
-
refreshToken:
|
|
32
|
-
url:
|
|
32
|
+
accessToken: credentials.accessToken,
|
|
33
|
+
clientId: backendConfig.cloud.clientId,
|
|
34
|
+
refreshToken: credentials.refreshToken,
|
|
35
|
+
url: backendConfig.cloud.url,
|
|
33
36
|
},
|
|
34
37
|
workspace: resolvedName,
|
|
35
38
|
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ListrTask } from 'listr2';
|
|
2
|
+
export interface CloneAndConfigureCtx {
|
|
3
|
+
resolvedGitEnv: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
export interface CloneAndConfigureProjectInput<Ctx extends CloneAndConfigureCtx = CloneAndConfigureCtx> {
|
|
6
|
+
/**
|
|
7
|
+
* Returns the env map that should be inspected for `hereyaGit*` markers.
|
|
8
|
+
* Called when the "Resolving git credentials" task runs, so callers can
|
|
9
|
+
* read up-to-date values from the Listr context (e.g. `ctx.prefixedEnv`)
|
|
10
|
+
* after earlier tasks have populated it.
|
|
11
|
+
*/
|
|
12
|
+
getEnv(ctx: Ctx): Promise<Record<string, string>> | Record<string, string>;
|
|
13
|
+
/**
|
|
14
|
+
* Project name to use for env resolution / config persistence / state save.
|
|
15
|
+
* Can be a value or a callback that reads it from ctx (useful when the
|
|
16
|
+
* canonical name comes from a previous task, e.g. `ctx.initOutput.project.name`).
|
|
17
|
+
*/
|
|
18
|
+
getProject(ctx: Ctx): Promise<string> | string;
|
|
19
|
+
/**
|
|
20
|
+
* Workspace name to use for env resolution / config persistence / state save.
|
|
21
|
+
*/
|
|
22
|
+
getWorkspace(ctx: Ctx): Promise<string> | string;
|
|
23
|
+
hereyaBinPath: string;
|
|
24
|
+
/**
|
|
25
|
+
* Optional override for the error thrown when `hereyaGitRemoteUrl` is missing
|
|
26
|
+
* from the resolved git env. Lets call sites surface a flow-specific message
|
|
27
|
+
* (e.g. "Template did not provide hereyaGitRemoteUrl").
|
|
28
|
+
*/
|
|
29
|
+
missingRemoteUrlError?: string;
|
|
30
|
+
targetDir: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns Listr tasks that:
|
|
34
|
+
* 1. Filter `hereyaGit*` env vars (from a caller-supplied env map) and resolve
|
|
35
|
+
* them via the workspace executor (this exercises the github-app marker path).
|
|
36
|
+
* 2. Clone the repository using the resolved git credentials and the hereya
|
|
37
|
+
* credential helper.
|
|
38
|
+
* 3. Persist the local hereya.yaml, set up the credential helper for the
|
|
39
|
+
* cloned repo, and save the project state on the cloud backend.
|
|
40
|
+
*
|
|
41
|
+
* Used by both `hereya init` (template flow) and `hereya import-repo --clone`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function cloneAndConfigureProjectTasks<Ctx extends CloneAndConfigureCtx = CloneAndConfigureCtx>(input: CloneAndConfigureProjectInput<Ctx>): ListrTask<Ctx>[];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { getBackend } from '../backend/index.js';
|
|
2
|
+
import { getExecutorForWorkspace } from '../executor/context.js';
|
|
3
|
+
import { getConfigManager } from './config/index.js';
|
|
4
|
+
import { gitUtils } from './git-utils.js';
|
|
5
|
+
/**
|
|
6
|
+
* Returns Listr tasks that:
|
|
7
|
+
* 1. Filter `hereyaGit*` env vars (from a caller-supplied env map) and resolve
|
|
8
|
+
* them via the workspace executor (this exercises the github-app marker path).
|
|
9
|
+
* 2. Clone the repository using the resolved git credentials and the hereya
|
|
10
|
+
* credential helper.
|
|
11
|
+
* 3. Persist the local hereya.yaml, set up the credential helper for the
|
|
12
|
+
* cloned repo, and save the project state on the cloud backend.
|
|
13
|
+
*
|
|
14
|
+
* Used by both `hereya init` (template flow) and `hereya import-repo --clone`.
|
|
15
|
+
*/
|
|
16
|
+
export function cloneAndConfigureProjectTasks(input) {
|
|
17
|
+
const { getEnv, getProject, getWorkspace, hereyaBinPath, missingRemoteUrlError, targetDir } = input;
|
|
18
|
+
return [
|
|
19
|
+
{
|
|
20
|
+
async task(ctx) {
|
|
21
|
+
const env = await getEnv(ctx);
|
|
22
|
+
const project = await getProject(ctx);
|
|
23
|
+
const workspace = await getWorkspace(ctx);
|
|
24
|
+
const gitEnvToResolve = {};
|
|
25
|
+
for (const [key, value] of Object.entries(env || {})) {
|
|
26
|
+
if (key.startsWith('hereyaGit'))
|
|
27
|
+
gitEnvToResolve[key] = value;
|
|
28
|
+
}
|
|
29
|
+
const executor$ = await getExecutorForWorkspace(workspace);
|
|
30
|
+
if (!executor$.success)
|
|
31
|
+
throw new Error(executor$.reason);
|
|
32
|
+
ctx.resolvedGitEnv = await executor$.executor.resolveEnvValues({
|
|
33
|
+
env: gitEnvToResolve,
|
|
34
|
+
project,
|
|
35
|
+
workspace,
|
|
36
|
+
});
|
|
37
|
+
if (!ctx.resolvedGitEnv.hereyaGitRemoteUrl) {
|
|
38
|
+
throw new Error(missingRemoteUrlError
|
|
39
|
+
?? 'Project metadata does not contain hereyaGitRemoteUrl.');
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
title: 'Resolving git credentials',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
async task(ctx) {
|
|
46
|
+
const cloneResult = await gitUtils.cloneWithCredentialHelper({
|
|
47
|
+
gitUrl: ctx.resolvedGitEnv.hereyaGitRemoteUrl,
|
|
48
|
+
hereyaBinPath,
|
|
49
|
+
password: ctx.resolvedGitEnv.hereyaGitPassword,
|
|
50
|
+
targetDir,
|
|
51
|
+
username: ctx.resolvedGitEnv.hereyaGitUsername,
|
|
52
|
+
});
|
|
53
|
+
if (!cloneResult.success)
|
|
54
|
+
throw new Error(cloneResult.reason);
|
|
55
|
+
},
|
|
56
|
+
title: 'Cloning project repository',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
async task(ctx) {
|
|
60
|
+
const project = await getProject(ctx);
|
|
61
|
+
const workspace = await getWorkspace(ctx);
|
|
62
|
+
const configManager = getConfigManager();
|
|
63
|
+
const existing = await configManager.loadConfig({ projectRootDir: targetDir });
|
|
64
|
+
const config = {
|
|
65
|
+
...(existing.found ? existing.config : {}),
|
|
66
|
+
project,
|
|
67
|
+
workspace,
|
|
68
|
+
};
|
|
69
|
+
await configManager.saveConfig({ config, projectRootDir: targetDir });
|
|
70
|
+
await gitUtils.setupCredentialHelper({ hereyaBinPath, projectDir: targetDir });
|
|
71
|
+
const backend = await getBackend();
|
|
72
|
+
await backend.saveState(config, workspace);
|
|
73
|
+
},
|
|
74
|
+
title: 'Setting up project',
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
}
|
package/dist/lib/hereya-token.js
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getCloudCredentials, loadBackendConfig } from '../backend/config.js';
|
|
2
2
|
export const hereyaTokenUtils = {
|
|
3
3
|
async generateHereyaToken(description) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
4
|
+
const backendConfig = await loadBackendConfig();
|
|
5
|
+
if (!backendConfig.cloud) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const { clientId, url } = backendConfig.cloud;
|
|
9
|
+
const credentials = await getCloudCredentials(clientId);
|
|
10
|
+
if (!credentials) {
|
|
10
11
|
return null;
|
|
11
12
|
}
|
|
12
13
|
const formData = new FormData();
|
|
13
14
|
formData.append('description', description);
|
|
14
15
|
formData.append('expiresInDays', '365');
|
|
15
|
-
const response = await fetch(`${
|
|
16
|
+
const response = await fetch(`${url}/api/personal-tokens`, {
|
|
16
17
|
body: formData,
|
|
17
18
|
headers: {
|
|
18
|
-
Authorization: `Bearer ${
|
|
19
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
19
20
|
},
|
|
20
21
|
method: 'POST',
|
|
21
22
|
});
|
|
@@ -23,6 +24,6 @@ export const hereyaTokenUtils = {
|
|
|
23
24
|
return null;
|
|
24
25
|
}
|
|
25
26
|
const result = await response.json();
|
|
26
|
-
return { cloudUrl:
|
|
27
|
+
return { cloudUrl: url, token: result.data.token };
|
|
27
28
|
},
|
|
28
29
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as yaml from 'yaml';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import {
|
|
3
|
+
import { getCurrentBackendType } from '../../backend/config.js';
|
|
4
|
+
import { BackendType, getBackend } from '../../backend/index.js';
|
|
4
5
|
import { IacType } from '../../iac/common.js';
|
|
5
6
|
import { InfrastructureType } from '../../infrastructure/common.js';
|
|
6
|
-
import { isCloudBackendActive } from '../active-cloud.js';
|
|
7
7
|
import { CloudPackageManager } from './cloud.js';
|
|
8
8
|
import { GitHubPackageManager } from './github.js';
|
|
9
9
|
import { LocalPackageManager } from './local.js';
|
|
@@ -31,21 +31,23 @@ export async function resolvePackage(input) {
|
|
|
31
31
|
return { found: false, reason: 'Invalid package format. Package name cannot contain dots (.) nor double dashes (--)' };
|
|
32
32
|
}
|
|
33
33
|
const isLocal = packageName.startsWith('local/');
|
|
34
|
-
// Try cloud first if not local and backend is cloud
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
// Try cloud first if not local and backend is cloud
|
|
35
|
+
if (!isLocal) {
|
|
36
|
+
const backendType = await getCurrentBackendType();
|
|
37
|
+
if (backendType === BackendType.Cloud) {
|
|
38
|
+
const result = await tryCloudResolution(input);
|
|
39
|
+
if (result.found) {
|
|
40
|
+
return result; // Success
|
|
41
|
+
}
|
|
42
|
+
// Failed - check if we should fallback to GitHub or return the error
|
|
43
|
+
if (!packageName.includes('/')) {
|
|
44
|
+
// Simple package names can't fallback to GitHub, return the detailed error
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
// For owner/repo format, we can fallback to GitHub
|
|
48
|
+
input.logger?.debug(`Package ${packageName} not found in registry, trying GitHub...`);
|
|
49
|
+
input.logger?.debug(`Registry error: ${result.reason}`);
|
|
40
50
|
}
|
|
41
|
-
// Failed - check if we should fallback to GitHub or return the error
|
|
42
|
-
if (!packageName.includes('/')) {
|
|
43
|
-
// Simple package names can't fallback to GitHub, return the detailed error
|
|
44
|
-
return result;
|
|
45
|
-
}
|
|
46
|
-
// For owner/repo format, we can fallback to GitHub
|
|
47
|
-
input.logger?.debug(`Package ${packageName} not found in registry, trying GitHub...`);
|
|
48
|
-
input.logger?.debug(`Registry error: ${result.reason}`);
|
|
49
51
|
}
|
|
50
52
|
// Determine protocol for standard resolution
|
|
51
53
|
const protocol = isLocal ? 'local' : '';
|
|
@@ -61,10 +63,12 @@ export async function downloadPackage(pkgUrl, destPath) {
|
|
|
61
63
|
if (pkgUrl.startsWith('local/')) {
|
|
62
64
|
protocol = 'local';
|
|
63
65
|
}
|
|
64
|
-
else if (pkgUrl.includes('#')
|
|
65
|
-
// Registry package URLs contain commit SHA
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
else if (pkgUrl.includes('#')) {
|
|
67
|
+
// Registry package URLs contain commit SHA
|
|
68
|
+
const backendType = await getCurrentBackendType();
|
|
69
|
+
if (backendType === BackendType.Cloud) {
|
|
70
|
+
protocol = 'cloud';
|
|
71
|
+
}
|
|
68
72
|
}
|
|
69
73
|
// Otherwise defaults to GitHub
|
|
70
74
|
const packageManager = await getPackageManager(protocol);
|