@getjack/jack 0.1.2 → 0.1.3

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.
Files changed (91) hide show
  1. package/package.json +54 -47
  2. package/src/commands/agents.ts +145 -10
  3. package/src/commands/down.ts +110 -102
  4. package/src/commands/feedback.ts +189 -0
  5. package/src/commands/init.ts +8 -12
  6. package/src/commands/login.ts +88 -0
  7. package/src/commands/logout.ts +14 -0
  8. package/src/commands/logs.ts +21 -0
  9. package/src/commands/mcp.ts +134 -7
  10. package/src/commands/new.ts +43 -17
  11. package/src/commands/open.ts +13 -6
  12. package/src/commands/projects.ts +269 -143
  13. package/src/commands/secrets.ts +413 -0
  14. package/src/commands/services.ts +96 -123
  15. package/src/commands/ship.ts +5 -1
  16. package/src/commands/whoami.ts +31 -0
  17. package/src/index.ts +218 -144
  18. package/src/lib/agent-files.ts +34 -0
  19. package/src/lib/agents.ts +390 -22
  20. package/src/lib/asset-hash.ts +50 -0
  21. package/src/lib/auth/client.ts +115 -0
  22. package/src/lib/auth/constants.ts +5 -0
  23. package/src/lib/auth/guard.ts +57 -0
  24. package/src/lib/auth/index.ts +18 -0
  25. package/src/lib/auth/store.ts +54 -0
  26. package/src/lib/binding-validator.ts +136 -0
  27. package/src/lib/build-helper.ts +211 -0
  28. package/src/lib/cloudflare-api.ts +24 -0
  29. package/src/lib/config.ts +5 -6
  30. package/src/lib/control-plane.ts +295 -0
  31. package/src/lib/debug.ts +3 -1
  32. package/src/lib/deploy-mode.ts +93 -0
  33. package/src/lib/deploy-upload.ts +92 -0
  34. package/src/lib/errors.ts +2 -0
  35. package/src/lib/github.ts +31 -1
  36. package/src/lib/hooks.ts +4 -12
  37. package/src/lib/intent.ts +88 -0
  38. package/src/lib/jsonc.ts +125 -0
  39. package/src/lib/local-paths.test.ts +902 -0
  40. package/src/lib/local-paths.ts +258 -0
  41. package/src/lib/managed-deploy.ts +175 -0
  42. package/src/lib/managed-down.ts +159 -0
  43. package/src/lib/mcp-config.ts +55 -34
  44. package/src/lib/names.ts +9 -29
  45. package/src/lib/project-operations.ts +676 -249
  46. package/src/lib/project-resolver.ts +476 -0
  47. package/src/lib/registry.ts +76 -37
  48. package/src/lib/resources.ts +196 -0
  49. package/src/lib/schema.ts +30 -1
  50. package/src/lib/storage/file-filter.ts +1 -0
  51. package/src/lib/storage/index.ts +5 -1
  52. package/src/lib/telemetry.ts +14 -0
  53. package/src/lib/tty.ts +15 -0
  54. package/src/lib/zip-packager.ts +255 -0
  55. package/src/mcp/resources/index.ts +8 -2
  56. package/src/mcp/server.ts +32 -4
  57. package/src/mcp/tools/index.ts +35 -13
  58. package/src/mcp/types.ts +6 -0
  59. package/src/mcp/utils.ts +1 -1
  60. package/src/templates/index.ts +42 -4
  61. package/src/templates/types.ts +13 -0
  62. package/templates/CLAUDE.md +166 -0
  63. package/templates/api/.jack.json +4 -0
  64. package/templates/api/bun.lock +1 -0
  65. package/templates/api/wrangler.jsonc +5 -0
  66. package/templates/hello/.jack.json +28 -0
  67. package/templates/hello/package.json +10 -0
  68. package/templates/hello/src/index.ts +11 -0
  69. package/templates/hello/tsconfig.json +11 -0
  70. package/templates/hello/wrangler.jsonc +5 -0
  71. package/templates/miniapp/.jack.json +15 -4
  72. package/templates/miniapp/bun.lock +135 -40
  73. package/templates/miniapp/index.html +1 -0
  74. package/templates/miniapp/package.json +3 -1
  75. package/templates/miniapp/public/.well-known/farcaster.json +7 -5
  76. package/templates/miniapp/public/icon.png +0 -0
  77. package/templates/miniapp/public/og.png +0 -0
  78. package/templates/miniapp/schema.sql +8 -0
  79. package/templates/miniapp/src/App.tsx +254 -3
  80. package/templates/miniapp/src/components/ShareSheet.tsx +147 -0
  81. package/templates/miniapp/src/hooks/useAI.ts +35 -0
  82. package/templates/miniapp/src/hooks/useGuestbook.ts +11 -1
  83. package/templates/miniapp/src/hooks/useShare.ts +76 -0
  84. package/templates/miniapp/src/index.css +15 -0
  85. package/templates/miniapp/src/lib/api.ts +2 -1
  86. package/templates/miniapp/src/worker.ts +515 -1
  87. package/templates/miniapp/wrangler.jsonc +15 -3
  88. package/LICENSE +0 -190
  89. package/README.md +0 -55
  90. package/src/commands/cloud.ts +0 -230
  91. package/templates/api/wrangler.toml +0 -3
