@getjack/jack 0.1.27 → 0.1.29
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/package.json +1 -1
- package/src/commands/cd.ts +163 -0
- package/src/commands/clone.ts +112 -68
- package/src/commands/domain.ts +506 -0
- package/src/commands/domains.ts +215 -0
- package/src/commands/down.ts +18 -12
- package/src/commands/hack.ts +185 -8
- package/src/commands/init.ts +52 -1
- package/src/commands/link.ts +25 -43
- package/src/commands/logs.ts +2 -2
- package/src/commands/mcp.ts +74 -3
- package/src/commands/new.ts +48 -54
- package/src/commands/projects.ts +53 -10
- package/src/commands/secrets.ts +5 -1
- package/src/commands/services.ts +16 -4
- package/src/commands/shell-init.ts +43 -0
- package/src/commands/ship.ts +2 -11
- package/src/commands/skills.ts +335 -0
- package/src/commands/update.ts +31 -0
- package/src/commands/upgrade.ts +14 -0
- package/src/index.ts +116 -24
- package/src/lib/agent-integration.ts +1 -2
- package/src/lib/agents.ts +2 -2
- package/src/lib/auth/login-flow.ts +1 -1
- package/src/lib/clone-core.ts +252 -0
- package/src/lib/config.ts +22 -0
- package/src/lib/control-plane.ts +31 -5
- package/src/lib/fuzzy.ts +93 -0
- package/src/lib/hooks.ts +4 -0
- package/src/lib/managed-deploy.ts +4 -1
- package/src/lib/managed-down.ts +20 -5
- package/src/lib/output.ts +62 -0
- package/src/lib/picker.ts +406 -0
- package/src/lib/project-detection.ts +5 -2
- package/src/lib/project-list.ts +66 -5
- package/src/lib/project-operations.ts +68 -6
- package/src/lib/prompts.ts +1 -1
- package/src/lib/services/db-execute.ts +8 -1
- package/src/lib/services/db-list.ts +4 -1
- package/src/lib/services/domain-operations.ts +379 -0
- package/src/lib/services/storage-config.ts +1 -5
- package/src/lib/services/storage-delete.ts +1 -1
- package/src/lib/services/storage-info.ts +2 -4
- package/src/lib/services/vectorize-config.ts +1 -5
- package/src/lib/services/vectorize-create.ts +3 -1
- package/src/lib/shell-integration.ts +202 -0
- package/src/lib/telemetry-config.ts +50 -4
- package/src/lib/telemetry.ts +71 -2
- package/src/lib/version-check.ts +1 -3
- package/src/lib/wrangler-config.test.ts +2 -2
- package/src/lib/wrangler-config.ts +1 -1
- package/src/lib/zip-packager.ts +1 -3
- package/src/mcp/tools/index.ts +261 -7
- package/src/templates/index.ts +10 -1
- package/templates/ai-chat/.jack.json +1 -5
- package/templates/ai-chat/public/chat.js +130 -130
- package/templates/ai-chat/src/index.ts +9 -13
- package/templates/ai-chat/src/jack-ai.ts +6 -2
- package/templates/saas/.jack.json +6 -1
- package/templates/saas/src/auth.ts +8 -4
- package/templates/saas/src/client/App.tsx +22 -7
- package/templates/saas/src/client/components/ProtectedRoute.tsx +9 -2
- package/templates/saas/src/client/components/ThemeToggle.tsx +1 -6
- package/templates/saas/src/client/components/ui/accordion.tsx +1 -1
- package/templates/saas/src/client/components/ui/alert-dialog.tsx +2 -2
- package/templates/saas/src/client/components/ui/alert.tsx +2 -2
- package/templates/saas/src/client/components/ui/avatar.tsx +1 -1
- package/templates/saas/src/client/components/ui/badge.tsx +2 -2
- package/templates/saas/src/client/components/ui/breadcrumb.tsx +1 -1
- package/templates/saas/src/client/components/ui/button-group.tsx +2 -2
- package/templates/saas/src/client/components/ui/button.tsx +2 -2
- package/templates/saas/src/client/components/ui/card.tsx +1 -1
- package/templates/saas/src/client/components/ui/carousel.tsx +2 -2
- package/templates/saas/src/client/components/ui/checkbox.tsx +1 -1
- package/templates/saas/src/client/components/ui/command.tsx +2 -2
- package/templates/saas/src/client/components/ui/context-menu.tsx +1 -1
- package/templates/saas/src/client/components/ui/dialog.tsx +1 -1
- package/templates/saas/src/client/components/ui/drawer.tsx +1 -1
- package/templates/saas/src/client/components/ui/dropdown-menu.tsx +1 -1
- package/templates/saas/src/client/components/ui/empty.tsx +1 -1
- package/templates/saas/src/client/components/ui/field.tsx +2 -2
- package/templates/saas/src/client/components/ui/form.tsx +5 -5
- package/templates/saas/src/client/components/ui/hover-card.tsx +1 -1
- package/templates/saas/src/client/components/ui/input-group.tsx +3 -3
- package/templates/saas/src/client/components/ui/input-otp.tsx +1 -1
- package/templates/saas/src/client/components/ui/input.tsx +1 -1
- package/templates/saas/src/client/components/ui/item.tsx +3 -3
- package/templates/saas/src/client/components/ui/label.tsx +1 -1
- package/templates/saas/src/client/components/ui/menubar.tsx +1 -1
- package/templates/saas/src/client/components/ui/navigation-menu.tsx +1 -1
- package/templates/saas/src/client/components/ui/pagination.tsx +2 -2
- package/templates/saas/src/client/components/ui/popover.tsx +1 -1
- package/templates/saas/src/client/components/ui/progress.tsx +1 -1
- package/templates/saas/src/client/components/ui/radio-group.tsx +1 -1
- package/templates/saas/src/client/components/ui/resizable.tsx +1 -1
- package/templates/saas/src/client/components/ui/scroll-area.tsx +1 -1
- package/templates/saas/src/client/components/ui/select.tsx +1 -1
- package/templates/saas/src/client/components/ui/separator.tsx +1 -1
- package/templates/saas/src/client/components/ui/sheet.tsx +1 -1
- package/templates/saas/src/client/components/ui/sidebar.tsx +4 -4
- package/templates/saas/src/client/components/ui/slider.tsx +1 -1
- package/templates/saas/src/client/components/ui/switch.tsx +1 -1
- package/templates/saas/src/client/components/ui/table.tsx +1 -1
- package/templates/saas/src/client/components/ui/tabs.tsx +1 -1
- package/templates/saas/src/client/components/ui/textarea.tsx +1 -1
- package/templates/saas/src/client/components/ui/toggle-group.tsx +3 -3
- package/templates/saas/src/client/components/ui/toggle.tsx +2 -2
- package/templates/saas/src/client/components/ui/tooltip.tsx +1 -1
- package/templates/saas/src/client/hooks/useSubscription.ts +5 -4
- package/templates/saas/src/client/lib/auth-client.ts +1 -1
- package/templates/saas/src/client/lib/plans.ts +1 -6
- package/templates/saas/src/client/lib/utils.ts +1 -1
- package/templates/saas/src/client/main.tsx +1 -1
- package/templates/saas/src/client/pages/DashboardPage.tsx +41 -9
- package/templates/saas/src/client/pages/ForgotPasswordPage.tsx +11 -2
- package/templates/saas/src/client/pages/HomePage.tsx +11 -2
- package/templates/saas/src/client/pages/LoginPage.tsx +11 -2
- package/templates/saas/src/client/pages/PricingPage.tsx +20 -10
- package/templates/saas/src/client/pages/ResetPasswordPage.tsx +14 -11
- package/templates/saas/src/client/pages/SignupPage.tsx +11 -2
- package/templates/saas/src/index.ts +28 -19
- package/templates/saas/vite.config.ts +1 -1
- package/templates/semantic-search/.jack.json +1 -5
- package/templates/semantic-search/src/index.ts +8 -4
- package/templates/semantic-search/src/jack-ai.ts +6 -2
- package/templates/semantic-search/src/jack-vectorize.ts +5 -1
package/package.json
CHANGED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cd command - print project path for shell integration
|
|
3
|
+
*
|
|
4
|
+
* Usage: jack cd <name>
|
|
5
|
+
*
|
|
6
|
+
* Outputs ONLY the absolute path to stdout (no other output).
|
|
7
|
+
* Error messages go to stderr.
|
|
8
|
+
*
|
|
9
|
+
* Exit codes:
|
|
10
|
+
* - 0: Success (path printed)
|
|
11
|
+
* - 1: No match, error, or project cannot be resolved
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { CloneCollisionError, ProjectNotFoundError, cloneProject } from "../lib/clone-core.ts";
|
|
16
|
+
import { getJackHome } from "../lib/config.ts";
|
|
17
|
+
import { fuzzyFilter } from "../lib/fuzzy.ts";
|
|
18
|
+
import { error } from "../lib/output.ts";
|
|
19
|
+
import { registerPath } from "../lib/paths-index.ts";
|
|
20
|
+
import { linkProject } from "../lib/project-link.ts";
|
|
21
|
+
import { type ResolvedProject, listAllProjects } from "../lib/project-resolver.ts";
|
|
22
|
+
import {
|
|
23
|
+
detectShell,
|
|
24
|
+
getRcFilePath,
|
|
25
|
+
isInstalled as isShellIntegrationInstalled,
|
|
26
|
+
} from "../lib/shell-integration.ts";
|
|
27
|
+
|
|
28
|
+
function maybeShowShellHint(path: string): void {
|
|
29
|
+
const shell = detectShell();
|
|
30
|
+
const rcFile = getRcFilePath(shell);
|
|
31
|
+
|
|
32
|
+
if (!rcFile || shell === "unknown") return;
|
|
33
|
+
if (isShellIntegrationInstalled(rcFile)) return;
|
|
34
|
+
console.error(`cd ${path}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Find the best matching project by name using fuzzy matching.
|
|
39
|
+
* Tiebreaker: most recently deployed wins (updatedAt descending).
|
|
40
|
+
*
|
|
41
|
+
* @param query - The search query
|
|
42
|
+
* @param projects - All resolved projects
|
|
43
|
+
* @returns The best matching project or null
|
|
44
|
+
*/
|
|
45
|
+
function findBestMatch(query: string, projects: ResolvedProject[]): ResolvedProject | null {
|
|
46
|
+
// Sort by updatedAt descending before fuzzy filter so tiebreaker favors recent
|
|
47
|
+
const sorted = [...projects].sort((a, b) => {
|
|
48
|
+
const aTime = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
49
|
+
const bTime = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
50
|
+
return bTime - aTime;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Fuzzy filter preserves sort order for items with equal scores
|
|
54
|
+
const matches = fuzzyFilter(query, sorted, (p) => p.name);
|
|
55
|
+
|
|
56
|
+
return matches[0] ?? null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default async function cd(projectName?: string): Promise<void> {
|
|
60
|
+
// Validate project name
|
|
61
|
+
if (!projectName) {
|
|
62
|
+
// No match: exit 1, no message (shell wrapper handles it)
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let projects: ResolvedProject[];
|
|
67
|
+
|
|
68
|
+
// Fetch all projects (local + cloud)
|
|
69
|
+
try {
|
|
70
|
+
projects = await listAllProjects();
|
|
71
|
+
} catch (err) {
|
|
72
|
+
// Network timeout
|
|
73
|
+
error("Could not reach cloud. Check your connection.");
|
|
74
|
+
if (process.env.DEBUG || process.argv.includes("--debug")) {
|
|
75
|
+
console.error("Debug:", err);
|
|
76
|
+
}
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Find best match using fuzzy matching
|
|
81
|
+
const match = findBestMatch(projectName, projects);
|
|
82
|
+
|
|
83
|
+
if (!match) {
|
|
84
|
+
// No match: exit 1, no message
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check if project has local path
|
|
89
|
+
if (match.localPath) {
|
|
90
|
+
// Local copy exists - print path and exit
|
|
91
|
+
console.log(match.localPath);
|
|
92
|
+
maybeShowShellHint(match.localPath);
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Cloud-only project: check deploy mode
|
|
97
|
+
if (match.deployMode === "managed") {
|
|
98
|
+
// Managed cloud-only: auto-clone to JACK_HOME
|
|
99
|
+
const jackHome = getJackHome();
|
|
100
|
+
const targetDir = join(jackHome, match.slug || match.name);
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const result = await cloneProject(match.slug || match.name, targetDir, {
|
|
104
|
+
silent: true,
|
|
105
|
+
skipPrompts: true,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Link and register the cloned project
|
|
109
|
+
if (result.projectId) {
|
|
110
|
+
await linkProject(result.path, result.projectId, "managed");
|
|
111
|
+
await registerPath(result.projectId, result.path);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Print the cloned path
|
|
115
|
+
console.log(result.path);
|
|
116
|
+
maybeShowShellHint(result.path);
|
|
117
|
+
process.exit(0);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
if (err instanceof ProjectNotFoundError) {
|
|
120
|
+
// Project deleted from cloud
|
|
121
|
+
error(`Project '${match.name}' no longer exists in cloud.`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (err instanceof CloneCollisionError) {
|
|
126
|
+
// Directory exists but not linked - help user decide what to do
|
|
127
|
+
error("Directory exists but isn't linked to this project");
|
|
128
|
+
console.error("");
|
|
129
|
+
console.error(` cd ${err.targetDir}`);
|
|
130
|
+
console.error("");
|
|
131
|
+
console.error(" jack link keep local files, connect to cloud");
|
|
132
|
+
console.error(" jack clone replace local with cloud version");
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Check for "no source backup" error (orphan project)
|
|
137
|
+
const errMsg = err instanceof Error ? err.message : "";
|
|
138
|
+
if (errMsg.includes("source not found")) {
|
|
139
|
+
error(`'${match.name}' has no cloud backup.`);
|
|
140
|
+
console.error(" This project was created before source backup was enabled.");
|
|
141
|
+
console.error(` Remove it with: jack down ${match.name}`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Other clone errors (network, etc.)
|
|
146
|
+
error("Could not reach cloud. Check your connection.");
|
|
147
|
+
if (process.env.DEBUG || process.argv.includes("--debug")) {
|
|
148
|
+
console.error("Debug:", err);
|
|
149
|
+
}
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// BYO cloud-only: error with guidance
|
|
155
|
+
if (match.deployMode === "byo") {
|
|
156
|
+
error(`'${match.name}' is a BYO project with no local copy.`);
|
|
157
|
+
console.error(" Clone it manually or check your backup.");
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Unknown state (should not happen)
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
package/src/commands/clone.ts
CHANGED
|
@@ -1,41 +1,51 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
+
import { CloneCollisionError, type CloneReporter, cloneProject } from "../lib/clone-core.ts";
|
|
4
|
+
import { downloadProjectSource } from "../lib/control-plane.ts";
|
|
5
|
+
import { extractZipToDirectory } from "../lib/zip-utils.ts";
|
|
3
6
|
import { isCancel, promptSelectValue } from "../lib/hooks.ts";
|
|
4
|
-
import { downloadProjectSource, fetchProjectTags } from "../lib/control-plane.ts";
|
|
5
|
-
import { formatSize } from "../lib/format.ts";
|
|
6
7
|
import { box, error, info, spinner, success } from "../lib/output.ts";
|
|
7
|
-
import {
|
|
8
|
-
import { linkProject, updateProjectLink } from "../lib/project-link.ts";
|
|
8
|
+
import { readProjectLink } from "../lib/project-link.ts";
|
|
9
9
|
import { resolveProject } from "../lib/project-resolver.ts";
|
|
10
|
-
import { cloneFromCloud, getRemoteManifest } from "../lib/storage/index.ts";
|
|
11
|
-
import { extractZipToDirectory } from "../lib/zip-utils.ts";
|
|
12
10
|
|
|
13
11
|
export interface CloneFlags {
|
|
14
12
|
as?: string;
|
|
13
|
+
force?: boolean;
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
export default async function clone(projectName?: string, flags: CloneFlags = {}): Promise<void> {
|
|
17
|
+
// Check if we're in a linked directory and should pull/update
|
|
18
|
+
const currentLink = await readProjectLink(process.cwd());
|
|
19
|
+
|
|
20
|
+
if (!projectName && currentLink?.deploy_mode === "managed") {
|
|
21
|
+
// No project name, but in a managed linked directory - pull latest code
|
|
22
|
+
await pullCurrentProject(currentLink.project_id, flags);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
18
26
|
// Validate project name
|
|
19
27
|
if (!projectName) {
|
|
20
28
|
error("Project name required");
|
|
21
29
|
info("Usage: jack clone <project> [--as <directory>]");
|
|
30
|
+
info("Or run in a linked directory to pull latest code.");
|
|
22
31
|
process.exit(1);
|
|
23
32
|
}
|
|
24
33
|
|
|
25
34
|
// Determine target directory
|
|
26
35
|
const targetDir = resolve(flags.as ?? projectName);
|
|
36
|
+
const displayName = flags.as ?? projectName;
|
|
27
37
|
|
|
28
|
-
// Check if target directory exists
|
|
38
|
+
// Check if target directory exists and handle collision
|
|
29
39
|
if (existsSync(targetDir)) {
|
|
30
40
|
// If not TTY, error immediately
|
|
31
41
|
if (!process.stdout.isTTY) {
|
|
32
|
-
error(`Directory ${
|
|
42
|
+
error(`Directory ${displayName} already exists`);
|
|
33
43
|
process.exit(1);
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
// Prompt user for action
|
|
37
47
|
const action = await promptSelectValue(
|
|
38
|
-
`Directory ${
|
|
48
|
+
`Directory ${displayName} already exists. What would you like to do?`,
|
|
39
49
|
[
|
|
40
50
|
{ value: "overwrite", label: "Overwrite (delete and recreate)" },
|
|
41
51
|
{ value: "merge", label: "Merge (keep existing files)" },
|
|
@@ -52,78 +62,112 @@ export default async function clone(projectName?: string, flags: CloneFlags = {}
|
|
|
52
62
|
// Delete directory
|
|
53
63
|
await Bun.$`rm -rf ${targetDir}`.quiet();
|
|
54
64
|
}
|
|
65
|
+
// For "merge", we continue and let files be overwritten/added
|
|
55
66
|
}
|
|
56
67
|
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
// Create reporter for progress output
|
|
69
|
+
let currentSpinner: ReturnType<typeof spinner> | null = null;
|
|
70
|
+
|
|
71
|
+
const reporter: CloneReporter = {
|
|
72
|
+
onLookup: (name) => {
|
|
73
|
+
currentSpinner = spinner(`Looking up ${name}...`);
|
|
74
|
+
},
|
|
75
|
+
onLookupComplete: (found, isManaged) => {
|
|
76
|
+
if (found && isManaged) {
|
|
77
|
+
currentSpinner?.success("Found on jack cloud");
|
|
78
|
+
} else {
|
|
79
|
+
currentSpinner?.stop();
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
onDownloadStart: (source, details) => {
|
|
83
|
+
if (source === "cloud") {
|
|
84
|
+
currentSpinner = spinner("Downloading from jack cloud...");
|
|
85
|
+
} else {
|
|
86
|
+
// BYO mode - show file count info first, then start download spinner
|
|
87
|
+
if (details) {
|
|
88
|
+
success(`Found ${details}`);
|
|
89
|
+
}
|
|
90
|
+
currentSpinner = spinner("Downloading...");
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
onDownloadComplete: (fileCount, displayPath) => {
|
|
94
|
+
currentSpinner?.success(`Restored ${fileCount} file(s) to ${displayPath}`);
|
|
95
|
+
},
|
|
96
|
+
onDownloadError: (err) => {
|
|
97
|
+
currentSpinner?.error("Download failed");
|
|
98
|
+
error(err);
|
|
99
|
+
},
|
|
100
|
+
onTagsRestored: (count) => {
|
|
101
|
+
info(`Restored ${count} tag(s)`);
|
|
102
|
+
},
|
|
103
|
+
};
|
|
60
104
|
|
|
61
105
|
try {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const downloadSpin = spinner("Downloading from jack cloud...");
|
|
72
|
-
try {
|
|
73
|
-
const sourceZip = await downloadProjectSource(projectName);
|
|
74
|
-
const fileCount = await extractZipToDirectory(sourceZip, targetDir);
|
|
75
|
-
downloadSpin.success(`Restored ${fileCount} file(s) to ./${flags.as ?? projectName}/`);
|
|
76
|
-
} catch (err) {
|
|
77
|
-
downloadSpin.error("Download failed");
|
|
78
|
-
const message = err instanceof Error ? err.message : "Could not download project source";
|
|
79
|
-
error(message);
|
|
106
|
+
await cloneProject(projectName, targetDir, { silent: false, skipPrompts: false }, reporter);
|
|
107
|
+
|
|
108
|
+
// Show next steps
|
|
109
|
+
box("Next steps:", [`cd ${displayName}`, "bun install", "jack ship"]);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
if (err instanceof CloneCollisionError) {
|
|
112
|
+
// This shouldn't happen since we handle collision above, but just in case
|
|
113
|
+
error(err.message);
|
|
80
114
|
process.exit(1);
|
|
81
115
|
}
|
|
82
116
|
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
await updateProjectLink(targetDir, { tags: remoteTags });
|
|
92
|
-
info(`Restored ${remoteTags.length} tag(s)`);
|
|
117
|
+
// For ProjectNotFoundError and other errors
|
|
118
|
+
if (err instanceof Error) {
|
|
119
|
+
// Check if it's a "not found" error for BYO projects
|
|
120
|
+
if (err.message.includes("For BYO projects")) {
|
|
121
|
+
error(`Project not found: ${projectName}`);
|
|
122
|
+
info("For BYO projects, run 'jack sync' first to backup your project.");
|
|
123
|
+
} else {
|
|
124
|
+
error(err.message);
|
|
93
125
|
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
} else {
|
|
98
|
-
// BYO mode: use existing User R2 flow
|
|
99
|
-
spin.stop();
|
|
100
|
-
const fetchSpin = spinner(`Fetching from jack-storage/${projectName}/...`);
|
|
101
|
-
const manifest = await getRemoteManifest(projectName);
|
|
102
|
-
|
|
103
|
-
if (!manifest) {
|
|
104
|
-
fetchSpin.error(`Project not found: ${projectName}`);
|
|
105
|
-
info("For BYO projects, run 'jack sync' first to backup your project.");
|
|
106
|
-
process.exit(1);
|
|
126
|
+
} else {
|
|
127
|
+
error("Clone failed");
|
|
107
128
|
}
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
108
132
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Pull latest code from cloud into current linked directory
|
|
135
|
+
*/
|
|
136
|
+
async function pullCurrentProject(projectId: string, flags: CloneFlags): Promise<void> {
|
|
137
|
+
// Resolve project to get slug/name
|
|
138
|
+
const project = await resolveProject(projectId);
|
|
139
|
+
const projectName = project?.slug || project?.name || projectId;
|
|
112
140
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
141
|
+
if (!project) {
|
|
142
|
+
error("Could not resolve project from control plane");
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
116
145
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
146
|
+
// Confirm before overwriting (unless --force)
|
|
147
|
+
if (!flags.force && process.stdout.isTTY) {
|
|
148
|
+
const action = await promptSelectValue(`Pull latest code for ${projectName}?`, [
|
|
149
|
+
{ value: "yes", label: "Yes, overwrite local files" },
|
|
150
|
+
{ value: "cancel", label: "Cancel" },
|
|
151
|
+
]);
|
|
123
152
|
|
|
124
|
-
|
|
153
|
+
if (isCancel(action) || action === "cancel") {
|
|
154
|
+
info("Cancelled");
|
|
155
|
+
process.exit(0);
|
|
156
|
+
}
|
|
125
157
|
}
|
|
126
158
|
|
|
127
|
-
|
|
128
|
-
|
|
159
|
+
const currentSpinner = spinner("Downloading from jack cloud...");
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const sourceZip = await downloadProjectSource(projectName);
|
|
163
|
+
const fileCount = await extractZipToDirectory(sourceZip, process.cwd());
|
|
164
|
+
currentSpinner.success(`Updated ${fileCount} file(s)`);
|
|
165
|
+
success(`Pulled latest code for ${projectName}`);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
currentSpinner.error("Download failed");
|
|
168
|
+
if (err instanceof Error) {
|
|
169
|
+
error(err.message);
|
|
170
|
+
}
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
129
173
|
}
|