defang 0.5.28 → 0.5.36

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 ADDED
@@ -0,0 +1,37 @@
1
+ # Defang
2
+
3
+ Defang is a radically simpler way for developers to develop, deploy, and debug cloud applications.
4
+
5
+ - Public releases of the Defang CLI; [click here](https://github.com/DefangLabs/defang/releases/latest/) for the latest version
6
+ - Sample projects in Golang, Python, Node.js, and many more languages and frameworks, that show you how to accomplish various tasks and deploy them to the DOP using a Docker Compose file and the Defang CLI.
7
+ - Samples that show how to deploy an app using the [Defang Pulumi Provider](https://github.com/DefangLabs/pulumi-defang).
8
+
9
+ ## Getting started
10
+
11
+ - Read our [Getting Started](https://docs.defang.io/docs/getting-started) page
12
+ - Follow the installation instructions from the [Installing](https://docs.defang.io/docs/getting-started/installing) page
13
+ - Take a look at our [Samples folder](https://github.com/DefangLabs/defang/tree/main/samples) for example projects in various programming languages.
14
+ - Try the AI integration by running `defang generate`
15
+ - Start your new service with `defang compose up`
16
+
17
+ ## Support
18
+
19
+ - File any issues [right here on GitHub](https://github.com/DefangLabs/defang/issues)
20
+
21
+ ## Environment Variables
22
+
23
+ The Defang CLI recognizes the following environment variables:
24
+
25
+ - `COMPOSE_PROJECT_NAME` - The name of the project to use; overrides the name in the `compose.yaml` file
26
+ - `DEFANG_ACCESS_TOKEN` - The access token to use for authentication; if not specified, uses token from `defang login`
27
+ - `DEFANG_CD_BUCKET` - The S3 bucket to use for the BYOC CD pipeline; defaults to `defang-cd-bucket-…`
28
+ - `DEFANG_CD_IMAGE` - The image to use for the Continuous Deployment (CD) pipeline; defaults to `public.ecr.aws/defang-io/cd:public-beta`
29
+ - `DEFANG_DEBUG` - set this to `1` or `true` to enable debug logging
30
+ - `DEFANG_DISABLE_ANALYTICS` - If set to `true`, disables sending analytics to Defang; defaults to `false`
31
+ - `DEFANG_FABRIC` - The address of the Defang Fabric to use; defaults to `fabric-prod1.defang.dev`
32
+ - `DEFANG_HIDE_HINTS` - If set to `true`, hides hints in the CLI output; defaults to `false`
33
+ - `DEFANG_HIDE_UPDATE` - If set to `true`, hides the update notification; defaults to `false`
34
+ - `DEFANG_PROVIDER` - The name of the cloud provider to use, `auto` (default), `aws`, or `defang`
35
+ - `NO_COLOR` - If set to any value, disables color output; by default, color output is enabled depending on the terminal
36
+ - `TZ` - The timezone to use for log timestamps: an IANA TZ name like `UTC` or `Europe/Amsterdam`; defaults to `Local`
37
+ - `XDG_STATE_HOME` - The directory to use for storing state; defaults to `~/.local/state`
package/bin/cli.js CHANGED
@@ -1,263 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
- if (k2 === undefined) k2 = k;
5
- var desc = Object.getOwnPropertyDescriptor(m, k);
6
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
- desc = { enumerable: true, get: function() { return m[k]; } };
8
- }
9
- Object.defineProperty(o, k2, desc);
10
- }) : (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k;
12
- o[k2] = m[k];
13
- }));
14
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
- Object.defineProperty(o, "default", { enumerable: true, value: v });
16
- }) : function(o, v) {
17
- o["default"] = v;
18
- });
19
- var __importStar = (this && this.__importStar) || function (mod) {
20
- if (mod && mod.__esModule) return mod;
21
- var result = {};
22
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
23
- __setModuleDefault(result, mod);
24
- return result;
25
- };
26
- var __importDefault = (this && this.__importDefault) || function (mod) {
27
- return (mod && mod.__esModule) ? mod : { "default": mod };
28
- };
29
3
  Object.defineProperty(exports, "__esModule", { value: true });
