21 changed files with 7744 additions and 2 deletions
@ -1,3 +1,73 @@ |
|||||||
# agent.corneruniverse.com |
# agent |
||||||
|
|
||||||
AI code agent for building websites |
This template should help get you started developing with Vue 3 in Vite. |
||||||
|
|
||||||
|
## Recommended IDE Setup |
||||||
|
|
||||||
|
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). |
||||||
|
|
||||||
|
## Recommended Browser Setup |
||||||
|
|
||||||
|
- Chromium-based browsers (Chrome, Edge, Brave, etc.): |
||||||
|
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) |
||||||
|
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters) |
||||||
|
- Firefox: |
||||||
|
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/) |
||||||
|
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/) |
||||||
|
|
||||||
|
## Type Support for `.vue` Imports in TS |
||||||
|
|
||||||
|
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. |
||||||
|
|
||||||
|
## Customize configuration |
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vite.dev/config/). |
||||||
|
|
||||||
|
## Project Setup |
||||||
|
|
||||||
|
```sh |
||||||
|
npm install |
||||||
|
``` |
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development |
||||||
|
|
||||||
|
```sh |
||||||
|
npm run dev |
||||||
|
``` |
||||||
|
|
||||||
|
### Type-Check, Compile and Minify for Production |
||||||
|
|
||||||
|
```sh |
||||||
|
npm run build |
||||||
|
``` |
||||||
|
|
||||||
|
### Run Unit Tests with [Vitest](https://vitest.dev/) |
||||||
|
|
||||||
|
```sh |
||||||
|
npm run test:unit |
||||||
|
``` |
||||||
|
|
||||||
|
### Run End-to-End Tests with [Playwright](https://playwright.dev) |
||||||
|
|
||||||
|
```sh |
||||||
|
# Install browsers for the first run |
||||||
|
npx playwright install |
||||||
|
|
||||||
|
# When testing on CI, must build the project first |
||||||
|
npm run build |
||||||
|
|
||||||
|
# Runs the end-to-end tests |
||||||
|
npm run test:e2e |
||||||
|
# Runs the tests only on Chromium |
||||||
|
npm run test:e2e -- --project=chromium |
||||||
|
# Runs the tests of a specific file |
||||||
|
npm run test:e2e -- tests/example.spec.ts |
||||||
|
# Runs the tests in debug mode |
||||||
|
npm run test:e2e -- --debug |
||||||
|
``` |
||||||
|
|
||||||
|
### Lint with [ESLint](https://eslint.org/) |
||||||
|
|
||||||
|
```sh |
||||||
|
npm run lint |
||||||
|
``` |
||||||
|
|||||||
@ -0,0 +1,4 @@ |
|||||||
|
{ |
||||||
|
"extends": "@tsconfig/node24/tsconfig.json", |
||||||
|
"include": ["./**/*"] |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
import { test, expect } from '@playwright/test'; |
||||||
|
|
||||||
|
// See here how to get started:
|
||||||
|
// https://playwright.dev/docs/intro
|
||||||
|
test('visits the app root url', async ({ page }) => { |
||||||
|
await page.goto('/'); |
||||||
|
await expect(page.locator('h1')).toHaveText('You did it!'); |
||||||
|
}) |
||||||
@ -0,0 +1,38 @@ |
|||||||
|
import { globalIgnores } from 'eslint/config' |
||||||
|
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript' |
||||||
|
import pluginVue from 'eslint-plugin-vue' |
||||||
|
import pluginPlaywright from 'eslint-plugin-playwright' |
||||||
|
import pluginVitest from '@vitest/eslint-plugin' |
||||||
|
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting' |
||||||
|
import pluginOxlint from 'eslint-plugin-oxlint' |
||||||
|
|
||||||
|
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
|
||||||
|
// import { configureVueProject } from '@vue/eslint-config-typescript'
|
||||||
|
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
|
||||||
|
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
|
||||||
|
|
||||||
|
export default defineConfigWithVueTs( |
||||||
|
{ |
||||||
|
name: 'app/files-to-lint', |
||||||
|
files: ['**/*.{vue,ts,mts,tsx}'], |
||||||
|
}, |
||||||
|
|
||||||
|
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']), |
||||||
|
|
||||||
|
...pluginVue.configs['flat/essential'], |
||||||
|
vueTsConfigs.recommended, |
||||||
|
|
||||||
|
{ |
||||||
|
...pluginPlaywright.configs['flat/recommended'], |
||||||
|
files: ['e2e/**/*.{test,spec}.{js,ts,jsx,tsx}'], |
||||||
|
}, |
||||||
|
|
||||||
|
{ |
||||||
|
...pluginVitest.configs.recommended, |
||||||
|
files: ['src/**/__tests__/*'], |
||||||
|
}, |
||||||
|
|
||||||
|
skipFormatting, |
||||||
|
|
||||||
|
...pluginOxlint.configs['flat/recommended'], |
||||||
|
) |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang=""> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8"> |
||||||
|
<link rel="icon" href="/favicon.ico"> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||||
|
<title>Vite App</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="app"></div> |
||||||
|
<script type="module" src="/src/main.ts"></script> |
||||||
|
</body> |
||||||
|
</html> |
||||||
@ -0,0 +1,54 @@ |
|||||||
|
{ |
||||||
|
"name": "agent", |
||||||
|
"version": "0.0.0", |
||||||
|
"private": true, |
||||||
|
"type": "module", |
||||||
|
"engines": { |
||||||
|
"node": "^20.19.0 || >=22.12.0" |
||||||
|
}, |
||||||
|
"scripts": { |
||||||
|
"dev": "vite", |
||||||
|
"build": "run-p type-check \"build-only {@}\" --", |
||||||
|
"preview": "vite preview", |
||||||
|
"test:unit": "vitest", |
||||||
|
"test:e2e": "playwright test", |
||||||
|
"build-only": "vite build", |
||||||
|
"type-check": "vue-tsc --build", |
||||||
|
"lint": "run-s lint:*", |
||||||
|
"lint:oxlint": "oxlint . --fix", |
||||||
|
"lint:eslint": "eslint . --fix --cache", |
||||||
|
"format": "prettier --write --experimental-cli src/" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"pinia": "^3.0.4", |
||||||
|
"vue": "^3.5.26", |
||||||
|
"vue-router": "^4.6.4" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@playwright/test": "^1.57.0", |
||||||
|
"@tsconfig/node24": "^24.0.3", |
||||||
|
"@types/jsdom": "^27.0.0", |
||||||
|
"@types/node": "^24.10.4", |
||||||
|
"@vitejs/plugin-vue": "^6.0.3", |
||||||
|
"@vitejs/plugin-vue-jsx": "^5.1.3", |
||||||
|
"@vitest/eslint-plugin": "^1.6.5", |
||||||
|
"@vue/eslint-config-prettier": "^10.2.0", |
||||||
|
"@vue/eslint-config-typescript": "^14.6.0", |
||||||
|
"@vue/test-utils": "^2.4.6", |
||||||
|
"@vue/tsconfig": "^0.8.1", |
||||||
|
"eslint": "^9.39.2", |
||||||
|
"eslint-plugin-oxlint": "~1.38.0", |
||||||
|
"eslint-plugin-playwright": "^2.4.0", |
||||||
|
"eslint-plugin-vue": "~10.6.2", |
||||||
|
"jiti": "^2.6.1", |
||||||
|
"jsdom": "^27.4.0", |
||||||
|
"npm-run-all2": "^8.0.4", |
||||||
|
"oxlint": "~1.38.0", |
||||||
|
"prettier": "3.7.4", |
||||||
|
"typescript": "~5.9.3", |
||||||
|
"vite": "beta", |
||||||
|
"vite-plugin-vue-devtools": "^8.0.5", |
||||||
|
"vitest": "^4.0.16", |
||||||
|
"vue-tsc": "^3.2.2" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,110 @@ |
|||||||
|
import process from 'node:process' |
||||||
|
import { defineConfig, devices } from '@playwright/test' |
||||||
|
|
||||||
|
/** |
||||||
|
* Read environment variables from file. |
||||||
|
* https://github.com/motdotla/dotenv
|
||||||
|
*/ |
||||||
|
// require('dotenv').config();
|
||||||
|
|
||||||
|
/** |
||||||
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
|
*/ |
||||||
|
export default defineConfig({ |
||||||
|
testDir: './e2e', |
||||||
|
/* Maximum time one test can run for. */ |
||||||
|
timeout: 30 * 1000, |
||||||
|
expect: { |
||||||
|
/** |
||||||
|
* Maximum time expect() should wait for the condition to be met. |
||||||
|
* For example in `await expect(locator).toHaveText();` |
||||||
|
*/ |
||||||
|
timeout: 5000, |
||||||
|
}, |
||||||
|
/* Fail the build on CI if you accidentally left test.only in the source code. */ |
||||||
|
forbidOnly: !!process.env.CI, |
||||||
|
/* Retry on CI only */ |
||||||
|
retries: process.env.CI ? 2 : 0, |
||||||
|
/* Opt out of parallel tests on CI. */ |
||||||
|
workers: process.env.CI ? 1 : undefined, |
||||||
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ |
||||||
|
reporter: 'html', |
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ |
||||||
|
use: { |
||||||
|
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ |
||||||
|
actionTimeout: 0, |
||||||
|
/* Base URL to use in actions like `await page.goto('/')`. */ |
||||||
|
baseURL: process.env.CI ? 'http://localhost:4173' : 'http://localhost:5173', |
||||||
|
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ |
||||||
|
trace: 'on-first-retry', |
||||||
|
|
||||||
|
/* Only on CI systems run the tests headless */ |
||||||
|
headless: !!process.env.CI, |
||||||
|
}, |
||||||
|
|
||||||
|
/* Configure projects for major browsers */ |
||||||
|
projects: [ |
||||||
|
{ |
||||||
|
name: 'chromium', |
||||||
|
use: { |
||||||
|
...devices['Desktop Chrome'], |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'firefox', |
||||||
|
use: { |
||||||
|
...devices['Desktop Firefox'], |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'webkit', |
||||||
|
use: { |
||||||
|
...devices['Desktop Safari'], |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
/* Test against mobile viewports. */ |
||||||
|
// {
|
||||||
|
// name: 'Mobile Chrome',
|
||||||
|
// use: {
|
||||||
|
// ...devices['Pixel 5'],
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Safari',
|
||||||
|
// use: {
|
||||||
|
// ...devices['iPhone 12'],
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against branded browsers. */ |
||||||
|
// {
|
||||||
|
// name: 'Microsoft Edge',
|
||||||
|
// use: {
|
||||||
|
// channel: 'msedge',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Google Chrome',
|
||||||
|
// use: {
|
||||||
|
// channel: 'chrome',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
], |
||||||
|
|
||||||
|
/* Folder for test artifacts such as screenshots, videos, traces, etc. */ |
||||||
|
// outputDir: 'test-results/',
|
||||||
|
|
||||||
|
/* Run your local dev server before starting the tests */ |
||||||
|
webServer: { |
||||||
|
/** |
||||||
|
* Use the dev server by default for faster feedback loop. |
||||||
|
* Use the preview server on CI for more realistic testing. |
||||||
|
* Playwright will re-use the local server if there is already a dev-server running. |
||||||
|
*/ |
||||||
|
command: process.env.CI ? 'npm run preview' : 'npm run dev', |
||||||
|
port: process.env.CI ? 4173 : 5173, |
||||||
|
reuseExistingServer: !process.env.CI, |
||||||
|
}, |
||||||
|
}) |
||||||
|
After Width: | Height: | Size: 4.2 KiB |
@ -0,0 +1,397 @@ |
|||||||
|
<template> |
||||||
|
<div id="app"> |
||||||
|
<header> |
||||||
|
<h1>AI Code Agent</h1> |
||||||
|
<div v-if="user" class="user-info"> |
||||||
|
{{ user.username }} |
||||||
|
<button @click="logout">Logout</button> |
||||||
|
</div> |
||||||
|
</header> |
||||||
|
|
||||||
|
<main> |
||||||
|
<!-- Login --> |
||||||
|
<div v-if="!user" class="login-section"> |
||||||
|
<p>Login with your Gitea account to get started.</p> |
||||||
|
<a :href="apiUrl + '/api/auth/login'" class="btn">Login with Gitea</a> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- Main Interface --> |
||||||
|
<div v-else class="workspace"> |
||||||
|
<!-- Repo Selection --> |
||||||
|
<div class="section"> |
||||||
|
<label>Repository</label> |
||||||
|
<select v-model="selectedRepo" @change="clearState"> |
||||||
|
<option value="">Select a repository...</option> |
||||||
|
<option v-for="repo in repos" :key="repo.name" :value="repo.name"> |
||||||
|
{{ repo.name }} |
||||||
|
</option> |
||||||
|
</select> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- Prompt Input --> |
||||||
|
<div v-if="selectedRepo" class="section"> |
||||||
|
<label>What would you like to change?</label> |
||||||
|
<textarea |
||||||
|
v-model="prompt" |
||||||
|
placeholder="e.g., Add a contact form to the homepage with name, email, and message fields" |
||||||
|
rows="4" |
||||||
|
></textarea> |
||||||
|
<button |
||||||
|
@click="generate" |
||||||
|
:disabled="generating || !prompt.trim()" |
||||||
|
class="btn" |
||||||
|
> |
||||||
|
{{ generating ? "Generating..." : "Generate Changes" }} |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- Error Display --> |
||||||
|
<div v-if="error" class="error"> |
||||||
|
{{ error }} |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- Generated Changes --> |
||||||
|
<div v-if="operations" class="section"> |
||||||
|
<h2>Proposed Changes</h2> |
||||||
|
<p class="summary">{{ operations.summary }}</p> |
||||||
|
|
||||||
|
<div |
||||||
|
v-for="op in operations.operations" |
||||||
|
:key="op.path" |
||||||
|
class="file-change" |
||||||
|
> |
||||||
|
<div class="file-header"> |
||||||
|
<span class="action" :class="op.action">{{ op.action }}</span> |
||||||
|
<span class="path">{{ op.path }}</span> |
||||||
|
</div> |
||||||
|
<pre v-if="op.content" class="content">{{ op.content }}</pre> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="actions"> |
||||||
|
<button @click="submit" :disabled="submitting" class="btn primary"> |
||||||
|
{{ submitting ? "Creating PR..." : "Create Pull Request" }} |
||||||
|
</button> |
||||||
|
<button @click="clearState" class="btn secondary">Discard</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- PR Created --> |
||||||
|
<div v-if="result" class="section success"> |
||||||
|
<h2>Pull Request Created!</h2> |
||||||
|
<p> |
||||||
|
<a :href="result.pr_url" target="_blank" |
||||||
|
>View PR #{{ result.pr_number }} in Gitea</a |
||||||
|
> |
||||||
|
</p> |
||||||
|
<p v-if="result.preview_url"> |
||||||
|
<a :href="result.preview_url" target="_blank" |
||||||
|
>Preview: {{ result.preview_url }}</a |
||||||
|
> |
||||||
|
<br /><small>(Preview will be ready in ~30 seconds)</small> |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</main> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
data() { |
||||||
|
return { |
||||||
|
apiUrl: import.meta.env.VITE_API_URL || "", |
||||||
|
user: null, |
||||||
|
repos: [], |
||||||
|
selectedRepo: "", |
||||||
|
prompt: "", |
||||||
|
generating: false, |
||||||
|
operations: null, |
||||||
|
submitting: false, |
||||||
|
result: null, |
||||||
|
error: null, |
||||||
|
}; |
||||||
|
}, |
||||||
|
async mounted() { |
||||||
|
await this.checkAuth(); |
||||||
|
if (this.user) { |
||||||
|
await this.loadRepos(); |
||||||
|
} |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
async checkAuth() { |
||||||
|
try { |
||||||
|
const res = await fetch(`${this.apiUrl}/api/auth/user`, { |
||||||
|
credentials: "include", |
||||||
|
}); |
||||||
|
if (res.ok) { |
||||||
|
this.user = await res.json(); |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
console.error("Auth check failed:", e); |
||||||
|
} |
||||||
|
}, |
||||||
|
async logout() { |
||||||
|
await fetch(`${this.apiUrl}/api/auth/logout`, { |
||||||
|
method: "POST", |
||||||
|
credentials: "include", |
||||||
|
}); |
||||||
|
this.user = null; |
||||||
|
this.repos = []; |
||||||
|
this.clearState(); |
||||||
|
}, |
||||||
|
async loadRepos() { |
||||||
|
try { |
||||||
|
const res = await fetch(`${this.apiUrl}/api/repos`, { |
||||||
|
credentials: "include", |
||||||
|
}); |
||||||
|
if (res.ok) { |
||||||
|
this.repos = await res.json(); |
||||||
|
} |
||||||
|
} catch (e) { |
||||||
|
console.error("Failed to load repos:", e); |
||||||
|
} |
||||||
|
}, |
||||||
|
clearState() { |
||||||
|
this.prompt = ""; |
||||||
|
this.operations = null; |
||||||
|
this.result = null; |
||||||
|
this.error = null; |
||||||
|
}, |
||||||
|
async generate() { |
||||||
|
this.generating = true; |
||||||
|
this.error = null; |
||||||
|
this.operations = null; |
||||||
|
this.result = null; |
||||||
|
|
||||||
|
try { |
||||||
|
const res = await fetch(`${this.apiUrl}/api/generate`, { |
||||||
|
method: "POST", |
||||||
|
headers: { "Content-Type": "application/json" }, |
||||||
|
credentials: "include", |
||||||
|
body: JSON.stringify({ |
||||||
|
repo: this.selectedRepo, |
||||||
|
prompt: this.prompt, |
||||||
|
}), |
||||||
|
}); |
||||||
|
|
||||||
|
const data = await res.json(); |
||||||
|
if (!res.ok) throw new Error(data.error || "Generation failed"); |
||||||
|
|
||||||
|
this.operations = data; |
||||||
|
} catch (e) { |
||||||
|
this.error = e.message; |
||||||
|
} finally { |
||||||
|
this.generating = false; |
||||||
|
} |
||||||
|
}, |
||||||
|
async submit() { |
||||||
|
this.submitting = true; |
||||||
|
this.error = null; |
||||||
|
|
||||||
|
try { |
||||||
|
const res = await fetch(`${this.apiUrl}/api/submit`, { |
||||||
|
method: "POST", |
||||||
|
headers: { "Content-Type": "application/json" }, |
||||||
|
credentials: "include", |
||||||
|
body: JSON.stringify({ |
||||||
|
repo: this.selectedRepo, |
||||||
|
operations: this.operations, |
||||||
|
summary: this.operations.summary, |
||||||
|
prompt: this.prompt, |
||||||
|
}), |
||||||
|
}); |
||||||
|
|
||||||
|
const data = await res.json(); |
||||||
|
if (!res.ok) throw new Error(data.error || "Submit failed"); |
||||||
|
|
||||||
|
this.result = data; |
||||||
|
this.operations = null; |
||||||
|
} catch (e) { |
||||||
|
this.error = e.message; |
||||||
|
} finally { |
||||||
|
this.submitting = false; |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
}; |
||||||
|
</script> |
||||||
|
|
||||||
|
<style> |
||||||
|
* { |
||||||
|
box-sizing: border-box; |
||||||
|
} |
||||||
|
|
||||||
|
body { |
||||||
|
margin: 0; |
||||||
|
font-family: |
||||||
|
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; |
||||||
|
background: #f5f5f5; |
||||||
|
} |
||||||
|
|
||||||
|
#app { |
||||||
|
max-width: 800px; |
||||||
|
margin: 0 auto; |
||||||
|
padding: 20px; |
||||||
|
} |
||||||
|
|
||||||
|
header { |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: center; |
||||||
|
margin-bottom: 30px; |
||||||
|
} |
||||||
|
|
||||||
|
h1 { |
||||||
|
margin: 0; |
||||||
|
font-size: 24px; |
||||||
|
} |
||||||
|
|
||||||
|
.user-info { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
gap: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
.section { |
||||||
|
background: white; |
||||||
|
padding: 20px; |
||||||
|
border-radius: 8px; |
||||||
|
margin-bottom: 20px; |
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |
||||||
|
} |
||||||
|
|
||||||
|
label { |
||||||
|
display: block; |
||||||
|
font-weight: 600; |
||||||
|
margin-bottom: 8px; |
||||||
|
} |
||||||
|
|
||||||
|
select, |
||||||
|
textarea { |
||||||
|
width: 100%; |
||||||
|
padding: 10px; |
||||||
|
border: 1px solid #ddd; |
||||||
|
border-radius: 4px; |
||||||
|
font-size: 16px; |
||||||
|
margin-bottom: 15px; |
||||||
|
} |
||||||
|
|
||||||
|
textarea { |
||||||
|
resize: vertical; |
||||||
|
} |
||||||
|
|
||||||
|
.btn { |
||||||
|
display: inline-block; |
||||||
|
padding: 10px 20px; |
||||||
|
background: #4a9eff; |
||||||
|
color: white; |
||||||
|
border: none; |
||||||
|
border-radius: 4px; |
||||||
|
font-size: 16px; |
||||||
|
cursor: pointer; |
||||||
|
text-decoration: none; |
||||||
|
} |
||||||
|
|
||||||
|
.btn:hover { |
||||||
|
background: #3a8eef; |
||||||
|
} |
||||||
|
|
||||||
|
.btn:disabled { |
||||||
|
background: #ccc; |
||||||
|
cursor: not-allowed; |
||||||
|
} |
||||||
|
|
||||||
|
.btn.secondary { |
||||||
|
background: #666; |
||||||
|
} |
||||||
|
|
||||||
|
.btn.primary { |
||||||
|
background: #22c55e; |
||||||
|
} |
||||||
|
|
||||||
|
.error { |
||||||
|
background: #fee; |
||||||
|
color: #c00; |
||||||
|
padding: 15px; |
||||||
|
border-radius: 4px; |
||||||
|
margin-bottom: 20px; |
||||||
|
} |
||||||
|
|
||||||
|
.success { |
||||||
|
background: #efe; |
||||||
|
border: 1px solid #afa; |
||||||
|
} |
||||||
|
|
||||||
|
.summary { |
||||||
|
font-style: italic; |
||||||
|
color: #666; |
||||||
|
} |
||||||
|
|
||||||
|
.file-change { |
||||||
|
border: 1px solid #ddd; |
||||||
|
border-radius: 4px; |
||||||
|
margin: 15px 0; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
|
||||||
|
.file-header { |
||||||
|
background: #f5f5f5; |
||||||
|
padding: 10px; |
||||||
|
display: flex; |
||||||
|
gap: 10px; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.action { |
||||||
|
padding: 2px 8px; |
||||||
|
border-radius: 3px; |
||||||
|
font-size: 12px; |
||||||
|
font-weight: 600; |
||||||
|
text-transform: uppercase; |
||||||
|
} |
||||||
|
|
||||||
|
.action.modify { |
||||||
|
background: #fef3c7; |
||||||
|
color: #92400e; |
||||||
|
} |
||||||
|
.action.create { |
||||||
|
background: #d1fae5; |
||||||
|
color: #065f46; |
||||||
|
} |
||||||
|
.action.delete { |
||||||
|
background: #fee2e2; |
||||||
|
color: #991b1b; |
||||||
|
} |
||||||
|
|
||||||
|
.path { |
||||||
|
font-family: monospace; |
||||||
|
font-size: 14px; |
||||||
|
} |
||||||
|
|
||||||
|
.content { |
||||||
|
margin: 0; |
||||||
|
padding: 15px; |
||||||
|
overflow-x: auto; |
||||||
|
background: #fafafa; |
||||||
|
font-size: 13px; |
||||||
|
max-height: 300px; |
||||||
|
overflow-y: auto; |
||||||
|
} |
||||||
|
|
||||||
|
.actions { |
||||||
|
display: flex; |
||||||
|
gap: 10px; |
||||||
|
margin-top: 20px; |
||||||
|
} |
||||||
|
|
||||||
|
.login-section { |
||||||
|
text-align: center; |
||||||
|
padding: 60px 20px; |
||||||
|
background: white; |
||||||
|
border-radius: 8px; |
||||||
|
} |
||||||
|
|
||||||
|
a { |
||||||
|
color: #4a9eff; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
import { describe, it, expect } from "vitest"; |
||||||
|
|
||||||
|
import { mount } from "@vue/test-utils"; |
||||||
|
import App from "../App.vue"; |
||||||
|
|
||||||
|
describe("App", () => { |
||||||
|
it("mounts renders properly", () => { |
||||||
|
const wrapper = mount(App); |
||||||
|
expect(wrapper.text()).toContain("You did it!"); |
||||||
|
}); |
||||||
|
}); |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
import { createApp } from "vue"; |
||||||
|
import { createPinia } from "pinia"; |
||||||
|
|
||||||
|
import App from "./App.vue"; |
||||||
|
import router from "./router"; |
||||||
|
|
||||||
|
const app = createApp(App); |
||||||
|
|
||||||
|
app.use(createPinia()); |
||||||
|
app.use(router); |
||||||
|
|
||||||
|
app.mount("#app"); |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
import { createRouter, createWebHistory } from "vue-router"; |
||||||
|
|
||||||
|
const router = createRouter({ |
||||||
|
history: createWebHistory(import.meta.env.BASE_URL), |
||||||
|
routes: [], |
||||||
|
}); |
||||||
|
|
||||||
|
export default router; |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
import { ref, computed } from "vue"; |
||||||
|
import { defineStore } from "pinia"; |
||||||
|
|
||||||
|
export const useCounterStore = defineStore("counter", () => { |
||||||
|
const count = ref(0); |
||||||
|
const doubleCount = computed(() => count.value * 2); |
||||||
|
function increment() { |
||||||
|
count.value++; |
||||||
|
} |
||||||
|
|
||||||
|
return { count, doubleCount, increment }; |
||||||
|
}); |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
{ |
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json", |
||||||
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"], |
||||||
|
"exclude": ["src/**/__tests__/*"], |
||||||
|
"compilerOptions": { |
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", |
||||||
|
|
||||||
|
"paths": { |
||||||
|
"@/*": ["./src/*"] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,14 @@ |
|||||||
|
{ |
||||||
|
"files": [], |
||||||
|
"references": [ |
||||||
|
{ |
||||||
|
"path": "./tsconfig.node.json" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"path": "./tsconfig.app.json" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"path": "./tsconfig.vitest.json" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
{ |
||||||
|
"extends": "@tsconfig/node24/tsconfig.json", |
||||||
|
"include": [ |
||||||
|
"vite.config.*", |
||||||
|
"vitest.config.*", |
||||||
|
"cypress.config.*", |
||||||
|
"nightwatch.conf.*", |
||||||
|
"playwright.config.*", |
||||||
|
"eslint.config.*" |
||||||
|
], |
||||||
|
"compilerOptions": { |
||||||
|
"noEmit": true, |
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", |
||||||
|
|
||||||
|
"module": "ESNext", |
||||||
|
"moduleResolution": "Bundler", |
||||||
|
"types": ["node"] |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
{ |
||||||
|
"extends": "./tsconfig.app.json", |
||||||
|
"include": ["src/**/__tests__/*", "env.d.ts"], |
||||||
|
"exclude": [], |
||||||
|
"compilerOptions": { |
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo", |
||||||
|
|
||||||
|
"lib": [], |
||||||
|
"types": ["node", "jsdom"] |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,20 @@ |
|||||||
|
import { fileURLToPath, URL } from 'node:url' |
||||||
|
|
||||||
|
import { defineConfig } from 'vite' |
||||||
|
import vue from '@vitejs/plugin-vue' |
||||||
|
import vueJsx from '@vitejs/plugin-vue-jsx' |
||||||
|
import vueDevTools from 'vite-plugin-vue-devtools' |
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({ |
||||||
|
plugins: [ |
||||||
|
vue(), |
||||||
|
vueJsx(), |
||||||
|
vueDevTools(), |
||||||
|
], |
||||||
|
resolve: { |
||||||
|
alias: { |
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)) |
||||||
|
}, |
||||||
|
}, |
||||||
|
}) |
||||||
@ -0,0 +1,14 @@ |
|||||||
|
import { fileURLToPath } from 'node:url' |
||||||
|
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config' |
||||||
|
import viteConfig from './vite.config' |
||||||
|
|
||||||
|
export default mergeConfig( |
||||||
|
viteConfig, |
||||||
|
defineConfig({ |
||||||
|
test: { |
||||||
|
environment: 'jsdom', |
||||||
|
exclude: [...configDefaults.exclude, 'e2e/**'], |
||||||
|
root: fileURLToPath(new URL('./', import.meta.url)), |
||||||
|
}, |
||||||
|
}), |
||||||
|
) |
||||||
Loading…
Reference in new issue