@@ -1,74 +1,80 @@
1
- import { existsSync } from "node:fs";
2
1
  import { join } from "node:path";
3
- import { select } from "@inquirer/prompts";
2
+ import { fetchProjectResources } from "../lib/control-plane.ts";
4
3
  import { formatSize } from "../lib/format.ts";
4
+ import { promptSelect } from "../lib/hooks.ts";
5
5
  import { error, info, item, output as outputSpinner, success, warn } from "../lib/output.ts";
6
- import {
7
- type Project,
8
- getProject,
9
- getProjectDatabaseName,
10
- updateProjectDatabase,
11
- } from "../lib/registry.ts";
6
+ import { getProject } from "../lib/registry.ts";
7
+ import { parseWranglerResources } from "../lib/resources.ts";
12
8
  import {
13
9
  deleteDatabase,
14
10
  exportDatabase,
15
11
  generateExportFilename,
16
- getDatabaseInfo,
12
+ getDatabaseInfo as getWranglerDatabaseInfo,
17
13
  } from "../lib/services/db.ts";
18
14
  import { getProjectNameFromDir } from "../lib/storage/index.ts";
19
15
 
20
16
  /**
21
- * Get database name from wrangler.jsonc/toml file
22
- * Fallback when registry doesn't have the info
17
+ * Database info from control plane or wrangler config
23
18
  */
