@secureci/cli 0.3.0 → 0.4.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/dist/cli.js +35 -2
- package/dist/contract-source.js +63 -0
- package/dist/report.js +7 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -38,6 +38,7 @@ const commander_1 = require("commander");
|
|
|
38
38
|
const dry_run_1 = require("./runner/dry-run");
|
|
39
39
|
const executor_1 = require("./runner/executor");
|
|
40
40
|
const action_fetcher_1 = require("./runner/action-fetcher");
|
|
41
|
+
const contract_source_1 = require("./contract-source");
|
|
41
42
|
const validator_1 = require("./parser/validator");
|
|
42
43
|
const workflow_parser_1 = require("./parser/workflow-parser");
|
|
43
44
|
const pipeline_contract_parser_1 = require("./parser/pipeline-contract-parser");
|
|
@@ -384,10 +385,42 @@ async function validateFiles(files, contractFile, recurse = 'local', format = 't
|
|
|
384
385
|
if (!quiet)
|
|
385
386
|
fn();
|
|
386
387
|
};
|
|
388
|
+
// A contract may be a local file or `owner/repo/path@ref` in another
|
|
389
|
+
// repository; resolve it once and record which version was enforced.
|
|
390
|
+
let contractPath = contractFile;
|
|
391
|
+
let contract;
|
|
392
|
+
let contractError;
|
|
393
|
+
if (contractFile) {
|
|
394
|
+
try {
|
|
395
|
+
const source = await (0, contract_source_1.resolveContractSource)(contractFile, {
|
|
396
|
+
fetchFile: (uses) => (0, action_fetcher_1.fetchRepoFile)(uses),
|
|
397
|
+
resolveSha: (uses) => (0, action_fetcher_1.resolveActionSha)(uses),
|
|
398
|
+
warn: (message) => logger_1.logger.warn(message)
|
|
399
|
+
});
|
|
400
|
+
contractPath = source.file;
|
|
401
|
+
const remote = source.remote;
|
|
402
|
+
contract = remote
|
|
403
|
+
? {
|
|
404
|
+
source: remote.uses,
|
|
405
|
+
ref: remote.ref,
|
|
406
|
+
commit: remote.commit,
|
|
407
|
+
pinned: remote.pinned
|
|
408
|
+
}
|
|
409
|
+
: { source: contractFile };
|
|
410
|
+
if (remote) {
|
|
411
|
+
say(() => logger_1.logger.info(`Using contract ${remote.uses}${remote.commit ? ` at commit ${remote.commit.slice(0, 12)}` : ''}`));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
contractError = error instanceof Error ? error.message : 'Unknown error';
|
|
416
|
+
}
|
|
417
|
+
}
|
|
387
418
|
const entries = [];
|
|
388
419
|
for (const filePath of files) {
|
|
389
420
|
try {
|
|
390
|
-
|
|
421
|
+
if (contractError)
|
|
422
|
+
throw new Error(contractError);
|
|
423
|
+
const result = await validateOneFile(filePath, contractPath, recurse, say);
|
|
391
424
|
entries.push({ file: filePath, result });
|
|
392
425
|
}
|
|
393
426
|
catch (error) {
|
|
@@ -409,7 +442,7 @@ async function validateFiles(files, contractFile, recurse = 'local', format = 't
|
|
|
409
442
|
const allValid = entries.every((e) => e.result.valid);
|
|
410
443
|
if (quiet) {
|
|
411
444
|
// eslint-disable-next-line no-console
|
|
412
|
-
console.log((0, report_1.formatReport)(entries, format));
|
|
445
|
+
console.log((0, report_1.formatReport)(entries, format, { contract }));
|
|
413
446
|
if (!allValid)
|
|
414
447
|
process.exit(1);
|
|
415
448
|
return;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.resolveContractSource = resolveContractSource;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
// owner/repo/path/to/file@ref: the same form as a remote reusable workflow.
|
|
39
|
+
const REMOTE_CONTRACT = /^[^/.][^/]*\/[^/@]+\/[^@]+@([^@]+)$/;
|
|
40
|
+
const FULL_SHA = /^[0-9a-f]{40}$/;
|
|
41
|
+
/**
|
|
42
|
+
* Where to read `--contract` from. An existing local file always wins;
|
|
43
|
+
* otherwise `owner/repo/path@ref` is fetched like a remote reusable workflow.
|
|
44
|
+
* A ref that isn't a commit SHA can change under you, so it warns.
|
|
45
|
+
*/
|
|
46
|
+
async function resolveContractSource(value, deps) {
|
|
47
|
+
if (fs.existsSync(value))
|
|
48
|
+
return { file: value };
|
|
49
|
+
const match = REMOTE_CONTRACT.exec(value);
|
|
50
|
+
if (!match)
|
|
51
|
+
throw new Error(`Contract file not found: ${value}`);
|
|
52
|
+
const ref = match[1];
|
|
53
|
+
const pinned = FULL_SHA.test(ref);
|
|
54
|
+
if (!pinned) {
|
|
55
|
+
deps.warn(`Contract ${value} uses the moving ref "${ref}": its owner can change the standard without a change in this repository. Pin it to a commit SHA to control when it changes.`);
|
|
56
|
+
}
|
|
57
|
+
const file = await deps.fetchFile(value);
|
|
58
|
+
if (!file)
|
|
59
|
+
throw new Error(`Could not fetch contract ${value}`);
|
|
60
|
+
const commit = pinned ? ref : await deps.resolveSha(value);
|
|
61
|
+
return { file, remote: { uses: value, ref, commit, pinned } };
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=contract-source.js.map
|
package/dist/report.js
CHANGED
|
@@ -41,13 +41,15 @@ function sarifResults(entry) {
|
|
|
41
41
|
* carrying every file's findings, suitable for GitHub code scanning (upload
|
|
42
42
|
* with `github/codeql-action/upload-sarif`).
|
|
43
43
|
*/
|
|
44
|
-
function formatReport(entries, format) {
|
|
44
|
+
function formatReport(entries, format, meta = {}) {
|
|
45
|
+
const contract = meta.contract ? { contract: meta.contract } : {};
|
|
45
46
|
if (format === 'json') {
|
|
46
47
|
const body = entries.length === 1
|
|
47
|
-
? jsonForFile(entries[0])
|
|
48
|
+
? { ...jsonForFile(entries[0]), ...contract }
|
|
48
49
|
: {
|
|
49
50
|
valid: entries.every((e) => e.result.valid),
|
|
50
|
-
files: entries.map(jsonForFile)
|
|
51
|
+
files: entries.map(jsonForFile),
|
|
52
|
+
...contract
|
|
51
53
|
};
|
|
52
54
|
return JSON.stringify(body, null, 2);
|
|
53
55
|
}
|
|
@@ -63,7 +65,8 @@ function formatReport(entries, format) {
|
|
|
63
65
|
rules: Object.entries(RULES).map(([id, name]) => ({ id, name }))
|
|
64
66
|
}
|
|
65
67
|
},
|
|
66
|
-
results: entries.flatMap(sarifResults)
|
|
68
|
+
results: entries.flatMap(sarifResults),
|
|
69
|
+
...(meta.contract ? { properties: contract } : {})
|
|
67
70
|
}
|
|
68
71
|
]
|
|
69
72
|
}, null, 2);
|