Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ Please fill out the checklist below to help us review your PR quickly.

### Checklist

- [ ] My folder is named `<publisher>.<extension-id>` (lowercase, alphanumeric + hyphens only)
- [ ] My folder follows the `<author>/<extension-name>` convention (lowercase, alphanumeric + hyphens only)
- [ ] I have added an `extension.json` with all required fields
- [ ] The `id` field in my `extension.json` matches my folder name
- [ ] The `id` field in my `extension.json` matches my folder path (`author.extension-name`)
- [ ] I have added an icon file (PNG or SVG, under 100 KB)
- [ ] The `icon` field in `extension.json` matches my icon filename
- [ ] My extension is available at the install source I specified (winget/GitHub/Store)
- [ ] My extension is available at the install source I specified (winget/MS Store/URL)
- [ ] I have read the [Contributing Guide](../docs/CONTRIBUTING.md)

### Additional context
Expand Down
116 changes: 116 additions & 0 deletions .github/schemas/extension.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/microsoft/CmdPal-Extensions/main/.github/schemas/extension.schema.json",
"title": "Command Palette Extension",
"description": "Schema for a Command Palette extension submission.",
"type": "object",
"required": ["id", "title", "description", "author", "icon", "installSources"],
"additionalProperties": false,
"properties": {
"$schema": {
"type": "string",
"description": "Reference to this schema file."
},
"id": {
"type": "string",
"description": "Unique extension identifier in author.extension-name format. Must match the folder path (author/extension-name).",
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*\\.[a-z0-9]+(-[a-z0-9]+)*$",
"examples": ["jiripolasek.media-controls", "microsoft.sample-extension"]
},
"title": {
"type": "string",
"description": "Human-readable display name for the extension.",
"minLength": 1,
"maxLength": 100,
"examples": ["Media Controls for Command Palette", "Quick Notes", "Clipboard Manager"]
},
"description": {
"type": "string",
"description": "Short description of the extension.",
"minLength": 1,
"maxLength": 200,
"examples": ["Manage audio or video playback and switch between media apps directly from Command Palette."]
},
"author": {
"type": "object",
"description": "Extension author information.",
"required": ["name"],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "Author display name.",
"minLength": 1,
"maxLength": 100,
"examples": ["Jiri Polasek", "Microsoft"]
},
"url": {
"type": "string",
"description": "URL to the author's website or profile.",
"format": "uri",
"examples": ["https://jiripolasek.com", "https://github.com/microsoft"]
}
}
},
"icon": {
"type": "string",
"description": "Filename of the icon in the same folder. Must be a .png or .svg file.",
"pattern": "^[\\w.-]+\\.(png|svg)$",
"examples": ["icon.png", "icon.svg"]
},
"homepage": {
"type": "string",
"description": "URL to the project homepage or repository.",
"format": "uri",
"examples": ["https://github.com/jiripolasek/MediaControlsExtension"]
},
"tags": {
"type": "array",
"description": "Optional freeform tags for filtering. Maximum 5 tags.",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 30
},
"maxItems": 5,
"uniqueItems": true,
"examples": [["media", "playback", "audio"]]
},
"installSources": {
"type": "array",
"description": "One or more installation sources for this extension.",
"minItems": 1,
"items": {
"type": "object",
"description": "An installation source.",
"required": ["type"],
"oneOf": [

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can an extension have multiple install sources? like, from the store INCLUSIVE_OR from winget?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking we could let the user choose.. but maybe it's overkill? thoughts?

{
"properties": {
"type": { "const": "winget" },
"id": { "type": "string", "description": "Winget package identifier.", "minLength": 1, "examples": ["JiriPolasek.MediaControlsforCommandPalette"] }
},
"required": ["type", "id"],
"additionalProperties": false
},
{
"properties": {
"type": { "const": "msstore" },
"id": { "type": "string", "description": "Microsoft Store product ID.", "minLength": 1, "examples": ["9n3bq81g19k7"] }
},
"required": ["type", "id"],
"additionalProperties": false
},
{
"properties": {
"type": { "const": "url" },
"uri": { "type": "string", "description": "Direct download or release page URL.", "format": "uri", "examples": ["https://github.com/jiripolasek/MediaControlsExtension/releases"] }
},
"required": ["type", "uri"],
"additionalProperties": false
}
]
}
}
}
}
22 changes: 11 additions & 11 deletions scripts/generate.py → .github/scripts/generate.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""Generate the aggregate gallery JSON for CmdPal-Extensions.

Scans extensions/*/extension.json, merges them into a single
generated/extensions.json that the Command Palette app fetches at runtime.
extensions.json at the repo root that the Command Palette app fetches at runtime.

Usage:
python scripts/generate.py
python .github/scripts/generate.py
"""

import glob
Expand All @@ -13,25 +13,24 @@
import sys
from datetime import datetime, timezone

REPO_ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), ".."))
REPO_ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
EXTENSIONS_DIR = os.path.join(REPO_ROOT, "extensions")
OUTPUT_DIR = os.path.join(REPO_ROOT, "generated")
OUTPUT_FILE = os.path.join(OUTPUT_DIR, "extensions.json")
OUTPUT_FILE = os.path.join(REPO_ROOT, "extensions.json")

BASE_RAW_URL = (
"https://raw.githubusercontent.com/microsoft/CmdPal-Extensions/main"
)
GALLERY_SCHEMA_URL = (
f"{BASE_RAW_URL}/schemas/gallery.schema.json"
f"{BASE_RAW_URL}/.github/schemas/gallery.schema.json"
)

# Fields from extension.json that should not appear in the gallery output.
FIELDS_TO_REMOVE = {"$schema", "icon"}


def discover_extension_paths() -> list[str]:
"""Return sorted paths to every extension.json under extensions/."""
pattern = os.path.join(EXTENSIONS_DIR, "*", "extension.json")
"""Return sorted paths to every extension.json under extensions/<author>/<ext>/."""
pattern = os.path.join(EXTENSIONS_DIR, "*", "*", "extension.json")
return sorted(glob.glob(pattern))


Expand Down Expand Up @@ -61,8 +60,10 @@ def load_extension(path: str) -> dict | None:


def build_icon_url(extension_id: str, icon_filename: str) -> str:
"""Build the absolute raw GitHub URL for an extension's icon."""
return f"{BASE_RAW_URL}/extensions/{extension_id}/{icon_filename}"
"""Build the absolute raw GitHub URL for an extension's icon.
The id is author.extension-name, mapping to extensions/author/extension-name/."""
author, ext_name = extension_id.split(".", 1)
return f"{BASE_RAW_URL}/extensions/{author}/{ext_name}/{icon_filename}"


def transform_extension(data: dict) -> dict:
Expand Down Expand Up @@ -118,7 +119,6 @@ def generate_gallery() -> dict:

def write_gallery(gallery: dict) -> None:
"""Write the gallery JSON to the output file."""
os.makedirs(OUTPUT_DIR, exist_ok=True)
with open(OUTPUT_FILE, "w", encoding="utf-8", newline="\n") as f:
json.dump(gallery, f, indent=2, ensure_ascii=False)
f.write("\n")
Expand Down
File renamed without changes.
Loading
Loading