@getjack/jack 0.1.10 → 0.1.12
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/lib/build-helper.ts +16 -1
- package/src/lib/hooks.ts +94 -20
- package/src/lib/project-operations.ts +32 -2
package/package.json
CHANGED
package/src/lib/build-helper.ts
CHANGED
|
@@ -7,6 +7,19 @@ import { JackError, JackErrorCode } from "./errors.ts";
|
|
|
7
7
|
import { parseJsonc } from "./jsonc.ts";
|
|
8
8
|
import type { OperationReporter } from "./project-operations.ts";
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Get the wrangler config file path for a project
|
|
12
|
+
*/
|
|
13
|
+
function getWranglerConfigPath(projectPath: string): string | null {
|
|
14
|
+
const configs = ["wrangler.jsonc", "wrangler.toml", "wrangler.json"];
|
|
15
|
+
for (const config of configs) {
|
|
16
|
+
if (existsSync(join(projectPath, config))) {
|
|
17
|
+
return config;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
export interface BuildOutput {
|
|
11
24
|
outDir: string;
|
|
12
25
|
entrypoint: string;
|
|
@@ -195,7 +208,9 @@ export async function buildProject(options: BuildOptions): Promise<BuildOutput>
|
|
|
195
208
|
// Run wrangler dry-run to build without deploying
|
|
196
209
|
reporter?.start("Bundling runtime...");
|
|
197
210
|
|
|
198
|
-
const
|
|
211
|
+
const configFile = getWranglerConfigPath(projectPath);
|
|
212
|
+
const configArg = configFile ? ["--config", configFile] : [];
|
|
213
|
+
const dryRunResult = await $`wrangler deploy ${configArg} --dry-run --outdir=${outDir}`
|
|
199
214
|
.cwd(projectPath)
|
|
200
215
|
.nothrow()
|
|
201
216
|
.quiet();
|
package/src/lib/hooks.ts
CHANGED
|
@@ -1,10 +1,44 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import * as readline from "node:readline";
|
|
3
4
|
import type { HookAction } from "../templates/types";
|
|
4
5
|
import { applyJsonWrite } from "./json-edit";
|
|
5
6
|
import { getSavedSecrets } from "./secrets";
|
|
6
7
|
import { restoreTty } from "./tty";
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Read multi-line JSON input from stdin
|
|
11
|
+
* User pastes JSON, then presses Enter on empty line to submit
|
|
12
|
+
*/
|
|
13
|
+
async function readMultilineJson(prompt: string): Promise<string> {
|
|
14
|
+
console.error(prompt);
|
|
15
|
+
console.error("(Paste JSON, then press Enter on empty line to submit)\n");
|
|
16
|
+
|
|
17
|
+
const rl = readline.createInterface({
|
|
18
|
+
input: process.stdin,
|
|
19
|
+
output: process.stderr,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const lines: string[] = [];
|
|
23
|
+
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
rl.on("line", (line) => {
|
|
26
|
+
if (line.trim() === "" && lines.length > 0) {
|
|
27
|
+
rl.close();
|
|
28
|
+
resolve(lines.join("\n"));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (line.trim() !== "") {
|
|
32
|
+
lines.push(line);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
rl.on("close", () => {
|
|
37
|
+
resolve(lines.join("\n"));
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
8
42
|
export interface HookContext {
|
|
9
43
|
domain?: string; // deployed domain (e.g., "my-app.username.workers.dev")
|
|
10
44
|
url?: string; // full deployed URL
|
|
@@ -147,14 +181,39 @@ function resolveHookPath(filePath: string, context: HookContext): string {
|
|
|
147
181
|
return join(context.projectDir, filePath);
|
|
148
182
|
}
|
|
149
183
|
|
|
150
|
-
function isAccountAssociation(value: unknown):
|
|
184
|
+
function isAccountAssociation(value: unknown): boolean {
|
|
151
185
|
if (!value || typeof value !== "object") return false;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
186
|
+
// Check direct format: { header, payload, signature }
|
|
187
|
+
const obj = value as Record<string, unknown>;
|
|
188
|
+
if (typeof obj.header === "string" && typeof obj.payload === "string" && typeof obj.signature === "string") {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
// Check nested format from Farcaster: { accountAssociation: { header, payload, signature } }
|
|
192
|
+
if (obj.accountAssociation && typeof obj.accountAssociation === "object") {
|
|
193
|
+
const inner = obj.accountAssociation as Record<string, unknown>;
|
|
194
|
+
return typeof inner.header === "string" && typeof inner.payload === "string" && typeof inner.signature === "string";
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Extract the accountAssociation object (handles both nested and flat formats)
|
|
201
|
+
*/
|
|
202
|
+
function extractAccountAssociation(value: unknown): { header: string; payload: string; signature: string } | null {
|
|
203
|
+
if (!value || typeof value !== "object") return null;
|
|
204
|
+
const obj = value as Record<string, unknown>;
|
|
205
|
+
// Direct format
|
|
206
|
+
if (typeof obj.header === "string" && typeof obj.payload === "string" && typeof obj.signature === "string") {
|
|
207
|
+
return { header: obj.header, payload: obj.payload, signature: obj.signature };
|
|
208
|
+
}
|
|
209
|
+
// Nested format from Farcaster
|
|
210
|
+
if (obj.accountAssociation && typeof obj.accountAssociation === "object") {
|
|
211
|
+
const inner = obj.accountAssociation as Record<string, unknown>;
|
|
212
|
+
if (typeof inner.header === "string" && typeof inner.payload === "string" && typeof inner.signature === "string") {
|
|
213
|
+
return { header: inner.header, payload: inner.payload, signature: inner.signature };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
158
217
|
}
|
|
159
218
|
|
|
160
219
|
/**
|
|
@@ -363,16 +422,21 @@ const actionHandlers: {
|
|
|
363
422
|
return true;
|
|
364
423
|
}
|
|
365
424
|
|
|
366
|
-
const { input } = await import("@inquirer/prompts");
|
|
367
|
-
|
|
368
425
|
let rawValue = "";
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
426
|
+
|
|
427
|
+
// Use multi-line input for JSON validation (handles paste from Farcaster etc.)
|
|
428
|
+
if (action.validate === "json" || action.validate === "accountAssociation") {
|
|
429
|
+
rawValue = await readMultilineJson(action.message);
|
|
430
|
+
} else {
|
|
431
|
+
const { input } = await import("@inquirer/prompts");
|
|
432
|
+
try {
|
|
433
|
+
rawValue = await input({ message: action.message });
|
|
434
|
+
} catch (err) {
|
|
435
|
+
if (err instanceof Error && err.name === "ExitPromptError") {
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
throw err;
|
|
374
439
|
}
|
|
375
|
-
throw err;
|
|
376
440
|
}
|
|
377
441
|
|
|
378
442
|
if (!rawValue.trim()) {
|
|
@@ -384,14 +448,24 @@ const actionHandlers: {
|
|
|
384
448
|
try {
|
|
385
449
|
parsedInput = JSON.parse(rawValue);
|
|
386
450
|
} catch {
|
|
387
|
-
|
|
388
|
-
|
|
451
|
+
// Try normalizing whitespace (handles some multi-line paste issues)
|
|
452
|
+
try {
|
|
453
|
+
const normalized = rawValue.replace(/\n\s*/g, "");
|
|
454
|
+
parsedInput = JSON.parse(normalized);
|
|
455
|
+
} catch {
|
|
456
|
+
ui.error("Invalid JSON input");
|
|
457
|
+
return action.required ? false : true;
|
|
458
|
+
}
|
|
389
459
|
}
|
|
390
460
|
}
|
|
391
461
|
|
|
392
|
-
if (action.validate === "accountAssociation"
|
|
393
|
-
|
|
394
|
-
|
|
462
|
+
if (action.validate === "accountAssociation") {
|
|
463
|
+
if (!isAccountAssociation(parsedInput)) {
|
|
464
|
+
ui.error("Invalid accountAssociation JSON (expected header, payload, signature)");
|
|
465
|
+
return action.required ? false : true;
|
|
466
|
+
}
|
|
467
|
+
// Extract the actual accountAssociation object (handles nested format from Farcaster)
|
|
468
|
+
parsedInput = extractAccountAssociation(parsedInput);
|
|
395
469
|
}
|
|
396
470
|
|
|
397
471
|
if (action.writeJson) {
|
|
@@ -168,6 +168,36 @@ const noopReporter: OperationReporter = {
|
|
|
168
168
|
box() {},
|
|
169
169
|
};
|
|
170
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Get the wrangler config file path for a project
|
|
173
|
+
* Returns the first found: wrangler.jsonc, wrangler.toml, wrangler.json
|
|
174
|
+
*/
|
|
175
|
+
function getWranglerConfigPath(projectPath: string): string | null {
|
|
176
|
+
const configs = ["wrangler.jsonc", "wrangler.toml", "wrangler.json"];
|
|
177
|
+
for (const config of configs) {
|
|
178
|
+
if (existsSync(join(projectPath, config))) {
|
|
179
|
+
return config;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Run wrangler deploy with explicit config to avoid parent directory conflicts
|
|
187
|
+
*/
|
|
188
|
+
async function runWranglerDeploy(
|
|
189
|
+
projectPath: string,
|
|
190
|
+
options: { dryRun?: boolean; outDir?: string } = {},
|
|
191
|
+
) {
|
|
192
|
+
const configFile = getWranglerConfigPath(projectPath);
|
|
193
|
+
const configArg = configFile ? ["--config", configFile] : [];
|
|
194
|
+
const dryRunArgs = options.dryRun
|
|
195
|
+
? ["--dry-run", ...(options.outDir ? ["--outdir", options.outDir] : [])]
|
|
196
|
+
: [];
|
|
197
|
+
|
|
198
|
+
return await $`wrangler deploy ${configArg} ${dryRunArgs}`.cwd(projectPath).nothrow().quiet();
|
|
199
|
+
}
|
|
200
|
+
|
|
171
201
|
/**
|
|
172
202
|
* Run bun install and managed project creation in parallel.
|
|
173
203
|
* Handles partial failures with cleanup.
|
|
@@ -1072,7 +1102,7 @@ export async function createProject(
|
|
|
1072
1102
|
|
|
1073
1103
|
reporter.start("Deploying...");
|
|
1074
1104
|
|
|
1075
|
-
const deployResult = await
|
|
1105
|
+
const deployResult = await runWranglerDeploy(targetDir);
|
|
1076
1106
|
|
|
1077
1107
|
if (deployResult.exitCode !== 0) {
|
|
1078
1108
|
reporter.stop();
|
|
@@ -1357,7 +1387,7 @@ export async function deployProject(options: DeployOptions = {}): Promise<Deploy
|
|
|
1357
1387
|
}
|
|
1358
1388
|
|
|
1359
1389
|
const spin = reporter.spinner("Deploying...");
|
|
1360
|
-
const result = await
|
|
1390
|
+
const result = await runWranglerDeploy(projectPath);
|
|
1361
1391
|
|
|
1362
1392
|
if (result.exitCode !== 0) {
|
|
1363
1393
|
spin.error("Deploy failed");
|