feat: scaffold vite chrome extension
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
VITE_GITEA_BASE_URL=http://gitea.orson.tealthrone
|
||||||
|
VITE_GITEA_TOKEN=
|
||||||
|
VITE_GITEA_REPO_OWNER=jacob-mathison
|
||||||
|
VITE_GITEA_REPO_NAME=mp-silma-ai-aide
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## Project
|
||||||
|
|
||||||
|
`mp-silma-ai-aide` is a Vite + TypeScript Chrome extension scaffold. The built `dist/` directory is intended to be loaded in Chrome as an unpacked Manifest V3 extension.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
- Install dependencies: `npm install`
|
||||||
|
- Run local Vite dev server: `npm run dev`
|
||||||
|
- Build extension: `npm run build`
|
||||||
|
- Preview build output: `npm run preview`
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
Configuration is read from Vite environment variables:
|
||||||
|
|
||||||
|
- `VITE_GITEA_BASE_URL`: Base URL for the Gitea instance, for example `http://gitea.orson.tealthrone`.
|
||||||
|
- `VITE_GITEA_TOKEN`: Gitea API token.
|
||||||
|
- `VITE_GITEA_REPO_OWNER`: Target Gitea owner. Default: `jacob-mathison`.
|
||||||
|
- `VITE_GITEA_REPO_NAME`: Target Gitea repository. Default: `mp-silma-ai-aide`.
|
||||||
|
|
||||||
|
Use `.env.local` for local secrets. Keep `.env.local` and any real token-bearing env files out of git. Update `.env.example` when adding new required variables.
|
||||||
|
|
||||||
|
## Extension Build Notes
|
||||||
|
|
||||||
|
- `vite.config.ts` emits `dist/manifest.json` during production builds.
|
||||||
|
- `dist/` is generated output and should not be edited directly.
|
||||||
|
- Host permissions are derived from `VITE_GITEA_BASE_URL`. Rebuild after changing the base URL.
|
||||||
|
- Runtime API calls are centralized in `src/api.ts`.
|
||||||
|
- Popup state is centralized in the vanilla Zustand store at `src/store.ts`.
|
||||||
|
|
||||||
|
## Coding Guidelines
|
||||||
|
|
||||||
|
- Keep the app TypeScript-first and strict.
|
||||||
|
- Prefer small, focused modules under `src/`.
|
||||||
|
- Do not commit secrets or generated build output.
|
||||||
|
- Run `npm run build` before handing off code changes.
|
||||||
@@ -1,3 +1,19 @@
|
|||||||
# mp-silma-ai-aide
|
# mp-silma-ai-aide
|
||||||
|
|
||||||
Context-aware AI aide — project-agnostic tooling to give Silma better context for supporting any codebase or task.
|
Context-aware AI aide — project-agnostic tooling to give Silma better context for supporting any codebase or task.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
Build the Chrome extension:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Load the generated `dist/` folder in Chrome as an unpacked extension.
|
||||||
|
|||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Silma AI Aide</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="app-shell">
|
||||||
|
<header class="masthead">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Gitea</p>
|
||||||
|
<h1>Silma AI Aide</h1>
|
||||||
|
</div>
|
||||||
|
<span id="config-badge" class="badge">Checking</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="panel">
|
||||||
|
<dl class="config-list">
|
||||||
|
<div>
|
||||||
|
<dt>Base URL</dt>
|
||||||
|
<dd id="base-url">Not configured</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt>Token</dt>
|
||||||
|
<dd id="token-state">Not configured</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt>Target repo</dt>
|
||||||
|
<dd id="target-repo">Not configured</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<button id="test-connection" type="button">Test connection</button>
|
||||||
|
<p id="status" role="status">Ready.</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# mp-silma-ai-aide
|
||||||
|
|
||||||
|
This project is a basic Vite + TypeScript Chrome extension scaffold. The extension popup reads Gitea configuration from Vite env variables, stores popup state in Zustand, and uses Axios to call the Gitea API.
|
||||||
|
|
||||||
|
Important files:
|
||||||
|
|
||||||
|
- `AGENTS.md`: Project instructions for coding agents.
|
||||||
|
- `manifest.llm.json`: Machine-readable project metadata.
|
||||||
|
- `.env.example`: Required environment variables.
|
||||||
|
- `vite.config.ts`: Vite build config and generated Chrome extension manifest logic.
|
||||||
|
- `src/api.ts`: Axios client and Gitea configuration handling.
|
||||||
|
- `src/store.ts`: Vanilla Zustand popup state store.
|
||||||
|
- `src/main.ts`: Popup UI behavior.
|
||||||
|
- `index.html`: Popup document.
|
||||||
|
|
||||||
|
Build with `npm run build`. Load the generated `dist/` folder in Chrome as an unpacked extension. Do not edit `dist/` directly.
|
||||||
|
|
||||||
|
Required env variables:
|
||||||
|
|
||||||
|
- `VITE_GITEA_BASE_URL`
|
||||||
|
- `VITE_GITEA_TOKEN`
|
||||||
|
- `VITE_GITEA_REPO_OWNER`
|
||||||
|
- `VITE_GITEA_REPO_NAME`
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"schema_version": "1.0",
|
||||||
|
"name": "mp-silma-ai-aide",
|
||||||
|
"description": "A Vite and TypeScript Chrome extension popup for Gitea-backed Silma AI aide workflows.",
|
||||||
|
"project_type": "chrome-extension",
|
||||||
|
"language": "typescript",
|
||||||
|
"package_manager": "npm",
|
||||||
|
"entrypoints": {
|
||||||
|
"popup_html": "index.html",
|
||||||
|
"popup_script": "src/main.ts",
|
||||||
|
"state_store": "src/store.ts",
|
||||||
|
"api_client": "src/api.ts",
|
||||||
|
"vite_config": "vite.config.ts",
|
||||||
|
"generated_manifest": "dist/manifest.json"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"install": "npm install",
|
||||||
|
"dev": "npm run dev",
|
||||||
|
"build": "npm run build",
|
||||||
|
"preview": "npm run preview"
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"example_file": ".env.example",
|
||||||
|
"local_file": ".env.local",
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"name": "VITE_GITEA_BASE_URL",
|
||||||
|
"required": true,
|
||||||
|
"secret": false,
|
||||||
|
"description": "Base URL for the Gitea instance."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "VITE_GITEA_TOKEN",
|
||||||
|
"required": true,
|
||||||
|
"secret": true,
|
||||||
|
"description": "Gitea API token used by the popup client."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "VITE_GITEA_REPO_OWNER",
|
||||||
|
"required": true,
|
||||||
|
"secret": false,
|
||||||
|
"description": "Owner for the target Gitea repository."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "VITE_GITEA_REPO_NAME",
|
||||||
|
"required": true,
|
||||||
|
"secret": false,
|
||||||
|
"description": "Name of the target Gitea repository."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"generated_paths": [
|
||||||
|
"dist/",
|
||||||
|
"node_modules/"
|
||||||
|
],
|
||||||
|
"agent_guidance": "See AGENTS.md for coding and build instructions. Do not edit dist/ directly; rebuild from source instead."
|
||||||
|
}
|
||||||
Generated
+1710
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "mp-silma-ai-aide",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Chrome extension popup built with Vite for Silma AI aide workflows.",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc --noEmit && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.18.1",
|
||||||
|
"dotenv": "^17.4.2",
|
||||||
|
"zustand": "^5.0.14"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/chrome": "^0.2.2",
|
||||||
|
"@types/node": "^26.1.1",
|
||||||
|
"typescript": "^7.0.2",
|
||||||
|
"vite": "^8.1.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const rawGiteaBaseUrl = import.meta.env.VITE_GITEA_BASE_URL?.trim() ?? "";
|
||||||
|
const giteaToken = import.meta.env.VITE_GITEA_TOKEN?.trim() ?? "";
|
||||||
|
const rawGiteaRepoOwner = import.meta.env.VITE_GITEA_REPO_OWNER?.trim() ?? "";
|
||||||
|
const rawGiteaRepoName = import.meta.env.VITE_GITEA_REPO_NAME?.trim() ?? "";
|
||||||
|
|
||||||
|
export interface GiteaUser {
|
||||||
|
id: number;
|
||||||
|
login: string;
|
||||||
|
full_name?: string;
|
||||||
|
email?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const giteaConfig = {
|
||||||
|
baseUrl: rawGiteaBaseUrl,
|
||||||
|
hasToken: giteaToken.length > 0,
|
||||||
|
repoOwner: rawGiteaRepoOwner,
|
||||||
|
repoName: rawGiteaRepoName,
|
||||||
|
targetRepo:
|
||||||
|
rawGiteaRepoOwner.length > 0 && rawGiteaRepoName.length > 0
|
||||||
|
? `${rawGiteaRepoOwner}/${rawGiteaRepoName}`
|
||||||
|
: "",
|
||||||
|
isConfigured: rawGiteaBaseUrl.length > 0 && giteaToken.length > 0
|
||||||
|
};
|
||||||
|
|
||||||
|
function getApiBaseUrl(baseUrl: string): string {
|
||||||
|
const normalized = baseUrl.replace(/\/+$/, "");
|
||||||
|
return normalized.endsWith("/api/v1") ? normalized : `${normalized}/api/v1`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCurrentGiteaUser(): Promise<GiteaUser> {
|
||||||
|
if (!giteaConfig.isConfigured) {
|
||||||
|
throw new Error("Set VITE_GITEA_BASE_URL and VITE_GITEA_TOKEN before testing the connection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = axios.create({
|
||||||
|
baseURL: getApiBaseUrl(giteaConfig.baseUrl),
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${giteaToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await client.get<GiteaUser>("/user");
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
import "./styles.css";
|
||||||
|
import { appStore } from "./store";
|
||||||
|
|
||||||
|
const configBadge = document.querySelector<HTMLSpanElement>("#config-badge");
|
||||||
|
const baseUrl = document.querySelector<HTMLElement>("#base-url");
|
||||||
|
const tokenState = document.querySelector<HTMLElement>("#token-state");
|
||||||
|
const targetRepo = document.querySelector<HTMLElement>("#target-repo");
|
||||||
|
const status = document.querySelector<HTMLElement>("#status");
|
||||||
|
const testConnection = document.querySelector<HTMLButtonElement>("#test-connection");
|
||||||
|
|
||||||
|
function setText(element: HTMLElement | null, text: string): void {
|
||||||
|
if (element) {
|
||||||
|
element.textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function render(): void {
|
||||||
|
const state = appStore.getState();
|
||||||
|
const config = state.config;
|
||||||
|
|
||||||
|
setText(baseUrl, config.baseUrl || "Not configured");
|
||||||
|
setText(tokenState, config.hasToken ? "Configured" : "Not configured");
|
||||||
|
setText(targetRepo, config.targetRepo || "Not configured");
|
||||||
|
setText(configBadge, config.isConfigured ? "Configured" : "Missing env");
|
||||||
|
|
||||||
|
if (configBadge) {
|
||||||
|
configBadge.dataset.tone = config.isConfigured ? "success" : "warning";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
status.textContent = state.statusMessage;
|
||||||
|
status.dataset.tone = state.statusTone;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testConnection) {
|
||||||
|
testConnection.disabled = state.isTestingConnection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appStore.subscribe(render);
|
||||||
|
render();
|
||||||
|
|
||||||
|
testConnection?.addEventListener("click", () => {
|
||||||
|
void appStore.getState().testConnection();
|
||||||
|
});
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { createStore } from "zustand/vanilla";
|
||||||
|
import { getCurrentGiteaUser, giteaConfig, type GiteaUser } from "./api";
|
||||||
|
|
||||||
|
export type StatusTone = "neutral" | "success" | "error";
|
||||||
|
|
||||||
|
interface AppState {
|
||||||
|
config: typeof giteaConfig;
|
||||||
|
connectedUser: GiteaUser | null;
|
||||||
|
isTestingConnection: boolean;
|
||||||
|
statusMessage: string;
|
||||||
|
statusTone: StatusTone;
|
||||||
|
testConnection: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const appStore = createStore<AppState>()((set) => ({
|
||||||
|
config: giteaConfig,
|
||||||
|
connectedUser: null,
|
||||||
|
isTestingConnection: false,
|
||||||
|
statusMessage: "Ready.",
|
||||||
|
statusTone: "neutral",
|
||||||
|
testConnection: async () => {
|
||||||
|
set({
|
||||||
|
connectedUser: null,
|
||||||
|
isTestingConnection: true,
|
||||||
|
statusMessage: "Testing connection...",
|
||||||
|
statusTone: "neutral"
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await getCurrentGiteaUser();
|
||||||
|
set({
|
||||||
|
connectedUser: user,
|
||||||
|
statusMessage: `Connected as ${user.full_name || user.login}.`,
|
||||||
|
statusTone: "success"
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : "Connection failed.";
|
||||||
|
set({
|
||||||
|
connectedUser: null,
|
||||||
|
statusMessage: message,
|
||||||
|
statusTone: "error"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
set({ isTestingConnection: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
+139
@@ -0,0 +1,139 @@
|
|||||||
|
:root {
|
||||||
|
color: #1f2933;
|
||||||
|
background: #f5f7fa;
|
||||||
|
font-family:
|
||||||
|
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||||
|
sans-serif;
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-width: 360px;
|
||||||
|
margin: 0;
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-shell {
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.masthead {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
margin: 0 0 4px;
|
||||||
|
color: #52616f;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
color: #102a43;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
border: 1px solid #bcccdc;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 5px 9px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #334e68;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge[data-tone="success"] {
|
||||||
|
border-color: #7bc47f;
|
||||||
|
background: #e3f9e5;
|
||||||
|
color: #1b5e20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge[data-tone="warning"] {
|
||||||
|
border-color: #f7c948;
|
||||||
|
background: #fffbea;
|
||||||
|
color: #8d6e00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
border: 1px solid #d9e2ec;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 14px;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 8px 24px rgb(16 42 67 / 8%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-list div {
|
||||||
|
display: grid;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
color: #52616f;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
margin: 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
color: #102a43;
|
||||||
|
font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: #0b6bcb;
|
||||||
|
color: #ffffff;
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
cursor: wait;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status {
|
||||||
|
min-height: 20px;
|
||||||
|
margin: 0;
|
||||||
|
color: #52616f;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status[data-tone="success"] {
|
||||||
|
color: #1b5e20;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status[data-tone="error"] {
|
||||||
|
color: #b42318;
|
||||||
|
}
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_GITEA_BASE_URL?: string;
|
||||||
|
readonly VITE_GITEA_TOKEN?: string;
|
||||||
|
readonly VITE_GITEA_REPO_OWNER?: string;
|
||||||
|
readonly VITE_GITEA_REPO_NAME?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||||
|
"types": ["chrome"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src", "vite.config.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { existsSync, readFileSync } from "node:fs";
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
import { parse as parseDotenv } from "dotenv";
|
||||||
|
import { defineConfig, type Plugin } from "vite";
|
||||||
|
|
||||||
|
function loadExtensionEnv(mode: string): NodeJS.ProcessEnv {
|
||||||
|
const fileEnv: Record<string, string> = {};
|
||||||
|
|
||||||
|
for (const fileName of [".env", ".env.local", `.env.${mode}`, `.env.${mode}.local`]) {
|
||||||
|
const path = resolve(process.cwd(), fileName);
|
||||||
|
|
||||||
|
if (existsSync(path)) {
|
||||||
|
Object.assign(fileEnv, parseDotenv(readFileSync(path)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...fileEnv, ...process.env };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGiteaHostPermissions(baseUrl?: string): string[] {
|
||||||
|
if (!baseUrl?.trim()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = new URL(baseUrl);
|
||||||
|
return [`${url.protocol}//${url.host}/*`];
|
||||||
|
} catch {
|
||||||
|
console.warn(`VITE_GITEA_BASE_URL is not a valid URL: ${baseUrl}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function chromeExtensionManifest(mode: string): Plugin {
|
||||||
|
return {
|
||||||
|
name: "chrome-extension-manifest",
|
||||||
|
generateBundle() {
|
||||||
|
const env = loadExtensionEnv(mode);
|
||||||
|
const manifest = {
|
||||||
|
manifest_version: 3,
|
||||||
|
name: "Silma AI Aide",
|
||||||
|
version: "0.1.0",
|
||||||
|
description: "A Vite-powered popup for Gitea-backed Silma AI aide workflows.",
|
||||||
|
action: {
|
||||||
|
default_title: "Silma AI Aide",
|
||||||
|
default_popup: "index.html"
|
||||||
|
},
|
||||||
|
permissions: ["storage"],
|
||||||
|
host_permissions: getGiteaHostPermissions(env.VITE_GITEA_BASE_URL)
|
||||||
|
};
|
||||||
|
|
||||||
|
this.emitFile({
|
||||||
|
type: "asset",
|
||||||
|
fileName: "manifest.json",
|
||||||
|
source: `${JSON.stringify(manifest, null, 2)}\n`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => ({
|
||||||
|
plugins: [chromeExtensionManifest(mode)],
|
||||||
|
build: {
|
||||||
|
outDir: "dist",
|
||||||
|
emptyOutDir: true,
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
popup: resolve(__dirname, "index.html")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
Reference in New Issue
Block a user