@getjack/jack 0.1.3 → 0.1.5
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/README.md +103 -0
- package/package.json +2 -6
- package/src/commands/agents.ts +9 -24
- package/src/commands/clone.ts +27 -0
- package/src/commands/down.ts +31 -57
- package/src/commands/feedback.ts +4 -5
- package/src/commands/link.ts +147 -0
- package/src/commands/logs.ts +8 -18
- package/src/commands/new.ts +7 -1
- package/src/commands/projects.ts +162 -105
- package/src/commands/secrets.ts +7 -6
- package/src/commands/services.ts +5 -4
- package/src/commands/tag.ts +282 -0
- package/src/commands/unlink.ts +30 -0
- package/src/index.ts +46 -1
- package/src/lib/auth/index.ts +2 -0
- package/src/lib/auth/store.ts +26 -2
- package/src/lib/binding-validator.ts +4 -13
- package/src/lib/build-helper.ts +93 -5
- package/src/lib/control-plane.ts +48 -0
- package/src/lib/deploy-mode.ts +1 -1
- package/src/lib/managed-deploy.ts +11 -1
- package/src/lib/managed-down.ts +7 -20
- package/src/lib/paths-index.test.ts +546 -0
- package/src/lib/paths-index.ts +310 -0
- package/src/lib/project-link.test.ts +459 -0
- package/src/lib/project-link.ts +279 -0
- package/src/lib/project-list.test.ts +581 -0
- package/src/lib/project-list.ts +445 -0
- package/src/lib/project-operations.ts +304 -183
- package/src/lib/project-resolver.ts +191 -211
- package/src/lib/tags.ts +389 -0
- package/src/lib/telemetry.ts +81 -168
- package/src/lib/zip-packager.ts +9 -0
- package/src/templates/index.ts +5 -3
- package/templates/api/.jack/template.json +4 -0
- package/templates/hello/.jack/template.json +4 -0
- package/templates/miniapp/.jack/template.json +4 -0
- package/templates/nextjs/.jack.json +28 -0
- package/templates/nextjs/app/globals.css +9 -0
- package/templates/nextjs/app/isr-test/page.tsx +22 -0
- package/templates/nextjs/app/layout.tsx +19 -0
- package/templates/nextjs/app/page.tsx +8 -0
- package/templates/nextjs/bun.lock +2232 -0
- package/templates/nextjs/cloudflare-env.d.ts +3 -0
- package/templates/nextjs/next-env.d.ts +6 -0
- package/templates/nextjs/next.config.ts +8 -0
- package/templates/nextjs/open-next.config.ts +6 -0
- package/templates/nextjs/package.json +24 -0
- package/templates/nextjs/public/_headers +2 -0
- package/templates/nextjs/tsconfig.json +44 -0
- package/templates/nextjs/wrangler.jsonc +17 -0
- package/src/lib/local-paths.test.ts +0 -902
- package/src/lib/local-paths.ts +0 -258
- package/src/lib/registry.ts +0 -181
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">jack</h1>
|
|
3
|
+
<p align="center"><strong>Ship before you forget why you started.</strong></p>
|
|
4
|
+
</p>
|
|
5
|
+
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://www.npmjs.com/package/@getjack/jack"><img src="https://img.shields.io/npm/v/@getjack/jack" alt="npm"></a>
|
|
8
|
+
<a href="https://github.com/getjack-org/jack/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="license"></a>
|
|
9
|
+
<a href="https://docs.getjack.org"><img src="https://img.shields.io/badge/docs-getjack.org-green" alt="docs"></a>
|
|
10
|
+
<a href="https://discord.gg/fb64krv48R"><img src="https://img.shields.io/badge/discord-community-5865F2" alt="discord"></a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<p align="center">
|
|
16
|
+
<img src="https://docs.getjack.org/jack-demo.gif" alt="jack demo" width="600">
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
You're vibecoding. Ideas are flowing. You want to ship.
|
|
22
|
+
|
|
23
|
+
But first: config files, deployment setup, secret management, debugging infrastructure errors...
|
|
24
|
+
|
|
25
|
+
**30 minutes later, the spark is gone.**
|
|
26
|
+
|
|
27
|
+
jack removes the friction between your idea and a live URL.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bunx @getjack/jack new my-app # → deployed. live. done.
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
That's it. Write code. Ship again with `jack ship`. Stay in flow.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Why jack
|
|
38
|
+
|
|
39
|
+
**Instant deployment** — `jack new` creates AND deploys. You have a live URL before your first commit.
|
|
40
|
+
|
|
41
|
+
**Works with your agent** — Claude Code, Cursor, Codex, whatever. Every project includes AI context files so your agent understands the stack from prompt one.
|
|
42
|
+
|
|
43
|
+
**Roaming secrets** — Configure once, use across all projects. No more per-project secret ceremony.
|
|
44
|
+
|
|
45
|
+
**Project tracking** — 100 experiments scattered everywhere? `jack ls` finds them all.
|
|
46
|
+
|
|
47
|
+
**No lock-in** — Standard config files, standard TypeScript. Your projects work without jack installed.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# One command to create and deploy
|
|
55
|
+
bunx @getjack/jack new my-app
|
|
56
|
+
|
|
57
|
+
# Or install globally
|
|
58
|
+
bun add -g @getjack/jack
|
|
59
|
+
jack new my-app
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
You'll need [Bun](https://bun.sh) and a Cloudflare account (free tier works).
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Commands
|
|
67
|
+
|
|
68
|
+
| Command | What it does |
|
|
69
|
+
|---------|--------------|
|
|
70
|
+
| `jack new <name>` | Create and deploy a new project |
|
|
71
|
+
| `jack ship` | Deploy current project |
|
|
72
|
+
| `jack ls` | Show all your projects |
|
|
73
|
+
| `jack open` | Open project in browser |
|
|
74
|
+
| `jack secrets` | Manage project secrets |
|
|
75
|
+
| `jack feedback` | Share feedback or report issues |
|
|
76
|
+
|
|
77
|
+
Full documentation at **[docs.getjack.org](https://docs.getjack.org)**
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Who This Is For
|
|
82
|
+
|
|
83
|
+
Builders who ship constantly. You spin up new projects all the time, and suddenly you have dozens scattered across your machine—each with its own env vars, secrets, and deploy setup you've already forgotten.
|
|
84
|
+
|
|
85
|
+
jack keeps you organized. One command to ship, one place to find everything, zero setup overhead. Stay creative, skip the ceremony.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Philosophy
|
|
90
|
+
|
|
91
|
+
In Gibson's *Neuromancer*, "jacking in" means plugging directly into cyberspace. The body becomes irrelevant—you're pure thought in the matrix.
|
|
92
|
+
|
|
93
|
+
jack handles the infrastructure so you stay in creative flow. The boring parts disappear. You just build.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
Apache-2.0
|
|
100
|
+
|
|
101
|
+
<p align="center">
|
|
102
|
+
<i>Every deployment friction point is a creative thought lost.</i>
|
|
103
|
+
</p>
|
package/package.json
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getjack/jack",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Ship before you forget why you started. The vibecoder's deployment CLI.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"jack": "./src/index.ts"
|
|
9
9
|
},
|
|
10
|
-
"files": [
|
|
11
|
-
"src",
|
|
12
|
-
"templates"
|
|
13
|
-
],
|
|
10
|
+
"files": ["src", "templates"],
|
|
14
11
|
"engines": {
|
|
15
12
|
"bun": ">=1.0.0"
|
|
16
13
|
},
|
|
@@ -49,7 +46,6 @@
|
|
|
49
46
|
"archiver": "^7.0.1",
|
|
50
47
|
"human-id": "^4.1.3",
|
|
51
48
|
"meow": "^14.0.0",
|
|
52
|
-
"posthog-node": "^5.17.4",
|
|
53
49
|
"yocto-spinner": "^1.0.0",
|
|
54
50
|
"zod": "^4.2.1"
|
|
55
51
|
}
|
package/src/commands/agents.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from "../lib/agents.ts";
|
|
18
18
|
import { readConfig } from "../lib/config.ts";
|
|
19
19
|
import { error, info, item, output as outputSpinner, success, warn } from "../lib/output.ts";
|
|
20
|
-
import {
|
|
20
|
+
import { readTemplateMetadata } from "../lib/project-link.ts";
|
|
21
21
|
import { getProjectNameFromDir } from "../lib/storage/index.ts";
|
|
22
22
|
import { resolveTemplate } from "../templates/index.ts";
|
|
23
23
|
import type { Template } from "../templates/types.ts";
|
|
@@ -116,7 +116,7 @@ async function scanAndPrompt(): Promise<void> {
|
|
|
116
116
|
const newAgents = detectionResult.detected.filter(({ id }) => !existingAgents[id]);
|
|
117
117
|
|
|
118
118
|
if (newAgents.length === 0) {
|
|
119
|
-
success("
|
|
119
|
+
success("All agents up to date");
|
|
120
120
|
await listAgents();
|
|
121
121
|
return;
|
|
122
122
|
}
|
|
@@ -331,19 +331,10 @@ async function preferAgentCommand(args: string[]): Promise<void> {
|
|
|
331
331
|
async function refreshAgentFilesCommand(options: AgentsOptions = {}): Promise<void> {
|
|
332
332
|
const projectDir = process.cwd();
|
|
333
333
|
let projectName: string;
|
|
334
|
-
let project = null;
|
|
335
334
|
|
|
336
335
|
if (options.project) {
|
|
337
|
-
// When --project is specified, we
|
|
338
|
-
// since localPath is no longer stored in the registry
|
|
336
|
+
// When --project is specified, verify we're in that project's directory
|
|
339
337
|
projectName = options.project;
|
|
340
|
-
project = await getProject(projectName);
|
|
341
|
-
|
|
342
|
-
if (!project) {
|
|
343
|
-
error(`Project "${projectName}" not found in registry`);
|
|
344
|
-
info("List projects with: jack projects list");
|
|
345
|
-
process.exit(1);
|
|
346
|
-
}
|
|
347
338
|
|
|
348
339
|
// Verify the current directory matches the project
|
|
349
340
|
try {
|
|
@@ -354,7 +345,7 @@ async function refreshAgentFilesCommand(options: AgentsOptions = {}): Promise<vo
|
|
|
354
345
|
process.exit(1);
|
|
355
346
|
}
|
|
356
347
|
} catch {
|
|
357
|
-
error(
|
|
348
|
+
error("Current directory is not a valid project");
|
|
358
349
|
info(`Run this command from the ${projectName} project directory`);
|
|
359
350
|
process.exit(1);
|
|
360
351
|
}
|
|
@@ -370,17 +361,11 @@ async function refreshAgentFilesCommand(options: AgentsOptions = {}): Promise<vo
|
|
|
370
361
|
process.exit(1);
|
|
371
362
|
}
|
|
372
363
|
outputSpinner.stop();
|
|
373
|
-
|
|
374
|
-
// 2. Get project from registry to find template origin
|
|
375
|
-
project = await getProject(projectName);
|
|
376
|
-
if (!project) {
|
|
377
|
-
error(`Project "${projectName}" not found in registry`);
|
|
378
|
-
info("List projects with: jack projects list");
|
|
379
|
-
process.exit(1);
|
|
380
|
-
}
|
|
381
364
|
}
|
|
382
365
|
|
|
383
|
-
|
|
366
|
+
// 2. Read template metadata from .jack/template.json
|
|
367
|
+
const templateMetadata = await readTemplateMetadata(projectDir);
|
|
368
|
+
if (!templateMetadata) {
|
|
384
369
|
error("No template lineage found for this project");
|
|
385
370
|
info("This project was created before lineage tracking was added.");
|
|
386
371
|
info("Re-create the project with `jack new` to enable refresh.");
|
|
@@ -391,10 +376,10 @@ async function refreshAgentFilesCommand(options: AgentsOptions = {}): Promise<vo
|
|
|
391
376
|
outputSpinner.start("Loading template...");
|
|
392
377
|
let template: Template;
|
|
393
378
|
try {
|
|
394
|
-
template = await resolveTemplate(
|
|
379
|
+
template = await resolveTemplate(templateMetadata.name);
|
|
395
380
|
} catch (err) {
|
|
396
381
|
outputSpinner.stop();
|
|
397
|
-
error(`Failed to load template: ${
|
|
382
|
+
error(`Failed to load template: ${templateMetadata.name}`);
|
|
398
383
|
if (err instanceof Error) {
|
|
399
384
|
info(err.message);
|
|
400
385
|
}
|
package/src/commands/clone.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { select } from "@inquirer/prompts";
|
|
4
|
+
import { fetchProjectTags } from "../lib/control-plane.ts";
|
|
4
5
|
import { formatSize } from "../lib/format.ts";
|
|
5
6
|
import { box, error, info, spinner, success } from "../lib/output.ts";
|
|
7
|
+
import { registerPath } from "../lib/paths-index.ts";
|
|
8
|
+
import { linkProject, updateProjectLink } from "../lib/project-link.ts";
|
|
9
|
+
import { resolveProject } from "../lib/project-resolver.ts";
|
|
6
10
|
import { cloneFromCloud, getRemoteManifest } from "../lib/storage/index.ts";
|
|
7
11
|
|
|
8
12
|
export interface CloneFlags {
|
|
@@ -74,6 +78,29 @@ export default async function clone(projectName?: string, flags: CloneFlags = {}
|
|
|
74
78
|
|
|
75
79
|
downloadSpin.success(`Restored to ./${flags.as ?? projectName}/`);
|
|
76
80
|
|
|
81
|
+
// Link project to control plane if it's a managed project
|
|
82
|
+
try {
|
|
83
|
+
const project = await resolveProject(projectName);
|
|
84
|
+
if (project?.sources.controlPlane && project.remote?.projectId) {
|
|
85
|
+
// Managed project - link with control plane project ID
|
|
86
|
+
await linkProject(targetDir, project.remote.projectId, "managed");
|
|
87
|
+
await registerPath(project.remote.projectId, targetDir);
|
|
88
|
+
|
|
89
|
+
// Fetch and restore tags from control plane
|
|
90
|
+
try {
|
|
91
|
+
const remoteTags = await fetchProjectTags(project.remote.projectId);
|
|
92
|
+
if (remoteTags.length > 0) {
|
|
93
|
+
await updateProjectLink(targetDir, { tags: remoteTags });
|
|
94
|
+
info(`Restored ${remoteTags.length} tag(s)`);
|
|
95
|
+
}
|
|
96
|
+
} catch {
|
|
97
|
+
// Silent fail - tag restoration is non-critical
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// Not a control plane project or offline - continue without linking
|
|
102
|
+
}
|
|
103
|
+
|
|
77
104
|
// Show next steps
|
|
78
105
|
box("Next steps:", [`cd ${flags.as ?? projectName}`, "bun install", "jack ship"]);
|
|
79
106
|
}
|
package/src/commands/down.ts
CHANGED
|
@@ -9,8 +9,8 @@ import { fetchProjectResources } from "../lib/control-plane.ts";
|
|
|
9
9
|
import { promptSelect } from "../lib/hooks.ts";
|
|
10
10
|
import { managedDown } from "../lib/managed-down.ts";
|
|
11
11
|
import { error, info, item, output, success, warn } from "../lib/output.ts";
|
|
12
|
+
import { type LocalProjectLink, readProjectLink } from "../lib/project-link.ts";
|
|
12
13
|
import { resolveProject } from "../lib/project-resolver.ts";
|
|
13
|
-
import { type Project, getProject, updateProject } from "../lib/registry.ts";
|
|
14
14
|
import { parseWranglerResources } from "../lib/resources.ts";
|
|
15
15
|
import { deleteCloudProject, getProjectNameFromDir } from "../lib/storage/index.ts";
|
|
16
16
|
|
|
@@ -19,11 +19,14 @@ import { deleteCloudProject, getProjectNameFromDir } from "../lib/storage/index.
|
|
|
19
19
|
* For managed projects: fetch from control plane.
|
|
20
20
|
* For BYO projects: parse from wrangler.jsonc in cwd.
|
|
21
21
|
*/
|
|
22
|
-
async function resolveDatabaseName(
|
|
22
|
+
async function resolveDatabaseName(
|
|
23
|
+
link: LocalProjectLink | null,
|
|
24
|
+
projectName: string,
|
|
25
|
+
): Promise<string | null> {
|
|
23
26
|
// For managed projects, fetch from control plane
|
|
24
|
-
if (
|
|
27
|
+
if (link?.deploy_mode === "managed") {
|
|
25
28
|
try {
|
|
26
|
-
const resources = await fetchProjectResources(
|
|
29
|
+
const resources = await fetchProjectResources(link.project_id);
|
|
27
30
|
const d1 = resources.find((r) => r.resource_type === "d1");
|
|
28
31
|
return d1?.resource_name || null;
|
|
29
32
|
} catch {
|
|
@@ -70,50 +73,39 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
70
73
|
}
|
|
71
74
|
}
|
|
72
75
|
|
|
73
|
-
// Resolve project from all sources (
|
|
76
|
+
// Resolve project from all sources (local link + control plane)
|
|
74
77
|
const resolved = await resolveProject(name);
|
|
75
78
|
|
|
79
|
+
// Read local project link
|
|
80
|
+
const link = await readProjectLink(process.cwd());
|
|
81
|
+
|
|
76
82
|
// Check if found only on control plane (orphaned managed project)
|
|
77
|
-
if (resolved?.sources.controlPlane && !resolved.sources.
|
|
83
|
+
if (resolved?.sources.controlPlane && !resolved.sources.filesystem) {
|
|
78
84
|
console.error("");
|
|
79
85
|
info(`Found "${name}" on jack cloud, linking locally...`);
|
|
80
86
|
}
|
|
81
87
|
|
|
82
|
-
|
|
83
|
-
const project = await getProject(name);
|
|
84
|
-
|
|
85
|
-
if (!resolved && !project) {
|
|
88
|
+
if (!resolved && !link) {
|
|
86
89
|
// Not found anywhere
|
|
87
90
|
warn(`Project '${name}' not found`);
|
|
88
91
|
info("Will attempt to undeploy if deployed");
|
|
89
92
|
}
|
|
90
93
|
|
|
91
|
-
// Check if this is a managed project (
|
|
92
|
-
const isManaged =
|
|
93
|
-
resolved?.remote?.projectId ||
|
|
94
|
-
(project?.deploy_mode === "managed" && project.remote?.project_id);
|
|
94
|
+
// Check if this is a managed project (from link or resolved data)
|
|
95
|
+
const isManaged = link?.deploy_mode === "managed" || resolved?.remote?.projectId;
|
|
95
96
|
|
|
96
97
|
if (isManaged) {
|
|
97
|
-
//
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
resolved?.remote && resolved.url
|
|
106
|
-
? {
|
|
107
|
-
project_id: resolved.remote.projectId,
|
|
108
|
-
project_slug: resolved.slug,
|
|
109
|
-
org_id: resolved.remote.orgId,
|
|
110
|
-
runjack_url: resolved.url,
|
|
111
|
-
}
|
|
112
|
-
: undefined,
|
|
113
|
-
};
|
|
98
|
+
// Get the project ID from link or resolved data
|
|
99
|
+
const projectId = link?.project_id || resolved?.remote?.projectId;
|
|
100
|
+
const runjackUrl = resolved?.url || null;
|
|
101
|
+
|
|
102
|
+
if (!projectId) {
|
|
103
|
+
error("Cannot determine project ID for managed deletion");
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
114
106
|
|
|
115
107
|
// Route to managed deletion flow
|
|
116
|
-
const deleteSuccess = await managedDown(
|
|
108
|
+
const deleteSuccess = await managedDown({ projectId, runjackUrl }, name, flags);
|
|
117
109
|
if (!deleteSuccess) {
|
|
118
110
|
process.exit(0); // User cancelled
|
|
119
111
|
}
|
|
@@ -144,14 +136,6 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
144
136
|
await deleteWorker(name);
|
|
145
137
|
output.stop();
|
|
146
138
|
|
|
147
|
-
// Update registry - keep entry but clear worker URL
|
|
148
|
-
if (project) {
|
|
149
|
-
await updateProject(name, {
|
|
150
|
-
workerUrl: null,
|
|
151
|
-
lastDeployed: null,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
139
|
console.error("");
|
|
156
140
|
success(`'${name}' undeployed`);
|
|
157
141
|
info("Databases and backups were not affected");
|
|
@@ -162,10 +146,10 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
162
146
|
// Interactive mode - show what will be affected
|
|
163
147
|
console.error("");
|
|
164
148
|
info(`Project: ${name}`);
|
|
165
|
-
if (
|
|
166
|
-
item(`URL: ${
|
|
149
|
+
if (resolved?.url) {
|
|
150
|
+
item(`URL: ${resolved.url}`);
|
|
167
151
|
}
|
|
168
|
-
const dbName =
|
|
152
|
+
const dbName = await resolveDatabaseName(link, name);
|
|
169
153
|
if (dbName) {
|
|
170
154
|
item(`Database: ${dbName}`);
|
|
171
155
|
}
|
|
@@ -223,12 +207,10 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
223
207
|
|
|
224
208
|
// Handle backup deletion
|
|
225
209
|
let shouldDeleteR2 = false;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
shouldDeleteR2 = deleteR2Action === 0;
|
|
231
|
-
}
|
|
210
|
+
console.error("");
|
|
211
|
+
info("Delete backup for this project?");
|
|
212
|
+
const deleteR2Action = await promptSelect(["Yes", "No"]);
|
|
213
|
+
shouldDeleteR2 = deleteR2Action === 0;
|
|
232
214
|
|
|
233
215
|
// Execute deletions
|
|
234
216
|
console.error("");
|
|
@@ -279,14 +261,6 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
279
261
|
}
|
|
280
262
|
}
|
|
281
263
|
|
|
282
|
-
// Update registry - keep entry but clear worker URL
|
|
283
|
-
if (project) {
|
|
284
|
-
await updateProject(name, {
|
|
285
|
-
workerUrl: null,
|
|
286
|
-
lastDeployed: null,
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
|
|
290
264
|
console.error("");
|
|
291
265
|
success(`Project '${name}' undeployed`);
|
|
292
266
|
console.error("");
|
package/src/commands/feedback.ts
CHANGED
|
@@ -9,7 +9,7 @@ import pkg from "../../package.json";
|
|
|
9
9
|
import { getCredentials } from "../lib/auth/store.ts";
|
|
10
10
|
import { getControlApiUrl } from "../lib/control-plane.ts";
|
|
11
11
|
import { error, info, output, success } from "../lib/output.ts";
|
|
12
|
-
import {
|
|
12
|
+
import { getDeployMode, readProjectLink } from "../lib/project-link.ts";
|
|
13
13
|
import { getProjectNameFromDir } from "../lib/storage/index.ts";
|
|
14
14
|
import { getTelemetryConfig } from "../lib/telemetry.ts";
|
|
15
15
|
|
|
@@ -171,10 +171,9 @@ async function collectMetadata(attachPersonalInfo: boolean): Promise<FeedbackMet
|
|
|
171
171
|
if (attachPersonalInfo) {
|
|
172
172
|
try {
|
|
173
173
|
projectName = await getProjectNameFromDir(process.cwd());
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
174
|
+
// Read deploy mode from .jack/project.json
|
|
175
|
+
const link = await readProjectLink(process.cwd());
|
|
176
|
+
deployMode = link?.deploy_mode ?? null;
|
|
178
177
|
} catch {
|
|
179
178
|
// Not in a project directory, that's fine
|
|
180
179
|
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jack link - Link current directory to a jack cloud project or create BYO link
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* jack link my-api Link to existing managed project
|
|
6
|
+
* jack link --byo Create BYO link (generates local ID)
|
|
7
|
+
* jack link Interactive: prompts for project selection if logged in
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { select } from "@inquirer/prompts";
|
|
12
|
+
import { isLoggedIn } from "../lib/auth/index.ts";
|
|
13
|
+
import {
|
|
14
|
+
type ManagedProject,
|
|
15
|
+
findProjectBySlug,
|
|
16
|
+
listManagedProjects,
|
|
17
|
+
} from "../lib/control-plane.ts";
|
|
18
|
+
import { error, info, output, success } from "../lib/output.ts";
|
|
19
|
+
import { registerPath } from "../lib/paths-index.ts";
|
|
20
|
+
import { generateByoProjectId, linkProject, readProjectLink } from "../lib/project-link.ts";
|
|
21
|
+
|
|
22
|
+
export interface LinkFlags {
|
|
23
|
+
byo?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default async function link(projectName?: string, flags: LinkFlags = {}): Promise<void> {
|
|
27
|
+
// Check if already linked
|
|
28
|
+
const existingLink = await readProjectLink(process.cwd());
|
|
29
|
+
if (existingLink) {
|
|
30
|
+
error("This directory is already linked");
|
|
31
|
+
info(`Project ID: ${existingLink.project_id}`);
|
|
32
|
+
info("To re-link, first run: jack unlink");
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check for wrangler config
|
|
37
|
+
const hasWranglerConfig =
|
|
38
|
+
existsSync("wrangler.jsonc") || existsSync("wrangler.json") || existsSync("wrangler.toml");
|
|
39
|
+
|
|
40
|
+
if (!hasWranglerConfig) {
|
|
41
|
+
error("No wrangler config found");
|
|
42
|
+
info("Run this from a jack project directory");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// BYO mode - generate local ID
|
|
47
|
+
if (flags.byo) {
|
|
48
|
+
const projectId = generateByoProjectId();
|
|
49
|
+
output.start("Creating BYO link...");
|
|
50
|
+
await linkProject(process.cwd(), projectId, "byo");
|
|
51
|
+
await registerPath(projectId, process.cwd());
|
|
52
|
+
output.stop();
|
|
53
|
+
success("Linked as BYO project");
|
|
54
|
+
info(`Project ID: ${projectId}`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check if logged in for managed mode
|
|
59
|
+
const loggedIn = await isLoggedIn();
|
|
60
|
+
|
|
61
|
+
if (!loggedIn && !projectName) {
|
|
62
|
+
// Not logged in and no project name - suggest options
|
|
63
|
+
error("Not logged in to jack cloud");
|
|
64
|
+
info("Login with: jack login");
|
|
65
|
+
info("Or create a BYO link: jack link --byo");
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// If project name provided, find it on control plane
|
|
70
|
+
if (projectName) {
|
|
71
|
+
if (!loggedIn) {
|
|
72
|
+
error("Login required to link managed projects");
|
|
73
|
+
info("Run: jack login");
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
output.start(`Finding project: ${projectName}...`);
|
|
78
|
+
let project: ManagedProject | null = null;
|
|
79
|
+
try {
|
|
80
|
+
project = await findProjectBySlug(projectName);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
output.stop();
|
|
83
|
+
error("Failed to find project");
|
|
84
|
+
if (err instanceof Error) {
|
|
85
|
+
info(err.message);
|
|
86
|
+
}
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
output.stop();
|
|
90
|
+
|
|
91
|
+
if (!project) {
|
|
92
|
+
error(`Project not found: ${projectName}`);
|
|
93
|
+
info("List your projects with: jack projects list");
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
output.start("Linking project...");
|
|
98
|
+
await linkProject(process.cwd(), project.id, "managed");
|
|
99
|
+
await registerPath(project.id, process.cwd());
|
|
100
|
+
output.stop();
|
|
101
|
+
success(`Linked to: ${projectName}`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Interactive mode - list and select project
|
|
106
|
+
output.start("Loading projects...");
|
|
107
|
+
let projects: ManagedProject[] = [];
|
|
108
|
+
try {
|
|
109
|
+
projects = await listManagedProjects();
|
|
110
|
+
} catch (err) {
|
|
111
|
+
output.stop();
|
|
112
|
+
error("Failed to load projects");
|
|
113
|
+
if (err instanceof Error) {
|
|
114
|
+
info(err.message);
|
|
115
|
+
}
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
output.stop();
|
|
119
|
+
|
|
120
|
+
if (projects.length === 0) {
|
|
121
|
+
error("No managed projects found");
|
|
122
|
+
info("Create one with: jack new");
|
|
123
|
+
info("Or link as BYO: jack link --byo");
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
console.error("");
|
|
128
|
+
const choice = await select({
|
|
129
|
+
message: "Select a project to link:",
|
|
130
|
+
choices: projects.map((p) => ({
|
|
131
|
+
value: p.id,
|
|
132
|
+
name: `${p.slug} (${p.status})`,
|
|
133
|
+
})),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const selected = projects.find((p) => p.id === choice);
|
|
137
|
+
if (!selected) {
|
|
138
|
+
error("No project selected");
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
output.start("Linking project...");
|
|
143
|
+
await linkProject(process.cwd(), selected.id, "managed");
|
|
144
|
+
await registerPath(selected.id, process.cwd());
|
|
145
|
+
output.stop();
|
|
146
|
+
success(`Linked to: ${selected.slug}`);
|
|
147
|
+
}
|
package/src/commands/logs.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { output } from "../lib/output.ts";
|
|
3
|
-
import {
|
|
4
|
-
import { getProjectNameFromDir } from "../lib/storage/index.ts";
|
|
3
|
+
import { getDeployMode } from "../lib/project-link.ts";
|
|
5
4
|
|
|
6
5
|
// Lines containing these strings will be filtered out
|
|
7
6
|
const FILTERED_PATTERNS = ["⛅️ wrangler"];
|
|
@@ -19,22 +18,13 @@ export default async function logs(): Promise<void> {
|
|
|
19
18
|
process.exit(1);
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
// Check if this is a managed project
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (projectName) {
|
|
31
|
-
const project = await getProject(projectName);
|
|
32
|
-
if (project?.deploy_mode === "managed") {
|
|
33
|
-
output.warn("Real-time logs not yet available for managed projects");
|
|
34
|
-
output.info("Logs are being collected - web UI coming soon");
|
|
35
|
-
output.info("Track progress: https://github.com/getjack-org/jack/issues/2");
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
21
|
+
// Check if this is a managed project (read from .jack/project.json)
|
|
22
|
+
const deployMode = await getDeployMode(process.cwd());
|
|
23
|
+
if (deployMode === "managed") {
|
|
24
|
+
output.warn("Real-time logs not yet available for managed projects");
|
|
25
|
+
output.info("Logs are being collected - web UI coming soon");
|
|
26
|
+
output.info("Track progress: https://github.com/getjack-org/jack/issues/2");
|
|
27
|
+
return;
|
|
38
28
|
}
|
|
39
29
|
|
|
40
30
|
// BYOC project - use wrangler tail
|
package/src/commands/new.ts
CHANGED
|
@@ -8,7 +8,13 @@ import { createProject } from "../lib/project-operations.ts";
|
|
|
8
8
|
|
|
9
9
|
export default async function newProject(
|
|
10
10
|
nameOrPhrase?: string,
|
|
11
|
-
options: {
|
|
11
|
+
options: {
|
|
12
|
+
template?: string;
|
|
13
|
+
intent?: string;
|
|
14
|
+
managed?: boolean;
|
|
15
|
+
byo?: boolean;
|
|
16
|
+
ci?: boolean;
|
|
17
|
+
} = {},
|
|
12
18
|
): Promise<void> {
|
|
13
19
|
// Immediate feedback
|
|
14
20
|
output.start("Starting...");
|