Initial commit
This commit is contained in:
166
skills/artifact.scaffold/README.md
Normal file
166
skills/artifact.scaffold/README.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# artifact.scaffold
|
||||
|
||||
Generate new artifact templates automatically from metadata inputs.
|
||||
|
||||
## Overview
|
||||
|
||||
The `artifact.scaffold` skill creates fully compliant artifact descriptors in one call. It generates valid `.artifact.yaml` files, assigns auto-incremented versions starting at 0.1.0, and registers artifacts in the artifacts registry.
|
||||
|
||||
## Features
|
||||
|
||||
- **Automatic Generation**: Creates artifact YAML files from metadata inputs
|
||||
- **Schema Definition**: Supports field definitions with types, descriptions, and required flags
|
||||
- **Inheritance**: Supports extending from base artifacts
|
||||
- **Registry Management**: Automatically registers artifacts in `registry/artifacts.json`
|
||||
- **Validation**: Optional `--validate` flag to validate generated artifacts
|
||||
- **Version Management**: Auto-assigns version 0.1.0 to new artifacts
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Example
|
||||
|
||||
```bash
|
||||
python3 skills/artifact.scaffold/artifact_scaffold.py \
|
||||
--id "new.artifact" \
|
||||
--category "report"
|
||||
```
|
||||
|
||||
### With Field Definitions
|
||||
|
||||
```bash
|
||||
python3 skills/artifact.scaffold/artifact_scaffold.py \
|
||||
--id "new.artifact" \
|
||||
--category "report" \
|
||||
--fields '[{"name":"summary","type":"string","description":"Summary field","required":true}]'
|
||||
```
|
||||
|
||||
### With Inheritance and Validation
|
||||
|
||||
```bash
|
||||
python3 skills/artifact.scaffold/artifact_scaffold.py \
|
||||
--id "new.artifact" \
|
||||
--category "report" \
|
||||
--extends "base.artifact" \
|
||||
--fields '[{"name":"summary","type":"string"}]' \
|
||||
--validate
|
||||
```
|
||||
|
||||
### Custom Output Path
|
||||
|
||||
```bash
|
||||
python3 skills/artifact.scaffold/artifact_scaffold.py \
|
||||
--id "new.artifact" \
|
||||
--category "report" \
|
||||
--output "custom/path/artifact.yaml"
|
||||
```
|
||||
|
||||
## Input Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `--id` | string | Yes | Unique identifier for the artifact (e.g., "new.artifact") |
|
||||
| `--category` | string | Yes | Category/type of artifact (e.g., "report", "specification") |
|
||||
| `--extends` | string | No | Base artifact to extend from |
|
||||
| `--fields` | JSON array | No | Field definitions with name, type, description, and required properties |
|
||||
| `--output` | string | No | Custom output path for the artifact file |
|
||||
| `--validate` | flag | No | Validate the artifact after generation |
|
||||
|
||||
## Field Definition Format
|
||||
|
||||
Fields are provided as a JSON array with the following structure:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "field_name",
|
||||
"type": "string|number|boolean|object|array",
|
||||
"description": "Field description",
|
||||
"required": true|false
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
The skill outputs a JSON response with the following structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"status": "success",
|
||||
"artifact_id": "new.artifact",
|
||||
"file_path": "/path/to/artifact.yaml",
|
||||
"version": "0.1.0",
|
||||
"category": "report",
|
||||
"registry_path": "/path/to/registry/artifacts.json",
|
||||
"artifacts_registered": 1,
|
||||
"validation": {
|
||||
"valid": true,
|
||||
"errors": [],
|
||||
"warnings": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Generated Artifact Structure
|
||||
|
||||
The skill generates artifact YAML files with the following structure:
|
||||
|
||||
```yaml
|
||||
id: new.artifact
|
||||
version: 0.1.0
|
||||
category: report
|
||||
created_at: '2025-10-26T00:00:00.000000Z'
|
||||
metadata:
|
||||
description: new.artifact artifact
|
||||
tags:
|
||||
- report
|
||||
extends: base.artifact # Optional
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
summary:
|
||||
type: string
|
||||
description: Summary field
|
||||
required:
|
||||
- summary
|
||||
```
|
||||
|
||||
## Registry Management
|
||||
|
||||
Artifacts are automatically registered in `registry/artifacts.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"registry_version": "1.0.0",
|
||||
"generated_at": "2025-10-26T00:00:00.000000Z",
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "new.artifact",
|
||||
"version": "0.1.0",
|
||||
"category": "report",
|
||||
"created_at": "2025-10-26T00:00:00.000000Z",
|
||||
"description": "new.artifact artifact",
|
||||
"tags": ["report"],
|
||||
"extends": "base.artifact",
|
||||
"schema": { ... }
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `artifact.define`: For artifact type definitions and validation
|
||||
|
||||
## Status
|
||||
|
||||
**Active** - Ready for production use
|
||||
|
||||
## Tags
|
||||
|
||||
- artifacts
|
||||
- scaffolding
|
||||
- generation
|
||||
- templates
|
||||
- metadata
|
||||
1
skills/artifact.scaffold/__init__.py
Normal file
1
skills/artifact.scaffold/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Auto-generated package initializer for skills.
|
||||
410
skills/artifact.scaffold/artifact_scaffold.py
Executable file
410
skills/artifact.scaffold/artifact_scaffold.py
Executable file
@@ -0,0 +1,410 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
artifact_scaffold.py - Generate new artifact templates automatically from metadata inputs
|
||||
|
||||
Creates compliant artifact descriptors, registers them in the registry, and optionally validates them.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import yaml
|
||||
import argparse
|
||||
from typing import Dict, Any, List, Optional
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
from betty.config import BASE_DIR
|
||||
from betty.logging_utils import setup_logger
|
||||
from betty.errors import format_error_response
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
# Default artifact directories
|
||||
ARTIFACTS_DIR = os.path.join(BASE_DIR, "artifacts")
|
||||
REGISTRY_DIR = os.path.join(BASE_DIR, "registry")
|
||||
ARTIFACTS_REGISTRY_FILE = os.path.join(REGISTRY_DIR, "artifacts.json")
|
||||
|
||||
|
||||
def ensure_directories():
|
||||
"""Ensure required directories exist"""
|
||||
os.makedirs(ARTIFACTS_DIR, exist_ok=True)
|
||||
os.makedirs(REGISTRY_DIR, exist_ok=True)
|
||||
|
||||
|
||||
def load_artifacts_registry() -> Dict[str, Any]:
|
||||
"""Load the artifacts registry, or create a new one if it doesn't exist"""
|
||||
if os.path.exists(ARTIFACTS_REGISTRY_FILE):
|
||||
try:
|
||||
with open(ARTIFACTS_REGISTRY_FILE, 'r') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to load artifacts registry: {e}")
|
||||
return create_empty_registry()
|
||||
else:
|
||||
return create_empty_registry()
|
||||
|
||||
|
||||
def create_empty_registry() -> Dict[str, Any]:
|
||||
"""Create a new empty artifacts registry"""
|
||||
return {
|
||||
"registry_version": "1.0.0",
|
||||
"generated_at": datetime.utcnow().isoformat() + "Z",
|
||||
"artifacts": []
|
||||
}
|
||||
|
||||
|
||||
def save_artifacts_registry(registry: Dict[str, Any]):
|
||||
"""Save the artifacts registry"""
|
||||
registry["generated_at"] = datetime.utcnow().isoformat() + "Z"
|
||||
with open(ARTIFACTS_REGISTRY_FILE, 'w') as f:
|
||||
json.dump(registry, f, indent=2)
|
||||
logger.info(f"Saved artifacts registry to {ARTIFACTS_REGISTRY_FILE}")
|
||||
|
||||
|
||||
def generate_artifact_yaml(
|
||||
artifact_id: str,
|
||||
category: str,
|
||||
extends: Optional[str] = None,
|
||||
fields: Optional[List[Dict[str, str]]] = None,
|
||||
version: str = "0.1.0"
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate an artifact YAML structure
|
||||
|
||||
Args:
|
||||
artifact_id: Unique identifier for the artifact (e.g., "new.artifact")
|
||||
category: Category/type of artifact (e.g., "report", "specification")
|
||||
extends: Optional base artifact to extend from
|
||||
fields: List of field definitions with name and type
|
||||
version: Semantic version (default: 0.1.0)
|
||||
|
||||
Returns:
|
||||
Dictionary representing the artifact structure
|
||||
"""
|
||||
artifact = {
|
||||
"id": artifact_id,
|
||||
"version": version,
|
||||
"category": category,
|
||||
"created_at": datetime.utcnow().isoformat() + "Z",
|
||||
"metadata": {
|
||||
"description": f"{artifact_id} artifact",
|
||||
"tags": [category]
|
||||
}
|
||||
}
|
||||
|
||||
if extends:
|
||||
artifact["extends"] = extends
|
||||
|
||||
if fields:
|
||||
artifact["schema"] = {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
|
||||
for field in fields:
|
||||
field_name = field.get("name", "")
|
||||
field_type = field.get("type", "string")
|
||||
field_description = field.get("description", f"{field_name} field")
|
||||
field_required = field.get("required", False)
|
||||
|
||||
artifact["schema"]["properties"][field_name] = {
|
||||
"type": field_type,
|
||||
"description": field_description
|
||||
}
|
||||
|
||||
if field_required:
|
||||
artifact["schema"]["required"].append(field_name)
|
||||
|
||||
return artifact
|
||||
|
||||
|
||||
def get_artifact_filename(artifact_id: str) -> str:
|
||||
"""
|
||||
Generate filename for artifact YAML file
|
||||
|
||||
Args:
|
||||
artifact_id: The artifact ID (e.g., "new.artifact")
|
||||
|
||||
Returns:
|
||||
Filename in format: {artifact_id}.artifact.yaml
|
||||
"""
|
||||
# Replace dots with hyphens for filename
|
||||
safe_id = artifact_id.replace(".", "-")
|
||||
return f"{safe_id}.artifact.yaml"
|
||||
|
||||
|
||||
def save_artifact_yaml(artifact: Dict[str, Any], output_path: Optional[str] = None) -> str:
|
||||
"""
|
||||
Save artifact to YAML file
|
||||
|
||||
Args:
|
||||
artifact: The artifact dictionary
|
||||
output_path: Optional custom output path
|
||||
|
||||
Returns:
|
||||
Path to the saved file
|
||||
"""
|
||||
artifact_id = artifact["id"]
|
||||
|
||||
if output_path:
|
||||
file_path = output_path
|
||||
else:
|
||||
filename = get_artifact_filename(artifact_id)
|
||||
file_path = os.path.join(ARTIFACTS_DIR, filename)
|
||||
|
||||
with open(file_path, 'w') as f:
|
||||
yaml.dump(artifact, f, default_flow_style=False, sort_keys=False)
|
||||
|
||||
logger.info(f"Saved artifact to {file_path}")
|
||||
return file_path
|
||||
|
||||
|
||||
def register_artifact(artifact: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Register artifact in the artifacts registry
|
||||
|
||||
Args:
|
||||
artifact: The artifact dictionary
|
||||
|
||||
Returns:
|
||||
The updated registry
|
||||
"""
|
||||
registry = load_artifacts_registry()
|
||||
|
||||
# Check if artifact already exists
|
||||
artifact_id = artifact["id"]
|
||||
existing_idx = None
|
||||
for idx, reg_artifact in enumerate(registry["artifacts"]):
|
||||
if reg_artifact["id"] == artifact_id:
|
||||
existing_idx = idx
|
||||
break
|
||||
|
||||
# Create registry entry
|
||||
registry_entry = {
|
||||
"id": artifact["id"],
|
||||
"version": artifact["version"],
|
||||
"category": artifact["category"],
|
||||
"created_at": artifact["created_at"],
|
||||
"description": artifact.get("metadata", {}).get("description", ""),
|
||||
"tags": artifact.get("metadata", {}).get("tags", [])
|
||||
}
|
||||
|
||||
if "extends" in artifact:
|
||||
registry_entry["extends"] = artifact["extends"]
|
||||
|
||||
if "schema" in artifact:
|
||||
registry_entry["schema"] = artifact["schema"]
|
||||
|
||||
# Update or add entry
|
||||
if existing_idx is not None:
|
||||
registry["artifacts"][existing_idx] = registry_entry
|
||||
logger.info(f"Updated artifact {artifact_id} in registry")
|
||||
else:
|
||||
registry["artifacts"].append(registry_entry)
|
||||
logger.info(f"Added artifact {artifact_id} to registry")
|
||||
|
||||
save_artifacts_registry(registry)
|
||||
return registry
|
||||
|
||||
|
||||
def scaffold_artifact(
|
||||
artifact_id: str,
|
||||
category: str,
|
||||
extends: Optional[str] = None,
|
||||
fields: Optional[List[Dict[str, str]]] = None,
|
||||
output_path: Optional[str] = None,
|
||||
validate: bool = False
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Main scaffolding function
|
||||
|
||||
Args:
|
||||
artifact_id: Unique identifier for the artifact
|
||||
category: Category/type of artifact
|
||||
extends: Optional base artifact to extend from
|
||||
fields: List of field definitions
|
||||
output_path: Optional custom output path
|
||||
validate: Whether to run validation after scaffolding
|
||||
|
||||
Returns:
|
||||
Result dictionary with status and details
|
||||
"""
|
||||
try:
|
||||
ensure_directories()
|
||||
|
||||
# Generate artifact structure
|
||||
artifact = generate_artifact_yaml(
|
||||
artifact_id=artifact_id,
|
||||
category=category,
|
||||
extends=extends,
|
||||
fields=fields
|
||||
)
|
||||
|
||||
# Save to file
|
||||
file_path = save_artifact_yaml(artifact, output_path)
|
||||
|
||||
# Register in artifacts registry
|
||||
registry = register_artifact(artifact)
|
||||
|
||||
result = {
|
||||
"ok": True,
|
||||
"status": "success",
|
||||
"artifact_id": artifact_id,
|
||||
"file_path": file_path,
|
||||
"version": artifact["version"],
|
||||
"category": category,
|
||||
"registry_path": ARTIFACTS_REGISTRY_FILE,
|
||||
"artifacts_registered": len(registry["artifacts"])
|
||||
}
|
||||
|
||||
# Optional validation
|
||||
if validate:
|
||||
validation_result = validate_artifact(file_path)
|
||||
result["validation"] = validation_result
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to scaffold artifact: {e}", exc_info=True)
|
||||
return {
|
||||
"ok": False,
|
||||
"status": "failed",
|
||||
"error": str(e),
|
||||
"details": format_error_response(e)
|
||||
}
|
||||
|
||||
|
||||
def validate_artifact(file_path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Validate an artifact YAML file
|
||||
|
||||
Args:
|
||||
file_path: Path to the artifact YAML file
|
||||
|
||||
Returns:
|
||||
Validation result dictionary
|
||||
"""
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
artifact = yaml.safe_load(f)
|
||||
|
||||
errors = []
|
||||
warnings = []
|
||||
|
||||
# Required fields
|
||||
required_fields = ["id", "version", "category", "created_at"]
|
||||
for field in required_fields:
|
||||
if field not in artifact:
|
||||
errors.append(f"Missing required field: {field}")
|
||||
|
||||
# Version format check
|
||||
if "version" in artifact:
|
||||
version = artifact["version"]
|
||||
parts = version.split(".")
|
||||
if len(parts) != 3 or not all(p.isdigit() for p in parts):
|
||||
warnings.append(f"Version {version} may not follow semantic versioning (X.Y.Z)")
|
||||
|
||||
# Category check
|
||||
if "category" in artifact and not artifact["category"]:
|
||||
warnings.append("Category is empty")
|
||||
|
||||
# Schema validation
|
||||
if "schema" in artifact:
|
||||
schema = artifact["schema"]
|
||||
if "properties" not in schema:
|
||||
warnings.append("Schema missing 'properties' field")
|
||||
|
||||
is_valid = len(errors) == 0
|
||||
|
||||
return {
|
||||
"valid": is_valid,
|
||||
"errors": errors,
|
||||
"warnings": warnings,
|
||||
"file_path": file_path
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"valid": False,
|
||||
"errors": [f"Failed to validate: {str(e)}"],
|
||||
"warnings": [],
|
||||
"file_path": file_path
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate new artifact templates from metadata"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--id",
|
||||
required=True,
|
||||
help="Artifact ID (e.g., 'new.artifact')"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--category",
|
||||
required=True,
|
||||
help="Artifact category (e.g., 'report', 'specification')"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--extends",
|
||||
help="Base artifact to extend from (optional)"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--fields",
|
||||
help="JSON string of field definitions (e.g., '[{\"name\":\"summary\",\"type\":\"string\"}]')"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
help="Custom output path for the artifact file"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--validate",
|
||||
action="store_true",
|
||||
help="Validate the artifact after generation"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Parse fields if provided
|
||||
fields = None
|
||||
if args.fields:
|
||||
try:
|
||||
fields = json.loads(args.fields)
|
||||
except json.JSONDecodeError as e:
|
||||
print(json.dumps({
|
||||
"ok": False,
|
||||
"status": "failed",
|
||||
"error": f"Invalid JSON for fields: {e}"
|
||||
}, indent=2))
|
||||
sys.exit(1)
|
||||
|
||||
# Scaffold the artifact
|
||||
result = scaffold_artifact(
|
||||
artifact_id=args.id,
|
||||
category=args.category,
|
||||
extends=args.extends,
|
||||
fields=fields,
|
||||
output_path=args.output,
|
||||
validate=args.validate
|
||||
)
|
||||
|
||||
# Output result as JSON
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
# Exit with appropriate code
|
||||
sys.exit(0 if result.get("ok", False) else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
136
skills/artifact.scaffold/skill.yaml
Normal file
136
skills/artifact.scaffold/skill.yaml
Normal file
@@ -0,0 +1,136 @@
|
||||
name: artifact.scaffold
|
||||
version: 0.1.0
|
||||
description: >
|
||||
Generate new artifact templates automatically from metadata inputs.
|
||||
Creates fully compliant artifact descriptors with auto-incremented versions,
|
||||
saves them as .artifact.yaml files, and registers them in the artifacts registry.
|
||||
Supports optional validation of generated artifacts.
|
||||
|
||||
inputs:
|
||||
- name: id
|
||||
type: string
|
||||
required: true
|
||||
description: Unique identifier for the artifact (e.g., "new.artifact")
|
||||
|
||||
- name: category
|
||||
type: string
|
||||
required: true
|
||||
description: Category/type of artifact (e.g., "report", "specification")
|
||||
|
||||
- name: extends
|
||||
type: string
|
||||
required: false
|
||||
description: Optional base artifact to extend from (e.g., "base.artifact")
|
||||
|
||||
- name: fields
|
||||
type: array
|
||||
required: false
|
||||
description: List of field definitions with name, type, description, and required properties
|
||||
|
||||
- name: output
|
||||
type: string
|
||||
required: false
|
||||
description: Custom output path for the artifact file (defaults to artifacts/{id}.artifact.yaml)
|
||||
|
||||
- name: validate
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
description: Whether to validate the artifact after generation
|
||||
|
||||
outputs:
|
||||
- name: artifact_id
|
||||
type: string
|
||||
description: ID of the generated artifact
|
||||
|
||||
- name: file_path
|
||||
type: string
|
||||
description: Path to the generated artifact YAML file
|
||||
|
||||
- name: version
|
||||
type: string
|
||||
description: Version assigned to the artifact (default 0.1.0)
|
||||
|
||||
- name: category
|
||||
type: string
|
||||
description: Category of the artifact
|
||||
|
||||
- name: registry_path
|
||||
type: string
|
||||
description: Path to the artifacts registry
|
||||
|
||||
- name: artifacts_registered
|
||||
type: integer
|
||||
description: Total number of artifacts in the registry
|
||||
|
||||
- name: validation
|
||||
type: object
|
||||
required: false
|
||||
description: Validation results if --validate flag was used
|
||||
|
||||
dependencies:
|
||||
- artifact.define
|
||||
|
||||
entrypoints:
|
||||
- command: /skill/artifact/scaffold
|
||||
handler: artifact_scaffold.py
|
||||
runtime: python
|
||||
description: >
|
||||
Generate a new artifact template from metadata. Creates a valid .artifact.yaml
|
||||
file with the specified structure, registers it in the artifacts registry,
|
||||
and optionally validates the output.
|
||||
parameters:
|
||||
- name: id
|
||||
type: string
|
||||
required: true
|
||||
description: Artifact ID in namespace.name format
|
||||
- name: category
|
||||
type: string
|
||||
required: true
|
||||
description: Artifact category
|
||||
- name: extends
|
||||
type: string
|
||||
required: false
|
||||
description: Base artifact to extend
|
||||
- name: fields
|
||||
type: array
|
||||
required: false
|
||||
description: Field definitions as JSON array
|
||||
- name: output
|
||||
type: string
|
||||
required: false
|
||||
description: Custom output path
|
||||
- name: validate
|
||||
type: boolean
|
||||
required: false
|
||||
description: Validate after generation
|
||||
permissions:
|
||||
- filesystem:read
|
||||
- filesystem:write
|
||||
|
||||
status: active
|
||||
|
||||
tags:
|
||||
- artifacts
|
||||
- scaffolding
|
||||
- generation
|
||||
- templates
|
||||
- metadata
|
||||
|
||||
artifact_metadata:
|
||||
produces:
|
||||
- type: artifact-definition
|
||||
description: Generated artifact YAML descriptor file
|
||||
file_pattern: "*.artifact.yaml"
|
||||
content_type: application/yaml
|
||||
|
||||
- type: artifact-registry
|
||||
description: Updated artifacts registry with new entries
|
||||
file_pattern: "registry/artifacts.json"
|
||||
content_type: application/json
|
||||
|
||||
consumes:
|
||||
- type: artifact-metadata
|
||||
description: Optional artifact metadata for extension
|
||||
file_pattern: "*.artifact.yaml"
|
||||
content_type: application/yaml
|
||||
Reference in New Issue
Block a user