@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 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
- const result = await validateOneFile(filePath, contractFile, recurse, say);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@secureci/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "SecureCI: validate, check against pipeline standards, and run GitHub Actions workflows locally",
5
5
  "main": "dist/index.js",
6
6
  "bin": {