@mux/cli 0.8.0 → 1.0.0-beta.1

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.
Files changed (47) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +1216 -259
  3. package/bin/mux +31 -0
  4. package/package.json +64 -103
  5. package/bin/run +0 -6
  6. package/bin/run.cmd +0 -3
  7. package/lib/command-bases/asset-base.d.ts +0 -11
  8. package/lib/command-bases/asset-base.js +0 -41
  9. package/lib/command-bases/base.d.ts +0 -14
  10. package/lib/command-bases/base.js +0 -62
  11. package/lib/command-bases/live-base.d.ts +0 -11
  12. package/lib/command-bases/live-base.js +0 -31
  13. package/lib/commands/assets/create.d.ts +0 -10
  14. package/lib/commands/assets/create.js +0 -64
  15. package/lib/commands/assets/upload.d.ts +0 -20
  16. package/lib/commands/assets/upload.js +0 -148
  17. package/lib/commands/init.d.ts +0 -20
  18. package/lib/commands/init.js +0 -115
  19. package/lib/commands/live/complete.d.ts +0 -12
  20. package/lib/commands/live/complete.js +0 -54
  21. package/lib/commands/live/disable.d.ts +0 -13
  22. package/lib/commands/live/disable.js +0 -40
  23. package/lib/commands/live/enable.d.ts +0 -13
  24. package/lib/commands/live/enable.js +0 -40
  25. package/lib/commands/sign.d.ts +0 -11
  26. package/lib/commands/sign.js +0 -68
  27. package/lib/commands/spaces/sign.d.ts +0 -11
  28. package/lib/commands/spaces/sign.js +0 -79
  29. package/lib/config.d.ts +0 -10
  30. package/lib/config.js +0 -12
  31. package/lib/index.d.ts +0 -2
  32. package/lib/index.js +0 -7
  33. package/oclif.manifest.json +0 -1
  34. package/src/command-bases/asset-base.ts +0 -52
  35. package/src/command-bases/base.ts +0 -72
  36. package/src/command-bases/live-base.ts +0 -35
  37. package/src/commands/assets/create.ts +0 -74
  38. package/src/commands/assets/upload.ts +0 -192
  39. package/src/commands/init.ts +0 -150
  40. package/src/commands/live/complete.ts +0 -63
  41. package/src/commands/live/disable.ts +0 -47
  42. package/src/commands/live/enable.ts +0 -47
  43. package/src/commands/sign.ts +0 -76
  44. package/src/commands/spaces/sign.ts +0 -90
  45. package/src/config.ts +0 -13
  46. package/src/index.ts +0 -3
  47. package/yarn.lock +0 -7350