30
- const adm_zip_1 = __importDefault(require("adm-zip"));
31
- const axios_1 = __importDefault(require("axios"));
32
- const child_process = __importStar(require("child_process"));
33
- const fs_1 = require("fs");
34
- const os = __importStar(require("os"));
35
- const path = __importStar(require("path"));
36
- const tar = __importStar(require("tar"));
37
- const util_1 = require("util");
38
- const EXECUTABLE = "defang";
39
- const URL_LATEST_RELEASE = "https://api.github.com/repos/DefangLabs/defang/releases/latest";
40
- const HTTP_STATUS_OK = 200;
41
- const exec = (0, util_1.promisify)(child_process.exec);
42
- async function getLatestVersion() {
43
- const response = await axios_1.default.get(URL_LATEST_RELEASE);
44
- if (response.status !== HTTP_STATUS_OK) {
45
- throw new Error(`Failed to get latest version from GitHub. Status code: ${response.status}`);
46
- }
47
- return response.data.tag_name.replace("v", "").trim();
48
- }
49
- async function downloadAppArchive(version, archiveFilename, outputPath) {
50
- const repo = "DefangLabs/defang";
51
- const downloadUrl = `https://github.com/${repo}/releases/download/v${version}/${archiveFilename}`;
52
- const downloadTargetFile = path.join(outputPath, archiveFilename);
53
- return await downloadFile(downloadUrl, downloadTargetFile);
54
- }
55
- async function downloadFile(downloadUrl, downloadTargetFile) {
56
- try {
57
- const response = await axios_1.default.get(downloadUrl, {
58
- responseType: "arraybuffer",
59
- headers: {
60
- "Content-Type": "application/octet-stream",
61
- },
62
- });
63
- if (response?.data === undefined) {
64
- throw new Error(`Failed to download ${downloadUrl}. No data in response.`);
65
- }
66
- await fs_1.promises.writeFile(downloadTargetFile, response.data);
67
- return downloadTargetFile;
68
- }
69
- catch (error) {
70
- console.error(error);
71
- await fs_1.promises.unlink(downloadTargetFile);
72
- return null;
73
- }
74
- }
75
- async function extractArchive(archiveFilePath, outputPath) {
76
- let result = false;
77
- const ext = path.extname(archiveFilePath).toLocaleLowerCase();
78
- switch (ext) {
79
- case ".zip":
80
- result = await extractZip(archiveFilePath, outputPath);
81
- break;
82
- case ".gz":
83
- result = extractTarGz(archiveFilePath, outputPath);
84
- break;
85
- default:
86
- throw new Error(`Unsupported archive extension: ${ext}`);
87
- }
88
- return result;
89
- }
90
- async function extractZip(zipPath, outputPath) {
91
- try {
92
- const zip = new adm_zip_1.default(zipPath);
93
- const result = zip.extractEntryTo(EXECUTABLE, outputPath, true, true);
94
- await fs_1.promises.chmod(path.join(outputPath, EXECUTABLE), 755);
95
- return result;
96
- }
97
- catch (error) {
98
- console.error(`An error occurred during zip extraction: ${error}`);
99
- return false;
100
- }
101
- }
102
- function extractTarGz(tarGzFilePath, outputPath) {
103
- try {
104
- tar.extract({
105
- cwd: outputPath,
106
- file: tarGzFilePath,
107
- sync: true,
108
- strict: true,
109
- }, [EXECUTABLE]);
110
- return true;
111
- }
112
- catch (error) {
113
- console.error(`An error occurred during tar.gz extraction: ${error}`);
114
- return false;
115
- }
116
- }
117
- async function deleteArchive(archiveFilePath) {
118
- await fs_1.promises.unlink(archiveFilePath);
119
- }
120
- async function getVersion(filename) {
121
- const data = await fs_1.promises.readFile(filename, "utf8");
122
- const pkg = JSON.parse(data);
123
- return pkg.version;
124
- }
125
- function getAppArchiveFilename(version, platform, arch) {
126
- let compression = "zip";
127
- switch (platform) {
128
- case "windows":
129
- platform = "windows";
130
- break;
131
- case "linux":
132
- platform = "linux";
133
- compression = "tar.gz";
134
- break;
135
- case "darwin":
136
- platform = "macOS";
137
- break;
138
- default:
139
- throw new Error(`Unsupported operating system: ${platform}`);
140
- }
141
- switch (arch) {
142
- case "x64":
143
- arch = "amd64";
144
- break;
145
- case "arm64":
146
- arch = "arm64";
147
- break;
148
- default:
149
- throw new Error(`Unsupported architecture: ${arch}`);
150
- }
151
- if (platform === "macOS") {
152
- return `defang_${version}_${platform}.${compression}`;
153
- }
154
- return `defang_${version}_${platform}_${arch}.${compression}`;
155
- }
156
- async function install(version, saveDirectory) {
157
- try {
158
- console.log(`Getting latest defang cli`);
159
- const filename = getAppArchiveFilename(version, os.platform(), os.arch());
160
- const archiveFile = await downloadAppArchive(version, filename, saveDirectory);
161
- if (archiveFile == null || archiveFile.length === 0) {
162
- throw new Error(`Failed to download ${filename}`);
163
- }
164
- const result = await extractArchive(archiveFile, saveDirectory);
165
- if (result === false) {
166
- throw new Error(`Failed to install binaries!`);
167
- }
168
- await deleteArchive(archiveFile);
169
- }
170
- catch (error) {
171
- console.error(error);
172
- }
173
- }
174
- function getPathToExecutable() {
175
- let extension = "";
176
- if (["win32", "cygwin"].includes(process.platform)) {
177
- extension = ".exe";
178
- }
179
- const executablePath = path.join(__dirname, `${EXECUTABLE}${extension}`);
180
- try {
181
- return require.resolve(executablePath);
182
- }
183
- catch (e) {
184
- return null;
185
- }
186
- }
187
- function extractCLIVersions(versionInfo) {
188
- const versionRegex = /\d+\.\d+\.\d+/g;
189
- const matches = versionInfo.match(versionRegex);
190
- if (matches != null && matches.length >= 2) {
191
- return {
192
- defangCLI: matches[0],
193
- latestCLI: matches[1],
194
- };
195
- }
196
- else {
197
- throw new Error("Could not extract CLI versions from the output.");
198
- }
199
- }
200
- async function getVersionInfo() {
201
- let result = { current: null, latest: null };
202
- try {
203
- const execPath = getPathToExecutable();
204
- if (!execPath) {
205
- const latestVersion = await getLatestVersion();
206
- return { current: null, latest: latestVersion };
207
- }
208
- const versionInfo = await exec(execPath + " version");
209
- const verInfo = extractCLIVersions(versionInfo.stdout);
210
- result.current = verInfo.defangCLI;
211
- result.latest = verInfo.latestCLI;
212
- }
213
- catch (error) {
214
- console.error(error);
215
- }
216
- return result;
217
- }
218
- function extractCLIWrapperArgs(args) {
219
- const cliParams = {
220
- uselatest: true,
221
- };
222
- const outArgs = [];
223
- for (const arg of args) {
224
- const argLower = arg.toLowerCase().replaceAll(" ", "");
225
- if (argLower.startsWith("--use-latest")) {
226
- const startOfValue = argLower.indexOf("=");
227
- if (startOfValue >= 0) {
228
- if (argLower.slice(startOfValue + 1) == "false") {
229
- cliParams.uselatest = false;
230
- }
231
- }
232
- }
233
- else {
234
- outArgs.push(arg);
235
- }
236
- }
237
- return { cliParams, outArgs };
238
- }
239
- async function run() {
240
- try {
241
- const { cliParams, outArgs: args } = extractCLIWrapperArgs(process.argv.slice(2));
242
- if (cliParams.uselatest) {
243
- const { current, latest } = await getVersionInfo();
244
- if (latest != null && (current == null || current != latest)) {
245
- await install(latest, __dirname);
246
- }
247
- }
248
- const pathToExec = getPathToExecutable();
249
- if (!pathToExec) {
250
- throw new Error("Could not find the defang executable.");
251
- }
252
- const processResult = child_process.spawnSync(pathToExec, args, {
253
- stdio: "inherit",
254
- });
255
- processResult.error && console.error(processResult.error);
256
- process.exitCode = processResult.status ?? 1;
257
- }
258
- catch (error) {
259
- console.error(error);
260
- process.exitCode = 2;
261
- }
262
- }
263
- run();
4
+ var clilib_1 = require("./clilib");
5
+ (0, clilib_1.run)();
package/bin/clilib.js ADDED
@@ -0,0 +1,444 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
37
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
38
+ return new (P || (P = Promise))(function (resolve, reject) {
39
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
40
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
41
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
42
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
43
+ });
44
+ };
45
+ var __generator = (this && this.__generator) || function (thisArg, body) {
46
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
47
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
48
+ function verb(n) { return function (v) { return step([n, v]); }; }
49
+ function step(op) {
50
+ if (f) throw new TypeError("Generator is already executing.");
51
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
52
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
53
+ if (y = 0, t) op = [op[0] & 2, t.value];
54
+ switch (op[0]) {
55
+ case 0: case 1: t = op; break;
56
+ case 4: _.label++; return { value: op[1], done: false };
57
+ case 5: _.label++; y = op[1]; op = [0]; continue;
58
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
59
+ default:
60
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
61
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
62
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
63
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
64
+ if (t[2]) _.ops.pop();
65
+ _.trys.pop(); continue;
66
+ }
67
+ op = body.call(thisArg, _);
68
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
69
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
70
+ }
71
+ };
72
+ var __importDefault = (this && this.__importDefault) || function (mod) {
73
+ return (mod && mod.__esModule) ? mod : { "default": mod };
74
+ };
75
+ Object.defineProperty(exports, "__esModule", { value: true });
76
+ exports.install = install;
77
+ exports.run = run;
78
+ var adm_zip_1 = __importDefault(require("adm-zip"));
79
+ var axios_1 = __importDefault(require("axios"));
80
+ var child_process = __importStar(require("child_process"));
81
+ var fs = __importStar(require("fs"));
82
+ var path = __importStar(require("path"));
83
+ var tar = __importStar(require("tar"));
84
+ var util_1 = require("util");
85
+ var SEMVER_REGEX = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
86
+ var EXECUTABLE = "defang";
87
+ var URL_LATEST_RELEASE = "https://api.github.com/repos/DefangLabs/defang/releases/latest";
88
+ var HTTP_STATUS_OK = 200;
89
+ var exec = (0, util_1.promisify)(child_process.exec);
90
+ function getLatestVersion() {
91
+ return __awaiter(this, void 0, void 0, function () {
92
+ var response;
93
+ var _a, _b;
94
+ return __generator(this, function (_c) {
95
+ switch (_c.label) {
96
+ case 0: return [4, axios_1.default.get(URL_LATEST_RELEASE)];
97
+ case 1:
98
+ response = _c.sent();
99
+ if ((response === null || response === void 0 ? void 0 : response.status) !== HTTP_STATUS_OK) {
100
+ throw new Error("Failed to get latest version from GitHub. Status code: ".concat(response.status));
101
+ }
102
+ return [2, (_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a.tag_name) === null || _b === void 0 ? void 0 : _b.replace("v", "").trim()];
103
+ }
104
+ });
105
+ });
106
+ }
107
+ function downloadAppArchive(version, archiveFilename, outputPath) {
108
+ return __awaiter(this, void 0, void 0, function () {
109
+ var repo, downloadUrl, downloadTargetFile;
110
+ return __generator(this, function (_a) {
111
+ switch (_a.label) {
112
+ case 0:
113
+ repo = "DefangLabs/defang";
114
+ downloadUrl = "https://github.com/".concat(repo, "/releases/download/v").concat(version, "/").concat(archiveFilename);
115
+ downloadTargetFile = path.join(outputPath, archiveFilename);
116
+ return [4, downloadFile(downloadUrl, downloadTargetFile)];
117
+ case 1: return [2, _a.sent()];
118
+ }
119
+ });
120
+ });
121
+ }
122
+ function downloadFile(downloadUrl, downloadTargetFile) {
123
+ return __awaiter(this, void 0, void 0, function () {
124
+ var response, error_1;
125
+ return __generator(this, function (_a) {
126
+ switch (_a.label) {
127
+ case 0:
128
+ _a.trys.push([0, 3, , 5]);
129
+ return [4, axios_1.default.get(downloadUrl, {
130
+ responseType: "arraybuffer",
131
+ headers: {
132
+ "Content-Type": "application/octet-stream",
133
+ },
134
+ })];
135
+ case 1:
136
+ response = _a.sent();
137
+ if ((response === null || response === void 0 ? void 0 : response.data) === undefined) {
138
+ throw new Error("Failed to download ".concat(downloadUrl, ". No data in response."));
139
+ }
140
+ return [4, fs.promises.writeFile(downloadTargetFile, response.data)];
141
+ case 2:
142
+ _a.sent();
143
+ return [2, downloadTargetFile];
144
+ case 3:
145
+ error_1 = _a.sent();
146
+ console.error(error_1);
147
+ return [4, fs.promises.unlink(downloadTargetFile)];
148
+ case 4:
149
+ _a.sent();
150
+ return [2, null];
151
+ case 5: return [2];
152
+ }
153
+ });
154
+ });
155
+ }
156
+ function extractArchive(archiveFilePath, outputPath) {
157
+ return __awaiter(this, void 0, void 0, function () {
158
+ var result, ext, _a;
159
+ return __generator(this, function (_b) {
160
+ switch (_b.label) {
161
+ case 0:
162
+ result = false;
163
+ ext = path.extname(archiveFilePath).toLocaleLowerCase();
164
+ _a = ext;
165
+ switch (_a) {
166
+ case ".zip": return [3, 1];
167
+ case ".gz": return [3, 3];
168
+ }
169
+ return [3, 4];
170
+ case 1: return [4, extractZip(archiveFilePath, outputPath)];
171
+ case 2:
172
+ result = _b.sent();
173
+ return [3, 5];
174
+ case 3:
175
+ result = extractTarGz(archiveFilePath, outputPath);
176
+ return [3, 5];
177
+ case 4: return [2, false];
178
+ case 5: return [2, result];
179
+ }
180
+ });
181
+ });
182
+ }
183
+ function extractZip(zipPath, outputPath) {
184
+ return __awaiter(this, void 0, void 0, function () {
185
+ var zip, result, error_2;
186
+ return __generator(this, function (_a) {
187
+ switch (_a.label) {
188
+ case 0:
189
+ _a.trys.push([0, 2, , 3]);
190
+ zip = new adm_zip_1.default(zipPath);
191
+ result = zip.extractEntryTo(EXECUTABLE, outputPath, true, true);
192
+ return [4, fs.promises.chmod(path.join(outputPath, EXECUTABLE), 755)];
193
+ case 1:
194
+ _a.sent();
195
+ return [2, result];
196
+ case 2:
197
+ error_2 = _a.sent();
198
+ console.error("An error occurred during zip extraction: ".concat(error_2));
199
+ return [2, false];
200
+ case 3: return [2];
201
+ }
202
+ });
203
+ });
204
+ }
205
+ function extractTarGz(tarGzFilePath, outputPath) {
206
+ try {
207
+ tar.extract({
208
+ cwd: outputPath,
209
+ file: tarGzFilePath,
210
+ sync: true,
211
+ strict: true,
212
+ }, [EXECUTABLE]);
213
+ return true;
214
+ }
215
+ catch (error) {
216
+ console.error("An error occurred during tar.gz extraction: ".concat(error));
217
+ return false;
218
+ }
219
+ }
220
+ function deleteArchive(archiveFilePath) {
221
+ return __awaiter(this, void 0, void 0, function () {
222
+ return __generator(this, function (_a) {
223
+ switch (_a.label) {
224
+ case 0: return [4, fs.promises.unlink(archiveFilePath)];
225
+ case 1:
226
+ _a.sent();
227
+ return [2];
228
+ }
229
+ });
230
+ });
231
+ }
232
+ function getAppArchiveFilename(version, platform, arch) {
233
+ var compression = "zip";
234
+ if (!SEMVER_REGEX.test(version)) {
235
+ throw new Error("Unsupported version: ".concat(version));
236
+ }
237
+ switch (platform) {
238
+ case "win32":
239
+ case "windows":
240
+ platform = "windows";
241
+ break;
242
+ case "linux":
243
+ platform = "linux";
244
+ compression = "tar.gz";
245
+ break;
246
+ case "darwin":
247
+ platform = "macOS";
248
+ break;
249
+ default:
250
+ throw new Error("Unsupported operating system: ".concat(platform));
251
+ }
252
+ switch (arch) {
253
+ case "x64":
254
+ arch = "amd64";
255
+ break;
256
+ case "arm64":
257
+ arch = "arm64";
258
+ break;
259
+ default:
260
+ throw new Error("Unsupported architecture: ".concat(arch));
261
+ }
262
+ if (platform === "macOS") {
263
+ return "defang_".concat(version, "_").concat(platform, ".").concat(compression);
264
+ }
265
+ return "defang_".concat(version, "_").concat(platform, "_").concat(arch, ".").concat(compression);
266
+ }
267
+ function getPathToExecutable() {
268
+ var extension = "";
269
+ if (["win32", "cygwin"].includes(process.platform)) {
270
+ extension = ".exe";
271
+ }
272
+ var executablePath = path.join(__dirname, "".concat(EXECUTABLE).concat(extension));
273
+ try {
274
+ return require.resolve(executablePath);
275
+ }
276
+ catch (e) {
277
+ return null;
278
+ }
279
+ }
280
+ function extractCLIVersions(versionInfo) {
281
+ var versionRegex = /\d+\.\d+\.\d+/g;
282
+ var matches = versionInfo.match(versionRegex);
283
+ if (matches != null && matches.length >= 2) {
284
+ return {
285
+ defangCLI: matches[0],
286
+ latestCLI: matches[1],
287
+ };
288
+ }
289
+ else {
290
+ throw new Error("Could not extract CLI versions from the output.");
291
+ }
292
+ }
293
+ function getVersionInfo() {
294
+ return __awaiter(this, void 0, void 0, function () {
295
+ var result, execPath, latestVersion, versionInfo, verInfo, error_3;
296
+ return __generator(this, function (_a) {
297
+ switch (_a.label) {
298
+ case 0:
299
+ result = { current: null, latest: null };
300
+ _a.label = 1;
301
+ case 1:
302
+ _a.trys.push([1, 5, , 6]);
303
+ execPath = getPathToExecutable();
304
+ if (!!execPath) return [3, 3];
305
+ return [4, getLatestVersion()];
306
+ case 2:
307
+ latestVersion = _a.sent();
308
+ return [2, { current: null, latest: latestVersion }];
309
+ case 3: return [4, exec(execPath + " version")];
310
+ case 4:
311
+ versionInfo = _a.sent();
312
+ verInfo = extractCLIVersions(versionInfo.stdout);
313
+ result.current = verInfo.defangCLI;
314
+ result.latest = verInfo.latestCLI;
315
+ return [3, 6];
316
+ case 5:
317
+ error_3 = _a.sent();
318
+ console.error(error_3);
319
+ return [3, 6];
320
+ case 6: return [2, result];
321
+ }
322
+ });
323
+ });
324
+ }
325
+ function extractCLIWrapperArgs(args) {
326
+ var cliParams = {
327
+ uselatest: true,
328
+ };
329
+ var outArgs = [];
330
+ for (var _i = 0, args_1 = args; _i < args_1.length; _i++) {
331
+ var arg = args_1[_i];
332
+ var argLower = arg.toLowerCase().replaceAll(" ", "");
333
+ if (argLower.startsWith("--use-latest")) {
334
+ var startOfValue = argLower.indexOf("=");
335
+ if (startOfValue >= 0) {
336
+ if (argLower.slice(startOfValue + 1) == "false") {
337
+ cliParams.uselatest = false;
338
+ }
339
+ }
340
+ }
341
+ else {
342
+ outArgs.push(arg);
343
+ }
344
+ }
345
+ return { cliParams: cliParams, outArgs: outArgs };
346
+ }
347
+ function getEndNameFromPath(pathLine) {
348
+ var executableName = path.basename(pathLine);
349
+ return executableName.split(".")[0];
350
+ }
351
+ function install(version, saveDirectory, os) {
352
+ return __awaiter(this, void 0, void 0, function () {
353
+ var filename, archiveFile, result, error_4;
354
+ return __generator(this, function (_a) {
355
+ switch (_a.label) {
356
+ case 0:
357
+ _a.trys.push([0, 4, , 5]);
358
+ console.log("Getting latest defang cli");
359
+ filename = getAppArchiveFilename(version, os.platform, os.arch);
360
+ return [4, downloadAppArchive(version, filename, saveDirectory)];
361
+ case 1:
362
+ archiveFile = _a.sent();
363
+ if (archiveFile == null || archiveFile.length === 0) {
364
+ throw new Error("Failed to download ".concat(filename));
365
+ }
366
+ return [4, extractArchive(archiveFile, saveDirectory)];
367
+ case 2:
368
+ result = _a.sent();
369
+ if (result === false) {
370
+ throw new Error("Failed to install binaries!");
371
+ }
372
+ return [4, deleteArchive(archiveFile)];
373
+ case 3:
374
+ _a.sent();
375
+ return [3, 5];
376
+ case 4:
377
+ error_4 = _a.sent();
378
+ console.error(error_4);
379
+ throw error_4;
380
+ case 5: return [2];
381
+ }
382
+ });
383
+ });
384
+ }
385
+ function run() {
386
+ return __awaiter(this, void 0, void 0, function () {
387
+ var _a, cliParams, args, _b, current, latest, pathToExec, commandline, processResult, error_5;
388
+ var _c;
389
+ return __generator(this, function (_d) {
390
+ switch (_d.label) {
391
+ case 0:
392
+ _d.trys.push([0, 4, , 5]);
393
+ _a = extractCLIWrapperArgs(process.argv.slice(2)), cliParams = _a.cliParams, args = _a.outArgs;
394
+ if (!cliParams.uselatest) return [3, 3];
395
+ return [4, getVersionInfo()];
396
+ case 1:
397
+ _b = _d.sent(), current = _b.current, latest = _b.latest;
398
+ if (!(latest != null && (current == null || current != latest))) return [3, 3];
399
+ return [4, install(latest, __dirname, {
400
+ platform: process.platform,
401
+ arch: process.arch,
402
+ })];
403
+ case 2:
404
+ _d.sent();
405
+ _d.label = 3;
406
+ case 3:
407
+ pathToExec = getPathToExecutable();
408
+ if (!pathToExec) {
409
+ throw new Error("Could not find the defang executable.");
410
+ }
411
+ commandline = ["npx", getEndNameFromPath(pathToExec)]
412
+ .join(" ")
413
+ .trim();
414
+ processResult = child_process.spawnSync(pathToExec, args, {
415
+ stdio: "inherit",
416
+ env: __assign(__assign({}, process.env), { DEFANG_COMMAND_EXECUTOR: commandline }),
417
+ });
418
+ processResult.error && console.error(processResult.error);
419
+ process.exitCode = (_c = processResult.status) !== null && _c !== void 0 ? _c : 1;
420
+ return [3, 5];
421
+ case 4:
422
+ error_5 = _d.sent();
423
+ console.error(error_5);
424
+ process.exitCode = 2;
425
+ return [3, 5];
426
+ case 5: return [2];
427
+ }
428
+ });
429
+ });
430
+ }
431
+ var clilib = {
432
+ deleteArchive: deleteArchive,
433
+ downloadAppArchive: downloadAppArchive,
434
+ downloadFile: downloadFile,
435
+ extractArchive: extractArchive,
436
+ extractCLIVersions: extractCLIVersions,
437
+ extractCLIWrapperArgs: extractCLIWrapperArgs,
438
+ getAppArchiveFilename: getAppArchiveFilename,
439
+ getEndNameFromPath: getEndNameFromPath,
440
+ getLatestVersion: getLatestVersion,
441
+ getVersionInfo: getVersionInfo,
442
+ getPathToExecutable: getPathToExecutable,
443
+ };
444
+ exports.default = clilib;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "defang",
3
- "version": "0.5.28",
3
+ "version": "0.5.36",
4
4
  "author": "Defang Software Labs Inc.",