24
- async function getDatabaseFromWranglerConfig(projectPath: string): Promise<string | null> {
25
- // Try wrangler.jsonc first
26
- const jsoncPath = join(projectPath, "wrangler.jsonc");
27
- if (existsSync(jsoncPath)) {
28
- try {
29
- const content = await Bun.file(jsoncPath).text();
30
- // Remove comments for JSON parsing (simple approach)
31
- const jsonContent = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
32
- const config = JSON.parse(jsonContent);
33
- if (config.d1_databases?.[0]?.database_name) {
34
- return config.d1_databases[0].database_name;
35
- }
36
- } catch {
37
- // Ignore parse errors
38
- }
39
- }
19
+ interface ResolvedDatabaseInfo {
20
+ name: string;
21
+ id?: string;
22
+ source: "control-plane" | "wrangler";
23
+ }
40
24
 
41
- // Try wrangler.toml
42
- const tomlPath = join(projectPath, "wrangler.toml");
43
- if (existsSync(tomlPath)) {
44
- try {
45
- const content = await Bun.file(tomlPath).text();
46
- // Simple regex to extract database_name from [[d1_databases]] section
47
- const match = content.match(/database_name\s*=\s*"([^"]+)"/);
48
- if (match?.[1]) {
49
- return match[1];
50
- }
51
- } catch {
52
- // Ignore read errors
25
+ async function ensureLocalProjectContext(projectName: string): Promise<void> {
26
+ try {
27
+ const cwdProjectName = await getProjectNameFromDir(process.cwd());
28
+ if (cwdProjectName !== projectName) {
29
+ error(`Current directory is not the "${projectName}" project`);
30
+ info(`Run this command from the ${projectName} project directory`);
31
+ process.exit(1);
53
32
  }
33
+ } catch {
34
+ error("Could not determine project from current directory");
35
+ info(`Run this command from the ${projectName} project directory`);
36
+ process.exit(1);
54
37
  }
55
-
56
- return null;
57
38
  }
58
39
 
59
40
  /**
60
- * Get database name for a project, with fallback to wrangler config
41
+ * Get database info for a project.
42
+ * For managed: fetch from control plane
43
+ * For BYO: parse from wrangler.jsonc
61
44
  */
62
- async function resolveDbName(project: Project): Promise<string | null> {
63
- // First check registry
64
- const dbFromRegistry = getProjectDatabaseName(project);
65
- if (dbFromRegistry) {
66
- return dbFromRegistry;
45
+ async function resolveDatabaseInfo(projectName: string): Promise<ResolvedDatabaseInfo | null> {
46
+ const project = await getProject(projectName);
47
+
48
+ // For managed projects, fetch from control plane
49
+ if (project?.deploy_mode === "managed" && project.remote?.project_id) {
50
+ try {
51
+ const resources = await fetchProjectResources(project.remote.project_id);
52
+ const d1 = resources.find((r) => r.resource_type === "d1");
53
+ if (d1) {
54
+ return {
55
+ name: d1.resource_name,
56
+ id: d1.provider_id,
57
+ source: "control-plane",
58
+ };
59
+ }
60
+ } catch {
61
+ // Fall through to wrangler parsing
62
+ }
67
63
  }
68
64
 
69
- // Fallback: read from wrangler config file
70
- if (project.localPath && existsSync(project.localPath)) {
71
- return await getDatabaseFromWranglerConfig(project.localPath);
65
+ // For BYO or fallback, parse from wrangler config
66
+ try {
67
+ await ensureLocalProjectContext(projectName);
68
+ const resources = await parseWranglerResources(process.cwd());
69
+ if (resources.d1) {
70
+ return {
71
+ name: resources.d1.name,
72
+ id: resources.d1.id,
73
+ source: "wrangler",
74
+ };
75
+ }
76
+ } catch {
77
+ // No database found
72
78
  }
73
79
 
74
80
  return null;
@@ -147,29 +153,23 @@ async function resolveProjectName(options: ServiceOptions): Promise<string> {
147
153
  */
148
154
  async function dbInfo(options: ServiceOptions): Promise<void> {
149
155
  const projectName = await resolveProjectName(options);
150
- const project = await getProject(projectName);
151
-
152
- if (!project) {
153
- error(`Project "${projectName}" not found in registry`);
154
- info("List projects with: jack projects list");
155
- process.exit(1);
156
- }
157
-
158
- const dbName = await resolveDbName(project);
156
+ const dbInfo = await resolveDatabaseInfo(projectName);
159
157
 
160
- if (!dbName) {
158
+ if (!dbInfo) {
161
159
  console.error("");
162
- info("No database configured for this project");
160
+ error("No database found for this project.");
161
+ info("For managed projects, create a database with: jack services db create");
162
+ info("For BYO projects, add d1_databases to your wrangler.jsonc");
163
163
  console.error("");
164
164
  return;
165
165
  }
166
166
 
167
- // Fetch database info
167
+ // Fetch detailed database info via wrangler
168
168
  outputSpinner.start("Fetching database info...");
169
- const dbInfo = await getDatabaseInfo(dbName);
169
+ const wranglerDbInfo = await getWranglerDatabaseInfo(dbInfo.name);
170
170
  outputSpinner.stop();
171
171
 
172
- if (!dbInfo) {
172
+ if (!wranglerDbInfo) {
173
173
  console.error("");
174
174
  error("Database not found");
175
175
  info("It may have been deleted");
@@ -179,11 +179,14 @@ async function dbInfo(options: ServiceOptions): Promise<void> {
179
179
 
180
180
  // Display info
181
181
  console.error("");
182
- success(`Database: ${dbInfo.name}`);
182
+ success(`Database: ${wranglerDbInfo.name}`);
183
183
  console.error("");
184
- item(`Size: ${formatSize(dbInfo.sizeBytes)}`);
185
- item(`Tables: ${dbInfo.numTables}`);
186
- item(`ID: ${dbInfo.id}`);
184
+ item(`Size: ${formatSize(wranglerDbInfo.sizeBytes)}`);
185
+ item(`Tables: ${wranglerDbInfo.numTables}`);
186
+ item(`ID: ${dbInfo.id || wranglerDbInfo.id}`);
187
+ if (dbInfo.source === "control-plane") {
188
+ item("Source: managed (jack cloud)");
189
+ }
187
190
  console.error("");
188
191
  }
189
192
 
@@ -192,42 +195,27 @@ async function dbInfo(options: ServiceOptions): Promise<void> {
192
195
  */
193
196
  async function dbExport(options: ServiceOptions): Promise<void> {
194
197
  const projectName = await resolveProjectName(options);
195
- const project = await getProject(projectName);
196
-
197
- if (!project) {
198
- error(`Project "${projectName}" not found in registry`);
199
- info("List projects with: jack projects list");
200
- process.exit(1);
201
- }
198
+ const dbInfo = await resolveDatabaseInfo(projectName);
202
199
 
203
- const dbName = await resolveDbName(project);
204
-
205
- if (!dbName) {
200
+ if (!dbInfo) {
206
201
  console.error("");
207
- info("No database configured for this project");
202
+ error("No database found for this project.");
203
+ info("For managed projects, create a database with: jack services db create");
204
+ info("For BYO projects, add d1_databases to your wrangler.jsonc");
208
205
  console.error("");
209
206
  return;
210
207
  }
211
208
 
212
209
  // Generate filename
213
- const filename = generateExportFilename(dbName);
214
-
215
- // Determine output directory (project dir if in it, cwd otherwise)
216
- let outputDir = process.cwd();
217
- if (project.localPath && existsSync(project.localPath)) {
218
- // Check if we're in the project directory or subdirectory
219
- const cwd = process.cwd();
220
- if (cwd === project.localPath || cwd.startsWith(`${project.localPath}/`)) {
221
- outputDir = project.localPath;
222
- }
223
- }
210
+ const filename = generateExportFilename(dbInfo.name);
224
211
 
225
- const outputPath = join(outputDir, filename);
212
+ // Export to current directory
213
+ const outputPath = join(process.cwd(), filename);
226
214
 
227
215
  // Export
228
216
  outputSpinner.start("Exporting database...");
229
217
  try {
230
- await exportDatabase(dbName, outputPath);
218
+ await exportDatabase(dbInfo.name, outputPath);
231
219
  outputSpinner.stop();
232
220
 
233
221
  console.error("");
@@ -246,50 +234,38 @@ async function dbExport(options: ServiceOptions): Promise<void> {
246
234
  */
247
235
  async function dbDelete(options: ServiceOptions): Promise<void> {
248
236
  const projectName = await resolveProjectName(options);
249
- const project = await getProject(projectName);
250
-
251
- if (!project) {
252
- error(`Project "${projectName}" not found in registry`);
253
- info("List projects with: jack projects list");
254
- process.exit(1);
255
- }
256
-
257
- const dbName = await resolveDbName(project);
237
+ const dbInfo = await resolveDatabaseInfo(projectName);
258
238
 
259
- if (!dbName) {
239
+ if (!dbInfo) {
260
240
  console.error("");
261
- info("No database configured for this project");
241
+ error("No database found for this project.");
242
+ info("For managed projects, create a database with: jack services db create");
243
+ info("For BYO projects, add d1_databases to your wrangler.jsonc");
262
244
  console.error("");
263
245
  return;
264
246
  }
265
247
 
266
- // Get database info to show what will be deleted
248
+ // Get detailed database info to show what will be deleted
267
249
  outputSpinner.start("Fetching database info...");
268
- const dbInfo = await getDatabaseInfo(dbName);
250
+ const wranglerDbInfo = await getWranglerDatabaseInfo(dbInfo.name);
269
251
  outputSpinner.stop();
270
252
 
271
253
  // Show what will be deleted
272
254
  console.error("");
273
- info(`Database: ${dbName}`);
274
- if (dbInfo) {
275
- item(`Size: ${formatSize(dbInfo.sizeBytes)}`);
276
- item(`Tables: ${dbInfo.numTables}`);
255
+ info(`Database: ${dbInfo.name}`);
256
+ if (wranglerDbInfo) {
257
+ item(`Size: ${formatSize(wranglerDbInfo.sizeBytes)}`);
258
+ item(`Tables: ${wranglerDbInfo.numTables}`);
277
259
  }
278
260
  console.error("");
279
261
  warn("This will permanently delete the database and all its data");
280
262
  console.error("");
281
263
 
282
264
  // Confirm deletion
283
- console.error(" Esc to skip\n");
284
- const action = await select({
285
- message: `Delete database '${dbName}'?`,
286
- choices: [
287
- { name: "1. Yes", value: "yes" },
288
- { name: "2. No", value: "no" },
289
- ],
290
- });
291
-
292
- if (action === "no") {
265
+ console.error(` Delete database '${dbInfo.name}'?\n`);
266
+ const choice = await promptSelect(["Yes", "No"]);
267
+
268
+ if (choice !== 0) {
293
269
  info("Cancelled");
294
270
  return;
295
271
  }
@@ -297,12 +273,9 @@ async function dbDelete(options: ServiceOptions): Promise<void> {
297
273
  // Delete database
298
274
  outputSpinner.start("Deleting database...");
299
275
  try {
300
- await deleteDatabase(dbName);
276
+ await deleteDatabase(dbInfo.name);
301
277
  outputSpinner.stop();
302
278
 
303
- // Update registry (set db to null in services structure)
304
- await updateProjectDatabase(projectName, null);
305
-
306
279
  console.error("");
307
280
  success("Database deleted");
308
281
  console.error("");
@@ -2,7 +2,9 @@ import { getErrorDetails } from "../lib/errors.ts";
2
2
  import { output, spinner } from "../lib/output.ts";
3
3
  import { deployProject } from "../lib/project-operations.ts";
4
4
 
5
- export default async function ship(): Promise<void> {
5
+ export default async function ship(
6
+ options: { managed?: boolean; byo?: boolean } = {},
7
+ ): Promise<void> {
6
8
  const isCi = process.env.CI === "true" || process.env.CI === "1";
7
9
  try {
8
10
  const result = await deployProject({
@@ -20,6 +22,8 @@ export default async function ship(): Promise<void> {
20
22
  interactive: !isCi,
21
23
  includeSecrets: true,
22
24
  includeSync: true,
25
+ managed: options.managed,
26
+ byo: options.byo,
23
27
  });
24
28
 
25
29
  if (!result.workerUrl && result.deployOutput) {
@@ -0,0 +1,31 @@
1
+ import { getCredentials } from "../lib/auth/store.ts";
2
+ import { info, item, success } from "../lib/output.ts";
3
+
4
+ export default async function whoami(): Promise<void> {
5
+ const creds = await getCredentials();
6
+
7
+ if (!creds) {
8
+ info("Not logged in");
9
+ info("Run 'jack login' to sign in");
10
+ return;
11
+ }
12
+
13
+ console.error("");
14
+ success("Logged in");
15
+ item(`Email: ${creds.user.email}`);
16
+ item(`ID: ${creds.user.id}`);
17
+
18
+ if (creds.user.first_name) {
19
+ item(`Name: ${creds.user.first_name}${creds.user.last_name ? ` ${creds.user.last_name}` : ""}`);
20
+ }
21
+
22
+ const expiresIn = creds.expires_at - Math.floor(Date.now() / 1000);
23
+ if (expiresIn > 0) {
24
+ const hours = Math.floor(expiresIn / 3600);
25
+ const minutes = Math.floor((expiresIn % 3600) / 60);
26
+ item(`Token expires: ${hours}h ${minutes}m`);
27
+ } else {
28
+ item("Token: expired (will refresh on next request)");
29
+ }
30
+ console.error("");
31
+ }