@@ -1,35 +0,0 @@
1
- import { flags } from '@oclif/command';
2
- import { IFlag } from '@oclif/command/lib/flags';
3
-
4
- import CommandBase from './base';
5
-
6
- export default abstract class LiveCommandBase extends CommandBase {
7
- static argsForSingleLiveStream = [
8
- {
9
- name: 'streamName',
10
- description:
11
- "the name (coupled with --reference-type) to look up in Mux to yield this livestream",
12
- required: true,
13
- },
14
- ];
15
-
16
- static flagsForSingleLiveStream: Record<string, IFlag<any>> = {
17
- streamId: flags.string({
18
- name: 'reference-type',
19
- char: 't',
20
- description: 'the type of the provided reference name',
21
- options: ['stream-id'],
22
- default: 'stream-id',
23
- }),
24
- }
25
-
26
- protected getStreamId(flags: Record<string, any>, streamName: string): string {
27
- switch (flags.streamId) {
28
- case 'stream-id':
29
- // just a pass-through
30
- return streamName;
31
- }
32
-
33
- throw new Error("Could not derive a stream ID. Please pass one with --stream-id.");
34
- }
35
- }
@@ -1,74 +0,0 @@
1
- import * as chalk from 'chalk';
2
- import * as clipboard from 'clipboardy';
3
- import * as Listr from 'listr';
4
-
5
- import AssetCommandBase from '../../command-bases/asset-base';
6
-
7
- export default class AssetsCreate extends AssetCommandBase {
8
- static description =
9
- "Create a new asset in Mux using a file that's already available online";
10
-
11
- static args = [
12
- {
13
- name: 'input',
14
- description:
15
- "input URL for the file you'd like to create this asset from",
16
- required: true,
17
- },
18
- ];
19
-
20
- async run() {
21
- const { args, flags } = this.parse(AssetsCreate);
22
- let assetBodyParams = {
23
- input: args.input,
24
- playback_policies: flags.private ? ['signed'] : ['public'],
25
- };
26
-
27
- const tasks = new Listr([
28
- {
29
- title: 'Creating Mux Asset',
30
- task: async ctx => {
31
- const asset = await this.Video.Assets.create(assetBodyParams);
32
- ctx.asset = asset;
33
- },
34
- },
35
- {
36
- title: 'Waiting for asset to be playable',
37
- task: async ctx => {
38
- const asset = await this.pollAsset(ctx.asset.id);
39
- ctx.asset = asset;
40
- },
41
- },
42
- ]);
43
-
44
- try {
45
- const ctx = await tasks.run();
46
- const playbackUrl = this.playbackUrl(ctx.asset);
47
-
48
- if (!process.env.WSL_DISTRO_NAME) {
49
- await clipboard.write(playbackUrl);
50
- }
51
-
52
- this.log(
53
- chalk`
54
- 💫 {bold.blue Asset ready for your enjoyment!}
55
-
56
- {bold.underline Playback URL}
57
- ${playbackUrl}
58
- `
59
- );
60
- } catch (err) {
61
- console.log(
62
- chalk.redBright('Error:') +
63
- "\n\n" +
64
- err
65
- );
66
-
67
- if (err instanceof Error) {
68
- this.error(err);
69
- } else {
70
- throw err;
71
- }
72
- }
73
- }
74
- }
@@ -1,192 +0,0 @@
1
- import { CreateUploadParams, Upload } from '@mux/mux-node';
2
- import { flags } from '@oclif/command';
3
- import * as chalk from 'chalk';
4
- import * as clipboard from 'clipboardy';
5
- import * as fs from 'fs-extra';
6
- import * as inquirer from 'inquirer';
7
- import * as Listr from 'listr';
8
- import * as path from 'path';
9
- import * as request from 'request';
10
-
11
- import AssetCommandBase, { AssetBaseFlags } from '../../command-bases/asset-base';
12
-
13
- export type AssetCreateFlags = AssetBaseFlags & {
14
- filter: flags.IOptionFlag<string | undefined>;
15
- concurrent: flags.IOptionFlag<number>;
16
- };
17
-
18
- export default class AssetsCreate extends AssetCommandBase {
19
- static description = 'Create a new asset in Mux via a local file';
20
-
21
- static args = [
22
- {
23
- name: 'path',
24
- description: "local path for the file (or folder) you'd like to upload",
25
- required: true,
26
- },
27
- ];
28
-
29
- static flags: AssetCreateFlags = {
30
- ...AssetCommandBase.flags,
31
- filter: flags.string({
32
- char: 'f',
33
- description:
34
- 'regex that filters the selected destination if the provided path is a folder',
35
- }),
36
- concurrent: flags.integer({
37
- char: 'c',
38
- description: 'max number of files to upload at once',
39
- default: 3,
40
- }),
41
- private: flags.boolean({
42
- char: 'p',
43
- default: false,
44
- })
45
- };
46
-
47
- getFilePaths(filePath: string, filter = '') {
48
- if (fs.lstatSync(filePath).isDirectory()) {
49
- return fs.readdirSync(filePath).filter(file => file.match(filter));
50
- }
51
-
52
- return [filePath];
53
- }
54
-
55
- uploadFile(filePath: string, url: string) {
56
- return new Promise((resolve, reject) => {
57
- fs.createReadStream(path.resolve(process.cwd(), filePath)).pipe(
58
- request
59
- .put(url)
60
- .on('response', resolve)
61
- .on('error', reject)
62
- );
63
- });
64
- }
65
-
66
- pollUpload(uploadId: string): Promise<Upload> {
67
- return new Promise((resolve, reject) => {
68
- const poll = () =>
69
- setTimeout(async () => {
70
- try {
71
- const upload = await this.Video.Uploads.get(uploadId);
72
- if (upload.status === 'asset_created') {
73
- return resolve(upload);
74
- } else if (upload.status === 'errored') {
75
- return reject(upload);
76
- }
77
-
78
- poll();
79
- } catch (err) {
80
- reject(err);
81
- }
82
- }, 500);
83
-
84
- poll();
85
- });
86
- }
87
-
88
- async run() {
89
- const { args, flags } = this.parse(AssetsCreate);
90
-
91
- let assetBodyParams: CreateUploadParams = {
92
- new_asset_settings: {
93
- playback_policy: flags.private ? ['signed'] : ['public'],
94
- },
95
- };
96
-
97
- const regex = new RegExp(flags.filter || '', 'ig');
98
- const files = this.getFilePaths(
99
- path.resolve(process.cwd(), args.path)
100
- ).filter(file => file.match(regex));
101
-
102
- let prompt = { files };
103
- if (files.length === 0) {
104
- return this.log(
105
- `We were unable to find any files. You might want to double check your path or make sure your filter isn't too strict`
106
- );
107
- } else if (files.length > 1) {
108
- prompt = await inquirer.prompt([
109
- {
110
- type: 'checkbox',
111
- name: 'files',
112
- message: 'We found a few files! Do all of these look good?',
113
- choices: files,
114
- default: files,
115
- },
116
- ]);
117
- }
118
-
119
- const tasks = prompt.files.map((file: string) => ({
120
- title: `${file}: getting direct upload URL`,
121
- task: async (ctx: any, task: Listr.ListrTaskWrapper) => {
122
- const upload = await this.Video.Uploads.create(assetBodyParams);
123
-
124
- task.title = `${file}: uploading`;
125
- await this.uploadFile(
126
- path.resolve(process.cwd(), args.path, file),
127
- upload.url
128
- );
129
-
130
- task.title = `${file}: waiting for asset to be playable`;
131
- const { asset_id: assetId } = await this.pollUpload(upload.id);
132
-
133
- if (!assetId) {
134
- throw new Error(`Asset for upload '${upload.id}' failed to resolve.`);
135
- }
136
- const asset = await this.pollAsset(assetId);
137
-
138
- const playbackUrl = this.playbackUrl(asset);
139
- task.title = `${file}: ${playbackUrl}`;
140
- ctx.assets = [
141
- ...(ctx.assets || [['Filename', 'Asset ID', 'PlaybackURL']]),
142
- [file, asset.id, playbackUrl],
143
- ];
144
- },
145
- }));
146
-
147
- // I hate this `any` here, but I'm running into a weird issue casting
148
- // the `tasks` above to an array of `ListrTasks[]`.
149
- try {
150
- const finalCtx = await new Listr(tasks, {
151
- concurrent: flags.concurrent,
152
- }).run();
153
-
154
- const tsv = finalCtx.assets
155
- .map((row: string[]) => row.join('\t'))
156
- .join('\n');
157
- if (prompt.files.length > 1 && !process.env.WSL_DISTRO_NAME) {
158
- await clipboard.write(tsv);
159
-
160
- return this.log(
161
- chalk`
162
- 📹 {bold.underline Assets ready for your enjoyment:}
163
- ${tsv}
164
-
165
- {blue (copied to your clipboard)}`
166
- );
167
- }
168
-
169
- await clipboard.write(finalCtx.assets[1][2]);
170
- return this.log(
171
- chalk`
172
- 📹 {bold.underline Asset ready for your enjoyment:}
173
- ${tsv}
174
-
175
- {blue (since you only uploaded one asset, we just added the playback URL to your clipboard.)}`
176
- );
177
- } catch (err) {
178
- // TODO: make this clearer / separate it out per video for more obvious debugging.
179
- console.log(
180
- chalk.redBright('Error:') +
181
- "\n\n" +
182
- err
183
- );
184
-
185
- if (err instanceof Error) {
186
- this.error(err);
187
- } else {
188
- throw err;
189
- }
190
- }
191
- }
192
- }
@@ -1,150 +0,0 @@
1
- import Mux from '@mux/mux-node';
2
- import { flags } from '@oclif/command';
3
- import * as Parser from '@oclif/parser';
4
- import * as chalk from 'chalk';
5
- import * as dotenv from 'dotenv';
6
- import * as fs from 'fs-extra';
7
- import * as inquirer from 'inquirer';
8
- import * as path from 'path';
9
-
10
- import MuxBase from '../command-bases/base';
11
- import { MuxCliConfigV1 } from '../config';
12
-
13
- export type InitFlags = {
14
- force: Parser.flags.IBooleanFlag<boolean>;
15
- };
16
-
17
- export default class Init extends MuxBase {
18
- static description = 'set up a user-level config';
19
-
20
- static args = [
21
- {
22
- name: 'envFile',
23
- required: false,
24
- description: 'path to a Mux access token .env file',
25
- parse: (input: string) => path.resolve(input),
26
- },
27
- ];
28
-
29
- static flags: InitFlags = {
30
- force: flags.boolean({
31
- name: 'force',
32
- char: 'f',
33
- description: 'Will initialize a new file even if one already exists.',
34
- default: false,
35
- }),
36
- };
37
-
38
- Video: any;
39
- JWT: any;
40
-
41
- muxConfig: Partial<MuxCliConfigV1> = {};
42
-
43
- async run() {
44
- const { args, flags } = this.parse(Init);
45
-
46
- let prompts: {
47
- name: string;
48
- message: string;
49
- type: string;
50
- default?: string;
51
- }[] = [];
52
-
53
- if (args.envFile) {
54
- const envFile = path.resolve(args.envFile);
55
- const env = dotenv.config({ path: envFile });
56
- if (env.error) {
57
- this.error(`Unable to load env file: ${envFile}`);
58
- } else if (env.parsed) {
59
- this.log(
60
- chalk`Loaded your Mux .env file! Using token with id: {blue ${
61
- env.parsed.MUX_TOKEN_ID
62
- }}`
63
- );
64
- process.env.MUX_TOKEN_ID = env.parsed.MUX_TOKEN_ID;
65
- process.env.MUX_TOKEN_SECRET = env.parsed.MUX_TOKEN_SECRET;
66
- }
67
- } else {
68
- prompts = [
69
- {
70
- name: 'tokenId',
71
- message: "What's your token ID?",
72
- type: 'string',
73
- default: process.env.MUX_TOKEN_ID,
74
- },
75
- {
76
- name: 'tokenSecret',
77
- message: "What's your token secret?",
78
- type: 'password',
79
- default: process.env.MUX_TOKEN_SECRET,
80
- },
81
- ];
82
- }
83
-
84
- if (!flags.force && (await fs.pathExists(this.configFile))) {
85
- this.log(
86
- chalk`{bold.underline.red You are attempting to initialize with an existing configuration file!} If this is intentional, please pass {bold.yellow --force}.`
87
- );
88
- process.exit(1);
89
- }
90
-
91
- const signingKeyPrompt = {
92
- name: 'createSigningKey',
93
- message:
94
- 'Do you want to go ahead and set up a Signing Key? This is used to create tokens for signed playback policies.',
95
- type: 'confirm',
96
- };
97
-
98
- if (process.env.MUX_SIGNING_KEY || process.env.MUX_PRIVATE_KEY) {
99
- signingKeyPrompt.message =
100
- 'Looks like you already have a signing key configured. Would you like to create a new one?';
101
- }
102
-
103
- prompts = [...prompts, signingKeyPrompt];
104
-
105
- const answers = await inquirer.prompt(prompts);
106
- let { createSigningKey, tokenId, tokenSecret }: any = answers;
107
-
108
- // If the token was loaded from an env file they'll already be set in the appropriate environment variables and
109
- // the prompts themselves will be null.
110
- this.muxConfig.configVersion = 1;
111
- this.muxConfig.tokenId = process.env.MUX_TOKEN_ID = tokenId || process.env.MUX_TOKEN_ID;
112
- this.muxConfig.tokenSecret = process.env.MUX_TOKEN_SECRET = tokenSecret || process.env.MUX_TOKEN_SECRET;
113
-
114
- if (createSigningKey) {
115
- const { Video } = new Mux();
116
- try {
117
- // this is not great; we need to also update mux-node for this call
118
- // but that's a bigger can of worms. We'll come back to this one.
119
- // @ts-ignore
120
- const { id, private_key } = await Video.SigningKeys.create();
121
- this.muxConfig.signingKeyId = process.env.MUX_SIGNING_KEY = id;
122
- this.muxConfig.signingKeySecret = process.env.MUX_PRIVATE_KEY = private_key;
123
- } catch {
124
- this.error(
125
- "Couldn't create a signing key pair! Are you sure your token credentials were right?"
126
- );
127
- }
128
- }
129
-
130
- try {
131
- await fs.ensureDir(this.config.configDir);
132
- await fs.writeFile(
133
- this.configFile,
134
- JSON.stringify(this.muxConfig),
135
- 'utf8'
136
- );
137
- } catch (err) {
138
- // TODO: improve error handling type safety here
139
- if (err instanceof Error) {
140
- this.error(err);
141
- } else {
142
- throw err;
143
- }
144
- }
145
-
146
- this.log(
147
- chalk`{bold.underline Configuration written to:} ${this.configFile}`
148
- );
149
- }
150
- }
@@ -1,63 +0,0 @@
1
- import * as Listr from 'listr';
2
- import { flags } from '@oclif/command';
3
- import { IFlag } from '@oclif/command/lib/flags';
4
-
5
- import LiveCommandBase from '../../command-bases/live-base';
6
- import chalk = require('chalk');
7
-
8
- export default class LiveComplete extends LiveCommandBase {
9
- static description =
10
- "Signal to Mux that a live stream has concluded and should be closed.";
11
-
12
- static args = [
13
- ...LiveCommandBase.argsForSingleLiveStream,
14
- ];
15
-
16
- static flags: Record<string, IFlag<any>> = {
17
- ...LiveCommandBase.flagsForSingleLiveStream,
18
- disableAfterCompletion: flags.boolean({
19
- name: 'disable-after-completion',
20
- char: 'd',
21
- description: 'If set, disables the stream upon completion.',
22
- default: false,
23
- }),
24
- }
25
-
26
- async run() {
27
- const { args, flags } = this.parse(LiveComplete);
28
- const streamId: string = this.getStreamId(flags, args.streamName);
29
-
30
- try {
31
- await (new Listr([
32
- {
33
- title: `Signaling completion of '${streamId}'`,
34
- task: async (ctx, task) => {
35
- await this.Video.LiveStreams.signalComplete(streamId),
36
- task.title = `${task.title} (OK)`;
37
- },
38
- },
39
- {
40
- title: `Disabling '${streamId}'`,
41
- enabled: () => flags.disableAfterCompletion,
42
- task: async (ctx, task) => {
43
- await this.Video.LiveStreams.disable(streamId);
44
- task.title = `${task.title} (OK)`;
45
- },
46
- },
47
- ], {}).run());
48
- } catch (err) {
49
- // TODO: make this clearer
50
- console.log(
51
- chalk.redBright('Error:') +
52
- "\n\n" +
53
- err
54
- );
55
-
56
- if (err instanceof Error) {
57
- this.error(err);
58
- } else {
59
- throw err;
60
- }
61
- }
62
- }
63
- }
@@ -1,47 +0,0 @@
1
- import * as Listr from 'listr';
2
-
3
- import LiveCommandBase from '../../command-bases/live-base';
4
- import chalk = require('chalk');
5
-
6
- export default class LiveDisable extends LiveCommandBase {
7
- static description =
8
- "Disables a live stream and prevents encoders from streaming to it.";
9
-
10
- static args = [
11
- ...LiveCommandBase.argsForSingleLiveStream,
12
- ];
13
-
14
- static flags = {
15
- ...LiveCommandBase.flagsForSingleLiveStream,
16
- }
17
-
18
- async run() {
19
- const { args, flags } = this.parse(LiveDisable);
20
- const streamId: string = this.getStreamId(flags, args.streamName);
21
-
22
- try {
23
- await (new Listr([
24
- {
25
- title: `Disabling '${streamId}'`,
26
- task: async (ctx, task) => {
27
- await this.Video.LiveStreams.disable(streamId);
28
- task.title = `${task.title} (OK)`;
29
- },
30
- },
31
- ], {}).run());
32
- } catch (err) {
33
- // TODO: make this clearer
34
- console.log(
35
- chalk.redBright('Error:') +
36
- "\n\n" +
37
- err
38
- );
39
-
40
- if (err instanceof Error) {
41
- this.error(err);
42
- } else {
43
- throw err;
44
- }
45
- }
46
- }
47
- }
@@ -1,47 +0,0 @@
1
- import * as Listr from 'listr';
2
-
3
- import LiveCommandBase from '../../command-bases/live-base';
4
- import chalk = require('chalk');
5
-
6
- export default class LiveEnable extends LiveCommandBase {
7
- static description =
8
- "Enables a live stream, allowing encoders to streaming to it.";
9
-
10
- static args = [
11
- ...LiveCommandBase.argsForSingleLiveStream,
12
- ];
13
-
14
- static flags = {
15
- ...LiveCommandBase.flagsForSingleLiveStream,
16
- }
17
-
18
- async run() {
19
- const { args, flags } = this.parse(LiveEnable);
20
- const streamId: string = this.getStreamId(flags, args.streamName);
21
-
22
- try {
23
- await (new Listr([
24
- {
25
- title: `Enabling '${streamId}'`,
26
- task: async (ctx, task) => {
27
- await this.Video.LiveStreams.enable(streamId);
28
- task.title = `${task.title} (OK)`;
29
- },
30
- },
31
- ], {}).run());
32
- } catch (err) {
33
- // TODO: make this clearer
34
- console.log(
35
- chalk.redBright('Error:') +
36
- "\n\n" +
37
- err
38
- );
39
-
40
- if (err instanceof Error) {
41
- this.error(err);
42
- } else {
43
- throw err;
44
- }
45
- }
46
- }
47
- }
@@ -1,76 +0,0 @@
1
- import { flags } from '@oclif/command';
2
- // this is a load-bearing unused import due to oclif type issues
3
- import { IOptionFlag, IBooleanFlag } from '@oclif/parser/lib/flags';
4
- import * as chalk from 'chalk';
5
- import * as clipboard from 'clipboardy';
6
-
7
- import MuxBase from '../command-bases/base';
8
-
9
- export default class Sign extends MuxBase {
10
- static description = 'Creates a new signed URL token for a playback ID';
11
-
12
- static args = [
13
- {
14
- name: 'playback-id',
15
- description: 'Playback ID to create a signed URL token for.',
16
- required: true,
17
- },
18
- ];
19
-
20
- static flags: any = {
21
- expiresIn: flags.string({
22
- char: 'e',
23
- description: 'How long the signature is valid for. If no unit is specified, milliseconds is assumed.',
24
- default: '7d',
25
- }),
26
- type: flags.string({
27
- char: 't',
28
- description: 'What type of token this signature is for.',
29
- default: 'video',
30
- options: ['video', 'thumbnail', 'gif', 'storyboard'],
31
- }),
32
- raw: flags.boolean({
33
- char: 'r',
34
- description: 'If set, emits only the URL+JWT. Defaults to true for non-TTY.',
35
- default: !process.stdin.isTTY,
36
- }),
37
- };
38
-
39
- async run() {
40
- const parsed = this.parse(Sign);
41
- const args = parsed.args;
42
- const flags = parsed.flags as any;
43
-
44
- const playbackId = args['playback-id'];
45
-
46
- const options = {
47
- expiration: flags.expiresIn,
48
- type: flags.type as any, // TODO: is better checked in SDK, but this is ugly.
49
- keyId: this.MuxConfig.signingKeyId,
50
- keySecret: this.MuxConfig.signingKeySecret,
51
- };
52
- const key = this.JWT.sign(playbackId, options);
53
- const url = `https://stream.mux.com/${playbackId}.m3u8?token=${key}`;
54
-
55
- if (flags.raw) {
56
- console.log(url);
57
- } else {
58
- this.log(
59
- chalk`
60
- 🔑 {bold.underline Your Mux Signed Token}
61
- {blue ${key}}
62
-
63
- 🌏 {bold.underline Full URL}
64
- {green ${url}}
65
- `
66
- );
67
-
68
- try {
69
- await clipboard.write(url);
70
- this.log(`👉 Copied Full URL to your system clipboard`);
71
- } catch {
72
- this.error('Unable to copy full url automatically');
73
- }
74
- }
75
- }
76
- }