@heetmehta18/autodev 0.2.0 → 0.3.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.
- package/README.md +80 -22
- package/bin/index.js +138 -76
- package/package.json +6 -6
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -1,33 +1,91 @@
|
|
|
1
|
-
# AutoDev
|
|
1
|
+
# AutoDev CLI — The App Store for Developers ⚡
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@heetmehta18/autodev)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
**AutoDev** is an open-source, cross-platform developer environment bootstrapper. It acts as an **App Store for Developers**, simplifying complex toolchain setups through intelligent, profile-based automation.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 🚀 Key Features
|
|
11
|
+
|
|
12
|
+
* 🔍 **Polyglot Codebase Scanner**: Detects 30+ languages, frameworks, package managers, and DevOps infrastructure.
|
|
13
|
+
* 🛡️ **Supply-Chain Security Audits**: Queries the OSV (Open Source Vulnerabilities) database to find safety risks in your dependencies.
|
|
14
|
+
* ⚙️ **Ballast Installer**: Automatically extracts and links compilers/runtimes (like Node.js, Go, Python, Rust) to your path.
|
|
15
|
+
* 📦 **Monorepo / Multi-project Scanner**: Groups and maps nested modules dynamically within a monorepo structure.
|
|
16
|
+
* 🛰️ **Cloud IDE Scaffolding**: Runs `autodev containerize` to generate `.devcontainer.json` environment setups.
|
|
17
|
+
* 🔄 **Config Migrator**: Seamlessly upgrades legacy profile configurations to standard YAML.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 📦 Installation & Quick Start
|
|
22
|
+
|
|
23
|
+
You can run AutoDev on the fly using Node's package executor, or install it globally.
|
|
24
|
+
|
|
25
|
+
### 1. Run Instantly (No Installation)
|
|
26
|
+
Scan your workspace and bootstrap dependencies without installing anything permanently:
|
|
8
27
|
```bash
|
|
9
|
-
npx @heetmehta18/autodev
|
|
28
|
+
npx @heetmehta18/autodev setup
|
|
10
29
|
```
|
|
11
30
|
|
|
12
|
-
### Install Globally
|
|
31
|
+
### 2. Install Globally
|
|
32
|
+
Install the package globally for instant local terminal access:
|
|
13
33
|
```bash
|
|
14
34
|
npm install -g @heetmehta18/autodev
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 3. Usage
|
|
38
|
+
Verify that the CLI is installed and ready:
|
|
39
|
+
```bash
|
|
15
40
|
autodev --help
|
|
16
41
|
```
|
|
17
42
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 🛠️ Main CLI Commands
|
|
46
|
+
|
|
47
|
+
### 🔍 scan
|
|
48
|
+
Analyzes your current working directory for configuration markers, lockfiles, and monorepo folders:
|
|
49
|
+
```bash
|
|
50
|
+
autodev scan
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 📦 setup
|
|
54
|
+
Scans the project and aligns your local development environment by downloading missing runtimes:
|
|
55
|
+
```bash
|
|
56
|
+
autodev setup
|
|
57
|
+
autodev setup --yes # Skip confirmation prompts
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 🩺 doctor
|
|
61
|
+
Inspects your system configuration, checks tool versions against the `.autodev.lock.json` lockfile, and scans for exposed secrets (like AWS keys or GitHub tokens):
|
|
62
|
+
```bash
|
|
63
|
+
autodev doctor
|
|
64
|
+
autodev doctor --fix # Restore lockfile mismatches automatically
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 🛡️ audit
|
|
68
|
+
Scans lockfiles and dependencies for known supply-chain vulnerabilities using the OSV database:
|
|
69
|
+
```bash
|
|
70
|
+
autodev audit
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 💻 containerize
|
|
74
|
+
Generates dev container setup configurations (`.devcontainer.json`) and VSCode plugin recommendations based on the detected stack:
|
|
75
|
+
```bash
|
|
76
|
+
autodev containerize
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 🔄 migrate
|
|
80
|
+
Upgrades legacy `.json` profile configs to the standard `.autodev.yaml` schema:
|
|
81
|
+
```bash
|
|
82
|
+
autodev migrate
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## ⚙️ How It Works
|
|
88
|
+
|
|
89
|
+
1. **Platform Detection**: The wrapper maps your OS and CPU architecture (e.g. `linux/amd64`, `darwin/arm64`) to the corresponding compiled Go binary.
|
|
90
|
+
2. **Binary Caching**: Downloads the pre-compiled binary directly from the matching GitHub Release tag. Subsequent executions are run instantly from cache.
|
|
91
|
+
3. **Process Delegation**: Delegated execution forwards all streams, signals, exit codes, and Model Context Protocol (MCP) servers seamlessly.
|
package/bin/index.js
CHANGED
|
@@ -1,66 +1,71 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
const path = require(
|
|
5
|
-
const fs = require(
|
|
6
|
-
const os = require(
|
|
7
|
-
const https = require(
|
|
8
|
-
const http = require(
|
|
3
|
+
const { spawn, execSync, execFileSync } = require("child_process");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
const https = require("https");
|
|
8
|
+
const http = require("http");
|
|
9
9
|
|
|
10
10
|
// Determine OS & Arch mapping
|
|
11
11
|
const platformMap = {
|
|
12
|
-
darwin:
|
|
13
|
-
linux:
|
|
14
|
-
win32:
|
|
12
|
+
darwin: "darwin",
|
|
13
|
+
linux: "linux",
|
|
14
|
+
win32: "windows",
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
const archMap = {
|
|
18
|
-
x64:
|
|
19
|
-
arm64:
|
|
18
|
+
x64: "amd64",
|
|
19
|
+
arm64: "arm64",
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
const platform = platformMap[process.platform];
|
|
23
23
|
const arch = archMap[process.arch];
|
|
24
24
|
|
|
25
25
|
if (!platform || !arch) {
|
|
26
|
-
console.error(
|
|
26
|
+
console.error(
|
|
27
|
+
`[autodev] Unsupported platform/architecture: ${process.platform}/${process.arch}`,
|
|
28
|
+
);
|
|
27
29
|
process.exit(1);
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
const ext = platform ===
|
|
31
|
-
const binaryName = platform ===
|
|
32
|
+
const ext = platform === "windows" ? "zip" : "tar.gz";
|
|
33
|
+
const binaryName = platform === "windows" ? "autodev.exe" : "autodev";
|
|
32
34
|
|
|
33
35
|
// Version: prefer the latest GitHub release tag; fall back to package.json
|
|
34
|
-
const pkgJson = require(
|
|
36
|
+
const pkgJson = require("../package.json");
|
|
35
37
|
const fallbackVersion = `v${pkgJson.version}`;
|
|
36
38
|
|
|
37
39
|
function getLatestReleaseTag() {
|
|
38
40
|
return new Promise((resolve) => {
|
|
39
41
|
const options = {
|
|
40
|
-
hostname:
|
|
41
|
-
path:
|
|
42
|
+
hostname: "api.github.com",
|
|
43
|
+
path: "/repos/HEETMEHTA18/autodev/releases/latest",
|
|
42
44
|
headers: {
|
|
43
|
-
|
|
45
|
+
"User-Agent": "autodev-npm-cli",
|
|
44
46
|
},
|
|
45
|
-
timeout: 5000
|
|
47
|
+
timeout: 5000,
|
|
46
48
|
};
|
|
47
49
|
|
|
48
|
-
https
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
https
|
|
51
|
+
.get(options, (res) => {
|
|
52
|
+
let body = "";
|
|
53
|
+
res.on("data", (chunk) => (body += chunk));
|
|
54
|
+
res.on("end", () => {
|
|
55
|
+
try {
|
|
56
|
+
const json = JSON.parse(body);
|
|
57
|
+
const versionRegex = /^v?\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/;
|
|
58
|
+
if (json.tag_name && versionRegex.test(json.tag_name)) {
|
|
59
|
+
resolve(json.tag_name);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
} catch (_) {}
|
|
63
|
+
resolve(fallbackVersion);
|
|
64
|
+
});
|
|
65
|
+
})
|
|
66
|
+
.on("error", () => {
|
|
59
67
|
resolve(fallbackVersion);
|
|
60
68
|
});
|
|
61
|
-
}).on('error', () => {
|
|
62
|
-
resolve(fallbackVersion);
|
|
63
|
-
});
|
|
64
69
|
});
|
|
65
70
|
}
|
|
66
71
|
|
|
@@ -70,9 +75,9 @@ const binaryPath = path.join(binDir, binaryName);
|
|
|
70
75
|
|
|
71
76
|
// Development fallback paths
|
|
72
77
|
const devPaths = [
|
|
73
|
-
path.join(__dirname,
|
|
74
|
-
path.join(__dirname,
|
|
75
|
-
path.join(__dirname,
|
|
78
|
+
path.join(__dirname, "..", "..", "cli", "bin", binaryName),
|
|
79
|
+
path.join(__dirname, "..", "..", "..", "bin", binaryName),
|
|
80
|
+
path.join(__dirname, "..", "..", "..", "packages", "cli", "bin", binaryName),
|
|
76
81
|
];
|
|
77
82
|
|
|
78
83
|
let activeBinaryPath = binaryPath;
|
|
@@ -92,35 +97,43 @@ for (const devPath of devPaths) {
|
|
|
92
97
|
*/
|
|
93
98
|
function download(url, destPath, maxRedirects = 5) {
|
|
94
99
|
return new Promise((resolve, reject) => {
|
|
95
|
-
if (maxRedirects <= 0) return reject(new Error(
|
|
96
|
-
|
|
97
|
-
const client = url.startsWith('https') ? https : http;
|
|
98
|
-
client.get(url, (res) => {
|
|
99
|
-
// Follow redirects (GitHub releases return 302)
|
|
100
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
101
|
-
return download(res.headers.location, destPath, maxRedirects - 1)
|
|
102
|
-
.then(resolve)
|
|
103
|
-
.catch(reject);
|
|
104
|
-
}
|
|
100
|
+
if (maxRedirects <= 0) return reject(new Error("Too many redirects"));
|
|
105
101
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
102
|
+
const client = url.startsWith("https") ? https : http;
|
|
103
|
+
client
|
|
104
|
+
.get(url, (res) => {
|
|
105
|
+
// Follow redirects (GitHub releases return 302)
|
|
106
|
+
if (
|
|
107
|
+
res.statusCode >= 300 &&
|
|
108
|
+
res.statusCode < 400 &&
|
|
109
|
+
res.headers.location
|
|
110
|
+
) {
|
|
111
|
+
return download(res.headers.location, destPath, maxRedirects - 1)
|
|
112
|
+
.then(resolve)
|
|
113
|
+
.catch(reject);
|
|
114
|
+
}
|
|
109
115
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
if (res.statusCode !== 200) {
|
|
117
|
+
return reject(new Error(`HTTP ${res.statusCode} from ${url}`));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const fileStream = fs.createWriteStream(destPath);
|
|
121
|
+
res.pipe(fileStream);
|
|
122
|
+
fileStream.on("finish", () => {
|
|
123
|
+
fileStream.close();
|
|
124
|
+
resolve();
|
|
125
|
+
});
|
|
126
|
+
fileStream.on("error", reject);
|
|
127
|
+
})
|
|
128
|
+
.on("error", reject);
|
|
118
129
|
});
|
|
119
130
|
}
|
|
120
131
|
|
|
121
132
|
async function downloadBinary() {
|
|
122
133
|
let version = await getLatestReleaseTag();
|
|
123
|
-
console.log(
|
|
134
|
+
console.log(
|
|
135
|
+
`\n[autodev] Native binary not found. Downloading AutoDev ${version} for ${platform}/${arch}...`,
|
|
136
|
+
);
|
|
124
137
|
|
|
125
138
|
if (!fs.existsSync(binDir)) {
|
|
126
139
|
fs.mkdirSync(binDir, { recursive: true });
|
|
@@ -131,29 +144,40 @@ async function downloadBinary() {
|
|
|
131
144
|
const archiveFile = `${archiveName}.${ext}`;
|
|
132
145
|
let url = `https://github.com/HEETMEHTA18/autodev/releases/download/${version}/${archiveFile}`;
|
|
133
146
|
|
|
134
|
-
const tempFile = path.join(
|
|
147
|
+
const tempFile = path.join(
|
|
148
|
+
os.tmpdir(),
|
|
149
|
+
`autodev_download_${Date.now()}.${ext}`,
|
|
150
|
+
);
|
|
135
151
|
|
|
136
152
|
// Download using Node.js built-in HTTPS (handles redirects properly)
|
|
137
153
|
try {
|
|
138
154
|
console.log(`[autodev] Downloading from: ${url}`);
|
|
139
155
|
await download(url, tempFile);
|
|
140
156
|
} catch (err) {
|
|
141
|
-
const stableFallback =
|
|
157
|
+
const stableFallback = "v0.3.0";
|
|
142
158
|
if (version !== stableFallback) {
|
|
143
|
-
console.warn(
|
|
144
|
-
|
|
159
|
+
console.warn(
|
|
160
|
+
`\n[autodev] Failed to download version ${version}: ${err.message}`,
|
|
161
|
+
);
|
|
162
|
+
console.warn(
|
|
163
|
+
`[autodev] Falling back to last known stable release: ${stableFallback}...`,
|
|
164
|
+
);
|
|
145
165
|
version = stableFallback;
|
|
146
166
|
url = `https://github.com/HEETMEHTA18/autodev/releases/download/${version}/${archiveFile}`;
|
|
147
167
|
try {
|
|
148
168
|
console.log(`[autodev] Downloading from: ${url}`);
|
|
149
169
|
await download(url, tempFile);
|
|
150
170
|
} catch (retryErr) {
|
|
151
|
-
console.error(
|
|
171
|
+
console.error(
|
|
172
|
+
`\n[autodev] Error downloading stable release asset: ${retryErr.message}`,
|
|
173
|
+
);
|
|
152
174
|
console.error(`[autodev] Please verify your network connection.`);
|
|
153
175
|
process.exit(1);
|
|
154
176
|
}
|
|
155
177
|
} else {
|
|
156
|
-
console.error(
|
|
178
|
+
console.error(
|
|
179
|
+
`\n[autodev] Error downloading release asset: ${err.message}`,
|
|
180
|
+
);
|
|
157
181
|
console.error(`[autodev] URL: ${url}`);
|
|
158
182
|
process.exit(1);
|
|
159
183
|
}
|
|
@@ -162,13 +186,17 @@ async function downloadBinary() {
|
|
|
162
186
|
try {
|
|
163
187
|
// Verify the file was actually downloaded
|
|
164
188
|
if (!fs.existsSync(tempFile)) {
|
|
165
|
-
throw new Error(
|
|
189
|
+
throw new Error("Download completed but file not found on disk.");
|
|
166
190
|
}
|
|
167
191
|
const stat = fs.statSync(tempFile);
|
|
168
192
|
if (stat.size < 1000) {
|
|
169
|
-
throw new Error(
|
|
193
|
+
throw new Error(
|
|
194
|
+
`Downloaded file is too small (${stat.size} bytes), likely an error page.`,
|
|
195
|
+
);
|
|
170
196
|
}
|
|
171
|
-
console.log(
|
|
197
|
+
console.log(
|
|
198
|
+
`[autodev] Downloaded ${(stat.size / 1024 / 1024).toFixed(1)} MB`,
|
|
199
|
+
);
|
|
172
200
|
} catch (err) {
|
|
173
201
|
console.error(`\n[autodev] Error verifying download: ${err.message}`);
|
|
174
202
|
process.exit(1);
|
|
@@ -177,14 +205,23 @@ async function downloadBinary() {
|
|
|
177
205
|
// Extract
|
|
178
206
|
console.log(`[autodev] Extracting binary...`);
|
|
179
207
|
try {
|
|
180
|
-
if (ext ===
|
|
181
|
-
if (process.platform ===
|
|
182
|
-
|
|
208
|
+
if (ext === "zip") {
|
|
209
|
+
if (process.platform === "win32") {
|
|
210
|
+
const escapedTempFile = tempFile.replace(/'/g, "''");
|
|
211
|
+
const escapedBinDir = binDir.replace(/'/g, "''");
|
|
212
|
+
execSync(
|
|
213
|
+
`powershell -Command "Expand-Archive -Path '${escapedTempFile}' -DestinationPath '${escapedBinDir}' -Force"`,
|
|
214
|
+
{ stdio: "inherit" },
|
|
215
|
+
);
|
|
183
216
|
} else {
|
|
184
|
-
|
|
217
|
+
execFileSync("unzip", ["-o", tempFile, "-d", binDir], {
|
|
218
|
+
stdio: "inherit",
|
|
219
|
+
});
|
|
185
220
|
}
|
|
186
221
|
} else {
|
|
187
|
-
|
|
222
|
+
execFileSync("tar", ["-xzf", tempFile, "-C", binDir], {
|
|
223
|
+
stdio: "inherit",
|
|
224
|
+
});
|
|
188
225
|
}
|
|
189
226
|
|
|
190
227
|
// Clean up temp archive
|
|
@@ -193,7 +230,7 @@ async function downloadBinary() {
|
|
|
193
230
|
}
|
|
194
231
|
|
|
195
232
|
// Set execution permissions on Linux/macOS
|
|
196
|
-
if (process.platform !==
|
|
233
|
+
if (process.platform !== "win32" && fs.existsSync(binaryPath)) {
|
|
197
234
|
fs.chmodSync(binaryPath, 0o755);
|
|
198
235
|
}
|
|
199
236
|
console.log(`[autodev] Installation successful.\n`);
|
|
@@ -213,11 +250,36 @@ async function main() {
|
|
|
213
250
|
await downloadBinary();
|
|
214
251
|
}
|
|
215
252
|
|
|
216
|
-
// Forward execution
|
|
253
|
+
// Forward execution asynchronously to handle long-running / interactive processes properly
|
|
217
254
|
const args = process.argv.slice(2);
|
|
218
|
-
const
|
|
255
|
+
const child = spawn(activeBinaryPath, args, { stdio: "inherit" });
|
|
256
|
+
|
|
257
|
+
// Forward termination signals to the child process (critical for long-running servers / MCP)
|
|
258
|
+
const signals = ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"];
|
|
259
|
+
signals.forEach((signal) => {
|
|
260
|
+
process.on(signal, () => {
|
|
261
|
+
if (!child.killed) {
|
|
262
|
+
child.kill(signal);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
child.on("close", (code, signal) => {
|
|
268
|
+
if (code !== null) {
|
|
269
|
+
process.exit(code);
|
|
270
|
+
} else if (signal) {
|
|
271
|
+
// Exit with standard 128 + signal number
|
|
272
|
+
const signalCodes = { SIGINT: 2, SIGTERM: 15, SIGHUP: 1, SIGQUIT: 3 };
|
|
273
|
+
process.exit(128 + (signalCodes[signal] || 0));
|
|
274
|
+
} else {
|
|
275
|
+
process.exit(0);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
219
278
|
|
|
220
|
-
|
|
279
|
+
child.on("error", (err) => {
|
|
280
|
+
console.error(`[autodev] Failed to run binary: ${err.message}`);
|
|
281
|
+
process.exit(1);
|
|
282
|
+
});
|
|
221
283
|
}
|
|
222
284
|
|
|
223
285
|
main().catch((err) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heetmehta18/autodev",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "The App Store for Developers. Install languages, frameworks, and databases with a single command.",
|
|
5
5
|
"main": "bin/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"publishConfig": {
|
|
10
10
|
"access": "public"
|
|
11
11
|
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "node bin/index.js --help"
|
|
14
|
+
},
|
|
12
15
|
"repository": {
|
|
13
16
|
"type": "git",
|
|
14
17
|
"url": "git+https://github.com/HEETMEHTA18/autodev.git"
|
|
@@ -27,8 +30,5 @@
|
|
|
27
30
|
"bugs": {
|
|
28
31
|
"url": "https://github.com/HEETMEHTA18/autodev/issues"
|
|
29
32
|
},
|
|
30
|
-
"homepage": "https://github.com/HEETMEHTA18/autodev#readme"
|
|
31
|
-
|
|
32
|
-
"test": "node bin/index.js --help"
|
|
33
|
-
}
|
|
34
|
-
}
|
|
33
|
+
"homepage": "https://github.com/HEETMEHTA18/autodev#readme"
|
|
34
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 AutoDev Contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|