specshield 2.0.1 → 3.1.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 +84 -177
- package/package.json +34 -33
- package/src/api/bdctClient.js +28 -23
- package/src/cli.js +7 -3
- package/src/commands/bdct.js +141 -83
- package/src/commands/init.js +399 -0
- package/src/core/configWriter.js +221 -0
- package/src/core/projectConfig.js +189 -0
- package/src/core/projectDetect.js +180 -0
- package/src/api/contractsClient.js +0 -88
- package/src/commands/contracts.js +0 -561
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Load `.specshield.yml` from the working directory (or any ancestor) and
|
|
5
|
+
* apply its defaults to a parsed CLI options object.
|
|
6
|
+
*
|
|
7
|
+
* Precedence at call time:
|
|
8
|
+
* CLI flag > env var > .specshield.yml > hard-coded default
|
|
9
|
+
*
|
|
10
|
+
* Each `bdct` subcommand calls `applyBdctDefaults(opts, command)` after
|
|
11
|
+
* parsing args. Required-field validation happens here too — that way the
|
|
12
|
+
* CLI flags can stop being `requiredOption(...)` and fall through gracefully
|
|
13
|
+
* to the project config when the user is running from a configured repo.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const yaml = require('js-yaml');
|
|
19
|
+
const logger = require('../utils/logger');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Walk upward from `start` looking for `.specshield.yml` or `.specshield.yaml`.
|
|
23
|
+
* Returns the absolute path of the first hit, or null.
|
|
24
|
+
*/
|
|
25
|
+
function findProjectConfigFile(start = process.cwd()) {
|
|
26
|
+
let dir = path.resolve(start);
|
|
27
|
+
// Bound walk to a sensible depth — never escape the user's home directory.
|
|
28
|
+
for (let i = 0; i < 20; i++) {
|
|
29
|
+
for (const name of ['.specshield.yml', '.specshield.yaml']) {
|
|
30
|
+
const candidate = path.join(dir, name);
|
|
31
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
32
|
+
return candidate;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const parent = path.dirname(dir);
|
|
36
|
+
if (parent === dir) return null;
|
|
37
|
+
dir = parent;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let cached = null;
|
|
43
|
+
let cachedFor = null;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Returns the parsed config object (or an empty `{}` if no file is found).
|
|
47
|
+
* Cached per-process for the same starting directory; tests can pass
|
|
48
|
+
* `{ noCache: true }` or call `clearCache()` to refetch.
|
|
49
|
+
*/
|
|
50
|
+
function loadProjectConfig(start = process.cwd(), { noCache = false } = {}) {
|
|
51
|
+
if (!noCache && cached !== null && cachedFor === start) return cached;
|
|
52
|
+
|
|
53
|
+
const file = findProjectConfigFile(start);
|
|
54
|
+
if (!file) {
|
|
55
|
+
cached = {}; cachedFor = start;
|
|
56
|
+
return cached;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let parsed = {};
|
|
60
|
+
try {
|
|
61
|
+
const raw = fs.readFileSync(file, 'utf8');
|
|
62
|
+
parsed = yaml.load(raw) || {};
|
|
63
|
+
if (typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
64
|
+
logger.warn(`Ignoring ${file}: top-level must be a YAML mapping.`);
|
|
65
|
+
parsed = {};
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
logger.warn(`Failed to read ${file}: ${err.message}. Continuing without project config.`);
|
|
69
|
+
parsed = {};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
cached = { ...parsed, _file: file };
|
|
73
|
+
cachedFor = start;
|
|
74
|
+
return cached;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function clearCache() { cached = null; cachedFor = null; }
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Required-field declaration per BDCT subcommand. Same source-of-truth the
|
|
81
|
+
* old `requiredOption(...)` checks used; centralised here so missing fields
|
|
82
|
+
* produce a single, friendly error.
|
|
83
|
+
*/
|
|
84
|
+
const REQUIRED_FIELDS = {
|
|
85
|
+
'publish-provider': ['org', 'provider', 'version', 'spec'],
|
|
86
|
+
'publish-consumer': ['org', 'consumer', 'provider', 'version', 'contract'],
|
|
87
|
+
'verify': ['org', 'consumer', 'consumerVersion', 'provider', 'providerVersion'],
|
|
88
|
+
'can-i-deploy': ['org', 'service', 'version'],
|
|
89
|
+
'matrix': ['org'],
|
|
90
|
+
'list': ['org'],
|
|
91
|
+
'list-providers': ['org'],
|
|
92
|
+
'list-consumers': ['org'],
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Map a CLI option name to a path inside `.specshield.yml > bdct`.
|
|
97
|
+
* Returns null when there is no defaulting rule for that option.
|
|
98
|
+
*/
|
|
99
|
+
function bdctDefaultFor(bdct, name, command) {
|
|
100
|
+
if (!bdct) return undefined;
|
|
101
|
+
switch (name) {
|
|
102
|
+
case 'org': return bdct.org;
|
|
103
|
+
case 'server': return bdct.server;
|
|
104
|
+
case 'env': return bdct.environment;
|
|
105
|
+
|
|
106
|
+
case 'provider': {
|
|
107
|
+
// For consumer-side operations the provider lives under `bdct.consumer.provider`.
|
|
108
|
+
if (command === 'publish-consumer' || command === 'verify') {
|
|
109
|
+
return bdct.consumer && bdct.consumer.provider;
|
|
110
|
+
}
|
|
111
|
+
// For everything else it's the project's own provider name.
|
|
112
|
+
return bdct.provider && bdct.provider.name;
|
|
113
|
+
}
|
|
114
|
+
case 'consumer': return bdct.consumer && bdct.consumer.name;
|
|
115
|
+
case 'service': {
|
|
116
|
+
// can-i-deploy service is whichever role the project owns.
|
|
117
|
+
return (bdct.provider && bdct.provider.name)
|
|
118
|
+
|| (bdct.consumer && bdct.consumer.name);
|
|
119
|
+
}
|
|
120
|
+
case 'spec': return bdct.provider && bdct.provider.spec;
|
|
121
|
+
case 'contract': return bdct.consumer && bdct.consumer.contract;
|
|
122
|
+
case 'format': return bdct.consumer && bdct.consumer.format;
|
|
123
|
+
case 'branch': return bdct.provider && bdct.provider.branch;
|
|
124
|
+
default: return undefined;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Mutates `opts` in place: for every CLI option that is currently undefined
|
|
130
|
+
* (or empty string), look up a default in the loaded project config.
|
|
131
|
+
*
|
|
132
|
+
* Then verify every required field for the given `command` is present.
|
|
133
|
+
* Throws an Error listing all missing fields if not.
|
|
134
|
+
*/
|
|
135
|
+
function applyBdctDefaults(opts, command, { cwd = process.cwd() } = {}) {
|
|
136
|
+
const cfg = loadProjectConfig(cwd);
|
|
137
|
+
const bdct = cfg.bdct;
|
|
138
|
+
|
|
139
|
+
const FIELDS = [
|
|
140
|
+
'org', 'server', 'env',
|
|
141
|
+
'provider', 'consumer', 'service',
|
|
142
|
+
'version', 'consumerVersion', 'providerVersion',
|
|
143
|
+
'spec', 'contract', 'format', 'branch',
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
// Paths in the config file are interpreted relative to the config file's
|
|
147
|
+
// directory (not the CWD where the command was invoked). This lets a user
|
|
148
|
+
// run `specshield bdct publish-provider` from any subdirectory.
|
|
149
|
+
const configDir = cfg._file ? path.dirname(cfg._file) : cwd;
|
|
150
|
+
const resolvePath = (v) =>
|
|
151
|
+
!v || path.isAbsolute(v) ? v : path.resolve(configDir, v);
|
|
152
|
+
|
|
153
|
+
for (const f of FIELDS) {
|
|
154
|
+
if (opts[f] !== undefined && opts[f] !== '') continue;
|
|
155
|
+
const def = bdctDefaultFor(bdct, f, command);
|
|
156
|
+
if (def === undefined || def === null || def === '') continue;
|
|
157
|
+
opts[f] = (f === 'spec' || f === 'contract') ? resolvePath(def) : def;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Required-field check.
|
|
161
|
+
const required = REQUIRED_FIELDS[command] || [];
|
|
162
|
+
const missing = required.filter(k => !opts[k]);
|
|
163
|
+
if (missing.length > 0) {
|
|
164
|
+
const flagFor = (k) => '--' + k.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
165
|
+
const msg = [
|
|
166
|
+
`Missing required ${missing.length === 1 ? 'option' : 'options'} for \`bdct ${command}\`: `
|
|
167
|
+
+ missing.map(flagFor).join(', '),
|
|
168
|
+
];
|
|
169
|
+
if (cfg._file) {
|
|
170
|
+
msg.push(`Set them as CLI flags or add them under \`bdct\` in ${cfg._file}.`);
|
|
171
|
+
} else {
|
|
172
|
+
msg.push('Pass them as CLI flags, or run `specshield init` to write a `.specshield.yml`.');
|
|
173
|
+
}
|
|
174
|
+
const err = new Error(msg.join('\n'));
|
|
175
|
+
err.code = 'MISSING_REQUIRED_OPTIONS';
|
|
176
|
+
err.missing = missing;
|
|
177
|
+
throw err;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return opts;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports = {
|
|
184
|
+
findProjectConfigFile,
|
|
185
|
+
loadProjectConfig,
|
|
186
|
+
clearCache,
|
|
187
|
+
applyBdctDefaults,
|
|
188
|
+
REQUIRED_FIELDS,
|
|
189
|
+
};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Detect project context for `specshield init`.
|
|
5
|
+
*
|
|
6
|
+
* Pure I/O: reads files inside `cwd`, returns a plain object.
|
|
7
|
+
* No prompting, no network. Each detector is independent and never throws —
|
|
8
|
+
* unknown values come back as `null` so callers can decide whether to
|
|
9
|
+
* prompt or fail.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const yaml = require('js-yaml');
|
|
15
|
+
const cp = require('child_process');
|
|
16
|
+
|
|
17
|
+
// Glob order matters — first hit wins for the auto-detected default,
|
|
18
|
+
// later hits become "alternates" the wizard can offer in a multi-select.
|
|
19
|
+
const SPEC_CANDIDATES = [
|
|
20
|
+
'api/openapi.yaml', 'api/openapi.yml', 'api/openapi.json',
|
|
21
|
+
'openapi.yaml', 'openapi.yml', 'openapi.json',
|
|
22
|
+
'spec/openapi.yaml', 'spec/openapi.yml', 'spec/openapi.json',
|
|
23
|
+
'docs/openapi.yaml', 'docs/openapi.yml', 'docs/openapi.json',
|
|
24
|
+
'swagger.yaml', 'swagger.yml', 'swagger.json',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function fileExists(p) {
|
|
28
|
+
try { return fs.statSync(p).isFile(); } catch { return false; }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readJson(p) {
|
|
32
|
+
try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readText(p) {
|
|
36
|
+
try { return fs.readFileSync(p, 'utf8'); } catch { return null; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function safeLoadYaml(p) {
|
|
40
|
+
try { return yaml.load(fs.readFileSync(p, 'utf8')); } catch { return null; }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Returns true if the parsed object looks like an OpenAPI 3.x or Swagger 2.x spec.
|
|
45
|
+
* Cheap structural check — does not validate the full schema.
|
|
46
|
+
*/
|
|
47
|
+
function looksLikeOpenApi(parsed) {
|
|
48
|
+
if (!parsed || typeof parsed !== 'object') return false;
|
|
49
|
+
if (typeof parsed.openapi === 'string' && parsed.openapi.startsWith('3.')) return true;
|
|
50
|
+
if (typeof parsed.swagger === 'string') return true;
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Find every plausible OpenAPI spec under `cwd` from the candidate list.
|
|
56
|
+
* Returns absolute paths in canonical order; the first entry is the
|
|
57
|
+
* "best" guess (existing-file order in `SPEC_CANDIDATES`).
|
|
58
|
+
*/
|
|
59
|
+
function findSpecCandidates(cwd) {
|
|
60
|
+
const matches = [];
|
|
61
|
+
for (const rel of SPEC_CANDIDATES) {
|
|
62
|
+
const abs = path.join(cwd, rel);
|
|
63
|
+
if (fileExists(abs)) {
|
|
64
|
+
const parsed = safeLoadYaml(abs);
|
|
65
|
+
matches.push({ rel, abs, valid: looksLikeOpenApi(parsed) });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return matches;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Best-guess service / provider name in priority order:
|
|
73
|
+
* package.json name (with @scope stripped)
|
|
74
|
+
* pyproject.toml [project] name
|
|
75
|
+
* go.mod module last segment
|
|
76
|
+
* Cargo.toml [package] name
|
|
77
|
+
* pom.xml <artifactId>
|
|
78
|
+
* directory name
|
|
79
|
+
*/
|
|
80
|
+
function detectServiceName(cwd) {
|
|
81
|
+
// package.json
|
|
82
|
+
const pkg = readJson(path.join(cwd, 'package.json'));
|
|
83
|
+
if (pkg && typeof pkg.name === 'string') {
|
|
84
|
+
return { source: 'package.json', name: pkg.name.replace(/^@[^/]+\//, '') };
|
|
85
|
+
}
|
|
86
|
+
// pyproject.toml — read just enough to find the name without a TOML parser
|
|
87
|
+
const pyproject = readText(path.join(cwd, 'pyproject.toml'));
|
|
88
|
+
if (pyproject) {
|
|
89
|
+
const m = pyproject.match(/^\s*name\s*=\s*["']([^"']+)["']/m);
|
|
90
|
+
if (m) return { source: 'pyproject.toml', name: m[1] };
|
|
91
|
+
}
|
|
92
|
+
// go.mod
|
|
93
|
+
const goMod = readText(path.join(cwd, 'go.mod'));
|
|
94
|
+
if (goMod) {
|
|
95
|
+
const m = goMod.match(/^module\s+(\S+)/m);
|
|
96
|
+
if (m) return { source: 'go.mod', name: m[1].split('/').pop() };
|
|
97
|
+
}
|
|
98
|
+
// Cargo.toml
|
|
99
|
+
const cargo = readText(path.join(cwd, 'Cargo.toml'));
|
|
100
|
+
if (cargo) {
|
|
101
|
+
const m = cargo.match(/^\s*name\s*=\s*["']([^"']+)["']/m);
|
|
102
|
+
if (m) return { source: 'Cargo.toml', name: m[1] };
|
|
103
|
+
}
|
|
104
|
+
// pom.xml — naive single-line artifactId match (good enough for detection)
|
|
105
|
+
const pom = readText(path.join(cwd, 'pom.xml'));
|
|
106
|
+
if (pom) {
|
|
107
|
+
const m = pom.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
108
|
+
if (m) return { source: 'pom.xml', name: m[1] };
|
|
109
|
+
}
|
|
110
|
+
// Fallback — directory name
|
|
111
|
+
return { source: 'directory', name: path.basename(path.resolve(cwd)) };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Read git remote + current branch. Returns `{ remote, owner, repo, branch }`
|
|
116
|
+
* with all fields null if the directory is not a git working tree.
|
|
117
|
+
*/
|
|
118
|
+
function detectGit(cwd) {
|
|
119
|
+
const out = (cmd) => {
|
|
120
|
+
try {
|
|
121
|
+
return cp.execSync(cmd, { cwd, stdio: ['ignore', 'pipe', 'ignore'] })
|
|
122
|
+
.toString().trim();
|
|
123
|
+
} catch { return null; }
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const remote = out('git config --get remote.origin.url');
|
|
127
|
+
const branch = out('git symbolic-ref --short HEAD') || null;
|
|
128
|
+
|
|
129
|
+
let owner = null, repo = null;
|
|
130
|
+
if (remote) {
|
|
131
|
+
// Match git@host:owner/repo.git OR https://host/owner/repo(.git)?
|
|
132
|
+
const m = remote.match(/[:/]([^/:]+)\/([^/]+?)(?:\.git)?$/);
|
|
133
|
+
if (m) { owner = m[1]; repo = m[2]; }
|
|
134
|
+
}
|
|
135
|
+
return { remote, owner, repo, branch };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Suggest a default environment based on the current branch.
|
|
140
|
+
* `main` / `master` → "production", anything else → "staging".
|
|
141
|
+
*/
|
|
142
|
+
function suggestEnvironment(branch) {
|
|
143
|
+
if (!branch) return 'staging';
|
|
144
|
+
if (branch === 'main' || branch === 'master') return 'production';
|
|
145
|
+
return 'staging';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Top-level: run every detector and return one combined snapshot.
|
|
150
|
+
*/
|
|
151
|
+
function detectAll(cwd = process.cwd()) {
|
|
152
|
+
const specs = findSpecCandidates(cwd);
|
|
153
|
+
const service = detectServiceName(cwd);
|
|
154
|
+
const git = detectGit(cwd);
|
|
155
|
+
const env = suggestEnvironment(git.branch);
|
|
156
|
+
const existing = fileExists(path.join(cwd, '.specshield.yml'))
|
|
157
|
+
|| fileExists(path.join(cwd, '.specshield.yaml'));
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
cwd,
|
|
161
|
+
specs,
|
|
162
|
+
spec: specs.find(s => s.valid)?.rel || specs[0]?.rel || null,
|
|
163
|
+
service,
|
|
164
|
+
serviceName: service.name,
|
|
165
|
+
git,
|
|
166
|
+
branch: git.branch,
|
|
167
|
+
environment: env,
|
|
168
|
+
existing,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = {
|
|
173
|
+
SPEC_CANDIDATES,
|
|
174
|
+
looksLikeOpenApi,
|
|
175
|
+
findSpecCandidates,
|
|
176
|
+
detectServiceName,
|
|
177
|
+
detectGit,
|
|
178
|
+
suggestEnvironment,
|
|
179
|
+
detectAll,
|
|
180
|
+
};
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const axios = require('axios');
|
|
4
|
-
const { version } = require('../../package.json');
|
|
5
|
-
|
|
6
|
-
const DEFAULT_SERVER = 'https://specshield.io';
|
|
7
|
-
const TIMEOUT = 15000;
|
|
8
|
-
|
|
9
|
-
function buildClient(server, apiToken) {
|
|
10
|
-
const baseURL = (server || DEFAULT_SERVER).replace(/\/$/, '');
|
|
11
|
-
const headers = {
|
|
12
|
-
'Content-Type': 'application/json',
|
|
13
|
-
'X-SpecShield-Client': 'cli',
|
|
14
|
-
'X-SpecShield-Version': version,
|
|
15
|
-
};
|
|
16
|
-
if (apiToken) headers['X-Api-Key'] = apiToken;
|
|
17
|
-
return axios.create({ baseURL, timeout: TIMEOUT, headers });
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function apiError(err) {
|
|
21
|
-
if (err.response) {
|
|
22
|
-
const data = err.response.data;
|
|
23
|
-
const msg = (data && (data.message || data.error || data.title))
|
|
24
|
-
|| `HTTP ${err.response.status}`;
|
|
25
|
-
return new Error(`API error (${err.response.status}): ${msg}`);
|
|
26
|
-
}
|
|
27
|
-
if (err.request) return new Error(`No response from server: ${err.message}`);
|
|
28
|
-
return new Error(`Request failed: ${err.message}`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function publishContract(server, apiToken, payload) {
|
|
32
|
-
try {
|
|
33
|
-
const res = await buildClient(server, apiToken).post('/api/contracts/publish', payload);
|
|
34
|
-
return res.data;
|
|
35
|
-
} catch (err) { throw apiError(err); }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function listContracts(server, apiToken, { org, consumer, provider, contractName, status, page = 0, size = 20 } = {}) {
|
|
39
|
-
try {
|
|
40
|
-
const params = { page, size };
|
|
41
|
-
if (org) params.orgKey = org;
|
|
42
|
-
if (consumer) params.consumerServiceKey = consumer;
|
|
43
|
-
if (provider) params.providerServiceKey = provider;
|
|
44
|
-
if (contractName) params.contractName = contractName;
|
|
45
|
-
if (status) params.status = status;
|
|
46
|
-
const res = await buildClient(server, apiToken).get('/api/contracts', { params });
|
|
47
|
-
return res.data;
|
|
48
|
-
} catch (err) { throw apiError(err); }
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function getLatestContract(server, apiToken, { org, consumer, provider, contractName } = {}) {
|
|
52
|
-
try {
|
|
53
|
-
const params = {};
|
|
54
|
-
if (org) params.orgKey = org;
|
|
55
|
-
if (consumer) params.consumerServiceKey = consumer;
|
|
56
|
-
if (provider) params.providerServiceKey = provider;
|
|
57
|
-
if (contractName) params.contractName = contractName;
|
|
58
|
-
const res = await buildClient(server, apiToken).get('/api/contracts/latest', { params });
|
|
59
|
-
return res.data;
|
|
60
|
-
} catch (err) { throw apiError(err); }
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function verifyContract(server, apiToken, contractId, payload) {
|
|
64
|
-
try {
|
|
65
|
-
const res = await buildClient(server, apiToken).post(`/api/contracts/${contractId}/verify`, payload);
|
|
66
|
-
return res.data;
|
|
67
|
-
} catch (err) { throw apiError(err); }
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function getVerificationHistory(server, apiToken, contractId) {
|
|
71
|
-
try {
|
|
72
|
-
const res = await buildClient(server, apiToken).get(`/api/contracts/${contractId}/verifications`);
|
|
73
|
-
return res.data;
|
|
74
|
-
} catch (err) { throw apiError(err); }
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async function canIDeploy(server, apiToken, { provider, version: ver, environment } = {}) {
|
|
78
|
-
try {
|
|
79
|
-
const params = { version: ver };
|
|
80
|
-
if (environment) params.environment = environment;
|
|
81
|
-
const res = await buildClient(server, apiToken).get(
|
|
82
|
-
`/api/providers/${encodeURIComponent(provider)}/can-i-deploy`, { params }
|
|
83
|
-
);
|
|
84
|
-
return res.data;
|
|
85
|
-
} catch (err) { throw apiError(err); }
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
module.exports = { publishContract, listContracts, getLatestContract, verifyContract, getVerificationHistory, canIDeploy };
|