├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── config.yml
│ └── bug_report.yml
└── workflows
│ └── inactive-issues.yml
├── ui-shell
├── bun.lockb
├── jsconfig.json
├── src
│ ├── assets
│ │ ├── style.css
│ │ ├── tailwind-primevue-preset
│ │ │ ├── avatargroup
│ │ │ │ └── index.js
│ │ │ ├── inputgroup
│ │ │ │ └── index.js
│ │ │ ├── ripple
│ │ │ │ └── index.js
│ │ │ ├── skeleton
│ │ │ │ └── index.js
│ │ │ ├── inputgroupaddon
│ │ │ │ └── index.js
│ │ │ ├── toolbar
│ │ │ │ └── index.js
│ │ │ ├── inputmask
│ │ │ │ └── index.js
│ │ │ ├── card
│ │ │ │ └── index.js
│ │ │ ├── dataview
│ │ │ │ └── index.js
│ │ │ ├── inlinemessage
│ │ │ │ └── index.js
│ │ │ ├── chip
│ │ │ │ └── index.js
│ │ │ ├── tooltip
│ │ │ │ └── index.js
│ │ │ ├── knob
│ │ │ │ └── index.js
│ │ │ ├── textarea
│ │ │ │ └── index.js
│ │ │ ├── tag
│ │ │ │ └── index.js
│ │ │ ├── breadcrumb
│ │ │ │ └── index.js
│ │ │ ├── overlaypanel
│ │ │ │ └── index.js
│ │ │ ├── terminal
│ │ │ │ └── index.js
│ │ │ ├── avatar
│ │ │ │ └── index.js
│ │ │ ├── scrolltop
│ │ │ │ └── index.js
│ │ │ ├── inputtext
│ │ │ │ └── index.js
│ │ │ ├── selectbutton
│ │ │ │ └── index.js
│ │ │ ├── badge
│ │ │ │ └── index.js
│ │ │ ├── badgedirective
│ │ │ │ └── index.js
│ │ │ ├── scrollpanel
│ │ │ │ └── index.js
│ │ │ ├── dataviewlayoutoptions
│ │ │ │ └── index.js
│ │ │ ├── global.js
│ │ │ ├── progressbar
│ │ │ │ └── index.js
│ │ │ ├── rating
│ │ │ │ └── index.js
│ │ │ ├── password
│ │ │ │ └── index.js
│ │ │ ├── accordion
│ │ │ │ └── index.js
│ │ │ ├── togglebutton
│ │ │ │ └── index.js
│ │ │ ├── radiobutton
│ │ │ │ └── index.js
│ │ │ ├── message
│ │ │ │ └── index.js
│ │ │ ├── divider
│ │ │ │ └── index.js
│ │ │ ├── tabmenu
│ │ │ │ └── index.js
│ │ │ ├── inputswitch
│ │ │ │ └── index.js
│ │ │ ├── checkbox
│ │ │ │ └── index.js
│ │ │ ├── fieldset
│ │ │ │ └── index.js
│ │ │ ├── panel
│ │ │ │ └── index.js
│ │ │ ├── dock
│ │ │ │ └── index.js
│ │ │ ├── chips
│ │ │ │ └── index.js
│ │ │ ├── tristatecheckbox
│ │ │ │ └── index.js
│ │ │ ├── toast
│ │ │ │ └── index.js
│ │ │ ├── menu
│ │ │ │ └── index.js
│ │ │ └── tieredmenu
│ │ │ │ └── index.js
│ │ └── base.css
│ ├── router
│ │ └── index.js
│ ├── stores
│ │ └── counter.js
│ ├── components
│ │ └── __tests__
│ │ │ └── empty-state.cy.js
│ └── main.js
├── public
│ └── favicon.ico
├── postcss.config.js
├── cypress
│ ├── fixtures
│ │ └── example.json
│ ├── e2e
│ │ ├── jsconfig.json
│ │ └── example.cy.js
│ └── support
│ │ ├── component-index.html
│ │ ├── e2e.js
│ │ ├── commands.js
│ │ └── component.js
├── build.sh
├── vite.config.js
├── index.html
├── cypress.config.js
├── package.json
├── README.md
└── tailwind.config.js
├── resources
├── devdb.png
├── devdb.xcf
├── devdb-128x128.png
├── featured
│ ├── ddev.png
│ ├── daily-dev.png
│ ├── laravel-new.png
│ └── test-dev-tools.png
├── screenshots
│ ├── devdb.gif
│ ├── devdb.png
│ ├── main-view.png
│ ├── devdb-dad-joke.png
│ ├── new
│ │ ├── main-dark.png
│ │ ├── main-light.png
│ │ ├── mcp-setup.png
│ │ ├── mcp-usage.png
│ │ ├── providers.png
│ │ ├── mysql-explain.png
│ │ ├── main-light-dark.png
│ │ ├── main-light-dark.xcf
│ │ ├── providers-dark.png
│ │ ├── ui-actions-preview.png
│ │ ├── providers-light-dark.png
│ │ ├── providers-light-dark.xcf
│ │ ├── context-menu-contributions.png
│ │ └── laravel-eloquent-code-lens.png
│ ├── laravel-code-lens.png
│ ├── light-dark-mode.png
│ ├── light-dark-mode.xcf
│ ├── sample-view-location.png
│ ├── light-dark-mode-with-qr.png
│ ├── light-dark-mode-with-qr.xcf
│ ├── context-menu-contribution.png
│ └── sample-view-location-plain.png
├── light-dark-mode-with-qr.xcf
└── devdb-marketplace-url-qr-code.png
├── apps
├── rails
│ ├── todo-app
│ │ └── .gitignore
│ └── README.md
└── django
│ ├── todo-app
│ └── .gitignore
│ └── README.md
├── CHANGELOG.md
├── src
├── services
│ ├── mcp
│ │ └── no-vscode
│ │ │ ├── config.ts
│ │ │ ├── README.md
│ │ │ ├── logger.ts
│ │ │ └── port-manager.ts
│ ├── initialization-error-service.ts
│ ├── random-string-generator.ts
│ ├── logging-service.ts
│ ├── document-service.ts
│ ├── error-notification-service.ts
│ ├── codelens
│ │ └── code-lens-service.ts
│ ├── output-service.ts
│ ├── laravel
│ │ ├── code-runner
│ │ │ └── qualifier-service.ts
│ │ ├── sail.ts
│ │ ├── artisan-service.ts
│ │ └── laravel-core.ts
│ ├── pagination.ts
│ ├── config-service.ts
│ ├── connector.ts
│ ├── context-menu-service.ts
│ ├── string.ts
│ ├── workspace.ts
│ ├── go-to-table.ts
│ ├── adonis
│ │ ├── env-file-parser.ts
│ │ └── adonis-core.ts
│ └── html.ts
├── constants.ts
├── test
│ ├── runTest.ts
│ └── suite
│ │ ├── index.ts
│ │ └── services
│ │ ├── string.test.ts
│ │ ├── qualifier-service.test.ts
│ │ └── pagination.test.ts
├── providers
│ ├── postgres
│ │ ├── laravel-postgres-provider.ts
│ │ ├── ddev-postgres-provider.ts
│ │ ├── rails-postgres-provider.ts
│ │ ├── django-postgres-provider.ts
│ │ └── adonis-postgres-provider.ts
│ ├── sqlite
│ │ ├── file-picker-sqlite-provider.ts
│ │ ├── laravel-local-sqlite-provider.ts
│ │ └── rails-sqlite-provider.ts
│ └── mysql
│ │ ├── ddev-mysql-provider.ts
│ │ ├── laravel-mysql-provider.ts
│ │ ├── adonis-mysql-provider.ts
│ │ ├── rails-mysql-provider.ts
│ │ └── django-mysql-provider.ts
└── uri-handler.ts
├── scripts
├── showcase.sh
└── triage
│ ├── sqlite-triage.sh
│ ├── mysql-triage.sh
│ └── postgres-triage.sh
├── .vscode
├── extensions.json
├── settings.json
├── tasks.json
└── launch.json
├── .vscodeignore
├── .eslintrc.json
├── tsconfig.json
├── LICENSE.txt
├── .gitignore
├── esbuild.js
└── snippets
└── devdbrc.json
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: damms005
2 |
--------------------------------------------------------------------------------
/ui-shell/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/ui-shell/bun.lockb
--------------------------------------------------------------------------------
/ui-shell/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/resources/devdb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/devdb.png
--------------------------------------------------------------------------------
/resources/devdb.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/devdb.xcf
--------------------------------------------------------------------------------
/ui-shell/src/assets/style.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
--------------------------------------------------------------------------------
/resources/devdb-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/devdb-128x128.png
--------------------------------------------------------------------------------
/resources/featured/ddev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/featured/ddev.png
--------------------------------------------------------------------------------
/ui-shell/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/ui-shell/public/favicon.ico
--------------------------------------------------------------------------------
/resources/featured/daily-dev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/featured/daily-dev.png
--------------------------------------------------------------------------------
/resources/screenshots/devdb.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/devdb.gif
--------------------------------------------------------------------------------
/resources/screenshots/devdb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/devdb.png
--------------------------------------------------------------------------------
/resources/featured/laravel-new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/featured/laravel-new.png
--------------------------------------------------------------------------------
/resources/screenshots/main-view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/main-view.png
--------------------------------------------------------------------------------
/resources/featured/test-dev-tools.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/featured/test-dev-tools.png
--------------------------------------------------------------------------------
/resources/light-dark-mode-with-qr.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/light-dark-mode-with-qr.xcf
--------------------------------------------------------------------------------
/ui-shell/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/resources/screenshots/devdb-dad-joke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/devdb-dad-joke.png
--------------------------------------------------------------------------------
/resources/screenshots/new/main-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/new/main-dark.png
--------------------------------------------------------------------------------
/resources/screenshots/new/main-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/new/main-light.png
--------------------------------------------------------------------------------
/resources/screenshots/new/mcp-setup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/new/mcp-setup.png
--------------------------------------------------------------------------------
/resources/screenshots/new/mcp-usage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/new/mcp-usage.png
--------------------------------------------------------------------------------
/resources/screenshots/new/providers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/new/providers.png
--------------------------------------------------------------------------------
/resources/devdb-marketplace-url-qr-code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/devdb-marketplace-url-qr-code.png
--------------------------------------------------------------------------------
/resources/screenshots/laravel-code-lens.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/laravel-code-lens.png
--------------------------------------------------------------------------------
/resources/screenshots/light-dark-mode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/light-dark-mode.png
--------------------------------------------------------------------------------
/resources/screenshots/light-dark-mode.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/light-dark-mode.xcf
--------------------------------------------------------------------------------
/resources/screenshots/new/mysql-explain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/new/mysql-explain.png
--------------------------------------------------------------------------------
/resources/screenshots/new/main-light-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/new/main-light-dark.png
--------------------------------------------------------------------------------
/resources/screenshots/new/main-light-dark.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/new/main-light-dark.xcf
--------------------------------------------------------------------------------
/resources/screenshots/new/providers-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/new/providers-dark.png
--------------------------------------------------------------------------------
/resources/screenshots/new/ui-actions-preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/new/ui-actions-preview.png
--------------------------------------------------------------------------------
/resources/screenshots/sample-view-location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/sample-view-location.png
--------------------------------------------------------------------------------
/resources/screenshots/light-dark-mode-with-qr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/light-dark-mode-with-qr.png
--------------------------------------------------------------------------------
/resources/screenshots/light-dark-mode-with-qr.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/light-dark-mode-with-qr.xcf
--------------------------------------------------------------------------------
/resources/screenshots/new/providers-light-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/new/providers-light-dark.png
--------------------------------------------------------------------------------
/resources/screenshots/new/providers-light-dark.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/new/providers-light-dark.xcf
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/avatargroup/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: {
3 | class: 'flex items-center'
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/resources/screenshots/context-menu-contribution.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/context-menu-contribution.png
--------------------------------------------------------------------------------
/resources/screenshots/sample-view-location-plain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/sample-view-location-plain.png
--------------------------------------------------------------------------------
/apps/rails/todo-app/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this ephemeral Rails application directory
2 | *
3 | # But don't ignore this .gitignore file itself
4 | !.gitignore
5 |
--------------------------------------------------------------------------------
/resources/screenshots/new/context-menu-contributions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/new/context-menu-contributions.png
--------------------------------------------------------------------------------
/resources/screenshots/new/laravel-eloquent-code-lens.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/damms005/devdb-vscode/HEAD/resources/screenshots/new/laravel-eloquent-code-lens.png
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/inputgroup/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: {
3 | class: ['flex items-stretch', 'w-full']
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/apps/django/todo-app/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this ephemeral Django application directory
2 | *
3 | # But don't ignore this .gitignore file itself
4 | !.gitignore
5 |
--------------------------------------------------------------------------------
/ui-shell/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hey@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/ui-shell/cypress/e2e/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["es5", "dom"],
5 | "types": ["cypress"]
6 | },
7 | "include": ["./**/*", "../support/**/*"]
8 | }
9 |
--------------------------------------------------------------------------------
/ui-shell/cypress/e2e/example.cy.js:
--------------------------------------------------------------------------------
1 | // https://on.cypress.io/api
2 |
3 | describe('My First Test', () => {
4 | it('visits the app root url', () => {
5 | cy.visit('/')
6 | cy.contains('h1', 'You did it!')
7 | })
8 | })
9 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Feature request
4 | url: https://github.com/damms005/devdb-vscode/discussions/new?category=ideas
5 | about: Share ideas for new features
6 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/ripple/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: {
3 | class: ['block absolute bg-surface-0/50 rounded-full pointer-events-none'],
4 | style: 'transform: scale(0)'
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | We try not to use 'WIP' commit messages. Hence, a commit log with descriptive messages is the best CHANGELOG we can offer for now.
2 |
3 | [Click here for full commit log](https://github.com/damms005/devdb-vscode/commits/main)
4 |
--------------------------------------------------------------------------------
/src/services/mcp/no-vscode/config.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as os from 'os';
3 |
4 | export const MCP_CONFIG_DIR = path.join(os.homedir(), '.devdb');
5 | export const MCP_CONFIG_FILE = path.join(MCP_CONFIG_DIR, 'mcp.json');
6 |
--------------------------------------------------------------------------------
/scripts/showcase.sh:
--------------------------------------------------------------------------------
1 | # Script to showcase new DevDb features. It opens a new VS Code instance
2 | # with a Profile that is preset to suit making DevDb screenshots and screen casts
3 | code --profile "DevDb screenshot" '/Users/damms005/Herd/nft-marketplace'
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See http://go.microsoft.com/fwlink/?LinkId=827846
3 | // for the documentation about the extensions.json format
4 | "recommendations": [
5 | "dbaeumer.vscode-eslint",
6 | "connor4312.esbuild-problem-matchers"
7 | ]
8 | }
--------------------------------------------------------------------------------
/ui-shell/src/router/index.js:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory } from 'vue-router'
2 |
3 | const router = createRouter({
4 | history: createWebHistory(import.meta.env.BASE_URL),
5 | routes: [
6 | ]
7 | })
8 |
9 | export default router
10 |
--------------------------------------------------------------------------------
/ui-shell/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | echo 'Building Vue assets'
3 | pwd
4 | npm run build
5 | cd ..
6 | pwd
7 | echo 'Compiling extension code'
8 | npm run compile
9 | cd -
10 | echo 'Back to Vue project folder'
11 | pwd
12 | echo "Completed at $(date +"%Y-%m-%d %I:%M:%S%p")"
13 |
--------------------------------------------------------------------------------
/src/services/initialization-error-service.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * When running tests, the vscode module is not available.
3 | * So we here to prevent "Cannot find module 'vscode'" error.
4 | */
5 | export async function reportError(error: string) {
6 | const vscode = await require('vscode');
7 | vscode.window.showErrorMessage(`${String(error)}`)
8 | }
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const enum ExtensionConstants {
2 | extensionId = "damms005.devdb",
3 | globalVersionKey = "devdb-version",
4 | clickedGitHubStarring = "clicked-on-devdb-github-repo-starring",
5 | clickedToFollowOnX = "clicked-to-follow-on-x",
6 | clickedToSupport = "clicked-to-support",
7 | clickedToSponsor = "clicked-to-sponsor",
8 | }
9 |
--------------------------------------------------------------------------------
/ui-shell/cypress/support/component-index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Components App
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ui-shell/src/stores/counter.js:
--------------------------------------------------------------------------------
1 | import { ref, computed } from 'vue'
2 | import { defineStore } from 'pinia'
3 |
4 | export const useCounterStore = defineStore('counter', () => {
5 | const count = ref(0)
6 | const doubleCount = computed(() => count.value * 2)
7 | function increment() {
8 | count.value++
9 | }
10 |
11 | return { count, doubleCount, increment }
12 | })
13 |
--------------------------------------------------------------------------------
/ui-shell/vite.config.js:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from "node:url";
2 |
3 | import { defineConfig } from "vite";
4 | import vue from "@vitejs/plugin-vue";
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | plugins: [vue()],
9 | resolve: {
10 | alias: {
11 | "@": fileURLToPath(new URL("./src", import.meta.url)),
12 | },
13 | },
14 | });
15 |
--------------------------------------------------------------------------------
/ui-shell/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite App
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ui-shell/src/components/__tests__/empty-state.cy.js:
--------------------------------------------------------------------------------
1 | import Initializer from '../Initializer.vue'
2 |
3 | describe('Initializer', () => {
4 | it('playground', () => {
5 | cy.mount(Initializer, { props: { msg: 'Hey!' } })
6 | })
7 |
8 | it('renders properly', () => {
9 | cy.mount(Initializer, { props: { msg: 'Hey!' } })
10 | cy.get('h1').should('contain', 'Hey!')
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/ui-shell/cypress.config.js:
--------------------------------------------------------------------------------
1 | const { defineConfig } = require('cypress')
2 |
3 | module.exports = defineConfig({
4 | e2e: {
5 | specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}',
6 | baseUrl: 'http://localhost:4173'
7 | },
8 | component: {
9 | specPattern: 'src/**/__tests__/*.{cy,spec}.{js,ts,jsx,tsx}',
10 | devServer: {
11 | framework: 'vue',
12 | bundler: 'vite'
13 | }
14 | }
15 | })
16 |
--------------------------------------------------------------------------------
/src/services/random-string-generator.ts:
--------------------------------------------------------------------------------
1 | export function getRandomString(prefix = '', length = 8): string {
2 | let result = `${Date.now()}-`
3 |
4 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
5 | const charactersLength = characters.length
6 | for (let i = 0; i < length; i++) {
7 | result += characters.charAt(Math.floor(Math.random() * charactersLength))
8 | }
9 |
10 | return `${prefix}${result}`
11 | }
--------------------------------------------------------------------------------
/ui-shell/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import { createPinia } from 'pinia'
3 | import PrimeVue from 'primevue/config'
4 | import Lara from './assets/tailwind-primevue-preset'
5 | import App from './App.vue'
6 | import router from './router'
7 |
8 | const app = createApp(App)
9 |
10 | app.use(createPinia())
11 | app.use(router)
12 |
13 | app.use(PrimeVue, {
14 | unstyled: true,
15 | pt: Lara,
16 | })
17 |
18 | app.mount('#app')
19 |
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | # Ignore everything by default
2 | *
3 | **
4 |
5 | # Include only necessary files
6 | !package.json
7 | !LICENSE*
8 |
9 | # Include source and compiled files
10 | !*.js
11 | !dist/**/*.js
12 | !ui-shell/dist/**
13 |
14 | # Include icons
15 | !resources/devdb-128x128.png
16 | !resources/devdb.png
17 |
18 | # Include snippets and schemas
19 | !snippets/*
20 | !schemas/*
21 |
22 | # Include @vscode/sqlite3 because we excluded it from build in esbuild.js
23 | !node_modules/@vscode/sqlite3/**
--------------------------------------------------------------------------------
/src/services/logging-service.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { logToOutput } from './output-service';
3 |
4 | export function log(description: string, message: string, ...rest: any[]) {
5 | const config = vscode.workspace.getConfiguration('Devdb');
6 | const showDebugInfo = config.get('showDebugInfo', false);
7 |
8 | if (showDebugInfo) {
9 | console.log(message, ...rest);
10 | }
11 |
12 | logToOutput(`${message} ${rest.map(String).join(' ').trim()}`, description)
13 | }
14 |
--------------------------------------------------------------------------------
/src/services/document-service.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | /**
4 | * Gets the word under the cursor in the
5 | * current document
6 | */
7 | export function getWordUnderCursor(): string | undefined {
8 | const editor = vscode.window.activeTextEditor;
9 | if (!editor) return;
10 |
11 | const document = editor.document;
12 | const cursorPosition = editor.selection.active;
13 | const wordRange = document.getWordRangeAtPosition(cursorPosition);
14 |
15 | return document.getText(wordRange)
16 | }
--------------------------------------------------------------------------------
/src/services/mcp/no-vscode/README.md:
--------------------------------------------------------------------------------
1 | Modules in this folder should be self-contained and not access any VS Code APIs
2 |
3 | This is because they contain dependencies that are consumed directly by the external MCP server. The MCP server runs in its own process space, not having access to VS Code APIs which are created at runtime.
4 |
5 | Also, since we are using STDIO transport, no writing to stdio from our script, e.g. using console.log, etc. See https://modelcontextprotocol.io/docs/develop/build-server#logging-in-mcp-servers
6 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/skeleton/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props }) => ({
3 | class: [
4 | 'overflow-hidden',
5 | {
6 | 'animate-pulse': props.animation !== 'none'
7 | },
8 |
9 | // Round
10 | { 'rounded-full': props.shape === 'circle', 'rounded-md': props.shape !== 'circle' },
11 |
12 | // Colors
13 | 'bg-surface-200 dark:bg-surface-700'
14 | ]
15 | })
16 | };
17 |
--------------------------------------------------------------------------------
/src/services/mcp/no-vscode/logger.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as winston from 'winston';
3 | import { MCP_CONFIG_DIR } from './config';
4 |
5 | const logger = winston.createLogger({
6 | level: 'info',
7 | format: winston.format.combine(
8 | winston.format.timestamp(),
9 | winston.format.errors({ stack: true }),
10 | winston.format.json()
11 | ),
12 | defaultMeta: { service: 'devdb-mcp-server' },
13 | transports: [
14 | new winston.transports.File({ filename: path.join(MCP_CONFIG_DIR, 'mcp-log.txt') }),
15 | ],
16 | });
17 |
18 |
19 | export default logger
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "parserOptions": {
5 | "ecmaVersion": 6,
6 | "sourceType": "module"
7 | },
8 | "plugins": [
9 | "@typescript-eslint"
10 | ],
11 | "rules": {
12 | "@typescript-eslint/naming-convention": "off",
13 | "@typescript-eslint/semi": "off",
14 | "curly": "off",
15 | "no-throw-literal": "warn",
16 | "semi": "off"
17 | },
18 | "ignorePatterns": [
19 | "out",
20 | "dist",
21 | "**/*.d.ts"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/src/services/error-notification-service.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | export function showMissingDatabaseNotification() {
4 | vscode.window.showErrorMessage('No database connection found. Please select a database in DevDb and try again.', 'Connect').then(selection => {
5 | if (selection === 'Connect') {
6 | vscode.commands.executeCommand('devdb.focus');
7 | }
8 | });
9 | }
10 |
11 | export function showEmptyTablesNotification() {
12 | vscode.window.showErrorMessage('Tables not loaded yet. Please ensure to connect to a database in DevDb and try again.', 'Connect').then(selection => {
13 | if (selection === 'Connect') {
14 | vscode.commands.executeCommand('devdb.focus');
15 | }
16 | });
17 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": [
3 | "**/ui-shell/**"
4 | ],
5 | "compilerOptions": {
6 | "module": "Node16",
7 | "target": "ES2022",
8 | "outDir": "out",
9 | "lib": [
10 | "ES2022"
11 | ],
12 | "allowImportingTsExtensions": false,
13 | "sourceMap": true,
14 | "rootDir": "src",
15 | "strict": true /* enable all strict type-checking options */,
16 | "esModuleInterop": true,
17 | /* Additional Checks */
18 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
19 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
20 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
21 | }
22 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "editor.formatOnSave": true,
4 | "files.exclude": {
5 | "out": false // set this to true to hide the "out" folder with the compiled JS files
6 | },
7 | "search.exclude": {
8 | "out": true // set this to false to include "out" folder in search results
9 | },
10 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
11 | "typescript.tsc.autoDetect": "off",
12 | "editor.defaultFormatter": "esbenp.prettier-vscode",
13 | "cSpell.words": [
14 | "devdb",
15 | "devdbrc",
16 | "notnull",
17 | "snakecase",
18 | "testcontainers"
19 | ]
20 | }
--------------------------------------------------------------------------------
/src/test/runTest.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 |
3 | import { runTests } from '@vscode/test-electron';
4 |
5 | async function main() {
6 | try {
7 | // The folder containing the Extension Manifest package.json
8 | // Passed to `--extensionDevelopmentPath`
9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../');
10 |
11 | // The path to test runner
12 | // Passed to --extensionTestsPath
13 | const extensionTestsPath = path.resolve(__dirname, './suite/index');
14 |
15 | // Download VS Code, unzip it and run the integration test
16 | await runTests({ extensionDevelopmentPath, extensionTestsPath });
17 | } catch (err) {
18 | console.error('Failed to run tests', err);
19 | process.exit(1);
20 | }
21 | }
22 |
23 | main();
24 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/base.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --primary-50: 236 253 245;
3 | --primary-100: 209 250 229;
4 | --primary-200: 167 243 208;
5 | --primary-300: 110 231 183;
6 | --primary-400: 52 211 153;
7 | --primary-500: 16 185 129;
8 | --primary-600: 5 150 105;
9 | --primary-700: 4 120 87;
10 | --primary-800: 6 95 70;
11 | --primary-900: 4 78 56;
12 | --primary-950: 2 44 34;
13 | --surface-0: 255 255 255;
14 | --surface-50: 248 250 252;
15 | --surface-100: 241 245 249;
16 | --surface-200: 226 232 240;
17 | --surface-300: 203 213 225;
18 | --surface-400: 148 163 184;
19 | --surface-500: 100 116 139;
20 | --surface-600: 71 85 105;
21 | --surface-700: 45 55 72;
22 | --surface-800: 30 41 59;
23 | --surface-900: 15 23 42;
24 | --surface-950: 3 6 23;
25 | }
--------------------------------------------------------------------------------
/ui-shell/cypress/support/e2e.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/inputgroupaddon/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: {
3 | class: [
4 | // Flex
5 | 'flex items-center justify-center',
6 |
7 | // Shape
8 | 'first:rounded-l-md',
9 | 'last:rounded-r-md',
10 | 'border-y',
11 |
12 | 'last:border-r',
13 | 'border-l',
14 | 'border-r-0',
15 |
16 | // Space
17 | 'p-1.5',
18 |
19 | // Size
20 | 'min-w-[3rem]',
21 |
22 | // Color
23 | 'bg-surface-50 dark:bg-surface-800',
24 | 'text-surface-600 dark:text-surface-400',
25 | 'border-surface-300 dark:border-surface-600'
26 | ]
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/toolbar/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: {
3 | class: [
4 | // Flex & Alignment
5 | 'flex items-center justify-between flex-wrap',
6 | 'gap-6',
7 |
8 | // Spacing
9 | 'px-6 py-2',
10 | 'min-h-[4rem]',
11 |
12 | // Shape
13 | 'rounded-md',
14 | 'shadow-md',
15 |
16 | // Color
17 | 'bg-surface-0 dark:bg-surface-900',
18 | 'ring-1 ring-surface-100 dark:ring-surface-700'
19 | ]
20 | },
21 | start: {
22 | class: 'flex items-center'
23 | },
24 | center: {
25 | class: 'flex items-center'
26 | },
27 | end: {
28 | class: 'flex items-center'
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/.github/workflows/inactive-issues.yml:
--------------------------------------------------------------------------------
1 | name: Close inactive issues
2 | on:
3 | schedule:
4 | - cron: "1 1 1,14 * *"
5 |
6 | jobs:
7 | close-issues:
8 | runs-on: ubuntu-latest
9 | permissions:
10 | issues: write
11 | steps:
12 | - uses: actions/stale@v5
13 | with:
14 | any-of-labels: needs more info,awaiting response
15 | days-before-issue-stale: 30
16 | days-before-issue-close: 14
17 | stale-issue-label: "stale"
18 | stale-issue-message: "This issue is stale because it has been open for 30 days with no activity and the 'awaiting response' tag persists. Please note that it will be auto-closed soon if there is still no response. Thank you."
19 | close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
20 | repo-token: ${{ secrets.GITHUB_TOKEN }}
21 |
--------------------------------------------------------------------------------
/src/services/codelens/code-lens-service.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { LaravelCodelensService } from './laravel/laravel-codelens-service';
3 |
4 | export class LaravelCodelensProvider implements vscode.CodeLensProvider {
5 |
6 | private codeLenses: vscode.CodeLens[] = [];
7 | private _onDidChangeCodeLenses: vscode.EventEmitter = new vscode.EventEmitter();
8 | public readonly onDidChangeCodeLenses: vscode.Event = this._onDidChangeCodeLenses.event;
9 |
10 | constructor() { }
11 |
12 | public async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise {
13 | this.codeLenses = [];
14 |
15 | const laravelCodeLenses = await LaravelCodelensService.getCodelensFor(document)
16 |
17 | if (laravelCodeLenses) {
18 | this.codeLenses = laravelCodeLenses
19 | }
20 |
21 | return this.codeLenses;
22 | }
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "watch",
6 | "dependsOn": [
7 | "npm: watch:tsc",
8 | "npm: watch:esbuild"
9 | ],
10 | "presentation": {
11 | "reveal": "never"
12 | },
13 | "group": {
14 | "kind": "build",
15 | "isDefault": true
16 | }
17 | },
18 | {
19 | "type": "npm",
20 | "script": "watch:esbuild",
21 | "group": "build",
22 | "problemMatcher": "$esbuild-watch",
23 | "isBackground": true,
24 | "label": "npm: watch:esbuild",
25 | "presentation": {
26 | "group": "watch",
27 | "reveal": "never"
28 | }
29 | },
30 | {
31 | "type": "npm",
32 | "script": "watch:tsc",
33 | "group": "build",
34 | "problemMatcher": "$tsc-watch",
35 | "isBackground": true,
36 | "label": "npm: watch:tsc",
37 | "presentation": {
38 | "group": "watch",
39 | "reveal": "never"
40 | }
41 | }
42 | ]
43 | }
--------------------------------------------------------------------------------
/ui-shell/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add('login', (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This will overwrite an existing command --
25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/src/services/output-service.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 |
3 | let outputChannel: vscode.OutputChannel | undefined;
4 |
5 | /**
6 | * Logs a message to the DevDb output channel
7 | */
8 | export function logToOutput(message: string, description = ''): void {
9 | const channel = getOutputChannel();
10 | const formattedMessage = `${description ? `[${description}]` : ''} ${message}`;
11 | channel.appendLine(formattedMessage);
12 | }
13 |
14 | export function clearOutput(): void {
15 | const channel = getOutputChannel();
16 | channel.clear();
17 | }
18 |
19 | /**
20 | * Shows the DevDb output channel in the VS Code UI
21 | */
22 | export function showOutput(): void {
23 | const channel = getOutputChannel();
24 | channel.show();
25 | }
26 |
27 | function getOutputChannel(): vscode.OutputChannel {
28 | if (!outputChannel) {
29 | outputChannel = vscode.window.createOutputChannel("DevDb");
30 | }
31 | return outputChannel;
32 | }
33 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Run Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "args": [
13 | "--extensionDevelopmentPath=${workspaceFolder}",
14 | "--disable-extensions"
15 | ],
16 | "preLaunchTask": "${defaultBuildTask}"
17 | },
18 | {
19 | "name": "Extension Tests",
20 | "type": "extensionHost",
21 | "request": "launch",
22 | "args": [
23 | "--extensionDevelopmentPath=${workspaceFolder}",
24 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index",
25 | "--disable-extensions"
26 | ],
27 | "preLaunchTask": "${defaultBuildTask}"
28 | }
29 | ]
30 | }
--------------------------------------------------------------------------------
/ui-shell/cypress/support/component.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/component.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
22 | // Import global styles
23 | import '@/assets/main.css'
24 |
25 | import { mount } from 'cypress/vue'
26 |
27 | Cypress.Commands.add('mount', mount)
28 |
29 | // Example use:
30 | // cy.mount(MyComponent)
31 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/inputmask/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ context }) => ({
3 | class: [
4 | // Font
5 | 'font-sans leading-6',
6 |
7 | // Spacing
8 | 'm-0 py-1.5 px-3 sm:text-sm',
9 |
10 | // Colors
11 | 'text-surface-900 dark:text-surface-0',
12 | 'placeholder:text-surface-400 dark:placeholder:text-surface-500',
13 | 'bg-surface-0 dark:bg-surface-900',
14 | 'ring-1 ring-inset ring-surface-300 dark:ring-surface-700 ring-offset-0',
15 | 'shadow-sm',
16 |
17 | // Shape
18 | 'rounded-md',
19 | 'appearance-none',
20 |
21 | // Interactions
22 | {
23 | 'outline-none focus:ring-primary-500 dark:focus:ring-primary-400': !context.disabled,
24 | 'opacity-60 select-none pointer-events-none cursor-default': context.disabled
25 | }
26 | ]
27 | })
28 | };
29 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/card/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: {
3 | class: [
4 | //Shape
5 | 'rounded-lg',
6 | 'shadow-md',
7 |
8 | //Color
9 | 'bg-surface-0 dark:bg-surface-900',
10 | 'text-surface-700 dark:text-surface-0/80'
11 | ]
12 | },
13 | header: {
14 | class: ['border-b border-surface-200 dark:border-surface-700']
15 | },
16 | body: {
17 | class: 'py-5'
18 | },
19 | title: {
20 | class: 'text-lg font-medium mb-2 px-5 md:px-6'
21 | },
22 | subtitle: {
23 | class: [
24 | //Spacing
25 | 'mb-1 px-5 md:px-6',
26 |
27 | //Color
28 | 'text-surface-600 dark:text-surface-0/60'
29 | ]
30 | },
31 | content: {
32 | class: 'py-6 px-5 md:px-6'
33 | },
34 | footer: {
35 | class: ['px-5 md:px-6 pt-5 pb-0', 'border-t border-surface-200 dark:border-surface-700']
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/dataview/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | content: {
3 | class: [
4 | // Spacing
5 | 'p-0',
6 |
7 | // Shape
8 | 'border-0',
9 |
10 | // Color
11 | 'text-surface-700 dark:text-white/80',
12 | 'bg-surface-0 dark:bg-surface-800'
13 | ]
14 | },
15 | grid: {
16 | class: [
17 | // flex
18 | 'flex flex-wrap',
19 |
20 | // Spacing
21 | 'ml-0 mr-0 mt-0',
22 |
23 | // Color
24 | 'bg-surface-0 dark:bg-surface-800'
25 | ]
26 | },
27 | header: {
28 | class: [
29 | 'font-semibold',
30 |
31 | // Spacing
32 | 'p-6',
33 |
34 | // Color
35 | 'text-surface-800 dark:text-white/80',
36 | 'bg-surface-0 dark:bg-surface-800',
37 | 'border-surface-200 dark:border-surface-700 border-b'
38 | ]
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/src/test/suite/index.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import Mocha from 'mocha';
3 | import * as glob from 'glob';
4 |
5 | export function run(): Promise {
6 | // Create the mocha test
7 | const mocha = new Mocha({
8 | ui: 'tdd',
9 | color: true
10 | });
11 |
12 | const testsRoot = path.resolve(__dirname, '..');
13 |
14 | return new Promise((c, e) => {
15 | const testFiles = new glob.Glob("**/**.test.js", { cwd: testsRoot });
16 | const testFileStream = testFiles.stream();
17 |
18 | testFileStream.on("data", (file) => {
19 | mocha.addFile(path.resolve(testsRoot, file));
20 | });
21 | testFileStream.on("error", (err) => {
22 | e(err);
23 | });
24 | testFileStream.on("end", () => {
25 | try {
26 | // Run the mocha test
27 | mocha.run(failures => {
28 | if (failures > 0) {
29 | e(new Error(`${failures} tests failed.`));
30 | } else {
31 | c();
32 | }
33 | });
34 | } catch (err) {
35 | console.error(err);
36 | e(err);
37 | }
38 | });
39 | });
40 | }
41 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/inlinemessage/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props }) => ({
3 | class: [
4 | 'inline-flex items-center justify-center align-top gap-2',
5 | 'py-2 px-3 m-0 rounded-md',
6 | 'ring-1 ring-inset ring-surface-200 dark:ring-surface-700 ring-offset-0',
7 | {
8 | 'text-blue-500 dark:text-blue-300': props.severity == 'info',
9 | 'text-green-500 dark:text-green-300': props.severity == 'success',
10 | 'text-orange-500 dark:text-orange-300': props.severity == 'warn',
11 | 'text-red-500 dark:text-red-300': props.severity == 'error'
12 | }
13 | ]
14 | }),
15 | icon: {
16 | class: [
17 | // Sizing and Spacing
18 | 'w-4 h-4',
19 | 'shrink-0'
20 | ]
21 | },
22 | text: {
23 | class: [
24 | // Font and Text
25 | 'text-sm leading-none',
26 | 'font-medium'
27 | ]
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/chip/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: {
3 | class: [
4 | // Flexbox
5 | 'inline-flex items-center',
6 |
7 | // Spacing
8 | 'px-2 py-0.5',
9 |
10 | // Shape
11 | 'rounded-[1.14rem]',
12 |
13 | // Colors
14 | 'text-surface-700 dark:text-surface-0/70',
15 | 'bg-surface-200 dark:bg-surface-700'
16 | ]
17 | },
18 | label: {
19 | class: 'text-xs leading-6 mx-0'
20 | },
21 | icon: {
22 | class: 'leading-6 mr-2'
23 | },
24 | image: {
25 | class: ['w-6 h-6 mr-2', 'rounded-full']
26 | },
27 | removeIcon: {
28 | class: [
29 | // Shape
30 | 'rounded-md leading-6',
31 |
32 | // Spacing
33 | 'ml-2',
34 |
35 | // Size
36 | 'w-4 h-4',
37 |
38 | // Transition
39 | 'transition duration-200 ease-in-out',
40 |
41 | // Misc
42 | 'cursor-pointer'
43 | ]
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/tooltip/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ context, props }) => ({
3 | class: [
4 | // Position
5 | 'absolute',
6 | // Spacing
7 | {
8 | 'px-1.5': context?.right || context?.left || (!context?.right && !context?.left && !context?.top && !context?.bottom),
9 | 'py-1.5': context?.top || context?.bottom
10 | }
11 | ]
12 | }),
13 | arrow: {
14 | class: 'hidden'
15 | },
16 | text: {
17 | class: [
18 | // Size
19 | 'text-xs leading-none',
20 |
21 | // Spacing
22 | 'p-2',
23 |
24 | // Shape
25 | 'rounded-md',
26 |
27 | // Color
28 | 'text-surface-900 dark:text-surface-0/80',
29 | 'bg-surface-0 dark:bg-surface-900',
30 | 'ring-1 ring-inset ring-surface-200 dark:ring-surface-800 ring-offset-0',
31 |
32 | // Misc
33 | 'whitespace-pre-line',
34 | 'break-words'
35 | ]
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/knob/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props }) => ({
3 | class: [
4 | // Misc
5 | { 'opacity-60 select-none pointer-events-none cursor-default': props.disabled }
6 | ]
7 | }),
8 | range: {
9 | class: [
10 | // Stroke
11 | 'stroke-current',
12 |
13 | // Color
14 | 'stroke-surface-200 dark:stroke-surface-700',
15 |
16 | // Fill
17 | 'fill-none',
18 |
19 | // Transition
20 | 'transition duration-100 ease-in'
21 | ]
22 | },
23 | value: {
24 | class: [
25 | // Animation
26 | 'animate-dash-frame',
27 |
28 | // Color
29 | 'stroke-primary-500 dark:stroke-primary-400',
30 |
31 | // Fill
32 | 'fill-none'
33 | ]
34 | },
35 | label: {
36 | class: [
37 | // Text Style
38 | 'text-center text-xl',
39 |
40 | // Color
41 | 'fill-surface-600 dark:fill-surface-200'
42 | ]
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/ui-shell/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ui",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "watch": "chokidar -s '../src/**/*.ts' './src/**/*.{vue,js,ts,css}' './node_modules/devdb-ui/dist/**/*.*' -c './build.sh'",
9 | "preview": "vite preview",
10 | "test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'",
11 | "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'",
12 | "test:unit": "cypress run --component",
13 | "test:unit:dev": "cypress open --component"
14 | },
15 | "dependencies": {
16 | "devdb-ui": "file:../../devdb-ui",
17 | "pinia": "^2.3.1",
18 | "primevue": "^3.53.1",
19 | "vue": "^3.5.22",
20 | "vue-router": "^4.6.3"
21 | },
22 | "devDependencies": {
23 | "@vitejs/plugin-vue": "^4.6.2",
24 | "autoprefixer": "^10.4.21",
25 | "chokidar-cli": "^3.0.0",
26 | "cypress": "^13.17.0",
27 | "postcss": "^8.5.6",
28 | "start-server-and-test": "^2.1.2",
29 | "tailwindcss": "^3.4.18",
30 | "vite": "^7.1.10"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Damilola Emmanuel Olowookere
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/textarea/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ context }) => ({
3 | class: [
4 | // Font
5 | 'font-sans leading-6',
6 | 'sm:text-sm',
7 |
8 | // Spacing
9 | 'm-0',
10 | 'py-1.5 px-3',
11 |
12 | // Shape
13 | 'rounded-md',
14 | 'appearance-none',
15 |
16 | // Colors
17 | 'text-surface-900 dark:text-surface-0',
18 | 'placeholder:text-surface-400 dark:placeholder:text-surface-500',
19 | 'bg-surface-0 dark:bg-surface-900',
20 | 'ring-1 ring-inset ring-surface-300 dark:ring-surface-700 ring-offset-0',
21 | 'shadow-sm',
22 |
23 | // States
24 | {
25 | 'outline-none focus:ring-primary-500 dark:focus:ring-primary-400': !context.disabled,
26 | 'opacity-60 select-none pointer-events-none cursor-default': context.disabled
27 | },
28 |
29 | // Misc
30 | 'appearance-none',
31 | 'transition-colors duration-200'
32 | ]
33 | })
34 | };
35 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/tag/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props }) => ({
3 | class: [
4 | //Font
5 | 'text-xs font-bold',
6 |
7 | //Alignments
8 | 'inline-flex items-center justify-center',
9 |
10 | //Spacing
11 | 'px-2 py-1',
12 |
13 | //Shape
14 | {
15 | 'rounded-md': !props.rounded,
16 | 'rounded-full': props.rounded
17 | },
18 |
19 | //Colors
20 | 'text-white dark:text-surface-900',
21 | {
22 | 'bg-primary-500 dark:bg-primary-400': props.severity == null || props.severity == 'primary',
23 | 'bg-green-500 dark:bg-green-400': props.severity == 'success',
24 | 'bg-blue-500 dark:bg-blue-400': props.severity == 'info',
25 | 'bg-orange-500 dark:bg-orange-400': props.severity == 'warning',
26 | 'bg-red-500 dark:bg-red-400': props.severity == 'danger'
27 | }
28 | ]
29 | }),
30 | value: {
31 | class: 'leading-normal'
32 | },
33 | icon: {
34 | class: 'mr-1 text-sm'
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/src/services/laravel/code-runner/qualifier-service.ts:
--------------------------------------------------------------------------------
1 | import { Engine, Program } from 'php-parser';
2 |
3 | export function getAst(code: string): Program {
4 | const parser = new Engine({
5 | parser: {
6 | php7: true
7 | },
8 | ast: {
9 | withPositions: true
10 | }
11 | });
12 |
13 | return parser.parseCode(code, 'file.php');
14 | }
15 |
16 | export function extractUseStatements(ast: Program): string {
17 | let useStatements = '';
18 |
19 | if (ast.children) {
20 | ast.children.forEach((node: any) => {
21 | if (node.kind === 'namespace' && node.name) {
22 | if (node.children) {
23 | node.children.forEach((childNode: any) => {
24 | if (childNode.kind === 'usegroup') {
25 | childNode.items.forEach((useItem: any) => {
26 | const alias = useItem.alias ? ` as ${getAlias(useItem.alias)}` : '';
27 | useStatements += `use ${useItem.name}${alias};\n`;
28 | });
29 | }
30 | });
31 | }
32 | }
33 | });
34 | }
35 |
36 | return useStatements;
37 | }
38 |
39 | function getAlias(alias: any) {
40 | return typeof alias === 'object'
41 | ? `${alias.name}`
42 | : alias;
43 | }
44 |
45 | export function isNamespaced(ast: Program): boolean {
46 | return ast.children.some((node: any) => node.kind === 'namespace');
47 | }
--------------------------------------------------------------------------------
/scripts/triage/sqlite-triage.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Compute the script directory and set the database file path
4 | DB_PATH="$(dirname "$(readlink -f "$0")")/devdb-triage.sqlite"
5 |
6 | # Create a SQLite database file
7 | sqlite3 "$DB_PATH" << EOF
8 | CREATE TABLE book (
9 | id INTEGER PRIMARY KEY AUTOINCREMENT,
10 | title TEXT NOT NULL,
11 | author TEXT NOT NULL,
12 | published_date DATE,
13 | isbn TEXT,
14 | pages INTEGER,
15 | available BOOLEAN DEFAULT 1
16 | );
17 | INSERT INTO book (title, author, published_date, isbn, pages, available) VALUES
18 | ('The Great Gatsby', 'F. Scott Fitzgerald', '1925-04-10', '9780743273565', 180, 1),
19 | ('1984', 'George Orwell', '1949-06-08', '9780451524935', 328, 0),
20 | ('To Kill a Mockingbird', 'Harper Lee', '1960-07-11', '9780061120084', 281, 1),
21 | ('Pride and Prejudice', 'Jane Austen', '1813-01-28', '9781503290563', 279, 1),
22 | ('The Catcher in the Rye', 'J.D. Salinger', '1951-07-16', '9780316769488', 214, 0);
23 |
24 | EOF
25 |
26 | echo "SQLite triage setup complete."
27 |
28 | echo "Example connection details:"
29 |
30 | cat << EXAMPLE_CONNECTION
31 | {
32 | "database": "$DB_PATH"
33 | }
34 | EXAMPLE_CONNECTION
35 |
36 | echo "When done, remove the SQLite database file by runing:"
37 | echo "rm \"$DB_PATH\""
38 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/breadcrumb/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | menu: {
3 | class: [
4 | // Flex & Alignment
5 | 'flex items-center flex-nowrap gap-x-1.5',
6 |
7 | // Spacing
8 | 'm-0 p-0 list-none leading-none'
9 | ]
10 | },
11 | action: {
12 | class: [
13 | // Font
14 | 'font-semibold text-decoration-none text-sm',
15 |
16 | // Flex & Alignment
17 | 'flex items-center gap-x-1.5 ',
18 |
19 | // Shape
20 | 'rounded-md',
21 |
22 | // Color
23 | 'text-surface-500 dark:text-white/70',
24 |
25 | // States
26 | 'focus-visible:outline-none focus-visible:outline-offset-0',
27 | 'focus-visible:ring-2 focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400',
28 |
29 | // Transitions
30 | 'transition-shadow duration-200'
31 | ]
32 | },
33 | icon: {
34 | class: 'text-surface-500 dark:text-white/70'
35 | },
36 | separator: {
37 | class: [
38 | // Flex & Alignment
39 | 'flex items-center shrink-0',
40 |
41 | // Color
42 | 'text-surface-500 dark:text-white/70'
43 | ]
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/overlaypanel/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: {
3 | class: [
4 | // Shape
5 | 'rounded-lg',
6 | 'shadow-xl',
7 | 'border-0 dark:border',
8 |
9 | // Position
10 | 'absolute left-0 top-0 mt-2',
11 | 'z-40 transform origin-center',
12 |
13 | // Color
14 | 'bg-surface-0 dark:bg-surface-800',
15 | 'text-surface-700 dark:text-surface-0/80',
16 | 'dark:border-surface-700',
17 |
18 | // Before: Triangle
19 | 'before:absolute before:-top-2 before:ml-4 before:z-50',
20 | 'before:w-0 before:h-0 before:shadow-xl',
21 | 'before:border-transparent before:border-solid',
22 | 'before:border-x-[0.5rem] before:border-b-[0.5rem]',
23 | 'before:border-t-0 before:border-b-surface-0 dark:before:border-b-surface-800'
24 | ]
25 | },
26 | content: {
27 | class: 'p-6 items-center flex'
28 | },
29 | transition: {
30 | enterFromClass: 'opacity-0 scale-y-[0.8]',
31 | enterActiveClass: 'transition-[transform,opacity] duration-[120ms] ease-[cubic-bezier(0,0,0.2,1)]',
32 | leaveActiveClass: 'transition-opacity duration-100 ease-linear',
33 | leaveToClass: 'opacity-0'
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/terminal/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: {
3 | class: [
4 | // Spacing
5 | 'p-5',
6 |
7 | // Shape
8 | 'rounded-md',
9 |
10 | // Color
11 | 'bg-surface-900 text-white',
12 | 'border border-surface-700',
13 |
14 | // Sizing & Overflow
15 | 'h-72 overflow-auto'
16 | ]
17 | },
18 | container: {
19 | class: [
20 | // Flexbox
21 | 'flex items-center'
22 | ]
23 | },
24 | prompt: {
25 | class: [
26 | // Color
27 | 'text-surface-400'
28 | ]
29 | },
30 | response: {
31 | class: [
32 | // Color
33 | 'text-primary-400'
34 | ]
35 | },
36 | command: {
37 | class: [
38 | // Color
39 | 'text-primary-400'
40 | ]
41 | },
42 | commandtext: {
43 | class: [
44 | // Flexbox
45 | 'flex-1 shrink grow-0',
46 |
47 | // Shape
48 | 'border-0',
49 |
50 | // Spacing
51 | 'p-0',
52 |
53 | // Color
54 | 'bg-transparent text-inherit',
55 |
56 | // Outline
57 | 'outline-none'
58 | ]
59 | }
60 | };
61 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | /dist
3 | node_modules
4 | .vscode-test/
5 | *.vsix
6 | .aider*
7 | AIDER-CONVENTIONS.md
8 |
9 | # Logs
10 | /ui-shell/logs
11 | /ui-shell/*.log
12 | /ui-shell/npm-debug.log*
13 | /ui-shell/yarn-debug.log*
14 | /ui-shell/yarn-error.log*
15 | /ui-shell/pnpm-debug.log*
16 | /ui-shell/lerna-debug.log*
17 |
18 | /ui-shell/node_modules
19 | /ui-shell/.DS_Store
20 | /ui-shell/dist/**.*
21 |
22 | # We use the assets folder so we don't have to build
23 | # because we are yet to publish the UI package on NPM
24 | # https://github.com/damms005/devdb-vscode/actions/runs/8435299388/job/23100385334#step:6:25
25 | # https://github.com/damms005/devdb-vscode/blob/d7220957422d1929aec3e51db18e60a54a2e37c2/src/services/html.ts#L57
26 | !/ui-shell/dist/assets/**.*
27 |
28 | /ui-shell/dist-ssr
29 | /ui-shell/coverage
30 | /ui-shell/*.local
31 |
32 | /ui-shell/cypress/videos/
33 | /ui-shell/cypress/screenshots/
34 |
35 | # Editor directories and files
36 | /ui-shell/.vscode/*
37 | /ui-shell/!.vscode/extensions.json
38 | /ui-shell/.idea
39 | /ui-shell/*.suo
40 | /ui-shell/*.ntvs*
41 | /ui-shell/*.njsproj
42 | /ui-shell/*.sln
43 | /ui-shell/*.sw?
44 |
45 | .DS_Store
46 | .env
47 |
48 | # Dynamic Dockerfiles generated by boot scripts
49 | apps/*/Dockerfile
50 |
51 | # Todo app directory contents (except .gitkeep)
52 | apps/rails/todo-app/*
53 | !apps/rails/todo-app/.gitkeep
54 |
55 | ltex*
56 |
--------------------------------------------------------------------------------
/src/services/laravel/sail.ts:
--------------------------------------------------------------------------------
1 | import { join } from "path";
2 | import { parse } from 'yaml'
3 | import { fileExists, getBasePath, getWorkspaceFileContent } from "../workspace";
4 | import { getEnvFileValue } from "./laravel-core";
5 | import { KnexClientType } from "../../types";
6 |
7 | export async function hasLaravelSailDockerComposeFile() {
8 | const workspacePath = getBasePath()
9 | if (!workspacePath) return false
10 |
11 | const dockerComposeFilePath = join(workspacePath, 'docker-compose.yml');
12 |
13 | const exists = await fileExists(dockerComposeFilePath)
14 |
15 | return exists
16 | }
17 |
18 | export async function getPortFromDockerCompose(dialect: KnexClientType): Promise {
19 | const dockerComposeContent = (getWorkspaceFileContent('docker-compose.yml'))?.toString()
20 | if (!dockerComposeContent) return
21 |
22 | const dockerComposeParsed = parse(dockerComposeContent)
23 |
24 | const portDefinition: string = dockerComposeParsed.services?.[dialect]?.ports[0].toString()
25 | if (!portDefinition) return
26 |
27 | // Match string like '${FORWARD_DB_PORT:-3307}:3306' where FORWARD_DB_PORT and 3307 are captured
28 | const match = portDefinition.match(/\${(\w+):-(\d+)}:\d+/)
29 | if (!match) return
30 |
31 | const [, envVariable, defaultPort,] = match
32 | const port: string = await getEnvFileValue(envVariable) || defaultPort
33 |
34 | return parseInt(port)
35 | }
--------------------------------------------------------------------------------
/ui-shell/README.md:
--------------------------------------------------------------------------------
1 | # ui
2 |
3 | This template should help get you started developing with Vue 3 in Vite.
4 |
5 | ## Recommended IDE Setup
6 |
7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
8 |
9 | ## Customize configuration
10 |
11 | See [Vite Configuration Reference](https://vitejs.dev/config/).
12 |
13 | ## Project Setup
14 |
15 | ```sh
16 | npm install
17 | ```
18 |
19 | ### Compile and Hot-Reload for Development
20 |
21 | ```sh
22 | npm run dev
23 | ```
24 |
25 | ### Compile and Minify for Production
26 |
27 | ```sh
28 | npm run build
29 | ```
30 |
31 | ### Run Headed Component Tests with [Cypress Component Testing](https://on.cypress.io/component)
32 |
33 | ```sh
34 | npm run test:unit:dev # or `npm run test:unit` for headless testing
35 | ```
36 |
37 | ### Run End-to-End Tests with [Cypress](https://www.cypress.io/)
38 |
39 | ```sh
40 | npm run test:e2e:dev
41 | ```
42 |
43 | This runs the end-to-end tests against the Vite development server.
44 | It is much faster than the production build.
45 |
46 | But it's still recommended to test the production build with `test:e2e` before deploying (e.g. in CI environments):
47 |
48 | ```sh
49 | npm run build
50 | npm run test:e2e
51 | ```
52 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/avatar/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props, parent }) => ({
3 | class: [
4 | // Font
5 | {
6 | 'text-sm': props.size == null || props.size == 'normal',
7 | 'text-lg': props.size == 'large',
8 | 'text-xl': props.size == 'xlarge'
9 | },
10 |
11 | // Alignments
12 | 'inline-flex items-center justify-center',
13 | 'shrink-0',
14 | 'relative',
15 |
16 | // Sizes
17 | {
18 | 'h-8 w-8': props.size == null || props.size == 'normal',
19 | 'w-12 h-12': props.size == 'large',
20 | 'w-16 h-16': props.size == 'xlarge'
21 | },
22 | { '-ml-4': parent.instance.$style?.name == 'avatargroup' },
23 |
24 | // Shapes
25 | {
26 | 'rounded-lg': props.shape == 'square',
27 | 'rounded-full': props.shape == 'circle'
28 | },
29 | { 'border-2': parent.instance.$style?.name == 'avatargroup' },
30 |
31 | // Colors
32 | 'bg-surface-100 dark:bg-surface-700',
33 | { 'border-white dark:border-surface-800': parent.instance.$style?.name == 'avatargroup' }
34 | ]
35 | }),
36 | image: {
37 | class: 'h-full w-full'
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/ui-shell/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}', './src/services/html.ts'],
4 | theme: {
5 | extend: {
6 | colors: {
7 | 'primary-50': 'rgb(var(--primary-50))',
8 | 'primary-100': 'rgb(var(--primary-100))',
9 | 'primary-200': 'rgb(var(--primary-200))',
10 | 'primary-300': 'rgb(var(--primary-300))',
11 | 'primary-400': 'rgb(var(--primary-400))',
12 | 'primary-500': 'rgb(var(--primary-500))',
13 | 'primary-600': 'rgb(var(--primary-600))',
14 | 'primary-700': 'rgb(var(--primary-700))',
15 | 'primary-800': 'rgb(var(--primary-800))',
16 | 'primary-900': 'rgb(var(--primary-900))',
17 | 'primary-950': 'rgb(var(--primary-950))',
18 | 'surface-0': 'rgb(var(--surface-0))',
19 | 'surface-50': 'rgb(var(--surface-50))',
20 | 'surface-100': 'rgb(var(--surface-100))',
21 | 'surface-200': 'rgb(var(--surface-200))',
22 | 'surface-300': 'rgb(var(--surface-300))',
23 | 'surface-400': 'rgb(var(--surface-400))',
24 | 'surface-500': 'rgb(var(--surface-500))',
25 | 'surface-600': 'rgb(var(--surface-600))',
26 | 'surface-700': 'rgb(var(--surface-700))',
27 | 'surface-800': 'rgb(var(--surface-800))',
28 | 'surface-900': 'rgb(var(--surface-900))',
29 | 'surface-950': 'rgb(var(--surface-950))',
30 | },
31 | },
32 | },
33 | plugins: [],
34 | }
35 |
--------------------------------------------------------------------------------
/apps/django/README.md:
--------------------------------------------------------------------------------
1 | # Django Dynamic Database Boot System
2 |
3 | A script that generates and runs Django applications with different database configurations on-demand.
4 |
5 | ## Quick Start
6 |
7 | ```bash
8 | # Run with SQLite (default)
9 | ./boot.sh
10 |
11 | # Run with MySQL
12 | ./boot.sh mysql
13 |
14 | # Run with PostgreSQL
15 | ./boot.sh postgres
16 | ```
17 |
18 | ## Prerequisites
19 |
20 | - Python 3.7+
21 | - Docker (for MySQL/PostgreSQL)
22 |
23 | ## Features
24 |
25 | - Supports SQLite, MySQL, and PostgreSQL
26 | - Automatically creates a sample todo application
27 | - Loads demo data for testing
28 | - Finds available ports automatically
29 | - Cleans up when stopped (Ctrl+C)
30 |
31 | ## Database Setup
32 |
33 | ### MySQL
34 | ```bash
35 | docker run -d --name mysql-devdb-triage -p 2222:3306 -e MYSQL_ROOT_PASSWORD=mysecretpassword mysql:8.0
36 | ```
37 |
38 | ### PostgreSQL
39 | ```bash
40 | docker run -d --name postgres-devdb-triage -p 3333:5432 -e POSTGRES_PASSWORD=mysecretpassword postgres:16
41 | ```
42 |
43 | ## Note
44 |
45 | - Connection details are printed when the script runs
46 | - The application is ephemeral - all data is lost when stopped
47 | - The generated application is ignored by Git
48 |
49 | ## Troubleshooting
50 |
51 | - For database errors: Check if containers are running
52 | - For port conflicts: The script will find an available port
53 | - If Django is missing: Run `pipx install django`
54 |
--------------------------------------------------------------------------------
/src/services/pagination.ts:
--------------------------------------------------------------------------------
1 | export type PaginationData = {
2 | currentPage: number;
3 | firstRowOnPage: number;
4 | lastRowOnPage: number;
5 | totalRows: number;
6 | prevPage?: number;
7 | nextPage?: number;
8 | endPage?: number;
9 | itemsPerPage: number;
10 | displayText: string;
11 | };
12 |
13 | export function getPaginationFor(table: string, page: number, totalRows: number, itemsPerPage: number): PaginationData {
14 | if (totalRows === 0) {
15 | return {
16 | currentPage: 1,
17 | firstRowOnPage: 0,
18 | lastRowOnPage: 0,
19 | totalRows: totalRows,
20 | endPage: 1,
21 | itemsPerPage,
22 | displayText: `Showing 0 to 0 of 0 records`
23 | };
24 | }
25 |
26 | const endPage = Math.ceil(totalRows / itemsPerPage);
27 | // Ensure the current page is within valid bounds
28 | const validPage = Math.min(Math.max(page, 1), endPage);
29 |
30 | const firstRowOnPage = (validPage - 1) * itemsPerPage + 1;
31 | const lastRowOnPage = validPage === endPage ? totalRows : validPage * itemsPerPage;
32 |
33 | return {
34 | currentPage: validPage,
35 | firstRowOnPage,
36 | lastRowOnPage,
37 | totalRows: totalRows,
38 | prevPage: validPage > 1 ? validPage - 1 : undefined,
39 | nextPage: validPage < endPage ? validPage + 1 : undefined,
40 | endPage,
41 | itemsPerPage,
42 | displayText: `Showing ${firstRowOnPage.toLocaleString()} to ${lastRowOnPage.toLocaleString()} of ${totalRows.toLocaleString()} records`
43 | };
44 | }
--------------------------------------------------------------------------------
/apps/rails/README.md:
--------------------------------------------------------------------------------
1 | # Rails Dynamic Database Boot System
2 |
3 | A script that generates and runs Rails applications with different database configurations on-demand.
4 |
5 | ## Quick Start
6 |
7 | ```bash
8 | # Run with SQLite (default)
9 | ./boot.sh
10 |
11 | # Run with MySQL
12 | ./boot.sh mysql
13 |
14 | # Run with PostgreSQL
15 | ./boot.sh postgres
16 | ```
17 |
18 | ## Prerequisites
19 |
20 | - Ruby 3.0+
21 | - Rails 8.0+
22 | - Docker (for MySQL/PostgreSQL)
23 |
24 | ## Features
25 |
26 | - Supports SQLite, MySQL, and PostgreSQL
27 | - Automatically creates a sample todo application
28 | - Loads demo data for testing
29 | - Finds available ports automatically (starting from 3000)
30 | - Cleans up when stopped (Ctrl+C)
31 |
32 | ## Database Setup
33 |
34 | ### MySQL
35 | ```bash
36 | docker run -d --name mysql-devdb-triage -p 2222:3306 -e MYSQL_ROOT_PASSWORD=mysecretpassword mysql:8.0
37 | ```
38 |
39 | ### PostgreSQL
40 | ```bash
41 | docker run -d --name postgres-devdb-triage -p 3333:5432 -e POSTGRES_PASSWORD=mysecretpassword postgres:16
42 | ```
43 |
44 | ## Note
45 |
46 | - Connection details are printed when the script runs
47 | - The application is ephemeral - all data is lost when stopped
48 | - The generated application is ignored by Git
49 |
50 | ## Troubleshooting
51 |
52 | - For database errors: Check if containers are running
53 | - For port conflicts: The script will find an available port
54 | - If Rails is missing: Run `gem install rails`
55 |
--------------------------------------------------------------------------------
/src/services/config-service.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { CosmiconfigResult, cosmiconfig } from 'cosmiconfig';
3 | import { SqliteConfig, MysqlConfig, MssqlConfig, PostgresConfig } from '../types';
4 | import { getPathToWorkspaceFile } from './workspace';
5 |
6 | export const DEVDB_CONFIG_FILE_NAME = '.devdbrc'
7 |
8 | export function getConfigFilePath() {
9 | return getPathToWorkspaceFile(DEVDB_CONFIG_FILE_NAME)
10 | }
11 |
12 | export async function getConfigFileContent(): Promise<(SqliteConfig | MysqlConfig | MssqlConfig | PostgresConfig)[] | undefined> {
13 | const configFilePath = getConfigFilePath()
14 | if (!configFilePath) return
15 |
16 | try {
17 | const result: CosmiconfigResult = await cosmiconfig('devdb').load(configFilePath)
18 | if (!result) return
19 |
20 | return result.config as (SqliteConfig | MysqlConfig)[]
21 | } catch (error) {
22 | }
23 | }
24 |
25 | /**
26 | * Adds a SQLite database to the config file if it doesn't already exist
27 | */
28 | export async function addSqlDatabaseToConfig(sqliteFilePath: string) {
29 | if (!sqliteFilePath) return
30 |
31 | const config = await getConfigFileContent() || []
32 | const configExists = config.some(config => config.type === 'sqlite' && config.path === sqliteFilePath)
33 |
34 | if (configExists) return
35 |
36 | config.push({
37 | type: 'sqlite',
38 | path: sqliteFilePath
39 | })
40 |
41 | await vscode.workspace.fs.writeFile(
42 | vscode.Uri.file(getConfigFilePath() as string),
43 | Buffer.from(JSON.stringify(config, null, 2))
44 | )
45 | }
--------------------------------------------------------------------------------
/src/services/connector.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import knexlib from "knex";
3 | import { log } from './logging-service';
4 | import { logToOutput } from './output-service';
5 | import { KnexClientType } from '../types';
6 |
7 | export async function getConnectionFor(description: string, dialect: KnexClientType, host: string, port: number, username: string, password: string, database: string | undefined = undefined, notifyOnError = true, options?: Record): Promise {
8 |
9 | log(`Connector - ${description}`, `Attempting to connect to database: dialect=${dialect}, host=${host}, port=${port}, username=${username}, database=${database ? String(database[0]) + '*****' : ''}`);
10 |
11 | try {
12 | const connection: any = {
13 | host: host ? String(host) : host,
14 | port: port ? Number(port) : port,
15 | user: username ? String(username) : username,
16 | password: password ? String(password) : password,
17 | database: database ? String(database) : database,
18 | };
19 |
20 | // Add options to connection config for MSSQL
21 | if (dialect === 'mssql' && options) {
22 | connection.options = options;
23 | }
24 |
25 | const knex = knexlib.knex({
26 | client: dialect,
27 | connection,
28 | });
29 |
30 | return knex
31 | } catch (error) {
32 | const message = `Connection error for '${dialect} dialect': ${String(error)}`
33 | if (notifyOnError) {
34 | vscode.window.showErrorMessage(message)
35 | }
36 |
37 | logToOutput(message, 'Connector')
38 |
39 | return;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/services/context-menu-service.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { ModelMap } from '../types';
3 | import { getTableModelMapForCurrentWorkspace } from './codelens/laravel/laravel-codelens-service';
4 | import { getWordUnderCursor } from './document-service';
5 | import { LaravelFactoryGenerator } from './laravel/factory-generator';
6 | import { database } from './messenger';
7 | import { showMissingDatabaseNotification } from './error-notification-service';
8 | import { explainSelectedQuery } from './codelens/laravel/sql-query-explainer-provider';
9 |
10 | export async function contextMenuLaravelFactoryGenerator() {
11 | if (!database) {
12 | return showMissingDatabaseNotification()
13 | }
14 |
15 | const wordUnderCursor = getWordUnderCursor()
16 | if (!wordUnderCursor) {
17 | return vscode.window.showErrorMessage('No word under cursor');
18 | }
19 |
20 | const tableModelMap: ModelMap = await getTableModelMapForCurrentWorkspace()
21 | const model = tableModelMap[wordUnderCursor];
22 |
23 | if (!model) {
24 | return vscode.window.showErrorMessage(`No model found by name ${wordUnderCursor}`);
25 | }
26 |
27 | const generator = new LaravelFactoryGenerator(database);
28 | await generator.generateFactory(wordUnderCursor, model.filePath);
29 | }
30 |
31 | export async function contextMenuQueryExplainer() {
32 | const editor = vscode.window.activeTextEditor;
33 | if (!editor) return;
34 |
35 | const document = editor.document;
36 | if (!document) return;
37 |
38 | const selection = editor.selection;
39 |
40 | explainSelectedQuery(document, selection)
41 | }
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/scrolltop/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props }) => ({
3 | class: [
4 | // Flex & Alignment
5 | 'flex items-center justify-center',
6 |
7 | // Positioning
8 | {
9 | sticky: props.target === 'parent',
10 | fixed: props.target === 'window'
11 | },
12 | 'bottom-[20px] right-[20px]',
13 | 'ml-auto',
14 |
15 | // Shape & Size
16 | {
17 | 'rounded-md h-8 w-8': props.target === 'parent',
18 | 'h-12 w-12 rounded-full shadow-md': props.target === 'window'
19 | },
20 |
21 | // Color
22 | 'text-white dark:text-surface-900',
23 | {
24 | 'bg-primary-500 dark:bg-primary-400 hover:bg-primary-600 dark:hover:bg-primary-300': props.target === 'parent',
25 | 'bg-surface-500 dark:bg-surface-400 hover:bg-surface-600 dark:hover:bg-surface-300': props.target === 'window'
26 | },
27 |
28 | // States
29 | {
30 | 'hover:bg-primary-600 dark:hover:bg-primary-300': props.target === 'parent',
31 | 'hover:bg-surface-600 dark:hover:bg-surface-300': props.target === 'window'
32 | }
33 | ]
34 | }),
35 | transition: {
36 | enterFromClass: 'opacity-0',
37 | enterActiveClass: 'transition-opacity duration-150',
38 | leaveActiveClass: 'transition-opacity duration-150',
39 | leaveToClass: 'opacity-0'
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/src/services/laravel/artisan-service.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { exec } from 'child_process';
3 | import { promisify } from 'util';
4 | import { getBasePath } from '../workspace';
5 |
6 | const execAsync = promisify(exec);
7 |
8 | export class ArtisanService {
9 | constructor(private workspaceRoot: string) { }
10 |
11 | static create(): ArtisanService | undefined {
12 | const workspaceRoot = getBasePath();
13 | if (!workspaceRoot) {
14 | vscode.window.showErrorMessage('No workspace folder found');
15 | return undefined;
16 | }
17 | return new ArtisanService(workspaceRoot);
18 | }
19 |
20 | async runCommand(command: string, args: string[] = []): Promise {
21 | const config = vscode.workspace.getConfiguration('Devdb');
22 | const phpPath = config.get('phpExecutablePath') || 'php';
23 |
24 | const fullCommand = `${phpPath} artisan ${command} ${args.join(' ')}`;
25 |
26 | const { stdout, stderr } = await execAsync(
27 | fullCommand,
28 | { cwd: this.workspaceRoot }
29 | ).catch(error => {
30 | vscode.window.showErrorMessage(
31 | `Failed to run artisan command: ${error.message}`
32 | );
33 | return { stdout: '', stderr: error.message };
34 | });
35 |
36 | if (stderr) {
37 | vscode.window.showErrorMessage(
38 | `Error running artisan command: ${stderr}`
39 | );
40 | return false;
41 | }
42 |
43 | return true;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/services/string.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * If the string is longer than 30 chars, it will be shortened to 30 chars with ellipsis. It will be shortened by
3 | * adding the first 14 chars and the last 13 chars of the string together with an ellipsis in the middle.
4 | * e.g. /home/damms005/.SchoolServer/test-workspace/database.db3 becomes /home/damms005.../database.db3
5 | */
6 | export function brief(str: string, length: number = 60) {
7 | if (str.length <= length) return str
8 |
9 | const halfLength = length / 2
10 |
11 | const firstPart = str.substring(0, halfLength)
12 | const lastPart = str.substring(str.length - halfLength)
13 |
14 | return `${firstPart}...${lastPart}`
15 | }
16 |
17 | /**
18 | * Extracts unique variables from a given text
19 | * @param text The input text containing variables
20 | * @returns An array of unique variables found
21 | */
22 | export function extractVariables(text: string): string[] {
23 | const variableRegex = /\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:->[\w]+)+/g;
24 | return [...new Set(text.match(variableRegex) || [])];
25 | }
26 |
27 | /**
28 | * Replaces variables in the text with user-provided values
29 | * @param text The original text with variables
30 | * @param variableValues A map of variables to their replacement values
31 | * @returns The text with variables replaced
32 | */
33 | export function replaceVariables(text: string, variableValues: { [key: string]: string }): string {
34 | const variableRegex = /\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:->[\w]+)+/g;
35 |
36 | return text.replace(variableRegex, (match) => {
37 | return variableValues[match] || match;
38 | });
39 | }
--------------------------------------------------------------------------------
/src/services/laravel/laravel-core.ts:
--------------------------------------------------------------------------------
1 | import { getWorkspaceFileContent } from "../workspace";
2 |
3 | /**
4 | * Returns the hostname portion of the APP_URL environment variable.
5 | */
6 | export async function getHostname(): Promise {
7 | const appUrl = await getEnvFileValue('APP_URL')
8 | if (!appUrl) return
9 |
10 | const appUrlWithoutQuotes = appUrl.replace(/"/g, '')
11 | const appUrlWithoutTrailingSlash = appUrlWithoutQuotes.endsWith('/')
12 | ? appUrlWithoutQuotes.substring(0, appUrlWithoutQuotes.length - 1)
13 | : appUrlWithoutQuotes
14 |
15 | const appUrlWithoutProtocol = appUrlWithoutTrailingSlash.replace(/https?:\/\//, '')
16 | const appUrlWithoutPort = appUrlWithoutProtocol.replace(/:\d+/, '')
17 |
18 | return appUrlWithoutPort
19 | }
20 |
21 | export async function getEnvFileValue(envFileKey: string): Promise {
22 | const envFileContents = getWorkspaceFileContent('.env')?.toString()
23 | if (!envFileContents) return
24 |
25 | const lines = envFileContents.split('\n');
26 | const appUrlLine = lines.find((line: string) => line.startsWith(`${envFileKey}=`))
27 | if (!appUrlLine) return
28 |
29 | const appUrl = appUrlLine.substring(appUrlLine.indexOf('=') + 1)
30 | const appUrlWithoutQuotes = appUrl.replace(/"/g, '')
31 | const appUrlWithoutTrailingSlash = appUrlWithoutQuotes.endsWith('/')
32 | ? appUrlWithoutQuotes.substring(0, appUrlWithoutQuotes.length - 1)
33 | : appUrlWithoutQuotes
34 |
35 | const appUrlWithoutProtocol = appUrlWithoutTrailingSlash.replace(/https?:\/\//, '')
36 | const appUrlWithoutPort = appUrlWithoutProtocol.replace(/:\d+/, '')
37 |
38 | return appUrlWithoutPort.trim()
39 | }
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/inputtext/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props, context, parent }) => ({
3 | class: [
4 | // Font
5 | 'font-sans leading-6',
6 |
7 | // Spacing
8 | 'm-0',
9 | {
10 | 'py-3 px-4 text-lg sm:text-md': props.size == 'large',
11 | 'py-1 px-2 sm:text-sm': props.size == 'small',
12 | 'py-1.5 px-3 sm:text-sm': props.size == null
13 | },
14 |
15 | // Colors
16 | 'text-surface-900 dark:text-surface-0',
17 | 'placeholder:text-surface-400 dark:placeholder:text-surface-500',
18 | 'bg-surface-0 dark:bg-surface-900',
19 | 'shadow-sm',
20 | { 'ring-1 ring-inset ring-surface-300 dark:ring-surface-700 ring-offset-0': parent.instance.$name !== 'InputGroup' },
21 |
22 | // Shape
23 | { 'rounded-md': parent.instance.$name !== 'InputGroup' },
24 | { 'first:rounded-l-md rounded-none last:rounded-r-md': parent.instance.$name == 'InputGroup' },
25 | { 'border-0 border-y border-l last:border-r border-surface-300 dark:border-surface-600': parent.instance.$name == 'InputGroup' },
26 | { 'first:ml-0 ml-[-1px]': parent.instance.$name == 'InputGroup' && !props.showButtons },
27 | 'appearance-none',
28 |
29 | // Interactions
30 | {
31 | 'outline-none focus:ring-primary-500 dark:focus:ring-primary-400': !context.disabled,
32 | 'opacity-60 select-none pointer-events-none cursor-default': context.disabled
33 | }
34 | ]
35 | })
36 | };
37 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/selectbutton/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props }) => ({
3 | class: ['shadow-sm', { 'opacity-60 select-none pointer-events-none cursor-default': props.disabled }]
4 | }),
5 | button: ({ context }) => ({
6 | class: [
7 | 'relative',
8 | // Font
9 | 'text-sm',
10 | 'leading-none',
11 |
12 | // Flex Alignment
13 | 'inline-flex items-center align-bottom text-center',
14 |
15 | // Spacing
16 | 'px-2.5 py-1.5',
17 |
18 | // Shape
19 | 'ring-1 ring-surface-200 dark:ring-surface-700',
20 | 'first:rounded-l-md first:rounded-tr-none first:rounded-br-none',
21 | 'last:rounded-tl-none last:rounded-bl-none last:rounded-r-md ',
22 |
23 | // Color
24 | {
25 | 'bg-surface-0 dark:bg-surface-900': !context.active,
26 | 'text-surface-700 dark:text-white/80': !context.active,
27 | 'bg-surface-100 dark:bg-surface-700': context.active
28 | },
29 |
30 | // States
31 | 'focus:outline-none focus:outline-offset-0 focus:ring-primary-500 dark:focus:ring-primary-400 focus:z-10',
32 | 'hover:bg-surface-200 dark:hover:bg-surface-600/80',
33 | { 'opacity-60 select-none pointer-events-none cursor-default': context.disabled },
34 |
35 | // Transition
36 | 'transition duration-200',
37 |
38 | // Misc
39 | 'cursor-pointer select-none overflow-hidden'
40 | ]
41 | }),
42 | label: {
43 | class: 'font-semibold'
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/src/providers/postgres/laravel-postgres-provider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { DatabaseEngine, DatabaseEngineProvider } from '../../types';
3 | import { PostgresEngine } from '../../database-engines/postgres-engine';
4 | import { getConnectionInEnvFile } from '../../services/laravel/env-file-parser';
5 | import { isDdevProject, isComposerPhpProject } from '../../services/workspace';
6 |
7 | export const LaravelPostgresProvider: DatabaseEngineProvider = {
8 | name: 'Laravel PostgreSQL',
9 | type: 'postgres',
10 | id: 'laravel-postgres',
11 | description: 'Laravel PostgreSQL with default .env config',
12 | engine: undefined,
13 |
14 | async canBeUsedInCurrentWorkspace(): Promise {
15 | if (!isComposerPhpProject()) {
16 | return false;
17 | }
18 |
19 | if (isDdevProject()) {
20 | /**
21 | * This is simply to improve the DX. Else, we report false negative as
22 | * it tries to load from .env, which is not how DDEV projects work.
23 | *
24 | * @see https://discord.com/channels/664580571770388500/1348955044334141460/1349010258214781021
25 | */
26 | return false;
27 | }
28 |
29 | const connection = await getConnectionInEnvFile('pgsql', 'postgres')
30 | if (!connection) return false
31 |
32 | try {
33 | this.engine = new PostgresEngine(connection);
34 | } catch (error) {
35 | vscode.window.showErrorMessage(`Postgres connection error: ${String(error)}`)
36 | return false
37 | }
38 |
39 | return (await this.engine.isOkay())
40 | },
41 |
42 | reconnect(): Promise {
43 | return this.canBeUsedInCurrentWorkspace()
44 | },
45 |
46 | async getDatabaseEngine(): Promise {
47 | return this.engine
48 | }
49 | }
--------------------------------------------------------------------------------
/esbuild.js:
--------------------------------------------------------------------------------
1 | const esbuild = require('esbuild');
2 |
3 | const production = process.argv.includes('--production');
4 | const watch = process.argv.includes('--watch');
5 |
6 | async function main () {
7 | const ctx = await esbuild.context({
8 | entryPoints: ['src/extension.ts', 'src/services/mcp/no-vscode/server.ts'],
9 | bundle: true,
10 | format: 'cjs',
11 | minify: production,
12 | sourcemap: !production,
13 | sourcesContent: false,
14 | platform: 'node',
15 | outdir: 'dist',
16 | entryNames: '[dir]/[name]',
17 |
18 | /**
19 | * Reasons for externalizing:
20 | * - vscode: not a typical npm package - injected by the IDE at runtime
21 | * - @vscode/sqlite3: has native bindings
22 | */
23 | external: ['vscode', '@vscode/sqlite3'],
24 |
25 | metafile: true,
26 | logLevel: 'warning',
27 | plugins: [
28 | /* add to the end of plugins array */
29 | esbuildProblemMatcherPlugin
30 | ]
31 | });
32 | if (watch) {
33 | await ctx.watch();
34 | } else {
35 | await ctx.rebuild();
36 | await ctx.dispose();
37 | }
38 | }
39 |
40 | /**
41 | * @type {import('esbuild').Plugin}
42 | */
43 | const esbuildProblemMatcherPlugin = {
44 | name: 'esbuild-problem-matcher',
45 |
46 | setup (build) {
47 | build.onStart(() => {
48 | console.log('[watch] build started');
49 | });
50 | build.onEnd(result => {
51 | result.errors.forEach(({ text, location }) => {
52 | console.error(`✘ [ERROR] ${text}`);
53 | if (location == null) return;
54 | console.error(` ${location.file}:${location.line}:${location.column}:`);
55 | });
56 |
57 | console.log('[watch] build finished');
58 | });
59 | }
60 | };
61 |
62 | main().catch(e => {
63 | console.error(e);
64 | process.exit(1);
65 | });
66 |
--------------------------------------------------------------------------------
/src/uri-handler.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | export class DevDbUriHandler implements vscode.UriHandler {
4 | public handleUri(uri: vscode.Uri): vscode.ProviderResult {
5 | if (!uri.path.startsWith('/open/table')) {
6 | return;
7 | }
8 |
9 | const queryParams = new URLSearchParams(uri.query);
10 | const connectionId = queryParams.get('connectionId');
11 | const database = queryParams.get('database');
12 | const table = queryParams.get('table');
13 | const workspace = queryParams.get('workspace');
14 | const authority = queryParams.get('authority');
15 |
16 | if (!table) {
17 | vscode.window.showErrorMessage('DevDb: Missing required parameter "table" in URI');
18 | return;
19 | }
20 |
21 | if (workspace) {
22 | const currentWorkspaceFolders = vscode.workspace.workspaceFolders;
23 | if (!currentWorkspaceFolders || !currentWorkspaceFolders.some(folder =>
24 | folder.uri.fsPath === workspace ||
25 | folder.uri.path === workspace)) {
26 | return;
27 | }
28 | }
29 |
30 | if (authority) {
31 | const currentAuthority = vscode.env.remoteName;
32 | if (currentAuthority !== authority) {
33 | return;
34 | }
35 | }
36 |
37 | const params: any = { table: table };
38 |
39 | if (connectionId) {
40 | params.connectionId = connectionId;
41 | }
42 |
43 | if (database) {
44 | params.database = database;
45 | }
46 |
47 | return vscode.commands.executeCommand('devdb.openTable', params);
48 | }
49 | }
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/badge/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props, context }) => ({
3 | class: [
4 | // Font
5 | 'font-medium',
6 | {
7 | 'text-xs leading-[1.5rem]': props.size == null,
8 | 'text-lg leading-[2.25rem]': props.size == 'large',
9 | 'text-2xl leading-[3rem]': props.size == 'xlarge'
10 | },
11 |
12 | // Alignment
13 | 'text-center inline-block',
14 |
15 | // Size
16 | 'p-0 px-1',
17 | {
18 | 'min-w-[1.5rem] h-[1.5rem]': props.size == null,
19 | 'min-w-[2.25rem] h-[2.25rem]': props.size == 'large',
20 | 'min-w-[3rem] h-[3rem]': props.size == 'xlarge'
21 | },
22 |
23 | // Shape
24 | {
25 | 'rounded-full': props.value.length == 1,
26 | 'rounded-[0.71rem]': props.value.length !== 1
27 | },
28 |
29 | // Color
30 | 'text-white dark:text-surface-900',
31 | {
32 | 'bg-primary-500 dark:bg-primary-400': props.severity == null || props.severity == 'primary',
33 | 'bg-surface-500 dark:bg-surface-400': props.severity == 'secondary',
34 | 'bg-green-500 dark:bg-green-400': props.severity == 'success',
35 | 'bg-blue-500 dark:bg-blue-400': props.severity == 'info',
36 | 'bg-orange-500 dark:bg-orange-400': props.severity == 'warning',
37 | 'bg-purple-500 dark:bg-purple-400': props.severity == 'help',
38 | 'bg-red-500 dark:bg-red-400': props.severity == 'danger'
39 | }
40 | ]
41 | })
42 | };
43 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/badgedirective/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ context }) => ({
3 | class: [
4 | // Font
5 | 'font-medium',
6 | 'text-xs leading-6 font-sans',
7 |
8 | // Alignment
9 | 'flex items-center justify-center',
10 | 'text-center',
11 |
12 | // Position
13 | 'absolute top-0 right-0 transform translate-x-1/2 -translate-y-1/2 origin-top-right',
14 |
15 | // Size
16 | 'm-0',
17 | {
18 | 'p-0': context.nogutter || context.dot,
19 | 'p-1': !context.nogutter && !context.dot,
20 | 'min-w-[0.5rem] h-2': context.dot,
21 | 'min-w-[1rem] h-4': !context.dot
22 | },
23 |
24 | // Shape
25 | {
26 | 'rounded-full': context.nogutter || context.dot,
27 | 'rounded-[10px]': !context.nogutter && !context.dot
28 | },
29 |
30 | // Color
31 | 'text-white dark:text-surface-900',
32 | 'ring-1 ring-white dark:ring-surface-900',
33 | {
34 | 'bg-primary-500 dark:bg-primary-400': !context.info && !context.success && !context.warning && !context.danger && !context.help && !context.secondary,
35 | 'bg-surface-500 dark:bg-surface-400': context.secondary,
36 | 'bg-green-500 dark:bg-green-400': context.success,
37 | 'bg-blue-500 dark:bg-blue-400': context.info,
38 | 'bg-orange-500 dark:bg-orange-400': context.warning,
39 | 'bg-purple-500 dark:bg-purple-400': context.help,
40 | 'bg-red-500 dark:bg-red-400': context.danger
41 | }
42 | ]
43 | })
44 | };
45 |
--------------------------------------------------------------------------------
/src/providers/sqlite/file-picker-sqlite-provider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { DatabaseEngine, DatabaseEngineProvider } from "../../types";
3 | import { addSqlDatabaseToConfig } from '../../services/config-service';
4 | import { SqliteEngine } from '../../database-engines/sqlite-engine';
5 |
6 | export const FilePickerSqliteProvider: DatabaseEngineProvider = {
7 | name: 'SQLite Database File Picker',
8 | type: 'sqlite',
9 | id: 'file-picker-sqlite',
10 | description: 'SQLite database file from your computer',
11 | engine: undefined,
12 | isDefault: true,
13 |
14 | async canBeUsedInCurrentWorkspace(): Promise {
15 | return true;
16 | },
17 |
18 | reconnect(): Promise {
19 | return this.canBeUsedInCurrentWorkspace()
20 | },
21 |
22 | async getDatabaseEngine(): Promise {
23 | const filePath = await selectFile();
24 | if (!filePath) {
25 | vscode.window.showErrorMessage('No file selected.')
26 | return
27 | }
28 |
29 | this.engine = new SqliteEngine(filePath);
30 |
31 | let isOkay = false;
32 | try {
33 | isOkay = (await this.engine.isOkay())
34 | } catch (error) {
35 | vscode.window.showErrorMessage(`Error opening ${filePath}: ${String(error)}`)
36 | return
37 | }
38 |
39 | if (!isOkay) {
40 | vscode.window.showErrorMessage('The selected file is not a valid SQLite database.')
41 | return
42 | }
43 |
44 | await addSqlDatabaseToConfig(filePath)
45 |
46 | return this.engine
47 | }
48 | }
49 |
50 | async function selectFile(): Promise {
51 | const fileUri = await vscode.window.showOpenDialog({
52 | canSelectMany: false,
53 | openLabel: 'Open SQLite File',
54 | canSelectFolders: false,
55 | title: 'Select SQLite File',
56 | filters: { 'SQLite': ['sqlite', 'db'], 'All Files': ['*'] }
57 | })
58 |
59 | if (fileUri && fileUri[0]) {
60 | return fileUri[0].fsPath;
61 | }
62 | }
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/scrollpanel/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | wrapper: {
3 | class: [
4 | // Size & Position
5 | 'h-full w-full',
6 |
7 | // Layering
8 | 'z-[1]',
9 |
10 | // Spacing
11 | 'overflow-hidden',
12 |
13 | // Misc
14 | 'relative float-left'
15 | ]
16 | },
17 | content: {
18 | class: [
19 | // Size & Spacing
20 | 'h-[calc(100%+12px)] w-[calc(100%+12px)] pr-[12px] pb-[12px] pl-0 pt-0',
21 |
22 | // Overflow & Scrollbar
23 | 'overflow-scroll scrollbar-none',
24 |
25 | // Box Model
26 | 'box-border',
27 |
28 | // Position
29 | 'relative',
30 |
31 | // Webkit Specific
32 | '[&::-webkit-scrollbar]:hidden'
33 | ]
34 | },
35 | barX: {
36 | class: [
37 | // Size & Position
38 | 'h-[6px] bottom-0',
39 |
40 | // Appearance
41 | 'bg-surface-100 dark:bg-surface-700 rounded',
42 |
43 | // Interactivity
44 | 'cursor-pointer',
45 |
46 | // Visibility & Layering
47 | 'invisible z-20',
48 |
49 | // Transition
50 | 'transition duration-[250ms] ease-linear',
51 |
52 | // Misc
53 | 'relative'
54 | ]
55 | },
56 | barY: {
57 | class: [
58 | // Size & Position
59 | 'w-[6px] top-0',
60 |
61 | // Appearance
62 | 'bg-surface-100 dark:bg-surface-700 rounded',
63 |
64 | // Interactivity
65 | 'cursor-pointer',
66 |
67 | // Visibility & Layering
68 | 'z-20',
69 |
70 | // Transition
71 | 'transition duration-[250ms] ease-linear',
72 |
73 | // Misc
74 | 'relative'
75 | ]
76 | }
77 | };
78 |
--------------------------------------------------------------------------------
/src/providers/mysql/ddev-mysql-provider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { MysqlEngine } from '../../database-engines/mysql-engine';
3 | import { isDdevAvailable, getDatabaseConnection } from '../../services/ddev/ddev-service';
4 | import { DatabaseEngine, DatabaseEngineProvider } from '../../types';
5 | import { isDdevProject } from '../../services/workspace';
6 | import { logToOutput } from '../../services/output-service';
7 |
8 | export const DdevMysqlProvider: DatabaseEngineProvider = {
9 | name: 'DDEV - MySQL',
10 | type: 'mysql',
11 | id: 'ddev-mysql',
12 | ddev: true,
13 | description: 'MySQL databases in projects running in DDEV',
14 | engine: undefined,
15 |
16 | /**
17 | * Checks if this provider can be used in the current workspace
18 | * @returns Promise true if DDEV is active and MySQL connection is available
19 | */
20 | async canBeUsedInCurrentWorkspace(): Promise {
21 | try {
22 | if (!isDdevProject()) {
23 | logToOutput('Not a DDEV project', 'Postgres DDEV')
24 | return false;
25 | }
26 |
27 | // Check if DDEV is available
28 | const isDdevActive = await isDdevAvailable(this.name);
29 |
30 | if (!isDdevActive) {
31 | return false;
32 | }
33 |
34 | // Get database connection from DDEV
35 | const connection = await getDatabaseConnection('mysql2');
36 |
37 | if (!connection) {
38 | return false;
39 | }
40 |
41 | // Initialize the engine
42 | this.engine = new MysqlEngine(connection);
43 |
44 | // Verify the connection is working
45 | return await this.engine.isOkay();
46 | } catch (error) {
47 | vscode.window.showErrorMessage(`Error initializing MySQL DDEV provider: ${error}`);
48 | return false;
49 | }
50 | },
51 |
52 | reconnect(): Promise {
53 | return this.canBeUsedInCurrentWorkspace()
54 | },
55 |
56 | async getDatabaseEngine(): Promise {
57 | return this.engine
58 | }
59 | };
60 |
--------------------------------------------------------------------------------
/src/services/workspace.ts:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import fs from 'fs';
3 | import { existsSync, readFileSync } from 'fs';
4 | import { join } from 'path';
5 | import * as vscode from 'vscode';
6 |
7 | export function getBasePath(): string | undefined {
8 |
9 | const customBasePath = vscode.workspace.getConfiguration('Devdb').get('customBasePath');
10 |
11 | if (customBasePath && customBasePath.trim() !== '' && fs.existsSync(customBasePath)) {
12 | return customBasePath;
13 | }
14 |
15 | const workspaceFolders = vscode.workspace.workspaceFolders;
16 |
17 | if (!workspaceFolders || !workspaceFolders.length) return undefined
18 |
19 | return workspaceFolders[0].uri.fsPath;
20 | }
21 |
22 | /**
23 | * Returns the path to the workspace file.
24 | */
25 | export function getPathToWorkspaceFile(...subPath: string[]): string | undefined {
26 | const firstWorkspacePath = getBasePath()
27 | if (!firstWorkspacePath) return undefined
28 |
29 | return join(firstWorkspacePath, ...subPath);
30 | }
31 |
32 | export function getWorkspaceFileContent(...subPath: string[]): Buffer | undefined {
33 | const filePath = getPathToWorkspaceFile(...subPath)
34 | if (!filePath) return undefined
35 |
36 | if (!existsSync(filePath)) return undefined
37 |
38 | return readFileSync(filePath);
39 | }
40 |
41 | export async function fileExists(path: string): Promise {
42 | try {
43 | await vscode.workspace.fs.stat(vscode.Uri.file(path))
44 | return true
45 | } catch (error) {
46 | return false
47 | }
48 | }
49 |
50 | export function isDdevProject(): boolean {
51 | const workspaceRoot = getBasePath();
52 | if (!workspaceRoot) {
53 | return false;
54 | }
55 |
56 | return fs.existsSync(path.join(workspaceRoot, '.ddev'));
57 | }
58 |
59 | export function isComposerPhpProject(): boolean {
60 | // simply check if workspace root contains a .ddev directory
61 | const workspaceRoot = getBasePath();
62 | if (!workspaceRoot) {
63 | return false;
64 | }
65 |
66 | return fs.existsSync(path.join(workspaceRoot, 'composer.json'));
67 | }
--------------------------------------------------------------------------------
/src/providers/postgres/ddev-postgres-provider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { PostgresEngine } from '../../database-engines/postgres-engine';
3 | import { isDdevAvailable, getDatabaseConnection } from '../../services/ddev/ddev-service';
4 | import { DatabaseEngine, DatabaseEngineProvider } from '../../types';
5 | import { isDdevProject } from '../../services/workspace';
6 | import { logToOutput } from '../../services/output-service';
7 |
8 | export const DdevPostgresProvider: DatabaseEngineProvider = {
9 | name: 'DDEV - PostgreSQL',
10 | type: 'postgres',
11 | id: 'ddev-postgres',
12 | ddev: true,
13 | description: 'PostgreSQL databases in projects running in DDEV',
14 | engine: undefined as PostgresEngine | undefined,
15 |
16 | /**
17 | * Checks if this provider can be used in the current workspace
18 | * @returns Promise indicating if the provider can be used
19 | */
20 | async canBeUsedInCurrentWorkspace(): Promise {
21 | try {
22 | if (!isDdevProject()) {
23 | logToOutput('Not a DDEV project', 'Postgres DDEV')
24 | return false;
25 | }
26 |
27 | // Check if DDEV is available
28 | if (!(await isDdevAvailable(this.name))) {
29 | return false;
30 | }
31 |
32 | // Get PostgreSQL connection from DDEV
33 | const connection = await getDatabaseConnection('postgres');
34 | if (!connection) {
35 | return false;
36 | }
37 |
38 | // Initialize the engine with the connection
39 | this.engine = new PostgresEngine(connection);
40 |
41 | // Check if the engine is okay
42 | return await this.engine.isOkay();
43 | } catch (error) {
44 | vscode.window.showErrorMessage(`Failed to initialize PostgreSQL engine: ${error instanceof Error ? error.message : String(error)}`);
45 | return false;
46 | }
47 | },
48 |
49 | reconnect(): Promise {
50 | return this.canBeUsedInCurrentWorkspace()
51 | },
52 |
53 | async getDatabaseEngine(): Promise {
54 | return this.engine
55 | }
56 | };
57 |
--------------------------------------------------------------------------------
/src/providers/mysql/laravel-mysql-provider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { DatabaseEngine, DatabaseEngineProvider } from '../../types';
3 | import { MysqlEngine } from '../../database-engines/mysql-engine';
4 | import { getConnectionInEnvFile } from '../../services/laravel/env-file-parser';
5 | import { log } from '../../services/logging-service';
6 | import { isComposerPhpProject, isDdevProject } from '../../services/workspace';
7 |
8 | export const LaravelMysqlProvider: DatabaseEngineProvider = {
9 | name: 'Laravel Mysql (with Sail support)',
10 | type: 'mysql',
11 | id: 'laravel-mysql',
12 | description: 'Laravel MySQL with default .env config or Sail config in docker-compose.yml',
13 | engine: undefined,
14 |
15 | async canBeUsedInCurrentWorkspace(): Promise {
16 | if (!isComposerPhpProject()) {
17 | return false;
18 | }
19 |
20 | if (isDdevProject()) {
21 | /**
22 | * This is simply to improve the DX. Else, we report false negative as
23 | * it tries to load from .env, which is not how DDEV projects work.
24 | *
25 | * @see https://discord.com/channels/664580571770388500/1348955044334141460/1349010258214781021
26 | */
27 | return false;
28 | }
29 |
30 | log('Laravel MySQL', 'Checking if Laravel MySQL provider can be used in the current workspace...');
31 | const connection = await getConnectionInEnvFile('mysql', 'mysql2');
32 | log('Laravel MySQL', `Connection status: ${connection ? 'successful' : 'failed'}`);
33 | if (!connection) return false
34 |
35 | try {
36 | log('Laravel MySQL', 'Creating MySQL engine...');
37 | this.engine = new MysqlEngine(connection);
38 | } catch (error) {
39 | vscode.window.showErrorMessage(`MySQL connection error: ${String(error)}`);
40 | log('Laravel MySQL', `MySQL connection error: ${String(error)}`);
41 | return false
42 | }
43 |
44 | log('Laravel MySQL', 'OK');
45 | return (await this.engine.isOkay())
46 | },
47 |
48 | reconnect(): Promise {
49 | return this.canBeUsedInCurrentWorkspace()
50 | },
51 |
52 | async getDatabaseEngine(): Promise {
53 | return this.engine
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/dataviewlayoutoptions/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | listbutton: ({ props }) => ({
3 | class: [
4 | // Font
5 | 'leading-none',
6 |
7 | // Flex Alignment
8 | 'inline-flex items-center align-bottom text-center',
9 |
10 | // Shape
11 | 'rounded-md rounded-r-none',
12 |
13 | // Spacing
14 | 'px-2.5 py-1.5',
15 |
16 | // Color
17 | 'ring-1 ring-surface-200 dark:ring-surface-700',
18 | props.modelValue === 'list' ? 'bg-surface-100 dark:bg-surface-700 text-surface-700 dark:text-surface-0' : 'bg-surface-0 dark:bg-surface-900 text-surface-700 dark:text-white/80',
19 |
20 | // States
21 | 'focus:outline-none focus:outline-offset-0 focus:ring-primary-500 dark:focus:ring-primary-400',
22 | 'hover:bg-surface-200 dark:hover:bg-surface-600/80',
23 |
24 | // Transition
25 | 'transition duration-200',
26 |
27 | // Misc
28 | 'cursor-pointer select-none overflow-hidden'
29 | ]
30 | }),
31 | gridbutton: ({ props }) => ({
32 | class: [
33 | // Font
34 | 'leading-none',
35 |
36 | // Flex Alignment
37 | 'inline-flex items-center align-bottom text-center',
38 |
39 | // Shape
40 | 'rounded-md rounded-l-none',
41 |
42 | // Spacing
43 | 'px-2.5 py-1.5',
44 |
45 | // Color
46 | 'ring-1 ring-surface-200 dark:ring-surface-700',
47 | props.modelValue === 'grid' ? 'bg-surface-100 dark:bg-surface-700 text-surface-700 dark:text-surface-0' : 'bg-surface-0 dark:bg-surface-900 text-surface-700 dark:text-white/80',
48 |
49 | // States
50 | 'focus:outline-none focus:outline-offset-0 focus:ring-primary-500 dark:focus:ring-primary-400',
51 | 'hover:bg-surface-200 dark:hover:bg-surface-600/80',
52 |
53 | // Transition
54 | 'transition duration-200',
55 |
56 | // Misc
57 | 'cursor-pointer select-none overflow-hidden'
58 | ]
59 | })
60 | };
61 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/global.js:
--------------------------------------------------------------------------------
1 | export default {
2 | css: `
3 | *[data-pd-ripple="true"]{
4 | overflow: hidden;
5 | position: relative;
6 | }
7 | span[data-p-ink-active="true"]{
8 | animation: ripple 0.4s linear;
9 | }
10 | @keyframes ripple {
11 | 100% {
12 | opacity: 0;
13 | transform: scale(2.5);
14 | }
15 | }
16 |
17 | .progress-spinner-circle {
18 | stroke-dasharray: 89, 200;
19 | stroke-dashoffset: 0;
20 | animation: p-progress-spinner-dash 1.5s ease-in-out infinite, p-progress-spinner-color 6s ease-in-out infinite;
21 | stroke-linecap: round;
22 | }
23 |
24 | @keyframes p-progress-spinner-dash{
25 | 0% {
26 | stroke-dasharray: 1, 200;
27 | stroke-dashoffset: 0;
28 | }
29 |
30 | 50% {
31 | stroke-dasharray: 89, 200;
32 | stroke-dashoffset: -35px;
33 | }
34 | 100% {
35 | stroke-dasharray: 89, 200;
36 | stroke-dashoffset: -124px;
37 | }
38 | }
39 | @keyframes p-progress-spinner-color {
40 | 100%, 0% {
41 | stroke: #ff5757;
42 | }
43 | 40% {
44 | stroke: #696cff;
45 | }
46 | 66% {
47 | stroke: #1ea97c;
48 | }
49 | 80%, 90% {
50 | stroke: #cc8925;
51 | }
52 | }
53 |
54 | .progressbar-value-animate::after {
55 | will-change: left, right;
56 | animation: p-progressbar-indeterminate-anim-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
57 | }
58 | .progressbar-value-animate::before {
59 | will-change: left, right;
60 | animation: p-progressbar-indeterminate-anim 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
61 | }
62 | @keyframes p-progressbar-indeterminate-anim {
63 | 0% {
64 | left: -35%;
65 | right: 100%;
66 | }
67 | 60% {
68 | left: 100%;
69 | right: -90%;
70 | }
71 | 100% {
72 | left: 100%;
73 | right: -90%;
74 | }
75 | }
76 | `
77 | };
78 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/progressbar/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props }) => ({
3 | class: [
4 | // Position and Overflow
5 | 'relative overflow-hidden',
6 |
7 | // Shape and Size
8 | 'border-0',
9 | 'rounded-md',
10 | { 'h-7 pt-5': props.mode !== 'indeterminate' && props.showValue },
11 | { 'h-2 bg-surface-100 dark:bg-surface-700 ': props.mode == 'indeterminate' || !props.showValue },
12 |
13 | // Before & After (!indeterminate)
14 | { 'before:absolute before:w-full before:rounded-md before:h-2 before:top-[1.25rem] before:left-0 before:bottom-0 before:bg-surface-100 dark:before:bg-surface-700': props.mode !== 'indeterminate' }
15 | ]
16 | }),
17 | value: ({ props }) => ({
18 | class: [
19 | // Flexbox & Overflow & Position
20 | { 'absolute flex items-center justify-center': props.mode !== 'indeterminate' },
21 |
22 | // Colors
23 | 'bg-primary-500 dark:bg-primary-400',
24 |
25 | // Spacing & Sizing
26 | 'm-0',
27 | { 'h-2 w-0': props.mode !== 'indeterminate' },
28 |
29 | // Shape
30 | 'border-0 rounded-md',
31 |
32 | // Transitions
33 | {
34 | 'transition-width duration-1000 ease-in-out': props.mode !== 'indeterminate',
35 | 'progressbar-value-animate': props.mode == 'indeterminate'
36 | },
37 |
38 | // Before & After (indeterminate)
39 | {
40 | 'before:absolute before:top-0 before:left-0 before:bottom-0 before:bg-inherit ': props.mode == 'indeterminate',
41 | 'after:absolute after:top-0 after:left-0 after:bottom-0 after:bg-inherit after:delay-1000': props.mode == 'indeterminate'
42 | }
43 | ]
44 | }),
45 | label: {
46 | class: [
47 | // Flexbox
48 | 'inline-flex justify-end',
49 | 'absolute inset-0 mr-1 -top-[1.15rem]',
50 |
51 | // Font and Text
52 | 'text-sm text-surface-600 dark:text-surface-0/60',
53 | 'leading-none'
54 | ]
55 | }
56 | };
57 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/rating/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props }) => ({
3 | class: [
4 | 'relative',
5 |
6 | // Flex & Alignment
7 | 'flex items-center',
8 | 'gap-1',
9 |
10 | // Misc
11 | {
12 | 'opacity-60 select-none pointer-events-none cursor-default': props.disabled
13 | }
14 | ]
15 | }),
16 | cancelitem: ({ context }) => ({
17 | class: [
18 | // Flex & Alignment
19 | 'inline-flex items-center',
20 |
21 | //State
22 | {
23 | 'outline-none ring-2 ring-primary-500 dark:ring-primary-400': context.focused
24 | },
25 |
26 | // Misc
27 | 'cursor-pointer'
28 | ]
29 | }),
30 | cancelicon: {
31 | class: [
32 | // Size
33 | 'w-5 h-5',
34 |
35 | // Color
36 | 'text-red-500 dark:text-red-400'
37 | ]
38 | },
39 | item: ({ props, context }) => ({
40 | class: [
41 | // Flex & Alignment
42 | 'inline-flex items-center',
43 |
44 | // State
45 | {
46 | 'outline-none ring-2 ring-primary-500 dark:ring-primary-400': context.focused
47 | },
48 |
49 | // Misc
50 | {
51 | 'cursor-pointer': !props.readonly,
52 | 'cursor-default': props.readonly
53 | }
54 | ]
55 | }),
56 | officon: ({ props }) => ({
57 | class: [
58 | // Size
59 | 'w-5 h-5',
60 |
61 | // Color
62 | 'text-surface-700 dark:text-surface-0/70',
63 |
64 | // State
65 | { 'hover:text-primary-500 dark:hover:text-primary-400': !props.readonly },
66 |
67 | // Transition
68 | 'transition duration-200 ease-in'
69 | ]
70 | }),
71 | onicon: ({ props }) => ({
72 | class: [
73 | // Size
74 | 'w-5 h-5',
75 |
76 | // Color
77 | 'text-primary-500 dark:text-primary-400',
78 |
79 | // Transition
80 | 'transition duration-200 ease-in'
81 | ]
82 | })
83 | };
84 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/password/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props }) => ({
3 | class: [
4 | 'inline-flex relative',
5 | {
6 | 'opacity-60 select-none pointer-events-none cursor-default': props.disabled
7 | }
8 | ]
9 | }),
10 | panel: {
11 | class: [
12 | // Spacing
13 | 'p-3',
14 |
15 | // Shape
16 | 'border-0 dark:border',
17 | 'shadow-md rounded-md',
18 |
19 | // Colors
20 | 'bg-surface-0 dark:bg-surface-900',
21 | 'text-surface-700 dark:text-white/80',
22 | 'dark:border-surface-700'
23 | ]
24 | },
25 | meter: {
26 | class: [
27 | // Position and Overflow
28 | 'overflow-hidden',
29 | 'relative',
30 |
31 | // Shape and Size
32 | 'border-0',
33 | 'h-2',
34 | 'rounded-md',
35 |
36 | // Spacing
37 | 'mb-2',
38 |
39 | // Colors
40 | 'bg-surface-100 dark:bg-surface-700'
41 | ]
42 | },
43 | meterlabel: ({ instance }) => ({
44 | class: [
45 | // Size
46 | 'h-full',
47 |
48 | // Colors
49 | {
50 | 'bg-red-500 dark:bg-red-400/50': instance?.meter?.strength == 'weak',
51 | 'bg-orange-500 dark:bg-orange-400/50': instance?.meter?.strength == 'medium',
52 | 'bg-green-500 dark:bg-green-400/50': instance?.meter?.strength == 'strong'
53 | },
54 |
55 | // Transitions
56 | 'transition-all duration-1000 ease-in-out'
57 | ]
58 | }),
59 | showicon: {
60 | class: ['absolute top-1/2 right-3 -mt-2', 'text-surface-600 dark:text-white/70']
61 | },
62 | hideicon: {
63 | class: ['absolute top-1/2 right-3 -mt-2', 'text-surface-600 dark:text-white/70']
64 | },
65 | transition: {
66 | enterFromClass: 'opacity-0 scale-y-[0.8]',
67 | enterActiveClass: 'transition-[transform,opacity] duration-[120ms] ease-[cubic-bezier(0,0,0.2,1)]',
68 | leaveActiveClass: 'transition-opacity duration-100 ease-linear',
69 | leaveToClass: 'opacity-0'
70 | }
71 | };
72 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/accordion/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | accordiontab: {
3 | header: ({ props }) => ({
4 | class: [
5 | // Sizing
6 | 'pt-6 pb-0',
7 | 'mt-6',
8 |
9 | // Shape
10 | 'border-x-0 border-b-0',
11 |
12 | // Color
13 | 'border border-surface-200 dark:border-surface-700',
14 |
15 | // State
16 | { 'select-none pointer-events-none cursor-default opacity-60': props?.disabled }
17 | ]
18 | }),
19 | headerAction: ({ context }) => ({
20 | class: [
21 | //Font
22 | 'font-semibold',
23 | 'leading-7',
24 |
25 | // Alignments
26 | 'flex items-center justify-between flex-row-reverse',
27 | 'relative',
28 |
29 | // Shape
30 | 'rounded-md',
31 |
32 | // Color
33 | 'bg-transparent',
34 | 'text-surface-900 dark:text-surface-0',
35 |
36 | // States
37 | 'focus:outline-none focus:outline-offset-0 focus-visible:ring-2 focus-visible:ring-primary-600 ring-inset dark:focus-visible:ring-primary-500', // Focus
38 |
39 | // Misc
40 | 'cursor-pointer no-underline select-none'
41 | ]
42 | }),
43 | headerIcon: {
44 | class: 'inline-block ml-2'
45 | },
46 | headerTitle: {
47 | class: 'leading-7'
48 | },
49 | content: {
50 | class: [
51 | // Font
52 | 'leading-7',
53 |
54 | // Spacing
55 | 'pr-12 pt-2',
56 |
57 | // Color
58 | 'text-surface-600 dark:text-surface-0/70'
59 | ]
60 | },
61 | transition: {
62 | enterFromClass: 'max-h-0',
63 | enterActiveClass: 'overflow-hidden transition-[max-height] duration-1000 ease-[cubic-bezier(0.42,0,0.58,1)]',
64 | enterToClass: 'max-h-[1000px]',
65 | leaveFromClass: 'max-h-[1000px]',
66 | leaveActiveClass: 'overflow-hidden transition-[max-height] duration-[450ms] ease-[cubic-bezier(0,1,0,1)]',
67 | leaveToClass: 'max-h-0'
68 | }
69 | }
70 | };
71 |
--------------------------------------------------------------------------------
/scripts/triage/mysql-triage.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Pull the latest MySQL image
4 | # Use same as in test https://github.com/damms005/devdb-vscode/blob/84d2bd86595db2ecb21c6ffbe8b27b1459bebd92/src/test/suite/engines/mysql.test.ts#L13
5 | docker pull mysql:8.0.31
6 |
7 | # Check if container already exists
8 | if [ "$(docker ps -a --filter 'name=^/mysql-devdb-triage$' --format '{{.Names}}')" == "mysql-devdb-triage" ]; then
9 | echo "Container exists. Starting mysql-devdb-triage if not already running..."
10 | docker start mysql-devdb-triage
11 | else
12 | echo "Container does not exist. Creating a new mysql-devdb-triage container..."
13 | docker run --name mysql-devdb-triage -e MYSQL_ROOT_PASSWORD=mysecretpassword -p 2222:3306 -d mysql:8.0.31
14 | fi
15 |
16 | # Wait for the database to start
17 | echo "Waiting for MySQL to start..."
18 | until docker exec mysql-devdb-triage mysqladmin ping -h "localhost" --silent; do
19 | sleep 1
20 | done
21 |
22 | # Create a sample database and table
23 | docker exec -i mysql-devdb-triage mysql -uroot -pmysecretpassword -P 2222 << EOF
24 | CREATE DATABASE IF NOT EXISTS sample_db;
25 | USE sample_db;
26 |
27 | CREATE TABLE IF NOT EXISTS book (
28 | id INT AUTO_INCREMENT PRIMARY KEY,
29 | title VARCHAR(255) NOT NULL,
30 | author VARCHAR(255) NOT NULL,
31 | published_date DATE,
32 | isbn VARCHAR(13),
33 | pages INT,
34 | available BOOLEAN DEFAULT TRUE
35 | );
36 | INSERT INTO book (title, author, published_date, isbn, pages, available) VALUES
37 | ('The Great Gatsby', 'F. Scott Fitzgerald', '1925-04-10', '9780743273565', 180, TRUE),
38 | ('1984', 'George Orwell', '1949-06-08', '9780451524935', 328, FALSE),
39 | ('To Kill a Mockingbird', 'Harper Lee', '1960-07-11', '9780061120084', 281, TRUE),
40 | ('Pride and Prejudice', 'Jane Austen', '1813-01-28', '9781503290563', 279, TRUE),
41 | ('The Catcher in the Rye', 'J.D. Salinger', '1951-07-16', '9780316769488', 214, FALSE);
42 |
43 | EOF
44 |
45 | echo "MySQL triage setup complete."
46 |
47 | echo "Example connection details:"
48 |
49 | cat << EXAMPLE_CONNECTION
50 | {
51 | "host" : "localhost",
52 | "port" : 2222,
53 | "username" : "root",
54 | "password" : "mysecretpassword",
55 | "database" : "sample_db"
56 | }
57 | EXAMPLE_CONNECTION
58 |
59 | echo "To stop the MySQL container, run the following command:"
60 | echo "docker stop mysql-devdb-triage && docker rm mysql-devdb-triage"
61 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/togglebutton/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: {
3 | class: [
4 | 'relative',
5 |
6 | // Alignment
7 | 'inline-flex',
8 | 'align-bottom',
9 |
10 | // Misc
11 | 'cursor-pointer',
12 | 'select-none'
13 | ]
14 | },
15 | box: ({ props }) => ({
16 | class: [
17 | // Alignments
18 | 'items-center inline-flex flex-1 text-center align-bottom justify-center',
19 |
20 | // Sizes & Spacing
21 | 'px-2.5 py-1.5',
22 | 'text-sm',
23 |
24 | // Shapes
25 | 'rounded-md shadow-sm',
26 |
27 | // Colors
28 | 'text-surface-700 dark:text-white/80',
29 | 'ring-1 ring-surface-200 dark:ring-surface-700',
30 | {
31 | 'bg-surface-0 dark:bg-surface-900 ': !props.modelValue,
32 | 'bg-surface-100 dark:bg-surface-700': props.modelValue
33 | },
34 |
35 | // States
36 | 'peer-hover:bg-surface-200 dark:peer-hover:bg-surface-600/80',
37 | {
38 | 'peer-focus-visible:ring-2 peer-focus-visible:ring-inset peer-focus-visible:ring-primary-500 dark:peer-focus-visible:ring-primary-400': !props.disabled
39 | },
40 |
41 | // Transitions
42 | 'transition-all duration-200',
43 |
44 | // Misc
45 | { 'cursor-pointer': !props.disabled, 'opacity-60 select-none pointer-events-none cursor-default': props.disabled }
46 | ]
47 | }),
48 | label: {
49 | class: 'font-semibold text-center w-full'
50 | },
51 | input: {
52 | class: [
53 | 'peer',
54 |
55 | // Size
56 | 'w-full ',
57 | 'h-full',
58 |
59 | // Position
60 | 'absolute',
61 | 'top-0 left-0',
62 | 'z-10',
63 |
64 | // Spacing
65 | 'p-0',
66 | 'm-0',
67 |
68 | // Shape
69 | 'opacity-0',
70 | 'rounded-md',
71 | 'outline-none',
72 | 'border border-surface-200 dark:border-surface-700',
73 |
74 | // Misc
75 | 'appareance-none',
76 | 'cursor-pointer'
77 | ]
78 | },
79 | icon: {
80 | class: [' mr-2', 'text-surface-700 dark:text-white/80']
81 | }
82 | };
83 |
--------------------------------------------------------------------------------
/snippets/devdbrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "DevDB MySQL Configuration": {
3 | "prefix": "devdb mysql",
4 | "body": [
5 | "{",
6 | "\t\"name\": \"${1:My MySQL Connection}\",",
7 | "\t\"type\": \"mysql\",",
8 | "\t\"host\": \"${2:localhost}\",",
9 | "\t\"port\": ${3:3306},",
10 | "\t\"username\": \"${4:root}\",",
11 | "\t\"password\": \"${5:password}\",",
12 | "\t\"database\": \"${6:database_name}\"",
13 | "}"
14 | ],
15 | "description": "Add a new MySQL database configuration"
16 | },
17 | "DevDB MariaDB Configuration": {
18 | "prefix": "devdb mariadb",
19 | "body": [
20 | "{",
21 | "\t\"name\": \"${1:My MariaDB Connection}\",",
22 | "\t\"type\": \"mariadb\",",
23 | "\t\"host\": \"${2:localhost}\",",
24 | "\t\"port\": ${3:3306},",
25 | "\t\"username\": \"${4:root}\",",
26 | "\t\"password\": \"${5:password}\",",
27 | "\t\"database\": \"${6:database_name}\"",
28 | "}"
29 | ],
30 | "description": "Add a new MariaDB database configuration"
31 | },
32 | "DevDB PostgreSQL Configuration": {
33 | "prefix": "devdb postgres",
34 | "body": [
35 | "{",
36 | "\t\"name\": \"${1:My PostgreSQL Connection}\",",
37 | "\t\"type\": \"postgres\",",
38 | "\t\"host\": \"${2:localhost}\",",
39 | "\t\"port\": ${3:5432},",
40 | "\t\"username\": \"${4:postgres}\",",
41 | "\t\"password\": \"${5:password}\",",
42 | "\t\"database\": \"${6:database_name}\"",
43 | "}"
44 | ],
45 | "description": "Add a new PostgreSQL database configuration"
46 | },
47 | "DevDB SQLite Configuration": {
48 | "prefix": "devdb sqlite",
49 | "body": [
50 | "{",
51 | "\t\"type\": \"sqlite\",",
52 | "\t\"path\": \"${1:/path/to/database.db3}\"",
53 | "}"
54 | ],
55 | "description": "Add a new SQLite database configuration"
56 | },
57 | "DevDB MSSQL Configuration": {
58 | "prefix": "devdb mssql sqlserver",
59 | "body": [
60 | "{",
61 | "\t\"name\": \"${1:My MSSQL Connection}\",",
62 | "\t\"type\": \"mssql\",",
63 | "\t\"host\": \"${2:localhost}\",",
64 | "\t\"port\": ${3:1433},",
65 | "\t\"username\": \"${4:sa}\",",
66 | "\t\"password\": \"${5:password}\",",
67 | "\t\"database\": \"${6:database_name}\",",
68 | "\t\"options\": {",
69 | "\t\t\"trustServerCertificate\": ${7:true}",
70 | "\t}",
71 | "}"
72 | ],
73 | "description": "Add a new Microsoft SQL Server database configuration"
74 | }
75 | }
--------------------------------------------------------------------------------
/src/providers/sqlite/laravel-local-sqlite-provider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { join } from 'path';
3 | import { DotenvParseOutput, parse } from 'dotenv';
4 | import { DatabaseEngine, DatabaseEngineProvider } from '../../types';
5 | import { SqliteEngine } from '../../database-engines/sqlite-engine';
6 | import { fileExists, getBasePath, getWorkspaceFileContent, isComposerPhpProject } from '../../services/workspace';
7 |
8 | export const LaravelLocalSqliteProvider: DatabaseEngineProvider = {
9 | name: 'Laravel Local SQLite (default)',
10 | type: 'sqlite',
11 | id: 'laravel-local-sqlite',
12 | description: 'Laravel with local default SQLite database',
13 | engine: undefined,
14 |
15 | async canBeUsedInCurrentWorkspace(): Promise {
16 | if (!isComposerPhpProject()) {
17 | return false;
18 | }
19 |
20 | const configContent = getWorkspaceFileContent('config', 'database.php');
21 | if (!configContent) return false;
22 |
23 | const envFileContents = getWorkspaceFileContent('.env');
24 | if (!envFileContents) return false;
25 |
26 | const env = parse(envFileContents);
27 | const usesSqlite = env.DB_CONNECTION == 'sqlite';
28 | if (!usesSqlite) return false;
29 |
30 | const sqliteFilePath = await getSqliteFilePath(configContent.toString(), env)
31 |
32 | try {
33 | this.engine = new SqliteEngine(sqliteFilePath);
34 | } catch (error) {
35 | vscode.window.showErrorMessage(`SQLite file error ${sqliteFilePath}: ${String(error)}`)
36 | return false
37 | }
38 |
39 | return (await this.engine.isOkay())
40 | },
41 |
42 | reconnect(): Promise {
43 | return this.canBeUsedInCurrentWorkspace()
44 | },
45 |
46 | async getDatabaseEngine(): Promise {
47 | return this.engine
48 | }
49 | }
50 |
51 | async function getSqliteFilePath(configContent: string, envFileContent: DotenvParseOutput): Promise {
52 | // Match /env('DB_DATABASE', database_path('database.sqlite'))/ irrespective of whitespace
53 | const databasePathRegex = /env\(\s*['"]DB_DATABASE['"]\s*,\s*database_path\(\s*['"]database\.sqlite['"]\s*\)\s*\)/;
54 | if (!databasePathRegex.test(configContent)) return '';
55 |
56 | const databasePathDefinedInEnv = envFileContent.DB_DATABASE;
57 | if (databasePathDefinedInEnv) return databasePathDefinedInEnv;
58 |
59 | const workspacePath = getBasePath()
60 | if (!workspacePath) return ''
61 |
62 | const databaseFilePath = join(workspacePath, 'database', 'database.sqlite');
63 |
64 | const exists = await fileExists(databaseFilePath)
65 | if (!exists) return '';
66 |
67 | return databaseFilePath
68 | }
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/radiobutton/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: {
3 | class: [
4 | 'relative',
5 |
6 | // Flexbox & Alignment
7 | 'inline-flex',
8 | 'align-bottom',
9 |
10 | // Size
11 | 'w-4 h-4',
12 |
13 | // Misc
14 | 'cursor-default',
15 | 'select-none'
16 | ]
17 | },
18 | box: ({ props }) => ({
19 | class: [
20 | // Flexbox
21 | 'flex justify-center items-center',
22 |
23 | // Size
24 | 'w-4 h-4',
25 | 'text-sm',
26 | 'font-medium',
27 |
28 | // Shape
29 | 'border-2',
30 | 'rounded-full',
31 |
32 | // Transition
33 | 'transition duration-200 ease-in-out',
34 |
35 | // Colors
36 | {
37 | 'text-surface-700 dark:text-white/80': props.value !== props.modelValue && props.value !== undefined,
38 | 'bg-surface-0 dark:bg-surface-900': props.value !== props.modelValue && props.value !== undefined,
39 | 'border-surface-300 dark:border-surface-700': props.value !== props.modelValue && props.value !== undefined,
40 | 'border-primary-500 dark:border-primary-400': props.value == props.modelValue && props.value !== undefined
41 | },
42 |
43 | // States
44 | {
45 | 'outline-none outline-offset-0': !props.disabled,
46 | 'peer-focus-visible:ring-2 peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-surface-0 dark:focus-visible:ring-offset-surface-800 peer-focus-visible:ring-primary-500 dark:peer-focus-visible:ring-primary-400':
47 | !props.disabled,
48 | 'opacity-60 cursor-default': props.disabled
49 | }
50 | ]
51 | }),
52 | input: {
53 | class: [
54 | 'peer',
55 |
56 | // Size
57 | 'w-full ',
58 | 'h-full',
59 |
60 | // Position
61 | 'absolute',
62 | 'top-0 left-0',
63 | 'z-10',
64 |
65 | // Spacing
66 | 'p-0',
67 | 'm-0',
68 |
69 | // Shape
70 | 'opacity-0',
71 | 'rounded-md',
72 | 'outline-none',
73 | 'border-2 border-surface-300 dark:border-surface-700',
74 |
75 | // Misc
76 | 'appareance-none',
77 | 'cursor-default'
78 | ]
79 | },
80 | icon: {
81 | class: 'hidden'
82 | }
83 | };
84 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/message/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props }) => ({
3 | class: [
4 | // Spacing and Shape
5 | 'my-2 mx-0',
6 | 'rounded-md',
7 | 'ring-1 ring-inset ring-surface-200 dark:ring-surface-700 ring-offset-0',
8 |
9 | // Colors
10 | 'bg-surface-0 dark:bg-surface-800',
11 |
12 | {
13 | 'text-blue-500 dark:text-blue-300': props.severity == 'info',
14 | 'text-green-500 dark:text-green-300': props.severity == 'success',
15 | 'text-orange-500 dark:text-orange-300': props.severity == 'warn',
16 | 'text-red-500 dark:text-red-300': props.severity == 'error'
17 | }
18 | ]
19 | }),
20 | wrapper: {
21 | class: [
22 | // Flexbox
23 | 'flex items-center',
24 |
25 | // Spacing
26 | 'p-4'
27 | ]
28 | },
29 | icon: {
30 | class: [
31 | // Sizing and Spacing
32 | 'w-5 h-5',
33 | 'mr-3 shrink-0'
34 | ]
35 | },
36 | text: {
37 | class: [
38 | // Font and Text
39 | 'text-sm leading-none',
40 | 'font-medium'
41 | ]
42 | },
43 | button: ({ props }) => ({
44 | class: [
45 | // Flexbox
46 | 'flex items-center justify-center',
47 |
48 | // Size
49 | 'w-6 h-6',
50 |
51 | // Spacing and Misc
52 | 'ml-auto relative',
53 |
54 | // Shape
55 | 'rounded-full',
56 |
57 | // Colors
58 | 'bg-transparent',
59 | 'text-surface-700 dark:text-surface-0/80',
60 |
61 | // Transitions
62 | 'transition duration-200 ease-in-out',
63 |
64 | // States
65 | 'hover:bg-surface-100 dark:hover:bg-surface-700',
66 | 'outline-none focus:ring-1 focus:ring-inset',
67 | 'focus:ring-primary-500 dark:focus:ring-primary-400',
68 |
69 | // Misc
70 | 'overflow-hidden'
71 | ]
72 | }),
73 | closeicon: {
74 | class: [
75 | // Sizing and Spacing
76 | 'w-3 h-3',
77 | 'shrink-0'
78 | ]
79 | },
80 | transition: {
81 | enterFromClass: 'opacity-0',
82 | enterActiveClass: 'transition-opacity duration-300',
83 | leaveFromClass: 'max-h-40',
84 | leaveActiveClass: 'overflow-hidden transition-all duration-300 ease-in',
85 | leaveToClass: 'max-h-0 opacity-0 !m-0'
86 | }
87 | };
88 |
--------------------------------------------------------------------------------
/src/services/go-to-table.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { DevDbViewProvider } from "../devdb-view-provider";
3 | import { autoConnectProvider, getAvailableProviders, getConnectedDatabase } from "./messenger";
4 | import { DatabaseEngine } from '../types';
5 | import { logToOutput } from './output-service';
6 |
7 | export async function goToTable(devDbViewProvider: DevDbViewProvider | undefined) {
8 |
9 | logToOutput('Attempting to open table', 'Go to Table')
10 |
11 | if (!devDbViewProvider) {
12 | logToOutput('No DevDB view provider found. Please connect to a database first.', 'Go to Table')
13 | vscode.window.showErrorMessage('No DevDB view provider found. Please connect to a database first.');
14 | return;
15 | }
16 |
17 | const database = await getDatabase(devDbViewProvider)
18 |
19 | if (!database) {
20 | logToOutput('No database connection found. Please connect to one first.', 'Go to Table')
21 | vscode.window.showErrorMessage('No database connection found. Please connect to one first.');
22 | vscode.commands.executeCommand('devdb.focus');
23 | return;
24 | }
25 |
26 | const tables = await database.getTables();
27 |
28 | if (!tables || tables.length === 0) {
29 | logToOutput('No tables found in the currently connected database', 'Go to Table')
30 | vscode.window.showErrorMessage('No tables found in the currently connected database');
31 | return;
32 | }
33 |
34 | const selectedTable = await vscode.window.showQuickPick(tables, {
35 | placeHolder: 'Select a table to open',
36 | title: 'Go to Table'
37 | });
38 |
39 | if (selectedTable) {
40 | logToOutput(`Selected table: ${selectedTable}`, 'Go to Table')
41 | devDbViewProvider.setActiveTable(selectedTable);
42 | }
43 | }
44 |
45 | export async function getDatabase(devDbViewProvider: DevDbViewProvider): Promise {
46 | let database: DatabaseEngine | null = await getConnectedDatabase()
47 |
48 | if (database) {
49 | return database;
50 | }
51 |
52 | const nonDefaultProviders = (await getAvailableProviders()).filter((provider) => !provider.isDefault);
53 |
54 | if (nonDefaultProviders.length !== 1) {
55 | vscode.window.showErrorMessage(`This project has ${nonDefaultProviders.length} available database providers and we do not know which one to use. Please connect to one first.`);
56 | vscode.commands.executeCommand('devdb.focus');
57 | return null;
58 | }
59 |
60 | /**
61 | * This is just to ensure the UI is consistent with all the auto-connection
62 | * magic we are doing here in the backend
63 | */
64 | await autoConnectProvider(devDbViewProvider, nonDefaultProviders[0]);
65 |
66 | return await getConnectedDatabase()
67 | }
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/divider/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props }) => ({
3 | class: [
4 | // Flex and Position
5 | 'flex relative',
6 | { 'justify-center': props.layout == 'vertical' },
7 | { 'items-center': props.layout == 'vertical' },
8 | {
9 | 'justify-start': props?.align == 'left' && props.layout == 'horizontal',
10 | 'justify-center': props?.align == 'center' && props.layout == 'horizontal',
11 | 'justify-end': props?.align == 'right' && props.layout == 'horizontal',
12 | 'items-center': props?.align == 'top' && props.layout == 'vertical',
13 | 'items-start': props?.align == 'center' && props.layout == 'vertical',
14 | 'items-end': props?.align == 'bottom' && props.layout == 'vertical'
15 | },
16 |
17 | // Spacing
18 | {
19 | 'my-5 mx-0 py-0 px-5': props.layout == 'horizontal',
20 | 'mx-4 md:mx-5 py-5': props.layout == 'vertical'
21 | },
22 |
23 | // Size
24 | {
25 | 'w-full': props.layout == 'horizontal',
26 | 'min-h-full': props.layout == 'vertical'
27 | },
28 |
29 | // Before: Line
30 | 'before:block',
31 |
32 | // Position
33 | {
34 | 'before:absolute before:left-0 before:top-1/2': props.layout == 'horizontal',
35 | 'before:absolute before:left-1/2 before:top-0 before:transform before:-translate-x-1/2': props.layout == 'vertical'
36 | },
37 |
38 | // Size
39 | {
40 | 'before:w-full': props.layout == 'horizontal',
41 | 'before:min-h-full': props.layout == 'vertical'
42 | },
43 |
44 | // Shape
45 | {
46 | 'before:border-solid': props.type == 'solid',
47 | 'before:border-dotted': props.type == 'dotted',
48 | 'before:border-dashed': props.type == 'dashed'
49 | },
50 |
51 | // Color
52 | {
53 | 'before:border-t before:border-surface-200 before:dark:border-surface-600': props.layout == 'horizontal',
54 | 'before:border-l before:border-surface-200 before:dark:border-surface-600': props.layout == 'vertical'
55 | }
56 | ]
57 | }),
58 | content: {
59 | class: [
60 | // Space and Position
61 | 'p-2 z-10',
62 |
63 | // Color
64 | 'bg-surface-0 dark:bg-surface-800'
65 | ]
66 | }
67 | };
68 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/tabmenu/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: {
3 | class: 'overflow-x-auto'
4 | },
5 | menu: {
6 | class: [
7 | // Flexbox
8 | 'flex flex-1',
9 |
10 | // Spacing
11 | 'list-none',
12 | 'p-0 m-0',
13 |
14 | // Colors
15 | 'bg-surface-0 dark:bg-surface-800',
16 | 'border-b border-surface-200 dark:border-surface-700',
17 | 'text-surface-900 dark:text-surface-0/80'
18 | ]
19 | },
20 | menuitem: {
21 | class: 'mr-0'
22 | },
23 | action: ({ context, state }) => ({
24 | class: [
25 | 'relative',
26 |
27 | // Font
28 | 'font-medium',
29 | 'text-md',
30 |
31 | // Flexbox and Alignment
32 | 'flex items-center',
33 |
34 | // Spacing
35 | 'py-4 px-3',
36 | '-mb-[1px]',
37 |
38 | // Shape
39 | 'border-b-2',
40 | 'rounded-t-md',
41 |
42 | // Colors and Conditions
43 | {
44 | 'border-surface-200 dark:border-surface-700': state.d_activeIndex !== context.index,
45 | 'bg-surface-0 dark:bg-surface-800': state.d_activeIndex !== context.index,
46 | 'text-surface-700 dark:text-surface-0/80': state.d_activeIndex !== context.index,
47 |
48 | 'bg-surface-0 dark:bg-surface-800': state.d_activeIndex === context.index,
49 | 'border-primary-500 dark:border-primary-400': state.d_activeIndex === context.index,
50 | 'text-primary-500 dark:text-primary-400': state.d_activeIndex === context.index
51 | },
52 |
53 | // States
54 | 'focus-visible:outline-none focus-visible:outline-offset-0 focus-visible:ring-2 focus-visible:ring-inset',
55 | 'focus-visible:ring-primary-500 dark:focus-visible:ring-primary-400',
56 | {
57 | 'hover:bg-surface-0 dark:hover:bg-surface-800/80': state.d_activeIndex !== context.index,
58 | 'hover:border-surface-400 dark:hover:border-surface-600': state.d_activeIndex !== context.index,
59 | 'hover:text-surface-900 dark:hover:text-surface-0': state.d_activeIndex !== context.index
60 | },
61 |
62 | // Transitions
63 | 'transition-all duration-200',
64 |
65 | // Misc
66 | 'cursor-pointer select-none text-decoration-none',
67 | 'overflow-hidden',
68 | 'user-select-none',
69 | 'whitespace-nowrap'
70 | ]
71 | }),
72 | icon: {
73 | class: 'mr-2'
74 | }
75 | };
76 |
--------------------------------------------------------------------------------
/scripts/triage/postgres-triage.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Pull the latest PostgreSQL image
4 | # Use same as in test https://github.com/damms005/devdb-vscode/blob/eec97b76b630a2fc26b6bfaba17585a2c4dd1fd2/src/test/suite/engines/postgres.test.ts#L13
5 | docker pull postgres:13.3-alpine
6 |
7 | # Check if container exists
8 | if [ "$(docker ps -a --filter 'name=^/postgres-devdb-triage$' --format '{{.Names}}')" == "postgres-devdb-triage" ]; then
9 | echo "Container exists. Starting postgres-devdb-triage if not already running..."
10 | docker start postgres-devdb-triage
11 | else
12 | echo "Container does not exist. Creating a new postgres-devdb-triage container..."
13 | docker run --name postgres-devdb-triage -e POSTGRES_PASSWORD=mysecretpassword -p 3333:5432 -d postgres:13.3-alpine
14 | fi
15 |
16 | # Wait for the database to start
17 | echo "Waiting for PostgreSQL to start..."
18 | until docker exec postgres-devdb-triage pg_isready -U postgres; do
19 | sleep 1
20 | done
21 |
22 | # Create a sample database and table
23 | docker exec -i postgres-devdb-triage psql -U postgres << EOF
24 | CREATE DATABASE sample_db;
25 | \c sample_db
26 | CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
27 |
28 | CREATE TABLE book (
29 | id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
30 | title VARCHAR(255) NOT NULL,
31 | author VARCHAR(255) NOT NULL,
32 | published_date DATE,
33 | isbn VARCHAR(13),
34 | pages INT,
35 | available BOOLEAN DEFAULT TRUE,
36 | "user" UUID,
37 | "order" INT
38 | );
39 | INSERT INTO book (title, author, published_date, isbn, pages, available, "user", "order") VALUES
40 | ('The Great Gatsby', 'F. Scott Fitzgerald', '1925-04-10', '9780743273565', 180, TRUE, uuid_generate_v4(), 1),
41 | ('1984', 'George Orwell', '1949-06-08', '9780451524935', 328, FALSE, uuid_generate_v4(), 2),
42 | ('To Kill a Mockingbird', 'Harper Lee', '1960-07-11', '9780061120084', 281, TRUE, uuid_generate_v4(), 3),
43 | ('Pride and Prejudice', 'Jane Austen', '1813-01-28', '9781503290563', 279, TRUE, uuid_generate_v4(), 4),
44 | ('The Catcher in the Rye', 'J.D. Salinger', '1951-07-16', '9780316769488', 214, FALSE, uuid_generate_v4(), 5);
45 |
46 | EOF
47 |
48 | echo "PostgreSQL triage setup complete."
49 |
50 | echo "Example connection details:"
51 |
52 | cat << EXAMPLE_CONNECTION
53 | {
54 | "host" : "localhost",
55 | "port" : 3333,
56 | "username" : "postgres",
57 | "password" : "mysecretpassword",
58 | "database" : "sample_db"
59 | }
60 | EXAMPLE_CONNECTION
61 |
62 | echo "You can connect to it from inside the container like: 'psql sample_db postgres'"
63 | echo "To stop the PostgreSQL container, run the following command:"
64 | echo "docker stop postgres-devdb-triage && docker rm postgres-devdb-triage"
65 |
--------------------------------------------------------------------------------
/src/providers/postgres/rails-postgres-provider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import knex from 'knex';
3 | import { DatabaseEngine, DatabaseEngineProvider } from '../../types';
4 | import { PostgresEngine } from '../../database-engines/postgres-engine';
5 | import { isRailsProject, parseDatabaseYml, getRailsEnv } from '../../services/rails/rails-core';
6 | import { log } from '../../services/logging-service';
7 |
8 | export const RailsPostgresProvider: DatabaseEngineProvider = {
9 | name: 'Rails PostgreSQL',
10 | type: 'postgres',
11 | id: 'rails-postgres',
12 | description: 'Rails PostgreSQL with config/database.yml',
13 | engine: undefined,
14 |
15 | async canBeUsedInCurrentWorkspace(): Promise {
16 | if (!isRailsProject()) {
17 | log('Rails Postgres Provider', 'Not a Rails project');
18 | return false;
19 | }
20 |
21 | try {
22 | const config = await parseDatabaseYml(getRailsEnv());
23 | if (!config) {
24 | log('Rails Postgres Provider', 'Failed to parse database configuration using Rails runner');
25 | return false;
26 | }
27 |
28 | if (config.adapter !== 'postgres') {
29 | log('Rails Postgres Provider', `Database adapter is ${config.adapter}, not postgres`);
30 | return false;
31 | }
32 |
33 | const connection = knex({
34 | client: 'pg',
35 | connection: {
36 | host: config.host,
37 | port: config.port,
38 | user: config.username,
39 | password: config.password,
40 | database: config.database,
41 | pool: config.pool ? { min: 0, max: config.pool } : undefined,
42 | },
43 | acquireConnectionTimeout: config.timeout || 60000,
44 | });
45 |
46 | if (!connection) {
47 | log('Rails Postgres Provider', 'Failed to create Knex connection');
48 | return false;
49 | }
50 |
51 | this.engine = new PostgresEngine(connection);
52 | } catch (error) {
53 | const errorMessage = `Rails PostgreSQL connection error: ${String(error)}`;
54 | log('Rails Postgres Provider', errorMessage);
55 | vscode.window.showErrorMessage(errorMessage);
56 | return false;
57 | }
58 |
59 | try {
60 | const isOkay = await this.engine.isOkay();
61 | if (!isOkay) {
62 | log('Rails Postgres Provider', 'Database connection validation failed');
63 | }
64 | return isOkay;
65 | } catch (error) {
66 | const errorMessage = `Rails PostgreSQL connection validation error: ${String(error)}`;
67 | log('Rails Postgres Provider', errorMessage);
68 | vscode.window.showErrorMessage(errorMessage);
69 | return false;
70 | }
71 | },
72 |
73 | reconnect(): Promise {
74 | return this.canBeUsedInCurrentWorkspace();
75 | },
76 |
77 | async getDatabaseEngine(): Promise {
78 | return this.engine;
79 | }
80 | };
81 |
--------------------------------------------------------------------------------
/src/providers/mysql/adonis-mysql-provider.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as fs from 'fs';
3 | import { DatabaseEngine, DatabaseEngineProvider } from '../../types';
4 | import { log } from '../../services/logging-service';
5 | import { MysqlEngine } from '../../database-engines/mysql-engine';
6 | import { getConnectionInEnvFile } from '../../services/adonis/env-file-parser';
7 | import { isAdonisProject } from '../../services/adonis/adonis-core';
8 | import { logToOutput } from '../../services/output-service';
9 | import { getBasePath } from '../../services/workspace';
10 |
11 | export const AdonisMysqlProvider: DatabaseEngineProvider = {
12 | name: 'Adonis MySQL (Lucid ORM)',
13 | type: 'mysql',
14 | id: 'adonis-lucid-mysql',
15 | description: 'MySQL database for Adonis.js projects (Lucid ORM)',
16 | engine: undefined,
17 |
18 | async canBeUsedInCurrentWorkspace(): Promise {
19 | try {
20 | const workspaceRoot = getBasePath();
21 | if (!workspaceRoot) {
22 | log('Adonis MySQL', 'No workspace root found');
23 | return false;
24 | }
25 |
26 | const databaseTsPath = path.join(workspaceRoot, 'config', 'database.ts');
27 | const databaseJsPath = path.join(workspaceRoot, 'config', 'database.js');
28 |
29 | if (!isAdonisProject()) {
30 | logToOutput('Not an AdonisJS project', 'MySQL AdonisJS')
31 | return false;
32 | }
33 |
34 | const configPath = fs.existsSync(databaseTsPath) ? databaseTsPath : databaseJsPath;
35 | const configContent = fs.readFileSync(configPath, 'utf8');
36 |
37 | if (!configContent.includes('mysql') || configContent.includes('postgresql') || configContent.includes('postgres')) {
38 | log('Adonis MySQL', 'MySQL not configured in Adonis database config');
39 | return false;
40 | }
41 |
42 | const connection = await getConnectionInEnvFile('mysql', 'mysql2');
43 | if (!connection) {
44 | log('Adonis MySQL', 'Could not extract MySQL connection details from .env file');
45 | return false;
46 | }
47 |
48 | this.engine = new MysqlEngine(connection);
49 | return this.engine.isOkay();
50 | } catch (error) {
51 | log('Adonis postgres', 'Error checking if Adonis MySQL provider can be used', error);
52 | return false;
53 | }
54 | },
55 |
56 | reconnect(): Promise {
57 | return this.canBeUsedInCurrentWorkspace()
58 | },
59 |
60 | async getDatabaseEngine(): Promise {
61 | return this.engine
62 | }
63 | };
64 |
--------------------------------------------------------------------------------
/src/providers/postgres/django-postgres-provider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import knex from 'knex';
3 | import { DatabaseEngine, DatabaseEngineProvider } from '../../types';
4 | import { PostgresEngine } from '../../database-engines/postgres-engine';
5 | import { isDjangoProject, getDatabaseConfig, getDjangoDbAlias } from '../../services/django/django-core';
6 | import { log } from '../../services/logging-service';
7 |
8 | export const DjangoPostgresProvider: DatabaseEngineProvider = {
9 | name: 'Django PostgreSQL',
10 | type: 'postgres',
11 | id: 'django-postgres',
12 | description: 'Django PostgreSQL with settings.py',
13 | engine: undefined,
14 |
15 | async canBeUsedInCurrentWorkspace(): Promise {
16 | if (!isDjangoProject()) {
17 | log('Django Postgres Provider', 'Not a Django project');
18 | return false;
19 | }
20 |
21 | try {
22 | const config = await getDatabaseConfig(getDjangoDbAlias());
23 | if (!config) {
24 | log('Django Postgres Provider', 'Failed to parse database configuration using Django shell');
25 | return false;
26 | }
27 |
28 | if (config.adapter !== 'postgres') {
29 | log('Django Postgres Provider', `Database adapter is ${config.adapter}, not postgres`);
30 | return false;
31 | }
32 |
33 | const connection = knex({
34 | client: 'pg',
35 | connection: {
36 | host: config.host,
37 | port: config.port,
38 | user: config.username,
39 | password: config.password,
40 | database: config.database,
41 | pool: config.pool ? { min: 0, max: config.pool } : undefined,
42 | },
43 | acquireConnectionTimeout: config.timeout || 60000,
44 | });
45 |
46 | if (!connection) {
47 | log('Django Postgres Provider', 'Failed to create Knex connection');
48 | return false;
49 | }
50 |
51 | this.engine = new PostgresEngine(connection);
52 | } catch (error) {
53 | const errorMessage = `Django PostgreSQL connection error: ${String(error)}`;
54 | log('Django Postgres Provider', errorMessage);
55 | vscode.window.showErrorMessage(errorMessage);
56 | return false;
57 | }
58 |
59 | try {
60 | const isOkay = await this.engine.isOkay();
61 | if (!isOkay) {
62 | log('Django Postgres Provider', 'Database connection validation failed');
63 | }
64 | return isOkay;
65 | } catch (error) {
66 | const errorMessage = `Django PostgreSQL connection validation error: ${String(error)}`;
67 | log('Django Postgres Provider', errorMessage);
68 | vscode.window.showErrorMessage(errorMessage);
69 | return false;
70 | }
71 | },
72 |
73 | reconnect(): Promise {
74 | return this.canBeUsedInCurrentWorkspace();
75 | },
76 |
77 | async getDatabaseEngine(): Promise {
78 | return this.engine;
79 | }
80 | };
--------------------------------------------------------------------------------
/src/services/adonis/env-file-parser.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import knexlib from "knex";
3 | import { getEnvFileValue } from "./adonis-core";
4 | import { getConnectionFor } from "../connector";
5 | import { KnexClientType, LaravelConnection } from '../../types';
6 | import { log } from '../logging-service';
7 | import { reportError } from '../initialization-error-service';
8 |
9 | export async function getConnectionInEnvFile(connection: LaravelConnection, dialect: KnexClientType): Promise {
10 | log('Adonis env file parser', 'Fetching connection details from .env file. Laravel connection: ', connection);
11 | const host = await getHost();
12 | const username = await getEnvFileValue('DB_USER') || '';
13 | const password = await getEnvFileValue('DB_PASSWORD') || '';
14 | const database = await getEnvFileValue('DB_DATABASE');
15 | log('Adonis env file parser', `Laravel/${dialect} connection details: host=${host}, username=${username}, database=${database ? String(database[0]) + '*****' : ''}`);
16 |
17 | if (!database) {
18 | reportError('Adonis env file parser: missing database name in .env file')
19 | return
20 | }
21 |
22 | if (dialect !== 'mysql2' && dialect !== 'postgres') {
23 | vscode.window.showErrorMessage(`No support for '${dialect}' in Laravel Sail yet`)
24 |
25 | log('Adonis env file parser', `Error connecting using host configured in .env file. Conn:`, connection);
26 | return;
27 | }
28 |
29 | let portOrConnection = await getPort();
30 |
31 | if (!database || !portOrConnection) {
32 | log('Adonis env file parser', `Missing database or port: database=${database ? String(database[0]) + '*****' : ''}, port=${portOrConnection}`);
33 | return;
34 | }
35 |
36 | log('Adonis env file parser', `Laravel/${dialect} connection details:`, host, portOrConnection, username, database);
37 | if (typeof portOrConnection === 'object') {
38 | return portOrConnection
39 | }
40 |
41 | try {
42 | return await connectUsingHostConfiguredInEnvFile(dialect, host, portOrConnection, username, password, database)
43 | } catch (error) {
44 | return
45 | }
46 | }
47 |
48 | async function connectUsingHostConfiguredInEnvFile(dialect: KnexClientType, host: string, port: number, username: string, password: string, database: string): Promise {
49 | return await getConnectionFor('Adonis env file parser', dialect, host, port, username, password, database)
50 | }
51 |
52 | async function getHost() {
53 | const localhost = '127.0.0.1'
54 |
55 | return (await getEnvFileValue('DB_HOST')) || localhost
56 | }
57 |
58 | async function getPort(): Promise {
59 | const portInEnvFile = await getEnvFileValue('DB_PORT')
60 | return parseInt(portInEnvFile || '3306')
61 | }
62 |
--------------------------------------------------------------------------------
/src/services/mcp/no-vscode/port-manager.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import logger from "./logger"
3 | import { MCP_CONFIG_DIR, MCP_CONFIG_FILE } from './config';
4 |
5 | interface McpConfig {
6 | [workspaceId: string]: number;
7 | }
8 |
9 | function ensureConfigDir(): void {
10 | if (!fs.existsSync(MCP_CONFIG_DIR)) {
11 | fs.mkdirSync(MCP_CONFIG_DIR, { recursive: true });
12 | }
13 | }
14 |
15 | function readMcpConfig(): McpConfig {
16 | try {
17 | if (fs.existsSync(MCP_CONFIG_FILE)) {
18 | const content = fs.readFileSync(MCP_CONFIG_FILE, 'utf8');
19 | return JSON.parse(content);
20 | }
21 | } catch (error) {
22 | logger.error('Failed to read MCP config file:', error);
23 | }
24 | return {};
25 | }
26 |
27 | function writeMcpConfig(config: McpConfig): void {
28 | try {
29 | ensureConfigDir();
30 | fs.writeFileSync(MCP_CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
31 | } catch (error) {
32 | logger.error('Failed to write MCP config file:', error);
33 | throw error;
34 | }
35 | }
36 |
37 | export function savePort(port: number, workspaceId: string): void {
38 | try {
39 | const config = readMcpConfig();
40 |
41 | for (const existingWorkspaceId in config) {
42 | if (config[existingWorkspaceId] === port) {
43 | delete config[existingWorkspaceId];
44 | logger.info(`DevDB: Removed port ${port} from workspace ${existingWorkspaceId}`);
45 | }
46 | }
47 |
48 | config[workspaceId] = port;
49 | writeMcpConfig(config);
50 | } catch (error) {
51 | logger.error('Failed to save port to config:', error);
52 | throw error;
53 | }
54 | }
55 |
56 | export function getPort(): number | null {
57 | try {
58 | if (!process.env.WORKSPACE_ID) {
59 | throw new Error('WORKSPACE_ID environment variable is required for MCP server process');
60 | }
61 |
62 | const workspaceId = process.env.WORKSPACE_ID;
63 | const config = readMcpConfig();
64 | const port = config[workspaceId];
65 |
66 | if (port) {
67 | logger.info(`DevDB: Read port ${port} for workspace ${workspaceId} from ${MCP_CONFIG_FILE}`);
68 | return port;
69 | } else {
70 | logger.info(`DevDB: No port found for workspace ${workspaceId} in ${MCP_CONFIG_FILE}`);
71 | return null;
72 | }
73 | } catch (error) {
74 | logger.error('Failed to read port from config:', error);
75 | return null;
76 | }
77 | }
78 |
79 | export function clearPort(workspaceId: string): void {
80 | try {
81 | const config = readMcpConfig();
82 | if (config[workspaceId]) {
83 | delete config[workspaceId];
84 | writeMcpConfig(config);
85 | logger.info(`DevDB: Cleared port for workspace ${workspaceId}`);
86 | }
87 | } catch (error) {
88 | logger.error('Failed to clear port from config:', error);
89 | }
90 | }
91 |
92 | export function getConfigDir() {
93 | return MCP_CONFIG_DIR;
94 | }
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/inputswitch/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props }) => ({
3 | class: [
4 | // Alignments
5 | 'inline-flex relative',
6 | 'shrink-0',
7 |
8 | // Shape
9 | 'rounded-2xl',
10 |
11 | // Size
12 | 'h-5 w-9',
13 |
14 | // States
15 | {
16 | 'opacity-60 select-none pointer-events-none cursor-default': props.disabled
17 | }
18 | ]
19 | }),
20 | slider: ({ props }) => ({
21 | class: [
22 | // Position
23 | 'absolute top-0 left-0 right-0 bottom-0',
24 |
25 | // Shape
26 | 'rounded-2xl',
27 |
28 | // Before:
29 | 'before:absolute before:top-1/2',
30 | 'before:-mt-2',
31 | 'before:h-4 before:w-4',
32 | 'before:rounded-full',
33 | 'before:duration-200 before:transition before:ease-in-out',
34 | 'before:bg-surface-0 before:dark:bg-surface-900',
35 | 'before:shadow',
36 | { 'before:transform before:translate-x-4': props.modelValue == props.trueValue },
37 |
38 | // Colors
39 | 'border-2 border-transparent',
40 | {
41 | 'bg-surface-200 dark:bg-surface-700': !(props.modelValue == props.trueValue),
42 | 'bg-primary-500 dark:bg-primary-400': props.modelValue == props.trueValue
43 | },
44 |
45 | // States
46 | { 'peer-hover:bg-surface-300 dark:peer-hover:bg-surface-600 ': !(props.modelValue == props.trueValue) && !props.disabled },
47 | { 'peer-hover:bg-primary-600 dark:peer-hover:bg-surface-300 ': (props.modelValue == props.trueValue) && !props.disabled },
48 | 'peer-focus-visible:ring-2 peer-focus-visible:ring-primary-500 dark:peer-focus-visible:ring-primary-400',
49 |
50 | // Transition
51 | 'transition-colors duration-200',
52 |
53 | // Misc
54 | 'cursor-pointer'
55 | ]
56 | }),
57 | input: {
58 | class: [
59 | 'peer',
60 |
61 | // Size
62 | 'w-full ',
63 | 'h-full',
64 |
65 | // Position
66 | 'absolute',
67 | 'top-0 left-0',
68 | 'z-10',
69 |
70 | // Spacing
71 | 'p-0',
72 | 'm-0',
73 |
74 | // Shape
75 | 'rounded',
76 | 'border',
77 |
78 | // Shape
79 | 'opacity-0',
80 | 'rounded-md',
81 | 'outline-none',
82 | 'border-2 border-surface-300 dark:border-surface-700',
83 |
84 | // Misc
85 | 'appareance-none'
86 | ]
87 | }
88 | };
89 |
--------------------------------------------------------------------------------
/src/test/suite/services/string.test.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 | import { extractVariables, replaceVariables } from '../../../services/string';
3 |
4 | describe('Variable Replacement', () => {
5 | describe('extractVariables', () => {
6 | it('should extract single object property variable', () => {
7 | const text = 'SessionInfo::where("program_id", $program->id)';
8 | const variables = extractVariables(text);
9 | assert.deepStrictEqual(variables, ['$program->id']);
10 | });
11 |
12 | it('should extract multiple object property variables', () => {
13 | const text = 'SessionInfo::where("program_id", $program->id)->where("type", $program->type->name)';
14 | const variables = extractVariables(text);
15 | assert.deepStrictEqual(variables, ['$program->id', '$program->type->name']);
16 | });
17 |
18 | it('should extract nested object property variables', () => {
19 | const text = 'SessionInfo::where("type_id", $program->type->another->thing_id)';
20 | const variables = extractVariables(text);
21 | assert.deepStrictEqual(variables, ['$program->type->another->thing_id']);
22 | });
23 | });
24 |
25 | describe('replaceVariables', () => {
26 | it('should replace single object property variable', () => {
27 | const text = 'SessionInfo::where("program_id", $program->id)';
28 | const variableValues = {
29 | '$program->id': '123'
30 | };
31 | const result = replaceVariables(text, variableValues);
32 | assert.deepStrictEqual(result, 'SessionInfo::where("program_id", 123)');
33 | });
34 |
35 | it('should replace multiple object property variables', () => {
36 | const text = 'SessionInfo::where("program_id", $program->id)->where("type", $program->type->name)';
37 | const variableValues = {
38 | '$program->id': '123',
39 | '$program->type->name': '"active"'
40 | };
41 | const result = replaceVariables(text, variableValues);
42 | assert.deepStrictEqual(result, 'SessionInfo::where("program_id", 123)->where("type", "active")');
43 | });
44 |
45 | it('should replace nested object property variables', () => {
46 | const text = 'SessionInfo::where("type_id", $program->type->another->thing_id)';
47 | const variableValues = {
48 | '$program->type->another->thing_id': '456'
49 | };
50 | const result = replaceVariables(text, variableValues);
51 | assert.deepStrictEqual(result, 'SessionInfo::where("type_id", 456)');
52 | });
53 |
54 | it('should handle multiple occurrences of same variable', () => {
55 | const text = 'SessionInfo::where("program_id", $program->id)->orWhere("backup_id", $program->id)';
56 | const variableValues = {
57 | '$program->id': '123'
58 | };
59 | const result = replaceVariables(text, variableValues);
60 | assert.deepStrictEqual(result, 'SessionInfo::where("program_id", 123)->orWhere("backup_id", 123)');
61 | });
62 | });
63 | });
--------------------------------------------------------------------------------
/src/providers/postgres/adonis-postgres-provider.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as fs from 'fs';
3 | import { DatabaseEngine, DatabaseEngineProvider } from '../../types';
4 | import { log } from '../../services/logging-service';
5 | import { PostgresEngine } from '../../database-engines/postgres-engine';
6 | import { getConnectionInEnvFile } from '../../services/adonis/env-file-parser';
7 | import { isAdonisProject } from '../../services/adonis/adonis-core';
8 | import { logToOutput } from '../../services/output-service';
9 | import { getBasePath } from '../../services/workspace';
10 |
11 | export const AdonisPostgresProvider: DatabaseEngineProvider = {
12 | name: 'Adonis PostgreSQL (Lucid ORM)',
13 | type: 'postgres',
14 | id: 'adonis-lucid-postgres',
15 | description: 'PostgreSQL database for Adonis.js projects (Lucid ORM)',
16 | engine: undefined,
17 |
18 | async canBeUsedInCurrentWorkspace(): Promise {
19 | try {
20 | const workspaceRoot = getBasePath();
21 | if (!workspaceRoot) {
22 | log('Adonis PostgreSQL', 'No workspace root found');
23 | return false;
24 | }
25 |
26 | const databaseTsPath = path.join(workspaceRoot, 'config', 'database.ts');
27 | const databaseJsPath = path.join(workspaceRoot, 'config', 'database.js');
28 |
29 | if (!isAdonisProject()) {
30 | logToOutput('Not an AdonisJS project', 'Postgres AdonisJS')
31 | return false;
32 | }
33 |
34 | const configPath = fs.existsSync(databaseTsPath) ? databaseTsPath : databaseJsPath;
35 | const configContent = fs.readFileSync(configPath, 'utf8');
36 |
37 | // Look for PostgreSQL configuration in the Adonis database config
38 | const isPgConfigured = configContent.includes('postgres') ||
39 | configContent.includes('pg') ||
40 | configContent.includes('postgresql');
41 |
42 | if (!isPgConfigured) {
43 | return false;
44 | }
45 |
46 | // Extract connection details from .env file
47 | const connection = await getConnectionInEnvFile('pgsql', 'postgres');
48 | if (!connection) {
49 | return false;
50 | }
51 |
52 | this.engine = new PostgresEngine(connection);
53 |
54 | return (await this.engine.isOkay());
55 | } catch (error) {
56 | log('Adonis PostgreSQL', 'Could not check if Adonis PostgreSQL provider can be used:' + String(error));
57 | return false;
58 | }
59 | },
60 |
61 | reconnect(): Promise {
62 | return this.canBeUsedInCurrentWorkspace()
63 | },
64 |
65 | async getDatabaseEngine(): Promise {
66 | return this.engine
67 | }
68 | };
69 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: Bug report
2 | description: Report a problem you're experiencing
3 | labels: bug,unconfirmed,low priority
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | Thank you for taking the time to file a bug report. Before opening a bug report, please search the existing issues (both open and closed).
9 | To address this bug, please provide these information:
10 | - type: input
11 | id: extension-version
12 | attributes:
13 | label: Extension Version
14 | description: Please provide the full version of your installed DevDb.
15 | placeholder: v1.0.0
16 | validations:
17 | required: true
18 | - type: input
19 | id: os-version
20 | attributes:
21 | label: OS and Version
22 | description: Please provide your OS and version.
23 | placeholder: Ubuntu v23.10
24 | validations:
25 | required: true
26 | - type: input
27 | id: vs-code-version
28 | attributes:
29 | label: VS Code Version
30 | description: Please provide the full version of your VS Code.
31 | placeholder: v1.85.1
32 | validations:
33 | required: true
34 | - type: input
35 | id: database-and-version
36 | attributes:
37 | label: Database name and Version
38 | description: Please provide your database name and version. e.g. for MySQL, output of `mysql --version`.
39 | placeholder: mysql Ver 8.0.35 for Linux on x86_64 (MySQL Community Server - GPL)
40 | validations:
41 | required: true
42 | - type: textarea
43 | id: reproduction-steps
44 | attributes:
45 | label: Steps to reproduce
46 | description: Which steps do we need to take to reproduce the problem? Any code examples need to be **as short as possible**, remove any code that is unrelated to the bug. **This issue will be automatically closed and not reviewed if detailed replication steps are missing.**
47 | placeholder: |
48 | Steps to reproduce the behavior:
49 | 1. Launch VS Code
50 | 2. Open a '...'
51 | 3. Click on '....'
52 | 4. Scroll to '....'
53 | 5. See ... error
54 | validations:
55 | required: true
56 | - type: textarea
57 | id: expectation
58 | attributes:
59 | label: Expected behavior
60 | description: What did you expect to happen instead?
61 | placeholder: A clear and concise description of what you expected to happen.
62 | validations:
63 | required: true
64 | - type: textarea
65 | id: screenshots
66 | attributes:
67 | label: Screenshots
68 | description: Please add screen recording or screenshots to help explain your problem.
69 | validations:
70 | required: true
71 | - type: textarea
72 | id: additional-context
73 | attributes:
74 | label: Additional context
75 | description: Add any other context about the problem here.
76 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/checkbox/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: {
3 | class: [
4 | 'relative',
5 |
6 | // Alignment
7 | 'inline-flex',
8 | 'align-bottom',
9 |
10 | // Size
11 | 'w-4',
12 | 'h-4',
13 |
14 | // Misc
15 | 'cursor-default',
16 | 'select-none'
17 | ]
18 | },
19 | box: ({ props, context }) => ({
20 | class: [
21 | // Alignment
22 | 'flex',
23 | 'items-center',
24 | 'justify-center',
25 |
26 | // Size
27 | 'w-4',
28 | 'h-4',
29 |
30 | // Shape
31 | 'rounded',
32 | 'border',
33 |
34 | // Colors
35 | 'text-surface-600',
36 | {
37 | 'border-surface-300 bg-surface-0 dark:border-surface-700 dark:bg-surface-900': !context.checked,
38 | 'border-primary-500 bg-primary-500 dark:border-primary-400 dark:bg-primary-400': context.checked
39 | },
40 |
41 | {
42 | 'ring-2 ring-primary-500 dark:ring-primary-400': !props.disabled && context.focused,
43 | 'cursor-default opacity-60': props.disabled
44 | },
45 |
46 | // States
47 | {
48 | 'peer-focus-visible:ring-2 peer-focus-visible:ring-primary-500 dark:peer-focus-visible:ring-primary-400': !props.disabled,
49 | 'cursor-default opacity-60': props.disabled
50 | },
51 |
52 | // Transitions
53 | 'transition-colors',
54 | 'duration-200'
55 | ]
56 | }),
57 | input: {
58 | class: [
59 | 'peer',
60 |
61 | // Size
62 | 'w-full ',
63 | 'h-full',
64 |
65 | // Position
66 | 'absolute',
67 | 'top-0 left-0',
68 | 'z-10',
69 |
70 | // Spacing
71 | 'p-0',
72 | 'm-0',
73 |
74 | // Shape
75 | 'rounded',
76 | 'border',
77 |
78 | // Shape
79 | 'opacity-0',
80 | 'rounded-md',
81 | 'outline-none',
82 | 'border-2 border-surface-300 dark:border-surface-700',
83 |
84 | // Misc
85 | 'appareance-none'
86 | ]
87 | },
88 | icon: {
89 | class: [
90 | // Font
91 | 'text-normal',
92 |
93 | // Size
94 | 'w-3',
95 | 'h-3',
96 |
97 | // Colors
98 | 'text-white dark:text-surface-900',
99 |
100 | // Transitions
101 | 'transition-all',
102 | 'duration-200'
103 | ]
104 | }
105 | };
106 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/fieldset/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: {
3 | class: [
4 | 'block',
5 |
6 | // Spacing
7 | 'px-5 md:px-6 py-5',
8 |
9 | // Shape
10 | 'rounded-md rounded-lg',
11 |
12 | // Color
13 | 'bg-surface-0 dark:bg-surface-900',
14 | 'text-surface-700 dark:text-surface-0/80',
15 | 'ring-1 ring-inset ring-surface-300 dark:ring-surface-700 ring-offset-0'
16 | ]
17 | },
18 | legend: ({ props }) => ({
19 | class: [
20 | // Font
21 | 'font-medium',
22 | 'leading-none',
23 |
24 | //Spacing
25 | { 'p-0': props.toggleable, 'px-3 py-1.5': !props.toggleable },
26 |
27 | // Shape
28 | 'rounded-md',
29 |
30 | // Color
31 | 'text-surface-700 dark:text-surface-0/80',
32 |
33 | 'bg-surface-0 dark:bg-surface-900',
34 |
35 | // Transition
36 | 'transition-none',
37 |
38 | // States
39 | { '': props.toggleable },
40 | { 'focus:outline-none focus:outline-offset-0 focus:ring-2 focus:ring-primary-600 ring-inset dark:focus:ring-primary-500': props.toggleable }
41 | ]
42 | }),
43 | toggler: ({ props }) => ({
44 | class: [
45 | // Alignments
46 | 'flex items-center justify-center',
47 | 'relative',
48 |
49 | //Spacing
50 | { 'px-3 py-1.5': props.toggleable },
51 |
52 | // Shape
53 | { 'rounded-md': props.toggleable },
54 |
55 | // Color
56 | { 'text-surface-700 dark:text-surface-200 hover:text-surface-900 hover:text-surface-900': props.toggleable },
57 |
58 | // States
59 | { 'hover:text-surface-900 dark:hover:text-surface-100': props.toggleable },
60 | { 'focus:outline-none focus:outline-offset-0 focus:ring-2 focus:ring-inset focus:ring-primary-600 dark:focus:ring-primary-500': props.toggleable },
61 |
62 | // Misc
63 | {
64 | 'transition-none cursor-pointer overflow-hidden select-none': props.toggleable
65 | }
66 | ]
67 | }),
68 | togglerIcon: {
69 | class: 'mr-2 inline-block'
70 | },
71 | legendTitle: {
72 | class: 'flex items-center justify-center leading-none'
73 | },
74 | content: {
75 | class: 'p-0'
76 | },
77 | transition: {
78 | enterFromClass: 'max-h-0',
79 | enterActiveClass: 'overflow-hidden transition-[max-height] duration-1000 ease-[cubic-bezier(0.42,0,0.58,1)]',
80 | enterToClass: 'max-h-[1000px]',
81 | leaveFromClass: 'max-h-[1000px]',
82 | leaveActiveClass: 'overflow-hidden transition-[max-height] duration-[450ms] ease-[cubic-bezier(0,1,0,1)]',
83 | leaveToClass: 'max-h-0'
84 | }
85 | };
86 |
--------------------------------------------------------------------------------
/src/test/suite/services/qualifier-service.test.ts:
--------------------------------------------------------------------------------
1 | import { strict as assert } from 'node:assert';
2 | import { extractUseStatements, getAst, isNamespaced } from '../../../services/laravel/code-runner/qualifier-service';
3 |
4 | describe('extractUseStatements', () => {
5 | it('should extract plain use statements', () => {
6 | const code = `
7 | {
19 | const code = `
20 | {
31 | const code = `
32 | {
43 | const code = `
44 | {
57 | it('should return true for code with a namespace', () => {
58 | const code = `
59 | {
70 | const code = `
71 | {
81 | const code = `
82 | ({
6 | class: [
7 | // Alignments
8 | 'flex items-center justify-between',
9 |
10 | // Colors
11 | 'text-surface-700 dark:text-surface-0/80',
12 | 'bg-surface-0 dark:bg-surface-900',
13 | 'border-b border-surface-200 dark:border-surface-800',
14 |
15 | //Shape
16 | 'rounded-tl-lg rounded-tr-lg',
17 |
18 | // Conditional Spacing
19 | { 'px-5 md:px-6 py-5': !props.toggleable, 'py-3 px-5 md:px-6': props.toggleable }
20 | ]
21 | }),
22 | title: {
23 | class: 'leading-none font-medium'
24 | },
25 | toggler: {
26 | class: [
27 | // Alignments
28 | 'inline-flex items-center justify-center',
29 |
30 | // Sized
31 | 'w-8 h-8',
32 |
33 | //Shape
34 | 'border-0 rounded-full',
35 |
36 | //Color
37 | 'bg-transparent',
38 | 'text-surface-600 dark:text-surface-100/80',
39 |
40 | // States
41 | 'hover:text-surface-900 dark:hover:text-surface-0/80',
42 | 'hover:bg-surface-50 dark:hover:bg-surface-800/50',
43 | 'focus:outline-none focus:outline-offset-0 focus-visible:ring-2 focus-visible:ring-primary-600 focus-visible:ring-inset dark:focus-visible:ring-primary-500',
44 |
45 | // Transitions
46 | 'transition duration-200 ease-in-out',
47 |
48 | // Misc
49 | 'overflow-hidden relative no-underline'
50 | ]
51 | },
52 | togglerIcon: {
53 | class: 'inline-block'
54 | },
55 | content: {
56 | class: [
57 | // Spacing
58 | 'py-6 px-5 md:px-6',
59 |
60 | // Shape
61 | 'last:rounded-br-lg last:rounded-bl-lg',
62 |
63 | //Color
64 | 'bg-surface-0 dark:bg-surface-900',
65 | 'text-surface-700 dark:text-surface-0/80'
66 | ]
67 | },
68 | footer: {
69 | class: [
70 | // Spacing
71 | 'py-6 px-5 md:px-6',
72 |
73 | //Shape
74 | 'rounded-bl-lg rounded-br-lg',
75 |
76 | // Color
77 | 'bg-surface-0 dark:bg-surface-900',
78 | 'text-surface-600 dark:text-surface-0/70',
79 | 'border-t border-surface-200 dark:border-surface-800'
80 | ]
81 | },
82 | transition: {
83 | enterFromClass: 'max-h-0',
84 | enterActiveClass: 'overflow-hidden transition-[max-height] duration-1000 ease-[cubic-bezier(0.42,0,0.58,1)]',
85 | enterToClass: 'max-h-[1000px]',
86 | leaveFromClass: 'max-h-[1000px]',
87 | leaveActiveClass: 'overflow-hidden transition-[max-height] duration-[450ms] ease-[cubic-bezier(0,1,0,1)]',
88 | leaveToClass: 'max-h-0'
89 | }
90 | };
91 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/dock/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props }) => ({
3 | class: [
4 | // Positioning
5 | 'absolute z-1',
6 | {
7 | 'left-0 bottom-0 w-full': props.position == 'bottom',
8 | 'left-0 top-0 w-full': props.position == 'top',
9 | 'left-0 top-0 h-full': props.position == 'left',
10 | 'right-0 top-0 h-full': props.position == 'right'
11 | },
12 |
13 | // Flexbox & Alignment
14 | 'flex justify-center items-center',
15 |
16 | // Interactivity
17 | 'pointer-events-none'
18 | ]
19 | }),
20 | container: {
21 | class: [
22 | // Flexbox
23 | 'flex',
24 |
25 | // Shape & Border
26 | 'rounded-md',
27 |
28 | // Color
29 | 'bg-surface-0/10 dark:bg-surface-900/20 border border-surface-0/20',
30 | 'backdrop-blur-sm',
31 |
32 | // Spacing
33 | 'p-2',
34 |
35 | // Misc
36 | 'pointer-events-auto'
37 | ]
38 | },
39 | menu: ({ props }) => ({
40 | class: [
41 | // Flexbox & Alignment
42 | 'flex items-center justify-center',
43 | {
44 | 'flex-col': props.position == 'left' || props.position == 'right'
45 | },
46 |
47 | // List Style
48 | 'm-0 p-0 list-none',
49 |
50 | // Shape
51 | 'outline-none'
52 | ]
53 | }),
54 | menuitem: ({ props, context, instance }) => ({
55 | class: [
56 | // Spacing & Shape
57 | 'p-2 rounded-md',
58 |
59 | // Conditional Scaling
60 | {
61 | 'hover:scale-150': instance.currentIndex === context.index,
62 | 'scale-125': instance.currentIndex - 1 === context.index || instance.currentIndex + 1 === context.index,
63 | 'scale-110': instance.currentIndex - 2 === context.index || instance.currentIndex + 2 === context.index
64 | },
65 |
66 | // Positioning & Hover States
67 | {
68 | 'origin-bottom hover:mx-6': props.position == 'bottom',
69 | 'origin-top hover:mx-6': props.position == 'top',
70 | 'origin-left hover:my-6': props.position == 'left',
71 | 'origin-right hover:my-6': props.position == 'right'
72 | },
73 |
74 | // Transitions & Transform
75 | 'transition-all duration-200 ease-cubic-bezier-will-change-transform transform'
76 | ]
77 | }),
78 | action: {
79 | class: [
80 | // Flexbox & Alignment
81 | 'flex flex-col items-center justify-center',
82 |
83 | // Position
84 | 'relative',
85 |
86 | // Size
87 | 'w-16 h-16',
88 |
89 | // Misc
90 | 'cursor-default overflow-hidden'
91 | ]
92 | }
93 | };
94 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/chips/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props }) => ({
3 | class: [
4 | 'flex',
5 | {
6 | 'opacity-60 select-none pointer-events-none cursor-default': props.disabled
7 | }
8 | ]
9 | }),
10 | container: ({ state }) => ({
11 | class: [
12 | // Font
13 | 'font-sans sm:text-sm leading-none',
14 |
15 | // Flex
16 | 'flex items-center flex-wrap gap-1',
17 |
18 | // Spacing
19 | 'm-0 py-1 px-3',
20 |
21 | // Size
22 | 'w-full',
23 |
24 | // Shape
25 | 'list-none',
26 | 'rounded-md',
27 |
28 | // Color
29 | 'text-surface-900 dark:text-surface-0',
30 | 'bg-surface-0 dark:bg-surface-900',
31 | 'placeholder:text-surface-400 dark:placeholder:text-surface-500',
32 | 'shadow-sm',
33 |
34 | // States
35 | { 'ring-1 ring-inset ring-surface-300 dark:ring-surface-700 ring-offset-0': !state.focused, 'ring-2 ring-primary-500 dark:ring-primary-400': state.focused },
36 |
37 | // Transition
38 | 'transition-colors duration-200',
39 |
40 | // Misc
41 | 'cursor-text overflow-hidden',
42 | 'appearance-none'
43 | ]
44 | }),
45 |
46 | inputtoken: {
47 | class: ['py-0.5 px-0', 'inline-flex flex-auto']
48 | },
49 | input: {
50 | class: [
51 | // Font
52 | 'font-sans sm:text-sm leading-none',
53 |
54 | // Size
55 | 'w-full',
56 |
57 | // Spacing
58 | 'p-0 m-0',
59 |
60 | // Shape
61 | 'appearance-none rounded-none',
62 | 'border-0 outline-none',
63 |
64 | // Color
65 | 'text-surface-700 dark:text-white/80',
66 | 'bg-transparent',
67 | 'placeholder:text-surface-400 dark:placeholder:text-surface-500'
68 | ]
69 | },
70 | token: {
71 | class: [
72 | // Flexbox
73 | 'inline-flex items-center',
74 |
75 | // Spacing
76 | 'py-0.5 px-3',
77 |
78 | // Shape
79 | 'rounded-[1.14rem]',
80 |
81 | // Colors
82 | 'text-surface-700 dark:text-white/70',
83 | 'bg-surface-200 dark:bg-surface-700'
84 | ]
85 | },
86 | label: {
87 | class: 'leading-5'
88 | },
89 | removeTokenIcon: {
90 | class: [
91 | // Shape
92 | 'rounded-md leading-6',
93 |
94 | // Spacing
95 | 'ml-2',
96 |
97 | // Size
98 | 'w-4 h-4',
99 |
100 | // Transition
101 | 'transition duration-200 ease-in-out',
102 |
103 | // Misc
104 | 'cursor-pointer'
105 | ]
106 | }
107 | };
108 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/tristatecheckbox/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: {
3 | class: ['cursor-pointer inline-flex relative select-none align-bottom', 'w-4 h-4']
4 | },
5 | box: ({ props, context }) => ({
6 | class: [
7 | // Alignment
8 | 'flex',
9 | 'items-center',
10 | 'justify-center',
11 |
12 | // Size
13 | 'w-4',
14 | 'h-4',
15 |
16 | // Shape
17 | 'rounded',
18 | 'border',
19 |
20 | // Colors
21 | 'text-surface-600',
22 | {
23 | 'border-surface-300 bg-surface-0 dark:border-surface-700 dark:bg-surface-900': !context.active,
24 | 'border-primary-500 bg-primary-500 dark:border-primary-400 dark:bg-primary-400': context.active
25 | },
26 |
27 | {
28 | 'ring-2 ring-primary-500 dark:ring-primary-400': !props.disabled && context.focused,
29 | 'cursor-default opacity-60': props.disabled
30 | },
31 |
32 | // States
33 | {
34 | 'peer-focus-visible:ring-2 peer-focus-visible:ring-primary-500 dark:peer-focus-visible:ring-primary-400': !props.disabled,
35 | 'cursor-default opacity-60': props.disabled
36 | },
37 |
38 | // Transitions
39 | 'transition-colors',
40 | 'duration-200'
41 | ]
42 | }),
43 | input: {
44 | class: [
45 | 'peer',
46 |
47 | // Size
48 | 'w-full ',
49 | 'h-full',
50 |
51 | // Position
52 | 'absolute',
53 | 'top-0 left-0',
54 | 'z-10',
55 |
56 | // Spacing
57 | 'p-0',
58 | 'm-0',
59 |
60 | // Shape
61 | 'rounded',
62 | 'border',
63 |
64 | // Shape
65 | 'opacity-0',
66 | 'rounded-md',
67 | 'outline-none',
68 | 'border-2 border-surface-300 dark:border-surface-700',
69 |
70 | // Misc
71 | 'appareance-none'
72 | ]
73 | },
74 | checkicon: {
75 | class: [
76 | // Font
77 | 'text-normal',
78 |
79 | // Size
80 | 'w-3',
81 | 'h-3',
82 |
83 | // Colors
84 | 'text-white dark:text-surface-900',
85 |
86 | // Transitions
87 | 'transition-all',
88 | 'duration-200'
89 | ]
90 | },
91 | uncheckicon: {
92 | class: [
93 | // Font
94 | 'text-normal',
95 |
96 | // Size
97 | 'w-3',
98 | 'h-3',
99 |
100 | // Colors
101 | 'text-white dark:text-surface-900',
102 |
103 | // Transitions
104 | 'transition-all',
105 | 'duration-200'
106 | ]
107 | }
108 | };
109 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/toast/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: ({ props }) => ({
3 | class: [
4 | //Size and Shape
5 | 'w-96 rounded-md',
6 |
7 | // Positioning
8 | { '-translate-x-2/4': props.position == 'top-center' || props.position == 'bottom-center' }
9 | ]
10 | }),
11 | container: ({ props }) => ({
12 | class: [
13 | 'my-4 rounded-md w-full',
14 |
15 | 'shadow-lg',
16 | 'bg-surface-0 dark:bg-surface-800',
17 | 'ring-1 ring-inset ring-surface-200 dark:ring-surface-700 ring-offset-0',
18 | // Colors
19 | {
20 | 'text-blue-500 dark:text-blue-300': props.message.severity == 'info',
21 | 'text-green-500 dark:text-green-300': props.message.severity == 'success',
22 | 'text-orange-500 dark:text-orange-300': props.message.severity == 'warn',
23 | 'text-red-500 dark:text-red-300': props.message.severity == 'error'
24 | }
25 | ]
26 | }),
27 | content: {
28 | class: 'flex items-start p-4'
29 | },
30 | icon: {
31 | class: [
32 | // Sizing and Spacing
33 | 'w-5 h-5',
34 | 'mr-2 shrink-0'
35 | ]
36 | },
37 | text: {
38 | class: [
39 | // Font and Text
40 | 'text-sm leading-none',
41 | 'ml-2',
42 | 'flex-1'
43 | ]
44 | },
45 | summary: {
46 | class: 'font-medium block'
47 | },
48 | detail: {
49 | class: 'mt-1.5 block text-surface-600 dark:text-surface-0/70'
50 | },
51 | closebutton: {
52 | class: [
53 | // Flexbox
54 | 'flex items-center justify-center',
55 |
56 | // Size
57 | 'w-6 h-6',
58 |
59 | // Spacing and Misc
60 | 'ml-auto relative',
61 |
62 | // Shape
63 | 'rounded-full',
64 |
65 | // Colors
66 | 'bg-transparent',
67 | 'text-surface-700 dark:text-surface-0/80',
68 |
69 | // Transitions
70 | 'transition duration-200 ease-in-out',
71 |
72 | // States
73 | 'hover:bg-surface-100 dark:hover:bg-surface-700',
74 | 'outline-none focus:ring-1 focus:ring-inset',
75 | 'focus:ring-primary-500 dark:focus:ring-primary-400',
76 |
77 | // Misc
78 | 'overflow-hidden'
79 | ]
80 | },
81 | closeicon: {
82 | class: [
83 | // Sizing and Spacing
84 | 'w-3 h-3',
85 | 'shrink-0'
86 | ]
87 | },
88 | transition: {
89 | enterFromClass: 'opacity-0 translate-y-2/4',
90 | enterActiveClass: 'transition-[transform,opacity] duration-300',
91 | leaveFromClass: 'max-h-[1000px]',
92 | leaveActiveClass: '!transition-[max-height_.45s_cubic-bezier(0,1,0,1),opacity_.3s,margin-bottom_.3s] overflow-hidden',
93 | leaveToClass: 'max-h-0 opacity-0 mb-0'
94 | }
95 | };
96 |
--------------------------------------------------------------------------------
/src/test/suite/services/pagination.test.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 | import { getPaginationFor } from "../../../services/pagination";
3 |
4 | describe('Pagination Tests', () => {
5 | it('should handle the first page', () => {
6 | const result = getPaginationFor('test', 1, 100, 10);
7 | assert.strictEqual(result.prevPage, undefined);
8 | assert.strictEqual(result.nextPage, 2);
9 | assert.strictEqual(result.endPage, 10);
10 | assert.strictEqual(result.displayText, 'Showing 1 to 10 of 100 records');
11 | });
12 |
13 | it('should handle a middle page', () => {
14 | const result = getPaginationFor('test', 5, 100, 10);
15 | assert.strictEqual(result.prevPage, 4);
16 | assert.strictEqual(result.nextPage, 6);
17 | assert.strictEqual(result.endPage, 10);
18 | assert.strictEqual(result.displayText, 'Showing 41 to 50 of 100 records');
19 | });
20 |
21 | it('should handle the last page', () => {
22 | const result = getPaginationFor('test', 10, 100, 10);
23 | assert.strictEqual(result.prevPage, 9);
24 | assert.strictEqual(result.nextPage, undefined);
25 | assert.strictEqual(result.endPage, 10);
26 | assert.strictEqual(result.displayText, 'Showing 91 to 100 of 100 records');
27 | });
28 |
29 | it('should handle zero total items', () => {
30 | const result = getPaginationFor('test', 1, 0, 10);
31 | assert.strictEqual(result.prevPage, undefined);
32 | assert.strictEqual(result.nextPage, undefined);
33 | assert.strictEqual(result.endPage, 1);
34 | assert.strictEqual(result.displayText, 'Showing 0 to 0 of 0 records');
35 | });
36 |
37 | it('should handle a single item', () => {
38 | const result = getPaginationFor('test', 1, 1, 10);
39 | assert.strictEqual(result.prevPage, undefined);
40 | assert.strictEqual(result.nextPage, undefined);
41 | assert.strictEqual(result.endPage, 1);
42 | assert.strictEqual(result.displayText, 'Showing 1 to 1 of 1 records');
43 | });
44 |
45 | it('should handle a single page with less items than per page limit', () => {
46 | const result = getPaginationFor('test', 1, 8, 10);
47 | assert.strictEqual(result.prevPage, undefined);
48 | assert.strictEqual(result.nextPage, undefined);
49 | assert.strictEqual(result.endPage, 1);
50 | assert.strictEqual(result.displayText, 'Showing 1 to 8 of 8 records');
51 | });
52 |
53 | it('should handle the second page when total items are exactly one page plus one', () => {
54 | const result = getPaginationFor('test', 2, 11, 10);
55 | assert.strictEqual(result.prevPage, 1);
56 | assert.strictEqual(result.nextPage, undefined);
57 | assert.strictEqual(result.endPage, 2);
58 | assert.strictEqual(result.displayText, 'Showing 11 to 11 of 11 records');
59 | });
60 |
61 | it('should handle a large number of total items', () => {
62 | const result = getPaginationFor('test', 50, 1000000, 20);
63 | assert.strictEqual(result.prevPage, 49);
64 | assert.strictEqual(result.nextPage, 51);
65 | assert.strictEqual(result.endPage, 50000);
66 | assert.strictEqual(result.displayText, 'Showing 981 to 1,000 of 1,000,000 records');
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/menu/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: {
3 | class: [
4 | // Sizing and Shape
5 | 'min-w-[12rem]',
6 | 'rounded-md',
7 | // Spacing
8 | 'p-1.5',
9 | // Colors
10 | 'bg-surface-0 dark:bg-surface-700',
11 | 'text-surface-700 dark:text-white/80',
12 | 'ring-1 ring-surface-200 dark:ring-surface-700'
13 | ]
14 | },
15 | menu: {
16 | class: [
17 | // Spacings and Shape
18 | 'list-none',
19 | 'm-0',
20 | 'p-0',
21 | 'outline-none'
22 | ]
23 | },
24 | menuitem: {
25 | class: [
26 | // Space
27 | 'first:mt-0 mt-1'
28 | ]
29 | },
30 | content: ({ context }) => ({
31 | class: [
32 | //Shape
33 | 'rounded-md',
34 |
35 | // Colors
36 | {
37 | 'text-surface-700 dark:text-surface-0': !context.focused,
38 | 'bg-surface-100 text-primary-500 dark:bg-surface-300/10 dark:text-primary-400': context.focused
39 | },
40 |
41 | // Transitions
42 | 'transition-shadow',
43 | 'duration-200',
44 |
45 | // States
46 | 'hover:text-primary-600 dark:hover:text-primary-400',
47 | 'hover:bg-surface-100 dark:hover:bg-surface-400/10'
48 | ]
49 | }),
50 | action: {
51 | class: [
52 | 'relative',
53 |
54 | // Font
55 | 'font-semibold',
56 |
57 | // Flexbox
58 | 'flex',
59 | 'items-center',
60 |
61 | // Spacing
62 | 'py-2',
63 | 'px-3',
64 |
65 | // Misc
66 | 'no-underline',
67 | 'overflow-hidden',
68 | 'cursor-pointer',
69 | 'select-none'
70 | ]
71 | },
72 | icon: {
73 | class: [
74 | // Spacing
75 | 'mr-2',
76 | 'leading-6',
77 | 'text-sm'
78 | ]
79 | },
80 | label: {
81 | class: ['leading-6', 'text-sm']
82 | },
83 | submenuheader: {
84 | class: [
85 | // Font
86 | 'font-semibold',
87 | 'text-xs leading-6',
88 |
89 | // Spacing
90 | 'm-0 ',
91 | 'py-1',
92 | 'px-3',
93 |
94 | // Shape
95 | 'rounded-tl-none',
96 | 'rounded-tr-none',
97 |
98 | // Colors
99 | 'bg-surface-0 dark:bg-surface-700',
100 | 'text-surface-600 dark:text-surface-0/60'
101 | ]
102 | },
103 | transition: {
104 | enterFromClass: 'opacity-0 scale-y-[0.8]',
105 | enterActiveClass: 'transition-[transform,opacity] duration-[120ms] ease-[cubic-bezier(0,0,0.2,1)]',
106 | leaveActiveClass: 'transition-opacity duration-100 ease-linear',
107 | leaveToClass: 'opacity-0'
108 | }
109 | };
110 |
--------------------------------------------------------------------------------
/src/providers/mysql/rails-mysql-provider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { DatabaseEngine, DatabaseEngineProvider } from '../../types';
3 | import { MysqlEngine } from '../../database-engines/mysql-engine';
4 | import { isRailsProject, getRailsEnv, parseDatabaseYml } from '../../services/rails/rails-core';
5 | import knex from 'knex';
6 | import { log } from '../../services/logging-service';
7 |
8 | export const RailsMysqlProvider: DatabaseEngineProvider = {
9 | name: 'Rails MySQL',
10 | type: 'mysql',
11 | id: 'rails-mysql',
12 | description: 'Rails MySQL with config/database.yml',
13 | engine: undefined,
14 |
15 | async canBeUsedInCurrentWorkspace(): Promise {
16 | if (!isRailsProject()) {
17 | return false;
18 | }
19 |
20 | log('Rails MySQL', 'Checking if Rails MySQL provider can be used in the current workspace...');
21 |
22 | const railsEnv = getRailsEnv();
23 | const config = await parseDatabaseYml(railsEnv);
24 |
25 | if (!config) {
26 | log('Rails MySQL', 'Failed to parse database.yml configuration using Rails runner');
27 | vscode.window.showErrorMessage('Failed to parse Rails database configuration. Please ensure Rails is properly installed and the database.yml file is valid.');
28 | return false;
29 | }
30 |
31 | if (config.adapter !== 'mysql2') {
32 | log('Rails MySQL', `Database adapter is ${config.adapter}, not mysql2`);
33 | return false;
34 | }
35 |
36 | try {
37 | log('Rails MySQL', 'Creating Knex connection with Rails configuration...');
38 | const connection = knex({
39 | client: 'mysql2',
40 | connection: {
41 | host: config.host,
42 | port: config.port,
43 | user: config.username,
44 | password: config.password,
45 | database: config.database,
46 | pool: config.pool ? { min: 0, max: config.pool } : undefined
47 | },
48 | acquireConnectionTimeout: config.timeout || 60000
49 | });
50 |
51 | this.engine = new MysqlEngine(connection);
52 | log('Rails MySQL', 'MySQL engine created successfully');
53 | } catch (error) {
54 | const errorMessage = `MySQL connection error: ${String(error)}`;
55 | vscode.window.showErrorMessage(errorMessage);
56 | log('Rails MySQL', errorMessage);
57 | return false;
58 | }
59 |
60 | try {
61 | const isOkay = await this.engine.isOkay();
62 | if (!isOkay) {
63 | log('Rails MySQL', 'MySQL connection validation failed');
64 | vscode.window.showErrorMessage('Failed to connect to MySQL database. Please check your Rails database configuration.');
65 | return false;
66 | }
67 | log('Rails MySQL', 'MySQL connection validated successfully');
68 | return true;
69 | } catch (error) {
70 | const errorMessage = `MySQL connection validation error: ${String(error)}`;
71 | log('Rails MySQL', errorMessage);
72 | vscode.window.showErrorMessage('Failed to validate MySQL connection. Please check your database server and configuration.');
73 | return false;
74 | }
75 | },
76 |
77 | reconnect(): Promise {
78 | return this.canBeUsedInCurrentWorkspace();
79 | },
80 |
81 | async getDatabaseEngine(): Promise {
82 | return this.engine;
83 | }
84 | };
85 |
--------------------------------------------------------------------------------
/src/services/adonis/adonis-core.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as fs from 'fs';
3 | import { log } from '../../services/logging-service';
4 | import { getBasePath, getWorkspaceFileContent } from "../workspace";
5 |
6 | /**
7 | * Returns the hostname portion of the APP_URL environment variable.
8 | */
9 | export async function getHostname(): Promise {
10 | const appUrl = await getEnvFileValue('APP_URL')
11 | if (!appUrl) return
12 |
13 | const appUrlWithoutQuotes = appUrl.replace(/"/g, '')
14 | const appUrlWithoutTrailingSlash = appUrlWithoutQuotes.endsWith('/')
15 | ? appUrlWithoutQuotes.substring(0, appUrlWithoutQuotes.length - 1)
16 | : appUrlWithoutQuotes
17 |
18 | const appUrlWithoutProtocol = appUrlWithoutTrailingSlash.replace(/https?:\/\//, '')
19 | const appUrlWithoutPort = appUrlWithoutProtocol.replace(/:\d+/, '')
20 |
21 | return appUrlWithoutPort
22 | }
23 |
24 | export async function getEnvFileValue(envFileKey: string): Promise {
25 | const envFileContents = getWorkspaceFileContent('.env')?.toString()
26 | if (!envFileContents) return
27 |
28 | const lines = envFileContents.split('\n');
29 | const appUrlLine = lines.find(line => line.startsWith(`${envFileKey}=`))
30 | if (!appUrlLine) return
31 |
32 | const appUrl = appUrlLine.substring(appUrlLine.indexOf('=') + 1)
33 | const appUrlWithoutQuotes = appUrl.replace(/"/g, '')
34 | const appUrlWithoutTrailingSlash = appUrlWithoutQuotes.endsWith('/')
35 | ? appUrlWithoutQuotes.substring(0, appUrlWithoutQuotes.length - 1)
36 | : appUrlWithoutQuotes
37 |
38 | const appUrlWithoutProtocol = appUrlWithoutTrailingSlash.replace(/https?:\/\//, '')
39 | const appUrlWithoutPort = appUrlWithoutProtocol.replace(/:\d+/, '')
40 |
41 | return appUrlWithoutPort.trim()
42 | }
43 |
44 | export function isAdonisProject() {
45 | const workspaceRoot = getBasePath();
46 | if (!workspaceRoot) {
47 | log('Adonis PostgreSQL', 'No workspace root found');
48 | return false;
49 | }
50 |
51 | // Check if this is an Adonis project by looking for config/database.ts or config/database.js
52 | const databaseTsPath = path.join(workspaceRoot, 'config', 'database.ts');
53 | const databaseJsPath = path.join(workspaceRoot, 'config', 'database.js');
54 |
55 | if (!fs.existsSync(databaseTsPath) && !fs.existsSync(databaseJsPath)) {
56 | log('Adonis PostgreSQL', 'No Adonis database config file found');
57 | return false;
58 | }
59 |
60 | // Check package.json for Adonis dependencies
61 | const packageJsonPath = path.join(workspaceRoot, 'package.json');
62 | if (!fs.existsSync(packageJsonPath)) {
63 | log('Adonis PostgreSQL', 'No package.json found');
64 | return false;
65 | }
66 |
67 | const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
68 | const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
69 |
70 | const hasAdonisDependency = Object.keys(dependencies).some(dep =>
71 | dep === '@adonisjs/core' || dep === '@adonisjs/lucid' || dep.startsWith('@adonisjs/')
72 | );
73 |
74 | if (!hasAdonisDependency) {
75 | log('Adonis PostgreSQL', 'No Adonis dependencies found in package.json');
76 | return false;
77 | }
78 |
79 | return true
80 | }
--------------------------------------------------------------------------------
/src/providers/mysql/django-mysql-provider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { DatabaseEngine, DatabaseEngineProvider } from '../../types';
3 | import { MysqlEngine } from '../../database-engines/mysql-engine';
4 | import { isDjangoProject, getDatabaseConfig, getDjangoDbAlias } from '../../services/django/django-core';
5 | import knex from 'knex';
6 | import { log } from '../../services/logging-service';
7 |
8 | export const DjangoMysqlProvider: DatabaseEngineProvider = {
9 | name: 'Django MySQL',
10 | type: 'mysql',
11 | id: 'django-mysql',
12 | description: 'Django MySQL with settings.py',
13 | engine: undefined,
14 |
15 | async canBeUsedInCurrentWorkspace(): Promise {
16 | if (!isDjangoProject()) {
17 | return false;
18 | }
19 |
20 | log('Django MySQL', 'Checking if Django MySQL provider can be used in the current workspace...');
21 |
22 | const djangoDbAlias = getDjangoDbAlias();
23 | const config = await getDatabaseConfig(djangoDbAlias);
24 |
25 | if (!config) {
26 | log('Django MySQL', 'Failed to parse Django database configuration using Django shell');
27 | vscode.window.showErrorMessage('Failed to parse Django database configuration. Please ensure Django is properly installed and the settings.py file is valid.');
28 | return false;
29 | }
30 |
31 | if (config.adapter !== 'mysql') {
32 | log('Django MySQL', `Database adapter is ${config.adapter}, not mysql`);
33 | return false;
34 | }
35 |
36 | try {
37 | log('Django MySQL', 'Creating Knex connection with Django configuration...');
38 | const connection = knex({
39 | client: 'mysql2',
40 | connection: {
41 | host: config.host,
42 | port: config.port,
43 | user: config.username,
44 | password: config.password,
45 | database: config.database,
46 | pool: config.pool ? { min: 0, max: config.pool } : undefined
47 | },
48 | acquireConnectionTimeout: config.timeout || 60000
49 | });
50 |
51 | this.engine = new MysqlEngine(connection);
52 | log('Django MySQL', 'MySQL engine created successfully');
53 | } catch (error) {
54 | const errorMessage = `MySQL connection error: ${String(error)}`;
55 | vscode.window.showErrorMessage(errorMessage);
56 | log('Django MySQL', errorMessage);
57 | return false;
58 | }
59 |
60 | try {
61 | const isOkay = await this.engine.isOkay();
62 | if (!isOkay) {
63 | log('Django MySQL', 'MySQL connection validation failed');
64 | vscode.window.showErrorMessage('Failed to connect to MySQL database. Please check your Django database configuration.');
65 | return false;
66 | }
67 | log('Django MySQL', 'MySQL connection validated successfully');
68 | return true;
69 | } catch (error) {
70 | const errorMessage = `MySQL connection validation error: ${String(error)}`;
71 | log('Django MySQL', errorMessage);
72 | vscode.window.showErrorMessage('Failed to validate MySQL connection. Please check your database server and configuration.');
73 | return false;
74 | }
75 | },
76 |
77 | reconnect(): Promise {
78 | return this.canBeUsedInCurrentWorkspace();
79 | },
80 |
81 | async getDatabaseEngine(): Promise {
82 | return this.engine;
83 | }
84 | };
--------------------------------------------------------------------------------
/src/services/html.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'path';
2 | import * as vscode from 'vscode';
3 |
4 | export const FRONTEND_FOLDER_NAME = 'ui-shell'
5 |
6 | export type VueAssets = {
7 | jsFile: string;
8 | cssFile: string;
9 | };
10 |
11 | /**
12 | * Gets the html for the webview
13 | */
14 | export function getWebviewHtml(webview: vscode.Webview, jsFile: string, cssFile: string, _extensionUri: vscode.Uri) {
15 |
16 | // Get the local path to main script run in the webview, then convert it to a uri we can use in the webview.
17 | const vueAppScriptUri = webview.asWebviewUri(vscode.Uri.joinPath(_extensionUri, FRONTEND_FOLDER_NAME, 'dist', 'assets', jsFile));
18 |
19 | // Do the same for the stylesheet.
20 | const styleVueAppUri = webview.asWebviewUri(vscode.Uri.joinPath(_extensionUri, FRONTEND_FOLDER_NAME, 'dist', 'assets', cssFile));
21 |
22 | // Use nonce to allow specific scripts to be run.
23 | const nonce1 = getNonce();
24 | const nonce2 = getNonce();
25 |
26 | /**
27 | * Tailwindcss uses svg loaded from data:image..., at least for checkboxes.
28 | */
29 | const tailwindcss = 'data:'
30 |
31 | return `
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | `;
45 | }
46 |
47 | /**
48 | * Gets the compiled Vue assets from the Vue project output folder
49 | */
50 | export async function getVueAssets(context: vscode.ExtensionContext): Promise {
51 | const allFiles = await vscode.workspace.fs.readDirectory(vscode.Uri.file(context.extensionPath));
52 |
53 | return new Promise(async (resolve, reject) => {
54 | const uiFolder = allFiles.find((item) => item[0] === FRONTEND_FOLDER_NAME && item[1] === vscode.FileType.Directory);
55 |
56 | if (uiFolder) {
57 | const projectFolder = join(context.extensionPath, FRONTEND_FOLDER_NAME, 'dist', 'assets')
58 | const uiFiles: [string, vscode.FileType][] = await vscode.workspace.fs.readDirectory(vscode.Uri.file(projectFolder));
59 | const jsFile = uiFiles.find((item) => item[1] === vscode.FileType.File && item[0].endsWith('.js'));
60 | const cssFile = uiFiles.find((item) => item[1] === vscode.FileType.File && item[0].endsWith('.css'));
61 | if (!jsFile || !cssFile) return
62 |
63 | resolve({
64 | jsFile: jsFile[0],
65 | cssFile: cssFile[0]
66 | })
67 | }
68 |
69 | reject('Could not find UI assets');
70 | })
71 | }
72 |
73 | /**
74 | * Generates a random nonce for webview Content Security Policy
75 | */
76 | function getNonce() {
77 | let text = '';
78 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
79 | for (let i = 0; i < 32; i++) {
80 | text += possible.charAt(Math.floor(Math.random() * possible.length));
81 | }
82 | return text;
83 | }
--------------------------------------------------------------------------------
/src/providers/sqlite/rails-sqlite-provider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { DatabaseEngine, DatabaseEngineProvider } from '../../types';
3 | import { SqliteEngine } from '../../database-engines/sqlite-engine';
4 | import { isRailsProject, parseDatabaseYml, getRailsEnv } from '../../services/rails/rails-core';
5 | import { getBasePath } from '../../services/workspace';
6 | import { log } from '../../services/logging-service';
7 | import * as path from 'path';
8 |
9 | export const RailsSqliteProvider: DatabaseEngineProvider = {
10 | name: 'Rails SQLite',
11 | type: 'sqlite',
12 | id: 'rails-sqlite',
13 | description: 'Rails SQLite with config/database.yml',
14 | engine: undefined,
15 |
16 | async canBeUsedInCurrentWorkspace(): Promise {
17 | try {
18 | log('Rails SQLite Provider', 'Checking if Rails SQLite provider can be used');
19 |
20 | if (!isRailsProject()) {
21 | log('Rails SQLite Provider', 'Not a Rails project');
22 | return false;
23 | }
24 |
25 | const railsEnv = getRailsEnv();
26 | log('Rails SQLite Provider', `Using Rails environment: ${railsEnv}`);
27 |
28 | const config = await parseDatabaseYml(railsEnv);
29 |
30 | if (!config) {
31 | log('Rails SQLite Provider', 'Failed to parse database configuration using Rails runner');
32 | vscode.window.showErrorMessage('Rails SQLite: Could not parse database configuration. Please ensure Rails is properly installed and the database.yml file is valid.');
33 | return false;
34 | }
35 |
36 | if (config.adapter !== 'sqlite') {
37 | log('Rails SQLite Provider', `Database adapter is ${config.adapter}, not sqlite`);
38 | return false;
39 | }
40 |
41 | if (!config.database) {
42 | log('Rails SQLite Provider', 'No database path specified in configuration');
43 | vscode.window.showErrorMessage('Rails SQLite: No database path specified in config/database.yml');
44 | return false;
45 | }
46 |
47 | let databasePath = config.database;
48 | const workspaceRoot = getBasePath();
49 |
50 | if (workspaceRoot && !path.isAbsolute(databasePath)) {
51 | databasePath = path.resolve(workspaceRoot, databasePath);
52 | log('Rails SQLite Provider', `Resolved relative database path to: ${databasePath}`);
53 | }
54 |
55 | try {
56 | this.engine = new SqliteEngine(databasePath);
57 | } catch (error) {
58 | log('Rails SQLite Provider', `Failed to create SQLite engine: ${String(error)}`);
59 | vscode.window.showErrorMessage(`Rails SQLite file error ${databasePath}: ${String(error)}`);
60 | return false;
61 | }
62 |
63 | const isOkay = await this.engine.isOkay();
64 | log('Rails SQLite Provider', `Database connection test result: ${isOkay}`);
65 |
66 | if (!isOkay) {
67 | vscode.window.showErrorMessage(`Rails SQLite: Database file is not accessible or corrupted: ${databasePath}`);
68 | }
69 |
70 | return isOkay;
71 | } catch (error) {
72 | log('Rails SQLite Provider', `Unexpected error in canBeUsedInCurrentWorkspace: ${String(error)}`);
73 | vscode.window.showErrorMessage(`Rails SQLite: Unexpected error while checking database configuration: ${String(error)}`);
74 | return false;
75 | }
76 | },
77 |
78 | reconnect(): Promise {
79 | return this.canBeUsedInCurrentWorkspace();
80 | },
81 |
82 | async getDatabaseEngine(): Promise {
83 | return this.engine;
84 | }
85 | };
86 |
--------------------------------------------------------------------------------
/ui-shell/src/assets/tailwind-primevue-preset/tieredmenu/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | root: {
3 | class: [
4 | // Shape
5 | 'rounded-md',
6 |
7 | // Size
8 | 'min-w-[12rem]',
9 | 'p-1.5',
10 |
11 | // Colors
12 | 'bg-surface-0 dark:bg-surface-700',
13 | 'ring-1 ring-surface-200 dark:ring-surface-700'
14 | ]
15 | },
16 | menu: {
17 | class: [
18 | // Spacings and Shape
19 | 'list-none',
20 | 'm-0',
21 | 'p-0',
22 | 'outline-none'
23 | ]
24 | },
25 | menuitem: {
26 | class: ['relative first:mt-0 mt-1']
27 | },
28 | content: ({ context }) => ({
29 | class: [
30 | //Shape
31 | 'rounded-md',
32 |
33 | // Colors
34 | {
35 | 'text-surface-500 dark:text-white/70': !context.focused && !context.active,
36 | 'text-surface-500 dark:text-white/70 bg-surface-200 dark:bg-black/70': context.focused && !context.active,
37 | 'text-surface-900 dark:text-surface-0/80 bg-surface-50 dark:bg-black/70': context.focused && context.active,
38 | 'text-surface-900 dark:text-surface-0/80 bg-surface-50 dark:bg-black/70': !context.focused && context.active
39 | },
40 |
41 | // Hover States
42 | {
43 | 'hover:bg-surface-50 dark:hover:bg-surface-800': !context.active,
44 | 'hover:bg-surface-100 dark:hover:bg-black/40 text-surface-900 dark:text-surface-0/80': context.active
45 | },
46 |
47 | // Transitions
48 | 'transition-shadow',
49 | 'duration-200'
50 | ]
51 | }),
52 | action: {
53 | class: [
54 | 'relative',
55 |
56 | // Font
57 | 'font-semibold',
58 |
59 | // Flexbox
60 | 'flex',
61 | 'items-center',
62 |
63 | // Spacing
64 | 'py-2',
65 | 'px-3',
66 |
67 | // Misc
68 | 'no-underline',
69 | 'overflow-hidden',
70 | 'cursor-pointer',
71 | 'select-none'
72 | ]
73 | },
74 | icon: {
75 | class: [
76 | // Spacing
77 | 'mr-2',
78 | 'leading-6',
79 | 'text-sm'
80 | ]
81 | },
82 | label: {
83 | class: ['leading-none', 'text-sm']
84 | },
85 | submenuicon: {
86 | class: [
87 | // Position
88 | 'ml-auto'
89 | ]
90 | },
91 | submenu: {
92 | class: [
93 | // Size
94 | 'w-full sm:w-48',
95 |
96 | // Spacing
97 | 'p-1.5',
98 | 'm-0 mx-1.5',
99 | 'list-none',
100 |
101 | // Shape
102 | 'shadow-none sm:shadow-md',
103 | 'border-0',
104 |
105 | // Position
106 | 'static sm:absolute',
107 | 'z-10',
108 |
109 | // Color
110 | 'bg-surface-0 dark:bg-surface-700'
111 | ]
112 | },
113 | separator: {
114 | class: 'border-t border-surface-200 dark:border-surface-600 my-1'
115 | }
116 | };
117 |
--------------------------------------------------------------------------------