gitspace 0.2.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +21 -0
- package/.gitspace/bundle.json +50 -0
- package/.gitspace/select/01-status.sh +40 -0
- package/.gitspace/setup/01-install-deps.sh +12 -0
- package/.gitspace/setup/02-typecheck.sh +16 -0
- package/AGENTS.md +439 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +25 -0
- package/README.md +607 -0
- package/bin/gssh +62 -0
- package/bun.lock +647 -0
- package/docs/CONNECTION.md +623 -0
- package/docs/GATEWAY-WORKER.md +319 -0
- package/docs/GETTING-STARTED.md +448 -0
- package/docs/GITSPACE-PLATFORM.md +1819 -0
- package/docs/INFRASTRUCTURE.md +1347 -0
- package/docs/PROTOCOL.md +619 -0
- package/docs/QUICKSTART.md +174 -0
- package/docs/RELAY.md +327 -0
- package/docs/REMOTE-DESIGN.md +549 -0
- package/docs/ROADMAP.md +564 -0
- package/docs/SITE_DOCS_FIGMA_MAKE.md +1167 -0
- package/docs/STACK-DESIGN.md +588 -0
- package/docs/UNIFIED_ARCHITECTURE.md +292 -0
- package/experiments/pty-benchmark.ts +148 -0
- package/experiments/pty-latency.ts +100 -0
- package/experiments/router/client.ts +199 -0
- package/experiments/router/protocol.ts +74 -0
- package/experiments/router/router.ts +217 -0
- package/experiments/router/session.ts +180 -0
- package/experiments/router/test.ts +133 -0
- package/experiments/socket-bandwidth.ts +77 -0
- package/homebrew/gitspace.rb +45 -0
- package/landing-page/ATTRIBUTIONS.md +3 -0
- package/landing-page/README.md +11 -0
- package/landing-page/bun.lock +801 -0
- package/landing-page/guidelines/Guidelines.md +61 -0
- package/landing-page/index.html +37 -0
- package/landing-page/package.json +90 -0
- package/landing-page/postcss.config.mjs +15 -0
- package/landing-page/public/_redirects +1 -0
- package/landing-page/public/favicon.png +0 -0
- package/landing-page/src/app/App.tsx +53 -0
- package/landing-page/src/app/components/figma/ImageWithFallback.tsx +27 -0
- package/landing-page/src/app/components/ui/accordion.tsx +66 -0
- package/landing-page/src/app/components/ui/alert-dialog.tsx +157 -0
- package/landing-page/src/app/components/ui/alert.tsx +66 -0
- package/landing-page/src/app/components/ui/aspect-ratio.tsx +11 -0
- package/landing-page/src/app/components/ui/avatar.tsx +53 -0
- package/landing-page/src/app/components/ui/badge.tsx +46 -0
- package/landing-page/src/app/components/ui/breadcrumb.tsx +109 -0
- package/landing-page/src/app/components/ui/button.tsx +57 -0
- package/landing-page/src/app/components/ui/calendar.tsx +75 -0
- package/landing-page/src/app/components/ui/card.tsx +92 -0
- package/landing-page/src/app/components/ui/carousel.tsx +241 -0
- package/landing-page/src/app/components/ui/chart.tsx +353 -0
- package/landing-page/src/app/components/ui/checkbox.tsx +32 -0
- package/landing-page/src/app/components/ui/collapsible.tsx +33 -0
- package/landing-page/src/app/components/ui/command.tsx +177 -0
- package/landing-page/src/app/components/ui/context-menu.tsx +252 -0
- package/landing-page/src/app/components/ui/dialog.tsx +135 -0
- package/landing-page/src/app/components/ui/drawer.tsx +132 -0
- package/landing-page/src/app/components/ui/dropdown-menu.tsx +257 -0
- package/landing-page/src/app/components/ui/form.tsx +168 -0
- package/landing-page/src/app/components/ui/hover-card.tsx +44 -0
- package/landing-page/src/app/components/ui/input-otp.tsx +77 -0
- package/landing-page/src/app/components/ui/input.tsx +21 -0
- package/landing-page/src/app/components/ui/label.tsx +24 -0
- package/landing-page/src/app/components/ui/menubar.tsx +276 -0
- package/landing-page/src/app/components/ui/navigation-menu.tsx +168 -0
- package/landing-page/src/app/components/ui/pagination.tsx +127 -0
- package/landing-page/src/app/components/ui/popover.tsx +48 -0
- package/landing-page/src/app/components/ui/progress.tsx +31 -0
- package/landing-page/src/app/components/ui/radio-group.tsx +45 -0
- package/landing-page/src/app/components/ui/resizable.tsx +56 -0
- package/landing-page/src/app/components/ui/scroll-area.tsx +58 -0
- package/landing-page/src/app/components/ui/select.tsx +189 -0
- package/landing-page/src/app/components/ui/separator.tsx +28 -0
- package/landing-page/src/app/components/ui/sheet.tsx +139 -0
- package/landing-page/src/app/components/ui/sidebar.tsx +726 -0
- package/landing-page/src/app/components/ui/skeleton.tsx +13 -0
- package/landing-page/src/app/components/ui/slider.tsx +63 -0
- package/landing-page/src/app/components/ui/sonner.tsx +25 -0
- package/landing-page/src/app/components/ui/switch.tsx +31 -0
- package/landing-page/src/app/components/ui/table.tsx +116 -0
- package/landing-page/src/app/components/ui/tabs.tsx +66 -0
- package/landing-page/src/app/components/ui/textarea.tsx +18 -0
- package/landing-page/src/app/components/ui/toggle-group.tsx +73 -0
- package/landing-page/src/app/components/ui/toggle.tsx +47 -0
- package/landing-page/src/app/components/ui/tooltip.tsx +61 -0
- package/landing-page/src/app/components/ui/use-mobile.ts +21 -0
- package/landing-page/src/app/components/ui/utils.ts +6 -0
- package/landing-page/src/components/docs/DocsContent.tsx +718 -0
- package/landing-page/src/components/docs/DocsSidebar.tsx +84 -0
- package/landing-page/src/components/landing/CTA.tsx +59 -0
- package/landing-page/src/components/landing/Comparison.tsx +84 -0
- package/landing-page/src/components/landing/FaultyTerminal.tsx +424 -0
- package/landing-page/src/components/landing/Features.tsx +201 -0
- package/landing-page/src/components/landing/Hero.tsx +142 -0
- package/landing-page/src/components/landing/Pricing.tsx +140 -0
- package/landing-page/src/components/landing/Roadmap.tsx +86 -0
- package/landing-page/src/components/landing/Security.tsx +81 -0
- package/landing-page/src/components/landing/TerminalWindow.tsx +27 -0
- package/landing-page/src/components/landing/UseCases.tsx +55 -0
- package/landing-page/src/components/landing/Workflow.tsx +101 -0
- package/landing-page/src/components/layout/DashboardNavbar.tsx +37 -0
- package/landing-page/src/components/layout/Footer.tsx +55 -0
- package/landing-page/src/components/layout/LandingNavbar.tsx +82 -0
- package/landing-page/src/components/ui/badge.tsx +39 -0
- package/landing-page/src/components/ui/breadcrumb.tsx +115 -0
- package/landing-page/src/components/ui/button.tsx +57 -0
- package/landing-page/src/components/ui/card.tsx +79 -0
- package/landing-page/src/components/ui/mock-terminal.tsx +68 -0
- package/landing-page/src/components/ui/separator.tsx +28 -0
- package/landing-page/src/lib/utils.ts +6 -0
- package/landing-page/src/main.tsx +10 -0
- package/landing-page/src/pages/Dashboard.tsx +133 -0
- package/landing-page/src/pages/DocsPage.tsx +79 -0
- package/landing-page/src/pages/LandingPage.tsx +31 -0
- package/landing-page/src/pages/TerminalView.tsx +106 -0
- package/landing-page/src/styles/fonts.css +0 -0
- package/landing-page/src/styles/index.css +3 -0
- package/landing-page/src/styles/tailwind.css +4 -0
- package/landing-page/src/styles/theme.css +181 -0
- package/landing-page/vite.config.ts +19 -0
- package/npm/darwin-arm64/bin/gssh +0 -0
- package/npm/darwin-arm64/package.json +20 -0
- package/package.json +74 -0
- package/scripts/build.ts +284 -0
- package/scripts/release.ts +140 -0
- package/src/__tests__/test-utils.ts +298 -0
- package/src/commands/__tests__/serve-messages.test.ts +190 -0
- package/src/commands/access.ts +298 -0
- package/src/commands/add.ts +452 -0
- package/src/commands/auth.ts +364 -0
- package/src/commands/connect.ts +287 -0
- package/src/commands/directory.ts +16 -0
- package/src/commands/host.ts +396 -0
- package/src/commands/identity.ts +184 -0
- package/src/commands/list.ts +200 -0
- package/src/commands/relay.ts +315 -0
- package/src/commands/remove.ts +241 -0
- package/src/commands/serve.ts +1493 -0
- package/src/commands/share.ts +456 -0
- package/src/commands/status.ts +125 -0
- package/src/commands/switch.ts +353 -0
- package/src/commands/tmux.ts +317 -0
- package/src/core/__tests__/access.test.ts +240 -0
- package/src/core/access.ts +277 -0
- package/src/core/bundle.ts +342 -0
- package/src/core/config.ts +510 -0
- package/src/core/git.ts +317 -0
- package/src/core/github.ts +151 -0
- package/src/core/identity.ts +631 -0
- package/src/core/linear.ts +225 -0
- package/src/core/shell.ts +161 -0
- package/src/core/trusted-relays.ts +315 -0
- package/src/index.ts +821 -0
- package/src/lib/remote-session/index.ts +7 -0
- package/src/lib/remote-session/protocol.ts +267 -0
- package/src/lib/remote-session/session-handler.ts +581 -0
- package/src/lib/remote-session/workspace-scanner.ts +167 -0
- package/src/lib/tmux-lite/README.md +81 -0
- package/src/lib/tmux-lite/cli.ts +796 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/handshake-runner.ts +349 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/mock-relay.ts +291 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/test-identities.ts +142 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/authorization.integration.test.ts +339 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/e2e-communication.integration.test.ts +477 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/error-handling.integration.test.ts +499 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/handshake.integration.test.ts +371 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/security.integration.test.ts +573 -0
- package/src/lib/tmux-lite/crypto/access-control.test.ts +512 -0
- package/src/lib/tmux-lite/crypto/access-control.ts +320 -0
- package/src/lib/tmux-lite/crypto/frames.test.ts +262 -0
- package/src/lib/tmux-lite/crypto/frames.ts +141 -0
- package/src/lib/tmux-lite/crypto/handshake.ts +894 -0
- package/src/lib/tmux-lite/crypto/identity.test.ts +220 -0
- package/src/lib/tmux-lite/crypto/identity.ts +286 -0
- package/src/lib/tmux-lite/crypto/index.ts +51 -0
- package/src/lib/tmux-lite/crypto/invites.test.ts +381 -0
- package/src/lib/tmux-lite/crypto/invites.ts +215 -0
- package/src/lib/tmux-lite/crypto/keyexchange.ts +435 -0
- package/src/lib/tmux-lite/crypto/keys.test.ts +58 -0
- package/src/lib/tmux-lite/crypto/keys.ts +47 -0
- package/src/lib/tmux-lite/crypto/secretbox.test.ts +169 -0
- package/src/lib/tmux-lite/crypto/secretbox.ts +124 -0
- package/src/lib/tmux-lite/handshake-handler.ts +451 -0
- package/src/lib/tmux-lite/protocol.test.ts +307 -0
- package/src/lib/tmux-lite/protocol.ts +266 -0
- package/src/lib/tmux-lite/relay-client.ts +506 -0
- package/src/lib/tmux-lite/server.ts +1250 -0
- package/src/lib/tmux-lite/shell-integration.sh +37 -0
- package/src/lib/tmux-lite/terminal-queries.test.ts +54 -0
- package/src/lib/tmux-lite/terminal-queries.ts +49 -0
- package/src/relay/__tests__/e2e-flow.test.ts +1284 -0
- package/src/relay/__tests__/helpers/auth.ts +354 -0
- package/src/relay/__tests__/helpers/ports.ts +51 -0
- package/src/relay/__tests__/protocol-validation.test.ts +265 -0
- package/src/relay/authorization.ts +303 -0
- package/src/relay/embedded-assets.generated.d.ts +15 -0
- package/src/relay/identity.ts +352 -0
- package/src/relay/index.ts +57 -0
- package/src/relay/pipes.test.ts +427 -0
- package/src/relay/pipes.ts +195 -0
- package/src/relay/protocol.ts +804 -0
- package/src/relay/registries.test.ts +437 -0
- package/src/relay/registries.ts +593 -0
- package/src/relay/server.test.ts +1323 -0
- package/src/relay/server.ts +1092 -0
- package/src/relay/signing.ts +238 -0
- package/src/relay/types.ts +69 -0
- package/src/serve/client-session-manager.ts +622 -0
- package/src/serve/daemon.ts +497 -0
- package/src/serve/pty-session.ts +236 -0
- package/src/serve/types.ts +169 -0
- package/src/shared/components/Flow.tsx +453 -0
- package/src/shared/components/Flow.tui.tsx +343 -0
- package/src/shared/components/Flow.web.tsx +442 -0
- package/src/shared/components/Inbox.tsx +446 -0
- package/src/shared/components/Inbox.tui.tsx +262 -0
- package/src/shared/components/Inbox.web.tsx +329 -0
- package/src/shared/components/MachineList.tsx +187 -0
- package/src/shared/components/MachineList.tui.tsx +161 -0
- package/src/shared/components/MachineList.web.tsx +210 -0
- package/src/shared/components/ProjectList.tsx +176 -0
- package/src/shared/components/ProjectList.tui.tsx +109 -0
- package/src/shared/components/ProjectList.web.tsx +143 -0
- package/src/shared/components/SpacesBrowser.tsx +332 -0
- package/src/shared/components/SpacesBrowser.tui.tsx +163 -0
- package/src/shared/components/SpacesBrowser.web.tsx +221 -0
- package/src/shared/components/index.ts +103 -0
- package/src/shared/hooks/index.ts +16 -0
- package/src/shared/hooks/useNavigation.ts +226 -0
- package/src/shared/index.ts +122 -0
- package/src/shared/providers/LocalMachineProvider.ts +425 -0
- package/src/shared/providers/MachineProvider.ts +165 -0
- package/src/shared/providers/RemoteMachineProvider.ts +444 -0
- package/src/shared/providers/index.ts +26 -0
- package/src/shared/types.ts +145 -0
- package/src/tui/adapters.ts +120 -0
- package/src/tui/app.tsx +1816 -0
- package/src/tui/components/Terminal.tsx +580 -0
- package/src/tui/hooks/index.ts +35 -0
- package/src/tui/hooks/useAppState.ts +314 -0
- package/src/tui/hooks/useDaemonStatus.ts +174 -0
- package/src/tui/hooks/useInboxTUI.ts +113 -0
- package/src/tui/hooks/useRemoteMachines.ts +209 -0
- package/src/tui/index.ts +24 -0
- package/src/tui/state.ts +299 -0
- package/src/tui/terminal-bracketed-paste.test.ts +45 -0
- package/src/tui/terminal-bracketed-paste.ts +47 -0
- package/src/types/bundle.ts +112 -0
- package/src/types/config.ts +89 -0
- package/src/types/errors.ts +206 -0
- package/src/types/identity.ts +284 -0
- package/src/types/workspace-fuzzy.ts +49 -0
- package/src/types/workspace.ts +151 -0
- package/src/utils/bun-socket-writer.ts +80 -0
- package/src/utils/deps.ts +127 -0
- package/src/utils/fuzzy-match.ts +125 -0
- package/src/utils/logger.ts +127 -0
- package/src/utils/markdown.ts +254 -0
- package/src/utils/onboarding.ts +229 -0
- package/src/utils/prompts.ts +114 -0
- package/src/utils/run-commands.ts +112 -0
- package/src/utils/run-scripts.ts +142 -0
- package/src/utils/sanitize.ts +98 -0
- package/src/utils/secrets.ts +122 -0
- package/src/utils/shell-escape.ts +40 -0
- package/src/utils/utf8.ts +79 -0
- package/src/utils/workspace-state.ts +47 -0
- package/src/web/README.md +73 -0
- package/src/web/bun.lock +575 -0
- package/src/web/eslint.config.js +23 -0
- package/src/web/index.html +16 -0
- package/src/web/package.json +37 -0
- package/src/web/public/vite.svg +1 -0
- package/src/web/src/App.tsx +604 -0
- package/src/web/src/assets/react.svg +1 -0
- package/src/web/src/components/Terminal.tsx +207 -0
- package/src/web/src/hooks/useRelayConnection.ts +224 -0
- package/src/web/src/hooks/useTerminal.ts +699 -0
- package/src/web/src/index.css +55 -0
- package/src/web/src/lib/crypto/__tests__/web-terminal.test.ts +1158 -0
- package/src/web/src/lib/crypto/frames.ts +205 -0
- package/src/web/src/lib/crypto/handshake.ts +396 -0
- package/src/web/src/lib/crypto/identity.ts +128 -0
- package/src/web/src/lib/crypto/keyexchange.ts +246 -0
- package/src/web/src/lib/crypto/relay-signing.ts +53 -0
- package/src/web/src/lib/invite.ts +58 -0
- package/src/web/src/lib/storage/identity-store.ts +94 -0
- package/src/web/src/main.tsx +10 -0
- package/src/web/src/types/identity.ts +45 -0
- package/src/web/tsconfig.app.json +28 -0
- package/src/web/tsconfig.json +7 -0
- package/src/web/tsconfig.node.json +26 -0
- package/src/web/vite.config.ts +31 -0
- package/todo-security.md +92 -0
- package/tsconfig.json +23 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-shm +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-wal +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-shm +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-wal +0 -0
- package/worker/bun.lock +237 -0
- package/worker/package.json +22 -0
- package/worker/schema.sql +96 -0
- package/worker/src/handlers/auth.ts +451 -0
- package/worker/src/handlers/subdomains.ts +376 -0
- package/worker/src/handlers/user.ts +98 -0
- package/worker/src/index.ts +70 -0
- package/worker/src/middleware/auth.ts +152 -0
- package/worker/src/services/cloudflare.ts +609 -0
- package/worker/src/types.ts +96 -0
- package/worker/tsconfig.json +15 -0
- package/worker/wrangler.toml +26 -0
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for global and project configs
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
existsSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
readdirSync,
|
|
11
|
+
statSync,
|
|
12
|
+
chmodSync,
|
|
13
|
+
} from 'fs'
|
|
14
|
+
import { join, dirname } from 'path'
|
|
15
|
+
import { homedir } from 'os'
|
|
16
|
+
import type { GlobalConfig, ProjectConfig } from '../types/config.js'
|
|
17
|
+
import {
|
|
18
|
+
DEFAULT_GLOBAL_CONFIG,
|
|
19
|
+
createDefaultProjectConfig,
|
|
20
|
+
} from '../types/config.js'
|
|
21
|
+
import { SpacesError } from '../types/errors.js'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the global gitspace directory path
|
|
25
|
+
*/
|
|
26
|
+
export function getGitspaceDir(): string {
|
|
27
|
+
return join(homedir(), 'gitspace')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated Use getGitspaceDir() instead
|
|
32
|
+
*/
|
|
33
|
+
export function getSpacesDir(): string {
|
|
34
|
+
return getGitspaceDir()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get the global config file path
|
|
39
|
+
*/
|
|
40
|
+
export function getGlobalConfigPath(): string {
|
|
41
|
+
return join(getSpacesDir(), '.config.json')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get a project directory path
|
|
46
|
+
*/
|
|
47
|
+
export function getProjectDir(projectName: string): string {
|
|
48
|
+
return join(getSpacesDir(), projectName)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get a project config file path
|
|
53
|
+
*/
|
|
54
|
+
export function getProjectConfigPath(projectName: string): string {
|
|
55
|
+
return join(getProjectDir(projectName), '.config.json')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get the base repository directory for a project
|
|
60
|
+
*/
|
|
61
|
+
export function getProjectBaseDir(projectName: string): string {
|
|
62
|
+
return join(getProjectDir(projectName), 'base')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get the workspaces directory for a project
|
|
67
|
+
*/
|
|
68
|
+
export function getProjectWorkspacesDir(projectName: string): string {
|
|
69
|
+
return join(getProjectDir(projectName), 'workspaces')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get the scripts directory for a project
|
|
74
|
+
*/
|
|
75
|
+
export function getProjectScriptsDir(projectName: string): string {
|
|
76
|
+
return join(getProjectDir(projectName), 'scripts')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get a specific scripts phase directory (pre, setup, select)
|
|
81
|
+
*/
|
|
82
|
+
export function getScriptsPhaseDir(
|
|
83
|
+
projectName: string,
|
|
84
|
+
phase: 'pre' | 'setup' | 'select' | 'remove'
|
|
85
|
+
): string {
|
|
86
|
+
return join(getProjectScriptsDir(projectName), phase)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Initialize global config with defaults
|
|
91
|
+
*/
|
|
92
|
+
function initializeGlobalConfig(): GlobalConfig {
|
|
93
|
+
return {
|
|
94
|
+
...DEFAULT_GLOBAL_CONFIG,
|
|
95
|
+
projectsDir: getSpacesDir(),
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Read global configuration
|
|
101
|
+
*/
|
|
102
|
+
export function readGlobalConfig(): GlobalConfig {
|
|
103
|
+
const configPath = getGlobalConfigPath()
|
|
104
|
+
|
|
105
|
+
if (!existsSync(configPath)) {
|
|
106
|
+
// Return default config if file doesn't exist
|
|
107
|
+
return initializeGlobalConfig()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const content = readFileSync(configPath, 'utf-8')
|
|
112
|
+
const config = JSON.parse(content) as GlobalConfig
|
|
113
|
+
|
|
114
|
+
// Merge with defaults to ensure all fields exist
|
|
115
|
+
return {
|
|
116
|
+
...initializeGlobalConfig(),
|
|
117
|
+
...config,
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
throw new SpacesError(
|
|
121
|
+
`Failed to read global config: ${
|
|
122
|
+
error instanceof Error ? error.message : 'Unknown error'
|
|
123
|
+
}`,
|
|
124
|
+
'SYSTEM_ERROR',
|
|
125
|
+
2
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Write global configuration
|
|
132
|
+
*/
|
|
133
|
+
export function writeGlobalConfig(config: GlobalConfig): void {
|
|
134
|
+
const configPath = getGlobalConfigPath()
|
|
135
|
+
const spacesDir = dirname(configPath)
|
|
136
|
+
|
|
137
|
+
// Ensure spaces directory exists
|
|
138
|
+
if (!existsSync(spacesDir)) {
|
|
139
|
+
mkdirSync(spacesDir, { recursive: true })
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8')
|
|
144
|
+
chmodSync(configPath, 0o600)
|
|
145
|
+
} catch (error) {
|
|
146
|
+
throw new SpacesError(
|
|
147
|
+
`Failed to write global config: ${
|
|
148
|
+
error instanceof Error ? error.message : 'Unknown error'
|
|
149
|
+
}`,
|
|
150
|
+
'SYSTEM_ERROR',
|
|
151
|
+
2
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Update global configuration
|
|
158
|
+
*/
|
|
159
|
+
export function updateGlobalConfig(
|
|
160
|
+
updates: Partial<GlobalConfig>
|
|
161
|
+
): GlobalConfig {
|
|
162
|
+
const config = readGlobalConfig()
|
|
163
|
+
const updated = { ...config, ...updates }
|
|
164
|
+
writeGlobalConfig(updated)
|
|
165
|
+
return updated
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Read project configuration
|
|
170
|
+
*/
|
|
171
|
+
export function readProjectConfig(projectName: string): ProjectConfig {
|
|
172
|
+
const configPath = getProjectConfigPath(projectName)
|
|
173
|
+
|
|
174
|
+
if (!existsSync(configPath)) {
|
|
175
|
+
throw new SpacesError(`Project "${projectName}" not found`, 'USER_ERROR', 1)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const content = readFileSync(configPath, 'utf-8')
|
|
180
|
+
return JSON.parse(content) as ProjectConfig
|
|
181
|
+
} catch (error) {
|
|
182
|
+
throw new SpacesError(
|
|
183
|
+
`Failed to read project config for "${projectName}": ${
|
|
184
|
+
error instanceof Error ? error.message : 'Unknown error'
|
|
185
|
+
}`,
|
|
186
|
+
'SYSTEM_ERROR',
|
|
187
|
+
2
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Write project configuration
|
|
194
|
+
*/
|
|
195
|
+
export function writeProjectConfig(
|
|
196
|
+
projectName: string,
|
|
197
|
+
config: ProjectConfig
|
|
198
|
+
): void {
|
|
199
|
+
const configPath = getProjectConfigPath(projectName)
|
|
200
|
+
const projectDir = dirname(configPath)
|
|
201
|
+
|
|
202
|
+
// Ensure project directory exists
|
|
203
|
+
if (!existsSync(projectDir)) {
|
|
204
|
+
mkdirSync(projectDir, { recursive: true })
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8')
|
|
209
|
+
chmodSync(configPath, 0o600)
|
|
210
|
+
} catch (error) {
|
|
211
|
+
throw new SpacesError(
|
|
212
|
+
`Failed to write project config for "${projectName}": ${
|
|
213
|
+
error instanceof Error ? error.message : 'Unknown error'
|
|
214
|
+
}`,
|
|
215
|
+
'SYSTEM_ERROR',
|
|
216
|
+
2
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Update project configuration
|
|
223
|
+
*/
|
|
224
|
+
export function updateProjectConfig(
|
|
225
|
+
projectName: string,
|
|
226
|
+
updates: Partial<ProjectConfig>
|
|
227
|
+
): ProjectConfig {
|
|
228
|
+
const config = readProjectConfig(projectName)
|
|
229
|
+
const updated = { ...config, ...updates }
|
|
230
|
+
writeProjectConfig(projectName, updated)
|
|
231
|
+
return updated
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get current project name from env var or global config
|
|
236
|
+
* Resolution order:
|
|
237
|
+
* 1. SPACES_CURRENT_PROJECT environment variable
|
|
238
|
+
* 2. currentProject field in global config
|
|
239
|
+
* 3. null if neither is set
|
|
240
|
+
*/
|
|
241
|
+
export function getCurrentProject(): string | null {
|
|
242
|
+
// Check environment variable first
|
|
243
|
+
const envProject = process.env.SPACES_CURRENT_PROJECT
|
|
244
|
+
if (envProject) {
|
|
245
|
+
return envProject
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Fall back to global config
|
|
249
|
+
const globalConfig = readGlobalConfig()
|
|
250
|
+
return globalConfig.currentProject
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Set current project in global config
|
|
255
|
+
*/
|
|
256
|
+
export function setCurrentProject(projectName: string): void {
|
|
257
|
+
updateGlobalConfig({ currentProject: projectName })
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Check if the global config exists (first-time setup check)
|
|
262
|
+
*/
|
|
263
|
+
export function isFirstTimeSetup(): boolean {
|
|
264
|
+
return !existsSync(getGlobalConfigPath())
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Initialize spaces directory and config for first-time setup
|
|
269
|
+
*/
|
|
270
|
+
export function initializeSpaces(): void {
|
|
271
|
+
const spacesDir = getSpacesDir()
|
|
272
|
+
|
|
273
|
+
// Create spaces directory if it doesn't exist
|
|
274
|
+
if (!existsSync(spacesDir)) {
|
|
275
|
+
mkdirSync(spacesDir, { recursive: true })
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Create global config if it doesn't exist
|
|
279
|
+
if (!existsSync(getGlobalConfigPath())) {
|
|
280
|
+
writeGlobalConfig(initializeGlobalConfig())
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get all project names
|
|
286
|
+
*/
|
|
287
|
+
export function getAllProjectNames(): string[] {
|
|
288
|
+
const spacesDir = getSpacesDir()
|
|
289
|
+
|
|
290
|
+
if (!existsSync(spacesDir)) {
|
|
291
|
+
return []
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
const entries = readdirSync(spacesDir) as string[]
|
|
296
|
+
|
|
297
|
+
// Filter to only directories that have a .config.json file
|
|
298
|
+
return entries.filter((entry: string) => {
|
|
299
|
+
const projectDir = join(spacesDir, entry)
|
|
300
|
+
const configPath = join(projectDir, '.config.json')
|
|
301
|
+
return (
|
|
302
|
+
statSync(projectDir).isDirectory() &&
|
|
303
|
+
existsSync(configPath) &&
|
|
304
|
+
entry !== 'app' // Exclude the app directory
|
|
305
|
+
)
|
|
306
|
+
})
|
|
307
|
+
} catch (error) {
|
|
308
|
+
throw new SpacesError(
|
|
309
|
+
`Failed to list projects: ${
|
|
310
|
+
error instanceof Error ? error.message : 'Unknown error'
|
|
311
|
+
}`,
|
|
312
|
+
'SYSTEM_ERROR',
|
|
313
|
+
2
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Check if a project exists
|
|
320
|
+
*/
|
|
321
|
+
export function projectExists(projectName: string): boolean {
|
|
322
|
+
const configPath = getProjectConfigPath(projectName)
|
|
323
|
+
return existsSync(configPath)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Create a new project configuration
|
|
328
|
+
*/
|
|
329
|
+
export function createProject(
|
|
330
|
+
projectName: string,
|
|
331
|
+
repository: string,
|
|
332
|
+
baseBranch: string,
|
|
333
|
+
linearApiKey?: string,
|
|
334
|
+
linearTeamKey?: string
|
|
335
|
+
): ProjectConfig {
|
|
336
|
+
const config = createDefaultProjectConfig(
|
|
337
|
+
projectName,
|
|
338
|
+
repository,
|
|
339
|
+
baseBranch,
|
|
340
|
+
linearApiKey,
|
|
341
|
+
linearTeamKey
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
// Create project directories
|
|
345
|
+
const projectDir = getProjectDir(projectName)
|
|
346
|
+
const baseDir = getProjectBaseDir(projectName)
|
|
347
|
+
const workspacesDir = getProjectWorkspacesDir(projectName)
|
|
348
|
+
|
|
349
|
+
mkdirSync(projectDir, { recursive: true })
|
|
350
|
+
mkdirSync(baseDir, { recursive: true })
|
|
351
|
+
mkdirSync(workspacesDir, { recursive: true })
|
|
352
|
+
|
|
353
|
+
// Create scripts directories
|
|
354
|
+
mkdirSync(getScriptsPhaseDir(projectName, 'pre'), { recursive: true })
|
|
355
|
+
mkdirSync(getScriptsPhaseDir(projectName, 'setup'), { recursive: true })
|
|
356
|
+
mkdirSync(getScriptsPhaseDir(projectName, 'select'), { recursive: true })
|
|
357
|
+
mkdirSync(getScriptsPhaseDir(projectName, 'remove'), { recursive: true })
|
|
358
|
+
|
|
359
|
+
// Create example template scripts in each phase directory
|
|
360
|
+
const preExampleScript = `#!/bin/bash
|
|
361
|
+
# Pre-phase script - runs BEFORE workspace shell opens
|
|
362
|
+
#
|
|
363
|
+
# Current working directory: ~/gitspace/<project>/workspaces/<workspace>/
|
|
364
|
+
# (Scripts run from the workspace directory, so you can use relative paths)
|
|
365
|
+
#
|
|
366
|
+
# This script runs in your terminal immediately after the worktree is created.
|
|
367
|
+
# Perfect for preparation tasks like:
|
|
368
|
+
# - Copying environment files (cp .env.example .env)
|
|
369
|
+
# - Creating directories (mkdir -p tmp/uploads)
|
|
370
|
+
# - Any setup that other scripts might need
|
|
371
|
+
#
|
|
372
|
+
# Arguments:
|
|
373
|
+
# $1 - Workspace name (e.g., "my-feature")
|
|
374
|
+
# $2 - Repository name (e.g., "myorg/my-app")
|
|
375
|
+
#
|
|
376
|
+
# To use this script:
|
|
377
|
+
# 1. Rename it (e.g., 01-copy-env.sh)
|
|
378
|
+
# 2. Add your commands
|
|
379
|
+
# 3. Make it executable: chmod +x scripts/pre/01-copy-env.sh
|
|
380
|
+
|
|
381
|
+
WORKSPACE_NAME=$1
|
|
382
|
+
REPOSITORY=$2
|
|
383
|
+
|
|
384
|
+
echo "Running Spaces pre-install on: $WORKSPACE_NAME from $REPOSITORY"
|
|
385
|
+
`
|
|
386
|
+
|
|
387
|
+
const setupExampleScript = `#!/bin/bash
|
|
388
|
+
# Setup-phase script - runs ONCE when workspace is first created
|
|
389
|
+
#
|
|
390
|
+
# Current working directory: ~/gitspace/<project>/workspaces/<workspace>/
|
|
391
|
+
# (Scripts run from the workspace directory, so you can use relative paths)
|
|
392
|
+
#
|
|
393
|
+
# This script runs the first time a workspace is created.
|
|
394
|
+
# Perfect for one-time setup tasks like:
|
|
395
|
+
# - Installing dependencies (npm install, bundle install)
|
|
396
|
+
# - Initial builds (npm run build)
|
|
397
|
+
# - Database setup
|
|
398
|
+
# - Any expensive setup that should only run once
|
|
399
|
+
#
|
|
400
|
+
# After all setup scripts run, GitSpace creates a gitspace.lock marker file
|
|
401
|
+
# to prevent them from running again.
|
|
402
|
+
#
|
|
403
|
+
# Arguments:
|
|
404
|
+
# $1 - Workspace name (e.g., "my-feature")
|
|
405
|
+
# $2 - Repository name (e.g., "myorg/my-app")
|
|
406
|
+
#
|
|
407
|
+
# To use this script:
|
|
408
|
+
# 1. Rename it (e.g., 01-install.sh)
|
|
409
|
+
# 2. Add your commands
|
|
410
|
+
# 3. Make it executable: chmod +x scripts/setup/01-install.sh
|
|
411
|
+
|
|
412
|
+
WORKSPACE_NAME=$1
|
|
413
|
+
REPOSITORY=$2
|
|
414
|
+
|
|
415
|
+
echo "Setting up Spaces workspace on: $WORKSPACE_NAME from $REPOSITORY"
|
|
416
|
+
`
|
|
417
|
+
|
|
418
|
+
const selectExampleScript = `#!/bin/bash
|
|
419
|
+
# Select-phase script - runs EVERY TIME you switch to a workspace
|
|
420
|
+
#
|
|
421
|
+
# Current working directory: ~/gitspace/<project>/workspaces/<workspace>/
|
|
422
|
+
# (Scripts run from the workspace directory, so you can use relative paths)
|
|
423
|
+
#
|
|
424
|
+
# This script runs every time you switch to an existing workspace
|
|
425
|
+
# (where setup already completed).
|
|
426
|
+
# Perfect for quick status updates like:
|
|
427
|
+
# - Fetching latest changes (git fetch --all)
|
|
428
|
+
# - Checking workspace state (git status)
|
|
429
|
+
# - Environment checks
|
|
430
|
+
# - Quick status updates
|
|
431
|
+
#
|
|
432
|
+
# Arguments:
|
|
433
|
+
# $1 - Workspace name (e.g., "my-feature")
|
|
434
|
+
# $2 - Repository name (e.g., "myorg/my-app")
|
|
435
|
+
#
|
|
436
|
+
# To use this script:
|
|
437
|
+
# 1. Rename it (e.g., 01-fetch.sh)
|
|
438
|
+
# 2. Add your commands
|
|
439
|
+
# 3. Make it executable: chmod +x scripts/select/01-fetch.sh
|
|
440
|
+
|
|
441
|
+
WORKSPACE_NAME=$1
|
|
442
|
+
REPOSITORY=$2
|
|
443
|
+
|
|
444
|
+
echo "Running Spaces script on: $WORKSPACE_NAME from $REPOSITORY"
|
|
445
|
+
`
|
|
446
|
+
|
|
447
|
+
const removeExampleScript = `#!/bin/bash
|
|
448
|
+
# Remove-phase script - runs when workspace is REMOVED
|
|
449
|
+
#
|
|
450
|
+
# Current working directory: ~/gitspace/<project>/workspaces/<workspace>/
|
|
451
|
+
# (Scripts run from the workspace directory, so you can use relative paths)
|
|
452
|
+
#
|
|
453
|
+
# This script runs in your terminal when you remove a workspace,
|
|
454
|
+
# BEFORE the worktree is deleted. Perfect for cleanup tasks like:
|
|
455
|
+
# - Tearing down test databases
|
|
456
|
+
# - Removing cloud resources (S3 buckets, EC2 instances)
|
|
457
|
+
# - Cleaning up external services
|
|
458
|
+
# - Removing temporary Docker containers/volumes
|
|
459
|
+
# - Cleaning up API keys or tokens
|
|
460
|
+
#
|
|
461
|
+
# Arguments:
|
|
462
|
+
# $1 - Workspace name (e.g., "my-feature")
|
|
463
|
+
# $2 - Repository name (e.g., "myorg/my-app")
|
|
464
|
+
#
|
|
465
|
+
# To use this script:
|
|
466
|
+
# 1. Rename it (e.g., 01-cleanup-db.sh)
|
|
467
|
+
# 2. Add your commands
|
|
468
|
+
# 3. Make it executable: chmod +x scripts/remove/01-cleanup-db.sh
|
|
469
|
+
|
|
470
|
+
WORKSPACE_NAME=$1
|
|
471
|
+
REPOSITORY=$2
|
|
472
|
+
|
|
473
|
+
echo "Running Spaces cleanup on: $WORKSPACE_NAME from $REPOSITORY"
|
|
474
|
+
`
|
|
475
|
+
|
|
476
|
+
// Write and make executable
|
|
477
|
+
const preExamplePath = join(
|
|
478
|
+
getScriptsPhaseDir(projectName, 'pre'),
|
|
479
|
+
'00-example.sh'
|
|
480
|
+
)
|
|
481
|
+
const setupExamplePath = join(
|
|
482
|
+
getScriptsPhaseDir(projectName, 'setup'),
|
|
483
|
+
'00-example.sh'
|
|
484
|
+
)
|
|
485
|
+
const selectExamplePath = join(
|
|
486
|
+
getScriptsPhaseDir(projectName, 'select'),
|
|
487
|
+
'00-example.sh'
|
|
488
|
+
)
|
|
489
|
+
const removeExamplePath = join(
|
|
490
|
+
getScriptsPhaseDir(projectName, 'remove'),
|
|
491
|
+
'00-example.sh'
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
writeFileSync(preExamplePath, preExampleScript, 'utf-8')
|
|
495
|
+
chmodSync(preExamplePath, 0o755)
|
|
496
|
+
|
|
497
|
+
writeFileSync(setupExamplePath, setupExampleScript, 'utf-8')
|
|
498
|
+
chmodSync(setupExamplePath, 0o755)
|
|
499
|
+
|
|
500
|
+
writeFileSync(selectExamplePath, selectExampleScript, 'utf-8')
|
|
501
|
+
chmodSync(selectExamplePath, 0o755)
|
|
502
|
+
|
|
503
|
+
writeFileSync(removeExamplePath, removeExampleScript, 'utf-8')
|
|
504
|
+
chmodSync(removeExamplePath, 0o755)
|
|
505
|
+
|
|
506
|
+
// Write project config
|
|
507
|
+
writeProjectConfig(projectName, config)
|
|
508
|
+
|
|
509
|
+
return config
|
|
510
|
+
}
|