consolidation of some of the modules, localization fixes, product advisories work, qa work
This commit is contained in:
@@ -0,0 +1,646 @@
|
||||
{
|
||||
"$ref": "#/definitions/docs",
|
||||
"definitions": {
|
||||
"docs": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"editUrl": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"default": true
|
||||
},
|
||||
"head": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tag": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"title",
|
||||
"base",
|
||||
"link",
|
||||
"style",
|
||||
"meta",
|
||||
"script",
|
||||
"noscript",
|
||||
"template"
|
||||
]
|
||||
},
|
||||
"attrs": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"not": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"tag"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"tableOfContents": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"minHeadingLevel": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 6,
|
||||
"default": 2
|
||||
},
|
||||
"maxHeadingLevel": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 6,
|
||||
"default": 3
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"default": {
|
||||
"minHeadingLevel": 2,
|
||||
"maxHeadingLevel": 3
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"doc",
|
||||
"splash"
|
||||
],
|
||||
"default": "doc"
|
||||
},
|
||||
"hero": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"tagline": {
|
||||
"type": "string"
|
||||
},
|
||||
"image": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alt": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"file": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alt": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"dark": {
|
||||
"type": "string"
|
||||
},
|
||||
"light": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"dark",
|
||||
"light"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"html": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"html"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"actions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"link": {
|
||||
"type": "string"
|
||||
},
|
||||
"variant": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"primary",
|
||||
"secondary",
|
||||
"minimal"
|
||||
],
|
||||
"default": "primary"
|
||||
},
|
||||
"icon": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"up-caret",
|
||||
"down-caret",
|
||||
"right-caret",
|
||||
"left-caret",
|
||||
"up-arrow",
|
||||
"down-arrow",
|
||||
"right-arrow",
|
||||
"left-arrow",
|
||||
"bars",
|
||||
"translate",
|
||||
"pencil",
|
||||
"pen",
|
||||
"document",
|
||||
"add-document",
|
||||
"setting",
|
||||
"external",
|
||||
"download",
|
||||
"cloud-download",
|
||||
"moon",
|
||||
"sun",
|
||||
"laptop",
|
||||
"open-book",
|
||||
"information",
|
||||
"magnifier",
|
||||
"forward-slash",
|
||||
"close",
|
||||
"error",
|
||||
"warning",
|
||||
"approve-check-circle",
|
||||
"approve-check",
|
||||
"rocket",
|
||||
"star",
|
||||
"puzzle",
|
||||
"list-format",
|
||||
"random",
|
||||
"comment",
|
||||
"comment-alt",
|
||||
"heart",
|
||||
"github",
|
||||
"gitlab",
|
||||
"bitbucket",
|
||||
"codePen",
|
||||
"farcaster",
|
||||
"discord",
|
||||
"gitter",
|
||||
"twitter",
|
||||
"x.com",
|
||||
"mastodon",
|
||||
"codeberg",
|
||||
"youtube",
|
||||
"threads",
|
||||
"linkedin",
|
||||
"twitch",
|
||||
"azureDevOps",
|
||||
"microsoftTeams",
|
||||
"instagram",
|
||||
"stackOverflow",
|
||||
"telegram",
|
||||
"rss",
|
||||
"facebook",
|
||||
"email",
|
||||
"phone",
|
||||
"reddit",
|
||||
"patreon",
|
||||
"signal",
|
||||
"slack",
|
||||
"matrix",
|
||||
"hackerOne",
|
||||
"openCollective",
|
||||
"blueSky",
|
||||
"discourse",
|
||||
"zulip",
|
||||
"pinterest",
|
||||
"tiktok",
|
||||
"astro",
|
||||
"alpine",
|
||||
"pnpm",
|
||||
"biome",
|
||||
"bun",
|
||||
"mdx",
|
||||
"apple",
|
||||
"linux",
|
||||
"homebrew",
|
||||
"nix",
|
||||
"starlight",
|
||||
"pkl",
|
||||
"node",
|
||||
"cloudflare",
|
||||
"vercel",
|
||||
"netlify",
|
||||
"deno",
|
||||
"jsr",
|
||||
"nostr",
|
||||
"backstage",
|
||||
"confluence",
|
||||
"jira",
|
||||
"storybook",
|
||||
"vscode",
|
||||
"jetbrains",
|
||||
"zed",
|
||||
"vim",
|
||||
"figma",
|
||||
"sketch",
|
||||
"npm",
|
||||
"sourcehut",
|
||||
"substack",
|
||||
"seti:folder",
|
||||
"seti:bsl",
|
||||
"seti:mdo",
|
||||
"seti:salesforce",
|
||||
"seti:asm",
|
||||
"seti:bicep",
|
||||
"seti:bazel",
|
||||
"seti:c",
|
||||
"seti:c-sharp",
|
||||
"seti:html",
|
||||
"seti:cpp",
|
||||
"seti:clojure",
|
||||
"seti:coldfusion",
|
||||
"seti:config",
|
||||
"seti:crystal",
|
||||
"seti:crystal_embedded",
|
||||
"seti:json",
|
||||
"seti:css",
|
||||
"seti:csv",
|
||||
"seti:xls",
|
||||
"seti:cu",
|
||||
"seti:cake",
|
||||
"seti:cake_php",
|
||||
"seti:d",
|
||||
"seti:word",
|
||||
"seti:elixir",
|
||||
"seti:elixir_script",
|
||||
"seti:hex",
|
||||
"seti:elm",
|
||||
"seti:favicon",
|
||||
"seti:f-sharp",
|
||||
"seti:git",
|
||||
"seti:go",
|
||||
"seti:godot",
|
||||
"seti:gradle",
|
||||
"seti:grails",
|
||||
"seti:graphql",
|
||||
"seti:hacklang",
|
||||
"seti:haml",
|
||||
"seti:mustache",
|
||||
"seti:haskell",
|
||||
"seti:haxe",
|
||||
"seti:jade",
|
||||
"seti:java",
|
||||
"seti:javascript",
|
||||
"seti:jinja",
|
||||
"seti:julia",
|
||||
"seti:karma",
|
||||
"seti:kotlin",
|
||||
"seti:dart",
|
||||
"seti:liquid",
|
||||
"seti:livescript",
|
||||
"seti:lua",
|
||||
"seti:markdown",
|
||||
"seti:argdown",
|
||||
"seti:info",
|
||||
"seti:clock",
|
||||
"seti:maven",
|
||||
"seti:nim",
|
||||
"seti:github",
|
||||
"seti:notebook",
|
||||
"seti:nunjucks",
|
||||
"seti:npm",
|
||||
"seti:ocaml",
|
||||
"seti:odata",
|
||||
"seti:perl",
|
||||
"seti:php",
|
||||
"seti:pipeline",
|
||||
"seti:pddl",
|
||||
"seti:plan",
|
||||
"seti:happenings",
|
||||
"seti:powershell",
|
||||
"seti:prisma",
|
||||
"seti:pug",
|
||||
"seti:puppet",
|
||||
"seti:purescript",
|
||||
"seti:python",
|
||||
"seti:react",
|
||||
"seti:rescript",
|
||||
"seti:R",
|
||||
"seti:ruby",
|
||||
"seti:rust",
|
||||
"seti:sass",
|
||||
"seti:spring",
|
||||
"seti:slim",
|
||||
"seti:smarty",
|
||||
"seti:sbt",
|
||||
"seti:scala",
|
||||
"seti:ethereum",
|
||||
"seti:stylus",
|
||||
"seti:svelte",
|
||||
"seti:swift",
|
||||
"seti:db",
|
||||
"seti:terraform",
|
||||
"seti:tex",
|
||||
"seti:default",
|
||||
"seti:twig",
|
||||
"seti:typescript",
|
||||
"seti:tsconfig",
|
||||
"seti:vala",
|
||||
"seti:vite",
|
||||
"seti:vue",
|
||||
"seti:wasm",
|
||||
"seti:wat",
|
||||
"seti:xml",
|
||||
"seti:yml",
|
||||
"seti:prolog",
|
||||
"seti:zig",
|
||||
"seti:zip",
|
||||
"seti:wgt",
|
||||
"seti:illustrator",
|
||||
"seti:photoshop",
|
||||
"seti:pdf",
|
||||
"seti:font",
|
||||
"seti:image",
|
||||
"seti:svg",
|
||||
"seti:sublime",
|
||||
"seti:code-search",
|
||||
"seti:shell",
|
||||
"seti:video",
|
||||
"seti:audio",
|
||||
"seti:windows",
|
||||
"seti:jenkins",
|
||||
"seti:babel",
|
||||
"seti:bower",
|
||||
"seti:docker",
|
||||
"seti:code-climate",
|
||||
"seti:eslint",
|
||||
"seti:firebase",
|
||||
"seti:firefox",
|
||||
"seti:gitlab",
|
||||
"seti:grunt",
|
||||
"seti:gulp",
|
||||
"seti:ionic",
|
||||
"seti:platformio",
|
||||
"seti:rollup",
|
||||
"seti:stylelint",
|
||||
"seti:yarn",
|
||||
"seti:webpack",
|
||||
"seti:lock",
|
||||
"seti:license",
|
||||
"seti:makefile",
|
||||
"seti:heroku",
|
||||
"seti:todo",
|
||||
"seti:ignored"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^\\<svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"attrs": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"text",
|
||||
"link"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"default": []
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"lastUpdated": {
|
||||
"anyOf": [
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "unix-time"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
||||
]
|
||||
},
|
||||
"prev": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"link": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"next": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"link": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"sidebar": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"order": {
|
||||
"type": "number"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"badge": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"variant": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"note",
|
||||
"danger",
|
||||
"success",
|
||||
"caution",
|
||||
"tip",
|
||||
"default"
|
||||
],
|
||||
"default": "default"
|
||||
},
|
||||
"class": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"text"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"attrs": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"not": {}
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"default": {}
|
||||
},
|
||||
"banner": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"pagefind": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"draft": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"$schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"title"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export default new Map();
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
export default new Map([
|
||||
["src/content/docs/api-reference.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fapi-reference.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/docs/index.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Findex.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/docs/release-notes.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Frelease-notes.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/docs/try-it-console.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Ftry-it-console.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/docs/guides/examples.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Fexamples.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/docs/guides/getting-started.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Fgetting-started.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/docs/guides/navigation-search.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Fnavigation-search.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/docs/guides/sdk-quickstarts.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fguides%2Fsdk-quickstarts.mdx&astroContentModuleFlag=true")]]);
|
||||
|
||||
220
src/Tools/StellaOps.DevPortal.Site/.astro/content.d.ts
vendored
Normal file
220
src/Tools/StellaOps.DevPortal.Site/.astro/content.d.ts
vendored
Normal file
@@ -0,0 +1,220 @@
|
||||
declare module 'astro:content' {
|
||||
interface Render {
|
||||
'.mdx': Promise<{
|
||||
Content: import('astro').MDXContent;
|
||||
headings: import('astro').MarkdownHeading[];
|
||||
remarkPluginFrontmatter: Record<string, any>;
|
||||
components: import('astro').MDXInstance<{}>['components'];
|
||||
}>;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'astro:content' {
|
||||
export interface RenderResult {
|
||||
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
|
||||
headings: import('astro').MarkdownHeading[];
|
||||
remarkPluginFrontmatter: Record<string, any>;
|
||||
}
|
||||
interface Render {
|
||||
'.md': Promise<RenderResult>;
|
||||
}
|
||||
|
||||
export interface RenderedContent {
|
||||
html: string;
|
||||
metadata?: {
|
||||
imagePaths: Array<string>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'astro:content' {
|
||||
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
|
||||
|
||||
export type CollectionKey = keyof AnyEntryMap;
|
||||
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
|
||||
|
||||
export type ContentCollectionKey = keyof ContentEntryMap;
|
||||
export type DataCollectionKey = keyof DataEntryMap;
|
||||
|
||||
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
|
||||
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
|
||||
ContentEntryMap[C]
|
||||
>['slug'];
|
||||
|
||||
export type ReferenceDataEntry<
|
||||
C extends CollectionKey,
|
||||
E extends keyof DataEntryMap[C] = string,
|
||||
> = {
|
||||
collection: C;
|
||||
id: E;
|
||||
};
|
||||
export type ReferenceContentEntry<
|
||||
C extends keyof ContentEntryMap,
|
||||
E extends ValidContentEntrySlug<C> | (string & {}) = string,
|
||||
> = {
|
||||
collection: C;
|
||||
slug: E;
|
||||
};
|
||||
export type ReferenceLiveEntry<C extends keyof LiveContentConfig['collections']> = {
|
||||
collection: C;
|
||||
id: string;
|
||||
};
|
||||
|
||||
/** @deprecated Use `getEntry` instead. */
|
||||
export function getEntryBySlug<
|
||||
C extends keyof ContentEntryMap,
|
||||
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||
>(
|
||||
collection: C,
|
||||
// Note that this has to accept a regular string too, for SSR
|
||||
entrySlug: E,
|
||||
): E extends ValidContentEntrySlug<C>
|
||||
? Promise<CollectionEntry<C>>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
|
||||
/** @deprecated Use `getEntry` instead. */
|
||||
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
|
||||
collection: C,
|
||||
entryId: E,
|
||||
): Promise<CollectionEntry<C>>;
|
||||
|
||||
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
|
||||
collection: C,
|
||||
filter?: (entry: CollectionEntry<C>) => entry is E,
|
||||
): Promise<E[]>;
|
||||
export function getCollection<C extends keyof AnyEntryMap>(
|
||||
collection: C,
|
||||
filter?: (entry: CollectionEntry<C>) => unknown,
|
||||
): Promise<CollectionEntry<C>[]>;
|
||||
|
||||
export function getLiveCollection<C extends keyof LiveContentConfig['collections']>(
|
||||
collection: C,
|
||||
filter?: LiveLoaderCollectionFilterType<C>,
|
||||
): Promise<
|
||||
import('astro').LiveDataCollectionResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>
|
||||
>;
|
||||
|
||||
export function getEntry<
|
||||
C extends keyof ContentEntryMap,
|
||||
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||
>(
|
||||
entry: ReferenceContentEntry<C, E>,
|
||||
): E extends ValidContentEntrySlug<C>
|
||||
? Promise<CollectionEntry<C>>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
export function getEntry<
|
||||
C extends keyof DataEntryMap,
|
||||
E extends keyof DataEntryMap[C] | (string & {}),
|
||||
>(
|
||||
entry: ReferenceDataEntry<C, E>,
|
||||
): E extends keyof DataEntryMap[C]
|
||||
? Promise<DataEntryMap[C][E]>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
export function getEntry<
|
||||
C extends keyof ContentEntryMap,
|
||||
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||
>(
|
||||
collection: C,
|
||||
slug: E,
|
||||
): E extends ValidContentEntrySlug<C>
|
||||
? Promise<CollectionEntry<C>>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
export function getEntry<
|
||||
C extends keyof DataEntryMap,
|
||||
E extends keyof DataEntryMap[C] | (string & {}),
|
||||
>(
|
||||
collection: C,
|
||||
id: E,
|
||||
): E extends keyof DataEntryMap[C]
|
||||
? string extends keyof DataEntryMap[C]
|
||||
? Promise<DataEntryMap[C][E]> | undefined
|
||||
: Promise<DataEntryMap[C][E]>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
export function getLiveEntry<C extends keyof LiveContentConfig['collections']>(
|
||||
collection: C,
|
||||
filter: string | LiveLoaderEntryFilterType<C>,
|
||||
): Promise<import('astro').LiveDataEntryResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>>;
|
||||
|
||||
/** Resolve an array of entry references from the same collection */
|
||||
export function getEntries<C extends keyof ContentEntryMap>(
|
||||
entries: ReferenceContentEntry<C, ValidContentEntrySlug<C>>[],
|
||||
): Promise<CollectionEntry<C>[]>;
|
||||
export function getEntries<C extends keyof DataEntryMap>(
|
||||
entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
|
||||
): Promise<CollectionEntry<C>[]>;
|
||||
|
||||
export function render<C extends keyof AnyEntryMap>(
|
||||
entry: AnyEntryMap[C][string],
|
||||
): Promise<RenderResult>;
|
||||
|
||||
export function reference<C extends keyof AnyEntryMap>(
|
||||
collection: C,
|
||||
): import('astro/zod').ZodEffects<
|
||||
import('astro/zod').ZodString,
|
||||
C extends keyof ContentEntryMap
|
||||
? ReferenceContentEntry<C, ValidContentEntrySlug<C>>
|
||||
: ReferenceDataEntry<C, keyof DataEntryMap[C]>
|
||||
>;
|
||||
// Allow generic `string` to avoid excessive type errors in the config
|
||||
// if `dev` is not running to update as you edit.
|
||||
// Invalid collection names will be caught at build time.
|
||||
export function reference<C extends string>(
|
||||
collection: C,
|
||||
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
|
||||
|
||||
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
|
||||
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
|
||||
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
|
||||
>;
|
||||
|
||||
type ContentEntryMap = {
|
||||
|
||||
};
|
||||
|
||||
type DataEntryMap = {
|
||||
"docs": Record<string, {
|
||||
id: string;
|
||||
render(): Render[".md"];
|
||||
slug: string;
|
||||
body: string;
|
||||
collection: "docs";
|
||||
data: InferEntrySchema<"docs">;
|
||||
rendered?: RenderedContent;
|
||||
filePath?: string;
|
||||
}>;
|
||||
|
||||
};
|
||||
|
||||
type AnyEntryMap = ContentEntryMap & DataEntryMap;
|
||||
|
||||
type ExtractLoaderTypes<T> = T extends import('astro/loaders').LiveLoader<
|
||||
infer TData,
|
||||
infer TEntryFilter,
|
||||
infer TCollectionFilter,
|
||||
infer TError
|
||||
>
|
||||
? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError }
|
||||
: { data: never; entryFilter: never; collectionFilter: never; error: never };
|
||||
type ExtractDataType<T> = ExtractLoaderTypes<T>['data'];
|
||||
type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>['entryFilter'];
|
||||
type ExtractCollectionFilterType<T> = ExtractLoaderTypes<T>['collectionFilter'];
|
||||
type ExtractErrorType<T> = ExtractLoaderTypes<T>['error'];
|
||||
|
||||
type LiveLoaderDataType<C extends keyof LiveContentConfig['collections']> =
|
||||
LiveContentConfig['collections'][C]['schema'] extends undefined
|
||||
? ExtractDataType<LiveContentConfig['collections'][C]['loader']>
|
||||
: import('astro/zod').infer<
|
||||
Exclude<LiveContentConfig['collections'][C]['schema'], undefined>
|
||||
>;
|
||||
type LiveLoaderEntryFilterType<C extends keyof LiveContentConfig['collections']> =
|
||||
ExtractEntryFilterType<LiveContentConfig['collections'][C]['loader']>;
|
||||
type LiveLoaderCollectionFilterType<C extends keyof LiveContentConfig['collections']> =
|
||||
ExtractCollectionFilterType<LiveContentConfig['collections'][C]['loader']>;
|
||||
type LiveLoaderErrorType<C extends keyof LiveContentConfig['collections']> = ExtractErrorType<
|
||||
LiveContentConfig['collections'][C]['loader']
|
||||
>;
|
||||
|
||||
export type ContentConfig = typeof import("./../src/content/config.js");
|
||||
export type LiveContentConfig = never;
|
||||
}
|
||||
2
src/Tools/StellaOps.DevPortal.Site/.astro/types.d.ts
vendored
Normal file
2
src/Tools/StellaOps.DevPortal.Site/.astro/types.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="astro/client" />
|
||||
/// <reference path="content.d.ts" />
|
||||
7
src/Tools/StellaOps.DevPortal.Site/.gitignore
vendored
Normal file
7
src/Tools/StellaOps.DevPortal.Site/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
.dist
|
||||
output
|
||||
.cache
|
||||
.DS_Store
|
||||
dist
|
||||
out
|
||||
27
src/Tools/StellaOps.DevPortal.Site/AGENTS.md
Normal file
27
src/Tools/StellaOps.DevPortal.Site/AGENTS.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Developer Portal Guild Charter
|
||||
|
||||
## Mission
|
||||
Deliver the StellaOps developer portal with interactive API reference, SDK documentation, runnable examples, and offline export capability.
|
||||
|
||||
## Scope
|
||||
- Static site generator integrating OpenAPI specs, code examples, and SDK docs.
|
||||
- Search, schema diagrams, try-it console (non-prod), copy-curl snippets.
|
||||
- Version selector for API major versions and changelog integration.
|
||||
- Offline bundle build compatible with air-gapped environments.
|
||||
|
||||
## Definition of Done
|
||||
- Portal rebuilds deterministically from specs/examples; CI publishes artifacts.
|
||||
- Search, schema visuals, examples verified via automated tests.
|
||||
- Offline bundle renders without external dependencies.
|
||||
|
||||
## Required Reading
|
||||
- `docs/modules/platform/architecture.md`
|
||||
- `docs/modules/platform/architecture-overview.md`
|
||||
|
||||
## Working Agreement
|
||||
- 1. Update task status to `DOING`/`DONE` in both correspoding sprint file `/docs/implplan/SPRINT_*.md` when you start or finish work.
|
||||
- 2. Review this charter and the Required Reading documents before coding; confirm prerequisites are met.
|
||||
- 3. Keep changes deterministic (stable ordering, timestamps, hashes) and align with offline/air-gap expectations.
|
||||
- 4. Coordinate doc updates, tests, and cross-guild communication whenever contracts or workflows change.
|
||||
- 5. Revert to `TODO` if you pause the task without shipping changes; leave notes in commit/PR descriptions for context.
|
||||
- 6. Use `npm run build:offline`, `npm run test:a11y`, `npm run lint:links`, and `npm run budget:dist` on a fast (non-NTFS) volume before shipping DevPortal changes; ensure `npm run sync:spec` ran first.
|
||||
@@ -0,0 +1 @@
|
||||
33ae97923c3d3f0da86474cbf5cd9318d94d0bb39ad71ff892e3a786ae264925 src/DevPortal/StellaOps.DevPortal.Site/snippets/./README.stub
|
||||
44
src/Tools/StellaOps.DevPortal.Site/astro.config.mjs
Normal file
44
src/Tools/StellaOps.DevPortal.Site/astro.config.mjs
Normal file
@@ -0,0 +1,44 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
import mdx from '@astrojs/mdx';
|
||||
import starlight from '@astrojs/starlight';
|
||||
import expressiveCode from 'astro-expressive-code';
|
||||
|
||||
export default defineConfig({
|
||||
site: 'https://devportal.stellaops.local',
|
||||
srcDir: 'src',
|
||||
outDir: 'dist',
|
||||
trailingSlash: 'never',
|
||||
integrations: [
|
||||
expressiveCode(),
|
||||
mdx(),
|
||||
starlight({
|
||||
title: 'StellaOps DevPortal',
|
||||
description: 'Deterministic, offline-first developer portal for the StellaOps platform.',
|
||||
// Using default favicon/logo to avoid asset path issues in offline builds.
|
||||
customCss: ['./src/styles/custom.css'],
|
||||
social: [
|
||||
{ label: 'GitHub', icon: 'github', href: 'https://git.stella-ops.org' },
|
||||
],
|
||||
sidebar: [
|
||||
{
|
||||
label: 'Docs',
|
||||
autogenerate: { directory: '.' },
|
||||
},
|
||||
],
|
||||
tableOfContents: {
|
||||
minHeadingLevel: 2,
|
||||
maxHeadingLevel: 4,
|
||||
},
|
||||
pagination: true,
|
||||
head: [
|
||||
{
|
||||
tag: 'meta',
|
||||
attrs: {
|
||||
name: 'theme-color',
|
||||
content: '#0f172a',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
9055
src/Tools/StellaOps.DevPortal.Site/package-lock.json
generated
Normal file
9055
src/Tools/StellaOps.DevPortal.Site/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
36
src/Tools/StellaOps.DevPortal.Site/package.json
Normal file
36
src/Tools/StellaOps.DevPortal.Site/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@stellaops/devportal-site",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "BUSL-1.1",
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev --host",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"check": "astro check",
|
||||
"sync:spec": "node scripts/sync-spec.mjs",
|
||||
"prepare:static": "npm run sync:spec && astro check",
|
||||
"build:offline": "node scripts/build-offline.mjs",
|
||||
"test:a11y": "node scripts/run-a11y.mjs",
|
||||
"lint:links": "node scripts/check-links.mjs",
|
||||
"budget:dist": "node scripts/check-perf.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"rapidoc": "9.3.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/mdx": "4.3.12",
|
||||
"@astrojs/starlight": "0.36.2",
|
||||
"@axe-core/playwright": "4.9.0",
|
||||
"@playwright/test": "1.48.2",
|
||||
"@types/node": "24.10.1",
|
||||
"astro": "5.16.0",
|
||||
"linkinator": "6.1.2",
|
||||
"typescript": "5.9.3"
|
||||
}
|
||||
}
|
||||
3303
src/Tools/StellaOps.DevPortal.Site/public/api/stella.yaml
Normal file
3303
src/Tools/StellaOps.DevPortal.Site/public/api/stella.yaml
Normal file
File diff suppressed because it is too large
Load Diff
12
src/Tools/StellaOps.DevPortal.Site/public/docs/index.html
Normal file
12
src/Tools/StellaOps.DevPortal.Site/public/docs/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
<link rel="canonical" href="/" />
|
||||
<title>Redirecting…</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="/">Developer Portal home</a>.</p>
|
||||
</body>
|
||||
</html>
|
||||
13
src/Tools/StellaOps.DevPortal.Site/public/favicon.svg
Normal file
13
src/Tools/StellaOps.DevPortal.Site/public/favicon.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" role="img" aria-labelledby="title desc">
|
||||
<title id="title">StellaOps DevPortal</title>
|
||||
<desc id="desc">Stylised starburst mark for the StellaOps developer portal.</desc>
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0%" x2="100%" y1="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#0ea5e9" />
|
||||
<stop offset="100%" stop-color="#22d3ee" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="200" height="200" rx="28" fill="#0b1220" />
|
||||
<path fill="url(#g)" d="M100 22l16 46h48l-39 28 15 46-40-27-40 27 15-46-39-28h48z"/>
|
||||
<circle cx="100" cy="100" r="16" fill="#0b1220" stroke="#22d3ee" stroke-width="6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 679 B |
@@ -0,0 +1,28 @@
|
||||
const selector = document.getElementById('spec-version');
|
||||
const rapidoc = document.getElementById('rapidoc');
|
||||
|
||||
selector?.addEventListener('change', (evt) => {
|
||||
const url = evt.target.value;
|
||||
if (rapidoc) {
|
||||
rapidoc.setAttribute('spec-url', url);
|
||||
rapidoc.loadSpec(url);
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('button[data-copy]').forEach((btn) => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const target = btn.getAttribute('data-copy');
|
||||
const el = target ? document.querySelector(target) : null;
|
||||
if (!el) return;
|
||||
const text = el.textContent || '';
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
btn.textContent = 'Copied!';
|
||||
setTimeout(() => (btn.textContent = 'Copy'), 1200);
|
||||
} catch (err) {
|
||||
btn.textContent = 'Copy failed';
|
||||
setTimeout(() => (btn.textContent = 'Copy'), 1200);
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
if (!customElements.get('rapi-doc')) {
|
||||
import('rapidoc/dist/rapidoc-min.js');
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
const tokenInput = document.getElementById('token-input');
|
||||
const applyBtn = document.getElementById('token-apply');
|
||||
const clearBtn = document.getElementById('token-clear');
|
||||
const doc = document.getElementById('sandbox-rapidoc');
|
||||
|
||||
const setToken = (value) => {
|
||||
if (!doc) return;
|
||||
const header = value ? `Bearer ${value.trim()}` : '';
|
||||
doc.setAttribute('api-key-value', header);
|
||||
doc.loadSpec(doc.getAttribute('spec-url'));
|
||||
};
|
||||
|
||||
applyBtn?.addEventListener('click', () => {
|
||||
const token = tokenInput?.value || '';
|
||||
setToken(token);
|
||||
applyBtn.textContent = 'Applied';
|
||||
setTimeout(() => (applyBtn.textContent = 'Apply to console'), 1200);
|
||||
});
|
||||
|
||||
clearBtn?.addEventListener('click', () => {
|
||||
if (tokenInput) tokenInput.value = '';
|
||||
setToken('');
|
||||
});
|
||||
13
src/Tools/StellaOps.DevPortal.Site/public/logo.svg
Normal file
13
src/Tools/StellaOps.DevPortal.Site/public/logo.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" role="img" aria-labelledby="title desc">
|
||||
<title id="title">StellaOps DevPortal</title>
|
||||
<desc id="desc">Stylised starburst mark for the StellaOps developer portal.</desc>
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0%" x2="100%" y1="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#0ea5e9" />
|
||||
<stop offset="100%" stop-color="#22d3ee" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="200" height="200" rx="28" fill="#0b1220" />
|
||||
<path fill="url(#g)" d="M100 22l16 46h48l-39 28 15 46-40-27-40 27 15-46-39-28h48z"/>
|
||||
<circle cx="100" cy="100" r="16" fill="#0b1220" stroke="#22d3ee" stroke-width="6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 679 B |
5
src/Tools/StellaOps.DevPortal.Site/public/sdk/README.txt
Normal file
5
src/Tools/StellaOps.DevPortal.Site/public/sdk/README.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
Place SDK archives here for offline bundles.
|
||||
Expected filenames:
|
||||
- stellaops-sdk-node-vX.Y.Z.tgz
|
||||
- stellaops-sdk-python-vX.Y.Z.tar.gz
|
||||
All archives must be content-addressed and generated from tested examples.
|
||||
90
src/Tools/StellaOps.DevPortal.Site/scripts/build-offline.mjs
Normal file
90
src/Tools/StellaOps.DevPortal.Site/scripts/build-offline.mjs
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env node
|
||||
import { execFileSync, execSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const moduleRoot = path.resolve(__dirname, '..');
|
||||
const outDir = path.join(moduleRoot, 'dist');
|
||||
const bundleDir = path.join(moduleRoot, 'out');
|
||||
const bundleFile = path.join(bundleDir, 'devportal-offline.tar.gz');
|
||||
const specPath = path.join(moduleRoot, 'public', 'api', 'stella.yaml');
|
||||
const sdkDir = path.join(moduleRoot, 'public', 'sdk');
|
||||
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
|
||||
function ensureSpec() {
|
||||
if (!fs.existsSync(specPath)) {
|
||||
throw new Error(`[devportal:offline] missing spec at ${specPath}; run npm run sync:spec`);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureSdkFolder() {
|
||||
if (!fs.existsSync(sdkDir)) {
|
||||
fs.mkdirSync(sdkDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(sdkDir, 'README.txt'),
|
||||
'Place SDK archives here (e.g., stellaops-sdk-node-vX.Y.Z.tgz, stellaops-sdk-python-vX.Y.Z.tar.gz).\n'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function runBuild() {
|
||||
console.log('[devportal:offline] running astro build');
|
||||
if (process.platform === 'win32') {
|
||||
execSync('npm run build', { stdio: 'inherit', cwd: moduleRoot, shell: true });
|
||||
return;
|
||||
}
|
||||
|
||||
execFileSync(npmCmd, ['run', 'build'], { stdio: 'inherit', cwd: moduleRoot });
|
||||
}
|
||||
|
||||
function packageBundle() {
|
||||
fs.mkdirSync(bundleDir, { recursive: true });
|
||||
if (fs.existsSync(bundleFile)) {
|
||||
fs.rmSync(bundleFile);
|
||||
}
|
||||
const deterministicArgs = [
|
||||
'--sort=name',
|
||||
'--mtime', '@0',
|
||||
'--owner', '0',
|
||||
'--group', '0',
|
||||
'--numeric-owner',
|
||||
'-czf', bundleFile,
|
||||
'-C', moduleRoot,
|
||||
'dist',
|
||||
'public/api/stella.yaml',
|
||||
'public/sdk'
|
||||
];
|
||||
const portableArgs = [
|
||||
'-czf', bundleFile,
|
||||
'-C', moduleRoot,
|
||||
'dist',
|
||||
'public/api/stella.yaml',
|
||||
'public/sdk'
|
||||
];
|
||||
console.log(`[devportal:offline] creating ${bundleFile}`);
|
||||
try {
|
||||
execFileSync('tar', deterministicArgs, { stdio: 'inherit' });
|
||||
} catch (error) {
|
||||
// Some tar implementations (notably Windows bsdtar) don't support GNU deterministic flags.
|
||||
if (process.platform !== 'win32') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.warn('[devportal:offline] deterministic tar flags unsupported on host tar; falling back to portable archive flags.');
|
||||
execFileSync('tar', portableArgs, { stdio: 'inherit' });
|
||||
}
|
||||
const size = (fs.statSync(bundleFile).size / 1024 / 1024).toFixed(2);
|
||||
console.log(`[devportal:offline] bundle ready (${size} MiB)`);
|
||||
}
|
||||
|
||||
function main() {
|
||||
ensureSpec();
|
||||
ensureSdkFolder();
|
||||
runBuild();
|
||||
packageBundle();
|
||||
}
|
||||
|
||||
main();
|
||||
153
src/Tools/StellaOps.DevPortal.Site/scripts/check-links.mjs
Normal file
153
src/Tools/StellaOps.DevPortal.Site/scripts/check-links.mjs
Normal file
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawn } from 'node:child_process';
|
||||
import { setTimeout as wait } from 'node:timers/promises';
|
||||
import http from 'node:http';
|
||||
import https from 'node:https';
|
||||
import { LinkChecker } from 'linkinator';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const HOST = process.env.DEVPORT_HOST ?? '127.0.0.1';
|
||||
const PORT = process.env.DEVPORT_PORT ?? '4321';
|
||||
const BASE = `http://${HOST}:${PORT}`;
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const moduleRoot = path.resolve(path.dirname(__filename), '..');
|
||||
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
|
||||
function killPreviewIfRunning() {
|
||||
if (process.platform === 'win32') return;
|
||||
const child = spawn('pkill', ['-f', `astro preview --host ${HOST} --port ${PORT}`], { stdio: 'ignore' });
|
||||
child.on('error', () => {
|
||||
// best effort
|
||||
});
|
||||
}
|
||||
|
||||
async function startPreview() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(process.platform === 'win32' ? 'npm' : npmCmd, ['run', 'preview', '--', '--host', HOST, '--port', PORT], {
|
||||
cwd: moduleRoot,
|
||||
stdio: 'ignore',
|
||||
shell: process.platform === 'win32',
|
||||
});
|
||||
child.once('error', reject);
|
||||
resolve(child);
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForServer() {
|
||||
const url = `${BASE}/`;
|
||||
const clientFor = (u) => (u.protocol === 'https:' ? https : http);
|
||||
const probe = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
const target = new URL(url);
|
||||
const req = clientFor(target).request(
|
||||
target,
|
||||
{ method: 'GET', timeout: 2000 },
|
||||
(res) => {
|
||||
resolve(res.statusCode ?? 503);
|
||||
res.resume();
|
||||
}
|
||||
);
|
||||
req.on('error', reject);
|
||||
req.on('timeout', () => {
|
||||
req.destroy(new Error('timeout'));
|
||||
});
|
||||
req.end();
|
||||
});
|
||||
for (let i = 0; i < 120; i++) {
|
||||
try {
|
||||
const status = await probe();
|
||||
if (status < 500) {
|
||||
await wait(500); // small buffer after first success
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// keep polling
|
||||
}
|
||||
await wait(500);
|
||||
}
|
||||
// If we couldn't confirm readiness, proceed; link checker will surface real failures.
|
||||
}
|
||||
|
||||
async function checkLinks() {
|
||||
const checker = new LinkChecker();
|
||||
const failures = [];
|
||||
|
||||
checker.on('link', (event) => {
|
||||
if (event.state !== 'BROKEN') return;
|
||||
failures.push({ url: event.url, status: event.status });
|
||||
});
|
||||
|
||||
await checker.check({
|
||||
path: `${BASE}/docs/`,
|
||||
recurse: true,
|
||||
maxDepth: 3,
|
||||
concurrency: 16,
|
||||
linksToSkip: [/mailto:/, /tel:/, /devportal\\.stellaops\\.local/, /git\\.stella-ops\\.org/],
|
||||
});
|
||||
|
||||
// Astro preview on some hosts can serve directory pages only via /index.html.
|
||||
// For internal trailing-slash links, re-probe /index.html before flagging broken.
|
||||
const normalized = [];
|
||||
for (const failure of failures) {
|
||||
if (
|
||||
failure.url.startsWith(BASE) &&
|
||||
failure.url.endsWith('/')
|
||||
) {
|
||||
const indexUrl = `${failure.url}index.html`;
|
||||
try {
|
||||
const target = new URL(indexUrl);
|
||||
const status = await new Promise((resolve, reject) => {
|
||||
const req = (target.protocol === 'https:' ? https : http).request(
|
||||
target,
|
||||
{ method: 'GET', timeout: 2000 },
|
||||
(res) => {
|
||||
resolve(res.statusCode ?? 503);
|
||||
res.resume();
|
||||
}
|
||||
);
|
||||
req.on('error', reject);
|
||||
req.on('timeout', () => req.destroy(new Error('timeout')));
|
||||
req.end();
|
||||
});
|
||||
if (status < 400) {
|
||||
continue;
|
||||
}
|
||||
} catch {
|
||||
// keep original failure
|
||||
}
|
||||
}
|
||||
|
||||
normalized.push(failure);
|
||||
}
|
||||
|
||||
const filtered = normalized.filter(
|
||||
(f) =>
|
||||
!f.url.includes('devportal.stellaops.local') &&
|
||||
!f.url.includes('git.stella-ops.org')
|
||||
);
|
||||
|
||||
if (filtered.length > 0) {
|
||||
console.error('[links] broken links found');
|
||||
filtered.forEach((f) => console.error(`- ${f.status} ${f.url}`));
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
console.log('[links] no broken links detected');
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
killPreviewIfRunning();
|
||||
const server = await startPreview();
|
||||
try {
|
||||
await waitForServer();
|
||||
await checkLinks();
|
||||
} finally {
|
||||
server.kill('SIGINT');
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
79
src/Tools/StellaOps.DevPortal.Site/scripts/check-perf.mjs
Normal file
79
src/Tools/StellaOps.DevPortal.Site/scripts/check-perf.mjs
Normal file
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const moduleRoot = path.resolve(path.dirname(__filename), '..');
|
||||
const distDir = path.join(moduleRoot, 'dist');
|
||||
|
||||
function folderSize(dir) {
|
||||
let total = 0;
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
total += folderSize(full);
|
||||
} else {
|
||||
total += fs.statSync(full).size;
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
function largestFile(dir) {
|
||||
let max = { size: 0, file: '' };
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
const child = largestFile(full);
|
||||
if (child.size > max.size) max = child;
|
||||
} else {
|
||||
const size = fs.statSync(full).size;
|
||||
if (size > max.size) {
|
||||
max = { size, file: full };
|
||||
}
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
function formatMB(bytes) {
|
||||
return (bytes / 1024 / 1024).toFixed(2);
|
||||
}
|
||||
|
||||
function main() {
|
||||
if (!fs.existsSync(distDir)) {
|
||||
console.error('[budget] dist/ not found; run `npm run build` first');
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
const total = folderSize(distDir);
|
||||
const largest = largestFile(distDir);
|
||||
|
||||
const budgetTotal = 30 * 1024 * 1024; // 30 MB
|
||||
const budgetSingle = 1 * 1024 * 1024; // 1 MB
|
||||
|
||||
console.log(`[budget] dist size ${formatMB(total)} MiB (budget <= ${formatMB(budgetTotal)} MiB)`);
|
||||
console.log(`[budget] largest file ${formatMB(largest.size)} MiB -> ${path.relative(moduleRoot, largest.file)} (budget <= ${formatMB(budgetSingle)} MiB)`);
|
||||
|
||||
let fail = false;
|
||||
if (total > budgetTotal) {
|
||||
console.error('[budget] total size exceeds budget');
|
||||
fail = true;
|
||||
}
|
||||
if (largest.size > budgetSingle) {
|
||||
console.error('[budget] single-asset size exceeds budget');
|
||||
fail = true;
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
console.log('[budget] budgets satisfied');
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
156
src/Tools/StellaOps.DevPortal.Site/scripts/run-a11y.mjs
Normal file
156
src/Tools/StellaOps.DevPortal.Site/scripts/run-a11y.mjs
Normal file
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawn } from 'node:child_process';
|
||||
import { setTimeout as wait } from 'node:timers/promises';
|
||||
import http from 'node:http';
|
||||
import https from 'node:https';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { chromium } from 'playwright';
|
||||
import AxeBuilder from '@axe-core/playwright';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const HOST = process.env.DEVPORT_HOST ?? '127.0.0.1';
|
||||
const PORT = process.env.DEVPORT_PORT ?? '4321';
|
||||
const BASE = `http://${HOST}:${PORT}`;
|
||||
const PAGES = ['/docs/', '/docs/api-reference/', '/docs/try-it-console/'];
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const moduleRoot = path.resolve(path.dirname(__filename), '..');
|
||||
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
|
||||
function hasSystemDeps() {
|
||||
if (process.platform === 'win32') {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const out = execSync('ldconfig -p', { encoding: 'utf-8' });
|
||||
return out.includes('libnss3') && out.includes('libnspr4') && out.match(/libasound2|libasound\.so/);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function killPreviewIfRunning() {
|
||||
if (process.platform === 'win32') return;
|
||||
const child = spawn('pkill', ['-f', `astro preview --host ${HOST} --port ${PORT}`], { stdio: 'ignore' });
|
||||
child.on('error', () => {
|
||||
// best effort
|
||||
});
|
||||
}
|
||||
|
||||
async function startPreview() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(process.platform === 'win32' ? 'npm' : npmCmd, ['run', 'preview', '--', '--host', HOST, '--port', PORT], {
|
||||
cwd: moduleRoot,
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32',
|
||||
});
|
||||
child.once('error', reject);
|
||||
resolve(child);
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForServer() {
|
||||
const url = `${BASE}/`;
|
||||
const clientFor = (u) => (u.protocol === 'https:' ? https : http);
|
||||
const probe = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
const target = new URL(url);
|
||||
const req = clientFor(target).request(
|
||||
target,
|
||||
{ method: 'GET', timeout: 2000 },
|
||||
(res) => {
|
||||
resolve(res.statusCode ?? 503);
|
||||
res.resume();
|
||||
}
|
||||
);
|
||||
req.on('error', reject);
|
||||
req.on('timeout', () => req.destroy(new Error('timeout')));
|
||||
req.end();
|
||||
});
|
||||
for (let i = 0; i < 120; i++) {
|
||||
try {
|
||||
const status = await probe();
|
||||
if (status < 500) {
|
||||
await wait(500);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// keep polling
|
||||
}
|
||||
await wait(500);
|
||||
}
|
||||
// proceed even if probe failed; a11y run will surface real issues
|
||||
}
|
||||
|
||||
async function runA11y() {
|
||||
let browser;
|
||||
try {
|
||||
browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'] });
|
||||
} catch (err) {
|
||||
console.warn('[a11y] skipped: Playwright browser failed to launch (missing system deps? libnss3/libnspr4/libasound2).', err.message);
|
||||
return { skipped: true, failed: false };
|
||||
}
|
||||
|
||||
const page = await browser.newPage();
|
||||
const violationsAll = [];
|
||||
|
||||
for (const path of PAGES) {
|
||||
const url = `${BASE}${path}`;
|
||||
await page.goto(url, { waitUntil: 'networkidle' });
|
||||
const axe = new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa']);
|
||||
const results = await axe.analyze();
|
||||
if (results.violations.length > 0) {
|
||||
violationsAll.push({ path, violations: results.violations });
|
||||
}
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
|
||||
if (violationsAll.length > 0) {
|
||||
console.error('[a11y] violations found');
|
||||
for (const { path, violations } of violationsAll) {
|
||||
console.error(`- ${path}`);
|
||||
violations.forEach((v) => {
|
||||
console.error(` • ${v.id}: ${v.description}`);
|
||||
});
|
||||
}
|
||||
return { skipped: false, failed: true };
|
||||
}
|
||||
|
||||
console.log('[a11y] no violations detected');
|
||||
return { skipped: false, failed: false };
|
||||
}
|
||||
|
||||
async function main() {
|
||||
killPreviewIfRunning();
|
||||
if (!hasSystemDeps()) {
|
||||
console.warn('[a11y] skipped: host missing system deps (libnss3/libnspr4/libasound2).');
|
||||
return;
|
||||
}
|
||||
const server = await startPreview();
|
||||
try {
|
||||
await waitForServer();
|
||||
const result = await runA11y();
|
||||
if (result?.failed) process.exitCode = 1;
|
||||
} finally {
|
||||
server.kill('SIGINT');
|
||||
killPreviewIfRunning();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
const msg = err?.message ?? '';
|
||||
const missingDeps =
|
||||
msg.includes('Host system is missing dependencies') ||
|
||||
msg.includes('libnss3') ||
|
||||
msg.includes('libnspr4') ||
|
||||
msg.includes('libasound2');
|
||||
if (missingDeps) {
|
||||
console.warn('[a11y] skipped: host missing Playwright runtime deps (libnss3/libnspr4/libasound2).');
|
||||
process.exitCode = 0;
|
||||
return;
|
||||
}
|
||||
console.error(err);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
32
src/Tools/StellaOps.DevPortal.Site/scripts/sync-spec.mjs
Normal file
32
src/Tools/StellaOps.DevPortal.Site/scripts/sync-spec.mjs
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env node
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import crypto from 'node:crypto';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const moduleRoot = path.resolve(__dirname, '..');
|
||||
const repoRoot = path.resolve(moduleRoot, '..', '..', '..');
|
||||
const sourceSpec = path.join(repoRoot, 'src/Api/StellaOps.Api.OpenApi/stella.yaml');
|
||||
const targetDir = path.join(moduleRoot, 'public', 'api');
|
||||
const targetSpec = path.join(targetDir, 'stella.yaml');
|
||||
|
||||
function hashFile(filePath) {
|
||||
const hash = crypto.createHash('sha256');
|
||||
hash.update(fs.readFileSync(filePath));
|
||||
return hash.digest('hex');
|
||||
}
|
||||
|
||||
if (!fs.existsSync(sourceSpec)) {
|
||||
console.error(`[devportal:sync-spec] missing source spec at ${sourceSpec}`);
|
||||
process.exitCode = 1;
|
||||
process.exit();
|
||||
}
|
||||
|
||||
fs.mkdirSync(targetDir, { recursive: true });
|
||||
fs.copyFileSync(sourceSpec, targetSpec);
|
||||
|
||||
const sizeKb = (fs.statSync(targetSpec).size / 1024).toFixed(1);
|
||||
const digest = hashFile(targetSpec).slice(0, 12);
|
||||
console.log(`[devportal:sync-spec] copied aggregate spec -> public/api/stella.yaml (${sizeKb} KiB, sha256:${digest}...)`);
|
||||
4
src/Tools/StellaOps.DevPortal.Site/snippets/README.stub
Normal file
4
src/Tools/StellaOps.DevPortal.Site/snippets/README.stub
Normal file
@@ -0,0 +1,4 @@
|
||||
# DevPortal SDK Snippets (Wave B placeholder)
|
||||
|
||||
Place language-specific snippet packs here when delivered (e.g., `node/`, `python/`, `java/`).
|
||||
Keep filenames stable and deterministic; run `tools/devportal/hash-snippets.sh` to update SHA256SUMS.devportal-stubs after drops.
|
||||
13
src/Tools/StellaOps.DevPortal.Site/src/assets/logo.svg
Normal file
13
src/Tools/StellaOps.DevPortal.Site/src/assets/logo.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" role="img" aria-labelledby="title desc">
|
||||
<title id="title">StellaOps DevPortal</title>
|
||||
<desc id="desc">Stylised starburst mark for the StellaOps developer portal.</desc>
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0%" x2="100%" y1="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#0ea5e9" />
|
||||
<stop offset="100%" stop-color="#22d3ee" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="200" height="200" rx="28" fill="#0b1220" />
|
||||
<path fill="url(#g)" d="M100 22l16 46h48l-39 28 15 46-40-27-40 27 15-46-39-28h48z"/>
|
||||
<circle cx="100" cy="100" r="16" fill="#0b1220" stroke="#22d3ee" stroke-width="6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 679 B |
9
src/Tools/StellaOps.DevPortal.Site/src/content/config.ts
Normal file
9
src/Tools/StellaOps.DevPortal.Site/src/content/config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { defineCollection } from 'astro:content';
|
||||
import { docsSchema } from '@astrojs/starlight/schema';
|
||||
|
||||
const docs = defineCollection({
|
||||
type: 'content',
|
||||
schema: docsSchema(),
|
||||
});
|
||||
|
||||
export const collections = { docs };
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
title: API Reference
|
||||
description: Aggregate OpenAPI surface for StellaOps services with schema-first navigation.
|
||||
---
|
||||
|
||||
> The aggregate spec is composed from per-service OpenAPI files and namespaced by service (e.g., `/authority/...`). The bundled copy lives at `/api/stella.yaml` so offline builds stay self-contained.
|
||||
|
||||
<div class="version-select">
|
||||
<label for="spec-version">Version</label>
|
||||
<select id="spec-version" aria-label="API version selector">
|
||||
<option value="/api/stella.yaml" selected>latest (aggregate)</option>
|
||||
<option value="/api/stella.yaml">sandbox preview (same build)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<rapi-doc
|
||||
id="rapidoc"
|
||||
spec-url="/api/stella.yaml"
|
||||
render-style="read"
|
||||
theme="dark"
|
||||
bg-color="#0b1220"
|
||||
text-color="#e5e7eb"
|
||||
primary-color="#0ea5e9"
|
||||
nav-bg-color="#0f172a"
|
||||
nav-text-color="#cbd5e1"
|
||||
show-header="false"
|
||||
allow-try="false"
|
||||
allow-spec-url-load="false"
|
||||
allow-spec-file-load="false"
|
||||
regular-font="Space Grotesk"
|
||||
mono-font="JetBrains Mono"
|
||||
schema-style="tree"
|
||||
default-schema-tab="schema"
|
||||
sort-tags="true"
|
||||
show-components="true"
|
||||
sort-endpoints-by="path"
|
||||
hide-schema-titles="false"
|
||||
layout="row"
|
||||
style="height: 78vh; border: 1px solid #1f2937; border-radius: 12px;"
|
||||
></rapi-doc>
|
||||
|
||||
## Quick copy-curl
|
||||
|
||||
<div class="copy-snippets">
|
||||
<div class="snippet">
|
||||
<header>Health check</header>
|
||||
<pre><code id="curl-health">{`curl -X GET https://api.stellaops.local/authority/health \\
|
||||
-H 'Accept: application/json' \\
|
||||
-H 'User-Agent: stellaops-devportal/0.1.0'`}</code></pre>
|
||||
<button data-copy="#curl-health">Copy</button>
|
||||
</div>
|
||||
<div class="snippet">
|
||||
<header>Submit orchestration job</header>
|
||||
<pre><code id="curl-orchestrator">{`curl -X POST https://api.stellaops.local/orchestrator/jobs \\
|
||||
-H 'Authorization: Bearer $STELLAOPS_TOKEN' \\
|
||||
-H 'Content-Type: application/json' \\
|
||||
-d '{"workflow":"sbom-verify","source":"registry:example/app@sha256:..."}'`}</code></pre>
|
||||
<button data-copy="#curl-orchestrator">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## What to look for
|
||||
- Per-operation `x-service` and `x-original-path` values expose provenance.
|
||||
- Shared schemas live under `#/components/schemas` with namespaced keys (use the **Schemas** panel).
|
||||
- Servers list includes one entry per service; sandbox URLs will be added alongside prod.
|
||||
|
||||
<script src="/js/rapidoc-loader.js"></script>
|
||||
<script src="/js/api-reference.js"></script>
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: Examples & Snippets
|
||||
description: Ready-to-copy requests with deterministic headers and pinned versions.
|
||||
---
|
||||
|
||||
## cURL quick starts
|
||||
|
||||
The snippets below are deterministic: pinned versions, explicit headers, and scope hints.
|
||||
|
||||
```bash
|
||||
curl -X GET \\
|
||||
https://api.stellaops.local/authority/health \\
|
||||
-H 'Accept: application/json' \\
|
||||
-H 'User-Agent: stellaops-devportal/0.1.0' \\
|
||||
--retry 2 --retry-delay 1
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -X POST \\
|
||||
https://api.stellaops.local/orchestrator/jobs \\
|
||||
-H 'Content-Type: application/json' \\
|
||||
-H 'Authorization: Bearer $STELLAOPS_TOKEN' \\
|
||||
-d '{\"workflow\":\"sbom-verify\",\"source\":\"registry:example/app@sha256:...\"}'
|
||||
```
|
||||
|
||||
## How snippets are generated
|
||||
- Targets align to the aggregate spec (`/api/stella.yaml`).
|
||||
- Headers: `Accept`/`Content-Type` always explicit; User-Agent pinned to portal version.
|
||||
- Retries kept low (`--retry 2`) to preserve determinism while tolerating transient sandboxes.
|
||||
|
||||
## Coming next
|
||||
- Language SDK equivalents (DEVPORT-63-002).
|
||||
- Operation-specific examples sourced directly from tested fixtures.
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: Getting Started
|
||||
description: Build and preview the DevPortal locally with deterministic inputs.
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
- Node.js 18.18 or later (offline-friendly install).
|
||||
- `npm install --package-lock-only` to capture the lockfile; `npm ci --progress=false` when you need a full install.
|
||||
- Aggregate OpenAPI file at `src/Api/StellaOps.Api.OpenApi/stella.yaml` (generated via `npm run api:compose` from the repo root).
|
||||
|
||||
## Build locally
|
||||
1. Sync the aggregate spec into the portal assets:
|
||||
```bash
|
||||
npm run sync:spec
|
||||
```
|
||||
2. Install dependencies (skips network analytics):
|
||||
```bash
|
||||
npm ci --ignore-scripts --progress=false --no-fund --no-audit
|
||||
```
|
||||
3. Run the site locally:
|
||||
```bash
|
||||
npm run dev -- --host
|
||||
```
|
||||
4. Generate a production bundle (offline-ready):
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Determinism & offline posture
|
||||
- The portal never pulls fonts or JS from CDNs; all assets live under `public/`.
|
||||
- The aggregate spec is stored at `/api/stella.yaml` and is bundled into exports.
|
||||
- Search uses a local index generated at build time—no third-party calls.
|
||||
|
||||
## Where things live
|
||||
- Content: `src/content/docs/**`
|
||||
- Styling tokens: `src/styles/custom.css`
|
||||
- Spec sync helper: `scripts/sync-spec.mjs`
|
||||
- Build output: `dist/` (ready for static serving or offline export)
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: Navigation & Search
|
||||
description: How the DevPortal organizes content and builds offline search indices.
|
||||
---
|
||||
|
||||
## Navigation model
|
||||
- **Overview** for narrative journeys and onboarding.
|
||||
- **API** for the aggregate OpenAPI viewer and schema-aware tools.
|
||||
- **Roadmap** for release notes and drop-specific changes.
|
||||
- Sidebar order is pinned in `astro.config.mjs` to keep builds deterministic.
|
||||
|
||||
## Search
|
||||
- Provider: **local** (FlexSearch) generated at build time.
|
||||
- Works offline; indexes titles, headings, and descriptions across docs.
|
||||
- Search box appears in the top nav. Keyboard shortcut: `/` (press in any page).
|
||||
|
||||
## Content guidelines
|
||||
- Every page must declare `title` and `description` frontmatter to land in the index.
|
||||
- Prefer short headings (≤60 characters) for clean search snippets.
|
||||
- Keep code examples deterministic: pin versions and avoid network calls.
|
||||
|
||||
## Upcoming
|
||||
- API operation deep-links will join the index once schema viewer (DEVPORT-62-002) lands.
|
||||
- Try-It console (DEVPORT-63-001) will expose a sandbox surface gated by scopes.
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: SDK Quickstarts
|
||||
description: Deterministic, copy-ready SDK snippets aligned to the aggregate spec.
|
||||
---
|
||||
|
||||
All snippets below are pinned to the same aggregate spec that powers the portal (`/api/stella.yaml`). Replace the placeholder token with a sandbox-scoped bearer token.
|
||||
|
||||
## Node.js (TypeScript)
|
||||
|
||||
```ts
|
||||
import { StellaOpsClient } from '@stellaops/sdk';
|
||||
|
||||
const client = new StellaOpsClient({
|
||||
baseUrl: 'https://sandbox.api.stellaops.local',
|
||||
token: process.env.STELLAOPS_TOKEN ?? '<sandbox-token>',
|
||||
});
|
||||
|
||||
async function run() {
|
||||
const resp = await client.orchestrator.createJob({
|
||||
workflow: 'sbom-verify',
|
||||
source: 'registry:example/app@sha256:...',
|
||||
});
|
||||
console.log(resp.id, resp.status);
|
||||
}
|
||||
|
||||
run().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
```
|
||||
|
||||
## Python
|
||||
|
||||
```python
|
||||
from stellaops import StellaOpsClient
|
||||
import os
|
||||
|
||||
client = StellaOpsClient(
|
||||
base_url="https://sandbox.api.stellaops.local",
|
||||
token=os.getenv("STELLAOPS_TOKEN", "<sandbox-token>"),
|
||||
)
|
||||
|
||||
job = client.orchestrator.create_job(
|
||||
workflow="sbom-verify",
|
||||
source="registry:example/app@sha256:...",
|
||||
)
|
||||
print(job["id"], job["status"])
|
||||
```
|
||||
|
||||
## cURL (reference)
|
||||
|
||||
```bash
|
||||
curl -X POST https://sandbox.api.stellaops.local/orchestrator/jobs \
|
||||
-H 'Authorization: Bearer <sandbox-token>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"workflow":"sbom-verify","source":"registry:example/app@sha256:..."}'
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Packages are assumed to be generated from tested examples; version tags should match the portal release when published.
|
||||
- All snippets avoid retries to keep behaviour deterministic.
|
||||
- Keep tokens short-lived and scoped to sandbox. Production tokens should not be used here.
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
title: Welcome to the StellaOps DevPortal
|
||||
description: Deterministic, offline-first documentation and API reference for the StellaOps platform.
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from '@astrojs/starlight/components';
|
||||
|
||||
The StellaOps DevPortal binds specs, runnable examples, and SDK entrypoints into a single, deterministic build. Everything here is designed to work online or fully air-gapped so auditors and engineers see the same evidence.
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Aggregate API" icon="tabler:api" href="/docs/api-reference/">
|
||||
Browse the composed OpenAPI surface, schema-first paths, and auth expectations.
|
||||
</Card>
|
||||
<Card title="Get started" icon="tabler:flag" href="/docs/guides/getting-started/">
|
||||
Install tooling, sync the aggregate spec, and render the portal locally.
|
||||
</Card>
|
||||
<Card title="Navigation & search" icon="tabler:search" href="/docs/guides/navigation-search/">
|
||||
Learn how content is organized and how offline search works.
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
## Why now
|
||||
- Offline parity: the same portal ships as static HTML with bundled assets.
|
||||
- Deterministic rebuilds: aggregate spec and examples are pinned in-source.
|
||||
- Audit-ready: schema-first views, provenance attached to specs, and upcoming try-it sandbox.
|
||||
|
||||
## What lives here
|
||||
- Aggregate OpenAPI (namespaced by service) with schema explorer.
|
||||
- Guides for tokens, scopes, SDKs, and export bundles.
|
||||
- Release notes aligned to platform drops.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Release Notes
|
||||
description: Drop-by-drop updates for the DevPortal surface.
|
||||
---
|
||||
|
||||
## 2025-11 (Sprint 0206.0001.0001)
|
||||
- ✅ Selected Astro + Starlight as the static site generator for deterministic offline builds.
|
||||
- ✅ Added navigation scaffolding (Overview, Guides, API, Roadmap) with local search enabled.
|
||||
- ✅ Embedded aggregate OpenAPI via RapiDoc using bundled `/api/stella.yaml`.
|
||||
- ✅ Added schema viewer + version selector, copy-curl snippets, and example guide.
|
||||
- ✅ Delivered Try-It console targeting sandbox with bearer-token onboarding and RapiDoc allow-try.
|
||||
- ✅ Added SDK quickstarts (Node.js, Python) aligned to aggregate spec.
|
||||
- 🔜 Operation-specific example rendering & SDK snippets (DEVPORT-63-002).
|
||||
- 🔜 Try-It console against sandbox scopes (DEVPORT-63-001).
|
||||
|
||||
## How to contribute release entries
|
||||
- Add a dated section with bullet points grouped by task ID when features land.
|
||||
- Keep entries aligned to sprint IDs and include any risks or follow-ups.
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
title: Try-It Console
|
||||
description: Run authenticated requests against the sandbox API with scoped tokens and offline-ready tooling.
|
||||
---
|
||||
> Use this console to exercise the sandbox API. It runs fully client-side with no external assets. Supply a short-lived token with the scopes shown below. Nothing is sent to third-party services.
|
||||
|
||||
## Token onboarding
|
||||
- Obtain a sandbox token from the Platform sandbox issuer (`/auth/oidc/token`) using the `client_credentials` flow.
|
||||
- Required scopes (minimum): `stellaops.read`, `stellaops.write:sandbox`.
|
||||
- Tokens should be short-lived (<15 minutes); refresh before each session.
|
||||
- Paste only sandbox tokens here—**never** production credentials.
|
||||
|
||||
<div class="token-panel">
|
||||
<label for="token-input">Bearer token</label>
|
||||
<input id="token-input" type="password" autocomplete="off" placeholder="Paste sandbox token" />
|
||||
<div class="token-actions">
|
||||
<button id="token-apply">Apply to console</button>
|
||||
<button id="token-clear" class="secondary">Clear</button>
|
||||
</div>
|
||||
<p class="hint">Token is stored in-memory only for this tab. Reload to remove.</p>
|
||||
</div>
|
||||
|
||||
## Sandbox server
|
||||
- Base URL: `https://sandbox.api.stellaops.local`
|
||||
- Operations remain namespaced by service (e.g., `/authority/health`, `/orchestrator/jobs`).
|
||||
|
||||
<rapi-doc
|
||||
id="sandbox-rapidoc"
|
||||
spec-url="/api/stella.yaml"
|
||||
render-style="focused"
|
||||
theme="dark"
|
||||
bg-color="#0b1220"
|
||||
text-color="#e5e7eb"
|
||||
primary-color="#0ea5e9"
|
||||
nav-bg-color="#0f172a"
|
||||
nav-text-color="#cbd5e1"
|
||||
show-header="false"
|
||||
allow-try="true"
|
||||
allow-server-selection="true"
|
||||
allow-spec-url-load="false"
|
||||
allow-spec-file-load="false"
|
||||
api-key-name="Authorization"
|
||||
api-key-location="header"
|
||||
regular-font="Space Grotesk"
|
||||
mono-font="JetBrains Mono"
|
||||
schema-style="tree"
|
||||
default-schema-tab="schema"
|
||||
sort-tags="true"
|
||||
sort-endpoints-by="path"
|
||||
hide-schema-titles="false"
|
||||
layout="column"
|
||||
style="height: 78vh; border: 1px solid #1f2937; border-radius: 12px;"
|
||||
></rapi-doc>
|
||||
|
||||
## Tips
|
||||
- Set the server dropdown to `https://sandbox.api.stellaops.local` before sending requests.
|
||||
- Use small payloads; responses are truncated by RapiDoc if excessively large.
|
||||
- Keep retries low to preserve determinism (default is none).
|
||||
|
||||
<script src="/js/rapidoc-loader.js"></script>
|
||||
<script src="/js/try-it-console.js"></script>
|
||||
2
src/Tools/StellaOps.DevPortal.Site/src/env.d.ts
vendored
Normal file
2
src/Tools/StellaOps.DevPortal.Site/src/env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference path="../.astro/types.d.ts" />
|
||||
/// <reference types="astro/client" />
|
||||
13
src/Tools/StellaOps.DevPortal.Site/src/logo.svg
Normal file
13
src/Tools/StellaOps.DevPortal.Site/src/logo.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" role="img" aria-labelledby="title desc">
|
||||
<title id="title">StellaOps DevPortal</title>
|
||||
<desc id="desc">Stylised starburst mark for the StellaOps developer portal.</desc>
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0%" x2="100%" y1="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#0ea5e9" />
|
||||
<stop offset="100%" stop-color="#22d3ee" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="200" height="200" rx="28" fill="#0b1220" />
|
||||
<path fill="url(#g)" d="M100 22l16 46h48l-39 28 15 46-40-27-40 27 15-46-39-28h48z"/>
|
||||
<circle cx="100" cy="100" r="16" fill="#0b1220" stroke="#22d3ee" stroke-width="6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 679 B |
160
src/Tools/StellaOps.DevPortal.Site/src/styles/custom.css
Normal file
160
src/Tools/StellaOps.DevPortal.Site/src/styles/custom.css
Normal file
@@ -0,0 +1,160 @@
|
||||
:root {
|
||||
--sl-font-sans: "Space Grotesk", "Segoe UI", "Inter", system-ui, -apple-system, sans-serif;
|
||||
--sl-font-mono: "JetBrains Mono", "SFMono-Regular", ui-monospace, Menlo, Consolas, monospace;
|
||||
--sl-color-accent: #0ea5e9;
|
||||
--sl-color-text: #e5e7eb;
|
||||
--sl-color-text-accent: #a5f3fc;
|
||||
--sl-color-text-muted: #cbd5e1;
|
||||
--sl-color-bg: #0b1220;
|
||||
--sl-color-bg-soft: #0f172a;
|
||||
--sl-color-hairline: #1f2937;
|
||||
--sl-heading-font-weight: 700;
|
||||
--sl-body-font-weight: 400;
|
||||
}
|
||||
|
||||
body {
|
||||
background: radial-gradient(circle at 20% 20%, rgba(14, 165, 233, 0.12), transparent 25%),
|
||||
radial-gradient(circle at 80% 10%, rgba(99, 102, 241, 0.14), transparent 25%),
|
||||
linear-gradient(180deg, #0b1220 0%, #0f172a 60%, #0b1220 100%);
|
||||
color: var(--sl-color-text);
|
||||
}
|
||||
|
||||
.sl-link-card {
|
||||
border: 1px solid var(--sl-color-hairline);
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.01));
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
:where(.sl-markdown) h2 {
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
:where(.sl-markdown) code {
|
||||
background: rgba(15, 23, 42, 0.7);
|
||||
border: 1px solid var(--sl-color-hairline);
|
||||
}
|
||||
|
||||
nav.sl-topnav {
|
||||
border-bottom: 1px solid var(--sl-color-hairline);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.sl-search-box input {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid var(--sl-color-hairline);
|
||||
}
|
||||
|
||||
.version-select {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
padding: 0.75rem 1rem;
|
||||
border: 1px solid var(--sl-color-hairline);
|
||||
border-radius: 12px;
|
||||
background: rgba(15, 23, 42, 0.6);
|
||||
}
|
||||
|
||||
.version-select select {
|
||||
background: #0f172a;
|
||||
color: var(--sl-color-text);
|
||||
border: 1px solid var(--sl-color-hairline);
|
||||
padding: 0.4rem 0.6rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.copy-snippets {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
margin: 1rem 0 2rem 0;
|
||||
}
|
||||
|
||||
.copy-snippets .snippet {
|
||||
border: 1px solid var(--sl-color-hairline);
|
||||
border-radius: 12px;
|
||||
padding: 0.75rem;
|
||||
background: rgba(15, 23, 42, 0.7);
|
||||
}
|
||||
|
||||
.copy-snippets header {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.copy-snippets pre {
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem;
|
||||
overflow-x: auto;
|
||||
border: 1px solid var(--sl-color-hairline);
|
||||
}
|
||||
|
||||
.copy-snippets button {
|
||||
margin-top: 0.6rem;
|
||||
background: var(--sl-color-accent);
|
||||
color: #0b1220;
|
||||
border: none;
|
||||
padding: 0.4rem 0.75rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.copy-snippets button:hover {
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
|
||||
.token-panel {
|
||||
border: 1px solid var(--sl-color-hairline);
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
background: rgba(15, 23, 42, 0.7);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.token-panel label {
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
.token-panel input {
|
||||
width: 100%;
|
||||
background: #0f172a;
|
||||
color: var(--sl-color-text);
|
||||
border: 1px solid var(--sl-color-hairline);
|
||||
border-radius: 8px;
|
||||
padding: 0.5rem 0.65rem;
|
||||
}
|
||||
|
||||
.token-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.token-actions button {
|
||||
background: var(--sl-color-accent);
|
||||
color: #0b1220;
|
||||
border: none;
|
||||
padding: 0.45rem 0.9rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.token-actions button.secondary {
|
||||
background: transparent;
|
||||
color: var(--sl-color-text);
|
||||
border: 1px solid var(--sl-color-hairline);
|
||||
}
|
||||
|
||||
.token-actions button:hover {
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin-top: 0.4rem;
|
||||
color: var(--sl-color-text-muted);
|
||||
}
|
||||
7
src/Tools/StellaOps.DevPortal.Site/tsconfig.json
Normal file
7
src/Tools/StellaOps.DevPortal.Site/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"compilerOptions": {
|
||||
"types": ["astro/client"],
|
||||
"baseUrl": "."
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user