@heroku-cli/heroku-slugs 2.0.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/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ The ISC License (ISC)
2
+
3
+ Copyright © Heroku 2025
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # Heroku Slugs CLI Plugin
2
+
3
+ This plugin adds commands to the Heroku CLI for downloading slugs
4
+
5
+ ## Commands
6
+
7
+ ```
8
+ $ heroku slugs -a appname
9
+ Slugs in appname
10
+ v24: 00000000-bbbb-cccc-dddd-eeeeeeeeeeee
11
+ v23: 11111111-bbbb-cccc-dddd-eeeeeeeeeeee
12
+ v22: 22222222-bbbb-cccc-dddd-eeeeeeeeeeee
13
+ v21: 33333333-bbbb-cccc-dddd-eeeeeeeeeeee
14
+
15
+ $ heroku slugs:download 00000000-bbbb-cccc-dddd-eeeeeeeeeeee -a appname
16
+ ```
17
+
18
+ This will download the Slug directly from our filestore on S3
19
+
20
+ ## Using a proxy
21
+
22
+ ```
23
+ $ export HEROKU_HTTP_PROXY_HOST=<your-proxy-host>
24
+ $ export HEROKU_HTTP_PROXY_PORT=<your-proxy-port>
25
+ $ heroku slugs:download 00000000-bbbb-cccc-dddd-eeeeeeeeeeee -a appname
26
+ ```
27
+
28
+ <!-- toc -->
29
+ * [Heroku Slugs CLI Plugin](#heroku-slugs-cli-plugin)
30
+ * [Usage](#usage)
31
+ * [Commands](#commands)
32
+ <!-- tocstop -->
33
+
34
+ # Usage
35
+ <!-- usage -->
36
+ ```sh-session
37
+ $ npm install -g @heroku-cli/heroku-slugs
38
+ $ heroku COMMAND
39
+ running command...
40
+ $ heroku (--version)
41
+ @heroku-cli/heroku-slugs/2.0.0 darwin-x64 node-v20.18.3
42
+ $ heroku --help [COMMAND]
43
+ USAGE
44
+ $ heroku COMMAND
45
+ ...
46
+ ```
47
+ <!-- usagestop -->
48
+
49
+ # Commands
50
+ <!-- commands -->
51
+ * [`heroku slugs`](#heroku-slugs)
52
+ * [`heroku slugs:download [SLUG]`](#heroku-slugsdownload-slug)
53
+
54
+ ## `heroku slugs`
55
+
56
+ list recent slugs on application
57
+
58
+ ```
59
+ USAGE
60
+ $ heroku slugs -a <value> [-r <value>]
61
+
62
+ FLAGS
63
+ -a, --app=<value> (required) app to run command against
64
+ -r, --remote=<value> git remote of app to use
65
+
66
+ DESCRIPTION
67
+ list recent slugs on application
68
+
69
+ EXAMPLES
70
+ $ heroku slugs --app myapp
71
+ ```
72
+
73
+ _See code: [src/commands/slugs/index.ts](https://github.com/heroku/heroku-slugs/blob/v2.0.0/src/commands/slugs/index.ts)_
74
+
75
+ ## `heroku slugs:download [SLUG]`
76
+
77
+ download a slug's tarball to <APP_NAME>/slug.tar.gz and then extract the slug
78
+
79
+ ```
80
+ USAGE
81
+ $ heroku slugs:download [SLUG] -a <value> [-e] [-r <value>]
82
+
83
+ ARGUMENTS
84
+ SLUG name or ID of slug
85
+
86
+ FLAGS
87
+ -a, --app=<value> (required) app to run command against
88
+ -e, --no-extract-slug skip extracting slug after download
89
+ -r, --remote=<value> git remote of app to use
90
+
91
+ DESCRIPTION
92
+ download a slug's tarball to <APP_NAME>/slug.tar.gz and then extract the slug
93
+
94
+ EXAMPLES
95
+ $ heroku slugs:download --app example-app v2
96
+
97
+ $ heroku slugs:download --app example-app v2 --no-extract-slug
98
+ ```
99
+
100
+ _See code: [src/commands/slugs/download.ts](https://github.com/heroku/heroku-slugs/blob/v2.0.0/src/commands/slugs/download.ts)_
101
+ <!-- commandsstop -->
@@ -0,0 +1,14 @@
1
+ import { Command } from '@heroku-cli/command';
2
+ export default class SlugsDownload extends Command {
3
+ static args: {
4
+ slug: import("@oclif/core/lib/interfaces/parser").Arg<string | undefined, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ app: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
10
+ 'no-extract-slug': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
+ remote: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const command_1 = require("@heroku-cli/command");
4
+ const notifications_1 = require("@heroku-cli/notifications");
5
+ const core_1 = require("@oclif/core");
6
+ const node_child_process_1 = require("node:child_process");
7
+ const download_1 = require("../../lib/download");
8
+ class SlugsDownload extends command_1.Command {
9
+ async run() {
10
+ var _a;
11
+ const { args, flags } = await this.parse(SlugsDownload);
12
+ const { app } = flags;
13
+ const { slug } = args;
14
+ const noExtractSlug = flags['no-extract-slug'];
15
+ let id = slug;
16
+ if (!id) {
17
+ const { body: releases } = await this.heroku.get(`/apps/${app}/releases`, {
18
+ headers: {
19
+ Range: 'version ..; order=desc',
20
+ },
21
+ });
22
+ const currentRelease = releases.find(r => r.slug);
23
+ id = (_a = currentRelease === null || currentRelease === void 0 ? void 0 : currentRelease.slug) === null || _a === void 0 ? void 0 : _a.id;
24
+ }
25
+ if (!id) {
26
+ core_1.ux.error('No slug found. Specify the slug to download by its name or ID.');
27
+ }
28
+ const { body: appSlug } = await this.heroku.get(`/apps/${app}/slugs/${id}`);
29
+ if (!appSlug.blob || !appSlug.blob.url) {
30
+ this.error('This slug has no blob to download.');
31
+ }
32
+ core_1.ux.log(`Downloading slug ${id} to ${app}/slug.tar.gz`);
33
+ const downloadStarted = Date.now();
34
+ (0, node_child_process_1.execSync)(`mkdir ${app}`);
35
+ await (0, download_1.download)(appSlug.blob.url, `${app}/slug.tar.gz`, { progress: true });
36
+ if (!noExtractSlug) {
37
+ core_1.ux.action.start(`Extracting ${app}/slug.tar.gz`);
38
+ (0, node_child_process_1.execSync)(`tar -xf ${app}/slug.tar.gz -C ${app}`);
39
+ core_1.ux.action.stop();
40
+ }
41
+ const downloadTime = Date.now() - downloadStarted;
42
+ if (downloadTime > 10 * 1000) {
43
+ const notification = {
44
+ message: 'download is finished',
45
+ subtitle: 'heroku slugs:download',
46
+ title: app,
47
+ };
48
+ (0, notifications_1.notify)(notification);
49
+ }
50
+ }
51
+ }
52
+ SlugsDownload.args = {
53
+ slug: core_1.Args.string({ description: 'name or ID of slug' }),
54
+ };
55
+ SlugsDownload.description = "download a slug's tarball to <APP_NAME>/slug.tar.gz and then extract the slug";
56
+ SlugsDownload.examples = [
57
+ '$ heroku slugs:download --app example-app v2',
58
+ '$ heroku slugs:download --app example-app v2 --no-extract-slug',
59
+ ];
60
+ SlugsDownload.flags = {
61
+ app: command_1.flags.app({ required: true }),
62
+ 'no-extract-slug': command_1.flags.boolean({ char: 'e', default: false, description: 'skip extracting slug after download' }),
63
+ remote: command_1.flags.remote(),
64
+ };
65
+ exports.default = SlugsDownload;
@@ -0,0 +1,10 @@
1
+ import { Command } from '@heroku-cli/command';
2
+ export default class SlugsIndex extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ app: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
7
+ remote: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
8
+ };
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const color_1 = require("@heroku-cli/color");
4
+ const command_1 = require("@heroku-cli/command");
5
+ const core_1 = require("@oclif/core");
6
+ class SlugsIndex extends command_1.Command {
7
+ async run() {
8
+ var _a;
9
+ const { flags } = await this.parse(SlugsIndex);
10
+ const { app } = flags;
11
+ const { body: releases } = await this.heroku.get(`/apps/${app}/releases`, {
12
+ headers: {
13
+ Range: 'version ..; order=desc',
14
+ },
15
+ });
16
+ core_1.ux.styledHeader(`${color_1.default.app(app)} Slugs`);
17
+ for (const r of releases.filter(r => r.slug))
18
+ core_1.ux.log(`v${r.version}: ${(_a = r.slug) === null || _a === void 0 ? void 0 : _a.id}`);
19
+ }
20
+ }
21
+ SlugsIndex.description = 'list recent slugs on application';
22
+ SlugsIndex.examples = [
23
+ '$ heroku slugs --app myapp',
24
+ ];
25
+ SlugsIndex.flags = {
26
+ app: command_1.flags.app({ required: true }),
27
+ remote: command_1.flags.remote(),
28
+ };
29
+ exports.default = SlugsIndex;
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = {};
@@ -0,0 +1,3 @@
1
+ export declare function download(url: string, path: string, opts: {
2
+ progress: any;
3
+ }): Promise<unknown>;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.download = download;
4
+ const https = require("https");
5
+ const fs = require("node:fs");
6
+ const Path = require("node:path");
7
+ const progress = require('smooth-progress');
8
+ const bytes = require('bytes');
9
+ // eslint-disable-next-line n/no-extraneous-require
10
+ const tunnel = require('tunnel-agent');
11
+ function download(url, path, opts) {
12
+ return new Promise((fulfill, reject) => {
13
+ fs.mkdirSync(Path.dirname(path), { recursive: true });
14
+ const file = fs.createWriteStream(path);
15
+ const agent = makeAgent();
16
+ https.get(url, { agent }, (rsp) => {
17
+ if (opts.progress)
18
+ showProgress(rsp);
19
+ rsp.pipe(file)
20
+ .on('error', reject)
21
+ .on('close', fulfill);
22
+ });
23
+ });
24
+ }
25
+ function makeAgent() {
26
+ const host = process.env.HEROKU_HTTP_PROXY_HOST;
27
+ if (!host) {
28
+ return;
29
+ }
30
+ const port = process.env.HEROKU_HTTP_PROXY_PORT || 8080;
31
+ const auth = process.env.HEROKU_HTTP_PROXY_AUTH;
32
+ const opts = { proxy: { host, port, proxyAuth: auth } };
33
+ return tunnel.httpsOverHttp(opts);
34
+ }
35
+ function showProgress(rsp) {
36
+ const bar = progress({
37
+ tmpl: 'Downloading... :bar :percent :eta :data',
38
+ total: parseInt(rsp.headers['content-length'], 10),
39
+ width: 25,
40
+ });
41
+ let total = 0;
42
+ rsp.on('data', (chunk) => {
43
+ total += chunk.length;
44
+ bar.tick(chunk.length, { data: bytes(total) });
45
+ });
46
+ }
@@ -0,0 +1 @@
1
+ {"root":["../src/index.ts","../src/commands/slugs/download.ts","../src/commands/slugs/index.ts","../src/lib/download.ts"],"version":"5.7.3"}
@@ -0,0 +1,100 @@
1
+ {
2
+ "commands": {
3
+ "slugs:download": {
4
+ "aliases": [],
5
+ "args": {
6
+ "slug": {
7
+ "description": "name or ID of slug",
8
+ "name": "slug"
9
+ }
10
+ },
11
+ "description": "download a slug's tarball to <APP_NAME>/slug.tar.gz and then extract the slug",
12
+ "examples": [
13
+ "$ heroku slugs:download --app example-app v2",
14
+ "$ heroku slugs:download --app example-app v2 --no-extract-slug"
15
+ ],
16
+ "flags": {
17
+ "app": {
18
+ "char": "a",
19
+ "description": "app to run command against",
20
+ "name": "app",
21
+ "required": true,
22
+ "hasDynamicHelp": false,
23
+ "multiple": false,
24
+ "type": "option"
25
+ },
26
+ "no-extract-slug": {
27
+ "char": "e",
28
+ "description": "skip extracting slug after download",
29
+ "name": "no-extract-slug",
30
+ "allowNo": false,
31
+ "type": "boolean"
32
+ },
33
+ "remote": {
34
+ "char": "r",
35
+ "description": "git remote of app to use",
36
+ "name": "remote",
37
+ "hasDynamicHelp": false,
38
+ "multiple": false,
39
+ "type": "option"
40
+ }
41
+ },
42
+ "hasDynamicHelp": false,
43
+ "hiddenAliases": [],
44
+ "id": "slugs:download",
45
+ "pluginAlias": "@heroku-cli/heroku-slugs",
46
+ "pluginName": "@heroku-cli/heroku-slugs",
47
+ "pluginType": "core",
48
+ "strict": true,
49
+ "isESM": false,
50
+ "relativePath": [
51
+ "dist",
52
+ "commands",
53
+ "slugs",
54
+ "download.js"
55
+ ]
56
+ },
57
+ "slugs": {
58
+ "aliases": [],
59
+ "args": {},
60
+ "description": "list recent slugs on application",
61
+ "examples": [
62
+ "$ heroku slugs --app myapp"
63
+ ],
64
+ "flags": {
65
+ "app": {
66
+ "char": "a",
67
+ "description": "app to run command against",
68
+ "name": "app",
69
+ "required": true,
70
+ "hasDynamicHelp": false,
71
+ "multiple": false,
72
+ "type": "option"
73
+ },
74
+ "remote": {
75
+ "char": "r",
76
+ "description": "git remote of app to use",
77
+ "name": "remote",
78
+ "hasDynamicHelp": false,
79
+ "multiple": false,
80
+ "type": "option"
81
+ }
82
+ },
83
+ "hasDynamicHelp": false,
84
+ "hiddenAliases": [],
85
+ "id": "slugs",
86
+ "pluginAlias": "@heroku-cli/heroku-slugs",
87
+ "pluginName": "@heroku-cli/heroku-slugs",
88
+ "pluginType": "core",
89
+ "strict": true,
90
+ "isESM": false,
91
+ "relativePath": [
92
+ "dist",
93
+ "commands",
94
+ "slugs",
95
+ "index.js"
96
+ ]
97
+ }
98
+ },
99
+ "version": "2.0.0"
100
+ }
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@heroku-cli/heroku-slugs",
3
+ "description": "Heroku CLI Plugin to manage and download slugs",
4
+ "version": "2.0.0",
5
+ "author": "Heroku",
6
+ "bugs": {
7
+ "url": "https://github.com/heroku/heroku-slugs/issues"
8
+ },
9
+ "dependencies": {
10
+ "@heroku-cli/color": "2.0.4",
11
+ "@heroku-cli/command": "^11.5.0",
12
+ "@heroku-cli/notifications": "1.2.5",
13
+ "@oclif/core": "^2.16.0",
14
+ "bytes": "^3.1.2",
15
+ "fs-extra": "^11.3.0",
16
+ "smooth-progress": "^1.1.0"
17
+ },
18
+ "devDependencies": {
19
+ "@heroku-cli/schema": "^2.0.0",
20
+ "@oclif/plugin-help": "^5",
21
+ "@types/bytes": "^3.1.4",
22
+ "@types/chai": "^4",
23
+ "@types/fs-extra": "^11.0.4",
24
+ "@types/mocha": "^10.0.10",
25
+ "@types/node": "20.14.8",
26
+ "@types/sinon": "^17.0.3",
27
+ "@types/supports-color": "^8.1.3",
28
+ "chai": "^4.4.1",
29
+ "eslint": "^8.57.0",
30
+ "eslint-config-oclif": "^5.2.2",
31
+ "eslint-config-oclif-typescript": "^3.1.14",
32
+ "eslint-plugin-mocha": "^10.5.0",
33
+ "mocha": "^11.1.0",
34
+ "nock": "^14.0.0",
35
+ "nyc": "^17.1.0",
36
+ "oclif": "4.14.36",
37
+ "sinon": "^19.0.2",
38
+ "stdout-stderr": "0.1.13",
39
+ "strip-ansi": "^6.0.1",
40
+ "ts-node": "^10.9.2",
41
+ "tsheredoc": "^1.0.1",
42
+ "typescript": "^5.7.3"
43
+ },
44
+ "engines": {
45
+ "node": ">= 20"
46
+ },
47
+ "files": [
48
+ "/dist",
49
+ "/oclif.manifest.json"
50
+ ],
51
+ "keywords": [
52
+ "heroku-plugin",
53
+ "oclif"
54
+ ],
55
+ "license": "ISC",
56
+ "main": "dist/index.js",
57
+ "mocha": {
58
+ "require": [
59
+ "test/init.js",
60
+ "ts-node/register"
61
+ ],
62
+ "watch-extensions": "ts",
63
+ "recursive": true,
64
+ "reporter": "spec",
65
+ "timeout": 360000
66
+ },
67
+ "types": "dist/index.d.ts",
68
+ "oclif": {
69
+ "commands": "./dist/commands",
70
+ "bin": "heroku",
71
+ "topics": {
72
+ "slugs": {
73
+ "description": "manage and download slugs"
74
+ }
75
+ }
76
+ },
77
+ "repository": "heroku/heroku-slugs",
78
+ "scripts": {
79
+ "build": "rm -rf dist && tsc -b",
80
+ "lint": "eslint . --ext .ts --config .eslintrc",
81
+ "postpack": "rm -f oclif.manifest.json",
82
+ "prepack": "yarn build && oclif manifest && oclif readme",
83
+ "test": "nyc mocha --forbid-only \"test/**/*.test.ts\"",
84
+ "version": "oclif readme && git add README.md"
85
+ }
86
+ }