@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/src/lib/local-paths.ts
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local Paths Index
|
|
3
|
-
*
|
|
4
|
-
* Tracks where projects live on the local filesystem.
|
|
5
|
-
* This is a cache - can be rebuilt by scanning directories.
|
|
6
|
-
*
|
|
7
|
-
* Design:
|
|
8
|
-
* - One project can have multiple local paths (forks, copies)
|
|
9
|
-
* - Paths are verified on read (deleted dirs are pruned)
|
|
10
|
-
* - Auto-registered when jack commands run from project dirs
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { existsSync } from "node:fs";
|
|
14
|
-
import { readdir } from "node:fs/promises";
|
|
15
|
-
import { join, resolve } from "node:path";
|
|
16
|
-
import { CONFIG_DIR } from "./config.ts";
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Local paths index structure
|
|
20
|
-
*/
|
|
21
|
-
export interface LocalPathsIndex {
|
|
22
|
-
version: 1;
|
|
23
|
-
/** Map of project name -> array of local paths */
|
|
24
|
-
paths: Record<string, string[]>;
|
|
25
|
-
/** Last time the index was updated */
|
|
26
|
-
updatedAt: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const INDEX_PATH = join(CONFIG_DIR, "local-paths.json");
|
|
30
|
-
|
|
31
|
-
/** Directories to skip when scanning */
|
|
32
|
-
const SKIP_DIRS = new Set([
|
|
33
|
-
"node_modules",
|
|
34
|
-
".git",
|
|
35
|
-
"dist",
|
|
36
|
-
"build",
|
|
37
|
-
".next",
|
|
38
|
-
".nuxt",
|
|
39
|
-
".output",
|
|
40
|
-
"coverage",
|
|
41
|
-
".turbo",
|
|
42
|
-
".cache",
|
|
43
|
-
]);
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Check if a directory has a wrangler config file
|
|
47
|
-
*/
|
|
48
|
-
function hasWranglerConfig(dir: string): boolean {
|
|
49
|
-
return (
|
|
50
|
-
existsSync(join(dir, "wrangler.jsonc")) ||
|
|
51
|
-
existsSync(join(dir, "wrangler.toml")) ||
|
|
52
|
-
existsSync(join(dir, "wrangler.json"))
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Read the local paths index from disk
|
|
58
|
-
*/
|
|
59
|
-
export async function readLocalPaths(): Promise<LocalPathsIndex> {
|
|
60
|
-
if (!existsSync(INDEX_PATH)) {
|
|
61
|
-
return { version: 1, paths: {}, updatedAt: new Date().toISOString() };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
return await Bun.file(INDEX_PATH).json();
|
|
66
|
-
} catch {
|
|
67
|
-
// Handle corrupted index file gracefully
|
|
68
|
-
return { version: 1, paths: {}, updatedAt: new Date().toISOString() };
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Write the local paths index to disk
|
|
74
|
-
*/
|
|
75
|
-
export async function writeLocalPaths(index: LocalPathsIndex): Promise<void> {
|
|
76
|
-
index.updatedAt = new Date().toISOString();
|
|
77
|
-
await Bun.write(INDEX_PATH, JSON.stringify(index, null, 2));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Register a local path for a project
|
|
82
|
-
* Idempotent - won't add duplicates
|
|
83
|
-
*/
|
|
84
|
-
export async function registerLocalPath(projectName: string, localPath: string): Promise<void> {
|
|
85
|
-
const absolutePath = resolve(localPath);
|
|
86
|
-
const index = await readLocalPaths();
|
|
87
|
-
|
|
88
|
-
if (!index.paths[projectName]) {
|
|
89
|
-
index.paths[projectName] = [];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Avoid duplicates
|
|
93
|
-
if (!index.paths[projectName].includes(absolutePath)) {
|
|
94
|
-
index.paths[projectName].push(absolutePath);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
await writeLocalPaths(index);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Remove a local path for a project
|
|
102
|
-
*/
|
|
103
|
-
export async function removeLocalPath(projectName: string, localPath: string): Promise<void> {
|
|
104
|
-
const absolutePath = resolve(localPath);
|
|
105
|
-
const index = await readLocalPaths();
|
|
106
|
-
|
|
107
|
-
if (index.paths[projectName]) {
|
|
108
|
-
index.paths[projectName] = index.paths[projectName].filter((p) => p !== absolutePath);
|
|
109
|
-
|
|
110
|
-
// Clean up empty arrays
|
|
111
|
-
if (index.paths[projectName].length === 0) {
|
|
112
|
-
delete index.paths[projectName];
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
await writeLocalPaths(index);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Get all local paths for a project, verified to exist
|
|
121
|
-
* Automatically prunes paths that no longer exist or lack wrangler config
|
|
122
|
-
*/
|
|
123
|
-
export async function getLocalPaths(projectName: string): Promise<string[]> {
|
|
124
|
-
const index = await readLocalPaths();
|
|
125
|
-
const paths = index.paths[projectName] || [];
|
|
126
|
-
|
|
127
|
-
// Verify paths exist and have wrangler config
|
|
128
|
-
const validPaths: string[] = [];
|
|
129
|
-
const invalidPaths: string[] = [];
|
|
130
|
-
|
|
131
|
-
for (const path of paths) {
|
|
132
|
-
if (hasWranglerConfig(path)) {
|
|
133
|
-
validPaths.push(path);
|
|
134
|
-
} else {
|
|
135
|
-
invalidPaths.push(path);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Prune invalid paths
|
|
140
|
-
if (invalidPaths.length > 0) {
|
|
141
|
-
index.paths[projectName] = validPaths;
|
|
142
|
-
if (validPaths.length === 0) {
|
|
143
|
-
delete index.paths[projectName];
|
|
144
|
-
}
|
|
145
|
-
await writeLocalPaths(index);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return validPaths;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Get all local paths for all projects, verified to exist
|
|
153
|
-
* Returns a map of projectName -> paths[]
|
|
154
|
-
*/
|
|
155
|
-
export async function getAllLocalPaths(): Promise<Record<string, string[]>> {
|
|
156
|
-
const index = await readLocalPaths();
|
|
157
|
-
const result: Record<string, string[]> = {};
|
|
158
|
-
let needsWrite = false;
|
|
159
|
-
|
|
160
|
-
for (const [projectName, paths] of Object.entries(index.paths)) {
|
|
161
|
-
const validPaths: string[] = [];
|
|
162
|
-
|
|
163
|
-
for (const path of paths) {
|
|
164
|
-
if (hasWranglerConfig(path)) {
|
|
165
|
-
validPaths.push(path);
|
|
166
|
-
} else {
|
|
167
|
-
needsWrite = true;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (validPaths.length > 0) {
|
|
172
|
-
result[projectName] = validPaths;
|
|
173
|
-
} else if (paths.length > 0) {
|
|
174
|
-
needsWrite = true;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Write back pruned index if needed
|
|
179
|
-
if (needsWrite) {
|
|
180
|
-
index.paths = result;
|
|
181
|
-
await writeLocalPaths(index);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return result;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Scan a directory recursively for jack projects
|
|
189
|
-
* Returns discovered projects with their paths
|
|
190
|
-
*/
|
|
191
|
-
export async function scanDirectoryForProjects(
|
|
192
|
-
rootDir: string,
|
|
193
|
-
maxDepth = 3,
|
|
194
|
-
): Promise<Array<{ name: string; path: string }>> {
|
|
195
|
-
const { getProjectNameFromDir } = await import("./storage/index.ts");
|
|
196
|
-
const discovered: Array<{ name: string; path: string }> = [];
|
|
197
|
-
const absoluteRoot = resolve(rootDir);
|
|
198
|
-
|
|
199
|
-
async function scan(dir: string, depth: number): Promise<void> {
|
|
200
|
-
if (depth > maxDepth) return;
|
|
201
|
-
|
|
202
|
-
// Check if this directory is a jack project
|
|
203
|
-
try {
|
|
204
|
-
const name = await getProjectNameFromDir(dir);
|
|
205
|
-
discovered.push({ name, path: dir });
|
|
206
|
-
return; // Don't scan subdirectories of projects
|
|
207
|
-
} catch {
|
|
208
|
-
// Not a project, continue scanning subdirectories
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Scan subdirectories
|
|
212
|
-
try {
|
|
213
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
214
|
-
|
|
215
|
-
for (const entry of entries) {
|
|
216
|
-
// Skip non-directories
|
|
217
|
-
if (!entry.isDirectory()) continue;
|
|
218
|
-
|
|
219
|
-
// Skip hidden directories and common non-project directories
|
|
220
|
-
if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) {
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const fullPath = join(dir, entry.name);
|
|
225
|
-
await scan(fullPath, depth + 1);
|
|
226
|
-
}
|
|
227
|
-
} catch {
|
|
228
|
-
// Permission denied or other error, skip silently
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
await scan(absoluteRoot, 0);
|
|
233
|
-
return discovered;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Register multiple discovered projects
|
|
238
|
-
* More efficient than calling registerLocalPath for each project
|
|
239
|
-
*/
|
|
240
|
-
export async function registerDiscoveredProjects(
|
|
241
|
-
projects: Array<{ name: string; path: string }>,
|
|
242
|
-
): Promise<void> {
|
|
243
|
-
const index = await readLocalPaths();
|
|
244
|
-
|
|
245
|
-
for (const { name, path } of projects) {
|
|
246
|
-
const absolutePath = resolve(path);
|
|
247
|
-
|
|
248
|
-
if (!index.paths[name]) {
|
|
249
|
-
index.paths[name] = [];
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (!index.paths[name].includes(absolutePath)) {
|
|
253
|
-
index.paths[name].push(absolutePath);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
await writeLocalPaths(index);
|
|
258
|
-
}
|
package/src/lib/registry.ts
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { CONFIG_DIR } from "./config.ts";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Deploy mode for a project
|
|
7
|
-
*/
|
|
8
|
-
export type DeployMode = "managed" | "byo";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Template origin tracking for agent file regeneration
|
|
12
|
-
*/
|
|
13
|
-
export interface TemplateOrigin {
|
|
14
|
-
type: "builtin" | "github";
|
|
15
|
-
name: string; // "miniapp", "api", or "user/repo"
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Remote metadata for managed projects
|
|
20
|
-
*/
|
|
21
|
-
export interface ManagedRemote {
|
|
22
|
-
project_id: string;
|
|
23
|
-
project_slug: string;
|
|
24
|
-
org_id: string;
|
|
25
|
-
runjack_url: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Project data stored in registry
|
|
30
|
-
*/
|
|
31
|
-
export interface Project {
|
|
32
|
-
workerUrl: string | null;
|
|
33
|
-
createdAt: string;
|
|
34
|
-
lastDeployed: string | null;
|
|
35
|
-
status?: "created" | "build_failed" | "live";
|
|
36
|
-
cloudflare?: {
|
|
37
|
-
accountId: string;
|
|
38
|
-
workerId: string;
|
|
39
|
-
};
|
|
40
|
-
template?: TemplateOrigin;
|
|
41
|
-
deploy_mode?: DeployMode;
|
|
42
|
-
remote?: ManagedRemote;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Project registry structure
|
|
47
|
-
*/
|
|
48
|
-
export interface Registry {
|
|
49
|
-
version: 2;
|
|
50
|
-
projects: Record<string, Project>;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export const REGISTRY_PATH = join(CONFIG_DIR, "projects.json");
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Migrate registry from v1 to v2
|
|
57
|
-
* - Removes localPath field (no longer tracked)
|
|
58
|
-
* - Removes resources.services.db field (fetch from control plane instead)
|
|
59
|
-
*/
|
|
60
|
-
async function migrateV1ToV2(v1Registry: {
|
|
61
|
-
version: 1;
|
|
62
|
-
projects: Record<string, unknown>;
|
|
63
|
-
}): Promise<Registry> {
|
|
64
|
-
const migrated: Registry = {
|
|
65
|
-
version: 2,
|
|
66
|
-
projects: {},
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
for (const [name, project] of Object.entries(v1Registry.projects)) {
|
|
70
|
-
const p = project as Record<string, unknown>;
|
|
71
|
-
// Remove localPath and resources, keep everything else
|
|
72
|
-
const { localPath, resources, ...rest } = p;
|
|
73
|
-
migrated.projects[name] = rest as unknown as Project;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return migrated;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Read project registry from disk
|
|
81
|
-
*/
|
|
82
|
-
export async function readRegistry(): Promise<Registry> {
|
|
83
|
-
if (!existsSync(REGISTRY_PATH)) {
|
|
84
|
-
return { version: 2, projects: {} };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
const raw = await Bun.file(REGISTRY_PATH).json();
|
|
89
|
-
|
|
90
|
-
// Auto-migrate v1 to v2
|
|
91
|
-
if (raw.version === 1) {
|
|
92
|
-
const migrated = await migrateV1ToV2(raw);
|
|
93
|
-
await writeRegistry(migrated);
|
|
94
|
-
return migrated;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Handle unversioned (legacy) registries
|
|
98
|
-
if (!raw.version) {
|
|
99
|
-
const migrated = await migrateV1ToV2({
|
|
100
|
-
version: 1,
|
|
101
|
-
projects: raw.projects || {},
|
|
102
|
-
});
|
|
103
|
-
await writeRegistry(migrated);
|
|
104
|
-
return migrated;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return raw as Registry;
|
|
108
|
-
} catch {
|
|
109
|
-
return { version: 2, projects: {} };
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Write project registry to disk
|
|
115
|
-
*/
|
|
116
|
-
export async function writeRegistry(registry: Registry): Promise<void> {
|
|
117
|
-
await Bun.write(REGISTRY_PATH, JSON.stringify(registry, null, 2));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Register or update a project in the registry
|
|
122
|
-
*/
|
|
123
|
-
export async function registerProject(name: string, data: Partial<Project>): Promise<void> {
|
|
124
|
-
const registry = await readRegistry();
|
|
125
|
-
const existing = registry.projects[name];
|
|
126
|
-
|
|
127
|
-
if (existing) {
|
|
128
|
-
registry.projects[name] = {
|
|
129
|
-
...existing,
|
|
130
|
-
...data,
|
|
131
|
-
};
|
|
132
|
-
} else {
|
|
133
|
-
registry.projects[name] = data as Project;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
await writeRegistry(registry);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Update project with partial data
|
|
141
|
-
*/
|
|
142
|
-
export async function updateProject(name: string, data: Partial<Project>): Promise<void> {
|
|
143
|
-
const registry = await readRegistry();
|
|
144
|
-
const existing = registry.projects[name];
|
|
145
|
-
|
|
146
|
-
if (!existing) {
|
|
147
|
-
throw new Error(`Project "${name}" not found in registry`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
registry.projects[name] = {
|
|
151
|
-
...existing,
|
|
152
|
-
...data,
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
await writeRegistry(registry);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Remove project from registry
|
|
160
|
-
*/
|
|
161
|
-
export async function removeProject(name: string): Promise<void> {
|
|
162
|
-
const registry = await readRegistry();
|
|
163
|
-
delete registry.projects[name];
|
|
164
|
-
await writeRegistry(registry);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Get single project from registry
|
|
169
|
-
*/
|
|
170
|
-
export async function getProject(name: string): Promise<Project | null> {
|
|
171
|
-
const registry = await readRegistry();
|
|
172
|
-
return registry.projects[name] ?? null;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Get all projects from registry
|
|
177
|
-
*/
|
|
178
|
-
export async function getAllProjects(): Promise<Record<string, Project>> {
|
|
179
|
-
const registry = await readRegistry();
|
|
180
|
-
return registry.projects;
|
|
181
|
-
}
|