feat: publish Sandwich Hime tooling preview
Publish the exact sanitized Agent Skill and VS Code preview source tree with independent license boundaries, deterministic provenance manifests, and no private development history. Material design and implementation assistance was provided by OpenAI Codex. Signed-off-by: Cole Speelman <crspeelman@gmail.com>
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Command export-public-snapshot creates a host-neutral exact-file snapshot.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
maxFileBytes = 1 << 20
|
||||
maxTotalBytes = 16 << 20
|
||||
maxFiles = 2000
|
||||
)
|
||||
|
||||
type entry struct {
|
||||
Path string
|
||||
Mode os.FileMode
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type snapshot struct {
|
||||
SchemaVersion int `json:"schema_version"`
|
||||
Project string `json:"project"`
|
||||
ExportPolicy string `json:"export_policy"`
|
||||
FileCount int `json:"file_count"`
|
||||
AllowlistSHA256 string `json:"allowlist_sha256"`
|
||||
ManifestSHA256 string `json:"manifest_sha256"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
var source, destination string
|
||||
flag.StringVar(&source, "source", ".", "clean Git worktree root")
|
||||
flag.StringVar(&destination, "destination", "", "new destination directory")
|
||||
flag.Parse()
|
||||
if destination == "" || flag.NArg() != 0 {
|
||||
fatalf("usage: go run ./scripts/export-public-snapshot.go --destination PATH")
|
||||
}
|
||||
|
||||
root := mustGit(source, "rev-parse", "--show-toplevel")
|
||||
root = mustReal(root)
|
||||
source = mustReal(source)
|
||||
if source != root {
|
||||
fatalf("--source must be the Git worktree root")
|
||||
}
|
||||
if strings.TrimSpace(mustGit(root, "status", "--porcelain=v1", "--untracked-files=all")) != "" {
|
||||
fatalf("release source worktree is dirty")
|
||||
}
|
||||
|
||||
parent := mustReal(filepath.Dir(destination))
|
||||
destination = filepath.Join(parent, filepath.Base(destination))
|
||||
if filepath.Base(destination) == "." || filepath.Base(destination) == ".." {
|
||||
fatalf("invalid destination")
|
||||
}
|
||||
if _, err := os.Lstat(destination); !os.IsNotExist(err) {
|
||||
fatalf("destination already exists or cannot be inspected")
|
||||
}
|
||||
gitDir := mustReal(mustGit(root, "rev-parse", "--absolute-git-dir"))
|
||||
common := mustGit(root, "rev-parse", "--git-common-dir")
|
||||
if !filepath.IsAbs(common) {
|
||||
common = filepath.Join(root, common)
|
||||
}
|
||||
common = mustReal(common)
|
||||
for _, protected := range []string{root, gitDir, common} {
|
||||
if atOrBelow(parent, protected) {
|
||||
fatalf("destination must be outside the worktree and Git metadata")
|
||||
}
|
||||
}
|
||||
|
||||
allowPath := "scripts/public-snapshot.allow"
|
||||
allow := mustGitBytes(root, "show", "HEAD:"+allowPath)
|
||||
paths := parseAllowlist(allow)
|
||||
entries := make([]entry, 0, len(paths))
|
||||
total := 0
|
||||
for _, path := range paths {
|
||||
line := mustGit(root, "ls-tree", "HEAD", "--", path)
|
||||
parts := strings.Fields(strings.SplitN(line, "\t", 2)[0])
|
||||
if len(parts) != 3 || parts[1] != "blob" || (parts[0] != "100644" && parts[0] != "100755") || !strings.HasSuffix(line, "\t"+path) {
|
||||
fatalf("allowlisted path is absent or not a regular file: %s", path)
|
||||
}
|
||||
data := mustGitBytes(root, "show", "HEAD:"+path)
|
||||
if len(data) > maxFileBytes || bytes.IndexByte(data, 0) >= 0 {
|
||||
fatalf("allowlisted file is binary or oversized: %s", path)
|
||||
}
|
||||
if unsafeContent(data) {
|
||||
fatalf("private path, key, or credential indicator in: %s", path)
|
||||
}
|
||||
total += len(data)
|
||||
if total > maxTotalBytes {
|
||||
fatalf("snapshot exceeds aggregate size limit")
|
||||
}
|
||||
mode := os.FileMode(0o644)
|
||||
if parts[0] == "100755" {
|
||||
mode = 0o755
|
||||
}
|
||||
entries = append(entries, entry{Path: path, Mode: mode, Data: data})
|
||||
}
|
||||
|
||||
stage, err := os.MkdirTemp(parent, ".sandwich-hime-tooling-public-")
|
||||
if err != nil {
|
||||
fatalf("create staging directory: %v", err)
|
||||
}
|
||||
owned := filepath.Join(stage, ".owned-public-export")
|
||||
if err := os.WriteFile(owned, []byte("owned staging directory\n"), 0o600); err != nil {
|
||||
fatalf("mark staging directory: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if _, err := os.Lstat(owned); err == nil {
|
||||
_ = os.RemoveAll(stage)
|
||||
}
|
||||
}()
|
||||
|
||||
for _, item := range entries {
|
||||
target := filepath.Join(stage, filepath.FromSlash(item.Path))
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
||||
fatalf("create snapshot directory: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(target, item.Data, item.Mode); err != nil {
|
||||
fatalf("write snapshot file: %v", err)
|
||||
}
|
||||
}
|
||||
manifest := buildManifest(entries)
|
||||
if err := os.WriteFile(filepath.Join(stage, "PUBLIC-SNAPSHOT.sha256"), manifest, 0o644); err != nil {
|
||||
fatalf("write manifest: %v", err)
|
||||
}
|
||||
allowSum := sha256.Sum256(allow)
|
||||
manifestSum := sha256.Sum256(manifest)
|
||||
metadata, err := json.Marshal(snapshot{
|
||||
SchemaVersion: 1, Project: "sandwich-hime-tooling", ExportPolicy: "exact-allowlist-v1",
|
||||
FileCount: len(entries), AllowlistSHA256: hex.EncodeToString(allowSum[:]), ManifestSHA256: hex.EncodeToString(manifestSum[:]),
|
||||
})
|
||||
if err != nil {
|
||||
fatalf("encode snapshot metadata: %v", err)
|
||||
}
|
||||
metadata = append(metadata, '\n')
|
||||
if err := os.WriteFile(filepath.Join(stage, "PUBLIC-SNAPSHOT.json"), metadata, 0o644); err != nil {
|
||||
fatalf("write snapshot metadata: %v", err)
|
||||
}
|
||||
_ = os.Remove(owned)
|
||||
epoch := time.Unix(0, 0)
|
||||
_ = filepath.Walk(stage, func(path string, info os.FileInfo, walkErr error) error {
|
||||
if walkErr == nil {
|
||||
_ = os.Chtimes(path, epoch, epoch)
|
||||
}
|
||||
return walkErr
|
||||
})
|
||||
if err := os.Rename(stage, destination); err != nil {
|
||||
fatalf("activate snapshot: %v", err)
|
||||
}
|
||||
fmt.Printf("public snapshot: %d exact files written\n", len(entries))
|
||||
}
|
||||
|
||||
func parseAllowlist(data []byte) []string {
|
||||
seen := map[string]bool{}
|
||||
var paths []string
|
||||
scanner := bufio.NewScanner(bytes.NewReader(data))
|
||||
for scanner.Scan() {
|
||||
path := scanner.Text()
|
||||
if path == "" || strings.HasPrefix(path, "#") {
|
||||
continue
|
||||
}
|
||||
clean := filepath.ToSlash(filepath.Clean(path))
|
||||
lower := strings.ToLower(clean)
|
||||
if clean != path || filepath.IsAbs(path) || strings.ContainsAny(path, "\\\t\r\n") || strings.HasPrefix(path, "../") || path == ".." || seen[path] {
|
||||
fatalf("invalid or duplicate allowlist entry: %q", path)
|
||||
}
|
||||
for _, forbidden := range []string{"/.git/", "/.github/", "/.gitea/", "/private/", "/history/", "/node_modules/", "/dist/", "/artifacts/"} {
|
||||
if strings.Contains("/"+lower+"/", forbidden) {
|
||||
fatalf("forbidden allowlist entry: %s", path)
|
||||
}
|
||||
}
|
||||
seen[path] = true
|
||||
paths = append(paths, path)
|
||||
}
|
||||
if err := scanner.Err(); err != nil || len(paths) == 0 || len(paths) > maxFiles {
|
||||
fatalf("allowlist is empty, oversized, or unreadable")
|
||||
}
|
||||
sort.Strings(paths)
|
||||
return paths
|
||||
}
|
||||
|
||||
func buildManifest(entries []entry) []byte {
|
||||
var out strings.Builder
|
||||
for _, item := range entries {
|
||||
sum := sha256.Sum256(item.Data)
|
||||
fmt.Fprintf(&out, "%x ./%s\n", sum, item.Path)
|
||||
}
|
||||
return []byte(out.String())
|
||||
}
|
||||
|
||||
func unsafeContent(data []byte) bool {
|
||||
lower := strings.ToLower(string(data))
|
||||
begin := "-----begin "
|
||||
markers := []string{
|
||||
begin + "private key-----", begin + "rsa private key-----", begin + "openssh private key-----",
|
||||
"/" + "home/" + "cole/", "c:\\" + "users\\" + "cole\\", "/mnt/c/" + "users/" + "cole/",
|
||||
"gh" + "p_", "gl" + "pat-", "xo" + "xb-",
|
||||
}
|
||||
for _, marker := range markers {
|
||||
if strings.Contains(lower, marker) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func mustGit(root string, args ...string) string {
|
||||
return strings.TrimSpace(string(mustGitBytes(root, args...)))
|
||||
}
|
||||
|
||||
func mustGitBytes(root string, args ...string) []byte {
|
||||
cmd := exec.Command("git", append([]string{"-C", root}, args...)...)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
fatalf("git %s failed", strings.Join(args, " "))
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
func mustReal(path string) string {
|
||||
abs, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
fatalf("resolve path: %v", err)
|
||||
}
|
||||
real, err := filepath.EvalSymlinks(abs)
|
||||
if err != nil {
|
||||
fatalf("resolve path: %v", err)
|
||||
}
|
||||
return filepath.Clean(real)
|
||||
}
|
||||
|
||||
func atOrBelow(path, root string) bool {
|
||||
rel, err := filepath.Rel(root, path)
|
||||
return err == nil && rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator))
|
||||
}
|
||||
|
||||
func fatalf(format string, args ...any) {
|
||||
fmt.Fprintf(os.Stderr, "public snapshot: "+format+"\n", args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
'use strict';
|
||||
|
||||
const childProcess = require('node:child_process');
|
||||
const crypto = require('node:crypto');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const root = path.join(__dirname, '..');
|
||||
const artifacts = path.join(root, 'artifacts');
|
||||
const editor = path.join(root, 'editors', 'vscode');
|
||||
fs.mkdirSync(artifacts, {recursive: true});
|
||||
|
||||
function run(command, args, cwd) {
|
||||
const result = childProcess.spawnSync(command, args, {cwd, encoding: 'utf8'});
|
||||
if (result.status !== 0) throw new Error(`${command} failed:\n${result.stderr || result.stdout}`);
|
||||
}
|
||||
|
||||
run('go', ['run', './scripts/package-skill.go'], root);
|
||||
run(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', 'package:vsix'], editor);
|
||||
|
||||
const sbom = childProcess.spawnSync('node', ['scripts/sbom.js'], {cwd: editor, encoding: 'utf8'});
|
||||
if (sbom.status !== 0) throw new Error(`SBOM generation failed:\n${sbom.stderr}`);
|
||||
const sbomName = 'sandwich-hime-0.1.0-preview.1.cdx.json';
|
||||
fs.writeFileSync(path.join(artifacts, sbomName), sbom.stdout, {encoding: 'utf8', mode: 0o644});
|
||||
|
||||
const names = [
|
||||
'sandwich-hime-skill-v0.1.0.zip',
|
||||
'sandwich-hime-0.1.0-preview.1.vsix',
|
||||
sbomName,
|
||||
];
|
||||
const sums = names.map((name) => {
|
||||
const bytes = fs.readFileSync(path.join(artifacts, name));
|
||||
return `${crypto.createHash('sha256').update(bytes).digest('hex')} ${name}`;
|
||||
});
|
||||
fs.writeFileSync(path.join(artifacts, 'SHA256SUMS'), `${sums.join('\n')}\n`, {encoding: 'utf8', mode: 0o644});
|
||||
console.log(sums.join('\n'));
|
||||
@@ -0,0 +1,104 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var skillFiles = []string{
|
||||
"LICENSE",
|
||||
"SKILL.md",
|
||||
"agents/openai.yaml",
|
||||
"references/authoring.md",
|
||||
"references/workflows.md",
|
||||
}
|
||||
|
||||
func main() {
|
||||
root, err := filepath.Abs(filepath.Join("skills", "sandwich-hime"))
|
||||
must(err)
|
||||
artifactDir := "artifacts"
|
||||
must(os.MkdirAll(artifactDir, 0o755))
|
||||
outputPath := filepath.Join(artifactDir, "sandwich-hime-skill-v0.1.0.zip")
|
||||
file, err := os.OpenFile(outputPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
|
||||
must(err)
|
||||
archive := zip.NewWriter(file)
|
||||
fixed := time.Date(1980, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
sort.Strings(skillFiles)
|
||||
for _, relative := range skillFiles {
|
||||
path := filepath.Join(root, filepath.FromSlash(relative))
|
||||
info, err := os.Lstat(path)
|
||||
must(err)
|
||||
if !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 {
|
||||
panic("skill archive input is not a regular file: " + relative)
|
||||
}
|
||||
header := &zip.FileHeader{Name: "sandwich-hime/" + relative, Method: zip.Deflate}
|
||||
header.SetModTime(fixed)
|
||||
header.SetMode(0o644)
|
||||
writer, err := archive.CreateHeader(header)
|
||||
must(err)
|
||||
source, err := os.Open(path)
|
||||
must(err)
|
||||
_, copyErr := io.Copy(writer, source)
|
||||
closeErr := source.Close()
|
||||
must(copyErr)
|
||||
must(closeErr)
|
||||
}
|
||||
must(archive.Close())
|
||||
must(file.Close())
|
||||
|
||||
verifyExactTree(root)
|
||||
content, err := os.ReadFile(outputPath)
|
||||
must(err)
|
||||
digest := fmt.Sprintf("%x", sha256.Sum256(content))
|
||||
must(os.WriteFile(filepath.Join(artifactDir, "sandwich-hime-skill-v0.1.0.zip.sha256"), []byte(digest+" "+filepath.Base(outputPath)+"\n"), 0o644))
|
||||
fmt.Printf("%s %s\n", digest, outputPath)
|
||||
}
|
||||
|
||||
func verifyExactTree(root string) {
|
||||
want := make(map[string]bool, len(skillFiles))
|
||||
for _, file := range skillFiles {
|
||||
want[file] = true
|
||||
}
|
||||
var actual []string
|
||||
must(filepath.WalkDir(root, func(path string, entry os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if entry.IsDir() {
|
||||
return nil
|
||||
}
|
||||
relative, err := filepath.Rel(root, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
relative = filepath.ToSlash(relative)
|
||||
actual = append(actual, relative)
|
||||
if !want[relative] {
|
||||
return fmt.Errorf("unexpected skill file: %s", relative)
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
if len(actual) != len(want) {
|
||||
panic(fmt.Sprintf("skill file count = %d, want %d", len(actual), len(want)))
|
||||
}
|
||||
for _, file := range actual {
|
||||
if strings.TrimSpace(file) == "" {
|
||||
panic("empty skill filename")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# Exact public source files; no directories or globs.
|
||||
.gitattributes
|
||||
.gitignore
|
||||
CONTRIBUTING.md
|
||||
COPYRIGHT
|
||||
DCO.txt
|
||||
LICENSE
|
||||
LICENSES.md
|
||||
README.md
|
||||
RELEASE.md
|
||||
SECURITY.md
|
||||
editors/vscode/.vscodeignore
|
||||
editors/vscode/LICENSE
|
||||
editors/vscode/README.md
|
||||
editors/vscode/SECURITY.md
|
||||
editors/vscode/THIRD_PARTY_NOTICES.md
|
||||
editors/vscode/language-configuration.json
|
||||
editors/vscode/package-lock.json
|
||||
editors/vscode/package.json
|
||||
editors/vscode/scripts/audit-dependencies.js
|
||||
editors/vscode/scripts/audit-package.js
|
||||
editors/vscode/scripts/build.js
|
||||
editors/vscode/scripts/clean.js
|
||||
editors/vscode/scripts/sbom.js
|
||||
editors/vscode/snippets/sando.json
|
||||
editors/vscode/src/core.js
|
||||
editors/vscode/src/extension.js
|
||||
editors/vscode/syntaxes/sando.tmLanguage.json
|
||||
editors/vscode/test/grammar/fixtures/mixed.sando
|
||||
editors/vscode/test/grammar/grammar.test.js
|
||||
editors/vscode/test/integration/run.js
|
||||
editors/vscode/test/integration/suite/index.js
|
||||
editors/vscode/test/unit/core.test.js
|
||||
editors/vscode/test/workspace/cards/badge.sando
|
||||
editors/vscode/test/workspace/go.mod
|
||||
editors/vscode/test/workspace/views/badge.sando
|
||||
editors/vscode/test/workspace/views/home.sando
|
||||
scripts/export-public-snapshot.go
|
||||
scripts/package-release.js
|
||||
scripts/package-skill.go
|
||||
scripts/public-snapshot.allow
|
||||
scripts/test-public-snapshot.sh
|
||||
scripts/validate-skill.js
|
||||
scripts/verify.ps1
|
||||
scripts/verify.sh
|
||||
skills/sandwich-hime/LICENSE
|
||||
skills/sandwich-hime/SKILL.md
|
||||
skills/sandwich-hime/agents/openai.yaml
|
||||
skills/sandwich-hime/references/authoring.md
|
||||
skills/sandwich-hime/references/workflows.md
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
set -euo pipefail
|
||||
root=$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
[[ -z $(git -C "$root" status --porcelain=v1 --untracked-files=all) ]] || {
|
||||
echo "snapshot test requires a clean committed worktree" >&2
|
||||
exit 1
|
||||
}
|
||||
parent=$(mktemp -d)
|
||||
trap 'rm -rf -- "$parent"' EXIT
|
||||
destination=$parent/public
|
||||
go run "$root/scripts/export-public-snapshot.go" --source "$root" --destination "$destination"
|
||||
test -f "$destination/PUBLIC-SNAPSHOT.json"
|
||||
test -f "$destination/PUBLIC-SNAPSHOT.sha256"
|
||||
test ! -e "$destination/.git"
|
||||
(cd "$destination" && sha256sum -c PUBLIC-SNAPSHOT.sha256)
|
||||
if go run "$root/scripts/export-public-snapshot.go" --source "$root" --destination "$destination"; then
|
||||
echo "snapshot exporter altered an existing destination" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
'use strict';
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const root = path.join(__dirname, '..', 'skills', 'sandwich-hime');
|
||||
const expected = ['LICENSE', 'SKILL.md', 'agents/openai.yaml', 'references/authoring.md', 'references/workflows.md'].sort();
|
||||
const actual = [];
|
||||
function walk(directory) {
|
||||
for (const entry of fs.readdirSync(directory, {withFileTypes: true})) {
|
||||
const target = path.join(directory, entry.name);
|
||||
if (entry.isSymbolicLink()) throw new Error(`skill symlink is forbidden: ${target}`);
|
||||
if (entry.isDirectory()) walk(target);
|
||||
else if (entry.isFile()) actual.push(path.relative(root, target).split(path.sep).join('/'));
|
||||
else throw new Error(`skill entry is not regular: ${target}`);
|
||||
}
|
||||
}
|
||||
walk(root);
|
||||
assertEqual(actual.sort(), expected, 'skill exact file allowlist');
|
||||
const skill = fs.readFileSync(path.join(root, 'SKILL.md'), 'utf8');
|
||||
if (!skill.startsWith('---\nname: sandwich-hime\ndescription: ') || !skill.includes('\n---\n')) throw new Error('SKILL.md frontmatter invalid');
|
||||
if ((skill.match(/^---$/gm) || []).length !== 2) throw new Error('SKILL.md frontmatter delimiters invalid');
|
||||
if (skill.split('\n').length > 500) throw new Error('SKILL.md exceeds 500 lines');
|
||||
for (const marker of ['himesan version --json', 'himesan check --json', 'Never hand-edit generated files', 'never execute `himesan dev`', '`.san` exclusively']) {
|
||||
if (!skill.toLowerCase().includes(marker.toLowerCase())) throw new Error(`skill safety marker missing: ${marker}`);
|
||||
}
|
||||
const metadata = fs.readFileSync(path.join(root, 'agents', 'openai.yaml'), 'utf8');
|
||||
for (const marker of ['display_name: "Sandwich Hime"', '$sandwich-hime']) if (!metadata.includes(marker)) throw new Error(`agent metadata marker missing: ${marker}`);
|
||||
console.log('portable skill exact-tree and safety contract verified');
|
||||
function assertEqual(left, right, label) { if (JSON.stringify(left) !== JSON.stringify(right)) throw new Error(`${label}: ${JSON.stringify(left)} != ${JSON.stringify(right)}`); }
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
$ErrorActionPreference = "Stop"
|
||||
$Root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||
Set-Location $Root
|
||||
node scripts/validate-skill.js
|
||||
if ($LASTEXITCODE -ne 0) { throw "skill validation failed" }
|
||||
go run ./scripts/package-skill.go
|
||||
if ($LASTEXITCODE -ne 0) { throw "skill packaging failed" }
|
||||
Push-Location editors/vscode
|
||||
try {
|
||||
npm run verify
|
||||
if ($LASTEXITCODE -ne 0) { throw "VS Code verification failed" }
|
||||
$FirstSBOM = (& node scripts/sbom.js) -join "`n"
|
||||
if ($LASTEXITCODE -ne 0) { throw "SBOM generation failed" }
|
||||
$SecondSBOM = (& node scripts/sbom.js) -join "`n"
|
||||
if ($LASTEXITCODE -ne 0) { throw "SBOM generation failed" }
|
||||
if ($FirstSBOM -cne $SecondSBOM) { throw "SBOM output is not deterministic" }
|
||||
}
|
||||
finally { Pop-Location }
|
||||
git diff --check
|
||||
if ($LASTEXITCODE -ne 0) { throw "git diff check failed" }
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
set -euo pipefail
|
||||
root=$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
cd "$root"
|
||||
node scripts/validate-skill.js
|
||||
go run ./scripts/package-skill.go
|
||||
(cd editors/vscode && npm run verify)
|
||||
first_sbom=$(mktemp)
|
||||
second_sbom=$(mktemp)
|
||||
trap 'rm -f -- "$first_sbom" "$second_sbom"' EXIT
|
||||
node editors/vscode/scripts/sbom.js >"$first_sbom"
|
||||
node editors/vscode/scripts/sbom.js >"$second_sbom"
|
||||
cmp -s "$first_sbom" "$second_sbom"
|
||||
git diff --check
|
||||
Reference in New Issue
Block a user