Pull Project initial implementation
This commit is contained in:
parent
69f45867b2
commit
cc7cdee60b
@ -246,6 +246,8 @@ export const projectApi = {
|
||||
get: (id: string) => api.get<Project>(`/api/v1/projects/${id}`),
|
||||
create: (data: { name: string; slug: string; gitUrl?: string }) =>
|
||||
api.post<Project>("/api/v1/projects", data),
|
||||
pull: (gitUrl: string) =>
|
||||
api.post<Project>("/api/v1/projects/pull", { gitUrl }),
|
||||
update: (
|
||||
id: string,
|
||||
data: Partial<{
|
||||
|
||||
@ -116,6 +116,83 @@ function NewProjectForm({
|
||||
);
|
||||
}
|
||||
|
||||
function PullProjectForm({
|
||||
onCancel,
|
||||
onSuccess,
|
||||
}: {
|
||||
onCancel: () => void;
|
||||
onSuccess: (project: Project) => void;
|
||||
}) {
|
||||
const [gitUrl, setGitUrl] = useState("");
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!gitUrl.trim()) {
|
||||
setError("Git Repository URL is required");
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitting(true);
|
||||
setError("");
|
||||
|
||||
try {
|
||||
const project = await projectApi.pull(gitUrl.trim());
|
||||
onSuccess(project);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to pull project");
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-lg">
|
||||
<h2 className="text-xl font-semibold mb-2">Pull New Project</h2>
|
||||
<p className="text-sm text-text-muted mb-6">
|
||||
Import an existing project from a git repository. The project name and
|
||||
slug will be derived automatically from the repository URL.
|
||||
</p>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm text-text-secondary mb-1">
|
||||
Git Repository URL *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={gitUrl}
|
||||
onChange={(e) => setGitUrl(e.target.value)}
|
||||
className="w-full px-3 py-2 bg-bg-tertiary border border-border-default rounded text-text-primary focus:border-brand focus:outline-none"
|
||||
placeholder="https://github.com/user/repo.git"
|
||||
autoFocus
|
||||
/>
|
||||
<p className="text-xs text-text-muted mt-1">
|
||||
HTTPS or SSH format (e.g., git@github.com:user/repo.git)
|
||||
</p>
|
||||
</div>
|
||||
{error && <p className="text-red-500 text-sm">{error}</p>}
|
||||
<div className="flex gap-3 pt-2">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="px-4 py-2 bg-brand text-white rounded hover:bg-red-700 transition-colors disabled:opacity-50"
|
||||
>
|
||||
{submitting ? "Pulling..." : "Pull Project"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
className="px-4 py-2 border border-border-default text-text-secondary rounded hover:bg-bg-tertiary transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface EditProjectFormProps {
|
||||
project: Project;
|
||||
onCancel: () => void;
|
||||
@ -820,6 +897,7 @@ export default function ProjectManager({ user }: ProjectManagerProps) {
|
||||
);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showNewForm, setShowNewForm] = useState(false);
|
||||
const [showPullForm, setShowPullForm] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadProjects();
|
||||
@ -854,6 +932,12 @@ export default function ProjectManager({ user }: ProjectManagerProps) {
|
||||
loadProjects();
|
||||
};
|
||||
|
||||
const handleProjectPulled = (project: Project) => {
|
||||
setShowPullForm(false);
|
||||
loadProjects();
|
||||
navigate(`/projects/${project.slug}`);
|
||||
};
|
||||
|
||||
const handleProjectDeleted = () => {
|
||||
setSelectedProject(null);
|
||||
loadProjects();
|
||||
@ -908,26 +992,43 @@ export default function ProjectManager({ user }: ProjectManagerProps) {
|
||||
);
|
||||
}
|
||||
|
||||
const showForm = showNewForm || showPullForm;
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex bg-bg-primary overflow-hidden">
|
||||
{/* Left Sidebar - Project List */}
|
||||
<aside className="w-64 border-r border-border-subtle bg-bg-secondary flex flex-col overflow-hidden">
|
||||
<div className="p-3 border-b border-border-subtle flex-shrink-0">
|
||||
<div className="p-3 border-b border-border-subtle flex-shrink-0 space-y-2">
|
||||
<button
|
||||
onClick={() => setShowNewForm(true)}
|
||||
onClick={() => {
|
||||
setShowNewForm(true);
|
||||
setShowPullForm(false);
|
||||
}}
|
||||
className="w-full px-3 py-2 bg-brand text-white rounded hover:bg-red-700 transition-colors text-sm font-medium"
|
||||
>
|
||||
New Project
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowPullForm(true);
|
||||
setShowNewForm(false);
|
||||
}}
|
||||
className="w-full px-3 py-2 border border-border-default text-text-secondary rounded hover:bg-bg-tertiary hover:text-text-primary transition-colors text-sm font-medium"
|
||||
>
|
||||
Pull Project
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto p-2">
|
||||
{showNewForm ? (
|
||||
{showForm ? (
|
||||
<div className="p-2">
|
||||
<p className="text-sm text-text-muted mb-2">
|
||||
Creating new project...
|
||||
{showNewForm ? "Creating new project..." : "Pulling project..."}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setShowNewForm(false)}
|
||||
onClick={() => {
|
||||
setShowNewForm(false);
|
||||
setShowPullForm(false);
|
||||
}}
|
||||
className="text-sm text-text-secondary hover:text-text-primary transition-colors"
|
||||
>
|
||||
← Cancel
|
||||
@ -971,6 +1072,13 @@ export default function ProjectManager({ user }: ProjectManagerProps) {
|
||||
onSuccess={handleProjectCreated}
|
||||
/>
|
||||
</div>
|
||||
) : showPullForm ? (
|
||||
<div className="flex-1 overflow-y-auto p-6">
|
||||
<PullProjectForm
|
||||
onCancel={() => setShowPullForm(false)}
|
||||
onSuccess={handleProjectPulled}
|
||||
/>
|
||||
</div>
|
||||
) : selectedProject ? (
|
||||
<>
|
||||
{/* Center - Project Inspector */}
|
||||
|
||||
@ -28,6 +28,7 @@ export class ProjectApiControllerV1 extends DtpController {
|
||||
|
||||
this.router.get("/", this.getProjects.bind(this));
|
||||
this.router.post("/", this.createProject.bind(this));
|
||||
this.router.post("/pull", this.pullProject.bind(this));
|
||||
this.router.get("/:projectId", this.getProject.bind(this));
|
||||
this.router.put("/:projectId", this.updateProject.bind(this));
|
||||
this.router.delete("/:projectId", this.deleteProject.bind(this));
|
||||
@ -81,6 +82,65 @@ export class ProjectApiControllerV1 extends DtpController {
|
||||
}
|
||||
}
|
||||
|
||||
async pullProject(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { gitUrl } = req.body;
|
||||
|
||||
if (!gitUrl || typeof gitUrl !== "string") {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "gitUrl is required",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate the URL is parseable
|
||||
const trimmedUrl = gitUrl.trim();
|
||||
const isSshFormat = trimmedUrl.includes(":") && !trimmedUrl.startsWith("http") && !trimmedUrl.startsWith("ssh://");
|
||||
if (!isSshFormat) {
|
||||
try {
|
||||
new URL(trimmedUrl);
|
||||
} catch {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "invalid git URL format",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const project = await projectService.createFromGitUrl(req.user, trimmedUrl);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: project,
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.statusCode === 400) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle duplicate gitUrl (unique partial index violation)
|
||||
if (error.code === 11000) {
|
||||
res.status(409).json({
|
||||
success: false,
|
||||
message: "a project with this git URL already exists",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.log.error("failed to pull project", { error });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "failed to pull project",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getProject(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const id = req.params.projectId as string;
|
||||
|
||||
@ -47,6 +47,69 @@ class ProjectService extends DtpService {
|
||||
this.log.info("service stopped");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a git URL to extract the repository name.
|
||||
* Handles common formats:
|
||||
* https://github.com/user/repo.git
|
||||
* https://github.com/user/repo
|
||||
* git@github.com:user/repo.git
|
||||
* ssh://git@github.com/user/repo.git
|
||||
*/
|
||||
parseGitUrl(gitUrl: string): { name: string; slug: string } {
|
||||
const url = gitUrl.trim();
|
||||
let repoName: string;
|
||||
|
||||
// SSH format: git@host:owner/repo.git
|
||||
if (url.includes(":") && !url.startsWith("http") && !url.startsWith("ssh://")) {
|
||||
const parts = url.split(":").pop()!;
|
||||
repoName = parts.split("/").pop()!;
|
||||
} else {
|
||||
// HTTP(S) or ssh:// format
|
||||
const parsed = new URL(url);
|
||||
const segments = parsed.pathname.split("/").filter(Boolean);
|
||||
repoName = segments[segments.length - 1] || "";
|
||||
}
|
||||
|
||||
// Strip .git suffix
|
||||
repoName = repoName.replace(/\.git$/, "");
|
||||
|
||||
return {
|
||||
name: repoName,
|
||||
slug: slug(repoName),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a project from a git URL by parsing the URL to derive
|
||||
* the project name and slug automatically. Handles slug collisions
|
||||
* by appending a numeric suffix (e.g., my-repo-2).
|
||||
*/
|
||||
async createFromGitUrl(user: IUser, gitUrl: string): Promise<IProject> {
|
||||
const { name, slug: baseSlug } = this.parseGitUrl(gitUrl);
|
||||
|
||||
if (!name) {
|
||||
const error = new Error("could not derive project name from git URL");
|
||||
error.statusCode = 400;
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Resolve slug collisions by appending a numeric suffix
|
||||
let projectSlug = baseSlug;
|
||||
let suffix = 2;
|
||||
while (await Project.findOne({ slug: projectSlug, user: user._id })) {
|
||||
projectSlug = `${baseSlug}-${suffix}`;
|
||||
suffix++;
|
||||
}
|
||||
|
||||
this.log.info("creating project from git URL", { name, slug: projectSlug, gitUrl });
|
||||
|
||||
return this.create(user, {
|
||||
name,
|
||||
slug: projectSlug,
|
||||
gitUrl,
|
||||
});
|
||||
}
|
||||
|
||||
async create(user: IUser, definition: IProjectDefinition): Promise<IProject> {
|
||||
const NOW = new Date();
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
@ -23,6 +23,23 @@ describe('Project API Endpoints', () => {
|
||||
expect(content).toContain('createProject');
|
||||
});
|
||||
|
||||
it('should have POST /pull route', () => {
|
||||
const controllerPath = path.join(ROOT_DIR, 'src', 'controllers', 'api', 'v1', 'project.ts');
|
||||
const content = fs.readFileSync(controllerPath, 'utf-8');
|
||||
expect(content).toContain('pullProject');
|
||||
expect(content).toContain('/pull');
|
||||
});
|
||||
|
||||
it('should register /pull route before /:projectId', () => {
|
||||
const controllerPath = path.join(ROOT_DIR, 'src', 'controllers', 'api', 'v1', 'project.ts');
|
||||
const content = fs.readFileSync(controllerPath, 'utf-8');
|
||||
const pullIndex = content.indexOf('"/pull"');
|
||||
const paramIndex = content.indexOf('"/:projectId"');
|
||||
expect(pullIndex).toBeGreaterThan(0);
|
||||
expect(paramIndex).toBeGreaterThan(0);
|
||||
expect(pullIndex).toBeLessThan(paramIndex);
|
||||
});
|
||||
|
||||
it('should use requireUser middleware', () => {
|
||||
const controllerPath = path.join(ROOT_DIR, 'src', 'controllers', 'api', 'v1', 'project.ts');
|
||||
const content = fs.readFileSync(controllerPath, 'utf-8');
|
||||
@ -55,6 +72,18 @@ describe('Project API Endpoints', () => {
|
||||
expect(content).toContain('getForUser');
|
||||
});
|
||||
|
||||
it('should have createFromGitUrl method', () => {
|
||||
const servicePath = path.join(ROOT_DIR, 'src', 'services', 'project.ts');
|
||||
const content = fs.readFileSync(servicePath, 'utf-8');
|
||||
expect(content).toContain('createFromGitUrl');
|
||||
});
|
||||
|
||||
it('should have parseGitUrl method', () => {
|
||||
const servicePath = path.join(ROOT_DIR, 'src', 'services', 'project.ts');
|
||||
const content = fs.readFileSync(servicePath, 'utf-8');
|
||||
expect(content).toContain('parseGitUrl');
|
||||
});
|
||||
|
||||
it('should populate user with password excluded', () => {
|
||||
const servicePath = path.join(ROOT_DIR, 'src', 'services', 'project.ts');
|
||||
const content = fs.readFileSync(servicePath, 'utf-8');
|
||||
@ -62,6 +91,60 @@ describe('Project API Endpoints', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Git URL Parsing', () => {
|
||||
// We import the service to test parseGitUrl directly
|
||||
let projectService: any;
|
||||
|
||||
beforeAll(async () => {
|
||||
const mod = await import(path.join(ROOT_DIR, 'src', 'services', 'project.ts'));
|
||||
projectService = mod.default;
|
||||
});
|
||||
|
||||
it('should parse HTTPS URL with .git suffix', () => {
|
||||
const result = projectService.parseGitUrl('https://github.com/user/my-repo.git');
|
||||
expect(result.name).toBe('my-repo');
|
||||
expect(result.slug).toBe('my-repo');
|
||||
});
|
||||
|
||||
it('should parse HTTPS URL without .git suffix', () => {
|
||||
const result = projectService.parseGitUrl('https://github.com/user/my-repo');
|
||||
expect(result.name).toBe('my-repo');
|
||||
expect(result.slug).toBe('my-repo');
|
||||
});
|
||||
|
||||
it('should parse SSH format URL', () => {
|
||||
const result = projectService.parseGitUrl('git@github.com:user/my-repo.git');
|
||||
expect(result.name).toBe('my-repo');
|
||||
expect(result.slug).toBe('my-repo');
|
||||
});
|
||||
|
||||
it('should parse SSH URL with ssh:// prefix', () => {
|
||||
const result = projectService.parseGitUrl('ssh://git@github.com/user/my-repo.git');
|
||||
expect(result.name).toBe('my-repo');
|
||||
expect(result.slug).toBe('my-repo');
|
||||
});
|
||||
|
||||
it('should handle repo names with special characters', () => {
|
||||
const result = projectService.parseGitUrl('https://github.com/user/my_cool_repo.git');
|
||||
expect(result.name).toBe('my_cool_repo');
|
||||
// slug() removes underscores entirely
|
||||
expect(result.slug).toBe('mycoolrepo');
|
||||
});
|
||||
|
||||
it('should handle repo names with mixed case', () => {
|
||||
const result = projectService.parseGitUrl('https://github.com/user/MyRepo.git');
|
||||
expect(result.name).toBe('MyRepo');
|
||||
// slug() lowercases
|
||||
expect(result.slug).toBe('myrepo');
|
||||
});
|
||||
|
||||
it('should trim whitespace from URL', () => {
|
||||
const result = projectService.parseGitUrl(' https://github.com/user/my-repo.git ');
|
||||
expect(result.name).toBe('my-repo');
|
||||
expect(result.slug).toBe('my-repo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Frontend API Client', () => {
|
||||
it('should add Authorization header with token', () => {
|
||||
const apiPath = path.join(ROOT_DIR, 'frontend', 'src', 'lib', 'api.ts');
|
||||
@ -81,6 +164,39 @@ describe('Project API Endpoints', () => {
|
||||
const content = fs.readFileSync(apiPath, 'utf-8');
|
||||
expect(content).toContain('getAll');
|
||||
});
|
||||
|
||||
it('should have pull method for project API', () => {
|
||||
const apiPath = path.join(ROOT_DIR, 'frontend', 'src', 'lib', 'api.ts');
|
||||
const content = fs.readFileSync(apiPath, 'utf-8');
|
||||
expect(content).toContain('pull:');
|
||||
expect(content).toContain('/projects/pull');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pull Project UI', () => {
|
||||
it('should have PullProjectForm component', () => {
|
||||
const uiPath = path.join(ROOT_DIR, 'frontend', 'src', 'pages', 'ProjectManager.tsx');
|
||||
const content = fs.readFileSync(uiPath, 'utf-8');
|
||||
expect(content).toContain('PullProjectForm');
|
||||
});
|
||||
|
||||
it('should have Pull Project button in sidebar', () => {
|
||||
const uiPath = path.join(ROOT_DIR, 'frontend', 'src', 'pages', 'ProjectManager.tsx');
|
||||
const content = fs.readFileSync(uiPath, 'utf-8');
|
||||
expect(content).toContain('Pull Project');
|
||||
});
|
||||
|
||||
it('should have showPullForm state', () => {
|
||||
const uiPath = path.join(ROOT_DIR, 'frontend', 'src', 'pages', 'ProjectManager.tsx');
|
||||
const content = fs.readFileSync(uiPath, 'utf-8');
|
||||
expect(content).toContain('showPullForm');
|
||||
});
|
||||
|
||||
it('should have handleProjectPulled handler', () => {
|
||||
const uiPath = path.join(ROOT_DIR, 'frontend', 'src', 'pages', 'ProjectManager.tsx');
|
||||
const content = fs.readFileSync(uiPath, 'utf-8');
|
||||
expect(content).toContain('handleProjectPulled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('User Interface', () => {
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
"openai": "^6.34.0",
|
||||
"playwright": "1.59.1",
|
||||
"simple-git": "^3.36.0",
|
||||
"simplegit": "^1.0.2",
|
||||
"socket.io-client": "^4.8.3",
|
||||
"turndown": "7.2.2"
|
||||
},
|
||||
|
||||
161
pnpm-lock.yaml
161
pnpm-lock.yaml
@ -121,6 +121,9 @@ importers:
|
||||
serve-favicon:
|
||||
specifier: ^2.5.1
|
||||
version: 2.5.1
|
||||
simplegit:
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2
|
||||
slug:
|
||||
specifier: ^11.0.1
|
||||
version: 11.0.1
|
||||
@ -405,6 +408,9 @@ importers:
|
||||
simple-git:
|
||||
specifier: ^3.36.0
|
||||
version: 3.36.0
|
||||
simplegit:
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2
|
||||
socket.io-client:
|
||||
specifier: ^4.8.3
|
||||
version: 4.8.3
|
||||
@ -1830,6 +1836,10 @@ packages:
|
||||
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
ansi-regex@2.1.1:
|
||||
resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
@ -1880,6 +1890,9 @@ packages:
|
||||
resolution: {integrity: sha512-p4jj6Fws4Iy2m0iCmI2am2ZNZCgbdgE+P8F/8csmn2vx7ixXrO2zGcuNsD46X5uZSVecmkEy/M06X2vG8KD6dQ==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
||||
async@1.5.2:
|
||||
resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==}
|
||||
|
||||
async@2.6.4:
|
||||
resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==}
|
||||
|
||||
@ -2006,6 +2019,10 @@ packages:
|
||||
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
camelcase@2.1.1:
|
||||
resolution: {integrity: sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
camera-controls@3.1.2:
|
||||
resolution: {integrity: sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==}
|
||||
engines: {node: '>=22.0.0', npm: '>=10.5.1'}
|
||||
@ -2041,6 +2058,9 @@ packages:
|
||||
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
cliui@3.2.0:
|
||||
resolution: {integrity: sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==}
|
||||
|
||||
cliui@8.0.1:
|
||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||
engines: {node: '>=12'}
|
||||
@ -2049,6 +2069,10 @@ packages:
|
||||
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
code-point-at@1.1.0:
|
||||
resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
codemirror@6.0.2:
|
||||
resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==}
|
||||
|
||||
@ -2205,6 +2229,10 @@ packages:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
decamelize@1.2.0:
|
||||
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
decimal.js@10.6.0:
|
||||
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
|
||||
|
||||
@ -2661,6 +2689,13 @@ packages:
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
ini@1.3.8:
|
||||
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
|
||||
|
||||
invert-kv@1.0.0:
|
||||
resolution: {integrity: sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
ioredis@5.10.1:
|
||||
resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==}
|
||||
engines: {node: '>=12.22.0'}
|
||||
@ -2692,6 +2727,10 @@ packages:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-fullwidth-code-point@1.0.0:
|
||||
resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-fullwidth-code-point@3.0.0:
|
||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||
engines: {node: '>=8'}
|
||||
@ -2801,6 +2840,10 @@ packages:
|
||||
resolution: {integrity: sha512-Y+CjUfLmIpoUCCRl0ub4smrYtGGr5AOa2AKOaWelGHOGz33X/Y/KizefGqbkwfz44+cnq/+9habclf8vOmu2LA==}
|
||||
engines: {node: '>=0.2.0'}
|
||||
|
||||
lcid@1.0.0:
|
||||
resolution: {integrity: sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
less@4.6.4:
|
||||
resolution: {integrity: sha512-OJmO5+HxZLLw0RLzkqaNHzcgEAQG7C0y3aMbwtCzIUFZsLMNNq/1IdAdHEycQ58CwUO3jPTHmoN+tE5I7FQxNg==}
|
||||
engines: {node: '>=18'}
|
||||
@ -3111,6 +3154,10 @@ packages:
|
||||
engines: {node: ^18 || >=20}
|
||||
hasBin: true
|
||||
|
||||
nconf@0.8.5:
|
||||
resolution: {integrity: sha512-YXTpOk2LI4QD2vAr4v40+nELcEbUA5BkIoeIz4Orfx9XA0Gxkkf70+KOvIVNDP2YQBz5XWg7l1zgiPvWb0zTPw==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
|
||||
needle@3.5.0:
|
||||
resolution: {integrity: sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==}
|
||||
engines: {node: '>= 4.4.x'}
|
||||
@ -3152,6 +3199,10 @@ packages:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
number-is-nan@1.0.1:
|
||||
resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
numeral@2.0.6:
|
||||
resolution: {integrity: sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==}
|
||||
|
||||
@ -3200,6 +3251,10 @@ packages:
|
||||
resolution: {integrity: sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
os-locale@1.4.0:
|
||||
resolution: {integrity: sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
parse-node-version@1.0.1:
|
||||
resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
|
||||
engines: {node: '>= 0.10'}
|
||||
@ -3515,6 +3570,9 @@ packages:
|
||||
scheduler@0.27.0:
|
||||
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
|
||||
|
||||
secure-keys@1.0.0:
|
||||
resolution: {integrity: sha512-nZi59hW3Sl5P3+wOO89eHBAAGwmCPd2aE1+dLZV5MO+ItQctIvAqihzaAXIQhvtH4KJPxM080HsnqltR2y8cWg==}
|
||||
|
||||
semver@5.7.2:
|
||||
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
|
||||
hasBin: true
|
||||
@ -3597,6 +3655,10 @@ packages:
|
||||
simple-git@3.36.0:
|
||||
resolution: {integrity: sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==}
|
||||
|
||||
simplegit@1.0.2:
|
||||
resolution: {integrity: sha512-jEQKokh61NP+oqj7oFpXtd29zOSO8rR1X1Rs11tNtBLxLhrR976zBnRmzR6zZes7MkqhVUtS1QnyiuBxGDLDrg==}
|
||||
hasBin: true
|
||||
|
||||
slash@3.0.0:
|
||||
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
||||
engines: {node: '>=8'}
|
||||
@ -3691,6 +3753,10 @@ packages:
|
||||
resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
string-width@1.0.2:
|
||||
resolution: {integrity: sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
@ -3698,6 +3764,10 @@ packages:
|
||||
string_decoder@1.3.0:
|
||||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||
|
||||
strip-ansi@3.0.1:
|
||||
resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||
engines: {node: '>=8'}
|
||||
@ -4060,10 +4130,19 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
|
||||
window-size@0.1.4:
|
||||
resolution: {integrity: sha512-2thx4pB0cV3h+Bw7QmMXcEbdmOzv9t0HFplJH/Lz6yu60hXYy5RT8rUu+wlIreVxWsGN20mo+MHeCSfUpQBwPw==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
hasBin: true
|
||||
|
||||
with@7.0.2:
|
||||
resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
wrap-ansi@2.1.0:
|
||||
resolution: {integrity: sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||
engines: {node: '>=10'}
|
||||
@ -4102,6 +4181,9 @@ packages:
|
||||
resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
y18n@3.2.2:
|
||||
resolution: {integrity: sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==}
|
||||
|
||||
y18n@5.0.8:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
@ -4114,6 +4196,9 @@ packages:
|
||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yargs@3.32.0:
|
||||
resolution: {integrity: sha512-ONJZiimStfZzhKamYvR/xvmgW3uEkAUFSP91y2caTEPhzF6uP2JfPiVZcq66b/YR0C3uitxSV7+T1x8p5bkmMg==}
|
||||
|
||||
yauzl@2.10.0:
|
||||
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
|
||||
|
||||
@ -5401,6 +5486,8 @@ snapshots:
|
||||
|
||||
agent-base@7.1.4: {}
|
||||
|
||||
ansi-regex@2.1.1: {}
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
@ -5436,6 +5523,8 @@ snapshots:
|
||||
|
||||
async-each-series@0.1.1: {}
|
||||
|
||||
async@1.5.2: {}
|
||||
|
||||
async@2.6.4:
|
||||
dependencies:
|
||||
lodash: 4.18.1
|
||||
@ -5615,6 +5704,8 @@ snapshots:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
get-intrinsic: 1.3.0
|
||||
|
||||
camelcase@2.1.1: {}
|
||||
|
||||
camera-controls@3.1.2(three@0.184.0):
|
||||
dependencies:
|
||||
three: 0.184.0
|
||||
@ -5652,6 +5743,12 @@ snapshots:
|
||||
|
||||
cli-width@4.1.0: {}
|
||||
|
||||
cliui@3.2.0:
|
||||
dependencies:
|
||||
string-width: 1.0.2
|
||||
strip-ansi: 3.0.1
|
||||
wrap-ansi: 2.1.0
|
||||
|
||||
cliui@8.0.1:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
@ -5660,6 +5757,8 @@ snapshots:
|
||||
|
||||
cluster-key-slot@1.1.2: {}
|
||||
|
||||
code-point-at@1.1.0: {}
|
||||
|
||||
codemirror@6.0.2:
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.2
|
||||
@ -5808,6 +5907,8 @@ snapshots:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
decamelize@1.2.0: {}
|
||||
|
||||
decimal.js@10.6.0: {}
|
||||
|
||||
decode-uri-component@0.2.2: {}
|
||||
@ -6368,6 +6469,10 @@ snapshots:
|
||||
|
||||
inherits@2.0.4: {}
|
||||
|
||||
ini@1.3.8: {}
|
||||
|
||||
invert-kv@1.0.0: {}
|
||||
|
||||
ioredis@5.10.1:
|
||||
dependencies:
|
||||
'@ioredis/commands': 1.5.1
|
||||
@ -6407,6 +6512,10 @@ snapshots:
|
||||
|
||||
is-extglob@2.1.1: {}
|
||||
|
||||
is-fullwidth-code-point@1.0.0:
|
||||
dependencies:
|
||||
number-is-nan: 1.0.1
|
||||
|
||||
is-fullwidth-code-point@3.0.0: {}
|
||||
|
||||
is-glob@4.0.3:
|
||||
@ -6552,6 +6661,10 @@ snapshots:
|
||||
|
||||
lazy@1.0.11: {}
|
||||
|
||||
lcid@1.0.0:
|
||||
dependencies:
|
||||
invert-kv: 1.0.0
|
||||
|
||||
less@4.6.4:
|
||||
dependencies:
|
||||
copy-anything: 3.0.5
|
||||
@ -6822,6 +6935,13 @@ snapshots:
|
||||
|
||||
nanoid@5.1.11: {}
|
||||
|
||||
nconf@0.8.5:
|
||||
dependencies:
|
||||
async: 1.5.2
|
||||
ini: 1.3.8
|
||||
secure-keys: 1.0.0
|
||||
yargs: 3.32.0
|
||||
|
||||
needle@3.5.0:
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
@ -6853,6 +6973,8 @@ snapshots:
|
||||
|
||||
normalize-path@3.0.0: {}
|
||||
|
||||
number-is-nan@1.0.1: {}
|
||||
|
||||
numeral@2.0.6: {}
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
@ -6887,6 +7009,10 @@ snapshots:
|
||||
dependencies:
|
||||
is-wsl: 1.1.0
|
||||
|
||||
os-locale@1.4.0:
|
||||
dependencies:
|
||||
lcid: 1.0.0
|
||||
|
||||
parse-node-version@1.0.1: {}
|
||||
|
||||
parse5@7.3.0:
|
||||
@ -7216,6 +7342,8 @@ snapshots:
|
||||
|
||||
scheduler@0.27.0: {}
|
||||
|
||||
secure-keys@1.0.0: {}
|
||||
|
||||
semver@5.7.2:
|
||||
optional: true
|
||||
|
||||
@ -7353,6 +7481,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
simplegit@1.0.2:
|
||||
dependencies:
|
||||
nconf: 0.8.5
|
||||
|
||||
slash@3.0.0: {}
|
||||
|
||||
slug@11.0.1: {}
|
||||
@ -7449,6 +7581,12 @@ snapshots:
|
||||
|
||||
strict-uri-encode@2.0.0: {}
|
||||
|
||||
string-width@1.0.2:
|
||||
dependencies:
|
||||
code-point-at: 1.1.0
|
||||
is-fullwidth-code-point: 1.0.0
|
||||
strip-ansi: 3.0.1
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
@ -7459,6 +7597,10 @@ snapshots:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
strip-ansi@3.0.1:
|
||||
dependencies:
|
||||
ansi-regex: 2.1.1
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
@ -7810,6 +7952,8 @@ snapshots:
|
||||
siginfo: 2.0.0
|
||||
stackback: 0.0.2
|
||||
|
||||
window-size@0.1.4: {}
|
||||
|
||||
with@7.0.2:
|
||||
dependencies:
|
||||
'@babel/parser': 7.29.2
|
||||
@ -7817,6 +7961,11 @@ snapshots:
|
||||
assert-never: 1.4.0
|
||||
babel-walk: 3.0.0-canary-5
|
||||
|
||||
wrap-ansi@2.1.0:
|
||||
dependencies:
|
||||
string-width: 1.0.2
|
||||
strip-ansi: 3.0.1
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
@ -7840,6 +7989,8 @@ snapshots:
|
||||
|
||||
xmlhttprequest-ssl@2.1.2: {}
|
||||
|
||||
y18n@3.2.2: {}
|
||||
|
||||
y18n@5.0.8: {}
|
||||
|
||||
yargs-parser@21.1.1: {}
|
||||
@ -7854,6 +8005,16 @@ snapshots:
|
||||
y18n: 5.0.8
|
||||
yargs-parser: 21.1.1
|
||||
|
||||
yargs@3.32.0:
|
||||
dependencies:
|
||||
camelcase: 2.1.1
|
||||
cliui: 3.2.0
|
||||
decamelize: 1.2.0
|
||||
os-locale: 1.4.0
|
||||
string-width: 1.0.2
|
||||
window-size: 0.1.4
|
||||
y18n: 3.2.2
|
||||
|
||||
yauzl@2.10.0:
|
||||
dependencies:
|
||||
buffer-crc32: 0.2.13
|
||||
|
||||
Loading…
Reference in New Issue
Block a user