5
5
  "description": "CLI for the Defang Opinionated Platform",
6
6
  "license": "MIT",
@@ -16,21 +16,53 @@
16
16
  "bugs": {
17
17
  "url": "https://github.com/DefangLabs/defang/issues"
18
18
  },
19
+ "mocha": {
20
+ "require": [
21
+ "ts-node/register"
22
+ ],
23
+ "extension": [
24
+ "ts",
25
+ "js"
26
+ ],
27
+ "spec": [
28
+ "test/**/*.spec.ts"
29
+ ],
30
+ "recursive": true
31
+ },
19
32
  "scripts": {
20
- "build": "tsc"
33
+ "build": "tsc",
34
+ "test": "TS_NODE_PROJECT='./tsconfig-test.json' mocha -r ts-node/register --loader=ts-node/esm -trace-warnings --no-warnings=ExperimentalWarning test/*.spec.ts"
21
35
  },
22
36
  "dependencies": {
23
37
  "adm-zip": "^0.5.14",
24
- "axios": "^1.6.8",
38
+ "axios": "^1.7.2",
39
+ "babel-register-esm": "^1.2.5",
25
40
  "https": "^1.0.0",
26
41
  "os": "^0.1.2",
27
42
  "tar": "^7.0.1"
28
43
  },
29
44
  "devDependencies": {
45
+ "@babel/core": "^7.24.7",
46
+ "@babel/preset-env": "^7.24.7",
47
+ "@babel/preset-typescript": "^7.24.7",
30
48
  "@types/adm-zip": "^0.5.5",
49
+ "@types/axios": "^0.14.0",
50
+ "@types/chai": "^4.3.16",
51
+ "@types/chai-as-promised": "^7.1.8",
52
+ "@types/mocha": "^10.0.7",
31
53
  "@types/node": "^20.12.7",
54
+ "@types/sinon": "^17.0.3",
55
+ "@types/source-map-support": "^0.5.10",
32
56
  "@types/tar": "^6.1.13",
33
- "typescript": "^5.4.5"
57
+ "chai": "^5.1.1",
58
+ "chai-as-promised": "^8.0.0",
59
+ "cross-env": "^7.0.3",
60
+ "mocha": "^10.6.0",
61
+ "nyc": "^17.0.0",
62
+ "sinon": "^18.0.0",
63
+ "source-map-support": "^0.5.21",
64
+ "ts-node": "^10.9.2",
65
+ "typescript": "^5.5.3"
34
66
  },
