diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..6e2aac9 --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "extensions-dev", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "dev"], + "port": 4321 + } + ] +} diff --git a/openapi/extensions-v2.json b/openapi/extensions-v2.json index 8a61019..66e34d7 100644 --- a/openapi/extensions-v2.json +++ b/openapi/extensions-v2.json @@ -171,6 +171,11 @@ "minLength": 1, "maxLength": 100 }, + "spdx_id": { + "type": "string", + "description": "A current SPDX license identifier (https://spdx.org/licenses/). Omitted for custom or proprietary licenses.", + "example": "MIT" + }, "URL": { "type": "string", "maxLength": 2048, diff --git a/package-lock.json b/package-lock.json index fde9d6a..cddba98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "marked": "^18.0.5", "sanitize-html": "^2.17.5", "semver": "^7.8.2", + "spdx-license-ids": "^3.0.23", "tailwindcss": "^4.3.0" }, "devDependencies": { @@ -6529,6 +6530,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "license": "CC0-1.0" + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", diff --git a/package.json b/package.json index cf1c602..c1b32fc 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "marked": "^18.0.5", "sanitize-html": "^2.17.5", "semver": "^7.8.2", + "spdx-license-ids": "^3.0.23", "tailwindcss": "^4.3.0" }, "devDependencies": { diff --git a/src/components/ExtensionForm.astro b/src/components/ExtensionForm.astro index 187b9be..8cdfd95 100644 --- a/src/components/ExtensionForm.astro +++ b/src/components/ExtensionForm.astro @@ -1,8 +1,9 @@ --- -import { EXTENSION_TYPES, SOURCE_TYPES } from '@/types'; +import { EXTENSION_TYPES, SOURCE_TYPES, SPDX_LICENSE_IDS } from '@/types'; import type { Developer, Extension } from '@/types'; -import { CircleAlert } from '@lucide/astro'; +import { CircleAlert, SquarePen } from '@lucide/astro'; import { stripUrlScheme } from '@/lib/url-prefix'; +import { OTHER_LICENSE } from '@/lib/extension-form'; interface Props { developer: Developer; @@ -16,6 +17,38 @@ const isEdit = Boolean(extension); // nothing already published to carry through — an extension can reach this // form with no releases yet (rejected and never resubmitted). const requireRelease = !isEdit || extension!.releases.length === 0; + +const existingSpdxId = extension?.license.spdx_id; +// An existing license only lands in the custom bucket when it truly isn't +// one of the ids this form offers — covers both genuinely custom/proprietary +// licenses and (defensively) a stored spdx_id this deployed list doesn't +// recognize, e.g. after a downgrade. +const isCustomLicense = + isEdit && (!existingSpdxId || !SPDX_LICENSE_IDS.includes(existingSpdxId)); +const licenseSelectValue = isCustomLicense + ? OTHER_LICENSE + : (existingSpdxId ?? ''); + +// Surfaced first in the license combobox as a "Popular Licenses" group so +// the common case doesn't need typing to find — a presentational shortcut +// only. Every id here still comes from SPDX_LICENSE_IDS below; nothing +// restricts submission to this set, and the full list remains searchable +// underneath it. +const POPULAR_SPDX_IDS = [ + 'MIT', + 'Apache-2.0', + 'GPL-2.0-only', + 'GPL-3.0-only', + 'AGPL-3.0-only', + 'LGPL-3.0-only', + 'MPL-2.0', + 'BSD-2-Clause', + 'BSD-3-Clause', + 'ISC', + 'Unlicense', +] as const; +const popularSpdxIds = new Set(POPULAR_SPDX_IDS); +const otherSpdxIds = SPDX_LICENSE_IDS.filter((id) => !popularSpdxIds.has(id)); --- { @@ -33,9 +66,20 @@ const requireRelease = !isEdit || extension!.releases.length === 0;
Publisher -

- Publishing as {developer.name} ({developer.id}). - Edit Developer Profile +

+ + Publishing as {developer.name} ({developer.id}). + + + +

@@ -45,6 +89,19 @@ const requireRelease = !isEdit || extension!.releases.length === 0;
Extension +
+ + +
- { EXTENSION_TYPES.map((t) => (
-
- - -