refactor: Move Astro project to root directory
- Move all Astro files from dealplustech-astro/ to root - Archive Next.js code in _nextjs-backup/ - Update .gitignore for Astro project - Simplify project structure This completes the migration from Next.js to Astro. The Astro project is now at the root level.
This commit is contained in:
@@ -1,168 +0,0 @@
|
||||
{
|
||||
"$ref": "#/definitions/products",
|
||||
"definitions": {
|
||||
"products": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"nameEn": {
|
||||
"type": "string"
|
||||
},
|
||||
"slug": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"shortDescription": {
|
||||
"type": "string"
|
||||
},
|
||||
"keywords": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"seoContent": {
|
||||
"type": "string"
|
||||
},
|
||||
"image": {
|
||||
"type": "string"
|
||||
},
|
||||
"specifications": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"unit": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"label",
|
||||
"value"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"applications": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"certifications": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"productTables": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tableName": {
|
||||
"type": "string"
|
||||
},
|
||||
"headers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"rows": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"tableName",
|
||||
"headers",
|
||||
"rows"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"faq": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"question": {
|
||||
"type": "string"
|
||||
},
|
||||
"answer": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"question",
|
||||
"answer"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"relatedProductIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"schemaData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"brand": {
|
||||
"type": "string"
|
||||
},
|
||||
"manufacturer": {
|
||||
"type": "string"
|
||||
},
|
||||
"material": {
|
||||
"type": "string"
|
||||
},
|
||||
"category": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"$schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"nameEn",
|
||||
"slug",
|
||||
"description",
|
||||
"image"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export default new Map();
|
||||
@@ -1 +0,0 @@
|
||||
export default new Map();
|
||||
215
dealplustech-astro/.astro/content.d.ts
vendored
215
dealplustech-astro/.astro/content.d.ts
vendored
@@ -1,215 +0,0 @@
|
||||
declare module 'astro:content' {
|
||||
export interface RenderResult {
|
||||
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
|
||||
headings: import('astro').MarkdownHeading[];
|
||||
remarkPluginFrontmatter: Record<string, any>;
|
||||
}
|
||||
interface Render {
|
||||
'.md': Promise<RenderResult>;
|
||||
}
|
||||
|
||||
export interface RenderedContent {
|
||||
html: string;
|
||||
metadata?: {
|
||||
imagePaths: Array<string>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'astro:content' {
|
||||
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
|
||||
|
||||
export type CollectionKey = keyof AnyEntryMap;
|
||||
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
|
||||
|
||||
export type ContentCollectionKey = keyof ContentEntryMap;
|
||||
export type DataCollectionKey = keyof DataEntryMap;
|
||||
|
||||
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
|
||||
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
|
||||
ContentEntryMap[C]
|
||||
>['slug'];
|
||||
|
||||
export type ReferenceDataEntry<
|
||||
C extends CollectionKey,
|
||||
E extends keyof DataEntryMap[C] = string,
|
||||
> = {
|
||||
collection: C;
|
||||
id: E;
|
||||
};
|
||||
export type ReferenceContentEntry<
|
||||
C extends keyof ContentEntryMap,
|
||||
E extends ValidContentEntrySlug<C> | (string & {}) = string,
|
||||
> = {
|
||||
collection: C;
|
||||
slug: E;
|
||||
};
|
||||
export type ReferenceLiveEntry<C extends keyof LiveContentConfig['collections']> = {
|
||||
collection: C;
|
||||
id: string;
|
||||
};
|
||||
|
||||
/** @deprecated Use `getEntry` instead. */
|
||||
export function getEntryBySlug<
|
||||
C extends keyof ContentEntryMap,
|
||||
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||
>(
|
||||
collection: C,
|
||||
// Note that this has to accept a regular string too, for SSR
|
||||
entrySlug: E,
|
||||
): E extends ValidContentEntrySlug<C>
|
||||
? Promise<CollectionEntry<C>>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
|
||||
/** @deprecated Use `getEntry` instead. */
|
||||
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
|
||||
collection: C,
|
||||
entryId: E,
|
||||
): Promise<CollectionEntry<C>>;
|
||||
|
||||
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
|
||||
collection: C,
|
||||
filter?: (entry: CollectionEntry<C>) => entry is E,
|
||||
): Promise<E[]>;
|
||||
export function getCollection<C extends keyof AnyEntryMap>(
|
||||
collection: C,
|
||||
filter?: (entry: CollectionEntry<C>) => unknown,
|
||||
): Promise<CollectionEntry<C>[]>;
|
||||
|
||||
export function getLiveCollection<C extends keyof LiveContentConfig['collections']>(
|
||||
collection: C,
|
||||
filter?: LiveLoaderCollectionFilterType<C>,
|
||||
): Promise<
|
||||
import('astro').LiveDataCollectionResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>
|
||||
>;
|
||||
|
||||
export function getEntry<
|
||||
C extends keyof ContentEntryMap,
|
||||
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||
>(
|
||||
entry: ReferenceContentEntry<C, E>,
|
||||
): E extends ValidContentEntrySlug<C>
|
||||
? Promise<CollectionEntry<C>>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
export function getEntry<
|
||||
C extends keyof DataEntryMap,
|
||||
E extends keyof DataEntryMap[C] | (string & {}),
|
||||
>(
|
||||
entry: ReferenceDataEntry<C, E>,
|
||||
): E extends keyof DataEntryMap[C]
|
||||
? Promise<DataEntryMap[C][E]>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
export function getEntry<
|
||||
C extends keyof ContentEntryMap,
|
||||
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||
>(
|
||||
collection: C,
|
||||
slug: E,
|
||||
): E extends ValidContentEntrySlug<C>
|
||||
? Promise<CollectionEntry<C>>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
export function getEntry<
|
||||
C extends keyof DataEntryMap,
|
||||
E extends keyof DataEntryMap[C] | (string & {}),
|
||||
>(
|
||||
collection: C,
|
||||
id: E,
|
||||
): E extends keyof DataEntryMap[C]
|
||||
? string extends keyof DataEntryMap[C]
|
||||
? Promise<DataEntryMap[C][E]> | undefined
|
||||
: Promise<DataEntryMap[C][E]>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
export function getLiveEntry<C extends keyof LiveContentConfig['collections']>(
|
||||
collection: C,
|
||||
filter: string | LiveLoaderEntryFilterType<C>,
|
||||
): Promise<import('astro').LiveDataEntryResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>>;
|
||||
|
||||
/** Resolve an array of entry references from the same collection */
|
||||
export function getEntries<C extends keyof ContentEntryMap>(
|
||||
entries: ReferenceContentEntry<C, ValidContentEntrySlug<C>>[],
|
||||
): Promise<CollectionEntry<C>[]>;
|
||||
export function getEntries<C extends keyof DataEntryMap>(
|
||||
entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
|
||||
): Promise<CollectionEntry<C>[]>;
|
||||
|
||||
export function render<C extends keyof AnyEntryMap>(
|
||||
entry: AnyEntryMap[C][string],
|
||||
): Promise<RenderResult>;
|
||||
|
||||
export function reference<C extends keyof AnyEntryMap>(
|
||||
collection: C,
|
||||
): import('astro/zod').ZodEffects<
|
||||
import('astro/zod').ZodString,
|
||||
C extends keyof ContentEntryMap
|
||||
? ReferenceContentEntry<C, ValidContentEntrySlug<C>>
|
||||
: ReferenceDataEntry<C, keyof DataEntryMap[C]>
|
||||
>;
|
||||
// Allow generic `string` to avoid excessive type errors in the config
|
||||
// if `dev` is not running to update as you edit.
|
||||
// Invalid collection names will be caught at build time.
|
||||
export function reference<C extends string>(
|
||||
collection: C,
|
||||
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
|
||||
|
||||
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
|
||||
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
|
||||
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
|
||||
>;
|
||||
|
||||
type ContentEntryMap = {
|
||||
|
||||
};
|
||||
|
||||
type DataEntryMap = {
|
||||
"blog": Record<string, {
|
||||
id: string;
|
||||
body?: string;
|
||||
collection: "blog";
|
||||
data: any;
|
||||
rendered?: RenderedContent;
|
||||
filePath?: string;
|
||||
}>;
|
||||
"products": Record<string, {
|
||||
id: string;
|
||||
body?: string;
|
||||
collection: "products";
|
||||
data: any;
|
||||
rendered?: RenderedContent;
|
||||
filePath?: string;
|
||||
}>;
|
||||
|
||||
};
|
||||
|
||||
type AnyEntryMap = ContentEntryMap & DataEntryMap;
|
||||
|
||||
type ExtractLoaderTypes<T> = T extends import('astro/loaders').LiveLoader<
|
||||
infer TData,
|
||||
infer TEntryFilter,
|
||||
infer TCollectionFilter,
|
||||
infer TError
|
||||
>
|
||||
? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError }
|
||||
: { data: never; entryFilter: never; collectionFilter: never; error: never };
|
||||
type ExtractDataType<T> = ExtractLoaderTypes<T>['data'];
|
||||
type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>['entryFilter'];
|
||||
type ExtractCollectionFilterType<T> = ExtractLoaderTypes<T>['collectionFilter'];
|
||||
type ExtractErrorType<T> = ExtractLoaderTypes<T>['error'];
|
||||
|
||||
type LiveLoaderDataType<C extends keyof LiveContentConfig['collections']> =
|
||||
LiveContentConfig['collections'][C]['schema'] extends undefined
|
||||
? ExtractDataType<LiveContentConfig['collections'][C]['loader']>
|
||||
: import('astro/zod').infer<
|
||||
Exclude<LiveContentConfig['collections'][C]['schema'], undefined>
|
||||
>;
|
||||
type LiveLoaderEntryFilterType<C extends keyof LiveContentConfig['collections']> =
|
||||
ExtractEntryFilterType<LiveContentConfig['collections'][C]['loader']>;
|
||||
type LiveLoaderCollectionFilterType<C extends keyof LiveContentConfig['collections']> =
|
||||
ExtractCollectionFilterType<LiveContentConfig['collections'][C]['loader']>;
|
||||
type LiveLoaderErrorType<C extends keyof LiveContentConfig['collections']> = ExtractErrorType<
|
||||
LiveContentConfig['collections'][C]['loader']
|
||||
>;
|
||||
|
||||
export type ContentConfig = typeof import("../src/content.config.mjs");
|
||||
export type LiveContentConfig = never;
|
||||
}
|
||||
2
dealplustech-astro/.astro/types.d.ts
vendored
2
dealplustech-astro/.astro/types.d.ts
vendored
@@ -1,2 +0,0 @@
|
||||
/// <reference types="astro/client" />
|
||||
/// <reference path="content.d.ts" />
|
||||
4
dealplustech-astro/.vscode/extensions.json
vendored
Normal file
4
dealplustech-astro/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
11
dealplustech-astro/.vscode/launch.json
vendored
Normal file
11
dealplustech-astro/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
# 🔬 Easypanel API Testing Results
|
||||
|
||||
## ✅ What Works
|
||||
|
||||
### 1. Authentication
|
||||
```bash
|
||||
curl -H "Authorization: Bearer TOKEN" \
|
||||
http://110.164.146.46:3000/api/trpc/setup.getStatus
|
||||
# ✅ Returns: {"result":{"data":{"json":{"isComplete":true}}}}
|
||||
```
|
||||
|
||||
### 2. List Projects
|
||||
```bash
|
||||
curl -H "Authorization: Bearer TOKEN" \
|
||||
http://110.164.146.46:3000/api/trpc/projects.listProjectsAndServices
|
||||
# ✅ Returns project list including "customerwebsite"
|
||||
```
|
||||
|
||||
### 3. Inspect Service
|
||||
```bash
|
||||
curl -H "Authorization: Bearer TOKEN" \
|
||||
http://110.164.146.46:3000/api/trpc/services.app.inspectService
|
||||
# ✅ Works when service exists
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ❌ What Fails - Service Creation
|
||||
|
||||
### Endpoint
|
||||
```
|
||||
POST /api/trpc/services.app.createService
|
||||
```
|
||||
|
||||
### Schema (from OpenAPI)
|
||||
```json
|
||||
{
|
||||
"input": {
|
||||
"json": {
|
||||
"projectName": "customerwebsite",
|
||||
"serviceName": "my-app",
|
||||
"source": {
|
||||
"type": "image" | "github",
|
||||
// For "image":
|
||||
"image": "nginx:alpine",
|
||||
"username": "...",
|
||||
"password": "...",
|
||||
// For "github":
|
||||
"owner": "...",
|
||||
"repo": "...",
|
||||
"ref": "main",
|
||||
"path": "..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Attempts & Errors
|
||||
|
||||
#### Attempt 1: Basic Image
|
||||
```json
|
||||
{
|
||||
"input": {
|
||||
"json": {
|
||||
"projectName": "customerwebsite",
|
||||
"serviceName": "test",
|
||||
"source": {
|
||||
"type": "image",
|
||||
"image": "nginx:alpine",
|
||||
"port": 80
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
**Result:** ❌ 500 Error
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"json": {
|
||||
"message": "[{\"code\":\"invalid_type\",\"expected\":\"object\",\"received\":\"undefined\",\"path\":[],\"message\":\"Required\"}]"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Attempt 2: With Git Source
|
||||
```json
|
||||
{
|
||||
"input": {
|
||||
"json": {
|
||||
"projectName": "customerwebsite",
|
||||
"serviceName": "test-git",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"repository": "http://...",
|
||||
"branch": "main"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
**Result:** ❌ Same "Required" error
|
||||
|
||||
#### Attempt 3: Using `project` instead of `projectName`
|
||||
```json
|
||||
{
|
||||
"input": {
|
||||
"json": {
|
||||
"project": "customerwebsite",
|
||||
"name": "test"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
**Result:** ❌ Same error
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Analysis
|
||||
|
||||
The Zod validation error `"expected":"object","received":"undefined","path":[]` suggests:
|
||||
|
||||
1. **Missing required field** at root level
|
||||
2. **Schema mismatch** - the API expects additional fields not in OpenAPI spec
|
||||
3. **Possible tRPC format issue** - Easypanel might use a different input format
|
||||
|
||||
---
|
||||
|
||||
## 💡 Hypothesis
|
||||
|
||||
Easypanel's `createService` endpoint might require:
|
||||
|
||||
### Option A: Additional Required Fields
|
||||
```json
|
||||
{
|
||||
"input": {
|
||||
"json": {
|
||||
"projectName": "...",
|
||||
"serviceName": "...",
|
||||
"source": {...},
|
||||
// Missing required fields:
|
||||
"environmentVariables": [],
|
||||
"domains": [],
|
||||
"ports": [],
|
||||
"mounts": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Option B: Different tRPC Format
|
||||
```json
|
||||
{
|
||||
"type": "query",
|
||||
"input": {...}
|
||||
}
|
||||
```
|
||||
|
||||
### Option C: Requires Project ID (not name)
|
||||
```json
|
||||
{
|
||||
"input": {
|
||||
"json": {
|
||||
"project": "cmkw22b00000007tu4gim48q9", // Actual ID
|
||||
"name": "test"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Next Steps to Debug
|
||||
|
||||
### 1. Capture Browser Network Traffic
|
||||
1. Open Easypanel dashboard
|
||||
2. Open DevTools → Network tab
|
||||
3. Create a service manually
|
||||
4. Inspect the exact API request
|
||||
5. Copy request payload
|
||||
|
||||
### 2. Test with cURL
|
||||
Use the exact payload from browser
|
||||
|
||||
### 3. Alternative: Use Easypanel CLI (if exists)
|
||||
Check if Easypanel provides official CLI
|
||||
|
||||
### 4. Contact Easypanel Support
|
||||
Ask for correct API schema for `services.app.createService`
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Current Recommendation
|
||||
|
||||
Until API is figured out, use **manual creation**:
|
||||
|
||||
1. **Create service via dashboard** (2 minutes)
|
||||
2. **Copy service ID**
|
||||
3. **Register with skill**: `./deploy.sh register SERVICE_ID`
|
||||
4. **Future updates**: Automated via `./deploy.sh update`
|
||||
|
||||
This is still 80% automated!
|
||||
|
||||
---
|
||||
|
||||
## 📞 Need Your Help
|
||||
|
||||
**Can you:**
|
||||
1. Open browser DevTools when creating service in Easypanel
|
||||
2. Copy the exact API request payload
|
||||
3. Share it so I can update the skill?
|
||||
|
||||
Or if you know the correct schema, let me know!
|
||||
|
||||
---
|
||||
|
||||
**Status:** ⚠️ API schema unclear
|
||||
**Workaround:** Manual creation + automated updates
|
||||
**Automation Level:** 80% (will be 100% with correct schema)
|
||||
@@ -1,132 +0,0 @@
|
||||
# 🚀 Fully Automated Deployment - v5.0
|
||||
|
||||
## ✅ Complete Workflow
|
||||
|
||||
The skill now automates ALL steps:
|
||||
|
||||
### Step 1: Create Service
|
||||
```bash
|
||||
POST /api/trpc/services.app.createService
|
||||
{
|
||||
"json": {
|
||||
"projectName": "customerwebsite",
|
||||
"domains": [{"host": "$(EASYPANEL_DOMAIN)"}],
|
||||
"serviceName": "dealplustech-astro"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Configure Git
|
||||
```bash
|
||||
POST /api/trpc/services.app.updateSourceGit
|
||||
{
|
||||
"json": {
|
||||
"projectName": "customerwebsite",
|
||||
"serviceName": "dealplustech-astro",
|
||||
"repo": "http://110.164.146.46:3001/dealplustech/dealplustech-astro.git",
|
||||
"ref": "main",
|
||||
"path": "/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Set Build Type
|
||||
```bash
|
||||
POST /api/trpc/services.app.updateBuild
|
||||
{
|
||||
"json": {
|
||||
"projectName": "customerwebsite",
|
||||
"serviceName": "dealplustech-astro",
|
||||
"build": {"type": "nixpacks"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Get Primary Domain
|
||||
```bash
|
||||
GET /api/trpc/domains.getPrimaryDomain
|
||||
{
|
||||
"json": {
|
||||
"projectName": "customerwebsite",
|
||||
"serviceName": "dealplustech-astro"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Wait for Deployment
|
||||
- Polls service status every 10 seconds
|
||||
- Waits up to 5 minutes (30 attempts)
|
||||
- Reports when status is "running" or "ready"
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Usage
|
||||
|
||||
### First Deployment
|
||||
```bash
|
||||
cd dealplustech-astro
|
||||
./skills/easypanel-deploy/deploy.sh deploy
|
||||
```
|
||||
|
||||
**That's it!** The script handles everything automatically!
|
||||
|
||||
### Redeploy After Changes
|
||||
```bash
|
||||
git push # Push your changes
|
||||
./skills/easypanel-deploy/deploy.sh redeploy # Trigger redeploy
|
||||
```
|
||||
|
||||
### Check Status
|
||||
```bash
|
||||
./skills/easypanel-deploy/deploy.sh status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Benefits
|
||||
|
||||
| Feature | Before v5.0 | v5.0 |
|
||||
|---------|-------------|------|
|
||||
| Manual steps | 4-6 steps | **0 steps** |
|
||||
| Time required | 5-10 minutes | **1 command** |
|
||||
| API calls | N/A | **5 automated calls** |
|
||||
| Error handling | Manual | **Automatic** |
|
||||
| Wait for deploy | Manual | **Automatic** |
|
||||
| Domain info | Manual lookup | **Automatic** |
|
||||
|
||||
---
|
||||
|
||||
## 📋 What Happens
|
||||
|
||||
```
|
||||
1. ./deploy.sh deploy
|
||||
↓
|
||||
2. Creates service in Easypanel
|
||||
↓
|
||||
3. Configures Git repository
|
||||
↓
|
||||
4. Sets build type (nixpacks)
|
||||
↓
|
||||
5. Gets auto-generated domain
|
||||
↓
|
||||
6. Waits for deployment
|
||||
↓
|
||||
7. ✅ Deployment complete!
|
||||
```
|
||||
|
||||
**Zero manual steps!**
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Ready to Test!
|
||||
|
||||
```bash
|
||||
# Deploy now!
|
||||
./skills/easypanel-deploy/deploy.sh deploy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ Fully Automated
|
||||
**Automation Level:** 100%
|
||||
**Manual Steps:** 0
|
||||
@@ -1,284 +0,0 @@
|
||||
# 🚀 Easypanel Deployment Guide
|
||||
|
||||
**Project:** Deal Plus Tech Astro Migration
|
||||
**Deployment Target:** Easypanel (Docker-based)
|
||||
**Build Command:** `npm run build`
|
||||
**Output:** Static site served via `npm run preview`
|
||||
|
||||
---
|
||||
|
||||
## 📋 Prerequisites
|
||||
|
||||
1. **Easypanel Account** - Access to Easypanel instance at `http://110.164.146.46:3000`
|
||||
2. **Git Repository** - Code pushed to Git (Gitea/GitHub/GitLab)
|
||||
3. **Domain** (optional) - Custom domain for production
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Deployment Steps
|
||||
|
||||
### Option 1: Deploy from Git Repository (Recommended)
|
||||
|
||||
#### Step 1: Connect Git Repository
|
||||
|
||||
1. Login to Easypanel: `http://110.164.146.46:3000`
|
||||
2. Click **"New Project"** or select existing project
|
||||
3. Click **"New Service"** → **"Git Repository"**
|
||||
4. Connect your Git provider (Gitea, GitHub, GitLab)
|
||||
5. Select repository: `dealplustech/dealplustech-astro`
|
||||
6. Select branch: `main`
|
||||
|
||||
#### Step 2: Configure Build Settings
|
||||
|
||||
**Build Configuration:**
|
||||
- **Build Command:** `npm run build`
|
||||
- **Publish Directory:** `dist`
|
||||
- **Dockerfile:** (Leave empty - uses auto-detection)
|
||||
- **Node Version:** `20`
|
||||
|
||||
**Environment Variables:**
|
||||
```bash
|
||||
NODE_ENV=production
|
||||
PORT=4321
|
||||
HOST=0.0.0.0
|
||||
```
|
||||
|
||||
#### Step 3: Deploy
|
||||
|
||||
1. Click **"Deploy"**
|
||||
2. Wait for build to complete (~2-3 minutes)
|
||||
3. Easypanel will automatically assign a URL (e.g., `dealplustech-astro.easypanel.app`)
|
||||
4. Test the deployment
|
||||
|
||||
#### Step 4: Auto-Deploy (Optional)
|
||||
|
||||
Enable auto-deploy on push:
|
||||
1. Go to Service Settings → **Git**
|
||||
2. Enable **"Auto Deploy"**
|
||||
3. Select branch: `main`
|
||||
|
||||
Now every push to `main` will trigger automatic deployment!
|
||||
|
||||
---
|
||||
|
||||
### Option 2: Deploy with Dockerfile
|
||||
|
||||
If you prefer using the provided Dockerfile:
|
||||
|
||||
#### Step 1: Build Docker Image Locally
|
||||
|
||||
```bash
|
||||
cd dealplustech-astro
|
||||
|
||||
# Build Docker image
|
||||
docker build -t dealplustech-astro:latest .
|
||||
|
||||
# Test locally
|
||||
docker run -p 4321:4321 dealplustech-astro:latest
|
||||
```
|
||||
|
||||
Visit `http://localhost:4321` to test
|
||||
|
||||
#### Step 2: Push to Container Registry
|
||||
|
||||
```bash
|
||||
# Tag for your registry
|
||||
docker tag dealplustech-astro:latest your-registry.com/dealplustech-astro:latest
|
||||
|
||||
# Push to registry
|
||||
docker push your-registry.com/dealplustech-astro:latest
|
||||
```
|
||||
|
||||
#### Step 3: Deploy on Easypanel
|
||||
|
||||
1. Login to Easypanel
|
||||
2. **New Service** → **"Docker Image"**
|
||||
3. Enter image URL: `your-registry.com/dealplustech-astro:latest`
|
||||
4. Configure port: `4321`
|
||||
5. Click **"Deploy"**
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Value | Description |
|
||||
|----------|-------|-------------|
|
||||
| `NODE_ENV` | `production` | Production mode |
|
||||
| `PORT` | `4321` | Astro preview server port |
|
||||
| `HOST` | `0.0.0.0` | Listen on all interfaces |
|
||||
|
||||
### Resource Allocation
|
||||
|
||||
**Recommended Resources:**
|
||||
- **CPU:** 0.5 - 1 vCPU
|
||||
- **Memory:** 512MB - 1GB
|
||||
- **Storage:** 1GB (for logs and assets)
|
||||
|
||||
### Custom Domain
|
||||
|
||||
To add a custom domain:
|
||||
|
||||
1. Go to Service Settings → **Domains**
|
||||
2. Click **"Add Domain"**
|
||||
3. Enter your domain: `dealplustech.co.th`
|
||||
4. Update DNS records:
|
||||
```
|
||||
Type: CNAME
|
||||
Name: www
|
||||
Value: your-easypanel-url.easypanel.app
|
||||
```
|
||||
5. Enable SSL (Easypanel provides auto-SSL)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Monitoring
|
||||
|
||||
### Health Check
|
||||
|
||||
The Dockerfile includes a health check endpoint:
|
||||
- **URL:** `http://your-domain:4321/`
|
||||
- **Expected:** HTTP 200 OK
|
||||
|
||||
### Logs
|
||||
|
||||
View logs in Easypanel:
|
||||
1. Select Service
|
||||
2. Click **"Logs"** tab
|
||||
3. Filter by date/time
|
||||
|
||||
### Metrics
|
||||
|
||||
Easypanel provides:
|
||||
- CPU usage
|
||||
- Memory usage
|
||||
- Network traffic
|
||||
- Request count
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Update Deployment
|
||||
|
||||
### Automatic Updates (Git Auto-Deploy)
|
||||
|
||||
If auto-deploy is enabled:
|
||||
1. Push changes to `main` branch
|
||||
2. Easypanel automatically rebuilds
|
||||
3. New version deploys in ~2-3 minutes
|
||||
|
||||
### Manual Updates
|
||||
|
||||
1. Go to Service → **Deployments**
|
||||
2. Click **"Redeploy"**
|
||||
3. Select latest commit
|
||||
4. Click **"Deploy Now"**
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Build Fails
|
||||
|
||||
**Issue:** `npm run build` fails
|
||||
|
||||
**Solution:**
|
||||
1. Check build logs in Easypanel
|
||||
2. Verify `package.json` scripts
|
||||
3. Test build locally: `npm run build`
|
||||
4. Check Node version (must be 20+)
|
||||
|
||||
### 502 Bad Gateway
|
||||
|
||||
**Issue:** Service returns 502 error
|
||||
|
||||
**Solution:**
|
||||
1. Check if container is running
|
||||
2. Verify port is 4321
|
||||
3. Check health check endpoint
|
||||
4. Review container logs
|
||||
|
||||
### Static Assets Not Loading
|
||||
|
||||
**Issue:** Images/CSS return 404
|
||||
|
||||
**Solution:**
|
||||
1. Verify `public/` folder is copied in Dockerfile
|
||||
2. Check asset paths in code
|
||||
3. Rebuild and redeploy
|
||||
|
||||
---
|
||||
|
||||
## 📝 Post-Deployment Checklist
|
||||
|
||||
- [ ] Test homepage loads
|
||||
- [ ] Test product pages (6 products)
|
||||
- [ ] Test blog posts (3 posts)
|
||||
- [ ] Test mobile responsiveness
|
||||
- [ ] Verify FloatingContact buttons work
|
||||
- [ ] Test all navigation links
|
||||
- [ ] Check SEO metadata
|
||||
- [ ] Setup custom domain (if needed)
|
||||
- [ ] Enable SSL certificate
|
||||
- [ ] Configure CDN (optional)
|
||||
- [ ] Setup monitoring alerts
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Production Optimization
|
||||
|
||||
### Enable Compression
|
||||
|
||||
Easypanel automatically enables gzip compression for static assets.
|
||||
|
||||
### CDN Integration
|
||||
|
||||
For better performance:
|
||||
|
||||
1. Sign up for CDN (Cloudflare, BunnyCDN, etc.)
|
||||
2. Point CDN to Easypanel URL
|
||||
3. Update DNS to point to CDN
|
||||
4. Configure cache rules
|
||||
|
||||
### Caching Headers
|
||||
|
||||
Astro sets optimal cache headers by default:
|
||||
- **HTML:** No cache (always fresh)
|
||||
- **Assets:** 1 year (immutable)
|
||||
- **Images:** 1 year (immutable)
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support
|
||||
|
||||
**Easypanel Documentation:** https://docs.easypanel.io
|
||||
**Astro Documentation:** https://docs.astro.build
|
||||
**Project Repository:** [Your Git Repo]
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Deploy Commands
|
||||
|
||||
```bash
|
||||
# Build locally
|
||||
npm run build
|
||||
|
||||
# Test production build locally
|
||||
npm run preview
|
||||
|
||||
# Build Docker image
|
||||
docker build -t dealplustech-astro:latest .
|
||||
|
||||
# Run Docker container
|
||||
docker run -p 4321:4321 dealplustech-astro:latest
|
||||
|
||||
# Push to registry
|
||||
docker push your-registry.com/dealplustech-astro:latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Deployment Status:** ✅ Ready to Deploy
|
||||
**Estimated Deploy Time:** 2-3 minutes
|
||||
**First Deploy:** Manual
|
||||
**Subsequent Deploys:** Automatic (if enabled)
|
||||
@@ -1,118 +0,0 @@
|
||||
# 🚢 Image Deployment Options
|
||||
|
||||
## The Issue
|
||||
|
||||
When you built `dealplustech-astro:latest`, it's only on your **local Docker**. Easypanel server can't access it.
|
||||
|
||||
## ✅ Solutions
|
||||
|
||||
### Option 1: Easypanel Docker Registry (Recommended)
|
||||
|
||||
Easypanel provides a built-in Docker registry.
|
||||
|
||||
**Steps:**
|
||||
|
||||
```bash
|
||||
# 1. Get registry URL (usually SERVER_IP:3001)
|
||||
# For your setup: 110.164.146.46:3001
|
||||
|
||||
# 2. Login to registry
|
||||
docker login 110.164.146.46:3001
|
||||
# Use Easypanel credentials
|
||||
|
||||
# 3. Tag image
|
||||
docker tag dealplustech-astro:latest 110.164.146.46:3001/customerwebsite/dealplustech-astro:latest
|
||||
|
||||
# 4. Push
|
||||
docker push 110.164.146.46:3001/customerwebsite/dealplustech-astro:latest
|
||||
|
||||
# 5. In Easypanel dashboard, use:
|
||||
# Image: 110.164.146.46:3001/customerwebsite/dealplustech-astro:latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Option 2: Docker Hub (Public)
|
||||
|
||||
```bash
|
||||
# Tag with your Docker Hub username
|
||||
docker tag dealplustech-astro:latest yourusername/dealplustech-astro:latest
|
||||
|
||||
# Push
|
||||
docker push yourusername/dealplustech-astro:latest
|
||||
|
||||
# In Easypanel, use:
|
||||
# Image: yourusername/dealplustech-astro:latest
|
||||
```
|
||||
|
||||
**Note:** Public repository - anyone can see it
|
||||
|
||||
---
|
||||
|
||||
### Option 3: Private Registry (Harbor, GitLab, etc.)
|
||||
|
||||
```bash
|
||||
# Tag for your registry
|
||||
docker tag dealplustech-astro:latest registry.yourcompany.com/project/dealplustech-astro:latest
|
||||
|
||||
# Login
|
||||
docker login registry.yourcompany.com
|
||||
|
||||
# Push
|
||||
docker push registry.yourcompany.com/project/dealplustech-astro:latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Option 4: Direct Server Deployment
|
||||
|
||||
If you have SSH access to Easypanel server:
|
||||
|
||||
```bash
|
||||
# Export image
|
||||
docker save dealplustech-astro:latest > dealplustech-astro.tar
|
||||
|
||||
# Copy to server
|
||||
scp dealplustech-astro.tar user@110.164.146.46:/tmp/
|
||||
|
||||
# SSH to server and load
|
||||
ssh user@110.164.146.46
|
||||
docker load < /tmp/dealplustech-astro.tar
|
||||
|
||||
# In Easypanel, use:
|
||||
# Image: dealplustech-astro:latest (local)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Recommended for Your Setup
|
||||
|
||||
**Use Easypanel's built-in registry:**
|
||||
|
||||
```bash
|
||||
# 1. Login
|
||||
docker login 110.164.146.46:3001
|
||||
|
||||
# 2. Tag
|
||||
docker tag dealplustech-astro:latest 110.164.146.46:3001/customerwebsite/dealplustech-astro:latest
|
||||
|
||||
# 3. Push
|
||||
docker push 110.164.146.46:3001/customerwebsite/dealplustech-astro:latest
|
||||
|
||||
# 4. Create service in Easypanel with:
|
||||
# Image: 110.164.146.46:3001/customerwebsite/dealplustech-astro:latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Checklist
|
||||
|
||||
- [ ] Choose registry option
|
||||
- [ ] Login to registry
|
||||
- [ ] Tag image correctly
|
||||
- [ ] Push to registry
|
||||
- [ ] Use full image name in Easypanel
|
||||
|
||||
---
|
||||
|
||||
**Next Step:** Push to Easypanel registry, then create service!
|
||||
@@ -1,177 +0,0 @@
|
||||
# 🚀 Deployment Summary & Next Steps
|
||||
|
||||
## ✅ What's Complete
|
||||
|
||||
### 1. Docker Image Built
|
||||
- **Image:** `dealplustech-astro:latest`
|
||||
- **Size:** ~564MB
|
||||
- **Status:** Ready to deploy
|
||||
|
||||
### 2. Easypanel Connection
|
||||
- **URL:** http://110.164.146.46:3000
|
||||
- **Token:** ✅ Configured
|
||||
- **Project Found:** `customerwebsite`
|
||||
|
||||
### 3. Skill Created
|
||||
- **Location:** `skills/easypanel-deploy/`
|
||||
- **Version:** 2.2
|
||||
- **Features:** State management, lifecycle control
|
||||
|
||||
---
|
||||
|
||||
## 📋 Manual Steps Required
|
||||
|
||||
Easypanel API has limitations for service creation. Follow these steps:
|
||||
|
||||
### Step 1: Create Service in Dashboard
|
||||
|
||||
1. **Open Easypanel:**
|
||||
```
|
||||
http://110.164.146.46:3000
|
||||
```
|
||||
|
||||
2. **Select Project:**
|
||||
- Click on: `customerwebsite`
|
||||
|
||||
3. **Create New Service:**
|
||||
- Click: **"New Service"**
|
||||
- Choose: **"Docker image"**
|
||||
|
||||
4. **Configure Service:**
|
||||
```
|
||||
Name: dealplustech-astro
|
||||
Image: dealplustech-astro:latest
|
||||
Port: 4321
|
||||
```
|
||||
|
||||
5. **Deploy:**
|
||||
- Click: **"Create"** or **"Deploy"**
|
||||
- Wait for deployment (~2-3 minutes)
|
||||
|
||||
6. **Copy Service ID:**
|
||||
- Go to service settings
|
||||
- Copy the ID (looks like: `svc_abc123...`)
|
||||
|
||||
### Step 2: Register Service ID
|
||||
|
||||
```bash
|
||||
cd dealplustech-astro
|
||||
|
||||
# Register the service ID you copied
|
||||
./skills/easypanel-deploy/deploy.sh register svc_abc123...
|
||||
```
|
||||
|
||||
### Step 3: Verify
|
||||
|
||||
```bash
|
||||
# Check status
|
||||
./skills/easypanel-deploy/deploy.sh status
|
||||
|
||||
# Should show:
|
||||
# ✅ Service: dealplustech-astro
|
||||
# ID: svc_abc123...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Future Updates
|
||||
|
||||
After registration, updates are easy:
|
||||
|
||||
```bash
|
||||
# Make code changes
|
||||
git pull
|
||||
|
||||
# Update deployment
|
||||
./skills/easypanel-deploy/deploy.sh update
|
||||
|
||||
# This will:
|
||||
# 1. Rebuild Docker image
|
||||
# 2. Ready for deployment
|
||||
# 3. Click "Deploy" in Easypanel to apply
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Quick Commands
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `./deploy.sh deploy` | Initial deployment |
|
||||
| `./deploy.sh register ID` | Register service |
|
||||
| `./deploy.sh update` | Update service |
|
||||
| `./deploy.sh status` | Check status |
|
||||
| `./deploy.sh list` | List projects |
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Docker Build Fails
|
||||
```bash
|
||||
# Rebuild manually
|
||||
cd dealplustech-astro
|
||||
docker build -t dealplustech-astro:latest .
|
||||
```
|
||||
|
||||
### Can't Access Easypanel
|
||||
- Check VPN/connection
|
||||
- URL: http://110.164.146.46:3000
|
||||
- Contact admin if needed
|
||||
|
||||
### Service Won't Start
|
||||
1. Check logs in Easypanel dashboard
|
||||
2. Verify port 4321 is available
|
||||
3. Check resource allocation
|
||||
|
||||
---
|
||||
|
||||
## 📁 Files Created
|
||||
|
||||
```
|
||||
dealplustech-astro/
|
||||
├── skills/easypanel-deploy/
|
||||
│ ├── deploy.sh # Main script
|
||||
│ ├── SKILL_v2.md # Documentation
|
||||
│ └── README.md # Quick start
|
||||
├── easypanel.config.json # Configuration
|
||||
├── Dockerfile # Docker config
|
||||
└── DEPLOYMENT_SUMMARY.md # This file
|
||||
|
||||
~/.easypanel/
|
||||
├── credentials # API token
|
||||
└── state.json # Service IDs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist
|
||||
|
||||
- [ ] Docker image built ✅
|
||||
- [ ] Easypanel token configured ✅
|
||||
- [ ] Project identified (`customerwebsite`) ✅
|
||||
- [ ] Create service in dashboard ⏳
|
||||
- [ ] Register service ID ⏳
|
||||
- [ ] Verify deployment ⏳
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Next Action
|
||||
|
||||
**Go to Easypanel dashboard and create the service:**
|
||||
|
||||
1. Open: http://110.164.146.46:3000
|
||||
2. Select: `customerwebsite` project
|
||||
3. New Service → Docker image
|
||||
4. Name: `dealplustech-astro`
|
||||
5. Image: `dealplustech-astro:latest`
|
||||
6. Port: `4321`
|
||||
7. Deploy!
|
||||
|
||||
Then run: `./deploy.sh register YOUR_SERVICE_ID`
|
||||
|
||||
---
|
||||
|
||||
**Status:** Ready for manual service creation
|
||||
**Estimated Time:** 5 minutes
|
||||
**Difficulty:** Easy
|
||||
@@ -1,302 +0,0 @@
|
||||
# 🚀 Easypanel API Deployment Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This guide shows how to deploy the Deal Plus Tech Astro site to Easypanel using their API.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Easypanel Access**
|
||||
- URL: `http://110.164.146.46:3000`
|
||||
- Admin credentials
|
||||
|
||||
2. **API Token**
|
||||
- Login to Easypanel
|
||||
- Go to Settings → API
|
||||
- Generate new API token
|
||||
|
||||
3. **Docker Image**
|
||||
- Already built locally: `dealplustech-astro:latest`
|
||||
- Size: 564MB
|
||||
|
||||
---
|
||||
|
||||
## Method 1: Manual Deployment (Recommended)
|
||||
|
||||
### Step 1: Login to Easypanel
|
||||
|
||||
```
|
||||
URL: http://110.164.146.46:3000
|
||||
```
|
||||
|
||||
### Step 2: Create Project
|
||||
|
||||
1. Click **"New Project"**
|
||||
2. Name: `dealplustech`
|
||||
3. Description: "Deal Plus Tech Websites"
|
||||
4. Click **"Create"**
|
||||
|
||||
### Step 3: Deploy Docker Image
|
||||
|
||||
1. Select project: `dealplustech`
|
||||
2. Click **"New Service"**
|
||||
3. Choose: **"Docker Image"**
|
||||
4. Configure:
|
||||
- **Name:** `dealplustech-astro`
|
||||
- **Image:** `dealplustech-astro:latest`
|
||||
- **Port:** `4321`
|
||||
5. Click **"Deploy"**
|
||||
|
||||
### Step 4: Verify Deployment
|
||||
|
||||
1. Wait for build to complete (~2-3 minutes)
|
||||
2. Click on service to view logs
|
||||
3. Look for: "Astro preview ready"
|
||||
4. Access URL provided by Easypanel
|
||||
|
||||
---
|
||||
|
||||
## Method 2: Automated Deployment (API)
|
||||
|
||||
### Step 1: Get API Token
|
||||
|
||||
```bash
|
||||
# Login to Easypanel and get token from Settings → API
|
||||
export EASYPANEL_API_TOKEN="your-api-token-here"
|
||||
```
|
||||
|
||||
### Step 2: Run Deployment Script
|
||||
|
||||
```bash
|
||||
cd dealplustech-astro
|
||||
|
||||
# Option A: With token as argument
|
||||
./deploy-easypanel.sh your-api-token
|
||||
|
||||
# Option B: With environment variable
|
||||
export EASYPANEL_API_TOKEN="your-api-token"
|
||||
./deploy-easypanel.sh
|
||||
```
|
||||
|
||||
### Step 3: Monitor Deployment
|
||||
|
||||
```bash
|
||||
# View service status
|
||||
curl -s "http://110.164.146.46:3000/api/trpc/services.app.inspectService" \
|
||||
-H "Authorization: Bearer $EASYPANEL_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"input":{"json":{"projectName":"dealplustech","serviceName":"dealplustech-astro"}}}' \
|
||||
--insecure | python3 -m json.tool
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Method 3: Direct Docker Deployment
|
||||
|
||||
If you have direct Docker access to the Easypanel server:
|
||||
|
||||
```bash
|
||||
# SSH to Easypanel server
|
||||
ssh user@110.164.146.46
|
||||
|
||||
# Run container directly
|
||||
docker run -d \
|
||||
-p 4321:4321 \
|
||||
--name dealplustech-astro \
|
||||
--restart unless-stopped \
|
||||
-e NODE_ENV=production \
|
||||
-e PORT=4321 \
|
||||
-e HOST=0.0.0.0 \
|
||||
dealplustech-astro:latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### List Projects
|
||||
|
||||
```bash
|
||||
curl -X GET "http://110.164.146.46:3000/api/trpc/projects.listProjects" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
--insecure
|
||||
```
|
||||
|
||||
### Create Service
|
||||
|
||||
```bash
|
||||
curl -X POST "http://110.164.146.46:3000/api/trpc/services.app.create" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"projectName": "dealplustech",
|
||||
"name": "dealplustech-astro",
|
||||
"type": "docker",
|
||||
"docker": {
|
||||
"image": "dealplustech-astro:latest",
|
||||
"port": 4321
|
||||
},
|
||||
"env": {
|
||||
"NODE_ENV": "production",
|
||||
"PORT": "4321"
|
||||
}
|
||||
}' \
|
||||
--insecure
|
||||
```
|
||||
|
||||
### Inspect Service
|
||||
|
||||
```bash
|
||||
curl -X GET "http://110.164.146.46:3000/api/trpc/services.app.inspectService" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{"input":{"json":{"projectName":"dealplustech","serviceName":"dealplustech-astro"}}}' \
|
||||
--insecure
|
||||
```
|
||||
|
||||
### Get Service Logs
|
||||
|
||||
```bash
|
||||
curl -X GET "http://110.164.146.46:3000/api/trpc/services.common.getLogs" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{"input":{"json":{"projectName":"dealplustech","serviceName":"dealplustech-astro","lines":50}}}' \
|
||||
--insecure
|
||||
```
|
||||
|
||||
### Deploy/Redeploy Service
|
||||
|
||||
```bash
|
||||
curl -X POST "http://110.164.146.46:3000/api/trpc/services.app.deploy" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{"input":{"json":{"projectName":"dealplustech","serviceName":"dealplustech-astro"}}}' \
|
||||
--insecure
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Value | Required |
|
||||
|----------|-------|----------|
|
||||
| `NODE_ENV` | `production` | ✅ Yes |
|
||||
| `PORT` | `4321` | ✅ Yes |
|
||||
| `HOST` | `0.0.0.0` | ✅ Yes |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### API Returns 401 Unauthorized
|
||||
|
||||
**Problem:** Invalid or missing API token
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Verify token is set
|
||||
echo $EASYPANEL_API_TOKEN
|
||||
|
||||
# Regenerate token in Easypanel dashboard
|
||||
```
|
||||
|
||||
### Service Won't Start
|
||||
|
||||
**Problem:** Container crashes on startup
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Check logs
|
||||
docker logs dealplustech-astro
|
||||
|
||||
# Common issues:
|
||||
# - Port already in use
|
||||
# - Missing environment variables
|
||||
# - Image build failed
|
||||
```
|
||||
|
||||
### Build Fails
|
||||
|
||||
**Problem:** Docker build errors
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Rebuild with verbose output
|
||||
cd dealplustech-astro
|
||||
docker build --no-cache -t dealplustech-astro:latest .
|
||||
|
||||
# Check for errors in output
|
||||
```
|
||||
|
||||
### Can't Access Easypanel
|
||||
|
||||
**Problem:** Connection timeout
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Test connection
|
||||
curl -I http://110.164.146.46:3000
|
||||
|
||||
# Check if Easypanel is running
|
||||
# Contact server administrator if needed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Post-Deployment
|
||||
|
||||
### 1. Verify Service
|
||||
|
||||
```bash
|
||||
curl http://your-easypanel-url:4321/
|
||||
```
|
||||
|
||||
Should return HTML with Thai content
|
||||
|
||||
### 2. Check Health
|
||||
|
||||
```bash
|
||||
curl -I http://your-easypanel-url:4321/
|
||||
```
|
||||
|
||||
Should return `HTTP/1.1 200 OK`
|
||||
|
||||
### 3. Setup Domain (Optional)
|
||||
|
||||
1. Go to Easypanel → Service Settings → Domains
|
||||
2. Add domain: `dealplustech.co.th`
|
||||
3. Update DNS records
|
||||
4. Enable SSL
|
||||
|
||||
### 4. Enable Auto-Deploy
|
||||
|
||||
For Git-based auto-deploy:
|
||||
1. Go to Service Settings → Git
|
||||
2. Connect repository
|
||||
3. Enable auto-deploy on push
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- **Easypanel Dashboard:** http://110.164.146.46:3000
|
||||
- **API Documentation:** http://110.164.146.46:3000/api
|
||||
- **Swagger UI:** http://110.164.146.46:3000/api (Swagger UI)
|
||||
- **Easypanel Docs:** https://docs.easypanel.io
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
For issues:
|
||||
1. Check service logs in Easypanel
|
||||
2. Review deployment script output
|
||||
3. Contact Easypanel support
|
||||
4. Check project documentation
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-03-02
|
||||
**Status:** ✅ Ready for Deployment
|
||||
@@ -1,22 +0,0 @@
|
||||
# Astro Production Dockerfile
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
# Copy all source code
|
||||
COPY . .
|
||||
|
||||
# Build Astro project (includes public folder in dist)
|
||||
RUN npm run build
|
||||
|
||||
# Expose port
|
||||
EXPOSE 4321
|
||||
|
||||
# Start Astro preview server
|
||||
CMD ["npm", "run", "preview", "--", "--host", "0.0.0.0", "--port", "4321"]
|
||||
@@ -1,310 +0,0 @@
|
||||
# Easypanel Deployment Skill
|
||||
|
||||
**Skill Name:** `easypanel-deploy`
|
||||
**Description:** Deploy Astro/Next.js/Vite apps to Easypanel with Docker
|
||||
**Version:** 1.0.0
|
||||
**Author:** Deal Plus Tech DevOps
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This skill provides a complete deployment workflow for static site generators (Astro, Next.js, Vite) to Easypanel using Docker containers.
|
||||
|
||||
---
|
||||
|
||||
## Capabilities
|
||||
|
||||
- ✅ **Dockerfile Generation** - Optimized multi-stage Dockerfile
|
||||
- ✅ **Easypanel Configuration** - Pre-configured for Easypanel deployment
|
||||
- ✅ **Git Integration** - Auto-deploy on push
|
||||
- ✅ **Environment Setup** - Environment variables and secrets
|
||||
- ✅ **Domain Configuration** - Custom domain + SSL setup
|
||||
- ✅ **Health Checks** - Container health monitoring
|
||||
- ✅ **Resource Optimization** - Recommended resource allocation
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. Easypanel instance (self-hosted or cloud)
|
||||
2. Git repository with Astro/Next.js/Vite project
|
||||
3. Docker installed (for local testing)
|
||||
|
||||
### Step 1: Prepare Project
|
||||
|
||||
```bash
|
||||
# Navigate to project
|
||||
cd your-project
|
||||
|
||||
# Ensure build script exists
|
||||
npm run build
|
||||
|
||||
# Test production build
|
||||
npm run preview
|
||||
```
|
||||
|
||||
### Step 2: Add Deployment Files
|
||||
|
||||
Create these files in project root:
|
||||
|
||||
**Dockerfile:**
|
||||
```dockerfile
|
||||
FROM node:20-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci --production
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/public ./public
|
||||
EXPOSE 4321
|
||||
CMD ["npm", "run", "preview", "--", "--host", "0.0.0.0", "--port", "4321"]
|
||||
```
|
||||
|
||||
**.dockerignore:**
|
||||
```
|
||||
node_modules
|
||||
dist
|
||||
*.log
|
||||
.git
|
||||
.env
|
||||
```
|
||||
|
||||
### Step 3: Deploy to Easypanel
|
||||
|
||||
#### Option A: Git Repository (Recommended)
|
||||
|
||||
1. Login to Easypanel
|
||||
2. **New Service** → **Git Repository**
|
||||
3. Select repository and branch
|
||||
4. Configure:
|
||||
- **Build Command:** `npm run build`
|
||||
- **Publish Directory:** `dist`
|
||||
- **Port:** `4321`
|
||||
5. Click **Deploy**
|
||||
|
||||
#### Option B: Docker Image
|
||||
|
||||
1. Build image:
|
||||
```bash
|
||||
docker build -t your-app:latest .
|
||||
docker push your-registry.com/your-app:latest
|
||||
```
|
||||
|
||||
2. Deploy on Easypanel:
|
||||
- **New Service** → **Docker Image**
|
||||
- Enter image URL
|
||||
- Set port: `4321`
|
||||
- Click **Deploy**
|
||||
|
||||
### Step 4: Configure Domain (Optional)
|
||||
|
||||
1. Go to Service Settings → **Domains**
|
||||
2. Add domain: `your-domain.com`
|
||||
3. Update DNS:
|
||||
```
|
||||
Type: CNAME
|
||||
Name: @
|
||||
Value: your-service.easypanel.app
|
||||
```
|
||||
4. Enable SSL
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|----------|----------|---------|-------------|
|
||||
| `NODE_ENV` | No | `production` | Node environment |
|
||||
| `PORT` | No | `4321` | Server port |
|
||||
| `HOST` | No | `0.0.0.0` | Bind address |
|
||||
|
||||
---
|
||||
|
||||
## Resource Recommendations
|
||||
|
||||
| Project Size | CPU | Memory | Storage |
|
||||
|--------------|-----|--------|---------|
|
||||
| **Small** (< 100MB) | 0.5 vCPU | 512MB | 1GB |
|
||||
| **Medium** (< 500MB) | 1 vCPU | 1GB | 2GB |
|
||||
| **Large** (> 500MB) | 2 vCPU | 2GB | 5GB |
|
||||
|
||||
---
|
||||
|
||||
## Health Check
|
||||
|
||||
**Endpoint:** `GET /`
|
||||
**Expected:** HTTP 200 OK
|
||||
**Timeout:** 3 seconds
|
||||
**Interval:** 30 seconds
|
||||
|
||||
### Manual Health Check
|
||||
|
||||
```bash
|
||||
curl http://your-service:4321/
|
||||
```
|
||||
|
||||
### Docker Health Check
|
||||
|
||||
```bash
|
||||
docker inspect --format='{{.State.Health.Status}}' your-container
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Fails
|
||||
|
||||
**Symptoms:** Build command returns error
|
||||
|
||||
**Solutions:**
|
||||
1. Check build logs
|
||||
2. Verify `package.json` scripts
|
||||
3. Test locally: `npm run build`
|
||||
4. Check Node version compatibility
|
||||
|
||||
### Container Crashes
|
||||
|
||||
**Symptoms:** Container exits immediately
|
||||
|
||||
**Solutions:**
|
||||
1. Check container logs: `docker logs <container>`
|
||||
2. Verify port configuration
|
||||
3. Check environment variables
|
||||
4. Review Dockerfile CMD instruction
|
||||
|
||||
### 502 Bad Gateway
|
||||
|
||||
**Symptoms:** Service returns 502 error
|
||||
|
||||
**Solutions:**
|
||||
1. Verify container is running
|
||||
2. Check port mapping
|
||||
3. Review health check status
|
||||
4. Inspect application logs
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Security
|
||||
|
||||
- ✅ Use `.dockerignore` to exclude sensitive files
|
||||
- ✅ Run as non-root user (add `USER node` to Dockerfile)
|
||||
- ✅ Use production dependencies only
|
||||
- ✅ Enable SSL for custom domains
|
||||
- ✅ Regular security updates
|
||||
|
||||
### Performance
|
||||
|
||||
- ✅ Multi-stage Docker builds
|
||||
- ✅ Minimize Docker image size
|
||||
- ✅ Enable compression (Easypanel default)
|
||||
- ✅ Use CDN for static assets
|
||||
- ✅ Configure proper caching
|
||||
|
||||
### Monitoring
|
||||
|
||||
- ✅ Enable health checks
|
||||
- ✅ Monitor resource usage
|
||||
- ✅ Set up log aggregation
|
||||
- ✅ Configure alerts for failures
|
||||
- ✅ Regular backup strategy
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Astro Project
|
||||
|
||||
```bash
|
||||
# Astro specific configuration
|
||||
npm create astro@latest
|
||||
npm run build # Output: dist/
|
||||
npm run preview # Port: 4321
|
||||
```
|
||||
|
||||
### Next.js Project
|
||||
|
||||
```bash
|
||||
# Next.js specific configuration
|
||||
npx create-next-app
|
||||
npm run build # Output: .next/
|
||||
npm run start # Port: 3000
|
||||
```
|
||||
|
||||
Update Dockerfile PORT to 3000 for Next.js.
|
||||
|
||||
### Vite Project
|
||||
|
||||
```bash
|
||||
# Vite specific configuration
|
||||
npm create vite@latest
|
||||
npm run build # Output: dist/
|
||||
npx serve dist # Port: 3000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with CI/CD
|
||||
|
||||
### GitHub Actions Example
|
||||
|
||||
```yaml
|
||||
name: Deploy to Easypanel
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build Docker Image
|
||||
run: docker build -t my-app:latest .
|
||||
|
||||
- name: Push to Registry
|
||||
run: docker push registry.com/my-app:latest
|
||||
|
||||
- name: Deploy to Easypanel
|
||||
run: |
|
||||
# Use Easypanel API or CLI
|
||||
easypanel deploy my-app:latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- **Easypanel Docs:** https://docs.easypanel.io
|
||||
- **Docker Docs:** https://docs.docker.com
|
||||
- **Astro Docs:** https://docs.astro.build
|
||||
- **Node.js Docker Best Practices:** https://github.com/nodejs/docker-node
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
1. Check troubleshooting section
|
||||
2. Review Easypanel documentation
|
||||
3. Check project logs
|
||||
4. Contact DevOps team
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-03-02
|
||||
**Skill Version:** 1.0.0
|
||||
**Status:** ✅ Production Ready
|
||||
@@ -1,164 +0,0 @@
|
||||
# 🚀 Final Deployment Guide - Deal Plus Tech Astro
|
||||
|
||||
## ✅ What's Ready
|
||||
|
||||
### 1. Docker Image
|
||||
- ✅ Built locally: `dealplustech-astro:local`
|
||||
- ✅ Size: ~564MB
|
||||
- ✅ Ready to push to registry
|
||||
|
||||
### 2. Easypanel Integration
|
||||
- ✅ API token configured
|
||||
- ✅ Project identified: `customerwebsite`
|
||||
- ✅ Deployment script ready
|
||||
|
||||
### 3. Registry Options
|
||||
|
||||
**Option A: Easypanel Registry** (If available)
|
||||
```bash
|
||||
URL: 110.164.146.46:3001
|
||||
Status: ⚠️ Port 3001 not accessible
|
||||
```
|
||||
|
||||
**Option B: Docker Hub** (Recommended fallback)
|
||||
```bash
|
||||
URL: hub.docker.com
|
||||
Status: ✅ Available
|
||||
```
|
||||
|
||||
**Option C: Direct Server Load** (If SSH access)
|
||||
```bash
|
||||
Server: 110.164.146.46
|
||||
Status: ⚠️ Requires SSH credentials
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Recommended Deployment Path
|
||||
|
||||
### Step 1: Push to Docker Hub (5 minutes)
|
||||
|
||||
```bash
|
||||
# 1. Login to Docker Hub
|
||||
docker login
|
||||
|
||||
# 2. Tag image
|
||||
docker tag dealplustech-astro:local yourusername/dealplustech-astro:latest
|
||||
|
||||
# 3. Push
|
||||
docker push yourusername/dealplustech-astro:latest
|
||||
```
|
||||
|
||||
### Step 2: Create Service in Easypanel (2 minutes)
|
||||
|
||||
```
|
||||
1. Open: http://110.164.146.46:3000
|
||||
2. Project: customerwebsite
|
||||
3. New Service → Docker image
|
||||
4. Image: yourusername/dealplustech-astro:latest
|
||||
5. Port: 4321
|
||||
6. Deploy
|
||||
7. Copy Service ID
|
||||
```
|
||||
|
||||
### Step 3: Register Service ID
|
||||
|
||||
```bash
|
||||
cd dealplustech-astro
|
||||
./skills/easypanel-deploy/deploy.sh register svc_xxx...
|
||||
```
|
||||
|
||||
### Step 4: Verify
|
||||
|
||||
```bash
|
||||
./skills/easypanel-deploy/deploy.sh status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Future Updates
|
||||
|
||||
Once registered:
|
||||
|
||||
```bash
|
||||
# Make changes
|
||||
git pull
|
||||
|
||||
# Update (automatically rebuilds and pushes)
|
||||
./skills/easypanel-deploy/deploy.sh update
|
||||
|
||||
# Redeploy in Easypanel dashboard
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Quick Commands
|
||||
|
||||
```bash
|
||||
# Deploy (builds + instructions)
|
||||
./skills/easypanel-deploy/deploy.sh deploy
|
||||
|
||||
# Register service
|
||||
./skills/easypanel-deploy/deploy.sh register SERVICE_ID
|
||||
|
||||
# Update (rebuild + push)
|
||||
./skills/easypanel-deploy/deploy.sh update
|
||||
|
||||
# Check status
|
||||
./skills/easypanel-deploy/deploy.sh status
|
||||
|
||||
# List projects
|
||||
./skills/easypanel-deploy/deploy.sh list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 Files Created
|
||||
|
||||
```
|
||||
dealplustech-astro/
|
||||
├── skills/easypanel-deploy/
|
||||
│ ├── deploy.sh ✅ v3.2 Smart deployment
|
||||
│ ├── SKILL_v2.md ✅ Full documentation
|
||||
│ ├── README.md ✅ Quick start
|
||||
│ └── AUTOMATIC_DEPLOYMENT.md ✅ Automation details
|
||||
├── DEPLOYMENT_OPTIONS.md ✅ Registry options
|
||||
├── FINAL_DEPLOYMENT_GUIDE.md ✅ This file
|
||||
└── easypanel.config.json ✅ Configuration
|
||||
|
||||
~/.easypanel/
|
||||
├── credentials ✅ API token
|
||||
└── state.json ✅ Service IDs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist
|
||||
|
||||
- [x] Docker image built
|
||||
- [x] Easypanel token configured
|
||||
- [x] Project identified (customerwebsite)
|
||||
- [ ] Push to Docker Hub
|
||||
- [ ] Create service in Easypanel
|
||||
- [ ] Register service ID
|
||||
- [ ] Verify deployment
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Next Action
|
||||
|
||||
**Push to Docker Hub:**
|
||||
|
||||
```bash
|
||||
docker login
|
||||
docker tag dealplustech-astro:local yourusername/dealplustech-astro:latest
|
||||
docker push yourusername/dealplustech-astro:latest
|
||||
```
|
||||
|
||||
**Then create service in Easypanel!**
|
||||
|
||||
---
|
||||
|
||||
**Status:** Ready for Docker Hub push
|
||||
**Estimated Time:** 10 minutes
|
||||
**Difficulty:** Easy
|
||||
@@ -1,167 +0,0 @@
|
||||
# 🔧 Fixes Applied - Product Tables & Responsive Fonts
|
||||
|
||||
## ✅ Issue 1: Product Tables Disappeared
|
||||
|
||||
### Problem
|
||||
The product specification tables (extracted from images) were not showing on product pages in the Astro migration.
|
||||
|
||||
### Root Cause
|
||||
- The Astro product detail page template (`[slug].astro`) was missing
|
||||
- Product tables data exists in `src/data/site-config.ts` but wasn't being rendered
|
||||
- Markdown files don't include table data (tables are in TypeScript file)
|
||||
|
||||
### Solution
|
||||
Created `src/pages/products/[slug].astro` with:
|
||||
1. **Table Rendering Section** - Displays all `productTables` from site-config
|
||||
2. **Proper Styling** - Matches original Next.js design with:
|
||||
- Table name headers
|
||||
- Alternating row colors
|
||||
- Responsive table containers
|
||||
- Proper borders and shadows
|
||||
|
||||
### Code Added
|
||||
```astro
|
||||
{productTables.length > 0 && (
|
||||
<section class="mb-12">
|
||||
<h2>ตารางข้อมูลผลิตภัณฑ์</h2>
|
||||
{productTables.map((table) => (
|
||||
<div class="bg-white rounded-2xl...">
|
||||
<h3>{table.tableName}</h3>
|
||||
<table>
|
||||
<thead>{table.headers.map(...)} </thead>
|
||||
<tbody>{table.rows.map(...)} </tbody>
|
||||
</table>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
```
|
||||
|
||||
### Data Source
|
||||
Tables are loaded from `src/data/site-config.ts` via `productCategories.find(p => p.id === product.data.id).productTables`
|
||||
|
||||
Products with tables:
|
||||
- ✅ ppr-welder (1 table)
|
||||
- ✅ poloplast (4 tables)
|
||||
- ✅ syler (2 tables)
|
||||
- ✅ xylent (3 tables)
|
||||
- ✅ pvc/upvc (6 tables)
|
||||
- ✅ realflex (3 tables)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Issue 2: Font Too Small on Large Screens
|
||||
|
||||
### Problem
|
||||
Text was not scaling properly on larger screens (desktop, 4K monitors).
|
||||
|
||||
### Root Cause
|
||||
- Base font size was fixed at browser default (16px)
|
||||
- No responsive scaling for larger viewports
|
||||
- Tailwind classes weren't enough for very large screens
|
||||
|
||||
### Solution
|
||||
Added responsive font scaling in `src/styles/global.css`:
|
||||
|
||||
```css
|
||||
/* Base font size */
|
||||
html {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Scale up for larger screens */
|
||||
@media (min-width: 1280px) {
|
||||
html { font-size: 18px; }
|
||||
}
|
||||
|
||||
@media (min-width: 1536px) {
|
||||
html { font-size: 20px; }
|
||||
}
|
||||
|
||||
@media (min-width: 1920px) {
|
||||
html { font-size: 22px; }
|
||||
}
|
||||
|
||||
@media (min-width: 2560px) {
|
||||
html { font-size: 24px; }
|
||||
}
|
||||
```
|
||||
|
||||
### Additional Responsive Text Classes
|
||||
```css
|
||||
.text-responsive-sm { text-sm md:text-base lg:text-lg xl:text-xl; }
|
||||
.text-responsive-base { text-base md:text-lg lg:text-xl xl:text-2xl; }
|
||||
.text-responsive-lg { text-lg md:text-xl lg:text-2xl xl:text-3xl; }
|
||||
.text-responsive-xl { text-xl md:text-2xl lg:text-3xl xl:text-4xl; }
|
||||
```
|
||||
|
||||
### Updated Components
|
||||
All major text elements now use responsive sizing:
|
||||
- **Buttons**: `text-base md:text-lg`
|
||||
- **Section titles**: `text-3xl md:text-4xl lg:text-5xl xl:text-6xl`
|
||||
- **Table headers**: `text-xl md:text-2xl lg:text-3xl`
|
||||
- **Table cells**: `text-base md:text-lg lg:text-xl`
|
||||
- **Features/FAQ**: `text-base md:text-lg lg:text-xl`
|
||||
|
||||
---
|
||||
|
||||
## 📊 Before & After Comparison
|
||||
|
||||
| Screen Size | Before | After |
|
||||
|-------------|--------|-------|
|
||||
| **Mobile (< 768px)** | 16px base | 16px base ✓ |
|
||||
| **Tablet (768-1280px)** | 16px base | 16px base ✓ |
|
||||
| **Desktop (1280-1536px)** | 16px base | **18px base** ↑ |
|
||||
| **Large (1536-1920px)** | 16px base | **20px base** ↑ |
|
||||
| **XL (1920-2560px)** | 16px base | **22px base** ↑ |
|
||||
| **4K (> 2560px)** | 16px base | **24px base** ↑ |
|
||||
|
||||
### Font Scaling Examples
|
||||
|
||||
**Product Title:**
|
||||
- Before: `text-4xl` (36px fixed)
|
||||
- After: `text-4xl md:text-5xl lg:text-6xl xl:text-7xl` (36px → 60px)
|
||||
|
||||
**Table Headers:**
|
||||
- Before: `text-lg` (18px fixed)
|
||||
- After: `text-xl md:text-2xl lg:text-3xl` (20px → 30px)
|
||||
|
||||
**Body Text:**
|
||||
- Before: `text-base` (16px fixed)
|
||||
- After: Responsive + base font scaling (16px → 24px)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Testing Checklist
|
||||
|
||||
### Product Tables
|
||||
- [ ] Visit any product page (e.g., `/pp-r-pp-rct-poloplast/`)
|
||||
- [ ] Scroll to "ตารางข้อมูลผลิตภัณฑ์" section
|
||||
- [ ] Verify tables are visible with proper styling
|
||||
- [ ] Check table headers have green background
|
||||
- [ ] Check rows alternate white/gray
|
||||
- [ ] Verify tables scroll horizontally on mobile
|
||||
|
||||
### Responsive Fonts
|
||||
- [ ] Test on mobile (375px) - text should be readable
|
||||
- [ ] Test on tablet (768px) - text slightly larger
|
||||
- [ ] Test on desktop (1920px) - text noticeably larger
|
||||
- [ ] Test on 4K (3840px) - text should be large and clear
|
||||
- [ ] Check product titles scale properly
|
||||
- [ ] Check table text scales properly
|
||||
- [ ] Check buttons scale properly
|
||||
|
||||
---
|
||||
|
||||
## 📁 Files Modified
|
||||
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `src/pages/products/[slug].astro` | ✅ Created - product detail page with tables |
|
||||
| `src/styles/global.css` | ✅ Updated - responsive font scaling |
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ Both issues fixed
|
||||
**Build:** ✅ Passing
|
||||
**Next:** Test on actual device/screen sizes
|
||||
@@ -1,231 +0,0 @@
|
||||
# 🚀 Gitea Repository Deployment - BEST METHOD!
|
||||
|
||||
## ✅ Why This is Better
|
||||
|
||||
| Method | Docker Registry | Gitea Repo |
|
||||
|--------|----------------|------------|
|
||||
| Build locally | ✅ Required | ❌ Not needed |
|
||||
| Push to registry | ✅ Required | ❌ Not needed |
|
||||
| Easypanel builds | ❌ No | ✅ Yes! |
|
||||
| Auto-deploy on push | ❌ No | ✅ Yes! |
|
||||
| Version control | ❌ No | ✅ Yes! |
|
||||
| Rollbacks | ❌ Hard | ✅ Easy |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 How It Works
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||
│ You Git │ │ Easypanel │ │ Deployed │
|
||||
│ Push │────▶│ Clones & │────▶│ Service │
|
||||
│ to Gitea │ │ Builds │ │ Running │
|
||||
└─────────────┘ └──────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
**Easypanel will:**
|
||||
1. Clone your Gitea repository
|
||||
2. Run `npm install`
|
||||
3. Run `npm run build`
|
||||
4. Deploy the `dist/` folder
|
||||
5. Serve on port 4321
|
||||
|
||||
---
|
||||
|
||||
## 📋 Setup Steps
|
||||
|
||||
### Step 1: Run Deploy Script
|
||||
|
||||
```bash
|
||||
cd dealplustech-astro
|
||||
./skills/easypanel-deploy/deploy.sh deploy
|
||||
```
|
||||
|
||||
**It will show:**
|
||||
```
|
||||
Gitea URL: http://110.164.146.46:3001
|
||||
Repository: dealplustech/dealplustech-astro
|
||||
Branch: main
|
||||
Build Command: npm run build
|
||||
Publish Directory: dist
|
||||
Port: 4321
|
||||
```
|
||||
|
||||
### Step 2: Create Service in Easypanel
|
||||
|
||||
```
|
||||
1. Open: http://110.164.146.46:3000
|
||||
|
||||
2. Select Project: customerwebsite
|
||||
|
||||
3. Click: New Service → Git Repository
|
||||
|
||||
4. Configure:
|
||||
┌─────────────────────────────────────┐
|
||||
│ Repository URL: │
|
||||
│ http://110.164.146.46:3001/ │
|
||||
│ dealplustech/dealplustech-astro │
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────┐
|
||||
│ Branch: main │
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────┐
|
||||
│ Build Command: npm run build │
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────┐
|
||||
│ Publish Directory: dist │
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────┐
|
||||
│ Port: 4321 │
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
5. Click: Deploy
|
||||
```
|
||||
|
||||
### Step 3: Wait for Build
|
||||
|
||||
Easypanel will:
|
||||
- ✅ Clone from Gitea (~10 seconds)
|
||||
- ✅ Run `npm install` (~30 seconds)
|
||||
- ✅ Run `npm run build` (~20 seconds)
|
||||
- ✅ Deploy `dist/` (~5 seconds)
|
||||
|
||||
**Total: ~1 minute**
|
||||
|
||||
### Step 4: Register Service ID
|
||||
|
||||
After deployment completes:
|
||||
|
||||
```bash
|
||||
# Copy Service ID from Easypanel dashboard
|
||||
./skills/easypanel-deploy/deploy.sh register svc_xxx...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Automatic Updates
|
||||
|
||||
### Enable Auto-Deploy
|
||||
|
||||
**In Easypanel:**
|
||||
1. Go to service settings
|
||||
2. Click: Git → Auto Deploy
|
||||
3. Enable: Auto Deploy on Push
|
||||
|
||||
**Then:**
|
||||
```bash
|
||||
# Make changes
|
||||
git add .
|
||||
git commit -m "Update something"
|
||||
git push
|
||||
|
||||
# Easypanel automatically rebuilds! 🎉
|
||||
```
|
||||
|
||||
### Manual Redeploy
|
||||
|
||||
```bash
|
||||
./skills/easypanel-deploy/deploy.sh redeploy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Benefits
|
||||
|
||||
### 1. No Docker Registry
|
||||
- ❌ No `docker build`
|
||||
- ❌ No `docker push`
|
||||
- ❌ No registry credentials
|
||||
|
||||
### 2. Automatic Deployments
|
||||
- ✅ Push to Gitea
|
||||
- ✅ Easypanel rebuilds
|
||||
- ✅ Zero manual steps
|
||||
|
||||
### 3. Version Control
|
||||
- ✅ Every commit = potential deployment
|
||||
- ✅ Easy rollbacks
|
||||
- ✅ Change history
|
||||
|
||||
### 4. Build Caching
|
||||
- ✅ `node_modules` cached
|
||||
- ✅ Faster builds
|
||||
- ✅ Efficient
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
### Gitea Repository
|
||||
|
||||
```bash
|
||||
URL: http://110.164.146.46:3001
|
||||
Owner: dealplustech
|
||||
Repo: dealplustech-astro
|
||||
Branch: main
|
||||
```
|
||||
|
||||
### Build Settings
|
||||
|
||||
```bash
|
||||
Build Command: npm run build
|
||||
Publish Directory: dist/
|
||||
Node Version: 20 (from .nvmrc or package.json)
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Add in Easypanel service settings:
|
||||
|
||||
```bash
|
||||
NODE_ENV=production
|
||||
NEXT_PUBLIC_SITE_URL=https://your-domain.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 Required Files
|
||||
|
||||
Your repository should have:
|
||||
|
||||
```
|
||||
dealplustech-astro/
|
||||
├── package.json ✅ Required (defines build)
|
||||
├── Dockerfile ⚠️ Optional (for Docker mode)
|
||||
├── astro.config.mjs ✅ Required
|
||||
├── src/ ✅ Required
|
||||
└── .gitignore ✅ Required
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Next Action
|
||||
|
||||
**Deploy now:**
|
||||
|
||||
```bash
|
||||
# 1. Run deployment script
|
||||
./skills/easypanel-deploy/deploy.sh deploy
|
||||
|
||||
# 2. Follow instructions to create service in Easypanel
|
||||
|
||||
# 3. After deployment:
|
||||
./skills/easypanel-deploy/deploy.sh register SERVICE_ID
|
||||
|
||||
# 4. Enable auto-deploy in Easypanel
|
||||
|
||||
# 5. Future updates: just git push!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**This is THE BEST way to deploy!** 🎉
|
||||
|
||||
- No Docker needed
|
||||
- No registry needed
|
||||
- Automatic deployments
|
||||
- Full version control
|
||||
@@ -1,249 +0,0 @@
|
||||
# 🎉 Deal Plus Tech - Astro Migration COMPLETE
|
||||
|
||||
**Migration Date:** 2026-03-02
|
||||
**Status:** ✅ Core Infrastructure & Content Migration Complete
|
||||
**Build Status:** ✅ Passing
|
||||
**Files Created:** 1,200+ lines of code
|
||||
|
||||
---
|
||||
|
||||
## ✅ COMPLETED (7/11 tasks)
|
||||
|
||||
### Phase 1: Foundation ✅
|
||||
1. ✅ Codebase Analysis - Mapped Next.js structure
|
||||
2. ✅ Astro Project Setup - Created with Tailwind 4
|
||||
3. ✅ Theme Migration - Industrial design system
|
||||
|
||||
### Phase 2: Core Components ✅
|
||||
4. ✅ Layouts - Header, Footer, BaseLayout (384 lines)
|
||||
5. ✅ Product Data - Content Collections schema
|
||||
6. ✅ Product Pages - 6 products migrated + templates
|
||||
|
||||
### Phase 3: Content Systems ✅
|
||||
7. ✅ **Blog System** - Full migration with 3 posts
|
||||
|
||||
---
|
||||
|
||||
## 📁 FINAL FILE STRUCTURE
|
||||
|
||||
```
|
||||
dealplustech-astro/
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ │ ├── Header.astro ✅ 223 lines (mobile menu + JS)
|
||||
│ │ ├── Footer.astro ✅ 115 lines
|
||||
│ │ ├── ProductCard.astro ✅ 38 lines
|
||||
│ │ └── BlogCard.astro ✅ 53 lines
|
||||
│ ├── layouts/
|
||||
│ │ └── BaseLayout.astro ✅ 46 lines (SEO + Thai support)
|
||||
│ ├── pages/
|
||||
│ │ ├── products/
|
||||
│ │ │ ├── index.astro ✅ Product listing
|
||||
│ │ │ └── [slug].astro ✅ Dynamic pages
|
||||
│ │ └── blog/
|
||||
│ │ ├── index.astro ✅ Blog listing
|
||||
│ │ └── [slug].astro ✅ Blog posts
|
||||
│ ├── content/
|
||||
│ │ ├── config.ts ✅ Products + Blog schemas
|
||||
│ │ ├── products/ ✅ 6 products
|
||||
│ │ │ ├── ppr-elephant.md
|
||||
│ │ │ ├── thai-ppr.md
|
||||
│ │ │ ├── poloplast.md
|
||||
│ │ │ ├── hdpe.md
|
||||
│ │ │ ├── syler.md
|
||||
│ │ │ └── xylent.md
|
||||
│ │ └── blog/ ✅ 3 posts (copied from Next.js)
|
||||
│ ├── data/
|
||||
│ │ ├── site-config.ts ✅ 116 lines (nav + config)
|
||||
│ │ └── utils.ts ✅ 43 lines (helpers)
|
||||
│ └── styles/
|
||||
│ └── global.css ✅ 114 lines (theme)
|
||||
└── dist/ ✅ Built output
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 MIGRATION METRICS
|
||||
|
||||
| Category | Next.js | Astro | Status |
|
||||
|----------|---------|-------|--------|
|
||||
| **Layouts** | 3 React | 3 Astro | ✅ Complete |
|
||||
| **Products** | 36 in config | 6 migrated | ✅ MVP Ready |
|
||||
| **Blog** | 3 posts | 3 migrated | ✅ Complete |
|
||||
| **Components** | 8 React | 4 Astro | ✅ Core done |
|
||||
| **Theme** | Tailwind 3 | Tailwind 4 | ✅ Upgraded |
|
||||
| **Build Size** | ~2.5MB | ~800KB | ✅ 68% smaller |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 BUILD OUTPUT
|
||||
|
||||
```bash
|
||||
✅ Build completed in 225ms
|
||||
✅ Generated routes:
|
||||
- /products/index.html
|
||||
- /blog/index.html
|
||||
- /blog/[slug].html (3 posts)
|
||||
- Products: 6 dynamic routes
|
||||
```
|
||||
|
||||
**Build Performance:**
|
||||
- **Next.js:** ~8-12 seconds
|
||||
- **Astro:** ~225ms
|
||||
- **Speedup:** 35-50x faster ⚡
|
||||
|
||||
---
|
||||
|
||||
## 📝 CONTENT MIGRATED
|
||||
|
||||
### Products (6/36 - Key Products)
|
||||
1. ✅ ppr-elephant - ท่อพีพีอาร์ตราช้าง (SCG)
|
||||
2. ✅ thai-ppr - ท่อ PPR Thai PPR
|
||||
3. ✅ poloplast - ท่อ PP-R/PP-RCT POLOPLAST (Germany)
|
||||
4. ✅ hdpe - ท่อ HDPE
|
||||
5. ✅ syler - ท่อไซเลอร์ (Fire Protection)
|
||||
6. ✅ xylent - ท่อระบายน้ำ 3 ชั้น XYLENT (Silent)
|
||||
|
||||
### Blog Posts (3/3)
|
||||
1. ✅ ข้อดี-ท่อ-hdpe.md
|
||||
2. ✅ ท่อ-ppr-คืออะไร.md
|
||||
3. ✅ บำรุงรักษาปั๊มน้ำ.md
|
||||
|
||||
---
|
||||
|
||||
## ⏳ REMAINING WORK (4/11 tasks)
|
||||
|
||||
### High Priority
|
||||
8. ⏳ Homepage & Static Pages (About, Contact, Services, etc.)
|
||||
9. ⏳ FloatingContact widget (React island)
|
||||
|
||||
### Medium Priority
|
||||
10. ⏳ Easypanel deployment setup
|
||||
|
||||
### Low Priority
|
||||
11. ⏳ Create Easypanel skill
|
||||
|
||||
---
|
||||
|
||||
## 🎯 PRODUCTION READINESS
|
||||
|
||||
| Requirement | Status | Notes |
|
||||
|-------------|--------|-------|
|
||||
| **Product Showcase** | ✅ Ready | 6 key products migrated |
|
||||
| **Blog System** | ✅ Ready | All posts migrated |
|
||||
| **Mobile Responsive** | ✅ Ready | Header/Footer tested |
|
||||
| **SEO** | ✅ Ready | Metadata + schema ready |
|
||||
| **Performance** | ✅ Excellent | 35-50x faster build |
|
||||
| **Thai Language** | ✅ Ready | Full support |
|
||||
|
||||
**MVP Status: ✅ READY FOR DEPLOYMENT**
|
||||
|
||||
---
|
||||
|
||||
## 📈 PERFORMANCE GAINS
|
||||
|
||||
### Build Performance
|
||||
- **Next.js:** 8-12s
|
||||
- **Astro:** 225ms
|
||||
- **Improvement:** ⚡ **35-50x faster**
|
||||
|
||||
### Bundle Size
|
||||
- **Next.js:** ~2.5MB (React + dependencies)
|
||||
- **Astro:** ~800KB (zero JS by default)
|
||||
- **Reduction:** 📉 **68% smaller**
|
||||
|
||||
### Runtime Performance
|
||||
- **Next.js:** React hydration required
|
||||
- **Astro:** Zero JS (static HTML)
|
||||
- **Improvement:** ⚡ **Instant load**
|
||||
|
||||
---
|
||||
|
||||
## 🔧 TECHNICAL DECISIONS
|
||||
|
||||
### Why Astro?
|
||||
1. **Zero JS by default** - Better performance
|
||||
2. **Content Collections** - Type-safe content
|
||||
3. **Markdown support** - Native blog integration
|
||||
4. **Smaller bundles** - Faster loading
|
||||
5. **Better SEO** - Pre-rendered HTML
|
||||
|
||||
### Migration Strategy
|
||||
- **Content Collections** - All products/blog as Markdown
|
||||
- **Static Pre-rendering** - All pages pre-built
|
||||
- **Progressive Enhancement** - Add JS only where needed
|
||||
- **Island Architecture** - React only for FloatingContact
|
||||
|
||||
---
|
||||
|
||||
## 📋 NEXT STEPS FOR USER
|
||||
|
||||
### Option 1: Deploy Now (Recommended)
|
||||
The MVP is ready with:
|
||||
- ✅ Product showcase (6 products)
|
||||
- ✅ Blog system (3 posts)
|
||||
- ✅ Mobile responsive
|
||||
- ✅ SEO optimized
|
||||
|
||||
**Deploy to Easypanel and test!**
|
||||
|
||||
### Option 2: Complete Remaining
|
||||
- Migrate remaining 30 products (copy-paste from Next.js)
|
||||
- Create homepage
|
||||
- Add FloatingContact widget
|
||||
|
||||
**Estimated time: 2-3 hours**
|
||||
|
||||
### Option 3: Hybrid
|
||||
Deploy MVP now, complete content migration incrementally.
|
||||
|
||||
---
|
||||
|
||||
## 🎓 LESSONS LEARNED
|
||||
|
||||
### What Worked Well
|
||||
1. **Content Collections** - Perfect for product catalogs
|
||||
2. **Markdown migration** - Straightforward copy-paste
|
||||
3. **Tailwind 4** - Works seamlessly with Astro
|
||||
4. **Mobile menu** - Vanilla JS in Astro components
|
||||
|
||||
### Challenges
|
||||
1. **Import paths** - Relative path resolution in Astro
|
||||
2. **getStaticPaths** - Required for dynamic routes
|
||||
3. **Blog schema** - Flexible field names (category/categories)
|
||||
|
||||
### Best Practices
|
||||
1. **Start with schema** - Define content structure first
|
||||
2. **Test builds early** - Catch path issues quickly
|
||||
3. **Use Markdown** - Content is easier to manage
|
||||
4. **Progressive enhancement** - Add JS only when needed
|
||||
|
||||
---
|
||||
|
||||
## 📞 SUPPORT
|
||||
|
||||
**Migration Documentation:** `dealplustech-astro/MIGRATION_STATUS.md`
|
||||
**Build Logs:** Check `dist/` folder
|
||||
**Content:** `src/content/` directory
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-03-02 09:18 AM
|
||||
**Build Status:** ✅ Passing
|
||||
**Migration Progress:** 64% Complete (7/11 tasks)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 READY TO DEPLOY!
|
||||
|
||||
The Astro migration is **production-ready** for MVP launch.
|
||||
|
||||
**Key Features Working:**
|
||||
- ✅ Product showcase with 6 key products
|
||||
- ✅ Blog system with all 3 posts
|
||||
- ✅ Mobile-responsive layouts
|
||||
- ✅ SEO-optimized pages
|
||||
- ✅ Thai language support
|
||||
- ✅ Industrial theme
|
||||
|
||||
**Deploy to Easypanel and test!** 🎉
|
||||
@@ -1,199 +0,0 @@
|
||||
# Deal Plus Tech - Astro Migration Status
|
||||
|
||||
**Generated:** 2026-03-02
|
||||
**Migration Approach:** Full rewrite (Option C - Focused)
|
||||
|
||||
---
|
||||
|
||||
## ✅ COMPLETED (4/11 tasks)
|
||||
|
||||
### 1. ✅ Project Setup
|
||||
- Created Astro project: `dealplustech-astro/`
|
||||
- Tailwind 4 configured
|
||||
- TypeScript strict mode enabled
|
||||
- Kanit font support configured
|
||||
|
||||
### 2. ✅ Theme Migration
|
||||
- Industrial theme colors (Primary green, Secondary slate, Accent yellow)
|
||||
- Custom shadows (card, industrial, bold)
|
||||
- All component classes migrated (btn-primary, card, section-title, etc.)
|
||||
- 70 lines of custom CSS
|
||||
|
||||
### 3. ✅ Core Infrastructure
|
||||
- **Content Collections** configured with full product schema
|
||||
- **BaseLayout.astro** - HTML shell with Thai SEO
|
||||
- **Header.astro** - Fully functional with mobile menu
|
||||
- **Footer.astro** - Complete with all sections
|
||||
- **utils.ts** - Helper functions (cn, formatPrice, etc.)
|
||||
- **site-config.ts** - Navigation + company info
|
||||
|
||||
### 4. ✅ Example Product
|
||||
- **ppr-elephant.md** - Full migration example (161 lines)
|
||||
- Demonstrates Markdown frontmatter + content structure
|
||||
- All fields mapped from Next.js structure
|
||||
|
||||
---
|
||||
|
||||
## 📁 CREATED FILES
|
||||
|
||||
```
|
||||
dealplustech-astro/
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ │ ├── Header.astro ✅ 223 lines (mobile menu + JS)
|
||||
│ │ └── Footer.astro ✅ 115 lines
|
||||
│ ├── layouts/
|
||||
│ │ └── BaseLayout.astro ✅ 46 lines
|
||||
│ ├── pages/
|
||||
│ │ └── products/
|
||||
│ │ ├── index.astro ✅ Product listing
|
||||
│ │ └── [slug].astro ✅ Dynamic product pages
|
||||
│ ├── content/
|
||||
│ │ ├── config.ts ✅ Product schema
|
||||
│ │ └── products/
|
||||
│ │ └── ppr-elephant.md ✅ Example product
|
||||
│ ├── data/
|
||||
│ │ ├── site-config.ts ✅ 116 lines
|
||||
│ │ └── README.md ⏳ Pending
|
||||
│ ├── lib/
|
||||
│ │ └── utils.ts ✅ 43 lines
|
||||
│ └── styles/
|
||||
│ └── global.css ✅ 114 lines (theme)
|
||||
└── astro.config.mjs ✅ Tailwind 4
|
||||
```
|
||||
|
||||
**Total: ~850 lines of code created**
|
||||
|
||||
---
|
||||
|
||||
## ⏳ REMAINING WORK (7/11 tasks)
|
||||
|
||||
### 5. ⏳ Migrate Product Data (36 products)
|
||||
**Status:** 1/36 complete (ppr-elephant)
|
||||
**Remaining:** 35 products to convert to Markdown
|
||||
|
||||
| Priority | Product ID | Status |
|
||||
|----------|------------|--------|
|
||||
| ✅ Done | ppr-elephant | Complete |
|
||||
| ⏳ High | thai-ppr | Pending |
|
||||
| ⏳ High | poloplast | Pending |
|
||||
| ⏳ High | hdpe | Pending |
|
||||
| ⏳ High | pvc | Pending |
|
||||
| ⏳ Medium | syler | Pending |
|
||||
| ⏳ Medium | xylent | Pending |
|
||||
| ⏳ Medium | realflex | Pending |
|
||||
| ⏳ Low | (27 more products) | Pending |
|
||||
|
||||
**Estimate:** 4-6 hours (10 min/product)
|
||||
|
||||
---
|
||||
|
||||
### 6. ⏳ Migrate Static Pages
|
||||
**Status:** 0/7 pages
|
||||
|
||||
| Page | Next.js Route | Status |
|
||||
|------|---------------|--------|
|
||||
| Homepage | `/` | ⏳ Pending |
|
||||
| About Us | `/about-us/` | ⏳ Pending |
|
||||
| Contact Us | `/contact-us/` | ⏳ Pending |
|
||||
| Portfolio | `/all-projects/` | ⏳ Pending |
|
||||
| Services | `/services/` | ⏳ Pending |
|
||||
| Sales Engineer | `/sales-engineer/` | ⏳ Pending |
|
||||
| Join Us | `/join-us/` | ⏳ Pending |
|
||||
|
||||
**Estimate:** 2-3 hours
|
||||
|
||||
---
|
||||
|
||||
### 7. ⏳ Migrate Blog System
|
||||
**Status:** Not started
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Create blog collection schema
|
||||
- [ ] Migrate WordPress integration
|
||||
- [ ] Create blog listing page
|
||||
- [ ] Create blog post template
|
||||
|
||||
**Estimate:** 2-3 hours
|
||||
|
||||
---
|
||||
|
||||
### 8. ⏳ Migrate FloatingContact Widget
|
||||
**Status:** Not started
|
||||
|
||||
**Current:** React component (client-side floating widget)
|
||||
**Migration:** Keep as React island with `client:load`
|
||||
|
||||
**Estimate:** 1 hour
|
||||
|
||||
---
|
||||
|
||||
### 9. ⏳ Easypanel Deployment Setup
|
||||
**Status:** Not started
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Create Dockerfile
|
||||
- [ ] Configure build command
|
||||
- [ ] Setup environment variables
|
||||
- [ ] Test deployment
|
||||
|
||||
**Estimate:** 1-2 hours
|
||||
|
||||
---
|
||||
|
||||
### 10. ⏳ Create Easypanel Skill
|
||||
**Status:** Not started
|
||||
|
||||
**Estimate:** 2-3 hours
|
||||
|
||||
---
|
||||
|
||||
## 📊 OVERALL PROGRESS
|
||||
|
||||
| Category | Progress | Status |
|
||||
|----------|----------|--------|
|
||||
| **Infrastructure** | 100% | ✅ Complete |
|
||||
| **Layouts** | 100% | ✅ Complete |
|
||||
| **Products** | 3% | ⏳ 1/36 done |
|
||||
| **Static Pages** | 0% | ⏳ Pending |
|
||||
| **Blog** | 0% | ⏳ Pending |
|
||||
| **Deployment** | 0% | ⏳ Pending |
|
||||
|
||||
**Overall: ~36% complete** (infrastructure done, content migration in progress)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 RECOMMENDED NEXT STEPS
|
||||
|
||||
1. **Migrate 5 key products** (PPR, HDPE, PVC, Syler, POLOPLAST) - 1 hour
|
||||
2. **Create homepage** - 1 hour
|
||||
3. **Setup deployment** - 1 hour
|
||||
4. **Test and verify** - 1 hour
|
||||
|
||||
**Then:** Migrate remaining products and blog incrementally
|
||||
|
||||
---
|
||||
|
||||
## 🎯 PRODUCTION READY WHEN
|
||||
|
||||
- [ ] 5 core products migrated
|
||||
- [ ] Homepage + About + Contact pages done
|
||||
- [ ] Deployment configured and tested
|
||||
- [ ] All links working
|
||||
- [ ] SEO metadata verified
|
||||
|
||||
**Estimated time to MVP:** 4-6 hours
|
||||
|
||||
---
|
||||
|
||||
## 📝 NOTES
|
||||
|
||||
- Product data conversion is straightforward (copy frontmatter, paste content)
|
||||
- All styling preserved and working
|
||||
- Mobile menu tested and functional
|
||||
- Thai language fully supported
|
||||
- SEO schema ready to implement
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-03-02 09:00 AM
|
||||
@@ -1 +0,0 @@
|
||||
web: npm run preview -- --host 0.0.0.0 --port $PORT
|
||||
@@ -1,43 +0,0 @@
|
||||
# Astro Starter Kit: Minimal
|
||||
|
||||
```sh
|
||||
npm create astro@latest -- --template minimal
|
||||
```
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```text
|
||||
/
|
||||
├── public/
|
||||
├── src/
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.json
|
||||
```
|
||||
|
||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
|
||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||
@@ -1,11 +0,0 @@
|
||||
// @ts-check
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
vite: {
|
||||
plugins: [tailwindcss()]
|
||||
}
|
||||
});
|
||||
@@ -1,101 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Easypanel Deployment Script for Deal Plus Tech Astro
|
||||
# Usage: ./deploy-easypanel.sh [api-token]
|
||||
|
||||
set -e
|
||||
|
||||
EASYPANEL_URL="http://110.164.146.46:3000"
|
||||
APP_NAME="dealplustech-astro"
|
||||
PROJECT_NAME="dealplustech"
|
||||
DOCKER_IMAGE="dealplustech-astro:latest"
|
||||
PORT=4321
|
||||
API_TOKEN="${1:-$EASYPANEL_API_TOKEN}"
|
||||
|
||||
echo "============================================================"
|
||||
echo "🚀 Deploying to Easypanel"
|
||||
echo "============================================================"
|
||||
|
||||
# Check if API token is provided
|
||||
if [ -z "$API_TOKEN" ]; then
|
||||
echo "⚠️ No API token provided"
|
||||
echo ""
|
||||
echo "📋 Manual Deployment Steps:"
|
||||
echo "1. Open Easypanel: $EASYPANEL_URL"
|
||||
echo "2. Create/Select project: $PROJECT_NAME"
|
||||
echo "3. Click 'New Service' → 'Docker Image'"
|
||||
echo "4. Enter image: $DOCKER_IMAGE"
|
||||
echo "5. Set port: $PORT"
|
||||
echo "6. Click 'Deploy'"
|
||||
echo ""
|
||||
echo "💡 To enable automated deployment:"
|
||||
echo " 1. Get API token from Easypanel (Settings → API)"
|
||||
echo " 2. Run: export EASYPANEL_API_TOKEN='your-token'"
|
||||
echo " 3. Run this script again: ./deploy-easypanel.sh"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build Docker image
|
||||
echo ""
|
||||
echo "🐳 Building Docker image..."
|
||||
cd dealplustech-astro
|
||||
docker build -t $DOCKER_IMAGE . || {
|
||||
echo "❌ Docker build failed"
|
||||
exit 1
|
||||
}
|
||||
echo "✅ Docker image built: $DOCKER_IMAGE"
|
||||
|
||||
# Deploy via API
|
||||
echo ""
|
||||
echo "🚀 Deploying to Easypanel via API..."
|
||||
|
||||
# Create deployment payload
|
||||
cat > /tmp/deploy.json << EOF
|
||||
{
|
||||
"projectName": "$PROJECT_NAME",
|
||||
"name": "$APP_NAME",
|
||||
"type": "docker",
|
||||
"docker": {
|
||||
"image": "$DOCKER_IMAGE",
|
||||
"port": $PORT
|
||||
},
|
||||
"env": {
|
||||
"NODE_ENV": "production",
|
||||
"PORT": "$PORT",
|
||||
"HOST": "0.0.0.0"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Make API call
|
||||
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$EASYPANEL_URL/api/trpc/services.app.create" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $API_TOKEN" \
|
||||
-d @/tmp/deploy.json \
|
||||
--insecure)
|
||||
|
||||
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
||||
BODY=$(echo "$RESPONSE" | head -n-1)
|
||||
|
||||
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then
|
||||
echo "✅ Service created successfully"
|
||||
echo "$BODY" | python3 -m json.tool 2>/dev/null || echo "$BODY"
|
||||
|
||||
# Trigger deployment
|
||||
echo ""
|
||||
echo "🔄 Triggering deployment..."
|
||||
# Would need service ID from previous response
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo "🎉 DEPLOYMENT COMPLETE!"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
echo "📍 Service: $APP_NAME"
|
||||
echo "🔗 Dashboard: $EASYPANEL_URL"
|
||||
echo ""
|
||||
echo "💡 Check deployment status in Easypanel dashboard"
|
||||
else
|
||||
echo "❌ Deployment failed (HTTP $HTTP_CODE)"
|
||||
echo "$BODY"
|
||||
exit 1
|
||||
fi
|
||||
3
dealplustech-astro/dist/_noop-middleware.mjs
vendored
3
dealplustech-astro/dist/_noop-middleware.mjs
vendored
@@ -1,3 +0,0 @@
|
||||
const onRequest = (_, next) => next();
|
||||
|
||||
export { onRequest };
|
||||
5
dealplustech-astro/dist/blog/index.html
vendored
5
dealplustech-astro/dist/blog/index.html
vendored
File diff suppressed because one or more lines are too long
@@ -1,112 +0,0 @@
|
||||
<!DOCTYPE html><html lang="th"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="generator" content="Astro v5.18.0"><meta name="description" content="ท่อ HDPE (High Density Polyethylene) เป็นท่อที่ได้รับความนิยมสูงในงานระบบน้ำ เนื่องจากความทนทานและความยืดหยุ่นที่เหนือกว่าท่อชนิดอื่น"><!-- Favicon --><link rel="icon" type="image/svg+xml" href="/favicon.svg"><link rel="alternate icon" href="/favicon.ico" sizes="any"><link rel="apple-touch-icon" href="/favicon.svg"><!-- Google Fonts: Kanit for Thai --><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Kanit:wght@300;400;500;600;700&display=swap" rel="stylesheet"><!-- SEO --><meta property="og:title" content="ข้อดีของท่อ HDPE ในงานระบบน้ำ ทำไมถึงเป็นตัวเลือกยอดนิยม"><meta property="og:description" content="ท่อ HDPE (High Density Polyethylene) เป็นท่อที่ได้รับความนิยมสูงในงานระบบน้ำ เนื่องจากความทนทานและความยืดหยุ่นที่เหนือกว่าท่อชนิดอื่น"><meta property="og:image" content="/og-image.jpg"><meta property="og:type" content="website"><meta name="twitter:card" content="summary_large_image"><title>ข้อดีของท่อ HDPE ในงานระบบน้ำ ทำไมถึงเป็นตัวเลือกยอดนิยม | ดีล พลัส เทค</title><style>html{font-family:Kanit,system-ui,sans-serif}
|
||||
</style></head> <body class="flex flex-col min-h-screen"> <main class="pt-32 pb-16"> <article class="container mx-auto px-4 max-w-4xl"> <!-- Header --> <header class="mb-8"> <div class="flex items-center gap-4 mb-4"> <span class="industrial-badge">ท่อ HDPE</span> <time class="text-secondary-500"> 10 มกราคม 2567 </time> <span class="text-secondary-500">•</span> <span class="text-secondary-500">Deal Plus Tech</span> </div> <h1 class="text-4xl md:text-5xl font-bold text-secondary-900 mb-4"> ข้อดีของท่อ HDPE ในงานระบบน้ำ ทำไมถึงเป็นตัวเลือกยอดนิยม </h1> </header> <!-- Featured Image --> <div class="relative aspect-video bg-secondary-100 rounded-xl overflow-hidden mb-8"> <img src="/images/2021/03/hdpe-pipe_000C.jpg" alt="ข้อดีของท่อ HDPE ในงานระบบน้ำ ทำไมถึงเป็นตัวเลือกยอดนิยม" class="object-cover w-full h-full" loading="lazy"> </div> <!-- Content --> <div class="prose prose-lg max-w-none prose-headings:font-bold prose-a:text-primary-600 hover:prose-a:text-primary-700 prose-img:rounded-xl"> <h2 id="ท่อ-hdpe-คืออะไร">ท่อ HDPE คืออะไร?</h2>
|
||||
<p>ท่อ HDPE (High Density Polyethylene) หรือท่อเอชดีพีอี เป็นท่อที่ผลิตจากโพลิเอทิลีนความหนาแน่นสูง เป็นวัสดุพลาสติกที่มีความแข็งแรงและทนทานเป็นอย่างมาก</p>
|
||||
<h2 id="ข้อดีของท่อ-hdpe">ข้อดีของท่อ HDPE</h2>
|
||||
<h3 id="1-ความยืดหยุ่นสูง">1. ความยืดหยุ่นสูง</h3>
|
||||
<p>ท่อ HDPE สามารถโค้งงอได้ถึง 45 องศา ทำให้เหมาะสำหรับพื้นที่ติดตั้งจำกัด และสามารถรองรับการเคลื่อนไหวของดินได้ดี</p>
|
||||
<h3 id="2-ทนทานต่อสารเคมี">2. ทนทานต่อสารเคมี</h3>
|
||||
<p>ท่อ HDPE ทนทานต่อการกัดกร่อนของสารเคมี กรด และด่าง ทำให้เหมาะสำหรับงานอุตสาหกรรม</p>
|
||||
<h3 id="3-อายุการใช้งานยาวนาน">3. อายุการใช้งานยาวนาน</h3>
|
||||
<p>ท่อ HDPE มีอายุการใช้งานมากกว่า 50 ปี เมื่อติดตั้งและใช้งานอย่างถูกต้อง</p>
|
||||
<h3 id="4-น้ำหนักเบา">4. น้ำหนักเบา</h3>
|
||||
<p>ท่อ HDPE มีน้ำหนักเบากว่าท่อโลหะ ทำให้ง่ายต่อการขนส่งและติดตั้ง</p>
|
||||
<h3 id="5-การเชื่อมต่อที่แน่นหนา">5. การเชื่อมต่อที่แน่นหนา</h3>
|
||||
<p>การเชื่อมท่อ HDPE ด้วยวิธี Butt Fusion ทำให้ท่อเชื่อมต่อกันเป็นเนื้อเดียว ไม่มีรอยต่อ ป้องกันการรั่วซึม</p>
|
||||
<h3 id="6-ปลอดภัยต่อสุขภาพ">6. ปลอดภัยต่อสุขภาพ</h3>
|
||||
<p>ท่อ HDPE ไม่เป็นสนิม ไม่ปล่อยสารพิษ ปลอดภัยสำหรับน้ำดื่ม</p>
|
||||
<h2 id="การใช้งานท่อ-hdpe">การใช้งานท่อ HDPE</h2>
|
||||
<h3 id="งานประปา">งานประปา</h3>
|
||||
<ul>
|
||||
<li>ท่อส่งน้ำประปา</li>
|
||||
<li>ระบบประปาในบ้านเรือน</li>
|
||||
<li>ระบบประปาในอาคาร</li>
|
||||
</ul>
|
||||
<h3 id="งานเกษตร">งานเกษตร</h3>
|
||||
<ul>
|
||||
<li>ระบบน้ำหยด</li>
|
||||
<li>ระบบสปริงเกลอร์</li>
|
||||
<li>ระบบน้ำเพื่อการเกษตร</li>
|
||||
</ul>
|
||||
<h3 id="งานอุตสาหกรรม">งานอุตสาหกรรม</h3>
|
||||
<ul>
|
||||
<li>ท่อส่งสารเคมี</li>
|
||||
<li>ระบบบำบัดน้ำเสีย</li>
|
||||
<li>งานโรงงานอุตสาหกรรม</li>
|
||||
</ul>
|
||||
<h3 id="งานโครงสร้างพื้นฐาน">งานโครงสร้างพื้นฐาน</h3>
|
||||
<ul>
|
||||
<li>งานท่อใต้ดิน</li>
|
||||
<li>ท่อร้อยสายไฟ</li>
|
||||
<li>งานสาธารณูปโภค</li>
|
||||
</ul>
|
||||
<h2 id="ขนาดท่อ-hdpe-ที่นิยมใช้">ขนาดท่อ HDPE ที่นิยมใช้</h2>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<table><thead><tr><th>ขนาด (มม.)</th><th>การใช้งาน</th></tr></thead><tbody><tr><td>16-32</td><td>งานประปาภายในบ้าน</td></tr><tr><td>40-63</td><td>งานประปาอาคารขนาดเล็ก</td></tr><tr><td>75-110</td><td>งานประปาอาคารขนาดใหญ่</td></tr><tr><td>125-315</td><td>งานท่อส่งน้ำหลัก</td></tr><tr><td>355-1200</td><td>งานโครงสร้างพื้นฐาน</td></tr></tbody></table>
|
||||
<h2 id="เกรดของท่อ-hdpe">เกรดของท่อ HDPE</h2>
|
||||
<h3 id="pe80">PE80</h3>
|
||||
<ul>
|
||||
<li>เหมาะสำหรับงานทั่วไป</li>
|
||||
<li>ทนแรงดันสูงสุด 8 MPa</li>
|
||||
</ul>
|
||||
<h3 id="pe100">PE100</h3>
|
||||
<ul>
|
||||
<li>เหมาะสำหรับงานที่ต้องการความแข็งแรงสูง</li>
|
||||
<li>ทนแรงดันสูงสุด 10 MPa</li>
|
||||
<li>เป็นเกรดที่นิยมใช้ในปัจจุบัน</li>
|
||||
</ul>
|
||||
<h2 id="การติดตั้งท่อ-hdpe">การติดตั้งท่อ HDPE</h2>
|
||||
<h3 id="วิธี-butt-fusion">วิธี Butt Fusion</h3>
|
||||
<ol>
|
||||
<li>ตัดท่อให้ตรง</li>
|
||||
<li>ทำความสะอาดผิวท่อ</li>
|
||||
<li>ใช้เครื่องเชื่อมท่อ HDPE</li>
|
||||
<li>ให้ความร้อนจนผิวท่อละลาย</li>
|
||||
<li>กดท่อเข้าด้วยกัน</li>
|
||||
<li>รอให้เย็นตัวลง</li>
|
||||
</ol>
|
||||
<h3 id="วิธี-electrofusion">วิธี Electrofusion</h3>
|
||||
<ol>
|
||||
<li>ใช้ข้อต่อแบบ Electrofusion</li>
|
||||
<li>เสียบปลั๊กไฟเข้ากับข้อต่อ</li>
|
||||
<li>รอจนกระบวนการเชื่อมเสร็จสิ้น</li>
|
||||
</ol>
|
||||
<h2 id="สรุป">สรุป</h2>
|
||||
<p>ท่อ HDPE เป็นตัวเลือกที่ยอดเยี่ยมสำหรับงานระบบน้ำ เนื่องจากมีความทนทาน ความยืดหยุ่น และอายุการใช้งานที่ยาวนาน ไม่ว่าจะเป็นงานประปา งานเกษตร หรืองานอุตสาหกรรม ท่อ HDPE สามารถตอบโจทย์ได้ทุกการใช้งาน</p>
|
||||
<hr>
|
||||
<p><strong>สนใจสินค้าท่อ HDPE?</strong>
|
||||
ติดต่อเราได้ที่:</p>
|
||||
<ul>
|
||||
<li>โทร: 090-555-1415</li>
|
||||
<li>LINE: jppselection</li>
|
||||
</ul>
|
||||
<p><a href="/%E0%B8%97%E0%B9%88%E0%B8%ADhdpe">ดูสินค้าท่อ HDPE ทั้งหมด</a></p> </div> <!-- Back to Blog --> <div class="mt-12 pt-8 border-t border-secondary-200"> <a href="/blog/" class="inline-flex items-center text-primary-600 font-medium hover:text-primary-700 transition-colors"> <svg class="w-5 h-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path> </svg>
|
||||
กลับสู่หน้าบทความ
|
||||
</a> </div> </article> </main> </body></html>
|
||||
@@ -1,64 +0,0 @@
|
||||
<!DOCTYPE html><html lang="th"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="generator" content="Astro v5.18.0"><meta name="description" content="ท่อ PPR (Polypropylene Random Copolymer) เป็นท่อพลาสติกที่ได้รับความนิยมสูงในการใช้งานระบบประปา บทความนี้จะอธิบายทุกสิ่งที่คุณต้องรู้เกี่ยวกับท่อ PPR"><!-- Favicon --><link rel="icon" type="image/svg+xml" href="/favicon.svg"><link rel="alternate icon" href="/favicon.ico" sizes="any"><link rel="apple-touch-icon" href="/favicon.svg"><!-- Google Fonts: Kanit for Thai --><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Kanit:wght@300;400;500;600;700&display=swap" rel="stylesheet"><!-- SEO --><meta property="og:title" content="ท่อ PPR คืออะไร? คู่มือฉบับสมบูรณ์สำหรับการเลือกใช้งาน"><meta property="og:description" content="ท่อ PPR (Polypropylene Random Copolymer) เป็นท่อพลาสติกที่ได้รับความนิยมสูงในการใช้งานระบบประปา บทความนี้จะอธิบายทุกสิ่งที่คุณต้องรู้เกี่ยวกับท่อ PPR"><meta property="og:image" content="/og-image.jpg"><meta property="og:type" content="website"><meta name="twitter:card" content="summary_large_image"><title>ท่อ PPR คืออะไร? คู่มือฉบับสมบูรณ์สำหรับการเลือกใช้งาน | ดีล พลัส เทค</title><style>html{font-family:Kanit,system-ui,sans-serif}
|
||||
</style></head> <body class="flex flex-col min-h-screen"> <main class="pt-32 pb-16"> <article class="container mx-auto px-4 max-w-4xl"> <!-- Header --> <header class="mb-8"> <div class="flex items-center gap-4 mb-4"> <span class="industrial-badge">ท่อ PPR</span> <time class="text-secondary-500"> 15 มกราคม 2567 </time> <span class="text-secondary-500">•</span> <span class="text-secondary-500">Deal Plus Tech</span> </div> <h1 class="text-4xl md:text-5xl font-bold text-secondary-900 mb-4"> ท่อ PPR คืออะไร? คู่มือฉบับสมบูรณ์สำหรับการเลือกใช้งาน </h1> </header> <!-- Featured Image --> <div class="relative aspect-video bg-secondary-100 rounded-xl overflow-hidden mb-8"> <img src="/images/2021/03/ppr-pipe_000C.jpg" alt="ท่อ PPR คืออะไร? คู่มือฉบับสมบูรณ์สำหรับการเลือกใช้งาน" class="object-cover w-full h-full" loading="lazy"> </div> <!-- Content --> <div class="prose prose-lg max-w-none prose-headings:font-bold prose-a:text-primary-600 hover:prose-a:text-primary-700 prose-img:rounded-xl"> <h2 id="ท่อ-ppr-คืออะไร">ท่อ PPR คืออะไร?</h2>
|
||||
<p>ท่อ PPR (Polypropylene Random Copolymer) หรือท่อพีพีอาร์ เป็นท่อพลาสติกที่ผลิตจากเม็ดพลาสติก PP-R 80 (Polypropylene Random Copolymer 80) ซึ่งเป็นวัสดุพลาสติกคุณภาพสูงที่มีความแข็งแรงและทนทานเป็นอย่างดี</p>
|
||||
<h2 id="ข้อดีของท่อ-ppr">ข้อดีของท่อ PPR</h2>
|
||||
<h3 id="1-ทนแรงดันและอุณหภูมิสูง">1. ทนแรงดันและอุณหภูมิสูง</h3>
|
||||
<p>ท่อ PPR สามารถทนแรงดันได้สูงถึง 20 บาร์ และทนต่ออุณหภูมิได้สูงถึง 95°C ทำให้เหมาะสำหรับใช้งานทั้งระบบน้ำเย็นและน้ำร้อน</p>
|
||||
<h3 id="2-สะอาดและปลอดภัย">2. สะอาดและปลอดภัย</h3>
|
||||
<p>ท่อ PPR ไม่เป็นสนิม ปราศจากโลหะหนักและสิ่งปนเปื้อน ทำให้น้ำที่ไหลผ่านสะอาดและปลอดภัยต่อการบริโภค</p>
|
||||
<h3 id="3-อายุการใช้งานยาวนาน">3. อายุการใช้งานยาวนาน</h3>
|
||||
<p>ด้วยคุณสมบัติที่ทนทาน ท่อ PPR มีอายุการใช้งานยาวนานกว่า 50 ปี</p>
|
||||
<h3 id="4-ติดตั้งง่าย">4. ติดตั้งง่าย</h3>
|
||||
<p>การเชื่อมต่อท่อ PPR ใช้วิธีเชื่อมด้วยความร้อน ทำให้ท่อและข้อต่อเป็นเนื้อเดียวกัน ไม่มีปัญหารั่วซึม</p>
|
||||
<h3 id="5-ประหยัดพลังงาน">5. ประหยัดพลังงาน</h3>
|
||||
<p>ท่อ PPR เป็นฉนวนกันความร้อนที่ดี ช่วยรักษาอุณหภูมิของน้ำได้ดีกว่าท่อโลหะ</p>
|
||||
<h2 id="การเลือกท่อ-ppr-ที่เหมาะสม">การเลือกท่อ PPR ที่เหมาะสม</h2>
|
||||
<h3 id="ขนาดท่อ">ขนาดท่อ</h3>
|
||||
<p>เลือกขนาดท่อให้เหมาะสมกับปริมาณน้ำที่ต้องการใช้งาน:</p>
|
||||
<ul>
|
||||
<li>ท่อขนาด 20-25 มม. เหมาะสำหรับบ้านเรือนทั่วไป</li>
|
||||
<li>ท่อขนาด 32-63 มม. เหมาะสำหรับอาคารขนาดใหญ่</li>
|
||||
</ul>
|
||||
<h3 id="เกรดของท่อ">เกรดของท่อ</h3>
|
||||
<ul>
|
||||
<li><strong>PN10</strong> - สำหรับน้ำเย็น ทนแรงดัน 10 บาร์</li>
|
||||
<li><strong>PN16</strong> - สำหรับน้ำอุ่น ทนแรงดัน 16 บาร์</li>
|
||||
<li><strong>PN20</strong> - สำหรับน้ำร้อน ทนแรงดัน 20 บาร์</li>
|
||||
</ul>
|
||||
<h2 id="การติดตั้งท่อ-ppr">การติดตั้งท่อ PPR</h2>
|
||||
<h3 id="ขั้นตอนการเชื่อมท่อ">ขั้นตอนการเชื่อมท่อ</h3>
|
||||
<ol>
|
||||
<li>ตัดท่อให้ตรงและเรียบ</li>
|
||||
<li>ทำความสะอาดผิวท่อและข้อต่อ</li>
|
||||
<li>ใช้เครื่องเชื่อมท่ออุณหภูมิ 260°C</li>
|
||||
<li>สอดท่อและข้อต่อเข้าด้วยกัน</li>
|
||||
<li>รอให้เย็นตัวลงประมาณ 2-3 นาที</li>
|
||||
</ol>
|
||||
<h3 id="ข้อควรระวัง">ข้อควรระวัง</h3>
|
||||
<ul>
|
||||
<li>หลีกเลี่ยงการติดตั้งในพื้นที่ที่มีแสงแดดโดยตรง</li>
|
||||
<li>ควรทิ้งระยะห่างสำหรับการขยายตัวของท่อ</li>
|
||||
<li>ตรวจสอบความร้อนของเครื่องเชื่อมก่อนใช้งาน</li>
|
||||
</ul>
|
||||
<h2 id="ท่อ-ppr-ตราช้าง">ท่อ PPR ตราช้าง</h2>
|
||||
<p>ท่อ PPR ตราช้าง เป็นท่อ PPR คุณภาพสูงที่ผลิตจากเม็ดพลาสติก PP-R 80 วัตถุดิบคุณภาพสูงมาตรฐานยุโรปจาก lyondellbasell</p>
|
||||
<p><strong>คุณสมบัติเด่น:</strong></p>
|
||||
<ul>
|
||||
<li>ทนแรงดันได้สูงสุด 20 บาร์</li>
|
||||
<li>ทนต่ออุณหภูมิได้สูงถึง 95°C</li>
|
||||
<li>ผลิตตามมาตรฐาน DIN8077 และ DIN8078 ของประเทศเยอรมัน</li>
|
||||
<li>รับประกันคุณภาพ</li>
|
||||
</ul>
|
||||
<h2 id="สรุป">สรุป</h2>
|
||||
<p>ท่อ PPR เป็นตัวเลือกที่ดีสำหรับระบบประปาในปัจจุบัน เนื่องจากมีความทนทานสูง ติดตั้งง่าย และมีอายุการใช้งานยาวนาน หากคุณกำลังมองหาท่อสำหรับงานระบบน้ำ ท่อ PPR เป็นตัวเลือกที่คุ้มค่าและเหมาะสม</p>
|
||||
<hr>
|
||||
<p><strong>สนใจสินค้าท่อ PPR?</strong>
|
||||
ติดต่อเราได้ที่:</p>
|
||||
<ul>
|
||||
<li>โทร: 090-555-1415</li>
|
||||
<li>LINE: jppselection</li>
|
||||
<li>อีเมล: <a href="mailto:dealplustech@gmail.com">dealplustech@gmail.com</a></li>
|
||||
</ul>
|
||||
<p><a href="/%E0%B8%97%E0%B9%88%E0%B8%AD%E0%B8%9E%E0%B8%B5%E0%B8%9E%E0%B8%B5%E0%B8%AD%E0%B8%B2%E0%B8%A3%E0%B9%8C%E0%B8%95%E0%B8%A3%E0%B8%B2%E0%B8%8A%E0%B9%89%E0%B8%B2%E0%B8%87">ดูสินค้าท่อ PPR ทั้งหมด</a></p> </div> <!-- Back to Blog --> <div class="mt-12 pt-8 border-t border-secondary-200"> <a href="/blog/" class="inline-flex items-center text-primary-600 font-medium hover:text-primary-700 transition-colors"> <svg class="w-5 h-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path> </svg>
|
||||
กลับสู่หน้าบทความ
|
||||
</a> </div> </article> </main> </body></html>
|
||||
@@ -1,156 +0,0 @@
|
||||
<!DOCTYPE html><html lang="th"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="generator" content="Astro v5.18.0"><meta name="description" content="ปั๊มน้ำเป็นอุปกรณ์สำคัญในระบบน้ำทุกบ้าน การบำรุงรักษาที่ถูกต้องจะช่วยยืดอายุการใช้งานและประหยัดค่าไฟฟ้า"><!-- Favicon --><link rel="icon" type="image/svg+xml" href="/favicon.svg"><link rel="alternate icon" href="/favicon.ico" sizes="any"><link rel="apple-touch-icon" href="/favicon.svg"><!-- Google Fonts: Kanit for Thai --><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Kanit:wght@300;400;500;600;700&display=swap" rel="stylesheet"><!-- SEO --><meta property="og:title" content="การบำรุงรักษาปั๊มน้ำให้มีอายุการใช้งานยาวนาน"><meta property="og:description" content="ปั๊มน้ำเป็นอุปกรณ์สำคัญในระบบน้ำทุกบ้าน การบำรุงรักษาที่ถูกต้องจะช่วยยืดอายุการใช้งานและประหยัดค่าไฟฟ้า"><meta property="og:image" content="/og-image.jpg"><meta property="og:type" content="website"><meta name="twitter:card" content="summary_large_image"><title>การบำรุงรักษาปั๊มน้ำให้มีอายุการใช้งานยาวนาน | ดีล พลัส เทค</title><style>html{font-family:Kanit,system-ui,sans-serif}
|
||||
</style></head> <body class="flex flex-col min-h-screen"> <main class="pt-32 pb-16"> <article class="container mx-auto px-4 max-w-4xl"> <!-- Header --> <header class="mb-8"> <div class="flex items-center gap-4 mb-4"> <span class="industrial-badge">ปั๊มน้ำ</span> <time class="text-secondary-500"> 5 มกราคม 2567 </time> <span class="text-secondary-500">•</span> <span class="text-secondary-500">Deal Plus Tech</span> </div> <h1 class="text-4xl md:text-5xl font-bold text-secondary-900 mb-4"> การบำรุงรักษาปั๊มน้ำให้มีอายุการใช้งานยาวนาน </h1> </header> <!-- Featured Image --> <div class="relative aspect-video bg-secondary-100 rounded-xl overflow-hidden mb-8"> <img src="/images/2021/02/Water-Pump1.jpg" alt="การบำรุงรักษาปั๊มน้ำให้มีอายุการใช้งานยาวนาน" class="object-cover w-full h-full" loading="lazy"> </div> <!-- Content --> <div class="prose prose-lg max-w-none prose-headings:font-bold prose-a:text-primary-600 hover:prose-a:text-primary-700 prose-img:rounded-xl"> <h2 id="ความสำคัญของการบำรุงรักษาปั๊มน้ำ">ความสำคัญของการบำรุงรักษาปั๊มน้ำ</h2>
|
||||
<p>ปั๊มน้ำเป็นหัวใจของระบบน้ำในบ้าน การบำรุงรักษาอย่างสม่ำเสมอจะช่วย:</p>
|
||||
<ul>
|
||||
<li>ยืดอายุการใช้งานของปั๊มน้ำ</li>
|
||||
<li>ลดปัญหาการเสีย</li>
|
||||
<li>ประหยัดค่าไฟฟ้า</li>
|
||||
<li>ป้องกันอุบัติเหตุจากการรั่วซึม</li>
|
||||
</ul>
|
||||
<h2 id="การบำรุงรักษาปั๊มน้ำแบบทำเอง">การบำรุงรักษาปั๊มน้ำแบบทำเอง</h2>
|
||||
<h3 id="1-ตรวจสอบสายไฟและสวิตช์">1. ตรวจสอบสายไฟและสวิตช์</h3>
|
||||
<ul>
|
||||
<li>ตรวจสอบสายไฟว่ามีรอยชำรุดหรือไม่</li>
|
||||
<li>ตรวจสอบสวิตช์ว่าทำงานปกติหรือไม่</li>
|
||||
<li>หากพบความผิดปกติควรเรียกช่าง</li>
|
||||
</ul>
|
||||
<h3 id="2-ทำความสะอาดตัวกรอง">2. ทำความสะอาดตัวกรอง</h3>
|
||||
<ul>
|
||||
<li>ปิดวาล์วน้ำเข้าก่อนทำความสะอาด</li>
|
||||
<li>ถอดตัวกรองออกมาล้าง</li>
|
||||
<li>ตรวจสอบว่ามีสิ่งปนเปื้อนหรือไม่</li>
|
||||
<li>ติดตั้งกลับเข้าที่เดิม</li>
|
||||
</ul>
|
||||
<h3 id="3-ตรวจสอบแรงดันน้ำ">3. ตรวจสอบแรงดันน้ำ</h3>
|
||||
<ul>
|
||||
<li>สังเกตแรงดันน้ำว่าลดลงหรือไม่</li>
|
||||
<li>ตรวจสอบว่ามีเสียงผิดปกติหรือไม่</li>
|
||||
<li>หากแรงดันลดลงอาจมีการรั่วซึม</li>
|
||||
</ul>
|
||||
<h3 id="4-ตรวจสอบถังแรงดัน-pressure-tank">4. ตรวจสอบถังแรงดัน (Pressure Tank)</h3>
|
||||
<ul>
|
||||
<li>ตรวจสอบว่าถังมีอากาศเพียงพอหรือไม่</li>
|
||||
<li>หากปั๊มเปิด-ปิดบ่อยผิดปกติ อาจต้องเติมอากาศ</li>
|
||||
<li>ควรตรวจสอบทุก 6 เดือน</li>
|
||||
</ul>
|
||||
<h2 id="ปัญหาที่พบบ่อยและวิธีแก้ไข">ปัญหาที่พบบ่อยและวิธีแก้ไข</h2>
|
||||
<h3 id="ปั๊มไม่ทำงาน">ปั๊มไม่ทำงาน</h3>
|
||||
<p><strong>สาเหตุ:</strong></p>
|
||||
<ul>
|
||||
<li>ไฟดับหรือสายไฟขาด</li>
|
||||
<li>สวิตช์เสีย</li>
|
||||
<li>มอเตอร์เสีย</li>
|
||||
</ul>
|
||||
<p><strong>วิธีแก้:</strong></p>
|
||||
<ul>
|
||||
<li>ตรวจสอบไฟและสายไฟ</li>
|
||||
<li>เปลี่ยนสวิตช์</li>
|
||||
<li>เรียกช่างซ่อมมอเตอร์</li>
|
||||
</ul>
|
||||
<h3 id="แรงดันน้ำต่ำ">แรงดันน้ำต่ำ</h3>
|
||||
<p><strong>สาเหตุ:</strong></p>
|
||||
<ul>
|
||||
<li>ตัวกรองอุดตัน</li>
|
||||
<li>ท่อรั่ว</li>
|
||||
<li>ใบพัดสึกหรอ</li>
|
||||
</ul>
|
||||
<p><strong>วิธีแก้:</strong></p>
|
||||
<ul>
|
||||
<li>ทำความสะอาดตัวกรอง</li>
|
||||
<li>ตรวจสอบและซ่อมท่อ</li>
|
||||
<li>เปลี่ยนใบพัด</li>
|
||||
</ul>
|
||||
<h3 id="ปั๊มเปิด-ปิดบ่อย">ปั๊มเปิด-ปิดบ่อย</h3>
|
||||
<p><strong>สาเหตุ:</strong></p>
|
||||
<ul>
|
||||
<li>ถังแรงดันอากาศรั่ว</li>
|
||||
<li>แผ่นไดอะแฟรมแตก</li>
|
||||
<li>วาล์วตรวจสอบแรงดันเสีย</li>
|
||||
</ul>
|
||||
<p><strong>วิธีแก้:</strong></p>
|
||||
<ul>
|
||||
<li>เติมอากาศในถัง</li>
|
||||
<li>เปลี่ยนแผ่นไดอะแฟรม</li>
|
||||
<li>เปลี่ยนวาล์ว</li>
|
||||
</ul>
|
||||
<h3 id="ปั๊มมีเสียงดังผิดปกติ">ปั๊มมีเสียงดังผิดปกติ</h3>
|
||||
<p><strong>สาเหตุ:</strong></p>
|
||||
<ul>
|
||||
<li>ลูกปืนเสีย</li>
|
||||
<li>ใบพัดชำรุด</li>
|
||||
<li>การติดตั้งไม่แน่นหนา</li>
|
||||
</ul>
|
||||
<p><strong>วิธีแก้:</strong></p>
|
||||
<ul>
|
||||
<li>เปลี่ยนลูกปืน</li>
|
||||
<li>เปลี่ยนใบพัด</li>
|
||||
<li>ตรวจสอบการยึดแน่น</li>
|
||||
</ul>
|
||||
<h2 id="ตารางการบำรุงรักษา">ตารางการบำรุงรักษา</h2>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<table><thead><tr><th>รายการ</th><th>ความถี่</th><th>หมายเหตุ</th></tr></thead><tbody><tr><td>ตรวจสอบสายไฟ</td><td>ทุกเดือน</td><td>มองหารอยชำรุด</td></tr><tr><td>ทำความสะอาดตัวกรอง</td><td>ทุก 3 เดือน</td><td>หรือเมื่อแรงดันลด</td></tr><tr><td>ตรวจสอบถังแรงดัน</td><td>ทุก 6 เดือน</td><td>เติมอากาศหากจำเป็น</td></tr><tr><td>ตรวจสอบสวิตช์</td><td>ทุกปี</td><td>เปลี่ยนหากเสีย</td></tr><tr><td>ตรวจสอบใบพัด</td><td>ทุก 2 ปี</td><td>โดยช่างผู้เชี่ยวชาญ</td></tr></tbody></table>
|
||||
<h2 id="เคล็ดลับการใช้งานปั๊มน้ำ">เคล็ดลับการใช้งานปั๊มน้ำ</h2>
|
||||
<h3 id="ประหยัดไฟฟ้า">ประหยัดไฟฟ้า</h3>
|
||||
<ul>
|
||||
<li>เลือกขนาดปั๊มที่เหมาะสมกับการใช้งาน</li>
|
||||
<li>ติดตั้งถังแรงดันขนาดเหมาะสม</li>
|
||||
<li>หลีกเลี่ยงการเปิด-ปิดปั๊มบ่อย</li>
|
||||
</ul>
|
||||
<h3 id="ป้องกันปัญหา">ป้องกันปัญหา</h3>
|
||||
<ul>
|
||||
<li>อย่าให้ปั๊มแห้ง (ทำงานโดยไม่มีน้ำ)</li>
|
||||
<li>ตรวจสอบรอยรั่วอย่างสม่ำเสมอ</li>
|
||||
<li>ใช้ตัวกรองเพื่อป้องกันสิ่งสกปรก</li>
|
||||
</ul>
|
||||
<h3 id="เมื่อต้องเปลี่ยนปั๊ม">เมื่อต้องเปลี่ยนปั๊ม</h3>
|
||||
<ul>
|
||||
<li>เลือกปั๊มที่มีคุณภาพ</li>
|
||||
<li>พิจารณาขนาดและกำลังที่เหมาะสม</li>
|
||||
<li>ติดตั้งโดยช่างผู้เชี่ยวชาญ</li>
|
||||
</ul>
|
||||
<h2 id="สรุป">สรุป</h2>
|
||||
<p>การบำรุงรักษาปั๊มน้ำอย่างสม่ำเสมอจะช่วยยืดอายุการใช้งาน ลดปัญหาการเสีย และประหยัดค่าใช้จ่ายในระยะยาว ควรตรวจสอบและบำรุงรักษาตามตารางที่กำหนด และหากพบปัญหาที่ไม่สามารถแก้ไขได้เอง ควรติดต่อช่างผู้เชี่ยวชาญ</p>
|
||||
<hr>
|
||||
<p><strong>ต้องการซื้อปั๊มน้ำหรืออุปกรณ์เสริม?</strong>
|
||||
ติดต่อเราได้ที่:</p>
|
||||
<ul>
|
||||
<li>โทร: 090-555-1415</li>
|
||||
<li>LINE: jppselection</li>
|
||||
</ul>
|
||||
<p><a href="/%E0%B8%9B%E0%B8%B1%E0%B9%8A%E0%B8%A1%E0%B8%99%E0%B9%89%E0%B8%B3-pump">ดูสินค้าปั๊มน้ำทั้งหมด</a></p> </div> <!-- Back to Blog --> <div class="mt-12 pt-8 border-t border-secondary-200"> <a href="/blog/" class="inline-flex items-center text-primary-600 font-medium hover:text-primary-700 transition-colors"> <svg class="w-5 h-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path> </svg>
|
||||
กลับสู่หน้าบทความ
|
||||
</a> </div> </article> </main> </body></html>
|
||||
@@ -1,14 +0,0 @@
|
||||
import { c as createComponent, d as addAttribute, h as renderHead, i as renderSlot, a as renderTemplate, b as createAstro } from './astro/server_D-JZF3a4.mjs';
|
||||
import 'piccolore';
|
||||
import 'clsx';
|
||||
/* empty css */
|
||||
|
||||
const $$Astro = createAstro();
|
||||
const $$BaseLayout = createComponent(($$result, $$props, $$slots) => {
|
||||
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
|
||||
Astro2.self = $$BaseLayout;
|
||||
const { title, description, image } = Astro2.props;
|
||||
return renderTemplate`<html lang="th"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="generator"${addAttribute(Astro2.generator, "content")}><meta name="description"${addAttribute(description || "\u0E1A\u0E23\u0E34\u0E29\u0E31\u0E17 \u0E14\u0E35\u0E25 \u0E1E\u0E25\u0E31\u0E2A \u0E40\u0E17\u0E04 \u0E08\u0E33\u0E01\u0E31\u0E14 - \u0E1C\u0E39\u0E49\u0E40\u0E0A\u0E35\u0E48\u0E22\u0E27\u0E0A\u0E32\u0E0D\u0E14\u0E49\u0E32\u0E19\u0E23\u0E30\u0E1A\u0E1A\u0E19\u0E49\u0E33 \u0E17\u0E48\u0E2D PPR \u0E15\u0E23\u0E32\u0E0A\u0E49\u0E32\u0E07 \u0E17\u0E48\u0E2D\u0E1E\u0E35\u0E1E\u0E35\u0E2D\u0E32\u0E23\u0E4C \u0E17\u0E48\u0E2D HDPE", "content")}><!-- Favicon --><link rel="icon" type="image/svg+xml" href="/favicon.svg"><link rel="alternate icon" href="/favicon.ico" sizes="any"><link rel="apple-touch-icon" href="/favicon.svg"><!-- Google Fonts: Kanit for Thai --><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Kanit:wght@300;400;500;600;700&display=swap" rel="stylesheet"><!-- SEO --><meta property="og:title"${addAttribute(title, "content")}><meta property="og:description"${addAttribute(description || "Deal Plus Tech - \u0E1C\u0E39\u0E49\u0E40\u0E0A\u0E35\u0E48\u0E22\u0E27\u0E0A\u0E32\u0E0D\u0E14\u0E49\u0E32\u0E19\u0E23\u0E30\u0E1A\u0E1A\u0E19\u0E49\u0E33", "content")}><meta property="og:image"${addAttribute(image || "/og-image.jpg", "content")}><meta property="og:type" content="website"><meta name="twitter:card" content="summary_large_image"><title>${title} | ดีล พลัส เทค</title>${renderHead()}</head> <body class="flex flex-col min-h-screen"> ${renderSlot($$result, $$slots["default"])} </body></html>`;
|
||||
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/layouts/BaseLayout.astro", void 0);
|
||||
|
||||
export { $$BaseLayout as $ };
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,469 +0,0 @@
|
||||
import { escape } from 'html-escaper';
|
||||
import { Traverse } from 'neotraverse/modern';
|
||||
import pLimit from 'p-limit';
|
||||
import { z } from 'zod';
|
||||
import { removeBase, isRemotePath, prependForwardSlash } from '@astrojs/internal-helpers/path';
|
||||
import { A as AstroError, R as RenderUndefinedEntryError, c as createComponent, u as unescapeHTML, a as renderTemplate, U as UnknownContentCollectionError, e as renderUniqueStylesheet, f as renderScriptElement, g as createHeadAndContent, r as renderComponent } from './astro/server_D-JZF3a4.mjs';
|
||||
import 'piccolore';
|
||||
import * as devalue from 'devalue';
|
||||
|
||||
const CONTENT_IMAGE_FLAG = "astroContentImageFlag";
|
||||
const IMAGE_IMPORT_PREFIX = "__ASTRO_IMAGE_";
|
||||
|
||||
const VALID_INPUT_FORMATS = [
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"png",
|
||||
"tiff",
|
||||
"webp",
|
||||
"gif",
|
||||
"svg",
|
||||
"avif"
|
||||
];
|
||||
const VALID_SUPPORTED_FORMATS = [
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"png",
|
||||
"tiff",
|
||||
"webp",
|
||||
"gif",
|
||||
"svg",
|
||||
"avif"
|
||||
];
|
||||
const DEFAULT_OUTPUT_FORMAT = "webp";
|
||||
const DEFAULT_HASH_PROPS = [
|
||||
"src",
|
||||
"width",
|
||||
"height",
|
||||
"format",
|
||||
"quality",
|
||||
"fit",
|
||||
"position",
|
||||
"background"
|
||||
];
|
||||
|
||||
function imageSrcToImportId(imageSrc, filePath) {
|
||||
imageSrc = removeBase(imageSrc, IMAGE_IMPORT_PREFIX);
|
||||
if (isRemotePath(imageSrc)) {
|
||||
return;
|
||||
}
|
||||
const ext = imageSrc.split(".").at(-1)?.toLowerCase();
|
||||
if (!ext || !VALID_INPUT_FORMATS.includes(ext)) {
|
||||
return;
|
||||
}
|
||||
const params = new URLSearchParams(CONTENT_IMAGE_FLAG);
|
||||
if (filePath) {
|
||||
params.set("importer", filePath);
|
||||
}
|
||||
return `${imageSrc}?${params.toString()}`;
|
||||
}
|
||||
|
||||
class ImmutableDataStore {
|
||||
_collections = /* @__PURE__ */ new Map();
|
||||
constructor() {
|
||||
this._collections = /* @__PURE__ */ new Map();
|
||||
}
|
||||
get(collectionName, key) {
|
||||
return this._collections.get(collectionName)?.get(String(key));
|
||||
}
|
||||
entries(collectionName) {
|
||||
const collection = this._collections.get(collectionName) ?? /* @__PURE__ */ new Map();
|
||||
return [...collection.entries()];
|
||||
}
|
||||
values(collectionName) {
|
||||
const collection = this._collections.get(collectionName) ?? /* @__PURE__ */ new Map();
|
||||
return [...collection.values()];
|
||||
}
|
||||
keys(collectionName) {
|
||||
const collection = this._collections.get(collectionName) ?? /* @__PURE__ */ new Map();
|
||||
return [...collection.keys()];
|
||||
}
|
||||
has(collectionName, key) {
|
||||
const collection = this._collections.get(collectionName);
|
||||
if (collection) {
|
||||
return collection.has(String(key));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
hasCollection(collectionName) {
|
||||
return this._collections.has(collectionName);
|
||||
}
|
||||
collections() {
|
||||
return this._collections;
|
||||
}
|
||||
/**
|
||||
* Attempts to load a DataStore from the virtual module.
|
||||
* This only works in Vite.
|
||||
*/
|
||||
static async fromModule() {
|
||||
try {
|
||||
const data = await import('./_astro_data-layer-content_NX5tLOlp.mjs');
|
||||
if (data.default instanceof Map) {
|
||||
return ImmutableDataStore.fromMap(data.default);
|
||||
}
|
||||
const map = devalue.unflatten(data.default);
|
||||
return ImmutableDataStore.fromMap(map);
|
||||
} catch {
|
||||
}
|
||||
return new ImmutableDataStore();
|
||||
}
|
||||
static async fromMap(data) {
|
||||
const store = new ImmutableDataStore();
|
||||
store._collections = data;
|
||||
return store;
|
||||
}
|
||||
}
|
||||
function dataStoreSingleton() {
|
||||
let instance = void 0;
|
||||
return {
|
||||
get: async () => {
|
||||
if (!instance) {
|
||||
instance = ImmutableDataStore.fromModule();
|
||||
}
|
||||
return instance;
|
||||
},
|
||||
set: (store) => {
|
||||
instance = store;
|
||||
}
|
||||
};
|
||||
}
|
||||
const globalDataStore = dataStoreSingleton();
|
||||
|
||||
const __vite_import_meta_env__ = {"ASSETS_PREFIX": undefined, "BASE_URL": "/", "DEV": false, "MODE": "production", "PROD": true, "SITE": undefined, "SSR": true};
|
||||
function createCollectionToGlobResultMap({
|
||||
globResult,
|
||||
contentDir
|
||||
}) {
|
||||
const collectionToGlobResultMap = {};
|
||||
for (const key in globResult) {
|
||||
const keyRelativeToContentDir = key.replace(new RegExp(`^${contentDir}`), "");
|
||||
const segments = keyRelativeToContentDir.split("/");
|
||||
if (segments.length <= 1) continue;
|
||||
const collection = segments[0];
|
||||
collectionToGlobResultMap[collection] ??= {};
|
||||
collectionToGlobResultMap[collection][key] = globResult[key];
|
||||
}
|
||||
return collectionToGlobResultMap;
|
||||
}
|
||||
z.object({
|
||||
tags: z.array(z.string()).optional(),
|
||||
lastModified: z.date().optional()
|
||||
});
|
||||
function createGetCollection({
|
||||
contentCollectionToEntryMap,
|
||||
dataCollectionToEntryMap,
|
||||
getRenderEntryImport,
|
||||
cacheEntriesByCollection,
|
||||
liveCollections
|
||||
}) {
|
||||
return async function getCollection(collection, filter) {
|
||||
if (collection in liveCollections) {
|
||||
throw new AstroError({
|
||||
...UnknownContentCollectionError,
|
||||
message: `Collection "${collection}" is a live collection. Use getLiveCollection() instead of getCollection().`
|
||||
});
|
||||
}
|
||||
const hasFilter = typeof filter === "function";
|
||||
const store = await globalDataStore.get();
|
||||
let type;
|
||||
if (collection in contentCollectionToEntryMap) {
|
||||
type = "content";
|
||||
} else if (collection in dataCollectionToEntryMap) {
|
||||
type = "data";
|
||||
} else if (store.hasCollection(collection)) {
|
||||
const { default: imageAssetMap } = await import('./content-assets_DleWbedO.mjs');
|
||||
const result = [];
|
||||
for (const rawEntry of store.values(collection)) {
|
||||
const data = updateImageReferencesInData(rawEntry.data, rawEntry.filePath, imageAssetMap);
|
||||
let entry = {
|
||||
...rawEntry,
|
||||
data,
|
||||
collection
|
||||
};
|
||||
if (entry.legacyId) {
|
||||
entry = emulateLegacyEntry(entry);
|
||||
}
|
||||
if (hasFilter && !filter(entry)) {
|
||||
continue;
|
||||
}
|
||||
result.push(entry);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
console.warn(
|
||||
`The collection ${JSON.stringify(
|
||||
collection
|
||||
)} does not exist or is empty. Please check your content config file for errors.`
|
||||
);
|
||||
return [];
|
||||
}
|
||||
const lazyImports = Object.values(
|
||||
type === "content" ? contentCollectionToEntryMap[collection] : dataCollectionToEntryMap[collection]
|
||||
);
|
||||
let entries = [];
|
||||
if (!Object.assign(__vite_import_meta_env__, { _: process.env._ })?.DEV && cacheEntriesByCollection.has(collection)) {
|
||||
entries = cacheEntriesByCollection.get(collection);
|
||||
} else {
|
||||
const limit = pLimit(10);
|
||||
entries = await Promise.all(
|
||||
lazyImports.map(
|
||||
(lazyImport) => limit(async () => {
|
||||
const entry = await lazyImport();
|
||||
return type === "content" ? {
|
||||
id: entry.id,
|
||||
slug: entry.slug,
|
||||
body: entry.body,
|
||||
collection: entry.collection,
|
||||
data: entry.data,
|
||||
async render() {
|
||||
return render({
|
||||
collection: entry.collection,
|
||||
id: entry.id,
|
||||
renderEntryImport: await getRenderEntryImport(collection, entry.slug)
|
||||
});
|
||||
}
|
||||
} : {
|
||||
id: entry.id,
|
||||
collection: entry.collection,
|
||||
data: entry.data
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
cacheEntriesByCollection.set(collection, entries);
|
||||
}
|
||||
if (hasFilter) {
|
||||
return entries.filter(filter);
|
||||
} else {
|
||||
return entries.slice();
|
||||
}
|
||||
};
|
||||
}
|
||||
function emulateLegacyEntry({ legacyId, ...entry }) {
|
||||
const legacyEntry = {
|
||||
...entry,
|
||||
id: legacyId,
|
||||
slug: entry.id
|
||||
};
|
||||
return {
|
||||
...legacyEntry,
|
||||
// Define separately so the render function isn't included in the object passed to `renderEntry()`
|
||||
render: () => renderEntry(legacyEntry)
|
||||
};
|
||||
}
|
||||
const CONTENT_LAYER_IMAGE_REGEX = /__ASTRO_IMAGE_="([^"]+)"/g;
|
||||
async function updateImageReferencesInBody(html, fileName) {
|
||||
const { default: imageAssetMap } = await import('./content-assets_DleWbedO.mjs');
|
||||
const imageObjects = /* @__PURE__ */ new Map();
|
||||
const { getImage } = await import('./_astro_assets_CWrzoUoo.mjs').then(n => n._);
|
||||
for (const [_full, imagePath] of html.matchAll(CONTENT_LAYER_IMAGE_REGEX)) {
|
||||
try {
|
||||
const decodedImagePath = JSON.parse(imagePath.replaceAll(""", '"'));
|
||||
let image;
|
||||
if (URL.canParse(decodedImagePath.src)) {
|
||||
image = await getImage(decodedImagePath);
|
||||
} else {
|
||||
const id = imageSrcToImportId(decodedImagePath.src, fileName);
|
||||
const imported = imageAssetMap.get(id);
|
||||
if (!id || imageObjects.has(id) || !imported) {
|
||||
continue;
|
||||
}
|
||||
image = await getImage({ ...decodedImagePath, src: imported });
|
||||
}
|
||||
imageObjects.set(imagePath, image);
|
||||
} catch {
|
||||
throw new Error(`Failed to parse image reference: ${imagePath}`);
|
||||
}
|
||||
}
|
||||
return html.replaceAll(CONTENT_LAYER_IMAGE_REGEX, (full, imagePath) => {
|
||||
const image = imageObjects.get(imagePath);
|
||||
if (!image) {
|
||||
return full;
|
||||
}
|
||||
const { index, ...attributes } = image.attributes;
|
||||
return Object.entries({
|
||||
...attributes,
|
||||
src: image.src,
|
||||
srcset: image.srcSet.attribute,
|
||||
// This attribute is used by the toolbar audit
|
||||
...Object.assign(__vite_import_meta_env__, { _: process.env._ }).DEV ? { "data-image-component": "true" } : {}
|
||||
}).map(([key, value]) => value ? `${key}="${escape(value)}"` : "").join(" ");
|
||||
});
|
||||
}
|
||||
function updateImageReferencesInData(data, fileName, imageAssetMap) {
|
||||
return new Traverse(data).map(function(ctx, val) {
|
||||
if (typeof val === "string" && val.startsWith(IMAGE_IMPORT_PREFIX)) {
|
||||
const src = val.replace(IMAGE_IMPORT_PREFIX, "");
|
||||
const id = imageSrcToImportId(src, fileName);
|
||||
if (!id) {
|
||||
ctx.update(src);
|
||||
return;
|
||||
}
|
||||
const imported = imageAssetMap?.get(id);
|
||||
if (imported) {
|
||||
ctx.update(imported);
|
||||
} else {
|
||||
ctx.update(src);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
async function renderEntry(entry) {
|
||||
if (!entry) {
|
||||
throw new AstroError(RenderUndefinedEntryError);
|
||||
}
|
||||
if ("render" in entry && !("legacyId" in entry)) {
|
||||
return entry.render();
|
||||
}
|
||||
if (entry.deferredRender) {
|
||||
try {
|
||||
const { default: contentModules } = await import('./content-modules_Dz-S_Wwv.mjs');
|
||||
const renderEntryImport = contentModules.get(entry.filePath);
|
||||
return render({
|
||||
collection: "",
|
||||
id: entry.id,
|
||||
renderEntryImport
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
const html = entry?.rendered?.metadata?.imagePaths?.length && entry.filePath ? await updateImageReferencesInBody(entry.rendered.html, entry.filePath) : entry?.rendered?.html;
|
||||
const Content = createComponent(() => renderTemplate`${unescapeHTML(html)}`);
|
||||
return {
|
||||
Content,
|
||||
headings: entry?.rendered?.metadata?.headings ?? [],
|
||||
remarkPluginFrontmatter: entry?.rendered?.metadata?.frontmatter ?? {}
|
||||
};
|
||||
}
|
||||
async function render({
|
||||
collection,
|
||||
id,
|
||||
renderEntryImport
|
||||
}) {
|
||||
const UnexpectedRenderError = new AstroError({
|
||||
...UnknownContentCollectionError,
|
||||
message: `Unexpected error while rendering ${String(collection)} → ${String(id)}.`
|
||||
});
|
||||
if (typeof renderEntryImport !== "function") throw UnexpectedRenderError;
|
||||
const baseMod = await renderEntryImport();
|
||||
if (baseMod == null || typeof baseMod !== "object") throw UnexpectedRenderError;
|
||||
const { default: defaultMod } = baseMod;
|
||||
if (isPropagatedAssetsModule(defaultMod)) {
|
||||
const { collectedStyles, collectedLinks, collectedScripts, getMod } = defaultMod;
|
||||
if (typeof getMod !== "function") throw UnexpectedRenderError;
|
||||
const propagationMod = await getMod();
|
||||
if (propagationMod == null || typeof propagationMod !== "object") throw UnexpectedRenderError;
|
||||
const Content = createComponent({
|
||||
factory(result, baseProps, slots) {
|
||||
let styles = "", links = "", scripts = "";
|
||||
if (Array.isArray(collectedStyles)) {
|
||||
styles = collectedStyles.map((style) => {
|
||||
return renderUniqueStylesheet(result, {
|
||||
type: "inline",
|
||||
content: style
|
||||
});
|
||||
}).join("");
|
||||
}
|
||||
if (Array.isArray(collectedLinks)) {
|
||||
links = collectedLinks.map((link) => {
|
||||
return renderUniqueStylesheet(result, {
|
||||
type: "external",
|
||||
src: isRemotePath(link) ? link : prependForwardSlash(link)
|
||||
});
|
||||
}).join("");
|
||||
}
|
||||
if (Array.isArray(collectedScripts)) {
|
||||
scripts = collectedScripts.map((script) => renderScriptElement(script)).join("");
|
||||
}
|
||||
let props = baseProps;
|
||||
if (id.endsWith("mdx")) {
|
||||
props = {
|
||||
components: propagationMod.components ?? {},
|
||||
...baseProps
|
||||
};
|
||||
}
|
||||
return createHeadAndContent(
|
||||
unescapeHTML(styles + links + scripts),
|
||||
renderTemplate`${renderComponent(
|
||||
result,
|
||||
"Content",
|
||||
propagationMod.Content,
|
||||
props,
|
||||
slots
|
||||
)}`
|
||||
);
|
||||
},
|
||||
propagation: "self"
|
||||
});
|
||||
return {
|
||||
Content,
|
||||
headings: propagationMod.getHeadings?.() ?? [],
|
||||
remarkPluginFrontmatter: propagationMod.frontmatter ?? {}
|
||||
};
|
||||
} else if (baseMod.Content && typeof baseMod.Content === "function") {
|
||||
return {
|
||||
Content: baseMod.Content,
|
||||
headings: baseMod.getHeadings?.() ?? [],
|
||||
remarkPluginFrontmatter: baseMod.frontmatter ?? {}
|
||||
};
|
||||
} else {
|
||||
throw UnexpectedRenderError;
|
||||
}
|
||||
}
|
||||
function isPropagatedAssetsModule(module) {
|
||||
return typeof module === "object" && module != null && "__astroPropagation" in module;
|
||||
}
|
||||
|
||||
// astro-head-inject
|
||||
|
||||
const liveCollections = {};
|
||||
|
||||
const contentDir = '/src/content/';
|
||||
|
||||
const contentEntryGlob = "";
|
||||
const contentCollectionToEntryMap = createCollectionToGlobResultMap({
|
||||
globResult: contentEntryGlob,
|
||||
contentDir,
|
||||
});
|
||||
|
||||
const dataEntryGlob = "";
|
||||
const dataCollectionToEntryMap = createCollectionToGlobResultMap({
|
||||
globResult: dataEntryGlob,
|
||||
contentDir,
|
||||
});
|
||||
createCollectionToGlobResultMap({
|
||||
globResult: { ...contentEntryGlob, ...dataEntryGlob },
|
||||
contentDir,
|
||||
});
|
||||
|
||||
let lookupMap = {};
|
||||
lookupMap = {};
|
||||
|
||||
new Set(Object.keys(lookupMap));
|
||||
|
||||
function createGlobLookup(glob) {
|
||||
return async (collection, lookupId) => {
|
||||
const filePath = lookupMap[collection]?.entries[lookupId];
|
||||
|
||||
if (!filePath) return undefined;
|
||||
return glob[collection][filePath];
|
||||
};
|
||||
}
|
||||
|
||||
const renderEntryGlob = "";
|
||||
const collectionToRenderEntryMap = createCollectionToGlobResultMap({
|
||||
globResult: renderEntryGlob,
|
||||
contentDir,
|
||||
});
|
||||
|
||||
const cacheEntriesByCollection = new Map();
|
||||
const getCollection = createGetCollection({
|
||||
contentCollectionToEntryMap,
|
||||
dataCollectionToEntryMap,
|
||||
getRenderEntryImport: createGlobLookup(collectionToRenderEntryMap),
|
||||
cacheEntriesByCollection,
|
||||
liveCollections,
|
||||
});
|
||||
|
||||
export { DEFAULT_OUTPUT_FORMAT as D, VALID_SUPPORTED_FORMATS as V, DEFAULT_HASH_PROPS as a, getCollection as g, renderEntry as r };
|
||||
File diff suppressed because one or more lines are too long
2058
dealplustech-astro/dist/chunks/astro/server_D-JZF3a4.mjs
vendored
2058
dealplustech-astro/dist/chunks/astro/server_D-JZF3a4.mjs
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,3 +0,0 @@
|
||||
import 'piccolore';
|
||||
import './astro/server_D-JZF3a4.mjs';
|
||||
import 'clsx';
|
||||
@@ -1,3 +0,0 @@
|
||||
const contentAssets = new Map();
|
||||
|
||||
export { contentAssets as default };
|
||||
@@ -1,3 +0,0 @@
|
||||
const contentModules = new Map();
|
||||
|
||||
export { contentModules as default };
|
||||
101
dealplustech-astro/dist/chunks/sharp_Dml_25Ys.mjs
vendored
101
dealplustech-astro/dist/chunks/sharp_Dml_25Ys.mjs
vendored
@@ -1,101 +0,0 @@
|
||||
import { A as AstroError, z as MissingSharp } from './astro/server_D-JZF3a4.mjs';
|
||||
import { b as baseService, p as parseQuality } from './_astro_assets_CWrzoUoo.mjs';
|
||||
|
||||
let sharp;
|
||||
const qualityTable = {
|
||||
low: 25,
|
||||
mid: 50,
|
||||
high: 80,
|
||||
max: 100
|
||||
};
|
||||
async function loadSharp() {
|
||||
let sharpImport;
|
||||
try {
|
||||
sharpImport = (await import('sharp')).default;
|
||||
} catch {
|
||||
throw new AstroError(MissingSharp);
|
||||
}
|
||||
sharpImport.cache(false);
|
||||
return sharpImport;
|
||||
}
|
||||
const fitMap = {
|
||||
fill: "fill",
|
||||
contain: "inside",
|
||||
cover: "cover",
|
||||
none: "outside",
|
||||
"scale-down": "inside",
|
||||
outside: "outside",
|
||||
inside: "inside"
|
||||
};
|
||||
const sharpService = {
|
||||
validateOptions: baseService.validateOptions,
|
||||
getURL: baseService.getURL,
|
||||
parseURL: baseService.parseURL,
|
||||
getHTMLAttributes: baseService.getHTMLAttributes,
|
||||
getSrcSet: baseService.getSrcSet,
|
||||
async transform(inputBuffer, transformOptions, config) {
|
||||
if (!sharp) sharp = await loadSharp();
|
||||
const transform = transformOptions;
|
||||
const kernel = config.service.config.kernel;
|
||||
if (transform.format === "svg") return { data: inputBuffer, format: "svg" };
|
||||
const result = sharp(inputBuffer, {
|
||||
failOnError: false,
|
||||
pages: -1,
|
||||
limitInputPixels: config.service.config.limitInputPixels
|
||||
});
|
||||
result.rotate();
|
||||
const { format } = await result.metadata();
|
||||
const withoutEnlargement = Boolean(transform.fit);
|
||||
if (transform.width && transform.height && transform.fit) {
|
||||
const fit = fitMap[transform.fit] ?? "inside";
|
||||
result.resize({
|
||||
width: Math.round(transform.width),
|
||||
height: Math.round(transform.height),
|
||||
kernel,
|
||||
fit,
|
||||
position: transform.position,
|
||||
withoutEnlargement
|
||||
});
|
||||
} else if (transform.height && !transform.width) {
|
||||
result.resize({
|
||||
height: Math.round(transform.height),
|
||||
kernel,
|
||||
withoutEnlargement
|
||||
});
|
||||
} else if (transform.width) {
|
||||
result.resize({
|
||||
width: Math.round(transform.width),
|
||||
kernel,
|
||||
withoutEnlargement
|
||||
});
|
||||
}
|
||||
if (transform.background) {
|
||||
result.flatten({ background: transform.background });
|
||||
}
|
||||
if (transform.format) {
|
||||
let quality = void 0;
|
||||
if (transform.quality) {
|
||||
const parsedQuality = parseQuality(transform.quality);
|
||||
if (typeof parsedQuality === "number") {
|
||||
quality = parsedQuality;
|
||||
} else {
|
||||
quality = transform.quality in qualityTable ? qualityTable[transform.quality] : void 0;
|
||||
}
|
||||
}
|
||||
if (transform.format === "webp" && format === "gif") {
|
||||
result.webp({ quality: typeof quality === "number" ? quality : void 0, loop: 0 });
|
||||
} else {
|
||||
result.toFormat(transform.format, { quality });
|
||||
}
|
||||
}
|
||||
const { data, info } = await result.toBuffer({ resolveWithObject: true });
|
||||
const needsCopy = "buffer" in data && data.buffer instanceof SharedArrayBuffer;
|
||||
return {
|
||||
data: needsCopy ? new Uint8Array(data) : data,
|
||||
format: info.format
|
||||
};
|
||||
}
|
||||
};
|
||||
var sharp_default = sharpService;
|
||||
|
||||
export { sharp_default as default };
|
||||
1806
dealplustech-astro/dist/chunks/site-config_BJHCdssj.mjs
vendored
1806
dealplustech-astro/dist/chunks/site-config_BJHCdssj.mjs
vendored
File diff suppressed because it is too large
Load Diff
BIN
dealplustech-astro/dist/favicon.ico
vendored
BIN
dealplustech-astro/dist/favicon.ico
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 655 B |
9
dealplustech-astro/dist/favicon.svg
vendored
9
dealplustech-astro/dist/favicon.svg
vendored
@@ -1,9 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||
<style>
|
||||
path { fill: #000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: #FFF; }
|
||||
}
|
||||
</style>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 749 B |
157
dealplustech-astro/dist/manifest_bX0VrZBn.mjs
vendored
157
dealplustech-astro/dist/manifest_bX0VrZBn.mjs
vendored
File diff suppressed because one or more lines are too long
3
dealplustech-astro/dist/noop-entrypoint.mjs
vendored
3
dealplustech-astro/dist/noop-entrypoint.mjs
vendored
@@ -1,3 +0,0 @@
|
||||
const server = {};
|
||||
|
||||
export { server };
|
||||
46
dealplustech-astro/dist/pages/blog.astro.mjs
vendored
46
dealplustech-astro/dist/pages/blog.astro.mjs
vendored
@@ -1,46 +0,0 @@
|
||||
import { c as createComponent, m as maybeRenderHead, d as addAttribute, a as renderTemplate, b as createAstro, r as renderComponent } from '../chunks/astro/server_D-JZF3a4.mjs';
|
||||
import 'piccolore';
|
||||
import { g as getCollection } from '../chunks/_astro_content_C5hvN5fw.mjs';
|
||||
import 'clsx';
|
||||
import { $ as $$BaseLayout } from '../chunks/BaseLayout_CQrO8n43.mjs';
|
||||
export { renderers } from '../renderers.mjs';
|
||||
|
||||
const $$Astro = createAstro();
|
||||
const $$BlogCard = createComponent(($$result, $$props, $$slots) => {
|
||||
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
|
||||
Astro2.self = $$BlogCard;
|
||||
const { post } = Astro2.props;
|
||||
const { title, excerpt, date, author, category, categories, image, featuredImage } = post.data;
|
||||
const postCategory = category || (Array.isArray(categories) ? categories[0] : "\u0E17\u0E31\u0E48\u0E27\u0E44\u0E1B");
|
||||
const postImage = image || featuredImage || "/images/2021/03/ppr-pipe_000C.jpg";
|
||||
return renderTemplate`${maybeRenderHead()}<a${addAttribute(`/blog/${post.slug}`, "href")} class="card group"> <div class="relative aspect-video bg-secondary-100 overflow-hidden"> <img${addAttribute(postImage, "src")}${addAttribute(title, "alt")} class="object-cover w-full h-48 group-hover:scale-105 transition-transform duration-300" loading="lazy"> <div class="absolute top-4 left-4"> <span class="industrial-badge">${postCategory}</span> </div> </div> <div class="p-6"> <time class="text-sm text-secondary-500"> ${new Date(date).toLocaleDateString("th-TH", { year: "numeric", month: "long", day: "numeric" })} </time> <h3 class="mt-2 text-xl font-bold text-secondary-900 group-hover:text-primary-600 transition-colors line-clamp-2"> ${title} </h3> ${excerpt && renderTemplate`<p class="mt-3 text-secondary-600 text-sm line-clamp-3"> ${excerpt} </p>`} <div class="mt-4 flex items-center text-primary-600 font-medium"> <span>อ่านต่อ</span> <svg class="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M9 5l7 7-7 7"></path> </svg> </div> </div> </a>`;
|
||||
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/components/BlogCard.astro", void 0);
|
||||
|
||||
const metadata = {
|
||||
title: "\u0E1A\u0E17\u0E04\u0E27\u0E32\u0E21\u0E04\u0E27\u0E32\u0E21\u0E23\u0E39\u0E49",
|
||||
description: "\u0E1A\u0E17\u0E04\u0E27\u0E32\u0E21\u0E04\u0E27\u0E32\u0E21\u0E23\u0E39\u0E49\u0E40\u0E01\u0E35\u0E48\u0E22\u0E27\u0E01\u0E31\u0E1A\u0E27\u0E31\u0E2A\u0E14\u0E38\u0E17\u0E48\u0E2D \u0E2D\u0E38\u0E1B\u0E01\u0E23\u0E13\u0E4C\u0E23\u0E30\u0E1A\u0E1A\u0E17\u0E48\u0E2D \u0E41\u0E25\u0E30\u0E40\u0E17\u0E04\u0E19\u0E34\u0E04\u0E01\u0E32\u0E23\u0E15\u0E34\u0E14\u0E15\u0E31\u0E49\u0E07"
|
||||
};
|
||||
const prerender = true;
|
||||
const $$Index = createComponent(async ($$result, $$props, $$slots) => {
|
||||
const posts = await getCollection("blog");
|
||||
return renderTemplate`${renderComponent($$result, "BaseLayout", $$BaseLayout, { "title": metadata.title, "description": metadata.description }, { "default": async ($$result2) => renderTemplate` ${maybeRenderHead()}<main class="pt-32 pb-16"> <div class="container mx-auto px-4"> <!-- Hero --> <div class="text-center mb-12"> <h1 class="text-4xl md:text-5xl font-bold text-secondary-900 mb-4">
|
||||
บทความ<span class="text-primary-600">ความรู้</span> </h1> <p class="text-xl text-secondary-600 max-w-2xl mx-auto">
|
||||
บทความความรู้เกี่ยวกับวัสดุท่อ อุปกรณ์ระบบท่อ และเทคนิคการติดตั้ง
|
||||
</p> </div> <!-- Blog Grid --> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> ${posts.map((post) => renderTemplate`${renderComponent($$result2, "BlogCard", $$BlogCard, { "post": post })}`)} </div> </div> </main> ` })}`;
|
||||
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/blog/index.astro", void 0);
|
||||
|
||||
const $$file = "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/blog/index.astro";
|
||||
const $$url = "/blog";
|
||||
|
||||
const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
||||
__proto__: null,
|
||||
default: $$Index,
|
||||
file: $$file,
|
||||
metadata,
|
||||
prerender,
|
||||
url: $$url
|
||||
}, Symbol.toStringTag, { value: 'Module' }));
|
||||
|
||||
const page = () => _page;
|
||||
|
||||
export { page };
|
||||
@@ -1,45 +0,0 @@
|
||||
import { c as createComponent, r as renderComponent, a as renderTemplate, b as createAstro, m as maybeRenderHead, d as addAttribute } from '../../chunks/astro/server_D-JZF3a4.mjs';
|
||||
import 'piccolore';
|
||||
import { r as renderEntry, g as getCollection } from '../../chunks/_astro_content_C5hvN5fw.mjs';
|
||||
import { $ as $$BaseLayout } from '../../chunks/BaseLayout_CQrO8n43.mjs';
|
||||
export { renderers } from '../../renderers.mjs';
|
||||
|
||||
const $$Astro = createAstro();
|
||||
async function getStaticPaths() {
|
||||
const posts = await getCollection("blog");
|
||||
return posts.map((post) => ({
|
||||
params: { slug: post.id },
|
||||
props: { post }
|
||||
}));
|
||||
}
|
||||
const $$slug = createComponent(async ($$result, $$props, $$slots) => {
|
||||
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
|
||||
Astro2.self = $$slug;
|
||||
const { post } = Astro2.props;
|
||||
const { Content } = await renderEntry(post);
|
||||
const { title, date, author, category, categories, image, featuredImage } = post.data;
|
||||
const postCategory = category || (Array.isArray(categories) ? categories[0] : "\u0E17\u0E31\u0E48\u0E27\u0E44\u0E1B");
|
||||
const postImage = image || featuredImage || "/images/2021/03/ppr-pipe_000C.jpg";
|
||||
return renderTemplate`${renderComponent($$result, "BaseLayout", $$BaseLayout, { "title": title, "description": post.data.excerpt }, { "default": async ($$result2) => renderTemplate` ${maybeRenderHead()}<main class="pt-32 pb-16"> <article class="container mx-auto px-4 max-w-4xl"> <!-- Header --> <header class="mb-8"> <div class="flex items-center gap-4 mb-4"> <span class="industrial-badge">${postCategory}</span> <time class="text-secondary-500"> ${new Date(date).toLocaleDateString("th-TH", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric"
|
||||
})} </time> <span class="text-secondary-500">•</span> <span class="text-secondary-500">${author}</span> </div> <h1 class="text-4xl md:text-5xl font-bold text-secondary-900 mb-4"> ${title} </h1> </header> <!-- Featured Image --> <div class="relative aspect-video bg-secondary-100 rounded-xl overflow-hidden mb-8"> <img${addAttribute(postImage, "src")}${addAttribute(title, "alt")} class="object-cover w-full h-full" loading="lazy"> </div> <!-- Content --> <div class="prose prose-lg max-w-none prose-headings:font-bold prose-a:text-primary-600 hover:prose-a:text-primary-700 prose-img:rounded-xl"> ${renderComponent($$result2, "Content", Content, {})} </div> <!-- Back to Blog --> <div class="mt-12 pt-8 border-t border-secondary-200"> <a href="/blog/" class="inline-flex items-center text-primary-600 font-medium hover:text-primary-700 transition-colors"> <svg class="w-5 h-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M10 19l-7-7m0 0l7-7m-7 7h18"></path> </svg>
|
||||
กลับสู่หน้าบทความ
|
||||
</a> </div> </article> </main> ` })}`;
|
||||
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/blog/[slug].astro", void 0);
|
||||
|
||||
const $$file = "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/blog/[slug].astro";
|
||||
const $$url = "/blog/[slug]";
|
||||
|
||||
const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
||||
__proto__: null,
|
||||
default: $$slug,
|
||||
file: $$file,
|
||||
getStaticPaths,
|
||||
url: $$url
|
||||
}, Symbol.toStringTag, { value: 'Module' }));
|
||||
|
||||
const page = () => _page;
|
||||
|
||||
export { page };
|
||||
65
dealplustech-astro/dist/pages/index.astro.mjs
vendored
65
dealplustech-astro/dist/pages/index.astro.mjs
vendored
@@ -1,65 +0,0 @@
|
||||
import { c as createComponent, m as maybeRenderHead, d as addAttribute, a as renderTemplate, r as renderComponent } from '../chunks/astro/server_D-JZF3a4.mjs';
|
||||
import 'piccolore';
|
||||
import { $ as $$BaseLayout } from '../chunks/BaseLayout_CQrO8n43.mjs';
|
||||
import 'clsx';
|
||||
import { s as siteConfig, p as productCategories } from '../chunks/site-config_BJHCdssj.mjs';
|
||||
export { renderers } from '../renderers.mjs';
|
||||
|
||||
const $$FloatingContact = createComponent(($$result, $$props, $$slots) => {
|
||||
return renderTemplate`${maybeRenderHead()}<div class="fixed bottom-6 right-6 z-40 flex flex-col gap-3"> <!-- LINE --> <a${addAttribute(`https://line.me/ti/p/${siteConfig.lineId}`, "href")} target="_blank" rel="noopener noreferrer" class="w-14 h-14 bg-[#00B900] rounded-full flex items-center justify-center shadow-lg hover:scale-110 transition-transform" aria-label="ติดต่อผ่าน LINE"> <svg class="w-7 h-7 text-white" viewBox="0 0 24 24" fill="currentColor"> <path d="M19.365 9.863c.349 0 .63.285.63.631 0 .345-.281.63-.63.63H17.61v1.125h1.755c.349 0 .63.283.63.63 0 .344-.281.629-.63.629h-2.386c-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63h2.386c.346 0 .627.285.627.63 0 .349-.281.63-.63.63H17.61v1.125h1.755zm-3.855 3.016c0 .27-.174.51-.432.596-.064.021-.133.031-.199.031-.211 0-.391-.09-.51-.25l-2.443-3.317v2.94c0 .344-.279.629-.631.629-.346 0-.626-.285-.626-.629V8.108c0-.27.173-.51.43-.595.06-.023.136-.033.194-.033.195 0 .375.104.495.254l2.462 3.33V8.108c0-.345.282-.63.63-.63.345 0 .63.285.63.63v4.771zm-5.741 0c0 .344-.282.629-.631.629-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63.346 0 .628.285.628.63v4.771zm-2.466.629H4.917c-.345 0-.63-.285-.63-.629V8.108c0-.345.285-.63.63-.63.348 0 .63.285.63.63v4.141h1.756c.348 0 .629.283.629.63 0 .344-.282.629-.629.629M24 10.314C24 4.943 18.615.572 12 .572S0 4.943 0 10.314c0 4.811 4.27 8.842 10.035 9.608.391.082.923.258 1.058.59.12.301.079.766.038 1.08l-.164 1.02c-.045.301-.24 1.186 1.049.645 1.291-.539 6.916-4.078 9.436-6.975C23.176 14.393 24 12.458 24 10.314"></path> </svg> </a> <!-- Phone --> <a${addAttribute(`tel:${siteConfig.phone}`, "href")} class="w-14 h-14 bg-primary-600 rounded-full flex items-center justify-center shadow-lg hover:scale-110 transition-transform" aria-label="โทรหาเรา"> <svg class="w-7 h-7 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"></path> </svg> </a> </div>`;
|
||||
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/components/FloatingContact.astro", void 0);
|
||||
|
||||
const $$Index = createComponent(($$result, $$props, $$slots) => {
|
||||
const featuredProducts = productCategories.filter(
|
||||
(p) => ["ppr-elephant", "hdpe", "poloplast", "syler", "xylent"].includes(p.id)
|
||||
).slice(0, 6);
|
||||
return renderTemplate`${renderComponent($$result, "BaseLayout", $$BaseLayout, { "title": "\u0E2B\u0E19\u0E49\u0E32\u0E41\u0E23\u0E01", "description": "\u0E1A\u0E23\u0E34\u0E29\u0E31\u0E17 \u0E14\u0E35\u0E25 \u0E1E\u0E25\u0E31\u0E2A \u0E40\u0E17\u0E04 \u0E08\u0E33\u0E01\u0E31\u0E14 - \u0E1C\u0E39\u0E49\u0E40\u0E0A\u0E35\u0E48\u0E22\u0E27\u0E0A\u0E32\u0E0D\u0E14\u0E49\u0E32\u0E19\u0E23\u0E30\u0E1A\u0E1A\u0E17\u0E48\u0E2D\u0E41\u0E25\u0E30 HVAC" }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<main> <!-- Hero Section --> <section class="relative h-[70vh] min-h-[500px] bg-secondary-900"> <div class="absolute inset-0 bg-gradient-to-r from-secondary-900 via-secondary-900/90 to-secondary-900/60 z-10"></div> <img src="/images/2021/03/ppr-pipe_000C.jpg" alt="ท่อพีพีอาร์คุณภาพสูง" class="absolute inset-0 w-full h-full object-cover opacity-50" loading="eager"> <div class="relative z-20 container mx-auto px-4 h-full flex items-center"> <div class="max-w-2xl"> <span class="inline-block px-4 py-2 bg-primary-600 text-white font-semibold mb-4 rounded">
|
||||
ผู้เชี่ยวชาญด้านระบบท่อและ HVAC
|
||||
</span> <h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-6 leading-tight">
|
||||
วัสดุท่อ อุปกรณ์ HVAC
|
||||
<span class="text-primary-400 block">และฉนวนหุ้มท่อ</span> </h1> <p class="text-lg md:text-xl text-secondary-200 mb-8">
|
||||
จำหน่ายและติดตั้งท่อ PPR, ท่อ HDPE, กริลแอร์, เทอร์โมเบรค และอุปกรณ์ระบบท่อครบวงจร พร้อมบริการให้คำปรึกษาจากทีมมืออาชีพ
|
||||
</p> <div class="flex flex-wrap gap-4"> <a href="/products/" class="btn-primary">
|
||||
ดูสินค้าทั้งหมด
|
||||
</a> <a href="/contact-us/" class="btn-outline border-white text-white hover:bg-white hover:text-secondary-900">
|
||||
ขอใบเสนอราคา
|
||||
</a> </div> </div> </div> </section> <!-- Features Section --> <section class="py-16 bg-secondary-800"> <div class="container mx-auto px-4"> <div class="grid grid-cols-1 md:grid-cols-3 gap-8"> <div class="text-center p-6"> <div class="w-16 h-16 bg-primary-600 rounded-lg flex items-center justify-center mx-auto mb-4"> <svg class="w-8 h-8 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"></path> </svg> </div> <h3 class="text-xl font-bold text-white mb-2">สินค้าคุณภาพ</h3> <p class="text-secondary-300">
|
||||
สินค้าทุกชิ้นผ่านมาตรฐานคุณภาพ พร้อมรับประกัน
|
||||
</p> </div> <div class="text-center p-6"> <div class="w-16 h-16 bg-primary-600 rounded-lg flex items-center justify-center mx-auto mb-4"> <svg class="w-8 h-8 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M13 10V3L4 14h7v7l9-11h-7z"></path> </svg> </div> <h3 class="text-xl font-bold text-white mb-2">จัดส่งรวดเร็ว</h3> <p class="text-secondary-300">
|
||||
จัดส่งสินค้าทั่วประเทศ รวดเร็วและปลอดภัย
|
||||
</p> </div> <div class="text-center p-6"> <div class="w-16 h-16 bg-primary-600 rounded-lg flex items-center justify-center mx-auto mb-4"> <svg class="w-8 h-8 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z"></path> </svg> </div> <h3 class="text-xl font-bold text-white mb-2">บริการหลังการขาย</h3> <p class="text-secondary-300">
|
||||
ทีมงานพร้อมให้คำปรึกษาและดูแลอย่างต่อเนื่อง
|
||||
</p> </div> </div> </div> </section> <!-- Featured Products --> <section class="py-16 bg-secondary-50"> <div class="container mx-auto px-4"> <div class="text-center mb-12"> <h2 class="text-3xl md:text-4xl font-bold text-secondary-900 mb-4">
|
||||
สินค้า<span class="text-primary-600">เด่น</span> </h2> <p class="text-secondary-600 text-lg">ผลิตภัณฑ์คุณภาพสูงที่ได้รับความนิยม</p> </div> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> ${featuredProducts.map((product) => renderTemplate`<a${addAttribute(product.href, "href")} class="card group"> <div class="relative aspect-video bg-secondary-100 overflow-hidden"> <img${addAttribute(product.image, "src")}${addAttribute(product.name, "alt")} class="object-cover w-full h-48 group-hover:scale-105 transition-transform duration-300" loading="lazy"> </div> <div class="p-6"> <h3 class="text-lg font-bold text-secondary-900 group-hover:text-primary-600 transition-colors"> ${product.name} </h3> <p class="mt-2 text-sm text-secondary-600 line-clamp-2"> ${product.shortDescription || product.description} </p> <div class="mt-4 flex items-center text-primary-600 font-medium"> <span>ดูรายละเอียด</span> <svg class="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M9 5l7 7-7 7"></path> </svg> </div> </div> </a>`)} </div> <div class="text-center mt-12"> <a href="/products/" class="btn-primary">
|
||||
ดูสินค้าทั้งหมด
|
||||
</a> </div> </div> </section> <!-- About Preview --> <section class="py-16 bg-white"> <div class="container mx-auto px-4"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center"> <div> <h2 class="text-3xl md:text-4xl font-bold text-secondary-900 mb-6">
|
||||
เกี่ยวกับ<span class="text-primary-600">เรา</span> </h2> <p class="text-lg text-secondary-600 mb-6">
|
||||
บริษัท ดีล พลัส เทค จำกัด เราเป็นผู้เชียวชาญด้านระบบน้ำ ให้คำแนะนำและจำหน่ายท่อ PPR ตราช้าง ท่อพีพีอาร์ ท่อ PPR ท่อ HDPE Thai PPR คุณภาพสูง ราคาถูก
|
||||
</p> <p class="text-secondary-700 mb-8">
|
||||
ด้วยประสบการณ์ยาวนาน เราพร้อมให้บริการสินค้าคุณภาพและคำแนะนำจากผู้เชี่ยวชาญ เพื่อให้งานระบบของคุณมีประสิทธิภาพสูงสุด
|
||||
</p> <a href="/about-us/" class="btn-secondary">
|
||||
อ่านเพิ่มเติม
|
||||
</a> </div> <div class="relative aspect-video bg-secondary-100 rounded-xl overflow-hidden"> <img src="/images/2021/03/ppr-pipe_000C.jpg" alt="เกี่ยวกับดีลพลัสเทค" class="object-cover w-full h-full" loading="lazy"> </div> </div> </div> </section> <!-- CTA Section --> <section class="py-16 bg-primary-600"> <div class="container mx-auto px-4 text-center"> <h2 class="text-3xl md:text-4xl font-bold text-white mb-4">
|
||||
สนใจสินค้าหรือต้องการคำปรึกษา?
|
||||
</h2> <p class="text-xl text-primary-100 mb-8 max-w-2xl mx-auto">
|
||||
ทีมงานของเราพร้อมให้คำแนะนำและช่วยคุณเลือกสินค้าที่เหมาะสมที่สุด
|
||||
</p> <div class="flex flex-wrap justify-center gap-4"> <a href="tel:090-555-1415" class="btn-secondary bg-white text-primary-600 hover:bg-primary-50">
|
||||
โทร: 090-555-1415
|
||||
</a> <a href="https://line.me/ti/p/@dealplustech" target="_blank" rel="noopener" class="btn-outline border-white text-white hover:bg-white hover:text-primary-600">
|
||||
เพิ่มเพื่อน LINE
|
||||
</a> </div> </div> </section> </main> ${renderComponent($$result2, "FloatingContact", $$FloatingContact, {})} ` })}`;
|
||||
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/index.astro", void 0);
|
||||
|
||||
const $$file = "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/index.astro";
|
||||
const $$url = "";
|
||||
|
||||
const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
||||
__proto__: null,
|
||||
default: $$Index,
|
||||
file: $$file,
|
||||
url: $$url
|
||||
}, Symbol.toStringTag, { value: 'Module' }));
|
||||
|
||||
const page = () => _page;
|
||||
|
||||
export { page };
|
||||
34
dealplustech-astro/dist/pages/products.astro.mjs
vendored
34
dealplustech-astro/dist/pages/products.astro.mjs
vendored
@@ -1,34 +0,0 @@
|
||||
import { c as createComponent, m as maybeRenderHead, d as addAttribute, a as renderTemplate, b as createAstro, r as renderComponent } from '../chunks/astro/server_D-JZF3a4.mjs';
|
||||
import 'piccolore';
|
||||
import { g as getCollection } from '../chunks/_astro_content_C5hvN5fw.mjs';
|
||||
import 'clsx';
|
||||
import { $ as $$BaseLayout } from '../chunks/BaseLayout_CQrO8n43.mjs';
|
||||
export { renderers } from '../renderers.mjs';
|
||||
|
||||
const $$Astro = createAstro();
|
||||
const $$ProductCard = createComponent(($$result, $$props, $$slots) => {
|
||||
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
|
||||
Astro2.self = $$ProductCard;
|
||||
const { product } = Astro2.props;
|
||||
const { name, shortDescription, image } = product.data;
|
||||
return renderTemplate`${maybeRenderHead()}<a${addAttribute(`/products/${product.data.slug}`, "href")} class="card group"> <div class="aspect-w-16 aspect-h-9 overflow-hidden bg-secondary-100"> <img${addAttribute(image || "/placeholder.jpg", "src")}${addAttribute(name, "alt")} class="object-cover w-full h-48 group-hover:scale-105 transition-transform duration-300" loading="lazy"> </div> <div class="p-6"> <h3 class="text-lg font-bold text-secondary-900 group-hover:text-primary-600 transition-colors"> ${name} </h3> ${shortDescription && renderTemplate`<p class="mt-2 text-sm text-secondary-600 line-clamp-2"> ${shortDescription} </p>`} <div class="mt-4 flex items-center text-primary-600 font-medium"> <span>ดูรายละเอียด</span> <svg class="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M9 5l7 7-7 7"></path> </svg> </div> </div> </a>`;
|
||||
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/components/ProductCard.astro", void 0);
|
||||
|
||||
const $$Index = createComponent(async ($$result, $$props, $$slots) => {
|
||||
const products = await getCollection("products");
|
||||
return renderTemplate`${renderComponent($$result, "BaseLayout", $$BaseLayout, { "title": "\u0E2A\u0E34\u0E19\u0E04\u0E49\u0E32\u0E17\u0E31\u0E49\u0E07\u0E2B\u0E21\u0E14", "description": "\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23\u0E2A\u0E34\u0E19\u0E04\u0E49\u0E32\u0E17\u0E31\u0E49\u0E07\u0E2B\u0E21\u0E14\u0E08\u0E32\u0E01 Deal Plus Tech" }, { "default": async ($$result2) => renderTemplate` ${maybeRenderHead()}<main class="py-12"> <div class="container mx-auto px-4 max-w-6xl"> <h1 class="section-title mb-8">สินค้าทั้งหมด</h1> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> ${products.map((product) => renderTemplate`${renderComponent($$result2, "ProductCard", $$ProductCard, { "product": product })}`)} </div> </div> </main> ` })}`;
|
||||
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/products/index.astro", void 0);
|
||||
|
||||
const $$file = "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/products/index.astro";
|
||||
const $$url = "/products";
|
||||
|
||||
const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
||||
__proto__: null,
|
||||
default: $$Index,
|
||||
file: $$file,
|
||||
url: $$url
|
||||
}, Symbol.toStringTag, { value: 'Module' }));
|
||||
|
||||
const page = () => _page;
|
||||
|
||||
export { page };
|
||||
@@ -1,48 +0,0 @@
|
||||
import { c as createComponent, r as renderComponent, a as renderTemplate, b as createAstro, m as maybeRenderHead, d as addAttribute } from '../../chunks/astro/server_D-JZF3a4.mjs';
|
||||
import 'piccolore';
|
||||
import { r as renderEntry, g as getCollection } from '../../chunks/_astro_content_C5hvN5fw.mjs';
|
||||
import { $ as $$BaseLayout } from '../../chunks/BaseLayout_CQrO8n43.mjs';
|
||||
import { p as productCategories } from '../../chunks/site-config_BJHCdssj.mjs';
|
||||
/* empty css */
|
||||
export { renderers } from '../../renderers.mjs';
|
||||
|
||||
const $$Astro = createAstro();
|
||||
async function getStaticPaths() {
|
||||
const products = await getCollection("products");
|
||||
return products.map((product) => ({
|
||||
params: { slug: product.data.slug },
|
||||
props: { product }
|
||||
}));
|
||||
}
|
||||
const $$slug = createComponent(async ($$result, $$props, $$slots) => {
|
||||
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
|
||||
Astro2.self = $$slug;
|
||||
const { product } = Astro2.props;
|
||||
const { Content } = await renderEntry(product);
|
||||
const productData = productCategories.find((p) => p.id === product.data.id);
|
||||
const productTables = productData?.productTables || [];
|
||||
return renderTemplate`${renderComponent($$result, "BaseLayout", $$BaseLayout, { "title": product.data.name, "description": product.data.shortDescription || product.data.description, "data-astro-cid-o422f4lv": true }, { "default": async ($$result2) => renderTemplate` ${maybeRenderHead()}<main class="py-12" data-astro-cid-o422f4lv> <article class="container mx-auto px-4 max-w-7xl" data-astro-cid-o422f4lv> <!-- Product Header --> <header class="mb-12" data-astro-cid-o422f4lv> <h1 class="text-4xl md:text-5xl lg:text-6xl xl:text-7xl font-bold text-secondary-900 mb-6" data-astro-cid-o422f4lv> ${product.data.name} </h1> <p class="text-lg md:text-xl lg:text-2xl xl:text-3xl text-secondary-600 max-w-4xl" data-astro-cid-o422f4lv> ${product.data.description} </p> </header> <!-- Content from Markdown --> <div class="prose prose-lg md:prose-xl lg:prose-2xl max-w-none mb-12" data-astro-cid-o422f4lv> ${renderComponent($$result2, "Content", Content, { "data-astro-cid-o422f4lv": true })} </div> <!-- Product Tables Section --> ${productTables.length > 0 && renderTemplate`<section class="mb-12" data-astro-cid-o422f4lv> <h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-secondary-900 mb-8 flex items-center gap-3" data-astro-cid-o422f4lv> <svg class="w-8 h-8 md:w-10 md:h-10 lg:w-12 lg:h-12 text-primary-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" data-astro-cid-o422f4lv> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" data-astro-cid-o422f4lv></path> </svg>
|
||||
ตารางข้อมูลผลิตภัณฑ์
|
||||
</h2> <div class="space-y-10" data-astro-cid-o422f4lv> ${productTables.map((table2, tableIndex) => renderTemplate`<div class="bg-white rounded-2xl border-2 border-secondary-200 shadow-lg" data-astro-cid-o422f4lv> <h3 class="text-lg md:text-xl lg:text-2xl font-semibold text-secondary-800 p-4 md:p-6 bg-secondary-50 border-b-2 border-secondary-200" data-astro-cid-o422f4lv> ${table2.tableName} </h3> <div class="w-full overflow-x-auto -mx-2 md:mx-0" data-astro-cid-o422f4lv> <div class="px-2 md:px-0" data-astro-cid-o422f4lv> <table class="w-full min-w-[600px] border-collapse" data-astro-cid-o422f4lv> <thead data-astro-cid-o422f4lv> <tr class="bg-primary-100" data-astro-cid-o422f4lv> ${table2.headers.map((header, headerIndex) => renderTemplate`<th class="px-3 py-2 md:px-4 md:py-3 text-left text-xs md:text-sm lg:text-base font-bold text-primary-800 border-b-2 border-primary-300 whitespace-nowrap" data-astro-cid-o422f4lv> ${header} </th>`)} </tr> </thead> <tbody data-astro-cid-o422f4lv> ${table2.rows.map((row, rowIndex) => renderTemplate`<tr${addAttribute(rowIndex % 2 === 0 ? "bg-white" : "bg-secondary-50", "class")} data-astro-cid-o422f4lv> ${row.map((cell, cellIndex) => renderTemplate`<td class="px-3 py-2 md:px-4 md:py-3 text-xs md:text-sm lg:text-base text-secondary-700 border-b border-secondary-100 break-words max-w-[200px] md:max-w-none" data-astro-cid-o422f4lv> ${cell} </td>`)} </tr>`)} </tbody> </table> </div> </div> </div>`)} </div> </section>`})}
|
||||
<tbody data-astro-cid-o422f4lv> ${table.rows.map((row, rowIndex) => renderTemplate`<tr${addAttribute(rowIndex % 2 === 0 ? "bg-white" : "bg-secondary-50", "class")} data-astro-cid-o422f4lv> ${row.map((cell, cellIndex) => renderTemplate`<td class="px-5 py-4 md:px-6 md:py-5 text-base md:text-lg lg:text-xl text-secondary-700 border-b border-secondary-100" data-astro-cid-o422f4lv> ${cell} </td>`)} </tr>`)} </tbody>
|
||||
))}
|
||||
|
||||
|
||||
)}
|
||||
<!-- Specifications --> ${product.data.specifications && product.data.specifications.length > 0 && renderTemplate`<section class="mb-12" data-astro-cid-o422f4lv> <h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-secondary-900 mb-8" data-astro-cid-o422f4lv>ข้อมูลจำเพาะ</h2> <div class="bg-white rounded-2xl border-2 border-secondary-200 p-6 md:p-8 shadow-lg" data-astro-cid-o422f4lv> <dl class="grid grid-cols-1 md:grid-cols-2 gap-6" data-astro-cid-o422f4lv> ${product.data.specifications.map((spec, index) => renderTemplate`<div class="flex flex-col md:flex-row md:justify-between border-b border-secondary-100 pb-4" data-astro-cid-o422f4lv> <dt class="text-base md:text-lg lg:text-xl font-semibold text-secondary-700 mb-2 md:mb-0 md:mr-4" data-astro-cid-o422f4lv>${spec.label}</dt> <dd class="text-base md:text-lg lg:text-xl text-secondary-900" data-astro-cid-o422f4lv> ${spec.value} ${spec.unit && renderTemplate`<span class="text-secondary-500 ml-2" data-astro-cid-o422f4lv>${spec.unit}</span>`} </dd> </div>`)} </dl> </div> </section>`} <!-- Features --> ${product.data.features && product.data.features.length > 0 && renderTemplate`<section class="mb-12" data-astro-cid-o422f4lv> <h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-secondary-900 mb-8" data-astro-cid-o422f4lv>คุณสมบัติเด่น</h2> <ul class="grid grid-cols-1 md:grid-cols-2 gap-6" data-astro-cid-o422f4lv> ${product.data.features.map((feature, index) => renderTemplate`<li class="flex items-start gap-4 bg-white p-6 rounded-xl border border-secondary-200 shadow" data-astro-cid-o422f4lv> <span class="text-2xl md:text-3xl text-primary-600 flex-shrink-0" data-astro-cid-o422f4lv>✓</span> <span class="text-base md:text-lg lg:text-xl text-secondary-700" data-astro-cid-o422f4lv>${feature}</span> </li>`)} </ul> </section>`} <!-- FAQ --> ${product.data.faq && product.data.faq.length > 0 && renderTemplate`<section class="mb-12" data-astro-cid-o422f4lv> <h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-secondary-900 mb-8" data-astro-cid-o422f4lv>คำถามที่พบบ่อย</h2> <div class="space-y-6" data-astro-cid-o422f4lv> ${product.data.faq.map((item, index) => renderTemplate`<div class="bg-white rounded-2xl p-6 md:p-8 border-2 border-secondary-200 shadow" data-astro-cid-o422f4lv> <h3 class="text-xl md:text-2xl lg:text-3xl font-bold text-secondary-900 mb-4" data-astro-cid-o422f4lv>${item.question}</h3> <p class="text-base md:text-lg lg:text-xl text-secondary-700 leading-relaxed" data-astro-cid-o422f4lv>${item.answer}</p> </div>`)} </div> </section>`} </article> </main> ` })} `;
|
||||
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/products/[slug].astro", void 0);
|
||||
|
||||
const $$file = "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/products/[slug].astro";
|
||||
const $$url = "/products/[slug]";
|
||||
|
||||
const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
||||
__proto__: null,
|
||||
default: $$slug,
|
||||
file: $$file,
|
||||
getStaticPaths,
|
||||
url: $$url
|
||||
}, Symbol.toStringTag, { value: 'Module' }));
|
||||
|
||||
const page = () => _page;
|
||||
|
||||
export { page };
|
||||
3
dealplustech-astro/dist/renderers.mjs
vendored
3
dealplustech-astro/dist/renderers.mjs
vendored
@@ -1,3 +0,0 @@
|
||||
const renderers = [];
|
||||
|
||||
export { renderers };
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"easypanel": {
|
||||
"url": "",
|
||||
"project": "customerwebsite",
|
||||
"app": {
|
||||
"name": "dealplustech-astro",
|
||||
"port": 4321,
|
||||
"image": "dealplustech-astro:latest"
|
||||
},
|
||||
"env": {
|
||||
"NODE_ENV": "production",
|
||||
"PORT": "4321",
|
||||
"HOST": "0.0.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
# Nixpacks Configuration for Astro
|
||||
# https://nixpacks.com/docs/configuration/file
|
||||
|
||||
[phases.setup]
|
||||
nixPkgs = ["nodejs-20_x"]
|
||||
|
||||
[phases.install]
|
||||
cmds = ["npm ci"]
|
||||
|
||||
[phases.build]
|
||||
cmds = ["npm run build"]
|
||||
|
||||
[start]
|
||||
cmd = "npm run preview -- --host 0.0.0.0 --port $PORT"
|
||||
5
dealplustech-astro/node_modules/is-docker/cli.js
generated
vendored
5
dealplustech-astro/node_modules/is-docker/cli.js
generated
vendored
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import process from 'node:process';
|
||||
import isDocker from './index.js';
|
||||
|
||||
process.exitCode = isDocker() ? 0 : 2;
|
||||
13
dealplustech-astro/node_modules/is-docker/index.d.ts
generated
vendored
13
dealplustech-astro/node_modules/is-docker/index.d.ts
generated
vendored
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
Check if the process is running inside a Docker container.
|
||||
|
||||
@example
|
||||
```
|
||||
import isDocker from 'is-docker';
|
||||
|
||||
if (isDocker()) {
|
||||
console.log('Running inside a Docker container');
|
||||
}
|
||||
```
|
||||
*/
|
||||
export default function isDocker(): boolean;
|
||||
29
dealplustech-astro/node_modules/is-docker/index.js
generated
vendored
29
dealplustech-astro/node_modules/is-docker/index.js
generated
vendored
@@ -1,29 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
|
||||
let isDockerCached;
|
||||
|
||||
function hasDockerEnv() {
|
||||
try {
|
||||
fs.statSync('/.dockerenv');
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function hasDockerCGroup() {
|
||||
try {
|
||||
return fs.readFileSync('/proc/self/cgroup', 'utf8').includes('docker');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default function isDocker() {
|
||||
// TODO: Use `??=` when targeting Node.js 16.
|
||||
if (isDockerCached === undefined) {
|
||||
isDockerCached = hasDockerEnv() || hasDockerCGroup();
|
||||
}
|
||||
|
||||
return isDockerCached;
|
||||
}
|
||||
9
dealplustech-astro/node_modules/is-docker/license
generated
vendored
9
dealplustech-astro/node_modules/is-docker/license
generated
vendored
@@ -1,9 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
44
dealplustech-astro/node_modules/is-docker/package.json
generated
vendored
44
dealplustech-astro/node_modules/is-docker/package.json
generated
vendored
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"name": "is-docker",
|
||||
"version": "3.0.0",
|
||||
"description": "Check if the process is running inside a Docker container",
|
||||
"license": "MIT",
|
||||
"repository": "sindresorhus/is-docker",
|
||||
"funding": "https://github.com/sponsors/sindresorhus",
|
||||
"author": {
|
||||
"name": "Sindre Sorhus",
|
||||
"email": "sindresorhus@gmail.com",
|
||||
"url": "https://sindresorhus.com"
|
||||
},
|
||||
"type": "module",
|
||||
"exports": "./index.js",
|
||||
"bin": "./cli.js",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "xo && ava && tsd"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"index.d.ts",
|
||||
"cli.js"
|
||||
],
|
||||
"keywords": [
|
||||
"detect",
|
||||
"docker",
|
||||
"dockerized",
|
||||
"container",
|
||||
"inside",
|
||||
"is",
|
||||
"env",
|
||||
"environment",
|
||||
"process"
|
||||
],
|
||||
"devDependencies": {
|
||||
"ava": "^3.15.0",
|
||||
"sinon": "^11.1.2",
|
||||
"tsd": "^0.17.0",
|
||||
"xo": "^0.44.0"
|
||||
}
|
||||
}
|
||||
27
dealplustech-astro/node_modules/is-docker/readme.md
generated
vendored
27
dealplustech-astro/node_modules/is-docker/readme.md
generated
vendored
@@ -1,27 +0,0 @@
|
||||
# is-docker
|
||||
|
||||
> Check if the process is running inside a Docker container
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
$ npm install is-docker
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import isDocker from 'is-docker';
|
||||
|
||||
if (isDocker()) {
|
||||
console.log('Running inside a Docker container');
|
||||
}
|
||||
```
|
||||
|
||||
## CLI
|
||||
|
||||
```
|
||||
$ is-docker
|
||||
```
|
||||
|
||||
Exits with code 0 if inside a Docker container and 2 if not.
|
||||
23
dealplustech-astro/node_modules/is-inside-container/index.js
generated
vendored
23
dealplustech-astro/node_modules/is-inside-container/index.js
generated
vendored
@@ -1,23 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
import isDocker from 'is-docker';
|
||||
|
||||
let cachedResult;
|
||||
|
||||
// Podman detection
|
||||
const hasContainerEnv = () => {
|
||||
try {
|
||||
fs.statSync('/run/.containerenv');
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default function isInsideContainer() {
|
||||
// TODO: Use `??=` when targeting Node.js 16.
|
||||
if (cachedResult === undefined) {
|
||||
cachedResult = hasContainerEnv() || isDocker();
|
||||
}
|
||||
|
||||
return cachedResult;
|
||||
}
|
||||
9
dealplustech-astro/node_modules/is-inside-container/license
generated
vendored
9
dealplustech-astro/node_modules/is-inside-container/license
generated
vendored
@@ -1,9 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
35
dealplustech-astro/node_modules/is-plain-obj/index.d.ts
generated
vendored
35
dealplustech-astro/node_modules/is-plain-obj/index.d.ts
generated
vendored
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
Check if a value is a plain object.
|
||||
|
||||
An object is plain if it's created by either `{}`, `new Object()`, or `Object.create(null)`.
|
||||
|
||||
@example
|
||||
```
|
||||
import isPlainObject from 'is-plain-obj';
|
||||
import {runInNewContext} from 'node:vm';
|
||||
|
||||
isPlainObject({foo: 'bar'});
|
||||
//=> true
|
||||
|
||||
isPlainObject(new Object());
|
||||
//=> true
|
||||
|
||||
isPlainObject(Object.create(null));
|
||||
//=> true
|
||||
|
||||
// This works across realms
|
||||
isPlainObject(runInNewContext('({})'));
|
||||
//=> true
|
||||
|
||||
isPlainObject([1, 2, 3]);
|
||||
//=> false
|
||||
|
||||
class Unicorn {}
|
||||
isPlainObject(new Unicorn());
|
||||
//=> false
|
||||
|
||||
isPlainObject(Math);
|
||||
//=> false
|
||||
```
|
||||
*/
|
||||
export default function isPlainObject<Value>(value: unknown): value is Record<PropertyKey, Value>;
|
||||
8
dealplustech-astro/node_modules/is-plain-obj/index.js
generated
vendored
8
dealplustech-astro/node_modules/is-plain-obj/index.js
generated
vendored
@@ -1,8 +0,0 @@
|
||||
export default function isPlainObject(value) {
|
||||
if (typeof value !== 'object' || value === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const prototype = Object.getPrototypeOf(value);
|
||||
return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value);
|
||||
}
|
||||
9
dealplustech-astro/node_modules/is-plain-obj/license
generated
vendored
9
dealplustech-astro/node_modules/is-plain-obj/license
generated
vendored
@@ -1,9 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
41
dealplustech-astro/node_modules/is-plain-obj/package.json
generated
vendored
41
dealplustech-astro/node_modules/is-plain-obj/package.json
generated
vendored
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"name": "is-plain-obj",
|
||||
"version": "4.1.0",
|
||||
"description": "Check if a value is a plain object",
|
||||
"license": "MIT",
|
||||
"repository": "sindresorhus/is-plain-obj",
|
||||
"funding": "https://github.com/sponsors/sindresorhus",
|
||||
"author": {
|
||||
"name": "Sindre Sorhus",
|
||||
"email": "sindresorhus@gmail.com",
|
||||
"url": "https://sindresorhus.com"
|
||||
},
|
||||
"type": "module",
|
||||
"exports": "./index.js",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "xo && ava && tsd"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"index.d.ts"
|
||||
],
|
||||
"keywords": [
|
||||
"object",
|
||||
"is",
|
||||
"check",
|
||||
"test",
|
||||
"type",
|
||||
"plain",
|
||||
"vanilla",
|
||||
"pure",
|
||||
"simple"
|
||||
],
|
||||
"devDependencies": {
|
||||
"ava": "^3.15.0",
|
||||
"tsd": "^0.14.0",
|
||||
"xo": "^0.38.2"
|
||||
}
|
||||
}
|
||||
58
dealplustech-astro/node_modules/is-plain-obj/readme.md
generated
vendored
58
dealplustech-astro/node_modules/is-plain-obj/readme.md
generated
vendored
@@ -1,58 +0,0 @@
|
||||
# is-plain-obj
|
||||
|
||||
> Check if a value is a plain object
|
||||
|
||||
An object is plain if it's created by either `{}`, `new Object()`, or `Object.create(null)`.
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
$ npm install is-plain-obj
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import isPlainObject from 'is-plain-obj';
|
||||
import {runInNewContext} from 'node:vm';
|
||||
|
||||
isPlainObject({foo: 'bar'});
|
||||
//=> true
|
||||
|
||||
isPlainObject(new Object());
|
||||
//=> true
|
||||
|
||||
isPlainObject(Object.create(null));
|
||||
//=> true
|
||||
|
||||
// This works across realms
|
||||
isPlainObject(runInNewContext('({})'));
|
||||
//=> true
|
||||
|
||||
isPlainObject([1, 2, 3]);
|
||||
//=> false
|
||||
|
||||
class Unicorn {}
|
||||
isPlainObject(new Unicorn());
|
||||
//=> false
|
||||
|
||||
isPlainObject(Math);
|
||||
//=> false
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [is-obj](https://github.com/sindresorhus/is-obj) - Check if a value is an object
|
||||
- [is](https://github.com/sindresorhus/is) - Type check values
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<b>
|
||||
<a href="https://tidelift.com/subscription/pkg/npm-is-plain-obj?utm_source=npm-is-plain-obj&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
|
||||
</b>
|
||||
<br>
|
||||
<sub>
|
||||
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
|
||||
</sub>
|
||||
</div>
|
||||
21
dealplustech-astro/node_modules/tinyglobby/LICENSE
generated
vendored
21
dealplustech-astro/node_modules/tinyglobby/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Madeline Gurriarán
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
25
dealplustech-astro/node_modules/tinyglobby/README.md
generated
vendored
25
dealplustech-astro/node_modules/tinyglobby/README.md
generated
vendored
@@ -1,25 +0,0 @@
|
||||
# tinyglobby
|
||||
|
||||
[](https://npmjs.com/package/tinyglobby)
|
||||
[](https://npmjs.com/package/tinyglobby)
|
||||
|
||||
A fast and minimal alternative to globby and fast-glob, meant to behave the same way.
|
||||
|
||||
Both globby and fast-glob present some behavior no other globbing lib has,
|
||||
which makes it hard to manually replace with something smaller and better.
|
||||
|
||||
This library uses only two subdependencies, compared to `globby`'s [23](https://npmgraph.js.org/?q=globby@14.1.0)
|
||||
and `fast-glob`'s [17](https://npmgraph.js.org/?q=fast-glob@3.3.3).
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import { glob, globSync } from 'tinyglobby';
|
||||
|
||||
await glob(['files/*.ts', '!**/*.d.ts'], { cwd: 'src' });
|
||||
globSync('src/**/*.ts', { ignore: '**/*.d.ts' });
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Visit https://superchupu.dev/tinyglobby to read the full documentation.
|
||||
350
dealplustech-astro/node_modules/tinyglobby/dist/index.cjs
generated
vendored
350
dealplustech-astro/node_modules/tinyglobby/dist/index.cjs
generated
vendored
@@ -1,350 +0,0 @@
|
||||
//#region rolldown:runtime
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
||||
key = keys[i];
|
||||
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
||||
get: ((k) => from[k]).bind(null, key),
|
||||
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
||||
});
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
||||
value: mod,
|
||||
enumerable: true
|
||||
}) : target, mod));
|
||||
|
||||
//#endregion
|
||||
let fs = require("fs");
|
||||
fs = __toESM(fs);
|
||||
let path = require("path");
|
||||
path = __toESM(path);
|
||||
let url = require("url");
|
||||
url = __toESM(url);
|
||||
let fdir = require("fdir");
|
||||
fdir = __toESM(fdir);
|
||||
let picomatch = require("picomatch");
|
||||
picomatch = __toESM(picomatch);
|
||||
|
||||
//#region src/utils.ts
|
||||
const isReadonlyArray = Array.isArray;
|
||||
const isWin = process.platform === "win32";
|
||||
const ONLY_PARENT_DIRECTORIES = /^(\/?\.\.)+$/;
|
||||
function getPartialMatcher(patterns, options = {}) {
|
||||
const patternsCount = patterns.length;
|
||||
const patternsParts = Array(patternsCount);
|
||||
const matchers = Array(patternsCount);
|
||||
const globstarEnabled = !options.noglobstar;
|
||||
for (let i = 0; i < patternsCount; i++) {
|
||||
const parts = splitPattern(patterns[i]);
|
||||
patternsParts[i] = parts;
|
||||
const partsCount = parts.length;
|
||||
const partMatchers = Array(partsCount);
|
||||
for (let j = 0; j < partsCount; j++) partMatchers[j] = (0, picomatch.default)(parts[j], options);
|
||||
matchers[i] = partMatchers;
|
||||
}
|
||||
return (input) => {
|
||||
const inputParts = input.split("/");
|
||||
if (inputParts[0] === ".." && ONLY_PARENT_DIRECTORIES.test(input)) return true;
|
||||
for (let i = 0; i < patterns.length; i++) {
|
||||
const patternParts = patternsParts[i];
|
||||
const matcher = matchers[i];
|
||||
const inputPatternCount = inputParts.length;
|
||||
const minParts = Math.min(inputPatternCount, patternParts.length);
|
||||
let j = 0;
|
||||
while (j < minParts) {
|
||||
const part = patternParts[j];
|
||||
if (part.includes("/")) return true;
|
||||
const match = matcher[j](inputParts[j]);
|
||||
if (!match) break;
|
||||
if (globstarEnabled && part === "**") return true;
|
||||
j++;
|
||||
}
|
||||
if (j === inputPatternCount) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
/* node:coverage ignore next 2 */
|
||||
const WIN32_ROOT_DIR = /^[A-Z]:\/$/i;
|
||||
const isRoot = isWin ? (p) => WIN32_ROOT_DIR.test(p) : (p) => p === "/";
|
||||
function buildFormat(cwd, root, absolute) {
|
||||
if (cwd === root || root.startsWith(`${cwd}/`)) {
|
||||
if (absolute) {
|
||||
const start = isRoot(cwd) ? cwd.length : cwd.length + 1;
|
||||
return (p, isDir) => p.slice(start, isDir ? -1 : void 0) || ".";
|
||||
}
|
||||
const prefix = root.slice(cwd.length + 1);
|
||||
if (prefix) return (p, isDir) => {
|
||||
if (p === ".") return prefix;
|
||||
const result = `${prefix}/${p}`;
|
||||
return isDir ? result.slice(0, -1) : result;
|
||||
};
|
||||
return (p, isDir) => isDir && p !== "." ? p.slice(0, -1) : p;
|
||||
}
|
||||
if (absolute) return (p) => path.posix.relative(cwd, p) || ".";
|
||||
return (p) => path.posix.relative(cwd, `${root}/${p}`) || ".";
|
||||
}
|
||||
function buildRelative(cwd, root) {
|
||||
if (root.startsWith(`${cwd}/`)) {
|
||||
const prefix = root.slice(cwd.length + 1);
|
||||
return (p) => `${prefix}/${p}`;
|
||||
}
|
||||
return (p) => {
|
||||
const result = path.posix.relative(cwd, `${root}/${p}`);
|
||||
if (p.endsWith("/") && result !== "") return `${result}/`;
|
||||
return result || ".";
|
||||
};
|
||||
}
|
||||
const splitPatternOptions = { parts: true };
|
||||
function splitPattern(path$2) {
|
||||
var _result$parts;
|
||||
const result = picomatch.default.scan(path$2, splitPatternOptions);
|
||||
return ((_result$parts = result.parts) === null || _result$parts === void 0 ? void 0 : _result$parts.length) ? result.parts : [path$2];
|
||||
}
|
||||
const ESCAPED_WIN32_BACKSLASHES = /\\(?![()[\]{}!+@])/g;
|
||||
function convertPosixPathToPattern(path$2) {
|
||||
return escapePosixPath(path$2);
|
||||
}
|
||||
function convertWin32PathToPattern(path$2) {
|
||||
return escapeWin32Path(path$2).replace(ESCAPED_WIN32_BACKSLASHES, "/");
|
||||
}
|
||||
/**
|
||||
* Converts a path to a pattern depending on the platform.
|
||||
* Identical to {@link escapePath} on POSIX systems.
|
||||
* @see {@link https://superchupu.dev/tinyglobby/documentation#convertPathToPattern}
|
||||
*/
|
||||
/* node:coverage ignore next 3 */
|
||||
const convertPathToPattern = isWin ? convertWin32PathToPattern : convertPosixPathToPattern;
|
||||
const POSIX_UNESCAPED_GLOB_SYMBOLS = /(?<!\\)([()[\]{}*?|]|^!|[!+@](?=\()|\\(?![()[\]{}!*+?@|]))/g;
|
||||
const WIN32_UNESCAPED_GLOB_SYMBOLS = /(?<!\\)([()[\]{}]|^!|[!+@](?=\())/g;
|
||||
const escapePosixPath = (path$2) => path$2.replace(POSIX_UNESCAPED_GLOB_SYMBOLS, "\\$&");
|
||||
const escapeWin32Path = (path$2) => path$2.replace(WIN32_UNESCAPED_GLOB_SYMBOLS, "\\$&");
|
||||
/**
|
||||
* Escapes a path's special characters depending on the platform.
|
||||
* @see {@link https://superchupu.dev/tinyglobby/documentation#escapePath}
|
||||
*/
|
||||
/* node:coverage ignore next */
|
||||
const escapePath = isWin ? escapeWin32Path : escapePosixPath;
|
||||
/**
|
||||
* Checks if a pattern has dynamic parts.
|
||||
*
|
||||
* Has a few minor differences with [`fast-glob`](https://github.com/mrmlnc/fast-glob) for better accuracy:
|
||||
*
|
||||
* - Doesn't necessarily return `false` on patterns that include `\`.
|
||||
* - Returns `true` if the pattern includes parentheses, regardless of them representing one single pattern or not.
|
||||
* - Returns `true` for unfinished glob extensions i.e. `(h`, `+(h`.
|
||||
* - Returns `true` for unfinished brace expansions as long as they include `,` or `..`.
|
||||
*
|
||||
* @see {@link https://superchupu.dev/tinyglobby/documentation#isDynamicPattern}
|
||||
*/
|
||||
function isDynamicPattern(pattern, options) {
|
||||
if ((options === null || options === void 0 ? void 0 : options.caseSensitiveMatch) === false) return true;
|
||||
const scan = picomatch.default.scan(pattern);
|
||||
return scan.isGlob || scan.negated;
|
||||
}
|
||||
function log(...tasks) {
|
||||
console.log(`[tinyglobby ${(/* @__PURE__ */ new Date()).toLocaleTimeString("es")}]`, ...tasks);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
//#region src/index.ts
|
||||
const PARENT_DIRECTORY = /^(\/?\.\.)+/;
|
||||
const ESCAPING_BACKSLASHES = /\\(?=[()[\]{}!*+?@|])/g;
|
||||
const BACKSLASHES = /\\/g;
|
||||
function normalizePattern(pattern, expandDirectories, cwd, props, isIgnore) {
|
||||
let result = pattern;
|
||||
if (pattern.endsWith("/")) result = pattern.slice(0, -1);
|
||||
if (!result.endsWith("*") && expandDirectories) result += "/**";
|
||||
const escapedCwd = escapePath(cwd);
|
||||
if (path.default.isAbsolute(result.replace(ESCAPING_BACKSLASHES, ""))) result = path.posix.relative(escapedCwd, result);
|
||||
else result = path.posix.normalize(result);
|
||||
const parentDirectoryMatch = PARENT_DIRECTORY.exec(result);
|
||||
const parts = splitPattern(result);
|
||||
if (parentDirectoryMatch === null || parentDirectoryMatch === void 0 ? void 0 : parentDirectoryMatch[0]) {
|
||||
const n = (parentDirectoryMatch[0].length + 1) / 3;
|
||||
let i = 0;
|
||||
const cwdParts = escapedCwd.split("/");
|
||||
while (i < n && parts[i + n] === cwdParts[cwdParts.length + i - n]) {
|
||||
result = result.slice(0, (n - i - 1) * 3) + result.slice((n - i) * 3 + parts[i + n].length + 1) || ".";
|
||||
i++;
|
||||
}
|
||||
const potentialRoot = path.posix.join(cwd, parentDirectoryMatch[0].slice(i * 3));
|
||||
if (!potentialRoot.startsWith(".") && props.root.length > potentialRoot.length) {
|
||||
props.root = potentialRoot;
|
||||
props.depthOffset = -n + i;
|
||||
}
|
||||
}
|
||||
if (!isIgnore && props.depthOffset >= 0) {
|
||||
var _props$commonPath;
|
||||
(_props$commonPath = props.commonPath) !== null && _props$commonPath !== void 0 || (props.commonPath = parts);
|
||||
const newCommonPath = [];
|
||||
const length = Math.min(props.commonPath.length, parts.length);
|
||||
for (let i = 0; i < length; i++) {
|
||||
const part = parts[i];
|
||||
if (part === "**" && !parts[i + 1]) {
|
||||
newCommonPath.pop();
|
||||
break;
|
||||
}
|
||||
if (part !== props.commonPath[i] || isDynamicPattern(part) || i === parts.length - 1) break;
|
||||
newCommonPath.push(part);
|
||||
}
|
||||
props.depthOffset = newCommonPath.length;
|
||||
props.commonPath = newCommonPath;
|
||||
props.root = newCommonPath.length > 0 ? path.posix.join(cwd, ...newCommonPath) : cwd;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function processPatterns({ patterns = ["**/*"], ignore = [], expandDirectories = true }, cwd, props) {
|
||||
if (typeof patterns === "string") patterns = [patterns];
|
||||
if (typeof ignore === "string") ignore = [ignore];
|
||||
const matchPatterns = [];
|
||||
const ignorePatterns = [];
|
||||
for (const pattern of ignore) {
|
||||
if (!pattern) continue;
|
||||
if (pattern[0] !== "!" || pattern[1] === "(") ignorePatterns.push(normalizePattern(pattern, expandDirectories, cwd, props, true));
|
||||
}
|
||||
for (const pattern of patterns) {
|
||||
if (!pattern) continue;
|
||||
if (pattern[0] !== "!" || pattern[1] === "(") matchPatterns.push(normalizePattern(pattern, expandDirectories, cwd, props, false));
|
||||
else if (pattern[1] !== "!" || pattern[2] === "(") ignorePatterns.push(normalizePattern(pattern.slice(1), expandDirectories, cwd, props, true));
|
||||
}
|
||||
return {
|
||||
match: matchPatterns,
|
||||
ignore: ignorePatterns
|
||||
};
|
||||
}
|
||||
function formatPaths(paths, relative) {
|
||||
for (let i = paths.length - 1; i >= 0; i--) {
|
||||
const path$2 = paths[i];
|
||||
paths[i] = relative(path$2);
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
function normalizeCwd(cwd) {
|
||||
if (!cwd) return process.cwd().replace(BACKSLASHES, "/");
|
||||
if (cwd instanceof URL) return (0, url.fileURLToPath)(cwd).replace(BACKSLASHES, "/");
|
||||
return path.default.resolve(cwd).replace(BACKSLASHES, "/");
|
||||
}
|
||||
function getCrawler(patterns, inputOptions = {}) {
|
||||
const options = process.env.TINYGLOBBY_DEBUG ? {
|
||||
...inputOptions,
|
||||
debug: true
|
||||
} : inputOptions;
|
||||
const cwd = normalizeCwd(options.cwd);
|
||||
if (options.debug) log("globbing with:", {
|
||||
patterns,
|
||||
options,
|
||||
cwd
|
||||
});
|
||||
if (Array.isArray(patterns) && patterns.length === 0) return [{
|
||||
sync: () => [],
|
||||
withPromise: async () => []
|
||||
}, false];
|
||||
const props = {
|
||||
root: cwd,
|
||||
commonPath: null,
|
||||
depthOffset: 0
|
||||
};
|
||||
const processed = processPatterns({
|
||||
...options,
|
||||
patterns
|
||||
}, cwd, props);
|
||||
if (options.debug) log("internal processing patterns:", processed);
|
||||
const matchOptions = {
|
||||
dot: options.dot,
|
||||
nobrace: options.braceExpansion === false,
|
||||
nocase: options.caseSensitiveMatch === false,
|
||||
noextglob: options.extglob === false,
|
||||
noglobstar: options.globstar === false,
|
||||
posix: true
|
||||
};
|
||||
const matcher = (0, picomatch.default)(processed.match, {
|
||||
...matchOptions,
|
||||
ignore: processed.ignore
|
||||
});
|
||||
const ignore = (0, picomatch.default)(processed.ignore, matchOptions);
|
||||
const partialMatcher = getPartialMatcher(processed.match, matchOptions);
|
||||
const format = buildFormat(cwd, props.root, options.absolute);
|
||||
const formatExclude = options.absolute ? format : buildFormat(cwd, props.root, true);
|
||||
const fdirOptions = {
|
||||
filters: [options.debug ? (p, isDirectory) => {
|
||||
const path$2 = format(p, isDirectory);
|
||||
const matches = matcher(path$2);
|
||||
if (matches) log(`matched ${path$2}`);
|
||||
return matches;
|
||||
} : (p, isDirectory) => matcher(format(p, isDirectory))],
|
||||
exclude: options.debug ? (_, p) => {
|
||||
const relativePath = formatExclude(p, true);
|
||||
const skipped = relativePath !== "." && !partialMatcher(relativePath) || ignore(relativePath);
|
||||
if (skipped) log(`skipped ${p}`);
|
||||
else log(`crawling ${p}`);
|
||||
return skipped;
|
||||
} : (_, p) => {
|
||||
const relativePath = formatExclude(p, true);
|
||||
return relativePath !== "." && !partialMatcher(relativePath) || ignore(relativePath);
|
||||
},
|
||||
fs: options.fs ? {
|
||||
readdir: options.fs.readdir || fs.default.readdir,
|
||||
readdirSync: options.fs.readdirSync || fs.default.readdirSync,
|
||||
realpath: options.fs.realpath || fs.default.realpath,
|
||||
realpathSync: options.fs.realpathSync || fs.default.realpathSync,
|
||||
stat: options.fs.stat || fs.default.stat,
|
||||
statSync: options.fs.statSync || fs.default.statSync
|
||||
} : void 0,
|
||||
pathSeparator: "/",
|
||||
relativePaths: true,
|
||||
resolveSymlinks: true,
|
||||
signal: options.signal
|
||||
};
|
||||
if (options.deep !== void 0) fdirOptions.maxDepth = Math.round(options.deep - props.depthOffset);
|
||||
if (options.absolute) {
|
||||
fdirOptions.relativePaths = false;
|
||||
fdirOptions.resolvePaths = true;
|
||||
fdirOptions.includeBasePath = true;
|
||||
}
|
||||
if (options.followSymbolicLinks === false) {
|
||||
fdirOptions.resolveSymlinks = false;
|
||||
fdirOptions.excludeSymlinks = true;
|
||||
}
|
||||
if (options.onlyDirectories) {
|
||||
fdirOptions.excludeFiles = true;
|
||||
fdirOptions.includeDirs = true;
|
||||
} else if (options.onlyFiles === false) fdirOptions.includeDirs = true;
|
||||
props.root = props.root.replace(BACKSLASHES, "");
|
||||
const root = props.root;
|
||||
if (options.debug) log("internal properties:", props);
|
||||
const relative = cwd !== root && !options.absolute && buildRelative(cwd, props.root);
|
||||
return [new fdir.fdir(fdirOptions).crawl(root), relative];
|
||||
}
|
||||
async function glob(patternsOrOptions, options) {
|
||||
if (patternsOrOptions && (options === null || options === void 0 ? void 0 : options.patterns)) throw new Error("Cannot pass patterns as both an argument and an option");
|
||||
const isModern = isReadonlyArray(patternsOrOptions) || typeof patternsOrOptions === "string";
|
||||
const opts = isModern ? options : patternsOrOptions;
|
||||
const patterns = isModern ? patternsOrOptions : patternsOrOptions.patterns;
|
||||
const [crawler, relative] = getCrawler(patterns, opts);
|
||||
if (!relative) return crawler.withPromise();
|
||||
return formatPaths(await crawler.withPromise(), relative);
|
||||
}
|
||||
function globSync(patternsOrOptions, options) {
|
||||
if (patternsOrOptions && (options === null || options === void 0 ? void 0 : options.patterns)) throw new Error("Cannot pass patterns as both an argument and an option");
|
||||
const isModern = isReadonlyArray(patternsOrOptions) || typeof patternsOrOptions === "string";
|
||||
const opts = isModern ? options : patternsOrOptions;
|
||||
const patterns = isModern ? patternsOrOptions : patternsOrOptions.patterns;
|
||||
const [crawler, relative] = getCrawler(patterns, opts);
|
||||
if (!relative) return crawler.sync();
|
||||
return formatPaths(crawler.sync(), relative);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
exports.convertPathToPattern = convertPathToPattern;
|
||||
exports.escapePath = escapePath;
|
||||
exports.glob = glob;
|
||||
exports.globSync = globSync;
|
||||
exports.isDynamicPattern = isDynamicPattern;
|
||||
147
dealplustech-astro/node_modules/tinyglobby/dist/index.d.cts
generated
vendored
147
dealplustech-astro/node_modules/tinyglobby/dist/index.d.cts
generated
vendored
@@ -1,147 +0,0 @@
|
||||
import { FSLike } from "fdir";
|
||||
|
||||
//#region src/utils.d.ts
|
||||
|
||||
/**
|
||||
* Converts a path to a pattern depending on the platform.
|
||||
* Identical to {@link escapePath} on POSIX systems.
|
||||
* @see {@link https://superchupu.dev/tinyglobby/documentation#convertPathToPattern}
|
||||
*/
|
||||
declare const convertPathToPattern: (path: string) => string;
|
||||
/**
|
||||
* Escapes a path's special characters depending on the platform.
|
||||
* @see {@link https://superchupu.dev/tinyglobby/documentation#escapePath}
|
||||
*/
|
||||
declare const escapePath: (path: string) => string;
|
||||
/**
|
||||
* Checks if a pattern has dynamic parts.
|
||||
*
|
||||
* Has a few minor differences with [`fast-glob`](https://github.com/mrmlnc/fast-glob) for better accuracy:
|
||||
*
|
||||
* - Doesn't necessarily return `false` on patterns that include `\`.
|
||||
* - Returns `true` if the pattern includes parentheses, regardless of them representing one single pattern or not.
|
||||
* - Returns `true` for unfinished glob extensions i.e. `(h`, `+(h`.
|
||||
* - Returns `true` for unfinished brace expansions as long as they include `,` or `..`.
|
||||
*
|
||||
* @see {@link https://superchupu.dev/tinyglobby/documentation#isDynamicPattern}
|
||||
*/
|
||||
declare function isDynamicPattern(pattern: string, options?: {
|
||||
caseSensitiveMatch: boolean;
|
||||
}): boolean;
|
||||
//#endregion
|
||||
//#region src/index.d.ts
|
||||
interface GlobOptions {
|
||||
/**
|
||||
* Whether to return absolute paths. Disable to have relative paths.
|
||||
* @default false
|
||||
*/
|
||||
absolute?: boolean;
|
||||
/**
|
||||
* Enables support for brace expansion syntax, like `{a,b}` or `{1..9}`.
|
||||
* @default true
|
||||
*/
|
||||
braceExpansion?: boolean;
|
||||
/**
|
||||
* Whether to match in case-sensitive mode.
|
||||
* @default true
|
||||
*/
|
||||
caseSensitiveMatch?: boolean;
|
||||
/**
|
||||
* The working directory in which to search. Results will be returned relative to this directory, unless
|
||||
* {@link absolute} is set.
|
||||
*
|
||||
* It is important to avoid globbing outside this directory when possible, even with absolute paths enabled,
|
||||
* as doing so can harm performance due to having to recalculate relative paths.
|
||||
* @default process.cwd()
|
||||
*/
|
||||
cwd?: string | URL;
|
||||
/**
|
||||
* Logs useful debug information. Meant for development purposes. Logs can change at any time.
|
||||
* @default false
|
||||
*/
|
||||
debug?: boolean;
|
||||
/**
|
||||
* Maximum directory depth to crawl.
|
||||
* @default Infinity
|
||||
*/
|
||||
deep?: number;
|
||||
/**
|
||||
* Whether to return entries that start with a dot, like `.gitignore` or `.prettierrc`.
|
||||
* @default false
|
||||
*/
|
||||
dot?: boolean;
|
||||
/**
|
||||
* Whether to automatically expand directory patterns.
|
||||
*
|
||||
* Important to disable if migrating from [`fast-glob`](https://github.com/mrmlnc/fast-glob).
|
||||
* @default true
|
||||
*/
|
||||
expandDirectories?: boolean;
|
||||
/**
|
||||
* Enables support for extglobs, like `+(pattern)`.
|
||||
* @default true
|
||||
*/
|
||||
extglob?: boolean;
|
||||
/**
|
||||
* Whether to traverse and include symbolic links. Can slightly affect performance.
|
||||
* @default true
|
||||
*/
|
||||
followSymbolicLinks?: boolean;
|
||||
/**
|
||||
* An object that overrides `node:fs` functions.
|
||||
* @default import('node:fs')
|
||||
*/
|
||||
fs?: FileSystemAdapter;
|
||||
/**
|
||||
* Enables support for matching nested directories with globstars (`**`).
|
||||
* If `false`, `**` behaves exactly like `*`.
|
||||
* @default true
|
||||
*/
|
||||
globstar?: boolean;
|
||||
/**
|
||||
* Glob patterns to exclude from the results.
|
||||
* @default []
|
||||
*/
|
||||
ignore?: string | readonly string[];
|
||||
/**
|
||||
* Enable to only return directories.
|
||||
* If `true`, disables {@link onlyFiles}.
|
||||
* @default false
|
||||
*/
|
||||
onlyDirectories?: boolean;
|
||||
/**
|
||||
* Enable to only return files.
|
||||
* @default true
|
||||
*/
|
||||
onlyFiles?: boolean;
|
||||
/**
|
||||
* @deprecated Provide patterns as the first argument instead.
|
||||
*/
|
||||
patterns?: string | readonly string[];
|
||||
/**
|
||||
* An `AbortSignal` to abort crawling the file system.
|
||||
* @default undefined
|
||||
*/
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
type FileSystemAdapter = Partial<FSLike>;
|
||||
/**
|
||||
* Asynchronously match files following a glob pattern.
|
||||
* @see {@link https://superchupu.dev/tinyglobby/documentation#glob}
|
||||
*/
|
||||
declare function glob(patterns: string | readonly string[], options?: Omit<GlobOptions, "patterns">): Promise<string[]>;
|
||||
/**
|
||||
* @deprecated Provide patterns as the first argument instead.
|
||||
*/
|
||||
declare function glob(options: GlobOptions): Promise<string[]>;
|
||||
/**
|
||||
* Synchronously match files following a glob pattern.
|
||||
* @see {@link https://superchupu.dev/tinyglobby/documentation#globSync}
|
||||
*/
|
||||
declare function globSync(patterns: string | readonly string[], options?: Omit<GlobOptions, "patterns">): string[];
|
||||
/**
|
||||
* @deprecated Provide patterns as the first argument instead.
|
||||
*/
|
||||
declare function globSync(options: GlobOptions): string[];
|
||||
//#endregion
|
||||
export { FileSystemAdapter, GlobOptions, convertPathToPattern, escapePath, glob, globSync, isDynamicPattern };
|
||||
147
dealplustech-astro/node_modules/tinyglobby/dist/index.d.mts
generated
vendored
147
dealplustech-astro/node_modules/tinyglobby/dist/index.d.mts
generated
vendored
@@ -1,147 +0,0 @@
|
||||
import { FSLike } from "fdir";
|
||||
|
||||
//#region src/utils.d.ts
|
||||
|
||||
/**
|
||||
* Converts a path to a pattern depending on the platform.
|
||||
* Identical to {@link escapePath} on POSIX systems.
|
||||
* @see {@link https://superchupu.dev/tinyglobby/documentation#convertPathToPattern}
|
||||
*/
|
||||
declare const convertPathToPattern: (path: string) => string;
|
||||
/**
|
||||
* Escapes a path's special characters depending on the platform.
|
||||
* @see {@link https://superchupu.dev/tinyglobby/documentation#escapePath}
|
||||
*/
|
||||
declare const escapePath: (path: string) => string;
|
||||
/**
|
||||
* Checks if a pattern has dynamic parts.
|
||||
*
|
||||
* Has a few minor differences with [`fast-glob`](https://github.com/mrmlnc/fast-glob) for better accuracy:
|
||||
*
|
||||
* - Doesn't necessarily return `false` on patterns that include `\`.
|
||||
* - Returns `true` if the pattern includes parentheses, regardless of them representing one single pattern or not.
|
||||
* - Returns `true` for unfinished glob extensions i.e. `(h`, `+(h`.
|
||||
* - Returns `true` for unfinished brace expansions as long as they include `,` or `..`.
|
||||
*
|
||||
* @see {@link https://superchupu.dev/tinyglobby/documentation#isDynamicPattern}
|
||||
*/
|
||||
declare function isDynamicPattern(pattern: string, options?: {
|
||||
caseSensitiveMatch: boolean;
|
||||
}): boolean;
|
||||
//#endregion
|
||||
//#region src/index.d.ts
|
||||
interface GlobOptions {
|
||||
/**
|
||||
* Whether to return absolute paths. Disable to have relative paths.
|
||||
* @default false
|
||||
*/
|
||||
absolute?: boolean;
|
||||
/**
|
||||
* Enables support for brace expansion syntax, like `{a,b}` or `{1..9}`.
|
||||
* @default true
|
||||
*/
|
||||
braceExpansion?: boolean;
|
||||
/**
|
||||
* Whether to match in case-sensitive mode.
|
||||
* @default true
|
||||
*/
|
||||
caseSensitiveMatch?: boolean;
|
||||
/**
|
||||
* The working directory in which to search. Results will be returned relative to this directory, unless
|
||||
* {@link absolute} is set.
|
||||
*
|
||||
* It is important to avoid globbing outside this directory when possible, even with absolute paths enabled,
|
||||
* as doing so can harm performance due to having to recalculate relative paths.
|
||||
* @default process.cwd()
|
||||
*/
|
||||
cwd?: string | URL;
|
||||
/**
|
||||
* Logs useful debug information. Meant for development purposes. Logs can change at any time.
|
||||
* @default false
|
||||
*/
|
||||
debug?: boolean;
|
||||
/**
|
||||
* Maximum directory depth to crawl.
|
||||
* @default Infinity
|
||||
*/
|
||||
deep?: number;
|
||||
/**
|
||||
* Whether to return entries that start with a dot, like `.gitignore` or `.prettierrc`.
|
||||
* @default false
|
||||
*/
|
||||
dot?: boolean;
|
||||
/**
|
||||
* Whether to automatically expand directory patterns.
|
||||
*
|
||||
* Important to disable if migrating from [`fast-glob`](https://github.com/mrmlnc/fast-glob).
|
||||
* @default true
|
||||
*/
|
||||
expandDirectories?: boolean;
|
||||
/**
|
||||
* Enables support for extglobs, like `+(pattern)`.
|
||||
* @default true
|
||||
*/
|
||||
extglob?: boolean;
|
||||
/**
|
||||
* Whether to traverse and include symbolic links. Can slightly affect performance.
|
||||
* @default true
|
||||
*/
|
||||
followSymbolicLinks?: boolean;
|
||||
/**
|
||||
* An object that overrides `node:fs` functions.
|
||||
* @default import('node:fs')
|
||||
*/
|
||||
fs?: FileSystemAdapter;
|
||||
/**
|
||||
* Enables support for matching nested directories with globstars (`**`).
|
||||
* If `false`, `**` behaves exactly like `*`.
|
||||
* @default true
|
||||
*/
|
||||
globstar?: boolean;
|
||||
/**
|
||||
* Glob patterns to exclude from the results.
|
||||
* @default []
|
||||
*/
|
||||
ignore?: string | readonly string[];
|
||||
/**
|
||||
* Enable to only return directories.
|
||||
* If `true`, disables {@link onlyFiles}.
|
||||
* @default false
|
||||
*/
|
||||
onlyDirectories?: boolean;
|
||||
/**
|
||||
* Enable to only return files.
|
||||
* @default true
|
||||
*/
|
||||
onlyFiles?: boolean;
|
||||
/**
|
||||
* @deprecated Provide patterns as the first argument instead.
|
||||
*/
|
||||
patterns?: string | readonly string[];
|
||||
/**
|
||||
* An `AbortSignal` to abort crawling the file system.
|
||||
* @default undefined
|
||||
*/
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
type FileSystemAdapter = Partial<FSLike>;
|
||||
/**
|
||||
* Asynchronously match files following a glob pattern.
|
||||
* @see {@link https://superchupu.dev/tinyglobby/documentation#glob}
|
||||
*/
|
||||
declare function glob(patterns: string | readonly string[], options?: Omit<GlobOptions, "patterns">): Promise<string[]>;
|
||||
/**
|
||||
* @deprecated Provide patterns as the first argument instead.
|
||||
*/
|
||||
declare function glob(options: GlobOptions): Promise<string[]>;
|
||||
/**
|
||||
* Synchronously match files following a glob pattern.
|
||||
* @see {@link https://superchupu.dev/tinyglobby/documentation#globSync}
|
||||
*/
|
||||
declare function globSync(patterns: string | readonly string[], options?: Omit<GlobOptions, "patterns">): string[];
|
||||
/**
|
||||
* @deprecated Provide patterns as the first argument instead.
|
||||
*/
|
||||
declare function globSync(options: GlobOptions): string[];
|
||||
//#endregion
|
||||
export { FileSystemAdapter, GlobOptions, convertPathToPattern, escapePath, glob, globSync, isDynamicPattern };
|
||||
318
dealplustech-astro/node_modules/tinyglobby/dist/index.mjs
generated
vendored
318
dealplustech-astro/node_modules/tinyglobby/dist/index.mjs
generated
vendored
@@ -1,318 +0,0 @@
|
||||
import nativeFs from "fs";
|
||||
import path, { posix } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { fdir } from "fdir";
|
||||
import picomatch from "picomatch";
|
||||
|
||||
//#region src/utils.ts
|
||||
const isReadonlyArray = Array.isArray;
|
||||
const isWin = process.platform === "win32";
|
||||
const ONLY_PARENT_DIRECTORIES = /^(\/?\.\.)+$/;
|
||||
function getPartialMatcher(patterns, options = {}) {
|
||||
const patternsCount = patterns.length;
|
||||
const patternsParts = Array(patternsCount);
|
||||
const matchers = Array(patternsCount);
|
||||
const globstarEnabled = !options.noglobstar;
|
||||
for (let i = 0; i < patternsCount; i++) {
|
||||
const parts = splitPattern(patterns[i]);
|
||||
patternsParts[i] = parts;
|
||||
const partsCount = parts.length;
|
||||
const partMatchers = Array(partsCount);
|
||||
for (let j = 0; j < partsCount; j++) partMatchers[j] = picomatch(parts[j], options);
|
||||
matchers[i] = partMatchers;
|
||||
}
|
||||
return (input) => {
|
||||
const inputParts = input.split("/");
|
||||
if (inputParts[0] === ".." && ONLY_PARENT_DIRECTORIES.test(input)) return true;
|
||||
for (let i = 0; i < patterns.length; i++) {
|
||||
const patternParts = patternsParts[i];
|
||||
const matcher = matchers[i];
|
||||
const inputPatternCount = inputParts.length;
|
||||
const minParts = Math.min(inputPatternCount, patternParts.length);
|
||||
let j = 0;
|
||||
while (j < minParts) {
|
||||
const part = patternParts[j];
|
||||
if (part.includes("/")) return true;
|
||||
const match = matcher[j](inputParts[j]);
|
||||
if (!match) break;
|
||||
if (globstarEnabled && part === "**") return true;
|
||||
j++;
|
||||
}
|
||||
if (j === inputPatternCount) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
/* node:coverage ignore next 2 */
|
||||
const WIN32_ROOT_DIR = /^[A-Z]:\/$/i;
|
||||
const isRoot = isWin ? (p) => WIN32_ROOT_DIR.test(p) : (p) => p === "/";
|
||||
function buildFormat(cwd, root, absolute) {
|
||||
if (cwd === root || root.startsWith(`${cwd}/`)) {
|
||||
if (absolute) {
|
||||
const start = isRoot(cwd) ? cwd.length : cwd.length + 1;
|
||||
return (p, isDir) => p.slice(start, isDir ? -1 : void 0) || ".";
|
||||
}
|
||||
const prefix = root.slice(cwd.length + 1);
|
||||
if (prefix) return (p, isDir) => {
|
||||
if (p === ".") return prefix;
|
||||
const result = `${prefix}/${p}`;
|
||||
return isDir ? result.slice(0, -1) : result;
|
||||
};
|
||||
return (p, isDir) => isDir && p !== "." ? p.slice(0, -1) : p;
|
||||
}
|
||||
if (absolute) return (p) => posix.relative(cwd, p) || ".";
|
||||
return (p) => posix.relative(cwd, `${root}/${p}`) || ".";
|
||||
}
|
||||
function buildRelative(cwd, root) {
|
||||
if (root.startsWith(`${cwd}/`)) {
|
||||
const prefix = root.slice(cwd.length + 1);
|
||||
return (p) => `${prefix}/${p}`;
|
||||
}
|
||||
return (p) => {
|
||||
const result = posix.relative(cwd, `${root}/${p}`);
|
||||
if (p.endsWith("/") && result !== "") return `${result}/`;
|
||||
return result || ".";
|
||||
};
|
||||
}
|
||||
const splitPatternOptions = { parts: true };
|
||||
function splitPattern(path$1) {
|
||||
var _result$parts;
|
||||
const result = picomatch.scan(path$1, splitPatternOptions);
|
||||
return ((_result$parts = result.parts) === null || _result$parts === void 0 ? void 0 : _result$parts.length) ? result.parts : [path$1];
|
||||
}
|
||||
const ESCAPED_WIN32_BACKSLASHES = /\\(?![()[\]{}!+@])/g;
|
||||
function convertPosixPathToPattern(path$1) {
|
||||
return escapePosixPath(path$1);
|
||||
}
|
||||
function convertWin32PathToPattern(path$1) {
|
||||
return escapeWin32Path(path$1).replace(ESCAPED_WIN32_BACKSLASHES, "/");
|
||||
}
|
||||
/**
|
||||
* Converts a path to a pattern depending on the platform.
|
||||
* Identical to {@link escapePath} on POSIX systems.
|
||||
* @see {@link https://superchupu.dev/tinyglobby/documentation#convertPathToPattern}
|
||||
*/
|
||||
/* node:coverage ignore next 3 */
|
||||
const convertPathToPattern = isWin ? convertWin32PathToPattern : convertPosixPathToPattern;
|
||||
const POSIX_UNESCAPED_GLOB_SYMBOLS = /(?<!\\)([()[\]{}*?|]|^!|[!+@](?=\()|\\(?![()[\]{}!*+?@|]))/g;
|
||||
const WIN32_UNESCAPED_GLOB_SYMBOLS = /(?<!\\)([()[\]{}]|^!|[!+@](?=\())/g;
|
||||
const escapePosixPath = (path$1) => path$1.replace(POSIX_UNESCAPED_GLOB_SYMBOLS, "\\$&");
|
||||
const escapeWin32Path = (path$1) => path$1.replace(WIN32_UNESCAPED_GLOB_SYMBOLS, "\\$&");
|
||||
/**
|
||||
* Escapes a path's special characters depending on the platform.
|
||||
* @see {@link https://superchupu.dev/tinyglobby/documentation#escapePath}
|
||||
*/
|
||||
/* node:coverage ignore next */
|
||||
const escapePath = isWin ? escapeWin32Path : escapePosixPath;
|
||||
/**
|
||||
* Checks if a pattern has dynamic parts.
|
||||
*
|
||||
* Has a few minor differences with [`fast-glob`](https://github.com/mrmlnc/fast-glob) for better accuracy:
|
||||
*
|
||||
* - Doesn't necessarily return `false` on patterns that include `\`.
|
||||
* - Returns `true` if the pattern includes parentheses, regardless of them representing one single pattern or not.
|
||||
* - Returns `true` for unfinished glob extensions i.e. `(h`, `+(h`.
|
||||
* - Returns `true` for unfinished brace expansions as long as they include `,` or `..`.
|
||||
*
|
||||
* @see {@link https://superchupu.dev/tinyglobby/documentation#isDynamicPattern}
|
||||
*/
|
||||
function isDynamicPattern(pattern, options) {
|
||||
if ((options === null || options === void 0 ? void 0 : options.caseSensitiveMatch) === false) return true;
|
||||
const scan = picomatch.scan(pattern);
|
||||
return scan.isGlob || scan.negated;
|
||||
}
|
||||
function log(...tasks) {
|
||||
console.log(`[tinyglobby ${(/* @__PURE__ */ new Date()).toLocaleTimeString("es")}]`, ...tasks);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
//#region src/index.ts
|
||||
const PARENT_DIRECTORY = /^(\/?\.\.)+/;
|
||||
const ESCAPING_BACKSLASHES = /\\(?=[()[\]{}!*+?@|])/g;
|
||||
const BACKSLASHES = /\\/g;
|
||||
function normalizePattern(pattern, expandDirectories, cwd, props, isIgnore) {
|
||||
let result = pattern;
|
||||
if (pattern.endsWith("/")) result = pattern.slice(0, -1);
|
||||
if (!result.endsWith("*") && expandDirectories) result += "/**";
|
||||
const escapedCwd = escapePath(cwd);
|
||||
if (path.isAbsolute(result.replace(ESCAPING_BACKSLASHES, ""))) result = posix.relative(escapedCwd, result);
|
||||
else result = posix.normalize(result);
|
||||
const parentDirectoryMatch = PARENT_DIRECTORY.exec(result);
|
||||
const parts = splitPattern(result);
|
||||
if (parentDirectoryMatch === null || parentDirectoryMatch === void 0 ? void 0 : parentDirectoryMatch[0]) {
|
||||
const n = (parentDirectoryMatch[0].length + 1) / 3;
|
||||
let i = 0;
|
||||
const cwdParts = escapedCwd.split("/");
|
||||
while (i < n && parts[i + n] === cwdParts[cwdParts.length + i - n]) {
|
||||
result = result.slice(0, (n - i - 1) * 3) + result.slice((n - i) * 3 + parts[i + n].length + 1) || ".";
|
||||
i++;
|
||||
}
|
||||
const potentialRoot = posix.join(cwd, parentDirectoryMatch[0].slice(i * 3));
|
||||
if (!potentialRoot.startsWith(".") && props.root.length > potentialRoot.length) {
|
||||
props.root = potentialRoot;
|
||||
props.depthOffset = -n + i;
|
||||
}
|
||||
}
|
||||
if (!isIgnore && props.depthOffset >= 0) {
|
||||
var _props$commonPath;
|
||||
(_props$commonPath = props.commonPath) !== null && _props$commonPath !== void 0 || (props.commonPath = parts);
|
||||
const newCommonPath = [];
|
||||
const length = Math.min(props.commonPath.length, parts.length);
|
||||
for (let i = 0; i < length; i++) {
|
||||
const part = parts[i];
|
||||
if (part === "**" && !parts[i + 1]) {
|
||||
newCommonPath.pop();
|
||||
break;
|
||||
}
|
||||
if (part !== props.commonPath[i] || isDynamicPattern(part) || i === parts.length - 1) break;
|
||||
newCommonPath.push(part);
|
||||
}
|
||||
props.depthOffset = newCommonPath.length;
|
||||
props.commonPath = newCommonPath;
|
||||
props.root = newCommonPath.length > 0 ? posix.join(cwd, ...newCommonPath) : cwd;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function processPatterns({ patterns = ["**/*"], ignore = [], expandDirectories = true }, cwd, props) {
|
||||
if (typeof patterns === "string") patterns = [patterns];
|
||||
if (typeof ignore === "string") ignore = [ignore];
|
||||
const matchPatterns = [];
|
||||
const ignorePatterns = [];
|
||||
for (const pattern of ignore) {
|
||||
if (!pattern) continue;
|
||||
if (pattern[0] !== "!" || pattern[1] === "(") ignorePatterns.push(normalizePattern(pattern, expandDirectories, cwd, props, true));
|
||||
}
|
||||
for (const pattern of patterns) {
|
||||
if (!pattern) continue;
|
||||
if (pattern[0] !== "!" || pattern[1] === "(") matchPatterns.push(normalizePattern(pattern, expandDirectories, cwd, props, false));
|
||||
else if (pattern[1] !== "!" || pattern[2] === "(") ignorePatterns.push(normalizePattern(pattern.slice(1), expandDirectories, cwd, props, true));
|
||||
}
|
||||
return {
|
||||
match: matchPatterns,
|
||||
ignore: ignorePatterns
|
||||
};
|
||||
}
|
||||
function formatPaths(paths, relative) {
|
||||
for (let i = paths.length - 1; i >= 0; i--) {
|
||||
const path$1 = paths[i];
|
||||
paths[i] = relative(path$1);
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
function normalizeCwd(cwd) {
|
||||
if (!cwd) return process.cwd().replace(BACKSLASHES, "/");
|
||||
if (cwd instanceof URL) return fileURLToPath(cwd).replace(BACKSLASHES, "/");
|
||||
return path.resolve(cwd).replace(BACKSLASHES, "/");
|
||||
}
|
||||
function getCrawler(patterns, inputOptions = {}) {
|
||||
const options = process.env.TINYGLOBBY_DEBUG ? {
|
||||
...inputOptions,
|
||||
debug: true
|
||||
} : inputOptions;
|
||||
const cwd = normalizeCwd(options.cwd);
|
||||
if (options.debug) log("globbing with:", {
|
||||
patterns,
|
||||
options,
|
||||
cwd
|
||||
});
|
||||
if (Array.isArray(patterns) && patterns.length === 0) return [{
|
||||
sync: () => [],
|
||||
withPromise: async () => []
|
||||
}, false];
|
||||
const props = {
|
||||
root: cwd,
|
||||
commonPath: null,
|
||||
depthOffset: 0
|
||||
};
|
||||
const processed = processPatterns({
|
||||
...options,
|
||||
patterns
|
||||
}, cwd, props);
|
||||
if (options.debug) log("internal processing patterns:", processed);
|
||||
const matchOptions = {
|
||||
dot: options.dot,
|
||||
nobrace: options.braceExpansion === false,
|
||||
nocase: options.caseSensitiveMatch === false,
|
||||
noextglob: options.extglob === false,
|
||||
noglobstar: options.globstar === false,
|
||||
posix: true
|
||||
};
|
||||
const matcher = picomatch(processed.match, {
|
||||
...matchOptions,
|
||||
ignore: processed.ignore
|
||||
});
|
||||
const ignore = picomatch(processed.ignore, matchOptions);
|
||||
const partialMatcher = getPartialMatcher(processed.match, matchOptions);
|
||||
const format = buildFormat(cwd, props.root, options.absolute);
|
||||
const formatExclude = options.absolute ? format : buildFormat(cwd, props.root, true);
|
||||
const fdirOptions = {
|
||||
filters: [options.debug ? (p, isDirectory) => {
|
||||
const path$1 = format(p, isDirectory);
|
||||
const matches = matcher(path$1);
|
||||
if (matches) log(`matched ${path$1}`);
|
||||
return matches;
|
||||
} : (p, isDirectory) => matcher(format(p, isDirectory))],
|
||||
exclude: options.debug ? (_, p) => {
|
||||
const relativePath = formatExclude(p, true);
|
||||
const skipped = relativePath !== "." && !partialMatcher(relativePath) || ignore(relativePath);
|
||||
if (skipped) log(`skipped ${p}`);
|
||||
else log(`crawling ${p}`);
|
||||
return skipped;
|
||||
} : (_, p) => {
|
||||
const relativePath = formatExclude(p, true);
|
||||
return relativePath !== "." && !partialMatcher(relativePath) || ignore(relativePath);
|
||||
},
|
||||
fs: options.fs ? {
|
||||
readdir: options.fs.readdir || nativeFs.readdir,
|
||||
readdirSync: options.fs.readdirSync || nativeFs.readdirSync,
|
||||
realpath: options.fs.realpath || nativeFs.realpath,
|
||||
realpathSync: options.fs.realpathSync || nativeFs.realpathSync,
|
||||
stat: options.fs.stat || nativeFs.stat,
|
||||
statSync: options.fs.statSync || nativeFs.statSync
|
||||
} : void 0,
|
||||
pathSeparator: "/",
|
||||
relativePaths: true,
|
||||
resolveSymlinks: true,
|
||||
signal: options.signal
|
||||
};
|
||||
if (options.deep !== void 0) fdirOptions.maxDepth = Math.round(options.deep - props.depthOffset);
|
||||
if (options.absolute) {
|
||||
fdirOptions.relativePaths = false;
|
||||
fdirOptions.resolvePaths = true;
|
||||
fdirOptions.includeBasePath = true;
|
||||
}
|
||||
if (options.followSymbolicLinks === false) {
|
||||
fdirOptions.resolveSymlinks = false;
|
||||
fdirOptions.excludeSymlinks = true;
|
||||
}
|
||||
if (options.onlyDirectories) {
|
||||
fdirOptions.excludeFiles = true;
|
||||
fdirOptions.includeDirs = true;
|
||||
} else if (options.onlyFiles === false) fdirOptions.includeDirs = true;
|
||||
props.root = props.root.replace(BACKSLASHES, "");
|
||||
const root = props.root;
|
||||
if (options.debug) log("internal properties:", props);
|
||||
const relative = cwd !== root && !options.absolute && buildRelative(cwd, props.root);
|
||||
return [new fdir(fdirOptions).crawl(root), relative];
|
||||
}
|
||||
async function glob(patternsOrOptions, options) {
|
||||
if (patternsOrOptions && (options === null || options === void 0 ? void 0 : options.patterns)) throw new Error("Cannot pass patterns as both an argument and an option");
|
||||
const isModern = isReadonlyArray(patternsOrOptions) || typeof patternsOrOptions === "string";
|
||||
const opts = isModern ? options : patternsOrOptions;
|
||||
const patterns = isModern ? patternsOrOptions : patternsOrOptions.patterns;
|
||||
const [crawler, relative] = getCrawler(patterns, opts);
|
||||
if (!relative) return crawler.withPromise();
|
||||
return formatPaths(await crawler.withPromise(), relative);
|
||||
}
|
||||
function globSync(patternsOrOptions, options) {
|
||||
if (patternsOrOptions && (options === null || options === void 0 ? void 0 : options.patterns)) throw new Error("Cannot pass patterns as both an argument and an option");
|
||||
const isModern = isReadonlyArray(patternsOrOptions) || typeof patternsOrOptions === "string";
|
||||
const opts = isModern ? options : patternsOrOptions;
|
||||
const patterns = isModern ? patternsOrOptions : patternsOrOptions.patterns;
|
||||
const [crawler, relative] = getCrawler(patterns, opts);
|
||||
if (!relative) return crawler.sync();
|
||||
return formatPaths(crawler.sync(), relative);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
export { convertPathToPattern, escapePath, glob, globSync, isDynamicPattern };
|
||||
73
dealplustech-astro/node_modules/tinyglobby/package.json
generated
vendored
73
dealplustech-astro/node_modules/tinyglobby/package.json
generated
vendored
@@ -1,73 +0,0 @@
|
||||
{
|
||||
"name": "tinyglobby",
|
||||
"version": "0.2.15",
|
||||
"description": "A fast and minimal alternative to globby and fast-glob",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.cts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.cjs"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"author": "Superchupu",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"glob",
|
||||
"patterns",
|
||||
"fast",
|
||||
"implementation"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/SuperchupuDev/tinyglobby.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/SuperchupuDev/tinyglobby/issues"
|
||||
},
|
||||
"homepage": "https://superchupu.dev/tinyglobby",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
},
|
||||
"dependencies": {
|
||||
"fdir": "^6.5.0",
|
||||
"picomatch": "^4.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.2.3",
|
||||
"@types/node": "^24.3.1",
|
||||
"@types/picomatch": "^4.0.2",
|
||||
"fast-glob": "^3.3.3",
|
||||
"fs-fixture": "^2.8.1",
|
||||
"glob": "^11.0.3",
|
||||
"tinybench": "^5.0.1",
|
||||
"tsdown": "^0.14.2",
|
||||
"typescript": "^5.9.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"provenance": true
|
||||
},
|
||||
"scripts": {
|
||||
"bench": "node benchmark/bench.ts",
|
||||
"bench:setup": "node benchmark/setup.ts",
|
||||
"build": "tsdown",
|
||||
"check": "biome check",
|
||||
"check:fix": "biome check --write --unsafe",
|
||||
"format": "biome format --write",
|
||||
"lint": "biome lint",
|
||||
"test": "node --test \"test/**/*.ts\"",
|
||||
"test:coverage": "node --test --experimental-test-coverage \"test/**/*.ts\"",
|
||||
"test:only": "node --test --test-only \"test/**/*.ts\"",
|
||||
"typecheck": "tsc --noEmit"
|
||||
}
|
||||
}
|
||||
21
dealplustech-astro/node_modules/vitefu/LICENSE
generated
vendored
21
dealplustech-astro/node_modules/vitefu/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Bjorn and Dominik
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
11
dealplustech-astro/node_modules/vitefu/README.md
generated
vendored
11
dealplustech-astro/node_modules/vitefu/README.md
generated
vendored
@@ -1,11 +0,0 @@
|
||||
# vitefu
|
||||
|
||||
Utilities for building frameworks with Vite.
|
||||
|
||||
## Usage
|
||||
|
||||
See [src/index.d.ts](./src/index.d.ts).
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
51
dealplustech-astro/node_modules/vitefu/package.json
generated
vendored
51
dealplustech-astro/node_modules/vitefu/package.json
generated
vendored
@@ -1,51 +0,0 @@
|
||||
{
|
||||
"name": "vitefu",
|
||||
"description": "Utilities for building frameworks with Vite",
|
||||
"version": "1.1.2",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"types": "./src/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.js",
|
||||
"require": "./src/index.cjs"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"src"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/svitejs/vitefu.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/svitejs/vitefu/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"vite",
|
||||
"framework",
|
||||
"utilities"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "uvu tests \".*\\.test\\.js\""
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"vite": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.18.63",
|
||||
"@types/pnpapi": "^0.0.5",
|
||||
"uvu": "^0.5.6",
|
||||
"vite": "^3.2.11"
|
||||
},
|
||||
"workspaces": [
|
||||
"tests/deps/*",
|
||||
"tests/projects/*",
|
||||
"tests/projects/workspace/packages/*"
|
||||
]
|
||||
}
|
||||
18
dealplustech-astro/node_modules/vitefu/src/index.cjs
generated
vendored
18
dealplustech-astro/node_modules/vitefu/src/index.cjs
generated
vendored
@@ -1,18 +0,0 @@
|
||||
// CJS -> ESM proxy file
|
||||
// Reference: https://github.com/vitejs/vite/blob/9f268dad2e82c0f1276b1098c0a28f1cf245aa50/packages/vite/index.cjs
|
||||
|
||||
module.exports = require('./sync.cjs')
|
||||
|
||||
// redirect async functions to ESM
|
||||
const asyncFunctions = [
|
||||
'crawlFrameworkPkgs',
|
||||
'findDepPkgJsonPath',
|
||||
'findClosestPkgJsonPath',
|
||||
'pkgNeedsOptimization'
|
||||
]
|
||||
|
||||
for (const fn of asyncFunctions) {
|
||||
module.exports[fn] = function () {
|
||||
return import('./index.js').then((mod) => mod[fn].apply(this, arguments))
|
||||
}
|
||||
}
|
||||
59
dealplustech-astro/node_modules/vitefu/src/index.d.cts
generated
vendored
59
dealplustech-astro/node_modules/vitefu/src/index.d.cts
generated
vendored
@@ -1,59 +0,0 @@
|
||||
// CJS types like `index.d.ts` but dumbed down and doesn't import from `vite`. Thanks TypeScript.
|
||||
|
||||
export interface CrawlFrameworkPkgsOptions {
|
||||
root: string
|
||||
isBuild: boolean
|
||||
workspaceRoot?: string
|
||||
viteUserConfig?: any
|
||||
isFrameworkPkgByJson?: (pkgJson: Record<string, any>) => boolean
|
||||
isFrameworkPkgByName?: (pkgName: string) => boolean | undefined
|
||||
isSemiFrameworkPkgByJson?: (pkgJson: Record<string, any>) => boolean
|
||||
isSemiFrameworkPkgByName?: (pkgName: string) => boolean | undefined
|
||||
}
|
||||
|
||||
export interface CrawlFrameworkPkgsResult {
|
||||
optimizeDeps: {
|
||||
include: string[]
|
||||
exclude: string[]
|
||||
}
|
||||
ssr: {
|
||||
noExternal: string[]
|
||||
external: string[]
|
||||
}
|
||||
}
|
||||
|
||||
export declare function crawlFrameworkPkgs(
|
||||
options: CrawlFrameworkPkgsOptions
|
||||
): Promise<CrawlFrameworkPkgsResult>
|
||||
|
||||
export declare function findDepPkgJsonPath(
|
||||
dep: string,
|
||||
parent: string
|
||||
): Promise<string | undefined>
|
||||
|
||||
export declare function findClosestPkgJsonPath(
|
||||
dir: string,
|
||||
predicate?: (pkgJsonPath: string) => boolean | Promise<boolean>
|
||||
): Promise<string | undefined>
|
||||
|
||||
export declare function pkgNeedsOptimization(
|
||||
pkgJson: Record<string, any>,
|
||||
pkgJsonPath: string
|
||||
): Promise<boolean>
|
||||
|
||||
export declare function isDepExcluded(
|
||||
dep: string,
|
||||
optimizeDepsExclude: any
|
||||
): boolean
|
||||
|
||||
export declare function isDepIncluded(
|
||||
dep: string,
|
||||
optimizeDepsInclude: any
|
||||
): boolean
|
||||
|
||||
export declare function isDepNoExternaled(
|
||||
dep: string,
|
||||
ssrNoExternal: any
|
||||
): boolean
|
||||
|
||||
export declare function isDepExternaled(dep: string, ssrExternal: any): boolean
|
||||
187
dealplustech-astro/node_modules/vitefu/src/index.d.ts
generated
vendored
187
dealplustech-astro/node_modules/vitefu/src/index.d.ts
generated
vendored
@@ -1,187 +0,0 @@
|
||||
import type { DepOptimizationOptions, SSROptions, UserConfig } from 'vite'
|
||||
|
||||
export interface CrawlFrameworkPkgsOptions {
|
||||
/**
|
||||
* Path to the root of the project that contains the `package.json`
|
||||
*/
|
||||
root: string
|
||||
/**
|
||||
* Whether we're currently in a Vite build
|
||||
*/
|
||||
isBuild: boolean
|
||||
|
||||
/**
|
||||
* Path to workspace root of the project
|
||||
*
|
||||
* setting this enables crawling devDependencies of private packages inside the workspace
|
||||
* you can use `import {searchForWorkspaceRoot} from 'vite'` to find it.
|
||||
*/
|
||||
workspaceRoot?: string
|
||||
|
||||
/**
|
||||
* Optional. If a Vite user config is passed, the output Vite config will respect the
|
||||
* set `optimizeDeps` and `ssr` options so it doesn't override it
|
||||
*/
|
||||
viteUserConfig?: UserConfig
|
||||
/**
|
||||
* Whether this is a framework package by checking it's `package.json`.
|
||||
* A framework package is one that exports special files that can't be processed
|
||||
* by esbuild natively. For example, exporting `.framework` files.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* return pkgJson.keywords?.includes('my-framework')
|
||||
* ```
|
||||
*/
|
||||
isFrameworkPkgByJson?: (pkgJson: Record<string, any>) => boolean
|
||||
/**
|
||||
* Whether this is a framework package by checking it's name. This is
|
||||
* usually used as a fast path. Return `true` or `false` if you know 100%
|
||||
* if it's a framework package or not. Return `undefined` to fallback to
|
||||
* `isFrameworkPkgByJson`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* return SPECIAL_PACKAGES.includes(pkgName) || undefined
|
||||
* ```
|
||||
*/
|
||||
isFrameworkPkgByName?: (pkgName: string) => boolean | undefined
|
||||
/**
|
||||
* Whether this is a semi-framework package by checking it's `package.json`.
|
||||
* A semi-framework package is one that **doesn't** export special files but
|
||||
* consumes other APIs of the framework. For example, it only does
|
||||
* `import { debounce } from 'my-framework/utils'`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* return Object.keys(pkgJson.dependencies || {}).includes('my-framework')
|
||||
* ```
|
||||
*/
|
||||
isSemiFrameworkPkgByJson?: (pkgJson: Record<string, any>) => boolean
|
||||
/**
|
||||
* Whether this is a semi-framework package by checking it's name. This is
|
||||
* usually used as a fast path. Return `true` or `false` if you know 100%
|
||||
* if it's a semi-framework package or not. Return `undefined` to fallback to
|
||||
* `isSemiFrameworkPkgByJson`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* return SPECIAL_SEMI_PACKAGES.includes(pkgName) || undefined
|
||||
* ```
|
||||
*/
|
||||
isSemiFrameworkPkgByName?: (pkgName: string) => boolean | undefined
|
||||
}
|
||||
|
||||
export interface CrawlFrameworkPkgsResult {
|
||||
optimizeDeps: {
|
||||
include: string[]
|
||||
exclude: string[]
|
||||
}
|
||||
ssr: {
|
||||
noExternal: string[]
|
||||
external: string[]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crawls for framework packages starting from `<root>/package.json` to build
|
||||
* out a partial Vite config. See the source code for details of how this is built.
|
||||
*/
|
||||
export declare function crawlFrameworkPkgs(
|
||||
options: CrawlFrameworkPkgsOptions
|
||||
): Promise<CrawlFrameworkPkgsResult>
|
||||
|
||||
/**
|
||||
* Find the `package.json` of a dep, starting from the parent, e.g. `process.cwd()`.
|
||||
* A simplified implementation of https://nodejs.org/api/esm.html#resolver-algorithm-specification
|
||||
* (PACKAGE_RESOLVE) for `package.json` specifically.
|
||||
*/
|
||||
export declare function findDepPkgJsonPath(
|
||||
dep: string,
|
||||
parent: string,
|
||||
): Promise<string | undefined>
|
||||
|
||||
/**
|
||||
* Find the closest `package.json` path by walking `dir` upwards.
|
||||
*
|
||||
* Pass a function to `predicate` to check if the current `package.json` is the
|
||||
* one you're looking for. For example, finding `package.json` that has the
|
||||
* `name` field only. Throwing inside the `predicate` is safe and acts the same
|
||||
* as returning false.
|
||||
*/
|
||||
export declare function findClosestPkgJsonPath(
|
||||
dir: string,
|
||||
predicate?: (pkgJsonPath: string) => boolean | Promise<boolean>
|
||||
): Promise<string | undefined>
|
||||
|
||||
/**
|
||||
* Check if a package needs to be optimized by Vite, aka if it's CJS-only
|
||||
*/
|
||||
export declare function pkgNeedsOptimization(
|
||||
pkgJson: Record<string, any>,
|
||||
pkgJsonPath: string
|
||||
): Promise<boolean>
|
||||
|
||||
/**
|
||||
* Check if a dependency is part of an existing `optimizeDeps.exclude` config
|
||||
* @param dep Dependency to be included
|
||||
* @param optimizeDepsExclude Existing `optimizeDeps.exclude` config
|
||||
* @example
|
||||
* ```ts
|
||||
* optimizeDeps: {
|
||||
* include: includesToAdd.filter((dep) => !isDepExcluded(dep, existingExclude))
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export declare function isDepExcluded(
|
||||
dep: string,
|
||||
optimizeDepsExclude: NonNullable<DepOptimizationOptions['exclude']>
|
||||
): boolean
|
||||
|
||||
/**
|
||||
* Check if a dependency is part of an existing `optimizeDeps.include` config
|
||||
* @param dep Dependency to be excluded
|
||||
* @param optimizeDepsInclude Existing `optimizeDeps.include` config
|
||||
* @example
|
||||
* ```ts
|
||||
* optimizeDeps: {
|
||||
* exclude: excludesToAdd.filter((dep) => !isDepIncluded(dep, existingInclude))
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export declare function isDepIncluded(
|
||||
dep: string,
|
||||
optimizeDepsInclude: NonNullable<DepOptimizationOptions['include']>
|
||||
): boolean
|
||||
|
||||
/**
|
||||
* Check if a dependency is part of an existing `ssr.noExternal` config
|
||||
* @param dep Dependency to be excluded
|
||||
* @param ssrNoExternal Existing `ssr.noExternal` config
|
||||
* @example
|
||||
* ```ts
|
||||
* ssr: {
|
||||
* external: externalsToAdd.filter((dep) => !isDepNoExternal(dep, existingNoExternal))
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export declare function isDepNoExternaled(
|
||||
dep: string,
|
||||
ssrNoExternal: NonNullable<SSROptions['noExternal']>
|
||||
): boolean
|
||||
|
||||
/**
|
||||
* Check if a dependency is part of an existing `ssr.external` config
|
||||
* @param dep Dependency to be noExternaled
|
||||
* @param ssrExternal Existing `ssr.external` config
|
||||
* @example
|
||||
* ```ts
|
||||
* ssr: {
|
||||
* noExternal: noExternalsToAdd.filter((dep) => !isDepExternal(dep, existingExternal))
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export declare function isDepExternaled(
|
||||
dep: string,
|
||||
ssrExternal: NonNullable<SSROptions['external']>
|
||||
): boolean
|
||||
315
dealplustech-astro/node_modules/vitefu/src/index.js
generated
vendored
315
dealplustech-astro/node_modules/vitefu/src/index.js
generated
vendored
@@ -1,315 +0,0 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import fsSync from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import path from 'node:path'
|
||||
import {
|
||||
isDepIncluded,
|
||||
isDepExcluded,
|
||||
isDepNoExternaled,
|
||||
isDepExternaled
|
||||
} from './sync.cjs'
|
||||
|
||||
/** @type {import('pnpapi')} */
|
||||
let pnp
|
||||
|
||||
|
||||
/** @type {Array<{ name: string; reference: string; }>} */
|
||||
let pnpWorkspaceLocators;
|
||||
|
||||
if (process.versions.pnp) {
|
||||
try {
|
||||
pnp = createRequire(import.meta.url)('pnpapi')
|
||||
// returns a set of physical locators https://yarnpkg.com/advanced/pnpapi#getdependencytreeroots
|
||||
// @ts-expect-error unfortunately doesn't exist in the `@types` package
|
||||
pnpWorkspaceLocators = pnp.getDependencyTreeRoots()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export { isDepIncluded, isDepExcluded, isDepNoExternaled, isDepExternaled }
|
||||
|
||||
/** @type {import('./index.d.ts').crawlFrameworkPkgs} */
|
||||
export async function crawlFrameworkPkgs(options) {
|
||||
const pkgJsonPath = await findClosestPkgJsonPath(options.root)
|
||||
if (!pkgJsonPath) {
|
||||
// don't throw as package.json is not required
|
||||
return {
|
||||
optimizeDeps: { include: [], exclude: [] },
|
||||
ssr: { noExternal: [], external: [] }
|
||||
}
|
||||
}
|
||||
const pkgJson = await readJson(pkgJsonPath).catch((e) => {
|
||||
throw new Error(`Unable to read ${pkgJsonPath}`, { cause: e })
|
||||
})
|
||||
|
||||
/** @type {string[]} */
|
||||
let optimizeDepsInclude = []
|
||||
/** @type {string[]} */
|
||||
let optimizeDepsExclude = []
|
||||
/** @type {string[]} */
|
||||
let ssrNoExternal = []
|
||||
/** @type {string[]} */
|
||||
let ssrExternal = []
|
||||
|
||||
await crawl(pkgJsonPath, pkgJson)
|
||||
|
||||
// respect vite user config
|
||||
if (options.viteUserConfig) {
|
||||
// remove includes that are explicitly excluded in optimizeDeps
|
||||
const _optimizeDepsExclude = options.viteUserConfig?.optimizeDeps?.exclude
|
||||
if (_optimizeDepsExclude) {
|
||||
optimizeDepsInclude = optimizeDepsInclude.filter(
|
||||
(dep) => !isDepExcluded(dep, _optimizeDepsExclude)
|
||||
)
|
||||
}
|
||||
// remove excludes that are explicitly included in optimizeDeps
|
||||
const _optimizeDepsInclude = options.viteUserConfig?.optimizeDeps?.include
|
||||
if (_optimizeDepsInclude) {
|
||||
optimizeDepsExclude = optimizeDepsExclude.filter(
|
||||
(dep) => !isDepIncluded(dep, _optimizeDepsInclude)
|
||||
)
|
||||
}
|
||||
// remove noExternals that are explicitly externalized
|
||||
const _ssrExternal = options.viteUserConfig?.ssr?.external
|
||||
if (_ssrExternal) {
|
||||
ssrNoExternal = ssrNoExternal.filter(
|
||||
(dep) => !isDepExternaled(dep, _ssrExternal)
|
||||
)
|
||||
}
|
||||
// remove externals that are explicitly noExternal
|
||||
const _ssrNoExternal = options.viteUserConfig?.ssr?.noExternal
|
||||
if (_ssrNoExternal) {
|
||||
ssrExternal = ssrExternal.filter(
|
||||
(dep) => !isDepNoExternaled(dep, _ssrNoExternal)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
optimizeDeps: {
|
||||
include: optimizeDepsInclude,
|
||||
exclude: optimizeDepsExclude
|
||||
},
|
||||
ssr: {
|
||||
noExternal: ssrNoExternal,
|
||||
external: ssrExternal
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* crawl the package.json dependencies for framework packages. rules:
|
||||
* 1. a framework package should be `optimizeDeps.exclude` and `ssr.noExternal`.
|
||||
* 2. the deps of the framework package should be `optimizeDeps.include` and `ssr.external`
|
||||
* unless the dep is also a framework package, in which case do no1 & no2 recursively.
|
||||
* 3. any non-framework packages that aren't imported by a framework package can be skipped entirely.
|
||||
* 4. a semi-framework package is like a framework package, except it isn't `optimizeDeps.exclude`,
|
||||
* but only applies `ssr.noExternal`.
|
||||
* @param {string} pkgJsonPath
|
||||
* @param {Record<string, any>} pkgJson
|
||||
* @param {string[]} [parentDepNames]
|
||||
*/
|
||||
async function crawl(pkgJsonPath, pkgJson, parentDepNames = []) {
|
||||
const isRoot = parentDepNames.length === 0
|
||||
const crawlDevDependencies = isRoot || isPrivateWorkspacePackage(pkgJsonPath,pkgJson,options.workspaceRoot)
|
||||
/** @type {string[]} */
|
||||
let deps = [
|
||||
...Object.keys(pkgJson.dependencies || {}),
|
||||
...((crawlDevDependencies) ? Object.keys(pkgJson.devDependencies || {}) : [])
|
||||
]
|
||||
|
||||
deps = deps.filter((dep) => {
|
||||
// skip circular deps
|
||||
if (parentDepNames.includes(dep)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isFrameworkPkg = options.isFrameworkPkgByName?.(dep)
|
||||
const isSemiFrameworkPkg = options.isSemiFrameworkPkgByName?.(dep)
|
||||
if (isFrameworkPkg) {
|
||||
// framework packages should be excluded from optimization as esbuild can't handle them.
|
||||
// otherwise it'll cause https://github.com/vitejs/vite/issues/3910
|
||||
optimizeDepsExclude.push(dep)
|
||||
// framework packages should be noExternal so that they go through vite's transformation
|
||||
// pipeline, since nodejs can't support them.
|
||||
ssrNoExternal.push(dep)
|
||||
} else if (isSemiFrameworkPkg) {
|
||||
// semi-framework packages should do the same except for optimization exclude as they
|
||||
// aren't needed to work (they don't contain raw framework components)
|
||||
ssrNoExternal.push(dep)
|
||||
}
|
||||
|
||||
// only those that are explictly false can skip crawling since we don't need to do anything
|
||||
// special for them
|
||||
if (isFrameworkPkg === false || isSemiFrameworkPkg === false) {
|
||||
return false
|
||||
}
|
||||
// if `true`, we need to crawl the nested deps to deep include and ssr externalize them in dev.
|
||||
// if `undefined`, it's the same as "i don't know". we need to crawl and find the package.json
|
||||
// to find out.
|
||||
else {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
const promises = deps.map(async (dep) => {
|
||||
const depPkgJsonPath = await _findDepPkgJsonPath(dep, pkgJsonPath, !!options.workspaceRoot)
|
||||
if (!depPkgJsonPath) return
|
||||
const depPkgJson = await readJson(depPkgJsonPath).catch(() => {})
|
||||
if (!depPkgJson) return
|
||||
|
||||
// fast path if this dep is already a framework dep based on the filter condition above
|
||||
const cachedIsFrameworkPkg = ssrNoExternal.includes(dep)
|
||||
if (cachedIsFrameworkPkg) {
|
||||
return crawl(depPkgJsonPath, depPkgJson, parentDepNames.concat(dep))
|
||||
}
|
||||
|
||||
// check if this dep is a framework dep, if so, track and crawl it
|
||||
const isFrameworkPkg = options.isFrameworkPkgByJson?.(depPkgJson)
|
||||
const isSemiFrameworkPkg = options.isSemiFrameworkPkgByJson?.(depPkgJson)
|
||||
if (isFrameworkPkg || isSemiFrameworkPkg) {
|
||||
// see explanation in filter condition above
|
||||
if (isFrameworkPkg) {
|
||||
optimizeDepsExclude.push(dep)
|
||||
ssrNoExternal.push(dep)
|
||||
} else if (isSemiFrameworkPkg) {
|
||||
ssrNoExternal.push(dep)
|
||||
}
|
||||
return crawl(depPkgJsonPath, depPkgJson, parentDepNames.concat(dep))
|
||||
}
|
||||
|
||||
// if we're crawling in a non-root state, the parent is 100% a framework package
|
||||
// because of the above if block. in this case, if it's dep of a non-framework
|
||||
// package, handle special cases for them.
|
||||
if (!isRoot) {
|
||||
// deep include it if it's a CJS package, so it becomes ESM and vite is happy.
|
||||
if (await pkgNeedsOptimization(depPkgJson, depPkgJsonPath)) {
|
||||
optimizeDepsInclude.push(parentDepNames.concat(dep).join(' > '))
|
||||
}
|
||||
// also externalize it in dev so it doesn't trip vite's SSR transformation.
|
||||
// we do in dev only as build cannot access deep external packages in strict
|
||||
// dependency installations, such as pnpm.
|
||||
if (!options.isBuild && !ssrExternal.includes(dep)) {
|
||||
ssrExternal.push(dep)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
}
|
||||
/** @type {import('./index.d.ts').findDepPkgJsonPath} */
|
||||
export async function findDepPkgJsonPath(dep, parent) {
|
||||
return _findDepPkgJsonPath(dep, parent, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* internal implementation to avoid exposing the usePnpWorkspaceLocators flag
|
||||
*
|
||||
* @param {string} dep
|
||||
* @param {string} parent
|
||||
* @param {boolean} usePnpWorkspaceLocators
|
||||
* @returns {Promise<undefined|string>}
|
||||
* @private
|
||||
*/
|
||||
async function _findDepPkgJsonPath(dep, parent, usePnpWorkspaceLocators) {
|
||||
if (pnp) {
|
||||
if(usePnpWorkspaceLocators) {
|
||||
try {
|
||||
// if we're in a workspace and the dep is a workspace dep,
|
||||
// then we'll try to resolve to it's real location
|
||||
const locator = pnpWorkspaceLocators.find((root) => root.name === dep)
|
||||
if (locator) {
|
||||
const pkgPath = pnp.getPackageInformation(locator).packageLocation
|
||||
return path.resolve(pkgPath, 'package.json')
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
try {
|
||||
const depRoot = pnp.resolveToUnqualified(dep, parent)
|
||||
if (!depRoot) return undefined
|
||||
return path.join(depRoot, 'package.json')
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
let root = parent
|
||||
while (root) {
|
||||
const pkg = path.join(root, 'node_modules', dep, 'package.json')
|
||||
try {
|
||||
await fs.access(pkg)
|
||||
// use 'node:fs' version to match 'vite:resolve' and avoid realpath.native quirk
|
||||
// https://github.com/sveltejs/vite-plugin-svelte/issues/525#issuecomment-1355551264
|
||||
return fsSync.realpathSync(pkg)
|
||||
} catch {}
|
||||
const nextRoot = path.dirname(root)
|
||||
if (nextRoot === root) break
|
||||
root = nextRoot
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
/** @type {import('./index.d.ts').findClosestPkgJsonPath} */
|
||||
export async function findClosestPkgJsonPath(dir, predicate = undefined) {
|
||||
if (dir.endsWith('package.json')) {
|
||||
dir = path.dirname(dir)
|
||||
}
|
||||
while (dir) {
|
||||
const pkg = path.join(dir, 'package.json')
|
||||
try {
|
||||
const stat = await fs.stat(pkg)
|
||||
if (stat.isFile() && (!predicate || (await predicate(pkg)))) {
|
||||
return pkg
|
||||
}
|
||||
} catch {}
|
||||
const nextDir = path.dirname(dir)
|
||||
if (nextDir === dir) break
|
||||
dir = nextDir
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
/** @type {import('./index.d.ts').pkgNeedsOptimization} */
|
||||
export async function pkgNeedsOptimization(pkgJson, pkgJsonPath) {
|
||||
// only optimize if is cjs, using the below as heuristic
|
||||
// see https://github.com/sveltejs/vite-plugin-svelte/issues/162
|
||||
if (pkgJson.module || pkgJson.exports) return false
|
||||
// if have main, ensure entry is js so vite can prebundle it
|
||||
// see https://github.com/sveltejs/vite-plugin-svelte/issues/233
|
||||
if (pkgJson.main) {
|
||||
const entryExt = path.extname(pkgJson.main)
|
||||
return !entryExt || entryExt === '.js' || entryExt === '.cjs'
|
||||
}
|
||||
// check if has implicit index.js entrypoint to prebundle
|
||||
// see https://github.com/sveltejs/vite-plugin-svelte/issues/281
|
||||
// see https://github.com/solidjs/vite-plugin-solid/issues/70#issuecomment-1306488154
|
||||
try {
|
||||
await fs.access(path.join(path.dirname(pkgJsonPath), 'index.js'))
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} findDepPkgJsonPath
|
||||
* @returns {Promise<Record<string, any>>}
|
||||
*/
|
||||
async function readJson(findDepPkgJsonPath) {
|
||||
return JSON.parse(await fs.readFile(findDepPkgJsonPath, 'utf8'))
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} pkgJsonPath
|
||||
* @param {Record<string,any>} pkgJson
|
||||
* @param {string} [workspaceRoot]
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isPrivateWorkspacePackage(pkgJsonPath,pkgJson,workspaceRoot = undefined) {
|
||||
return !!(
|
||||
workspaceRoot &&
|
||||
pkgJson.private &&
|
||||
!pkgJsonPath.match(/[/\\]node_modules[/\\]/) &&
|
||||
!path.relative(workspaceRoot,pkgJsonPath).startsWith('..')
|
||||
)
|
||||
}
|
||||
66
dealplustech-astro/node_modules/vitefu/src/sync.cjs
generated
vendored
66
dealplustech-astro/node_modules/vitefu/src/sync.cjs
generated
vendored
@@ -1,66 +0,0 @@
|
||||
// contains synchronous API only so it can be exported as CJS and ESM
|
||||
|
||||
/** @type {import('./index.d.ts').isDepIncluded} */
|
||||
function isDepIncluded(dep, optimizeDepsInclude) {
|
||||
return optimizeDepsInclude.some((id) => parseIncludeStr(id) === dep)
|
||||
}
|
||||
|
||||
/** @type {import('./index.d.ts').isDepExcluded} */
|
||||
function isDepExcluded(dep, optimizeDepsExclude) {
|
||||
dep = parseIncludeStr(dep)
|
||||
return optimizeDepsExclude.some(
|
||||
(id) => id === dep || dep.startsWith(`${id}/`)
|
||||
)
|
||||
}
|
||||
|
||||
/** @type {import('./index.d.ts').isDepNoExternaled} */
|
||||
function isDepNoExternaled(dep, ssrNoExternal) {
|
||||
if (ssrNoExternal === true) {
|
||||
return true
|
||||
} else {
|
||||
return isMatch(dep, ssrNoExternal)
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {import('./index.d.ts').isDepExternaled} */
|
||||
function isDepExternaled(dep, ssrExternal) {
|
||||
// If `ssrExternal` is `true`, it just means that all linked
|
||||
// dependencies should also be externalized by default. It doesn't
|
||||
// mean that a dependency is being explicitly externalized. So we
|
||||
// return `false` in this case.
|
||||
// @ts-expect-error can be true in Vite 6
|
||||
if (ssrExternal === true) {
|
||||
return false
|
||||
} else {
|
||||
return ssrExternal.includes(dep)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} raw could be "foo" or "foo > bar" etc
|
||||
*/
|
||||
function parseIncludeStr(raw) {
|
||||
const lastArrow = raw.lastIndexOf('>')
|
||||
return lastArrow === -1 ? raw : raw.slice(lastArrow + 1).trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} target
|
||||
* @param {string | RegExp | (string | RegExp)[]} pattern
|
||||
*/
|
||||
function isMatch(target, pattern) {
|
||||
if (Array.isArray(pattern)) {
|
||||
return pattern.some((p) => isMatch(target, p))
|
||||
} else if (typeof pattern === 'string') {
|
||||
return target === pattern
|
||||
} else if (pattern instanceof RegExp) {
|
||||
return pattern.test(target)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isDepIncluded,
|
||||
isDepExcluded,
|
||||
isDepNoExternaled,
|
||||
isDepExternaled
|
||||
}
|
||||
6048
dealplustech-astro/package-lock.json
generated
6048
dealplustech-astro/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"name": "dealplustech-astro",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"astro": "^5.17.1",
|
||||
"tailwindcss": "^4.2.1"
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
# 🤖 Automatic Service Creation - Current Limitations
|
||||
|
||||
## 📋 The Challenge
|
||||
|
||||
Easypanel's API has a **service creation endpoint** (`services.app.createService`), but it requires a **complex nested schema** that's difficult to construct automatically:
|
||||
|
||||
```json
|
||||
{
|
||||
"input": {
|
||||
"json": {
|
||||
"projectName": "customerwebsite",
|
||||
"serviceName": "my-app",
|
||||
"type": "docker",
|
||||
"source": {
|
||||
"type": "dockerImage",
|
||||
"dockerImage": "nginx:alpine",
|
||||
"dockerPort": 80,
|
||||
// ... more nested fields
|
||||
},
|
||||
// ... more required fields
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ✅ Current Solution (Semi-Automated)
|
||||
|
||||
The skill now handles the workflow intelligently:
|
||||
|
||||
### Step 1: Automated - Build & Prepare
|
||||
```bash
|
||||
./deploy.sh deploy
|
||||
```
|
||||
**What it does:**
|
||||
- ✅ Builds Docker image
|
||||
- ✅ Lists your projects
|
||||
- ✅ Checks for existing services
|
||||
- ✅ Provides step-by-step instructions
|
||||
|
||||
### Step 2: Manual - Create Service (2 minutes)
|
||||
```
|
||||
1. Open Easypanel dashboard
|
||||
2. Select project
|
||||
3. New Service → Docker image
|
||||
4. Enter image name and port
|
||||
5. Click Deploy
|
||||
6. Copy Service ID
|
||||
```
|
||||
|
||||
### Step 3: Automated - Register & Manage
|
||||
```bash
|
||||
./deploy.sh register svc_abc123...
|
||||
```
|
||||
**What it does:**
|
||||
- ✅ Saves service ID
|
||||
- ✅ Enables all management commands
|
||||
- ✅ Future updates are automated
|
||||
|
||||
## 🔄 After Registration - Fully Automated
|
||||
|
||||
Once registered, the skill can:
|
||||
|
||||
### Update Deployment
|
||||
```bash
|
||||
./deploy.sh update
|
||||
# Rebuilds Docker image automatically
|
||||
# Ready for deployment
|
||||
```
|
||||
|
||||
### Check Status
|
||||
```bash
|
||||
./deploy.sh status
|
||||
# Shows service details
|
||||
```
|
||||
|
||||
### List Projects
|
||||
```bash
|
||||
./deploy.sh list
|
||||
# Shows all projects and services
|
||||
```
|
||||
|
||||
## 🎯 Why This Approach?
|
||||
|
||||
### Advantages:
|
||||
1. **One-time manual step** (2 minutes)
|
||||
2. **Fully automated thereafter**
|
||||
3. **No complex API schema issues**
|
||||
4. **Works with current Easypanel API**
|
||||
5. **Secure** (you control service creation)
|
||||
|
||||
### Future Improvements:
|
||||
- Easypanel may simplify their API
|
||||
- We can add full automation when API supports it
|
||||
- Current workflow is production-ready
|
||||
|
||||
## 📊 Workflow Comparison
|
||||
|
||||
| Step | Fully Automated | Current (Semi-Auto) |
|
||||
|------|----------------|---------------------|
|
||||
| Build Docker | ✅ | ✅ |
|
||||
| Create Service | ❌ (API limitation) | ⚠️ Manual (2 min) |
|
||||
| Register ID | ❌ | ✅ |
|
||||
| Update | ❌ | ✅ |
|
||||
| Status | ❌ | ✅ |
|
||||
| Manage | ❌ | ✅ |
|
||||
|
||||
**Result:** 80% automated, 20% one-time manual setup
|
||||
|
||||
## 🔮 Future: Full Automation Path
|
||||
|
||||
When Easypanel API supports it:
|
||||
|
||||
```bash
|
||||
# Future (when API available)
|
||||
./deploy.sh deploy --automatic
|
||||
# Would:
|
||||
# 1. Build Docker image
|
||||
# 2. Create service via API
|
||||
# 3. Save service ID automatically
|
||||
# 4. Deploy immediately
|
||||
```
|
||||
|
||||
Until then, the current workflow is **production-ready and efficient**.
|
||||
|
||||
---
|
||||
|
||||
## 💡 Best Practices
|
||||
|
||||
### For Initial Deployment:
|
||||
1. Run `./deploy.sh deploy`
|
||||
2. Create service in dashboard (copy ID)
|
||||
3. Register with `./deploy.sh register ID`
|
||||
|
||||
### For Updates:
|
||||
1. Make code changes
|
||||
2. Run `./deploy.sh update`
|
||||
3. Click "Deploy" in Easypanel (or auto-deploy if enabled)
|
||||
|
||||
### For Multiple Services:
|
||||
Each service gets its own ID stored in `~/.easypanel/state.json`
|
||||
|
||||
---
|
||||
|
||||
**Current Status:** ✅ Production Ready
|
||||
**Automation Level:** 80% (20% one-time setup)
|
||||
**Future:** 100% automated when API supports it
|
||||
@@ -1,88 +0,0 @@
|
||||
# 🚀 Easypanel Deployment Skill - Quick Start
|
||||
|
||||
## 5-Minute Setup
|
||||
|
||||
### Step 1: Verify Token (Already Done ✅)
|
||||
|
||||
Your token is stored in: `~/.easypanel/credentials`
|
||||
|
||||
### Step 2: Deploy Your First App
|
||||
|
||||
```bash
|
||||
cd dealplustech-astro
|
||||
|
||||
# First deployment (creates service, saves ID)
|
||||
./skills/easypanel-deploy/deploy.sh deploy
|
||||
```
|
||||
|
||||
### Step 3: Update Your App
|
||||
|
||||
After making code changes:
|
||||
|
||||
```bash
|
||||
# Rebuild and redeploy (uses saved ID)
|
||||
./skills/easypanel-deploy/deploy.sh update
|
||||
```
|
||||
|
||||
### Step 4: Check Status
|
||||
|
||||
```bash
|
||||
# Anytime status check
|
||||
./skills/easypanel-deploy/deploy.sh status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Commands Cheat Sheet
|
||||
|
||||
| Command | What It Does |
|
||||
|---------|-------------|
|
||||
| `./deploy.sh deploy` | First-time deployment (saves ID) |
|
||||
| `./deploy.sh update` | Rebuild and redeploy (uses saved ID) |
|
||||
| `./deploy.sh restart` | Restart service |
|
||||
| `./deploy.sh status` | Show status |
|
||||
| `./deploy.sh logs` | View logs |
|
||||
| `./deploy.sh list` | List all projects |
|
||||
|
||||
---
|
||||
|
||||
## How State Works
|
||||
|
||||
**First Deploy:**
|
||||
```bash
|
||||
./deploy.sh deploy
|
||||
# Saves: service ID, project ID to ~/.easypanel/state.json
|
||||
```
|
||||
|
||||
**Every Update After:**
|
||||
```bash
|
||||
./deploy.sh update
|
||||
# Reads: service ID from state.json
|
||||
# Does: Rebuild + Redeploy
|
||||
```
|
||||
|
||||
**No need to remember IDs - skill handles it!** ✅
|
||||
|
||||
---
|
||||
|
||||
## Files Created
|
||||
|
||||
After first deploy:
|
||||
|
||||
```
|
||||
~/.easypanel/
|
||||
├── credentials # Your API token (secure)
|
||||
└── state.json # Service & project IDs (auto-generated)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Deploy now:** `./deploy.sh deploy`
|
||||
2. **Check status:** `./deploy.sh status`
|
||||
3. **Make changes, then update:** `./deploy.sh update`
|
||||
|
||||
---
|
||||
|
||||
**Full docs:** `SKILL_v2.md`
|
||||
@@ -1,623 +0,0 @@
|
||||
# 🚀 Easypanel Deployment Skill
|
||||
|
||||
**Skill ID:** `easypanel-deploy`
|
||||
**Version:** 2.0.0
|
||||
**Author:** Deal Plus Tech DevOps
|
||||
**Last Updated:** 2026-03-02
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Automated deployment skill for deploying Astro, Next.js, Vite, and other web applications to Easypanel via API.
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Authentication Setup
|
||||
|
||||
### Store Your API Token
|
||||
|
||||
**Option 1: Environment Variable (Recommended)**
|
||||
|
||||
Add to your shell profile (`~/.zshrc`, `~/.bashrc`, or `~/.profile`):
|
||||
|
||||
```bash
|
||||
export EASYPANEL_API_TOKEN="your-api-token-here"
|
||||
export EASYPANEL_URL="http://110.164.146.46:3000"
|
||||
```
|
||||
|
||||
Then reload:
|
||||
```bash
|
||||
source ~/.zshrc # or source ~/.bashrc
|
||||
```
|
||||
|
||||
**Option 2: Credential File**
|
||||
|
||||
Create `~/.easypanel/credentials`:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.easypanel
|
||||
cat > ~/.easypanel/credentials << EOF
|
||||
EASYPANEL_URL=http://110.164.146.46:3000
|
||||
EASYPANEL_API_TOKEN=your-api-token-here
|
||||
EASYPANEL_DEFAULT_PROJECT=default
|
||||
EOF
|
||||
|
||||
chmod 600 ~/.easypanel/credentials
|
||||
```
|
||||
|
||||
**Option 3: Pass Token Directly**
|
||||
|
||||
```bash
|
||||
./deploy-easypanel.sh your-api-token
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Configuration File
|
||||
|
||||
Create `easypanel.config.json` in your project root:
|
||||
|
||||
```json
|
||||
{
|
||||
"easypanel": {
|
||||
"url": "http://110.164.146.46:3000",
|
||||
"project": "dealplustech",
|
||||
"app": {
|
||||
"name": "dealplustech-astro",
|
||||
"port": 4321,
|
||||
"image": "dealplustech-astro:latest"
|
||||
},
|
||||
"env": {
|
||||
"NODE_ENV": "production",
|
||||
"PORT": "4321",
|
||||
"HOST": "0.0.0.0"
|
||||
},
|
||||
"docker": {
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile",
|
||||
"buildArgs": {}
|
||||
},
|
||||
"resources": {
|
||||
"cpu": "0.5",
|
||||
"memory": "512M",
|
||||
"storage": "1G"
|
||||
},
|
||||
"domain": {
|
||||
"enabled": false,
|
||||
"name": "dealplustech.co.th",
|
||||
"ssl": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Usage
|
||||
|
||||
### Quick Deploy
|
||||
|
||||
```bash
|
||||
# Navigate to project
|
||||
cd your-project
|
||||
|
||||
# Run deployment (uses token from environment)
|
||||
easypanel-deploy
|
||||
|
||||
# Or pass token directly
|
||||
easypanel-deploy --token your-api-token
|
||||
```
|
||||
|
||||
### Interactive Mode
|
||||
|
||||
```bash
|
||||
easypanel-deploy --interactive
|
||||
```
|
||||
|
||||
### Deploy Specific Environment
|
||||
|
||||
```bash
|
||||
easypanel-deploy --environment production
|
||||
easypanel-deploy --environment staging
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Commands
|
||||
|
||||
### `deploy` - Deploy Application
|
||||
|
||||
```bash
|
||||
easypanel-deploy deploy
|
||||
|
||||
# Options:
|
||||
# --token, -t API token (or use EASYPANEL_API_TOKEN env)
|
||||
# --project, -p Project name
|
||||
# --name, -n Service name
|
||||
# --image, -i Docker image
|
||||
# --port, -P Container port
|
||||
# --env, -e Environment variables (key=value)
|
||||
# --build Force rebuild Docker image
|
||||
# --no-build Skip Docker build
|
||||
# --dry-run Show what would be deployed
|
||||
```
|
||||
|
||||
### `status` - Check Deployment Status
|
||||
|
||||
```bash
|
||||
easypanel-deploy status
|
||||
|
||||
# Shows:
|
||||
# - Service status (running/stopped/error)
|
||||
# - Resource usage
|
||||
# - Recent deployments
|
||||
# - Exposed URLs
|
||||
```
|
||||
|
||||
### `logs` - View Service Logs
|
||||
|
||||
```bash
|
||||
easypanel-deploy logs
|
||||
|
||||
# Options:
|
||||
# --lines, -n Number of lines (default: 50)
|
||||
# --follow, -f Follow logs in real-time
|
||||
# --since Show logs since timestamp
|
||||
```
|
||||
|
||||
### `restart` - Restart Service
|
||||
|
||||
```bash
|
||||
easypanel-deploy restart
|
||||
```
|
||||
|
||||
### `stop` - Stop Service
|
||||
|
||||
```bash
|
||||
easypanel-deploy stop
|
||||
```
|
||||
|
||||
### `delete` - Delete Service
|
||||
|
||||
```bash
|
||||
easypanel-deploy delete --force # Force delete without confirmation
|
||||
```
|
||||
|
||||
### `list` - List All Services
|
||||
|
||||
```bash
|
||||
easypanel-deploy list
|
||||
|
||||
# Options:
|
||||
# --project, -p Filter by project
|
||||
# --format, -f Output format (table/json)
|
||||
```
|
||||
|
||||
### `info` - Show Service Details
|
||||
|
||||
```bash
|
||||
easypanel-deploy info
|
||||
|
||||
# Shows:
|
||||
# - Configuration
|
||||
# - Environment variables
|
||||
# - Resource allocation
|
||||
# - Deployment history
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
your-project/
|
||||
├── Dockerfile # Required
|
||||
├── easypanel.config.json # Optional (uses defaults if missing)
|
||||
├── .env # Optional (loaded as env vars)
|
||||
├── .dockerignore # Optional
|
||||
└── deploy-easypanel.sh # Deployment script (included in skill)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security Best Practices
|
||||
|
||||
### Token Storage
|
||||
|
||||
✅ **DO:**
|
||||
- Use environment variables
|
||||
- Store in credential manager (1Password, Keychain)
|
||||
- Use `.env` files (gitignored)
|
||||
- Rotate tokens regularly
|
||||
|
||||
❌ **DON'T:**
|
||||
- Commit tokens to Git
|
||||
- Share tokens in chat/clear text
|
||||
- Use tokens in CI/CD logs
|
||||
- Store in plain text files
|
||||
|
||||
### Token Rotation
|
||||
|
||||
```bash
|
||||
# Generate new token in Easypanel dashboard
|
||||
# Update environment variable
|
||||
export EASYPANEL_API_TOKEN="new-token"
|
||||
|
||||
# Test new token
|
||||
easypanel-deploy status
|
||||
|
||||
# Revoke old token in Easypanel dashboard
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐳 Docker Configuration
|
||||
|
||||
### Standard Dockerfile (Astro)
|
||||
|
||||
```dockerfile
|
||||
# Build Stage
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# Production Stage
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci --production
|
||||
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
EXPOSE 4321
|
||||
|
||||
CMD ["npx", "astro", "preview", "--host", "0.0.0.0", "--port", "4321"]
|
||||
```
|
||||
|
||||
### Next.js Dockerfile
|
||||
|
||||
```dockerfile
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci --production
|
||||
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
### Vite Dockerfile
|
||||
|
||||
```dockerfile
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:alpine
|
||||
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuration Reference
|
||||
|
||||
### `easypanel.config.json`
|
||||
|
||||
| Property | Type | Default | Description |
|
||||
|----------|------|---------|-------------|
|
||||
| `easypanel.url` | string | `http://110.164.146.46:3000` | Easypanel instance URL |
|
||||
| `easypanel.project` | string | `default` | Project name |
|
||||
| `easypanel.app.name` | string | Project folder name | Service name |
|
||||
| `easypanel.app.port` | number | `3000` | Container port |
|
||||
| `easypanel.app.image` | string | `{name}:latest` | Docker image name |
|
||||
| `easypanel.env` | object | `{}` | Environment variables |
|
||||
| `easypanel.docker.context` | string | `.` | Docker build context |
|
||||
| `easypanel.docker.dockerfile` | string | `Dockerfile` | Dockerfile path |
|
||||
| `easypanel.resources.cpu` | string | `"0.5"` | CPU allocation |
|
||||
| `easypanel.resources.memory` | string | `"512M"` | Memory allocation |
|
||||
| `easypanel.resources.storage` | string | `"1G"` | Storage allocation |
|
||||
| `easypanel.domain.enabled` | boolean | `false` | Enable custom domain |
|
||||
| `easypanel.domain.name` | string | `""` | Custom domain name |
|
||||
| `easypanel.domain.ssl` | boolean | `true` | Enable SSL |
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Troubleshooting
|
||||
|
||||
### API Connection Failed
|
||||
|
||||
```bash
|
||||
# Test connection
|
||||
curl -I http://110.164.146.46:3000
|
||||
|
||||
# Check if token is set
|
||||
echo $EASYPANEL_API_TOKEN
|
||||
|
||||
# Test API with token
|
||||
curl -H "Authorization: Bearer $EASYPANEL_API_TOKEN" \
|
||||
http://110.164.146.46:3000/api/trpc/setup.getStatus \
|
||||
--insecure
|
||||
```
|
||||
|
||||
### Docker Build Fails
|
||||
|
||||
```bash
|
||||
# Build with verbose output
|
||||
docker build --no-cache --progress=plain -t your-image:latest .
|
||||
|
||||
# Check Dockerfile syntax
|
||||
hadolint Dockerfile
|
||||
|
||||
# Test build locally
|
||||
docker run -p 4321:4321 your-image:latest
|
||||
```
|
||||
|
||||
### Service Won't Start
|
||||
|
||||
```bash
|
||||
# Check logs
|
||||
easypanel-deploy logs --lines 100
|
||||
|
||||
# Inspect service
|
||||
easypanel-deploy info
|
||||
|
||||
# Restart service
|
||||
easypanel-deploy restart
|
||||
|
||||
# Check resource allocation
|
||||
easypanel-deploy info | grep -A 10 "Resources"
|
||||
```
|
||||
|
||||
### Token Expired/Invalid
|
||||
|
||||
```bash
|
||||
# Generate new token in Easypanel dashboard
|
||||
# Update environment variable
|
||||
export EASYPANEL_API_TOKEN="new-token"
|
||||
|
||||
# Add to shell profile for persistence
|
||||
echo 'export EASYPANEL_API_TOKEN="new-token"' >> ~/.zshrc
|
||||
source ~/.zshrc
|
||||
|
||||
# Verify
|
||||
easypanel-deploy status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Monitoring
|
||||
|
||||
### Check Service Health
|
||||
|
||||
```bash
|
||||
easypanel-deploy status
|
||||
|
||||
# Output:
|
||||
# ✅ Service: dealplustech-astro
|
||||
# Status: Running
|
||||
# Uptime: 2 days, 4 hours
|
||||
# CPU: 12%
|
||||
# Memory: 256MB / 512MB
|
||||
# URL: http://dealplustech-astro.easypanel.app
|
||||
```
|
||||
|
||||
### View Metrics
|
||||
|
||||
```bash
|
||||
easypanel-deploy metrics
|
||||
|
||||
# Shows:
|
||||
# - CPU usage over time
|
||||
# - Memory usage
|
||||
# - Network traffic
|
||||
# - Request count
|
||||
```
|
||||
|
||||
### Setup Alerts
|
||||
|
||||
```bash
|
||||
easypanel-deploy alert --cpu 80 --memory 80 --email admin@example.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 CI/CD Integration
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
```yaml
|
||||
name: Deploy to Easypanel
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Build Docker image
|
||||
run: docker build -t dealplustech-astro:latest .
|
||||
|
||||
- name: Deploy to Easypanel
|
||||
run: |
|
||||
curl -X POST "$EASYPANEL_URL/api/trpc/services.app.deploy" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $EASYPANEL_API_TOKEN" \
|
||||
-d '{"input":{"json":{"projectName":"dealplustech","serviceName":"dealplustech-astro"}}}' \
|
||||
--insecure
|
||||
env:
|
||||
EASYPANEL_URL: ${{ secrets.EASYPANEL_URL }}
|
||||
EASYPANEL_API_TOKEN: ${{ secrets.EASYPANEL_API_TOKEN }}
|
||||
```
|
||||
|
||||
### GitLab CI
|
||||
|
||||
```yaml
|
||||
deploy:
|
||||
stage: deploy
|
||||
image: docker:20
|
||||
services:
|
||||
- docker:20-dind
|
||||
|
||||
script:
|
||||
- docker build -t dealplustech-astro:latest .
|
||||
- docker push $CI_REGISTRY_IMAGE:latest
|
||||
- |
|
||||
curl -X POST "$EASYPANEL_URL/api/trpc/services.app.deploy" \
|
||||
-H "Authorization: Bearer $EASYPANEL_API_TOKEN" \
|
||||
-d '{"input":{"json":{"projectName":"dealplustech","serviceName":"dealplustech-astro"}}}' \
|
||||
--insecure
|
||||
|
||||
only:
|
||||
- main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support & Resources
|
||||
|
||||
### Documentation
|
||||
- **Easypanel Docs:** https://docs.easypanel.io
|
||||
- **API Reference:** http://110.164.146.46:3000/api
|
||||
- **Skill Repo:** [Link to your skill repository]
|
||||
|
||||
### Getting Help
|
||||
1. Check troubleshooting section
|
||||
2. Review service logs: `easypanel-deploy logs`
|
||||
3. Check Easypanel dashboard
|
||||
4. Contact DevOps team
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Examples
|
||||
|
||||
### Deploy Astro Project
|
||||
|
||||
```bash
|
||||
cd astro-project
|
||||
easypanel-deploy deploy \
|
||||
--project dealplustech \
|
||||
--name my-astro-site \
|
||||
--port 4321
|
||||
```
|
||||
|
||||
### Deploy Next.js Project
|
||||
|
||||
```bash
|
||||
cd nextjs-project
|
||||
easypanel-deploy deploy \
|
||||
--project dealplustech \
|
||||
--name my-next-app \
|
||||
--port 3000 \
|
||||
--env NODE_ENV=production \
|
||||
--env NEXT_PUBLIC_API_URL=https://api.example.com
|
||||
```
|
||||
|
||||
### Deploy Vite Project
|
||||
|
||||
```bash
|
||||
cd vite-project
|
||||
easypanel-deploy deploy \
|
||||
--project dealplustech \
|
||||
--name my-vite-app \
|
||||
--port 80 \
|
||||
--image nginx:alpine
|
||||
```
|
||||
|
||||
### Multi-Environment Setup
|
||||
|
||||
```bash
|
||||
# Deploy to staging
|
||||
easypanel-deploy deploy \
|
||||
--project dealplustech \
|
||||
--name my-app-staging \
|
||||
--env NODE_ENV=staging \
|
||||
--env DATABASE_URL=staging-db-url
|
||||
|
||||
# Deploy to production
|
||||
easypanel-deploy deploy \
|
||||
--project dealplustech \
|
||||
--name my-app-production \
|
||||
--env NODE_ENV=production \
|
||||
--env DATABASE_URL=production-db-url
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist for New Projects
|
||||
|
||||
- [ ] Create `Dockerfile` for project
|
||||
- [ ] Add `easypanel.config.json` (optional)
|
||||
- [ ] Set `EASYPANEL_API_TOKEN` environment variable
|
||||
- [ ] Test local Docker build: `docker build -t test:latest .`
|
||||
- [ ] Test locally: `docker run -p 4321:4321 test:latest`
|
||||
- [ ] Deploy: `easypanel-deploy deploy`
|
||||
- [ ] Verify: `easypanel-deploy status`
|
||||
- [ ] Setup custom domain (optional)
|
||||
- [ ] Enable SSL (optional)
|
||||
- [ ] Configure monitoring/alerts
|
||||
|
||||
---
|
||||
|
||||
**Skill Version:** 2.0.0
|
||||
**Last Updated:** 2026-03-02
|
||||
**Status:** ✅ Production Ready
|
||||
**API Version:** Easypanel 2.24.0
|
||||
@@ -1,563 +0,0 @@
|
||||
# 🚀 Easypanel Deployment Skill v2.0
|
||||
|
||||
**Skill ID:** `easypanel-deploy`
|
||||
**Version:** 2.0.0 - **With State Management**
|
||||
**Author:** Deal Plus Tech DevOps
|
||||
**Last Updated:** 2026-03-02
|
||||
|
||||
---
|
||||
|
||||
## ✨ What's New in v2.0
|
||||
|
||||
### Key Features:
|
||||
- ✅ **Automatic ID Storage** - Saves project & service IDs after creation
|
||||
- ✅ **Full Lifecycle Management** - Deploy, update, start, stop, restart
|
||||
- ✅ **State Persistence** - Remembers your apps across sessions
|
||||
- ✅ **One-Command Updates** - Rebuild and redeploy with single command
|
||||
- ✅ **Status Monitoring** - Check app status anytime
|
||||
- ✅ **Log Access** - View deployment and runtime logs
|
||||
|
||||
---
|
||||
|
||||
## 📁 File Structure
|
||||
|
||||
```
|
||||
~/.easypanel/
|
||||
├── credentials # API token (secure, 600 permissions)
|
||||
└── state.json # Stored IDs and deployment history
|
||||
|
||||
your-project/
|
||||
├── skills/easypanel-deploy/
|
||||
│ ├── deploy.sh # Main deployment script
|
||||
│ ├── SKILL.md # This documentation
|
||||
│ └── README.md # Quick start
|
||||
└── easypanel.config.json # Project configuration (optional)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Commands
|
||||
|
||||
### `deploy` - Deploy Application
|
||||
|
||||
First-time deployment creates service and saves ID:
|
||||
|
||||
```bash
|
||||
./deploy.sh deploy
|
||||
|
||||
# Output:
|
||||
# ✅ Docker image built
|
||||
# ✅ Service created: my-app (svc_abc123...)
|
||||
# ✅ State saved: service.my-app = svc_abc123...
|
||||
# ✅ Deployment complete!
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `-b, --skip-build` - Skip Docker build
|
||||
|
||||
---
|
||||
|
||||
### `update` - Update Application
|
||||
|
||||
Rebuilds image and redeploys (uses stored service ID):
|
||||
|
||||
```bash
|
||||
./deploy.sh update
|
||||
|
||||
# Does:
|
||||
# 1. Rebuilds Docker image
|
||||
# 2. Triggers redeployment
|
||||
# 3. Shows deployment logs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `restart` - Restart Service
|
||||
|
||||
Restarts running service:
|
||||
|
||||
```bash
|
||||
./deploy.sh restart
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `start` - Start Service
|
||||
|
||||
Alias for restart:
|
||||
|
||||
```bash
|
||||
./deploy.sh start
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `stop` - Stop Service
|
||||
|
||||
Stops running service:
|
||||
|
||||
```bash
|
||||
./deploy.sh stop
|
||||
|
||||
# Note: May need manual action in dashboard depending on Easypanel API
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `status` - Show Service Status
|
||||
|
||||
Shows current status, resources, URLs:
|
||||
|
||||
```bash
|
||||
./deploy.sh status
|
||||
|
||||
# Output:
|
||||
# ============================================================
|
||||
# Service: my-app
|
||||
# ============================================================
|
||||
# Status: running
|
||||
# Type: docker
|
||||
# Image: my-app:latest
|
||||
# Port: 4321
|
||||
#
|
||||
# URLs:
|
||||
# - https://my-app.easypanel.app
|
||||
#
|
||||
# Resources:
|
||||
# CPU: 0.5
|
||||
# Memory: 512M
|
||||
# ============================================================
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `logs` - View Service Logs
|
||||
|
||||
Shows deployment and runtime logs:
|
||||
|
||||
```bash
|
||||
./deploy.sh logs # Last 50 lines
|
||||
./deploy.sh logs -n 100 # Last 100 lines
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `list` - List All Projects
|
||||
|
||||
Shows all projects and services:
|
||||
|
||||
```bash
|
||||
./deploy.sh list
|
||||
|
||||
# Output:
|
||||
# ID Name Services
|
||||
# -----------------------------------------------------------------
|
||||
# prj_abc123... dealplustech 3
|
||||
# prj_def456... staging 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 State Management
|
||||
|
||||
### What Gets Stored
|
||||
|
||||
After successful deployment, skill saves:
|
||||
|
||||
```json
|
||||
{
|
||||
"services": {
|
||||
"dealplustech-astro": {
|
||||
"id": "svc_abc123...",
|
||||
"project_id": "prj_def456...",
|
||||
"name": "dealplustech-astro",
|
||||
"image": "dealplustech-astro:latest",
|
||||
"port": 4321,
|
||||
"updated": "2026-03-02T10:45:00Z"
|
||||
}
|
||||
},
|
||||
"projects": {
|
||||
"dealplustech": {
|
||||
"id": "prj_def456...",
|
||||
"name": "dealplustech",
|
||||
"updated": "2026-03-02T10:45:00Z"
|
||||
}
|
||||
},
|
||||
"deployments": [
|
||||
{
|
||||
"service": "dealplustech-astro",
|
||||
"timestamp": "2026-03-02T10:45:00Z",
|
||||
"status": "success"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Why State Matters
|
||||
|
||||
With stored IDs, you can:
|
||||
|
||||
1. **Update without looking up IDs**
|
||||
```bash
|
||||
./deploy.sh update # Uses stored service ID
|
||||
```
|
||||
|
||||
2. **Check status instantly**
|
||||
```bash
|
||||
./deploy.sh status # Uses stored service ID
|
||||
```
|
||||
|
||||
3. **Manage multiple apps**
|
||||
```bash
|
||||
# Each app has its own stored ID
|
||||
./deploy.sh status # Current project
|
||||
```
|
||||
|
||||
4. **Resume after session ends**
|
||||
- IDs persist across terminal sessions
|
||||
- No need to re-lookup service IDs
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Deployment Workflow
|
||||
|
||||
### First Deploy
|
||||
|
||||
```bash
|
||||
# 1. Build and deploy
|
||||
./deploy.sh deploy
|
||||
|
||||
# What happens:
|
||||
# 1. Builds Docker image
|
||||
# 2. Gets/creates project (saves project ID)
|
||||
# 3. Creates service (saves service ID)
|
||||
# 4. Triggers deployment
|
||||
# 5. Saves all IDs to state.json
|
||||
```
|
||||
|
||||
### Subsequent Updates
|
||||
|
||||
```bash
|
||||
# 1. Update code
|
||||
git pull
|
||||
|
||||
# 2. Rebuild and redeploy
|
||||
./deploy.sh update
|
||||
|
||||
# What happens:
|
||||
# 1. Reads service ID from state.json
|
||||
# 2. Rebuilds Docker image
|
||||
# 3. Triggers redeployment
|
||||
# 4. Updates deployment history
|
||||
```
|
||||
|
||||
### Check Status Anytime
|
||||
|
||||
```bash
|
||||
# Quick status check
|
||||
./deploy.sh status
|
||||
|
||||
# View recent logs
|
||||
./deploy.sh logs -n 50
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
### Project Config (`easypanel.config.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"easypanel": {
|
||||
"url": "http://110.164.146.46:3000",
|
||||
"project": "dealplustech",
|
||||
"app": {
|
||||
"name": "dealplustech-astro",
|
||||
"port": 4321,
|
||||
"image": "dealplustech-astro:latest"
|
||||
},
|
||||
"env": {
|
||||
"NODE_ENV": "production",
|
||||
"PORT": "4321",
|
||||
"HOST": "0.0.0.0"
|
||||
},
|
||||
"resources": {
|
||||
"cpu": "0.5",
|
||||
"memory": "512M",
|
||||
"storage": "1G"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Credentials (`~/.easypanel/credentials`)
|
||||
|
||||
```bash
|
||||
EASYPANEL_URL=http://110.164.146.46:3000
|
||||
EASYPANEL_API_TOKEN=ep_live_abc123...
|
||||
EASYPANEL_DEFAULT_PROJECT=dealplustech
|
||||
```
|
||||
|
||||
### State (`~/.easypanel/state.json`)
|
||||
|
||||
Auto-generated on first deploy. Stores:
|
||||
- Project IDs
|
||||
- Service IDs
|
||||
- Deployment history
|
||||
- Configuration
|
||||
|
||||
---
|
||||
|
||||
## 📋 Usage Examples
|
||||
|
||||
### Example 1: Deploy New Project
|
||||
|
||||
```bash
|
||||
cd my-astro-project
|
||||
|
||||
# Deploy
|
||||
./skills/easypanel-deploy/deploy.sh deploy
|
||||
|
||||
# Check status
|
||||
./skills/easypanel-deploy/deploy.sh status
|
||||
```
|
||||
|
||||
### Example 2: Update Existing Project
|
||||
|
||||
```bash
|
||||
cd my-astro-project
|
||||
|
||||
# Make code changes
|
||||
git pull
|
||||
|
||||
# Update deployment
|
||||
./skills/easypanel-deploy/deploy.sh update
|
||||
|
||||
# Watch logs
|
||||
./skills/easypanel-deploy/deploy.sh logs -n 100 -f
|
||||
```
|
||||
|
||||
### Example 3: Manage Multiple Apps
|
||||
|
||||
```bash
|
||||
# Deploy app 1
|
||||
cd app1
|
||||
./deploy.sh deploy
|
||||
|
||||
# Deploy app 2
|
||||
cd ../app2
|
||||
./deploy.sh deploy
|
||||
|
||||
# Check status of current app
|
||||
./deploy.sh status
|
||||
|
||||
# List all projects
|
||||
./deploy.sh list
|
||||
```
|
||||
|
||||
### Example 4: Quick Status Check
|
||||
|
||||
```bash
|
||||
# Anytime, anywhere (in project directory)
|
||||
./deploy.sh status
|
||||
|
||||
# Output shows:
|
||||
# - Running status
|
||||
# - Resource usage
|
||||
# - Deployment URL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Service Not Found in State
|
||||
|
||||
**Problem:** `Service not found in state`
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Run deploy first to create service and save ID
|
||||
./deploy.sh deploy
|
||||
|
||||
# Or manually add to state.json
|
||||
nano ~/.easypanel/state.json
|
||||
```
|
||||
|
||||
### Project Not Found
|
||||
|
||||
**Problem:** `Project not found`
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Create project in Easypanel dashboard first
|
||||
# Then run deploy again
|
||||
./deploy.sh deploy
|
||||
```
|
||||
|
||||
### API Call Failed
|
||||
|
||||
**Problem:** `API call failed (HTTP 401)`
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Check token
|
||||
cat ~/.easypanel/credentials
|
||||
|
||||
# Regenerate token if needed
|
||||
# Update credentials file
|
||||
nano ~/.easypanel/credentials
|
||||
```
|
||||
|
||||
### State File Corrupted
|
||||
|
||||
**Problem:** JSON errors in state.json
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Backup and recreate
|
||||
cp ~/.easypanel/state.json ~/.easypanel/state.json.bak
|
||||
rm ~/.easypanel/state.json
|
||||
|
||||
# Next deploy will recreate
|
||||
./deploy.sh deploy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Deployment History
|
||||
|
||||
View deployment history:
|
||||
|
||||
```bash
|
||||
cat ~/.easypanel/state.json | python3 -m json.tool
|
||||
```
|
||||
|
||||
Shows:
|
||||
- All deployed services
|
||||
- Project associations
|
||||
- Last update timestamps
|
||||
- Deployment success/failure
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security
|
||||
|
||||
### State File Security
|
||||
|
||||
```bash
|
||||
# Set secure permissions
|
||||
chmod 600 ~/.easypanel/state.json
|
||||
|
||||
# Never commit to Git
|
||||
echo ".easypanel/" >> .gitignore
|
||||
```
|
||||
|
||||
### What's Safe to Share
|
||||
|
||||
- ✅ Service names
|
||||
- ✅ Project names
|
||||
- ✅ Port numbers
|
||||
- ✅ Image names
|
||||
|
||||
### What's NOT Safe to Share
|
||||
|
||||
- ❌ API token
|
||||
- ❌ Service IDs (can be used to manipulate)
|
||||
- ❌ Project IDs
|
||||
- ❌ Deployment URLs with tokens
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Advanced Usage
|
||||
|
||||
### Manual State Edit
|
||||
|
||||
```bash
|
||||
# Edit state manually
|
||||
nano ~/.easypanel/state.json
|
||||
|
||||
# Add service
|
||||
{
|
||||
"services": {
|
||||
"my-app": {
|
||||
"id": "svc_abc123...",
|
||||
"project_id": "prj_def456...",
|
||||
"name": "my-app",
|
||||
"port": 3000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Backup State
|
||||
|
||||
```bash
|
||||
# Backup before major changes
|
||||
cp ~/.easypanel/state.json ~/.easypanel/state.json.backup
|
||||
|
||||
# Restore if needed
|
||||
cp ~/.easypanel/state.json.backup ~/.easypanel/state.json
|
||||
```
|
||||
|
||||
### Migrate to New Machine
|
||||
|
||||
```bash
|
||||
# Copy state and credentials
|
||||
scp ~/.easypanel/* user@new-machine:~/.easypanel/
|
||||
|
||||
# Verify on new machine
|
||||
./deploy.sh status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Version:** 2.0.0
|
||||
**Status:** ✅ Production Ready
|
||||
**State Management:** ✅ Enabled
|
||||
**Last Updated:** 2026-03-02
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Automatic Service Creation
|
||||
|
||||
**Status:** Semi-automated (80% automated)
|
||||
|
||||
Easypanel's API requires initial service creation via dashboard. After that, everything is automated!
|
||||
|
||||
### Initial Setup (One-time, 2 minutes):
|
||||
|
||||
```bash
|
||||
# 1. Build and prepare
|
||||
./deploy.sh deploy
|
||||
|
||||
# 2. Create service in Easypanel dashboard
|
||||
# - Open: http://110.164.146.46:3000
|
||||
# - Project → New Service → Docker image
|
||||
# - Copy service ID
|
||||
|
||||
# 3. Register service ID
|
||||
./deploy.sh register svc_abc123...
|
||||
```
|
||||
|
||||
### After Registration (100% Automated):
|
||||
|
||||
```bash
|
||||
# Update deployment
|
||||
./deploy.sh update
|
||||
|
||||
# Check status
|
||||
./deploy.sh status
|
||||
|
||||
# List all services
|
||||
./deploy.sh list
|
||||
```
|
||||
|
||||
**Why this approach?**
|
||||
- Easypanel API has complex service creation schema
|
||||
- One-time manual step (2 minutes)
|
||||
- Fully automated thereafter
|
||||
- Production-ready now
|
||||
|
||||
See `AUTOMATIC_DEPLOYMENT.md` for detailed explanation.
|
||||
@@ -1,94 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
CREDENTIALS_FILE="$HOME/.easypanel/credentials"
|
||||
if [ -f "$CREDENTIALS_FILE" ]; then
|
||||
export $(grep -v '^#' "$CREDENTIALS_FILE" | xargs) 2>/dev/null || true
|
||||
fi
|
||||
|
||||
EASYPANEL_HOST="110.164.146.46"
|
||||
EASYPANEL_URL="http://${EASYPANEL_HOST}:3000"
|
||||
GITEA_REPO_URL="https://git.moreminimore.com/kunthawat/dealplustech.git"
|
||||
PROJECT_NAME="customerwebsite"
|
||||
GITEA_BRANCH="main"
|
||||
TIMESTAMP=$(date +%s)
|
||||
APP_NAME="dealplustech-${TIMESTAMP}"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}ℹ️ $1${NC}"; }
|
||||
log_success() { echo -e "${GREEN}✅ $1${NC}"; }
|
||||
log_error() { echo -e "${RED}❌ $1${NC}"; }
|
||||
|
||||
[ -z "$EASYPANEL_API_TOKEN" ] && { log_error "Token not set"; exit 1; }
|
||||
|
||||
echo "========================================"
|
||||
echo "🚀 Deploying $APP_NAME"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# Step 1
|
||||
log_info "Step 1/5: Creating service..."
|
||||
result=$(curl -s -X POST "${EASYPANEL_URL}/api/trpc/services.app.createService" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $EASYPANEL_API_TOKEN" \
|
||||
-d "{\"json\":{\"projectName\":\"$PROJECT_NAME\",\"domains\":[{\"host\":\"\$(EASYPANEL_DOMAIN)\"}],\"serviceName\":\"$APP_NAME\"}}" \
|
||||
--insecure)
|
||||
|
||||
if echo "$result" | grep -q '"error"'; then
|
||||
log_error "Failed: $result"
|
||||
exit 1
|
||||
fi
|
||||
log_success "✅ Service created: $APP_NAME"
|
||||
|
||||
# Step 2
|
||||
log_info "Step 2/5: Configuring Git..."
|
||||
log_info "Repository: $GITEA_REPO_URL"
|
||||
result=$(curl -s -X POST "${EASYPANEL_URL}/api/trpc/services.app.updateSourceGit" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $EASYPANEL_API_TOKEN" \
|
||||
-d "{\"json\":{\"projectName\":\"$PROJECT_NAME\",\"serviceName\":\"$APP_NAME\",\"repo\":\"$GITEA_REPO_URL\",\"ref\":\"$GITEA_BRANCH\",\"path\":\"/\"}}" \
|
||||
--insecure)
|
||||
|
||||
if echo "$result" | grep -q '"error"'; then
|
||||
log_error "Failed: $result"
|
||||
exit 1
|
||||
fi
|
||||
log_success "✅ Git configured"
|
||||
|
||||
# Step 3
|
||||
log_info "Step 3/5: Setting build type..."
|
||||
result=$(curl -s -X POST "${EASYPANEL_URL}/api/trpc/services.app.updateBuild" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $EASYPANEL_API_TOKEN" \
|
||||
-d "{\"json\":{\"projectName\":\"$PROJECT_NAME\",\"serviceName\":\"$APP_NAME\",\"build\":{\"type\":\"nixpacks\"}}}" \
|
||||
--insecure)
|
||||
log_success "✅ Build type set (nixpacks)"
|
||||
|
||||
# Step 4
|
||||
log_info "Step 4/5: Getting domain..."
|
||||
result=$(curl -s "${EASYPANEL_URL}/api/trpc/domains.getPrimaryDomain?input=%7B%22json%22%3A%7B%22projectName%22%3A%22$PROJECT_NAME%22%2C%22serviceName%22%3A%22$APP_NAME%22%7D%7D" \
|
||||
-H "Authorization: Bearer $EASYPANEL_API_TOKEN" --insecure)
|
||||
domain=$(echo "$result" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('result',{}).get('data',{}).get('domain',{}).get('host','pending'))" 2>/dev/null || echo "pending")
|
||||
log_success "✅ Domain: $domain"
|
||||
|
||||
# Step 5
|
||||
log_info "Step 5/5: Waiting for deployment..."
|
||||
for i in 1 2 3 4 5 6 7 8 9 10; do
|
||||
sleep 10
|
||||
result=$(curl -s "${EASYPANEL_URL}/api/trpc/services.app.inspectService?input=%7B%22json%22%3A%7B%22projectName%22%3A%22$PROJECT_NAME%22%2C%22serviceName%22%3A%22$APP_NAME%22%7D%7D" \
|
||||
-H "Authorization: Bearer $EASYPANEL_API_TOKEN" --insecure)
|
||||
status=$(echo "$result" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('result',{}).get('data',{}).get('status','building'))" 2>/dev/null || echo "building")
|
||||
log_info " Status: $status (attempt $i/10)"
|
||||
[ "$status" = "running" ] || [ "$status" = "ready" ] && break
|
||||
done
|
||||
|
||||
log_success "✅ Deployment complete!"
|
||||
log_info ""
|
||||
log_info "Service: $APP_NAME"
|
||||
log_info "URL: http://$domain"
|
||||
log_info ""
|
||||
log_info "To redeploy: ./deploy.sh redeploy $APP_NAME"
|
||||
@@ -1,280 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Easypanel Deployment Skill v2.2
|
||||
# Production-ready with error handling
|
||||
|
||||
set -e
|
||||
|
||||
CREDENTIALS_FILE="$HOME/.easypanel/credentials"
|
||||
STATE_FILE="$HOME/.easypanel/state.json"
|
||||
CONFIG_FILE="easypanel.config.json"
|
||||
|
||||
# Load credentials
|
||||
if [ -f "$CREDENTIALS_FILE" ]; then
|
||||
export $(grep -v '^#' "$CREDENTIALS_FILE" | xargs) 2>/dev/null || true
|
||||
fi
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}ℹ️ $1${NC}"; }
|
||||
log_success() { echo -e "${GREEN}✅ $1${NC}"; }
|
||||
log_warning() { echo -e "${YELLOW}⚠️ $1${NC}"; }
|
||||
log_error() { echo -e "${RED}❌ $1${NC}"; }
|
||||
|
||||
# Check token
|
||||
if [ -z "$EASYPANEL_API_TOKEN" ] || [ "$EASYPANEL_API_TOKEN" = "YOUR_API_TOKEN_HERE" ]; then
|
||||
log_error "API token not set!"
|
||||
echo ""
|
||||
echo "Edit ~/.easypanel/credentials and add your token"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Load config
|
||||
load_config() {
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
APP_NAME=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['easypanel']['app']['name'])" 2>/dev/null || echo "")
|
||||
PORT=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['easypanel']['app']['port'])" 2>/dev/null || echo "4321")
|
||||
DOCKER_IMAGE=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['easypanel']['app']['image'])" 2>/dev/null || echo "")
|
||||
PROJECT_NAME=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['easypanel']['project'])" 2>/dev/null || echo "default")
|
||||
fi
|
||||
APP_NAME="${APP_NAME:-$(basename "$(pwd)")}"
|
||||
PORT="${PORT:-4321}"
|
||||
DOCKER_IMAGE="${DOCKER_IMAGE:-$APP_NAME:latest}"
|
||||
PROJECT_NAME="${PROJECT_NAME:-default}"
|
||||
}
|
||||
|
||||
# Save state
|
||||
save_state() {
|
||||
python3 << PYEOF
|
||||
import json, os
|
||||
from datetime import datetime
|
||||
|
||||
state_file = "$STATE_FILE"
|
||||
os.makedirs(os.path.dirname(state_file), exist_ok=True)
|
||||
|
||||
if os.path.exists(state_file):
|
||||
with open(state_file, 'r') as f:
|
||||
state = json.load(f)
|
||||
else:
|
||||
state = {"version":"2.0","projects":{},"services":{},"deployments":[]}
|
||||
|
||||
if "$1" == "service":
|
||||
state['services']['$2'] = {'id':'$3','project_id':'$PROJECT_ID','name':'$APP_NAME','port':$PORT,'updated':datetime.utcnow().isoformat()+"Z"}
|
||||
elif "$1" == "project":
|
||||
state['projects']['$2'] = {'id':'$3','name':'$2','updated':datetime.utcnow().isoformat()+"Z"}
|
||||
|
||||
with open(state_file, 'w') as f:
|
||||
json.dump(state, f, indent=2)
|
||||
|
||||
print(f"Saved: {$1}.$2 = $3")
|
||||
PYEOF
|
||||
}
|
||||
|
||||
# Get state
|
||||
get_state() {
|
||||
python3 << PYEOF 2>/dev/null || echo ""
|
||||
import json
|
||||
try:
|
||||
with open('$STATE_FILE', 'r') as f:
|
||||
state = json.load(f)
|
||||
key, type = '$1', '$2'
|
||||
if type == 'service' and key in state.get('services', {}):
|
||||
print(state['services'][key].get('id', ''))
|
||||
elif type == 'project' and key in state.get('projects', {}):
|
||||
print(state['projects'][key].get('id', ''))
|
||||
except:
|
||||
pass
|
||||
PYEOF
|
||||
}
|
||||
|
||||
# List projects
|
||||
list_projects() {
|
||||
log_info "Fetching projects from Easypanel..."
|
||||
|
||||
local response=$(curl -s -w "\n%{http_code}" \
|
||||
"http://110.164.146.46:3000/api/trpc/projects.listProjects" \
|
||||
-H "Authorization: Bearer $EASYPANEL_API_TOKEN" \
|
||||
--insecure --compressed)
|
||||
|
||||
local http_code=$(echo "$response" | tail -1)
|
||||
local body=$(echo "$response" | sed '$d')
|
||||
|
||||
if [ "$http_code" != "200" ]; then
|
||||
log_error "Failed to fetch projects (HTTP $http_code)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$body" | python3 << 'PYEOF'
|
||||
import json, sys
|
||||
|
||||
try:
|
||||
data = json.load(sys.stdin)
|
||||
result = data.get('result', {})
|
||||
data_content = result.get('data', {}) if isinstance(result, dict) else {}
|
||||
items = data_content.get('items', [])
|
||||
|
||||
if not items:
|
||||
print("No projects found")
|
||||
else:
|
||||
print(f"\n{'ID':<25} {'Name':<30}")
|
||||
print("-" * 60)
|
||||
for proj in items:
|
||||
pid = str(proj.get('id', ''))[:23]
|
||||
name = str(proj.get('name', ''))[:28]
|
||||
print(f"{pid:<25} {name:<30}")
|
||||
print("-" * 60)
|
||||
print(f"Total: {len(items)} project(s)\n")
|
||||
except Exception as e:
|
||||
print(f"Error parsing response: {e}")
|
||||
print("Raw response:", sys.stdin.read()[:200])
|
||||
PYEOF
|
||||
}
|
||||
|
||||
# Deploy command
|
||||
cmd_deploy() {
|
||||
load_config
|
||||
|
||||
log_info "========================================"
|
||||
log_info "Deploying $APP_NAME"
|
||||
log_info "========================================"
|
||||
log_info "Project: $PROJECT_NAME"
|
||||
log_info "Image: $DOCKER_IMAGE"
|
||||
log_info "Port: $PORT"
|
||||
log_info ""
|
||||
|
||||
# Build Docker
|
||||
log_info "Building Docker image..."
|
||||
if [ ! "$SKIP_BUILD" = "true" ]; then
|
||||
docker build -t "$DOCKER_IMAGE" . || {
|
||||
log_error "Docker build failed"
|
||||
exit 1
|
||||
}
|
||||
log_success "Built: $DOCKER_IMAGE"
|
||||
fi
|
||||
|
||||
# List projects and find/create
|
||||
log_info "Looking for project: $PROJECT_NAME"
|
||||
list_projects
|
||||
|
||||
log_warning ""
|
||||
log_warning "⚠️ Manual Step Required"
|
||||
log_warning ""
|
||||
log_info "Easypanel API requires service creation via dashboard"
|
||||
log_info ""
|
||||
log_info "Steps:"
|
||||
log_info "1. Open: $EASYPANEL_URL"
|
||||
log_info "2. Select project: $PROJECT_NAME (or create it)"
|
||||
log_info "3. Click 'New Service' → 'Docker image'"
|
||||
log_info "4. Enter:"
|
||||
log_info " Name: $APP_NAME"
|
||||
log_info " Image: $DOCKER_IMAGE"
|
||||
log_info " Port: $PORT"
|
||||
log_info "5. Deploy"
|
||||
log_info ""
|
||||
log_info "After deployment, save the service ID:"
|
||||
log_info "./deploy.sh register SERVICE_ID"
|
||||
log_info ""
|
||||
|
||||
# Save deployment attempt
|
||||
save_state "last_attempt" "$APP_NAME" "deployment"
|
||||
}
|
||||
|
||||
# Register service
|
||||
cmd_register() {
|
||||
local service_id="$1"
|
||||
if [ -z "$service_id" ]; then
|
||||
log_error "Service ID required"
|
||||
log_info "Usage: ./deploy.sh register SERVICE_ID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
load_config
|
||||
save_state "service" "$APP_NAME" "$service_id"
|
||||
|
||||
log_success "✅ Service registered!"
|
||||
log_info "Service ID: $service_id"
|
||||
log_info "Next update: ./deploy.sh update"
|
||||
}
|
||||
|
||||
# Update service
|
||||
cmd_update() {
|
||||
load_config
|
||||
|
||||
SERVICE_ID=$(get_state "$APP_NAME" "service")
|
||||
|
||||
if [ -z "$SERVICE_ID" ]; then
|
||||
log_error "Service not registered"
|
||||
log_info "Run: ./deploy.sh deploy"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Updating: $APP_NAME ($SERVICE_ID)"
|
||||
|
||||
# Rebuild
|
||||
if [ ! "$SKIP_BUILD" = "true" ]; then
|
||||
docker build -t "$DOCKER_IMAGE" . || exit 1
|
||||
log_success "Rebuilt: $DOCKER_IMAGE"
|
||||
fi
|
||||
|
||||
log_info "✅ Image ready"
|
||||
log_info ""
|
||||
log_info "To deploy update:"
|
||||
log_info "1. Go to $EASYPANEL_URL"
|
||||
log_info "2. Select service: $APP_NAME"
|
||||
log_info "3. Click 'Deploy' to pull new image"
|
||||
}
|
||||
|
||||
# Status
|
||||
cmd_status() {
|
||||
load_config
|
||||
SERVICE_ID=$(get_state "$APP_NAME" "service")
|
||||
|
||||
if [ -z "$SERVICE_ID" ]; then
|
||||
log_error "Not deployed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Service: $APP_NAME"
|
||||
log_info "ID: $SERVICE_ID"
|
||||
log_info "Image: $DOCKER_IMAGE"
|
||||
log_info "Port: $PORT"
|
||||
}
|
||||
|
||||
# Help
|
||||
cmd_help() {
|
||||
cat << 'EOF'
|
||||
Easypanel Deployment Skill v2.2
|
||||
|
||||
Usage: ./deploy.sh [command]
|
||||
|
||||
Commands:
|
||||
deploy Build and prepare deployment
|
||||
register ID Register service with ID
|
||||
update Update existing service
|
||||
status Show status
|
||||
list List projects
|
||||
help This help
|
||||
|
||||
Workflow:
|
||||
1. ./deploy.sh deploy
|
||||
2. Create service in Easypanel dashboard
|
||||
3. ./deploy.sh register SERVICE_ID
|
||||
4. ./deploy.sh update (for future updates)
|
||||
EOF
|
||||
}
|
||||
|
||||
SKIP_BUILD="false"
|
||||
[ "$1" = "-b" ] || [ "$1" = "--skip-build" ] && SKIP_BUILD="true"
|
||||
|
||||
case "${1:-deploy}" in
|
||||
deploy) cmd_deploy ;;
|
||||
register) cmd_register "$2" ;;
|
||||
update) cmd_update ;;
|
||||
status) cmd_status ;;
|
||||
list) list_projects ;;
|
||||
help|--help|-h) cmd_help ;;
|
||||
*) cmd_help; exit 1 ;;
|
||||
esac
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user