35
67
  "main": "./bin/cli.js"
36
68
  }
@@ -0,0 +1,214 @@
1
+ //********************************
2
+ // To run these tests do the following:
3
+ // 1. In package.json set/add the field "type": "module"
4
+ // 2. Run `npm test`
5
+ //********************************
6
+ import * as mocha from "mocha";
7
+ import * as sinon from "sinon";
8
+ import * as chai from "chai";
9
+ import chaiAsPromised from "chai-as-promised";
10
+
11
+ import axios, { AxiosResponse } from "axios";
12
+ import fs from "fs";
13
+ import clilib from "../src/clilib.ts";
14
+
15
+ chai.use(chaiAsPromised);
16
+ const { assert, expect } = chai;
17
+
18
+ var sandbox: sinon.SinonSandbox;
19
+
20
+ describe("Testing getLatestVersion()", () => {
21
+ beforeEach(() => {
22
+ sandbox = sinon.createSandbox();
23
+ });
24
+
25
+ afterEach(() => {
26
+ sandbox.restore();
27
+ });
28
+
29
+ it("sanity", async () => {
30
+ const mockResponse: AxiosResponse = {
31
+ status: 200,
32
+ data: {
33
+ tag_name: "v0.5.32",
34
+ },
35
+ } as AxiosResponse;
36
+ sandbox.stub(axios, "get").returns(Promise.resolve(mockResponse));
37
+
38
+ await expect(clilib.getLatestVersion()).to.eventually.equal("0.5.32");
39
+ });
40
+
41
+ it("bad HTTP Status", async () => {
42
+ const mockResponse: AxiosResponse = {
43
+ status: 500,
44
+ } as AxiosResponse;
45
+ sandbox.stub(axios, "get").returns(Promise.reject(mockResponse));
46
+
47
+ await expect(clilib.getLatestVersion()).to.be.rejected;
48
+ });
49
+
50
+ it("empty tag_name", async () => {
51
+ const mockResponse: AxiosResponse = {
52
+ status: 200,
53
+ data: {
54
+ tag_name: "",
55
+ },
56
+ } as AxiosResponse;
57
+ sandbox.stub(axios, "get").returns(Promise.resolve(mockResponse));
58
+
59
+ await expect(clilib.getLatestVersion()).to.eventually.equal("");
60
+ });
61
+
62
+ it("ill-formed tag_name", async () => {
63
+ const mockResponse: AxiosResponse = {
64
+ status: 200,
65
+ data: {},
66
+ } as AxiosResponse;
67
+ sandbox.stub(axios, "get").returns(Promise.resolve(mockResponse));
68
+
69
+ await expect(clilib.getLatestVersion()).to.eventually.be.undefined;
70
+ });
71
+ });
72
+
73
+ describe("Testing downloadFile()", () => {
74
+ let axiosGetStub: sinon.SinonStub;
75
+ let writeStub: sinon.SinonStub;
76
+ let unlinkStub: sinon.SinonStub;
77
+
78
+ let downloadFileName = "target";
79
+ const url = "url";
80
+ const header = {
81
+ responseType: "arraybuffer",
82
+ headers: {
83
+ "Content-Type": "application/octet-stream",
84
+ },
85
+ };
86
+ let mockResponse: AxiosResponse;
87
+
88
+ beforeEach(() => {
89
+ sandbox = sinon.createSandbox();
90
+
91
+ downloadFileName = "target";
92
+ mockResponse = {
93
+ status: 200,
94
+ data: {},
95
+ } as AxiosResponse;
96
+
97
+ axiosGetStub = sandbox
98
+ .stub(axios, "get")
99
+ .returns(Promise.resolve(mockResponse));
100
+ writeStub = sandbox
101
+ .stub(fs.promises, "writeFile")
102
+ .callsFake(() => Promise.resolve());
103
+ unlinkStub = sandbox
104
+ .stub(fs.promises, "unlink")
105
+ .callsFake(() => Promise.resolve());
106
+ });
107
+
108
+ afterEach(() => {
109
+ sandbox.restore();
110
+ });
111
+
112
+ it("sanity", async () => {
113
+ await expect(
114
+ clilib.downloadFile(url, downloadFileName)
115
+ ).to.eventually.equal(downloadFileName);
116
+
117
+ sinon.assert.calledWith(axiosGetStub, url, header);
118
+ sinon.assert.calledWith(writeStub, downloadFileName, mockResponse.data);
119
+ sinon.assert.notCalled(unlinkStub);
120
+ });
121
+
122
+ it("download fails path", async () => {
123
+ axiosGetStub.returns(Promise.reject("failed"));
124
+ const targetFile = await clilib.downloadFile(url, downloadFileName);
125
+ await expect(
126
+ clilib.downloadFile(url, downloadFileName)
127
+ ).to.eventually.equal(targetFile);
128
+
129
+ sinon.assert.calledWith(axiosGetStub, url, header);
130
+ sinon.assert.notCalled(writeStub);
131
+ sinon.assert.calledWith(unlinkStub, downloadFileName);
132
+ });
133
+
134
+ it("write failed", async () => {
135
+ writeStub.returns(Promise.reject("failed"));
136
+ await expect(clilib.downloadFile(url, downloadFileName)).to.eventually.null;
137
+ sinon.assert.calledWith(axiosGetStub, url, header);
138
+ sinon.assert.calledWith(unlinkStub, downloadFileName);
139
+ });
140
+ });
141
+
142
+ describe("Testing getAppArchiveFilename()", () => {
143
+ it("returns expected filename", () => {
144
+ const iterationData = [
145
+ ["0.0.0", "win32", "x64", "defang_0.0.0_windows_amd64.zip"],
146
+ ["0.1.0", "windows", "x64", "defang_0.1.0_windows_amd64.zip"],
147
+ ["0.2.9", "windows", "arm64", "defang_0.2.9_windows_arm64.zip"],
148
+ ["0.3.10", "linux", "x64", "defang_0.3.10_linux_amd64.tar.gz"],
149
+ ["0.4.21", "linux", "arm64", "defang_0.4.21_linux_arm64.tar.gz"],
150
+ ["0.5.45", "darwin", "arm64", "defang_0.5.45_macOS.zip"],
151
+ ["0.5.45", "darwin", "x64", "defang_0.5.45_macOS.zip"],
152
+ ] as const;
153
+ const testFunc = (
154
+ version: string,
155
+ platform: string,
156
+ arch: string,
157
+ expected: string
158
+ ) =>
159
+ expect(clilib.getAppArchiveFilename(version, platform, arch)).to.be.equal(
160
+ expected
161
+ );
162
+ iterationData.forEach((testData) => testFunc.call(null, ...testData));
163
+ });
164
+
165
+ it("check error cases", () => {
166
+ const iterationData = [
167
+ ["", "windows", "x64"],
168
+ ["0.2.9", "windows", "risc64"],
169
+ ["0.5.45", "darwin", "powerpc"],
170
+ ] as const;
171
+ const testFunc = (version: string, platform: string, arch: string) =>
172
+ expect(() =>
173
+ clilib.getAppArchiveFilename(version, platform, arch)
174
+ ).to.throw();
175
+ iterationData.forEach((testData) => testFunc.call(null, ...testData));
176
+ });
177
+ });
178
+
179
+ describe("Testing extractCLIVersions()", () => {
180
+ it("sanity", async () => {
181
+ const versionInfo =
182
+ "Defang CLI: v0.5.24\nLatest CLI: v0.5.32\nDefang Fabric: v0.5.0-643";
183
+ const expected = { defangCLI: "0.5.24", latestCLI: "0.5.32" };
184
+
185
+ expect(clilib.extractCLIVersions(versionInfo)).to.be.deep.equal(expected);
186
+ });
187
+
188
+ it("missing v in version text", async () => {
189
+ const versionInfo =
190
+ "Defang CLI: 0.5.24\nLatest CLI: 0.5.32\nDefang Fabric: v0.5.0-643";
191
+ const expected = { defangCLI: "0.5.24", latestCLI: "0.5.32" };
192
+
193
+ expect(clilib.extractCLIVersions(versionInfo)).to.be.deep.equal(expected);
194
+ });
195
+
196
+ it("missing Defang CLI", () => {
197
+ const versionInfo =
198
+ "Defang CLI: \nLatest CLI: v0.5.32\nDefang Fabric: v0.5.0-643";
199
+ assert.doesNotThrow(() => clilib.extractCLIVersions(versionInfo));
200
+ });
201
+
202
+ it("missing Latest CLI", () => {
203
+ const versionInfo =
204
+ "Defang CLI: v0.5.24\nLatest CLI: \nDefang Fabric: v0.5.0-643";
205
+ assert.doesNotThrow(() => clilib.extractCLIVersions(versionInfo));
206
+ });
207
+
208
+ it("no fabric version in input", async () => {
209
+ const versionInfo = "Defang CLI: v0.5.24\nLatest CLI: v0.5.32\n";
210
+ const expected = { defangCLI: "0.5.24", latestCLI: "0.5.32" };
211
+
212
+ expect(clilib.extractCLIVersions(versionInfo)).to.be.deep.equal(expected);
213
+ });
214
+ });
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": "./",
4
+ "outDir": "bin",
5
+ "removeComments": true,
6
+ "target": "ES6",
7
+ "module": "ESNext",
8
+ "moduleResolution": "node",
9
+ "experimentalDecorators": true,
10
+ "strictPropertyInitialization": false,
11
+ "allowSyntheticDefaultImports":true,
12
+ "allowImportingTsExtensions": true,
13
+ "lib": ["es2021"],
14
+ "noEmit": true,
15
+ "isolatedModules": false,
16
+ "strict": false,
17
+ "noImplicitAny": false,
18
+ "typeRoots" : [
19
+ "../node_modules/@types"
20
+ ]
21
+ },
22
+ "include": ["src/**/*.ts", "test/cliexec.spec.ts"],
23
+ "exclude": ["node_modules", "**/*.spec.ts"]
24
+ }