heroku 11.0.0-alpha.1 → 11.0.0-alpha.11
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 +3 -0
- package/lib/commands/local/index.d.ts +19 -0
- package/lib/commands/local/index.js +77 -0
- package/lib/commands/local/run.d.ts +11 -0
- package/lib/commands/local/run.js +37 -0
- package/lib/commands/local/version.d.ts +5 -0
- package/lib/commands/local/version.js +10 -0
- package/lib/commands/members/add.d.ts +13 -0
- package/lib/commands/members/add.js +32 -0
- package/lib/commands/members/index.d.ts +12 -0
- package/lib/commands/members/index.js +80 -0
- package/lib/commands/members/remove.d.ts +10 -0
- package/lib/commands/members/remove.js +43 -0
- package/lib/commands/members/set.d.ts +11 -0
- package/lib/commands/members/set.js +24 -0
- package/lib/commands/pipelines/add.d.ts +14 -0
- package/lib/commands/pipelines/add.js +58 -0
- package/lib/commands/pipelines/connect.d.ts +12 -0
- package/lib/commands/pipelines/connect.js +49 -0
- package/lib/commands/pipelines/create.d.ts +15 -0
- package/lib/commands/pipelines/create.js +89 -0
- package/lib/commands/pipelines/destroy.d.ts +9 -0
- package/lib/commands/pipelines/destroy.js +24 -0
- package/lib/commands/pipelines/diff.d.ts +20 -0
- package/lib/commands/pipelines/diff.js +156 -0
- package/lib/commands/pipelines/index.d.ts +9 -0
- package/lib/commands/pipelines/index.js +25 -0
- package/lib/commands/pipelines/info.d.ts +13 -0
- package/lib/commands/pipelines/info.js +41 -0
- package/lib/lib/local/env-file-validator.d.ts +1 -0
- package/lib/lib/local/env-file-validator.js +10 -0
- package/lib/lib/local/fork-foreman.js +7 -0
- package/lib/lib/local/load-foreman-procfile.d.ts +1 -2
- package/lib/lib/local/load-foreman-procfile.js +5 -1
- package/lib/lib/local/{run-foreman.js → run-foreman.cjs} +2 -1
- package/lib/lib/members/team-invite-utils.d.ts +14 -0
- package/lib/lib/members/team-invite-utils.js +26 -0
- package/lib/lib/pipelines/disambiguate.js +2 -2
- package/lib/lib/pipelines/ownership.d.ts +5 -1
- package/lib/lib/pipelines/ownership.js +33 -44
- package/lib/lib/pipelines/render-pipeline.d.ts +7 -1
- package/lib/lib/pipelines/render-pipeline.js +62 -68
- package/npm-shrinkwrap.json +31675 -0
- package/oclif.manifest.json +7573 -0
- package/package.json +10 -8
- package/lib/lib/members/utils.d.ts +0 -2
- package/lib/lib/members/utils.js +0 -10
- package/lib/oldCommands/local/index.d.ts +0 -1
- package/lib/oldCommands/local/index.js +0 -91
- package/lib/oldCommands/local/run.d.ts +0 -1
- package/lib/oldCommands/local/run.js +0 -54
- package/lib/oldCommands/local/version.d.ts +0 -1
- package/lib/oldCommands/local/version.js +0 -17
- package/lib/oldCommands/members/add.d.ts +0 -1
- package/lib/oldCommands/members/add.js +0 -36
- package/lib/oldCommands/members/index.d.ts +0 -1
- package/lib/oldCommands/members/index.js +0 -92
- package/lib/oldCommands/members/remove.d.ts +0 -1
- package/lib/oldCommands/members/remove.js +0 -70
- package/lib/oldCommands/members/set.d.ts +0 -1
- package/lib/oldCommands/members/set.js +0 -24
- package/lib/oldCommands/pipelines/add.d.ts +0 -1
- package/lib/oldCommands/pipelines/add.js +0 -70
- package/lib/oldCommands/pipelines/connect.d.ts +0 -1
- package/lib/oldCommands/pipelines/connect.js +0 -69
- package/lib/oldCommands/pipelines/create.d.ts +0 -1
- package/lib/oldCommands/pipelines/create.js +0 -105
- package/lib/oldCommands/pipelines/destroy.d.ts +0 -1
- package/lib/oldCommands/pipelines/destroy.js +0 -34
- package/lib/oldCommands/pipelines/diff.d.ts +0 -1
- package/lib/oldCommands/pipelines/diff.js +0 -202
- package/lib/oldCommands/pipelines/index.d.ts +0 -1
- package/lib/oldCommands/pipelines/index.js +0 -34
- package/lib/oldCommands/pipelines/info.d.ts +0 -1
- package/lib/oldCommands/pipelines/info.js +0 -51
- /package/lib/lib/local/{run-foreman.d.ts → run-foreman.d.cts} +0 -0
package/README.md
CHANGED
|
@@ -53,10 +53,13 @@ For other issues, [submit a support ticket](https://help.heroku.com/).
|
|
|
53
53
|
* [`heroku help`](docs/help.md) - Display help for heroku.
|
|
54
54
|
* [`heroku keys`](docs/keys.md) - add/remove account ssh keys
|
|
55
55
|
* [`heroku labs`](docs/labs.md) - add/remove experimental features
|
|
56
|
+
* [`heroku local`](docs/local.md) - run Heroku app locally
|
|
56
57
|
* [`heroku logs`](docs/logs.md) - display recent log output
|
|
57
58
|
* [`heroku maintenance`](docs/maintenance.md) - enable/disable access to app
|
|
59
|
+
* [`heroku members`](docs/members.md) - manage organization members
|
|
58
60
|
* [`heroku notifications`](docs/notifications.md) - display notifications
|
|
59
61
|
* [`heroku orgs`](docs/orgs.md) - manage organizations
|
|
62
|
+
* [`heroku pipelines`](docs/pipelines.md) - manage pipelines
|
|
60
63
|
* [`heroku plugins`](docs/plugins.md) - List installed plugins.
|
|
61
64
|
* [`heroku ps`](docs/ps.md) - Client tools for Heroku Exec
|
|
62
65
|
* [`heroku regions`](docs/regions.md) - list available regions for deployment
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Index extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static aliases: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
processname: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static examples: string[];
|
|
9
|
+
static flags: {
|
|
10
|
+
procfile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
port: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
restart: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
concurrency: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<void>;
|
|
17
|
+
runForeman(execArgv: string[]): Promise<void>;
|
|
18
|
+
loadProcfile(procfilePath: string): Record<string, string>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { fork as foreman } from '../../lib/local/fork-foreman.js';
|
|
3
|
+
import { loadProc } from '../../lib/local/load-foreman-procfile.js';
|
|
4
|
+
import { validateEnvFile } from '../../lib/local/env-file-validator.js';
|
|
5
|
+
export default class Index extends Command {
|
|
6
|
+
// \n splits the description between the title shown in the help
|
|
7
|
+
// and the DESCRIPTION section shown in the help
|
|
8
|
+
static description = 'run heroku app locally\nStart the application specified by a Procfile (defaults to ./Procfile)';
|
|
9
|
+
static aliases = ['local:start'];
|
|
10
|
+
static args = {
|
|
11
|
+
processname: Args.string({ required: false, description: 'name of the process' }),
|
|
12
|
+
};
|
|
13
|
+
static examples = [
|
|
14
|
+
`$ heroku local
|
|
15
|
+
$ heroku local web
|
|
16
|
+
$ heroku local web=2
|
|
17
|
+
$ heroku local web=1,worker=2`,
|
|
18
|
+
];
|
|
19
|
+
static flags = {
|
|
20
|
+
procfile: Flags.string({
|
|
21
|
+
char: 'f',
|
|
22
|
+
description: 'use a different Procfile',
|
|
23
|
+
}),
|
|
24
|
+
env: Flags.string({
|
|
25
|
+
char: 'e',
|
|
26
|
+
description: 'location of env file (defaults to .env)',
|
|
27
|
+
}),
|
|
28
|
+
port: Flags.string({
|
|
29
|
+
char: 'p',
|
|
30
|
+
description: 'port to listen on',
|
|
31
|
+
}),
|
|
32
|
+
restart: Flags.boolean({
|
|
33
|
+
char: 'r',
|
|
34
|
+
description: 'restart process if it dies',
|
|
35
|
+
hidden: true,
|
|
36
|
+
}),
|
|
37
|
+
concurrency: Flags.string({
|
|
38
|
+
char: 'c',
|
|
39
|
+
description: 'number of processes to start',
|
|
40
|
+
hidden: true,
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
async run() {
|
|
44
|
+
const execArgv = ['start'];
|
|
45
|
+
const { args, flags } = await this.parse(Index);
|
|
46
|
+
if (flags.restart) {
|
|
47
|
+
this.error('--restart is no longer available\nUse forego instead: https://github.com/ddollar/forego');
|
|
48
|
+
}
|
|
49
|
+
if (flags.concurrency) {
|
|
50
|
+
this.error('--concurrency is no longer available\nUse forego instead: https://github.com/ddollar/forego');
|
|
51
|
+
}
|
|
52
|
+
const envFile = validateEnvFile(flags.env, this.warn.bind(this));
|
|
53
|
+
if (flags.procfile)
|
|
54
|
+
execArgv.push('--procfile', flags.procfile);
|
|
55
|
+
execArgv.push('--env', envFile);
|
|
56
|
+
if (flags.port)
|
|
57
|
+
execArgv.push('--port', flags.port);
|
|
58
|
+
if (args.processname) {
|
|
59
|
+
execArgv.push(args.processname);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const procfile = flags.procfile || 'Procfile';
|
|
63
|
+
const procHash = this.loadProcfile(procfile);
|
|
64
|
+
const processes = Object.keys(procHash).filter(x => x !== 'release');
|
|
65
|
+
execArgv.push(processes.join(','));
|
|
66
|
+
}
|
|
67
|
+
await this.runForeman(execArgv);
|
|
68
|
+
}
|
|
69
|
+
// Proxy method to make foreman calls testable
|
|
70
|
+
async runForeman(execArgv) {
|
|
71
|
+
return foreman(execArgv);
|
|
72
|
+
}
|
|
73
|
+
// Proxy method to make procfile loading testable
|
|
74
|
+
loadProcfile(procfilePath) {
|
|
75
|
+
return loadProc(procfilePath);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Run extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static strict: boolean;
|
|
6
|
+
static flags: {
|
|
7
|
+
env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
port: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { fork as foreman } from '../../lib/local/fork-foreman.js';
|
|
3
|
+
import { revertSortedArgs } from '../../lib/run/helpers.js';
|
|
4
|
+
import { validateEnvFile } from '../../lib/local/env-file-validator.js';
|
|
5
|
+
export default class Run extends Command {
|
|
6
|
+
static description = 'run a one-off command';
|
|
7
|
+
static examples = [
|
|
8
|
+
'$ heroku local:run bin/migrate',
|
|
9
|
+
];
|
|
10
|
+
static strict = false;
|
|
11
|
+
static flags = {
|
|
12
|
+
env: Flags.string({
|
|
13
|
+
char: 'e',
|
|
14
|
+
description: 'location of env file (defaults to .env)',
|
|
15
|
+
}),
|
|
16
|
+
port: Flags.string({
|
|
17
|
+
char: 'p',
|
|
18
|
+
description: 'port to listen on',
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
async run() {
|
|
22
|
+
const execArgv = ['run'];
|
|
23
|
+
const { argv, flags } = await this.parse(Run);
|
|
24
|
+
const commandArgs = revertSortedArgs(process.argv, argv);
|
|
25
|
+
if (commandArgs.length === 0) {
|
|
26
|
+
const errorMessage = 'Usage: heroku local:run [COMMAND]\nMust specify command to run';
|
|
27
|
+
this.error(errorMessage, { exit: -1 });
|
|
28
|
+
}
|
|
29
|
+
const envFile = validateEnvFile(flags.env, this.warn.bind(this));
|
|
30
|
+
execArgv.push('--env', envFile);
|
|
31
|
+
if (flags.port)
|
|
32
|
+
execArgv.push('--port', flags.port);
|
|
33
|
+
execArgv.push('--'); // disable node-foreman flag parsing
|
|
34
|
+
execArgv.push(...commandArgs); // eslint-disable-line unicorn/no-array-push-push
|
|
35
|
+
await foreman(execArgv);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { fork as foreman } from '../../lib/local/fork-foreman.js';
|
|
3
|
+
export default class Version extends Command {
|
|
4
|
+
static description = 'display node-foreman version';
|
|
5
|
+
async run() {
|
|
6
|
+
await this.parse(Version);
|
|
7
|
+
const execArgv = ['--version'];
|
|
8
|
+
await foreman(execArgv);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export default class MembersAdd extends Command {
|
|
3
|
+
static topic: string;
|
|
4
|
+
static description: string;
|
|
5
|
+
static flags: {
|
|
6
|
+
role: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
team: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
static args: {
|
|
10
|
+
email: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Command, flags } from '@heroku-cli/command';
|
|
2
|
+
import { Args } from '@oclif/core';
|
|
3
|
+
import { RoleCompletion } from '@heroku-cli/command/lib/completions.js';
|
|
4
|
+
import { addMemberToTeam, inviteMemberToTeam } from '../../lib/members/util.js';
|
|
5
|
+
import { isTeamInviteFeatureEnabled, ROLE_DESCRIPTION } from '../../lib/members/team-invite-utils.js';
|
|
6
|
+
export default class MembersAdd extends Command {
|
|
7
|
+
static topic = 'members';
|
|
8
|
+
static description = 'adds a user to a team';
|
|
9
|
+
static flags = {
|
|
10
|
+
role: flags.string({
|
|
11
|
+
char: 'r',
|
|
12
|
+
required: true,
|
|
13
|
+
description: ROLE_DESCRIPTION,
|
|
14
|
+
completion: RoleCompletion,
|
|
15
|
+
}),
|
|
16
|
+
team: flags.team({ required: true }),
|
|
17
|
+
};
|
|
18
|
+
static args = {
|
|
19
|
+
email: Args.string({ required: true, description: 'email address of the team member' }),
|
|
20
|
+
};
|
|
21
|
+
async run() {
|
|
22
|
+
const { flags, args } = await this.parse(MembersAdd);
|
|
23
|
+
const { team, role } = flags;
|
|
24
|
+
const { email } = args;
|
|
25
|
+
if (await isTeamInviteFeatureEnabled(team, this.heroku)) {
|
|
26
|
+
await inviteMemberToTeam(email, role, team, this.heroku);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
await addMemberToTeam(email, role, team, this.heroku);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export default class MembersIndex extends Command {
|
|
3
|
+
static topic: string;
|
|
4
|
+
static description: string;
|
|
5
|
+
static flags: {
|
|
6
|
+
role: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
pending: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
team: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { color } from '@heroku-cli/color';
|
|
2
|
+
import { Command, flags } from '@heroku-cli/command';
|
|
3
|
+
import { RoleCompletion } from '@heroku-cli/command/lib/completions.js';
|
|
4
|
+
import { ux } from '@oclif/core';
|
|
5
|
+
import { hux } from '@heroku/heroku-cli-util';
|
|
6
|
+
import { isTeamInviteFeatureEnabled, getTeamInvites } from '../../lib/members/team-invite-utils.js';
|
|
7
|
+
const buildTableColumns = (teamInvites) => {
|
|
8
|
+
const baseColumns = {
|
|
9
|
+
email: {
|
|
10
|
+
get: ({ email }) => color.cyan(email),
|
|
11
|
+
},
|
|
12
|
+
role: {
|
|
13
|
+
get: ({ role }) => color.green(role),
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
if (teamInvites.length > 0) {
|
|
17
|
+
return {
|
|
18
|
+
...baseColumns,
|
|
19
|
+
status: {
|
|
20
|
+
get: ({ status }) => color.green(status),
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return baseColumns;
|
|
25
|
+
};
|
|
26
|
+
export default class MembersIndex extends Command {
|
|
27
|
+
static topic = 'members';
|
|
28
|
+
static description = 'list members of a team';
|
|
29
|
+
static flags = {
|
|
30
|
+
role: flags.string({
|
|
31
|
+
char: 'r',
|
|
32
|
+
description: 'filter by role',
|
|
33
|
+
completion: RoleCompletion,
|
|
34
|
+
}),
|
|
35
|
+
pending: flags.boolean({ description: 'filter by pending team invitations' }),
|
|
36
|
+
json: flags.boolean({ description: 'output in json format' }),
|
|
37
|
+
team: flags.team({ required: true }),
|
|
38
|
+
};
|
|
39
|
+
async run() {
|
|
40
|
+
const { flags } = await this.parse(MembersIndex);
|
|
41
|
+
const { role, pending, json, team } = flags;
|
|
42
|
+
let teamInvites = [];
|
|
43
|
+
if (await isTeamInviteFeatureEnabled(team, this.heroku)) {
|
|
44
|
+
const invites = await getTeamInvites(team, this.heroku);
|
|
45
|
+
teamInvites = invites.map((invite) => ({
|
|
46
|
+
email: invite.user?.email || '',
|
|
47
|
+
role: invite.role,
|
|
48
|
+
status: 'pending',
|
|
49
|
+
federated: false,
|
|
50
|
+
user: invite.user,
|
|
51
|
+
two_factor_authentication: false,
|
|
52
|
+
created_at: invite.created_at || '',
|
|
53
|
+
updated_at: invite.updated_at || '',
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
let { body: members } = await this.heroku.get(`/teams/${team}/members`);
|
|
57
|
+
// Set status '' to all existing members
|
|
58
|
+
members.forEach((member) => {
|
|
59
|
+
member.status = '';
|
|
60
|
+
});
|
|
61
|
+
members = [...members, ...teamInvites].sort((a, b) => a.email.localeCompare(b.email));
|
|
62
|
+
if (role)
|
|
63
|
+
members = members.filter(m => m.role === role);
|
|
64
|
+
if (pending)
|
|
65
|
+
members = members.filter(m => m.status === 'pending');
|
|
66
|
+
if (json) {
|
|
67
|
+
ux.stdout(JSON.stringify(members, null, 3));
|
|
68
|
+
}
|
|
69
|
+
else if (members.length === 0) {
|
|
70
|
+
let msg = `No members in ${color.magenta(team || '')}`;
|
|
71
|
+
if (role)
|
|
72
|
+
msg += ` with role ${color.green(role)}`;
|
|
73
|
+
ux.stdout(msg);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const tableColumns = buildTableColumns(teamInvites);
|
|
77
|
+
hux.table(members, tableColumns);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export default class MembersRemove extends Command {
|
|
3
|
+
static topic: string;
|
|
4
|
+
static description: string;
|
|
5
|
+
static flags: {
|
|
6
|
+
team: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
};
|
|
8
|
+
static strict: boolean;
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { color } from '@heroku-cli/color';
|
|
2
|
+
import { Command, flags } from '@heroku-cli/command';
|
|
3
|
+
import { ux } from '@oclif/core';
|
|
4
|
+
import { isTeamInviteFeatureEnabled, getTeamInvites } from '../../lib/members/team-invite-utils.js';
|
|
5
|
+
const revokeInvite = async (email, team, heroku) => {
|
|
6
|
+
ux.action.start(`Revoking invite for ${color.cyan(email)} in ${color.magenta(team)}`);
|
|
7
|
+
await heroku.delete(`/teams/${team}/invitations/${email}`, {
|
|
8
|
+
headers: {
|
|
9
|
+
Accept: 'application/vnd.heroku+json; version=3.team-invitations',
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
ux.action.stop();
|
|
13
|
+
};
|
|
14
|
+
const removeUserMembership = async (email, team, heroku) => {
|
|
15
|
+
ux.action.start(`Removing ${color.cyan(email)} from ${color.magenta(team)}`);
|
|
16
|
+
await heroku.delete(`/teams/${team}/members/${encodeURIComponent(email)}`);
|
|
17
|
+
ux.action.stop();
|
|
18
|
+
};
|
|
19
|
+
export default class MembersRemove extends Command {
|
|
20
|
+
static topic = 'members';
|
|
21
|
+
static description = 'removes a user from a team';
|
|
22
|
+
static flags = {
|
|
23
|
+
team: flags.team({ required: true }),
|
|
24
|
+
};
|
|
25
|
+
static strict = false;
|
|
26
|
+
async run() {
|
|
27
|
+
const { flags, argv } = await this.parse(MembersRemove);
|
|
28
|
+
const { team } = flags;
|
|
29
|
+
const email = argv[0];
|
|
30
|
+
const teamInviteFeatureEnabled = await isTeamInviteFeatureEnabled(team, this.heroku);
|
|
31
|
+
let isInvitedUser = false;
|
|
32
|
+
if (teamInviteFeatureEnabled) {
|
|
33
|
+
const invites = await getTeamInvites(team, this.heroku);
|
|
34
|
+
isInvitedUser = Boolean(invites.some(m => m.user?.email === email));
|
|
35
|
+
}
|
|
36
|
+
if (teamInviteFeatureEnabled && isInvitedUser) {
|
|
37
|
+
await revokeInvite(email, team, this.heroku);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
await removeUserMembership(email, team, this.heroku);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export default class MembersSet extends Command {
|
|
3
|
+
static topic: string;
|
|
4
|
+
static description: string;
|
|
5
|
+
static strict: boolean;
|
|
6
|
+
static flags: {
|
|
7
|
+
role: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
team: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Command, flags } from '@heroku-cli/command';
|
|
2
|
+
import { RoleCompletion } from '@heroku-cli/command/lib/completions.js';
|
|
3
|
+
import { addMemberToTeam } from '../../lib/members/util.js';
|
|
4
|
+
import { ROLE_DESCRIPTION } from '../../lib/members/team-invite-utils.js';
|
|
5
|
+
export default class MembersSet extends Command {
|
|
6
|
+
static topic = 'members';
|
|
7
|
+
static description = 'sets a members role in a team';
|
|
8
|
+
static strict = false;
|
|
9
|
+
static flags = {
|
|
10
|
+
role: flags.string({
|
|
11
|
+
char: 'r',
|
|
12
|
+
required: true,
|
|
13
|
+
description: ROLE_DESCRIPTION,
|
|
14
|
+
completion: RoleCompletion,
|
|
15
|
+
}),
|
|
16
|
+
team: flags.team({ required: true }),
|
|
17
|
+
};
|
|
18
|
+
async run() {
|
|
19
|
+
const { flags, argv } = await this.parse(MembersSet);
|
|
20
|
+
const { role, team } = flags;
|
|
21
|
+
const email = argv[0];
|
|
22
|
+
await addMemberToTeam(email, role, team, this.heroku, 'PATCH');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export default class PipelinesAdd extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
stage: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
static args: {
|
|
11
|
+
pipeline: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { color } from '@heroku-cli/color';
|
|
2
|
+
import { Command, flags } from '@heroku-cli/command';
|
|
3
|
+
import { StageCompletion } from '@heroku-cli/command/lib/completions.js';
|
|
4
|
+
import { Args, ux } from '@oclif/core';
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
import { createCoupling } from '../../lib/api.js';
|
|
7
|
+
import disambiguate from '../../lib/pipelines/disambiguate.js';
|
|
8
|
+
import infer from '../../lib/pipelines/infer.js';
|
|
9
|
+
import { inferrableStageNames as stageNames } from '../../lib/pipelines/stages.js';
|
|
10
|
+
export default class PipelinesAdd extends Command {
|
|
11
|
+
static description = `add this app to a pipeline
|
|
12
|
+
The app and pipeline names must be specified.
|
|
13
|
+
The stage of the app will be guessed based on its name if not specified.`;
|
|
14
|
+
static examples = [
|
|
15
|
+
'$ heroku pipelines:add my-pipeline -a my-app -s production',
|
|
16
|
+
];
|
|
17
|
+
static flags = {
|
|
18
|
+
app: flags.app({ required: true }),
|
|
19
|
+
remote: flags.remote(),
|
|
20
|
+
stage: flags.string({
|
|
21
|
+
char: 's',
|
|
22
|
+
description: 'stage of first app in pipeline',
|
|
23
|
+
completion: StageCompletion,
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
static args = {
|
|
27
|
+
pipeline: Args.string({
|
|
28
|
+
description: 'name of pipeline',
|
|
29
|
+
required: true,
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
async run() {
|
|
33
|
+
const { args, flags } = await this.parse(PipelinesAdd);
|
|
34
|
+
const { app } = flags;
|
|
35
|
+
let stage;
|
|
36
|
+
const guesses = infer(app);
|
|
37
|
+
const questions = [];
|
|
38
|
+
const pipeline = await disambiguate(this.heroku, args.pipeline);
|
|
39
|
+
if (flags.stage) {
|
|
40
|
+
stage = flags.stage;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
questions.push({
|
|
44
|
+
type: 'list',
|
|
45
|
+
name: 'stage',
|
|
46
|
+
message: `Stage of ${app}`,
|
|
47
|
+
choices: stageNames,
|
|
48
|
+
default: guesses[1],
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const answers = await inquirer.prompt(questions);
|
|
52
|
+
if (answers.stage)
|
|
53
|
+
stage = answers.stage;
|
|
54
|
+
ux.action.start(`Adding ${color.app(app)} to ${color.pipeline(pipeline.name)} pipeline as ${stage}`);
|
|
55
|
+
await createCoupling(this.heroku, pipeline, app, stage);
|
|
56
|
+
ux.action.stop();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export default class Connect extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
repo: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
};
|
|
8
|
+
static args: {
|
|
9
|
+
name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Command, flags } from '@heroku-cli/command';
|
|
2
|
+
import { Args, ux } from '@oclif/core';
|
|
3
|
+
import { getPipeline } from '../../lib/api.js';
|
|
4
|
+
import GitHubAPI from '../../lib/pipelines/github-api.js';
|
|
5
|
+
import KolkrabbiAPI from '../../lib/pipelines/kolkrabbi-api.js';
|
|
6
|
+
import getGitHubToken from '../../lib/pipelines/setup/get-github-token.js';
|
|
7
|
+
import getNameAndRepo from '../../lib/pipelines/setup/get-name-and-repo.js';
|
|
8
|
+
import getRepo from '../../lib/pipelines/setup/get-repo.js';
|
|
9
|
+
import { nameAndRepo } from '../../lib/pipelines/setup/validate.js';
|
|
10
|
+
export default class Connect extends Command {
|
|
11
|
+
static description = 'connect a GitHub repo to an existing pipeline';
|
|
12
|
+
static examples = [
|
|
13
|
+
'$ heroku pipelines:connect my-pipeline -r githuborg/reponame',
|
|
14
|
+
];
|
|
15
|
+
static flags = {
|
|
16
|
+
repo: flags.string({
|
|
17
|
+
name: 'repo',
|
|
18
|
+
char: 'r',
|
|
19
|
+
description: 'the GitHub repository to connect to',
|
|
20
|
+
required: true,
|
|
21
|
+
}),
|
|
22
|
+
};
|
|
23
|
+
static args = {
|
|
24
|
+
name: Args.string({
|
|
25
|
+
description: 'name of pipeline',
|
|
26
|
+
required: true,
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
async run() {
|
|
30
|
+
const { args, flags } = await this.parse(Connect);
|
|
31
|
+
const combinedInputs = {
|
|
32
|
+
name: args.name,
|
|
33
|
+
repo: flags.repo,
|
|
34
|
+
};
|
|
35
|
+
const errors = nameAndRepo({ repo: flags.repo });
|
|
36
|
+
if (errors.length > 0) {
|
|
37
|
+
this.error(errors.join(', '));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const kolkrabbi = new KolkrabbiAPI(this.config.userAgent, () => this.heroku.auth);
|
|
41
|
+
const github = new GitHubAPI(this.config.userAgent, await getGitHubToken(kolkrabbi));
|
|
42
|
+
const { name: pipelineName, repo: repoName, } = await getNameAndRepo(combinedInputs);
|
|
43
|
+
const repo = await getRepo(github, repoName);
|
|
44
|
+
const pipeline = await getPipeline(this.heroku, pipelineName);
|
|
45
|
+
ux.action.start('Linking to repo');
|
|
46
|
+
await kolkrabbi.createPipelineRepository(pipeline.body.id, repo.id);
|
|
47
|
+
ux.action.stop();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@heroku-cli/command';
|
|
2
|
+
export default class Create extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
stage: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
team: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
static args: {
|
|
12
|
+
name: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { color } from '@heroku-cli/color';
|
|
2
|
+
import { Command, flags } from '@heroku-cli/command';
|
|
3
|
+
import { StageCompletion } from '@heroku-cli/command/lib/completions.js';
|
|
4
|
+
import { Args, ux } from '@oclif/core';
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
import { createCoupling, createPipeline, getAccountInfo, getTeam } from '../../lib/api.js';
|
|
7
|
+
import infer from '../../lib/pipelines/infer.js';
|
|
8
|
+
import { inferrableStageNames as stages } from '../../lib/pipelines/stages.js';
|
|
9
|
+
import { getGenerationByAppId } from '../../lib/apps/generation.js';
|
|
10
|
+
export default class Create extends Command {
|
|
11
|
+
static description = `create a new pipeline
|
|
12
|
+
An existing app must be specified as the first app in the pipeline.
|
|
13
|
+
The pipeline name will be inferred from the app name if not specified.
|
|
14
|
+
The stage of the app will be guessed based on its name if not specified.
|
|
15
|
+
The pipeline owner will be the user creating the pipeline if not specified with -t for teams or -o for orgs.`;
|
|
16
|
+
static examples = [
|
|
17
|
+
'$ heroku pipelines:create -a my-app-staging',
|
|
18
|
+
'$ heroku pipelines:create my-pipeline -a my-app-staging',
|
|
19
|
+
];
|
|
20
|
+
static flags = {
|
|
21
|
+
app: flags.app({ required: true }),
|
|
22
|
+
remote: flags.remote(),
|
|
23
|
+
stage: flags.string({
|
|
24
|
+
name: 'stage',
|
|
25
|
+
char: 's',
|
|
26
|
+
description: 'stage of first app in pipeline',
|
|
27
|
+
completion: StageCompletion,
|
|
28
|
+
}),
|
|
29
|
+
team: flags.team({
|
|
30
|
+
description: 'the team which will own the apps',
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
static args = {
|
|
34
|
+
name: Args.string({
|
|
35
|
+
description: 'name of pipeline (defaults to basename of the app)',
|
|
36
|
+
required: false,
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
async run() {
|
|
40
|
+
const { args, flags } = await this.parse(Create);
|
|
41
|
+
const { app, stage: inputStage, team: teamName } = flags;
|
|
42
|
+
let name;
|
|
43
|
+
let stage;
|
|
44
|
+
let owner;
|
|
45
|
+
const guesses = infer(app);
|
|
46
|
+
const questions = [];
|
|
47
|
+
if (args.name) {
|
|
48
|
+
name = args.name;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
questions.push({
|
|
52
|
+
type: 'input',
|
|
53
|
+
name: 'name',
|
|
54
|
+
message: 'Pipeline name',
|
|
55
|
+
default: guesses[0],
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (inputStage) {
|
|
59
|
+
stage = inputStage;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
questions.push({
|
|
63
|
+
type: 'list',
|
|
64
|
+
name: 'stage',
|
|
65
|
+
message: `Stage of ${app}`,
|
|
66
|
+
choices: stages,
|
|
67
|
+
default: guesses[1],
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const ownerType = teamName ? 'team' : 'user';
|
|
71
|
+
// If team or org is not specified, we assign ownership to the user creating
|
|
72
|
+
const response = teamName ? await getTeam(this.heroku, teamName) : await getAccountInfo(this.heroku);
|
|
73
|
+
owner = response.body;
|
|
74
|
+
const ownerID = owner.id;
|
|
75
|
+
owner = { id: ownerID, type: ownerType };
|
|
76
|
+
const answers = await inquirer.prompt(questions);
|
|
77
|
+
if (answers.name)
|
|
78
|
+
name = answers.name;
|
|
79
|
+
if (answers.stage)
|
|
80
|
+
stage = answers.stage;
|
|
81
|
+
ux.action.start(`Creating ${name} pipeline`);
|
|
82
|
+
const generation = await getGenerationByAppId(app, this.heroku);
|
|
83
|
+
const { body: pipeline } = await createPipeline(this.heroku, name, owner, generation);
|
|
84
|
+
ux.action.stop();
|
|
85
|
+
ux.action.start(`Adding ${color.app(app)} to ${color.pipeline(pipeline.name)} pipeline as ${stage}`);
|
|
86
|
+
await createCoupling(this.heroku, pipeline, app, stage);
|
|
87
|
+
ux.action.stop();
|
|
88
|
+
}
|
|
89
|
+
}
|