sarif-to-comment 0.1.1 → 0.2.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/CHANGELOG.md +21 -0
- package/README.md +68 -12
- package/dist/artifact-files.cjs +273 -0
- package/dist/cli.cjs +1062 -0
- package/dist/github.cjs +1181 -0
- package/dist/index.cjs +39 -0
- package/dist/placement.cjs +649 -0
- package/dist/prepare-review.cjs +1623 -0
- package/dist/publication.cjs +1055 -0
- package/dist/publish-sarif-review.cjs +560 -0
- package/dist/replacements.cjs +459 -0
- package/dist/sarif-authoring.cjs +313 -0
- package/dist/sarif-common.cjs +657 -0
- package/dist/sarif-inspection.cjs +834 -0
- package/dist/sarif-to-comment.cjs +37 -0
- package/dist/sarif-to-comment.d.ts +1018 -0
- package/dist/staged-changes.cjs +1140 -0
- package/dist/staged-git.cjs +391 -0
- package/docs/api/index.md +1 -1
- package/docs/api/sarif-to-comment.addsarifcomment.md +91 -0
- package/docs/api/sarif-to-comment.addsarifcommentoutcome.md +15 -0
- package/docs/api/sarif-to-comment.addstagedchangesoutcome.md +15 -0
- package/docs/api/sarif-to-comment.addstagedchangestosarif.md +66 -0
- package/docs/api/sarif-to-comment.createsarifdocument.md +73 -0
- package/docs/api/sarif-to-comment.iaddedfinding.md +123 -0
- package/docs/api/sarif-to-comment.iaddedfinding.ref.md +13 -0
- package/docs/api/sarif-to-comment.iaddedfinding.resultindex.md +13 -0
- package/docs/api/sarif-to-comment.iaddedfinding.runindex.md +13 -0
- package/docs/api/sarif-to-comment.iaddedfinding.tool.md +13 -0
- package/docs/api/sarif-to-comment.iaddedsarifcommentoutcome.finding.md +13 -0
- package/docs/api/sarif-to-comment.iaddedsarifcommentoutcome.md +102 -0
- package/docs/api/sarif-to-comment.iaddedsarifcommentoutcome.sarif.md +13 -0
- package/docs/api/sarif-to-comment.iaddedsarifcommentoutcome.status.md +13 -0
- package/docs/api/sarif-to-comment.iaddedstagedchangesoutcome.md +102 -0
- package/docs/api/sarif-to-comment.iaddedstagedchangesoutcome.receipt.md +13 -0
- package/docs/api/sarif-to-comment.iaddedstagedchangesoutcome.sarif.md +13 -0
- package/docs/api/sarif-to-comment.iaddedstagedchangesoutcome.status.md +13 -0
- package/docs/api/sarif-to-comment.iaddstagedchangesinput.md +144 -0
- package/docs/api/sarif-to-comment.iaddstagedchangesinput.repository.md +13 -0
- package/docs/api/sarif-to-comment.iaddstagedchangesinput.reviewedcommit.md +13 -0
- package/docs/api/sarif-to-comment.iaddstagedchangesinput.sarif.md +13 -0
- package/docs/api/sarif-to-comment.iaddstagedchangesinput.sourcerooturi.md +13 -0
- package/docs/api/sarif-to-comment.iaddstagedchangesinput.worktree.md +13 -0
- package/docs/api/sarif-to-comment.icreatesarifdocumentoptions.md +81 -0
- package/docs/api/sarif-to-comment.icreatesarifdocumentoptions.source.md +13 -0
- package/docs/api/sarif-to-comment.icreatesarifdocumentoptions.tool.md +13 -0
- package/docs/api/sarif-to-comment.ifailedstagedchangesoutcome.markdown.md +13 -0
- package/docs/api/sarif-to-comment.ifailedstagedchangesoutcome.md +102 -0
- package/docs/api/sarif-to-comment.ifailedstagedchangesoutcome.problems.md +13 -0
- package/docs/api/sarif-to-comment.ifailedstagedchangesoutcome.status.md +13 -0
- package/docs/api/sarif-to-comment.igithubrepository.md +81 -0
- package/docs/api/sarif-to-comment.igithubrepository.owner.md +13 -0
- package/docs/api/sarif-to-comment.igithubrepository.repo.md +13 -0
- package/docs/api/sarif-to-comment.iinspectedoutcome.md +81 -0
- package/docs/api/sarif-to-comment.iinspectedoutcome.status.md +13 -0
- package/docs/api/sarif-to-comment.iinspectedoutcome.view.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionartifactchange.artifactlocation.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionartifactchange.md +144 -0
- package/docs/api/sarif-to-comment.iinspectionartifactchange.othercontent.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionartifactchange.path.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionartifactchange.replacements.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionartifactchange.uri.md +13 -0
- package/docs/api/sarif-to-comment.iinspectiondiagnostic.md +102 -0
- package/docs/api/sarif-to-comment.iinspectiondiagnostic.message.md +13 -0
- package/docs/api/sarif-to-comment.iinspectiondiagnostic.pointer.md +13 -0
- package/docs/api/sarif-to-comment.iinspectiondiagnostic.severity.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionexternalproperties.content.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionexternalproperties.md +106 -0
- package/docs/api/sarif-to-comment.iinspectionexternalproperties.ref.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionexternalproperties.results.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfileproposal.artifactindex.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfileproposal.content.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfileproposal.filemode.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfileproposal.md +186 -0
- package/docs/api/sarif-to-comment.iinspectionfileproposal.operation.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfileproposal.othercontent.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfileproposal.path.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfileproposal.ref.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfinding.approval.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfinding.baselinestate.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfinding.fileproposals.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfinding.fixes.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfinding.kind.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfinding.level.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfinding.locations.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfinding.md +333 -0
- package/docs/api/sarif-to-comment.iinspectionfinding.message.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfinding.othercontent.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfinding.ref.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfinding.relatedlocations.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfinding.resultindex.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfinding.ruleid.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfinding.runindex.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfix.changes.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfix.description.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfix.descriptioncontent.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfix.md +144 -0
- package/docs/api/sarif-to-comment.iinspectionfix.othercontent.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionfix.ref.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionlocation.artifactlocation.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionlocation.charlength.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionlocation.charoffset.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionlocation.endcolumn.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionlocation.endline.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionlocation.logical.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionlocation.md +354 -0
- package/docs/api/sarif-to-comment.iinspectionlocation.message.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionlocation.messagecontent.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionlocation.othercontent.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionlocation.path.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionlocation.snippet.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionlocation.startcolumn.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionlocation.startline.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionlocation.uri.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionlocation.uribaseid.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionmessage.arguments.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionmessage.id.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionmessage.markdown.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionmessage.md +165 -0
- package/docs/api/sarif-to-comment.iinspectionmessage.othercontent.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionmessage.resolved.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionmessage.text.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionpreview.bytelength.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionpreview.md +207 -0
- package/docs/api/sarif-to-comment.iinspectionpreview.othercontent.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionpreview.shownchars.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionpreview.shownlines.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionpreview.state.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionpreview.text.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionpreview.totalchars.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionpreview.totallines.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionreplacement.deletedregion.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionreplacement.inserted.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionreplacement.md +102 -0
- package/docs/api/sarif-to-comment.iinspectionreplacement.othercontent.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionrun.approval.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionrun.columnkind.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionrun.index.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionrun.md +186 -0
- package/docs/api/sarif-to-comment.iinspectionrun.othercontent.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionrun.ref.md +13 -0
- package/docs/api/sarif-to-comment.iinspectionrun.source.md +16 -0
- package/docs/api/sarif-to-comment.iinspectionrun.tool.md +16 -0
- package/docs/api/sarif-to-comment.iinspectsarifoptions.md +102 -0
- package/docs/api/sarif-to-comment.iinspectsarifoptions.previewchars.md +13 -0
- package/docs/api/sarif-to-comment.iinspectsarifoptions.previewlines.md +13 -0
- package/docs/api/sarif-to-comment.iinspectsarifoptions.sourcerooturi.md +13 -0
- package/docs/api/sarif-to-comment.iinvalidsarifoutcome.markdown.md +13 -0
- package/docs/api/sarif-to-comment.iinvalidsarifoutcome.md +102 -0
- package/docs/api/sarif-to-comment.iinvalidsarifoutcome.problems.md +13 -0
- package/docs/api/sarif-to-comment.iinvalidsarifoutcome.status.md +13 -0
- package/docs/api/sarif-to-comment.inewsarifrun.md +102 -0
- package/docs/api/sarif-to-comment.inewsarifrun.source.md +13 -0
- package/docs/api/sarif-to-comment.inewsarifrun.toolname.md +13 -0
- package/docs/api/sarif-to-comment.inewsarifrun.toolversion.md +13 -0
- package/docs/api/sarif-to-comment.inspectsarif.md +80 -0
- package/docs/api/sarif-to-comment.inspectsarifoutcome.md +15 -0
- package/docs/api/sarif-to-comment.iproblem.md +102 -0
- package/docs/api/sarif-to-comment.iproblem.message.md +13 -0
- package/docs/api/sarif-to-comment.iproblem.path.md +13 -0
- package/docs/api/sarif-to-comment.iproblem.pointer.md +13 -0
- package/docs/api/sarif-to-comment.isarifcomment.endline.md +13 -0
- package/docs/api/sarif-to-comment.isarifcomment.file.md +13 -0
- package/docs/api/sarif-to-comment.isarifcomment.level.md +13 -0
- package/docs/api/sarif-to-comment.isarifcomment.line.md +13 -0
- package/docs/api/sarif-to-comment.isarifcomment.md +207 -0
- package/docs/api/sarif-to-comment.isarifcomment.message.md +13 -0
- package/docs/api/sarif-to-comment.isarifcomment.messageformat.md +13 -0
- package/docs/api/sarif-to-comment.isarifcomment.ruleid.md +13 -0
- package/docs/api/sarif-to-comment.isarifcomment.run.md +13 -0
- package/docs/api/sarif-to-comment.isarifinspection.diagnostics.md +13 -0
- package/docs/api/sarif-to-comment.isarifinspection.externalproperties.md +13 -0
- package/docs/api/sarif-to-comment.isarifinspection.findings.md +13 -0
- package/docs/api/sarif-to-comment.isarifinspection.format.md +13 -0
- package/docs/api/sarif-to-comment.isarifinspection.log.md +15 -0
- package/docs/api/sarif-to-comment.isarifinspection.md +207 -0
- package/docs/api/sarif-to-comment.isarifinspection.runs.md +13 -0
- package/docs/api/sarif-to-comment.isarifinspection.summary.md +20 -0
- package/docs/api/sarif-to-comment.isarifinspection.version.md +13 -0
- package/docs/api/sarif-to-comment.isariflog._schema.md +13 -0
- package/docs/api/sarif-to-comment.isariflog.md +100 -0
- package/docs/api/sarif-to-comment.isariflog.runs.md +13 -0
- package/docs/api/sarif-to-comment.isariflog.version.md +13 -0
- package/docs/api/sarif-to-comment.isarifsourcebinding.commit.md +13 -0
- package/docs/api/sarif-to-comment.isarifsourcebinding.md +61 -0
- package/docs/api/sarif-to-comment.isariftoolidentity.md +81 -0
- package/docs/api/sarif-to-comment.isariftoolidentity.name.md +13 -0
- package/docs/api/sarif-to-comment.isariftoolidentity.version.md +13 -0
- package/docs/api/sarif-to-comment.istagedchangereceipt.associated.md +13 -0
- package/docs/api/sarif-to-comment.istagedchangereceipt.explainedby.md +13 -0
- package/docs/api/sarif-to-comment.istagedchangereceipt.md +144 -0
- package/docs/api/sarif-to-comment.istagedchangereceipt.operation.md +13 -0
- package/docs/api/sarif-to-comment.istagedchangereceipt.path.md +13 -0
- package/docs/api/sarif-to-comment.istagedchangereceipt.replacements.md +13 -0
- package/docs/api/sarif-to-comment.istagedchangesreceipt.addedrun.md +13 -0
- package/docs/api/sarif-to-comment.istagedchangesreceipt.boundruns.md +13 -0
- package/docs/api/sarif-to-comment.istagedchangesreceipt.changes.md +13 -0
- package/docs/api/sarif-to-comment.istagedchangesreceipt.md +144 -0
- package/docs/api/sarif-to-comment.istagedchangesreceipt.reviewedcommit.md +13 -0
- package/docs/api/sarif-to-comment.istagedchangesreceipt.warnings.md +13 -0
- package/docs/api/sarif-to-comment.istagedreplacementreceipt.associated.md +13 -0
- package/docs/api/sarif-to-comment.istagedreplacementreceipt.endline.md +13 -0
- package/docs/api/sarif-to-comment.istagedreplacementreceipt.explainedby.md +13 -0
- package/docs/api/sarif-to-comment.istagedreplacementreceipt.insertion.md +13 -0
- package/docs/api/sarif-to-comment.istagedreplacementreceipt.md +148 -0
- package/docs/api/sarif-to-comment.istagedreplacementreceipt.startline.md +13 -0
- package/docs/api/sarif-to-comment.md +432 -2
- package/docs/getting-started.md +126 -3
- package/package.json +31 -18
- package/bin/sarif-to-comment.cjs +0 -275
- package/src/github.cjs +0 -1092
- package/src/index.cjs +0 -514
- package/src/placement.cjs +0 -586
- package/src/prepare-review.cjs +0 -1547
- package/src/publication.cjs +0 -994
- package/src/replacements.cjs +0 -463
- package/types/index.d.ts +0 -217
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# sarif-to-comment
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 6d868ab: The implementation is now strict TypeScript, and the shipped TypeScript declarations are generated from it rather than written by hand.
|
|
8
|
+
|
|
9
|
+
- No change to the public API, the CLI or behavior. The declarations describe the same functions and types as before.
|
|
10
|
+
- The packaged runtime files moved from `src/` and `bin/` to `dist/`. The package entry points are unchanged: `require('sarif-to-comment')`, `import … from 'sarif-to-comment'`, its TypeScript types and the `sarif-to-comment` command resolve through `package.json` as before. Only code that reached into the package's internal file paths is affected.
|
|
11
|
+
|
|
12
|
+
## 0.2.0
|
|
13
|
+
|
|
14
|
+
### Minor Changes
|
|
15
|
+
|
|
16
|
+
- Write, inspect and extend SARIF without an analyzer, and turn staged Git changes into suggested fixes.
|
|
17
|
+
|
|
18
|
+
- New library functions `createSarifDocument`, `addSarifComment`, `inspectSarif` and `addStagedChangesToSarif`, with TypeScript declarations. They work on ordinary in-memory SARIF, never change their input, and accept SARIF from any producer.
|
|
19
|
+
- New CLI commands `init`, `add-comment`, `inspect`, `add-staged-changes` and `publish`.
|
|
20
|
+
- `--format human|json` on every command: JSON mode prints exactly one document for every outcome, errors included.
|
|
21
|
+
- `add-staged-changes` reads only the Git index, never unstaged working-tree content. It attaches a change to a finding only when the finding's lines lie within the change, and fails with an explanation for changes it cannot represent. An existing output file is preserved under a timestamped `.old.` name.
|
|
22
|
+
- Publishing is unchanged. The original flag-only command keeps its exact behavior, output and exit statuses.
|
|
23
|
+
|
|
3
24
|
## 0.1.1
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# sarif-to-comment
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Write, inspect and publish [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html) findings as **one GitHub draft pull request review**:
|
|
4
4
|
|
|
5
5
|
- general feedback in the review body;
|
|
6
6
|
- findings on changed lines as inline comments;
|
|
@@ -8,6 +8,13 @@ Publish a ready [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/err
|
|
|
8
8
|
|
|
9
9
|
It is sent in a single create-review request.
|
|
10
10
|
|
|
11
|
+
The SARIF can come from any analyzer. Or you can write it yourself, with no analyzer:
|
|
12
|
+
- create a document and add findings on lines or line ranges;
|
|
13
|
+
- add the changes you staged with Git as suggested fixes on those findings;
|
|
14
|
+
- inspect the result before publishing.
|
|
15
|
+
|
|
16
|
+
Every step reads and writes ordinary SARIF and is optional. SARIF from an analyzer can be inspected, given staged changes or published directly, without any setup step.
|
|
17
|
+
|
|
11
18
|
The scope is deliberately narrow:
|
|
12
19
|
|
|
13
20
|
- **One-way.** SARIF goes to GitHub once. The tool never updates, reconciles, submits, restores or deletes a review afterwards. Supplying a new SARIF document (with a new state path) creates a separate review.
|
|
@@ -15,13 +22,16 @@ The scope is deliberately narrow:
|
|
|
15
22
|
- **Whole review or nothing.** If any finding can't be published faithfully, nothing is published, and the tool explains why.
|
|
16
23
|
- **Never duplicated.** A durable state file makes retries confirm the existing review instead of creating another.
|
|
17
24
|
|
|
18
|
-
Requires Node.js 22 or later. There are two surfaces, the **library** (SARIF in memory) and the **CLI** (
|
|
25
|
+
Requires Node.js 22 or later. There are two surfaces, the **library** (SARIF in memory) and the **CLI** (SARIF files); both run the same code.
|
|
19
26
|
|
|
20
27
|
## Documentation
|
|
21
28
|
|
|
22
29
|
These documents are included in the package. The links open them on unpkg (for the latest published version) and need no access to the source repository.
|
|
23
30
|
|
|
24
|
-
- **[Getting started](https://unpkg.com/sarif-to-comment/docs/getting-started.md):**
|
|
31
|
+
- **[Getting started](https://unpkg.com/sarif-to-comment/docs/getting-started.md):**
|
|
32
|
+
- credentials, the reviewed commit, the source root and the state path;
|
|
33
|
+
- complete library and CLI examples for publishing an analyzer's SARIF, and for writing a review yourself from staged changes;
|
|
34
|
+
- how to handle every outcome.
|
|
25
35
|
- **[API reference](https://unpkg.com/sarif-to-comment/docs/api/index.md):** generated by API Documenter from the package's TypeScript declarations.
|
|
26
36
|
- **[Changelog](https://unpkg.com/sarif-to-comment/CHANGELOG.md):** release notes produced by Changesets.
|
|
27
37
|
|
|
@@ -91,7 +101,8 @@ GH_TOKEN=... npx sarif-to-comment \
|
|
|
91
101
|
--state /var/lib/my-linter/reviews/acme-widgets-42-run-1817.json
|
|
92
102
|
```
|
|
93
103
|
|
|
94
|
-
- **Optional flags:** `--source-root FILE_URI`, `--old-source-commit FULLSHA
|
|
104
|
+
- **Optional flags:** `--source-root FILE_URI`, `--old-source-commit FULLSHA`, `--ignore-approval-hold` and `--format human|json`. Run `sarif-to-comment --help` for details; it needs no token and makes no request.
|
|
105
|
+
- **Command form:** `sarif-to-comment publish` followed by the same flags does exactly the same thing. With `--format json` either form prints one JSON document (`status`, `review`, `statePath` and the Markdown `message`), with the same exit status.
|
|
95
106
|
- **Same core as the library:** the CLI reads the file, calls the same `publishSarifReview`, and prints the same Markdown to stdout.
|
|
96
107
|
- **File encoding:** the SARIF file must be UTF-8 JSON.
|
|
97
108
|
- A leading UTF-8 byte-order mark is ignored, so the CLI and a library caller passing the same parsed document produce the same publication.
|
|
@@ -104,6 +115,35 @@ GH_TOKEN=... npx sarif-to-comment \
|
|
|
104
115
|
| 3 | uncertain: retry with the same `--state` |
|
|
105
116
|
| 1 | usage error, unreadable or unparsable SARIF file, refused request, or operational failure (details on stderr) |
|
|
106
117
|
|
|
118
|
+
## Quick start: write a review yourself
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
# In the repository, with your proposed change staged (git add); unstaged edits are ignored.
|
|
122
|
+
npx sarif-to-comment init --output review.sarif --tool-name "Review agent"
|
|
123
|
+
npx sarif-to-comment add-comment --sarif review.sarif --file src/parse.js --line 2 \
|
|
124
|
+
--message "Handle the empty-input case."
|
|
125
|
+
npx sarif-to-comment add-staged-changes --sarif review.sarif --output review.staged.sarif \
|
|
126
|
+
--worktree . --repo acme/widgets --commit c0dec0dec0dec0dec0dec0dec0dec0dec0dec0de
|
|
127
|
+
npx sarif-to-comment inspect --sarif review.staged.sarif
|
|
128
|
+
GH_TOKEN=... npx sarif-to-comment publish --sarif review.staged.sarif --repo acme/widgets --pull 42 \
|
|
129
|
+
--commit c0dec0dec0dec0dec0dec0dec0dec0dec0dec0de --state /var/lib/reviews/acme-widgets-42.json
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The library has the same operations for in-memory SARIF: `createSarifDocument`, `addSarifComment`, `inspectSarif`, `addStagedChangesToSarif` and `publishSarifReview`. Each returns a new document or a view and never changes its input.
|
|
133
|
+
|
|
134
|
+
- **Line numbers** refer to the reviewed commit. For a file the reviewed commit doesn't have, they refer to its staged content.
|
|
135
|
+
- **Associating changes with findings.** Only the staged index is read. A staged change becomes a fix on a finding only when the finding's lines lie within the lines the change replaces; neither is enlarged.
|
|
136
|
+
- A finding that only partly overlaps a change stays a comment. The receipt says so.
|
|
137
|
+
- A change that no finding explains is kept as a short factual finding attributed to `sarif-to-comment`. A change that only adds lines replaces no reviewed line, so it is never credited to a nearby finding; the receipt marks it `insertion: true`.
|
|
138
|
+
- Findings that already have fixes are never changed. If your staged change differs from such a fix on the same lines, the command fails rather than choose between them.
|
|
139
|
+
- **Files.** `init` refuses to overwrite an existing file. `add-comment` updates its SARIF file in place, atomically. `add-staged-changes` writes a separate output; if that output file already exists, it's first renamed to `<UTC time>.old.<name>`, and a failed run writes no output.
|
|
140
|
+
- **Output.** `inspect` shows every finding in full, with its locations and fixes, plus log-level and inline external properties verbatim. Only fix previews are shortened, visibly. It doesn't check whether the file can be published.
|
|
141
|
+
- `--format json` on any command prints one JSON document for every outcome, errors included. Exit statuses are the same in both formats.
|
|
142
|
+
- Exit statuses: 0 success; 2 content refused (not valid SARIF, or a staged change that can't be represented); 1 usage or operational error. `publish` keeps the exit statuses below.
|
|
143
|
+
- **Changes that can't be represented.** Staged edits to UTF-8 text files become fixes. Publication still checks native-suggestion compatibility: empty reviewed files, or files containing only a byte-order mark, have no source line for an inline suggestion and are blocked. File creations and deletions become proposed file operations: `inspect` shows them, but `publish` doesn't support them yet and blocks the review. `add-staged-changes` fails, naming the path, for mode changes, symbolic links, submodules, binary or non-UTF-8 files, conflicts and intent-to-add entries.
|
|
144
|
+
|
|
145
|
+
See the [getting-started guide](https://unpkg.com/sarif-to-comment/docs/getting-started.md#write-a-review-yourself) for complete, tested examples of both surfaces and of SARIF from an analyzer.
|
|
146
|
+
|
|
107
147
|
## Credentials
|
|
108
148
|
|
|
109
149
|
Use a GitHub **personal access token or user token**. It needs permission to read the repository and to create pull request reviews; for a fine-grained token that is *Contents: Read* and *Pull requests: Read and write*. The CLI reads `GH_TOKEN`, or else `GITHUB_TOKEN`; there is no token flag.
|
|
@@ -181,19 +221,35 @@ If that comparison can't establish the old side, you may pass `oldSourceCommit`
|
|
|
181
221
|
- The durability steps (write, flush, then send) are ordered for crash safety, but that has not been tested against power loss.
|
|
182
222
|
- GitHub Enterprise Server and GitHub App installation tokens are not supported.
|
|
183
223
|
- There is no review maintenance, re-review or synchronisation back to SARIF.
|
|
224
|
+
- Authoring can't yet remove or correct a finding in place, and there's no standalone check that a document can be published; `publish` performs every check. Staged file creations and deletions can be recorded in SARIF, but can't be published yet.
|
|
184
225
|
- npm attaches a provenance attestation only when the source repository is public at publish time. A release published while the repository is private has no provenance attestation (see [Releasing](#releasing)).
|
|
185
226
|
|
|
186
227
|
## Development
|
|
187
228
|
|
|
188
229
|
```sh
|
|
189
230
|
pnpm install
|
|
190
|
-
pnpm
|
|
231
|
+
pnpm run build # rebuild dist/ from src/, roll up the declarations, regenerate api-report/ and docs/api/
|
|
191
232
|
pnpm run check # lint, types, API report/docs freshness, release plan, tests (read-only)
|
|
192
|
-
pnpm
|
|
233
|
+
pnpm test # refuse a missing or stale dist/, then node --test "test/**/*.test.mts"
|
|
193
234
|
pnpm changeset # describe a change for the next release
|
|
194
235
|
```
|
|
195
236
|
|
|
196
|
-
|
|
237
|
+
Development and release tooling need Node 22.18.0 or later, enforced by `devEngines` in `package.json`: the tests and the build, check and release scripts are TypeScript that Node runs directly by type stripping. The published package still needs only Node 22 or later (`engines`).
|
|
238
|
+
|
|
239
|
+
Run `pnpm run build` before `pnpm run check` or `pnpm test`, and again after changing sources or build configuration. The tests exercise, and the package ships, the built `dist/`. `pnpm test` (and therefore `pnpm run check`) refuses a `dist/` that is missing, incomplete, or was built from different sources or configuration; it compares content hashes of every build input and output, not modification times.
|
|
240
|
+
|
|
241
|
+
The implementation is strict TypeScript, and there are no hand-written declarations. The public TypeScript declarations are generated from the implementation. `src/public-api.cts` declares the public API, and the CommonJS runtime entry `src/index.cts` is checked at compile time to export exactly its functions. `tsc` emits per-module declarations, API Extractor rolls them up into the shipped `dist/sarif-to-comment.d.ts` and writes a reviewable API report (`api-report/`) and a doc model, and API Documenter renders the doc model as the Markdown reference in `docs/api/`. `pnpm run check` fails when either is out of date.
|
|
242
|
+
|
|
243
|
+
`pnpm run build` rewrites `api-report/` and `docs/api/` whenever the public API or its TSDoc changes; commit them with the change. CI and the publish workflow build and then fail if either differs from the committed copy.
|
|
244
|
+
|
|
245
|
+
**Repository layout.**
|
|
246
|
+
- `src/*.cts`: the implementation. `tsc` compiles each module to CommonJS in `dist/*.cjs`; `src/index.cts` is the package entry and `src/sarif-to-comment.cts` the CLI executable.
|
|
247
|
+
- `dist/`: build output. It is not committed; the package ships its runtime modules and the rolled-up declarations.
|
|
248
|
+
- `test/`: `node:test` suites (`*.test.mts`), fixtures and helpers, run against `dist/`.
|
|
249
|
+
- `scripts/*.mts`: build, type-check, API documentation and release-guard tooling.
|
|
250
|
+
- `api-report/` and `docs/api/`: generated by `pnpm run build` and committed, so API changes are reviewed.
|
|
251
|
+
- `vendor/`: the official SARIF 2.1.0 schema used for validation.
|
|
252
|
+
- `.changeset/`: pending release notes.
|
|
197
253
|
|
|
198
254
|
## Releasing
|
|
199
255
|
|
|
@@ -201,16 +257,16 @@ Releases use [Changesets](https://changesets.dev/) for versioning and [npm trust
|
|
|
201
257
|
|
|
202
258
|
### Versions stay below 1.0.0
|
|
203
259
|
|
|
204
|
-
`MAXIMUM_RELEASE_MAJOR` in `scripts/release-guard.
|
|
260
|
+
`MAXIMUM_RELEASE_MAJOR` in `scripts/release-guard.mts` is `0`. Until someone deliberately raises it in a reviewed change, nothing can version or publish `1.0.0` or higher, including prereleases such as `1.0.0-rc.0`:
|
|
205
261
|
|
|
206
262
|
- **`pnpm run check`** (run in CI on every pull request) fails if a pending changeset would reach 1.0.0. A `major` changeset is refused with an explanation. It is never quietly converted to a smaller bump; choose `minor` yourself if the change shouldn't start 1.0.
|
|
207
263
|
- **`pnpm run release:version`** refuses the same plans before `changeset version` changes any file.
|
|
208
264
|
- **What counts as the plan.** The guard doesn't parse changeset files itself. It runs the real `changeset version` in a throwaway copy and judges the version and changelog entry Changesets produces, so any front matter Changesets accepts is judged by its actual effect. That includes quoted values such as `"sarif-to-comment": "major"`. A changeset Changesets can't read is refused, not ignored.
|
|
209
|
-
- **The publish workflow** refuses any `package.json` version of 1.0.0 or higher, and so does the `prepublishOnly` backstop for a manual publish
|
|
265
|
+
- **The publish workflow** refuses any `package.json` version of 1.0.0 or higher, and so does the `prepublishOnly` backstop for a manual publish, which also refuses a missing or stale `dist/`.
|
|
210
266
|
|
|
211
267
|
Changesets pre mode (prereleases) is not part of this release path.
|
|
212
268
|
|
|
213
|
-
**Deliberately releasing 1.0.** In a reviewed change, raise `MAXIMUM_RELEASE_MAJOR` to `1` and update the test in `test/release.test.
|
|
269
|
+
**Deliberately releasing 1.0.** In a reviewed change, raise `MAXIMUM_RELEASE_MAJOR` to `1` and update the test in `test/release.test.mts` that pins its value. That is the only step: a `major` changeset then versions and publishes `1.0.0` through the normal flow above, while `2.0.0` and above stay blocked.
|
|
214
270
|
|
|
215
271
|
### Making a release
|
|
216
272
|
|
|
@@ -229,8 +285,8 @@ Changesets pre mode (prereleases) is not part of this release path.
|
|
|
229
285
|
- pre mode is off;
|
|
230
286
|
- the package metadata is publishable, with the exact repository URL;
|
|
231
287
|
- the commit is on `main`;
|
|
232
|
-
- npm is at least 11.5.1 and Node at least 22.
|
|
233
|
-
- **When allowed:** it runs `pnpm run check`, packs the tarball, verifies it contains exactly the distribution files, and publishes that tarball.
|
|
288
|
+
- npm is at least 11.5.1 and Node at least 22.18.0.
|
|
289
|
+
- **When allowed:** it builds `dist/`, runs `pnpm run check`, packs the tarball, verifies it contains exactly the distribution files, and publishes that tarball.
|
|
234
290
|
|
|
235
291
|
Publishes never overlap, and a publish in progress is never cancelled.
|
|
236
292
|
4. **If publishing fails**, what to do depends on where the cause is. A version npm has already accepted can never be republished.
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* File handling for the CLI's local SARIF artifacts. The library never touches
|
|
4
|
+
* files; the command-line interface uses this module so that every receipt it
|
|
5
|
+
* prints describes what actually happened on disk.
|
|
6
|
+
*
|
|
7
|
+
* Responsibilities and guarantees:
|
|
8
|
+
* - Reading: SARIF and message files are strict UTF-8 (a leading byte-order
|
|
9
|
+
* mark is an encoding signature and is removed); undecodable bytes are
|
|
10
|
+
* refused rather than replaced, so nothing is silently altered.
|
|
11
|
+
* - Exclusive creation (`createExclusive`): the content is written and
|
|
12
|
+
* flushed to a temporary sibling, then hard-linked to the destination,
|
|
13
|
+
* which fails if the destination exists. A file therefore appears whole or
|
|
14
|
+
* not at all, and an output that reappeared concurrently is never
|
|
15
|
+
* overwritten.
|
|
16
|
+
* - Ownership (`acquireOwnership`): cooperating sarif-to-comment commands
|
|
17
|
+
* editing or producing the same artifact exclude each other with an
|
|
18
|
+
* exclusively created marker file, `.<name>.sarif-to-comment-lock`, beside
|
|
19
|
+
* the artifact. A marker that already exists is reported, never taken over:
|
|
20
|
+
* only a person can know that its owner is gone. The owner removes it when
|
|
21
|
+
* it finishes, whatever the outcome.
|
|
22
|
+
* - In-place replacement (`replaceIfUnchanged`): the new content is written
|
|
23
|
+
* to a temporary sibling with the original mode; immediately before the
|
|
24
|
+
* atomic rename the file is re-read and compared with the bytes the edit
|
|
25
|
+
* was computed from, and an observed external change is refused. This is
|
|
26
|
+
* not a filesystem compare-and-swap: a non-cooperating writer acting
|
|
27
|
+
* between that check and the rename is outside the guarantee.
|
|
28
|
+
* - Output preservation (`archiveExisting`): an existing output is moved
|
|
29
|
+
* aside as `<YYYY-MM-DDTHH-mm-ss.SSSZ>.old.<name>` in its directory, the
|
|
30
|
+
* stamp being the file's birth time in UTC (or its modification time when
|
|
31
|
+
* the platform reports no birth time), with `-2`, `-3`, … appended to the
|
|
32
|
+
* stamp on collision. The archive is an exclusive hard link followed by
|
|
33
|
+
* removal of the original name, so it never overwrites anything.
|
|
34
|
+
*
|
|
35
|
+
* Failures a user must act on are `ArtifactError`s with actionable messages
|
|
36
|
+
* that name the paths involved.
|
|
37
|
+
*/
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.ArtifactError = void 0;
|
|
40
|
+
exports.readTextFile = readTextFile;
|
|
41
|
+
exports.readJsonFile = readJsonFile;
|
|
42
|
+
exports.createExclusive = createExclusive;
|
|
43
|
+
exports.ownershipMarkerFor = ownershipMarkerFor;
|
|
44
|
+
exports.acquireOwnership = acquireOwnership;
|
|
45
|
+
exports.replaceIfUnchanged = replaceIfUnchanged;
|
|
46
|
+
exports.archiveExisting = archiveExisting;
|
|
47
|
+
exports.sameExistingFile = sameExistingFile;
|
|
48
|
+
const crypto = require("node:crypto");
|
|
49
|
+
const fs = require("node:fs");
|
|
50
|
+
const path = require("node:path");
|
|
51
|
+
/** A file problem the user must resolve; the message names the paths. */
|
|
52
|
+
class ArtifactError extends Error {
|
|
53
|
+
}
|
|
54
|
+
exports.ArtifactError = ArtifactError;
|
|
55
|
+
/**
|
|
56
|
+
* A caught failure's `message`, read as a template would read `err.message`.
|
|
57
|
+
* Node's file-system calls throw Errors (SystemError), so this is their
|
|
58
|
+
* message; a value without one reads as "undefined".
|
|
59
|
+
*/
|
|
60
|
+
function messageOf(err) {
|
|
61
|
+
return String(typeof err === 'object' && err !== null && 'message' in err ? err.message : undefined);
|
|
62
|
+
}
|
|
63
|
+
/** A caught failure's Node error `code` (such as `EEXIST`), or undefined when it has none. */
|
|
64
|
+
function codeOf(err) {
|
|
65
|
+
return typeof err === 'object' && err !== null && 'code' in err ? err.code : undefined;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Decoder for UTF-8 text files: `fatal` refuses invalid bytes instead of
|
|
69
|
+
* substituting U+FFFD; the default `ignoreBOM: false` removes a leading BOM.
|
|
70
|
+
*/
|
|
71
|
+
const UTF8 = new TextDecoder('utf-8', { fatal: true, ignoreBOM: false });
|
|
72
|
+
/** Reads a UTF-8 text file; `label` names it in errors (e.g. "message file"). */
|
|
73
|
+
function readTextFile(file, label) {
|
|
74
|
+
let bytes;
|
|
75
|
+
try {
|
|
76
|
+
bytes = fs.readFileSync(file);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
throw new ArtifactError(`cannot read ${label} ${file}: ${messageOf(err)}`);
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
return { text: UTF8.decode(bytes), bytes };
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
throw new ArtifactError(`${label} ${file} is not valid UTF-8; it must be UTF-8 encoded.`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Reads a UTF-8 JSON file: { value, bytes } (the bytes as read, for change detection). */
|
|
89
|
+
function readJsonFile(file, label) {
|
|
90
|
+
const { text, bytes } = readTextFile(file, label);
|
|
91
|
+
try {
|
|
92
|
+
const value = JSON.parse(text);
|
|
93
|
+
return { value, bytes };
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
throw new ArtifactError(`${label} ${file} is not valid JSON: ${messageOf(err)}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Flushes a directory entry change (best effort where directories cannot be opened). */
|
|
100
|
+
function syncDirectory(dir) {
|
|
101
|
+
let fd;
|
|
102
|
+
try {
|
|
103
|
+
fd = fs.openSync(dir, 'r');
|
|
104
|
+
fs.fsyncSync(fd);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Some platforms do not allow fsync on a directory; the rename/link itself is still atomic.
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
if (fd !== undefined)
|
|
111
|
+
fs.closeSync(fd);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Writes and flushes `text` to a new uniquely named temporary sibling of `file`. */
|
|
115
|
+
function writeTemporarySibling(file, text, mode) {
|
|
116
|
+
const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${String(process.pid)}.${crypto.randomUUID()}.tmp`);
|
|
117
|
+
const fd = fs.openSync(temporary, 'wx', mode ?? 0o666);
|
|
118
|
+
try {
|
|
119
|
+
fs.writeFileSync(fd, text);
|
|
120
|
+
if (mode !== undefined)
|
|
121
|
+
fs.fchmodSync(fd, mode);
|
|
122
|
+
fs.fsyncSync(fd);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
fs.closeSync(fd);
|
|
126
|
+
}
|
|
127
|
+
return temporary;
|
|
128
|
+
}
|
|
129
|
+
/** Removes a temporary file if it is still present. */
|
|
130
|
+
function discard(temporary) {
|
|
131
|
+
try {
|
|
132
|
+
fs.unlinkSync(temporary);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Already gone.
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Creates `file` with `text` only if it does not exist. `hooks.beforeLink`
|
|
140
|
+
* (tests only) runs after the temporary file is complete.
|
|
141
|
+
*/
|
|
142
|
+
function createExclusive(file, text, hooks = {}) {
|
|
143
|
+
const temporary = writeTemporarySibling(file, text);
|
|
144
|
+
try {
|
|
145
|
+
if (hooks.beforeLink)
|
|
146
|
+
hooks.beforeLink();
|
|
147
|
+
try {
|
|
148
|
+
fs.linkSync(temporary, file);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
if (codeOf(err) === 'EEXIST') {
|
|
152
|
+
throw new ArtifactError(`${file} already exists (it may have been created by another program); it was left unchanged.`);
|
|
153
|
+
}
|
|
154
|
+
throw new ArtifactError(`cannot create ${file}: ${messageOf(err)}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
discard(temporary);
|
|
159
|
+
}
|
|
160
|
+
syncDirectory(path.dirname(file));
|
|
161
|
+
}
|
|
162
|
+
/** The ownership marker for an artifact: `.<name>.sarif-to-comment-lock` in its directory. */
|
|
163
|
+
function ownershipMarkerFor(file) {
|
|
164
|
+
return path.join(path.dirname(file), `.${path.basename(file)}.sarif-to-comment-lock`);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Takes exclusive cooperating-writer ownership of `file` (which need not
|
|
168
|
+
* exist). Returns a release function. Throws ArtifactError if another owner
|
|
169
|
+
* holds it.
|
|
170
|
+
*/
|
|
171
|
+
function acquireOwnership(file) {
|
|
172
|
+
const marker = ownershipMarkerFor(file);
|
|
173
|
+
const token = `${String(process.pid)} ${new Date().toISOString()} ${crypto.randomUUID()}\n`;
|
|
174
|
+
try {
|
|
175
|
+
fs.writeFileSync(marker, token, { flag: 'wx' });
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
if (codeOf(err) === 'EEXIST') {
|
|
179
|
+
throw new ArtifactError(`${file} is being written by another sarif-to-comment command (ownership marker ${marker}). ` +
|
|
180
|
+
`Wait for it to finish. If no such command is running, the marker is stale: delete ${marker} and run again.`);
|
|
181
|
+
}
|
|
182
|
+
throw new ArtifactError(`cannot take ownership of ${file} (marker ${marker}): ${messageOf(err)}`);
|
|
183
|
+
}
|
|
184
|
+
return function release() {
|
|
185
|
+
// Remove only our own marker: never delete one another process created.
|
|
186
|
+
try {
|
|
187
|
+
if (fs.readFileSync(marker, 'utf8') === token)
|
|
188
|
+
fs.unlinkSync(marker);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// Already removed.
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Atomically replaces `file` with `text`, provided its content still equals
|
|
197
|
+
* `expectedBytes` immediately before the rename. `hooks.beforeReplace` (tests
|
|
198
|
+
* only) runs after the temporary file is complete.
|
|
199
|
+
*/
|
|
200
|
+
function replaceIfUnchanged(file, expectedBytes, text, hooks = {}) {
|
|
201
|
+
const { mode } = fs.statSync(file);
|
|
202
|
+
const temporary = writeTemporarySibling(file, text, mode & 0o7777);
|
|
203
|
+
try {
|
|
204
|
+
if (hooks.beforeReplace)
|
|
205
|
+
hooks.beforeReplace();
|
|
206
|
+
let current;
|
|
207
|
+
try {
|
|
208
|
+
current = fs.readFileSync(file);
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
throw new ArtifactError(`${file} could not be re-read before replacement (${messageOf(err)}); it was not changed.`);
|
|
212
|
+
}
|
|
213
|
+
if (!current.equals(expectedBytes)) {
|
|
214
|
+
throw new ArtifactError(`${file} changed while this command was running; it was not overwritten. Run the command again.`);
|
|
215
|
+
}
|
|
216
|
+
fs.renameSync(temporary, file);
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
discard(temporary);
|
|
220
|
+
}
|
|
221
|
+
syncDirectory(path.dirname(file));
|
|
222
|
+
}
|
|
223
|
+
/** A stat time as the archive stamp, `YYYY-MM-DDTHH-mm-ss.SSSZ` (colons are not portable in names). */
|
|
224
|
+
function archiveStamp(milliseconds) {
|
|
225
|
+
return new Date(milliseconds).toISOString().replace(/:/g, '-');
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Moves an existing `file` aside under its archive name. Returns
|
|
229
|
+
* { path, from, timeSource: 'birth' | 'modified' }, or null if `file` does not
|
|
230
|
+
* exist. `options.stat` (tests only) replaces fs.statSync.
|
|
231
|
+
*/
|
|
232
|
+
function archiveExisting(file, { stat = fs.statSync } = {}) {
|
|
233
|
+
let info;
|
|
234
|
+
try {
|
|
235
|
+
info = stat(file);
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
if (codeOf(err) === 'ENOENT')
|
|
239
|
+
return null;
|
|
240
|
+
throw new ArtifactError(`cannot examine existing output ${file}: ${messageOf(err)}`);
|
|
241
|
+
}
|
|
242
|
+
const birth = Number.isFinite(info.birthtimeMs) && info.birthtimeMs > 0;
|
|
243
|
+
const stamp = archiveStamp(birth ? info.birthtimeMs : info.mtimeMs);
|
|
244
|
+
const dir = path.dirname(file);
|
|
245
|
+
const name = path.basename(file);
|
|
246
|
+
for (let n = 1;; n += 1) {
|
|
247
|
+
const archive = path.join(dir, `${stamp}${n === 1 ? '' : `-${String(n)}`}.old.${name}`);
|
|
248
|
+
try {
|
|
249
|
+
fs.linkSync(file, archive);
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
if (codeOf(err) === 'EEXIST')
|
|
253
|
+
continue;
|
|
254
|
+
throw new ArtifactError(`cannot archive existing output ${file} as ${archive}: ${messageOf(err)}`);
|
|
255
|
+
}
|
|
256
|
+
fs.unlinkSync(file);
|
|
257
|
+
syncDirectory(dir);
|
|
258
|
+
return { path: archive, from: file, timeSource: birth ? 'birth' : 'modified' };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/** True when two existing paths name the same file (including symbolic and hard links). */
|
|
262
|
+
function sameExistingFile(a, b) {
|
|
263
|
+
let sa;
|
|
264
|
+
let sb;
|
|
265
|
+
try {
|
|
266
|
+
sa = fs.statSync(a);
|
|
267
|
+
sb = fs.statSync(b);
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
return sa.dev === sb.dev && sa.ino === sb.ino;
|
|
273
|
+
}
|