@webmaster-droid/cli 0.1.0 → 0.2.0
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/dist/index.js +252 -108
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { fileURLToPath } from "url";
|
|
|
9
9
|
import { Command } from "commander";
|
|
10
10
|
import { glob } from "glob";
|
|
11
11
|
import { parse as parse2 } from "@babel/parser";
|
|
12
|
-
import
|
|
12
|
+
import traverseModule from "@babel/traverse";
|
|
13
13
|
import * as t2 from "@babel/types";
|
|
14
14
|
import { createTwoFilesPatch } from "diff";
|
|
15
15
|
import { createJiti } from "jiti";
|
|
@@ -106,6 +106,19 @@ function transformEditableTextCodemod(source, filePath, cwd) {
|
|
|
106
106
|
|
|
107
107
|
// src/index.ts
|
|
108
108
|
var program = new Command();
|
|
109
|
+
var CLI_VERSION = "0.1.0-alpha.0";
|
|
110
|
+
var traverse2 = traverseModule.default ?? traverseModule;
|
|
111
|
+
function emitCliEnvelope(payload, isError = false) {
|
|
112
|
+
const out = JSON.stringify(payload, null, 2);
|
|
113
|
+
if (isError) {
|
|
114
|
+
console.error(out);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
console.log(out);
|
|
118
|
+
}
|
|
119
|
+
function errorToMessage(error) {
|
|
120
|
+
return error instanceof Error ? error.message : String(error);
|
|
121
|
+
}
|
|
109
122
|
async function ensureDir(filePath) {
|
|
110
123
|
await fs.mkdir(path2.dirname(filePath), { recursive: true });
|
|
111
124
|
}
|
|
@@ -113,14 +126,19 @@ async function readJson(filePath) {
|
|
|
113
126
|
const raw = await fs.readFile(filePath, "utf8");
|
|
114
127
|
return JSON.parse(raw);
|
|
115
128
|
}
|
|
116
|
-
program.name("webmaster-droid").description("Webmaster Droid CLI").version(
|
|
117
|
-
program.command("init").description("Initialize optional webmaster-droid config in current project").option("--framework <framework>", "framework", "next").option("--backend <backend>", "backend", "
|
|
129
|
+
program.name("webmaster-droid").description("Webmaster Droid CLI").version(CLI_VERSION);
|
|
130
|
+
program.command("init").description("Initialize optional webmaster-droid config in current project").option("--framework <framework>", "framework", "next").option("--backend <backend>", "backend (supabase|aws)", "supabase").option("--out <dir>", "output dir", ".").action(async (opts) => {
|
|
131
|
+
const backendRaw = String(opts.backend ?? "supabase").trim().toLowerCase();
|
|
132
|
+
if (backendRaw !== "supabase" && backendRaw !== "aws") {
|
|
133
|
+
throw new Error(`Unsupported backend '${opts.backend}'. Expected 'supabase' or 'aws'.`);
|
|
134
|
+
}
|
|
135
|
+
const backend = backendRaw;
|
|
118
136
|
const outDir = path2.resolve(process.cwd(), opts.out);
|
|
119
137
|
const configPath = path2.join(outDir, "webmaster-droid.config.ts");
|
|
120
138
|
await ensureDir(configPath);
|
|
121
139
|
const config = `export default {
|
|
122
140
|
framework: "${opts.framework}",
|
|
123
|
-
backend: "${
|
|
141
|
+
backend: "${backend}",
|
|
124
142
|
apiBaseUrlEnv: "NEXT_PUBLIC_AGENT_API_BASE_URL"
|
|
125
143
|
};
|
|
126
144
|
`;
|
|
@@ -139,13 +157,26 @@ program.command("init").description("Initialize optional webmaster-droid config
|
|
|
139
157
|
envExample,
|
|
140
158
|
[
|
|
141
159
|
"NEXT_PUBLIC_AGENT_API_BASE_URL=http://localhost:8787",
|
|
142
|
-
"
|
|
143
|
-
"
|
|
144
|
-
"
|
|
160
|
+
"",
|
|
161
|
+
"# Supabase (default backend)",
|
|
162
|
+
"NEXT_PUBLIC_SUPABASE_URL=",
|
|
163
|
+
"NEXT_PUBLIC_SUPABASE_ANON_KEY=",
|
|
164
|
+
"SUPABASE_URL=",
|
|
165
|
+
"SUPABASE_ANON_KEY=",
|
|
166
|
+
"SUPABASE_SERVICE_ROLE_KEY=",
|
|
145
167
|
"SUPABASE_JWKS_URL=",
|
|
168
|
+
"CMS_SUPABASE_BUCKET=webmaster-droid-cms",
|
|
169
|
+
"CMS_STORAGE_PREFIX=cms",
|
|
170
|
+
"",
|
|
171
|
+
"# Shared runtime",
|
|
172
|
+
"CMS_PUBLIC_BASE_URL=https://your-domain.example",
|
|
146
173
|
"MODEL_OPENAI_ENABLED=true",
|
|
147
174
|
"MODEL_GEMINI_ENABLED=true",
|
|
148
|
-
"DEFAULT_MODEL_ID=openai:gpt-5.2"
|
|
175
|
+
"DEFAULT_MODEL_ID=openai:gpt-5.2",
|
|
176
|
+
"",
|
|
177
|
+
"# AWS (optional backend)",
|
|
178
|
+
"CMS_S3_BUCKET=",
|
|
179
|
+
"CMS_S3_REGION="
|
|
149
180
|
].join("\n") + "\n",
|
|
150
181
|
"utf8"
|
|
151
182
|
);
|
|
@@ -193,117 +224,169 @@ schema.command("build").description("Compile schema file to runtime manifest JSO
|
|
|
193
224
|
await fs.writeFile(output, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
194
225
|
console.log(`Wrote manifest: ${output}`);
|
|
195
226
|
});
|
|
196
|
-
program.command("scan").description("Scan source files for static content candidates").argument("<srcDir>", "source directory").option("--out <file>", "report output", ".webmaster-droid/scan-report.json").action(async (srcDir, opts) => {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const findings = [];
|
|
204
|
-
for (const file of files) {
|
|
205
|
-
const code = await fs.readFile(file, "utf8");
|
|
206
|
-
const ast = parse2(code, {
|
|
207
|
-
sourceType: "module",
|
|
208
|
-
plugins: ["typescript", "jsx"]
|
|
227
|
+
program.command("scan").description("Scan source files for static content candidates").argument("<srcDir>", "source directory").option("--out <file>", "report output", ".webmaster-droid/scan-report.json").option("--json", "emit machine-readable JSON output", false).action(async (srcDir, opts) => {
|
|
228
|
+
try {
|
|
229
|
+
const root = path2.resolve(process.cwd(), srcDir);
|
|
230
|
+
const files = await glob("**/*.{ts,tsx,js,jsx}", {
|
|
231
|
+
cwd: root,
|
|
232
|
+
absolute: true,
|
|
233
|
+
ignore: ["**/*.d.ts", "**/node_modules/**", "**/.next/**", "**/dist/**"]
|
|
209
234
|
});
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
235
|
+
const findings = [];
|
|
236
|
+
for (const file of files) {
|
|
237
|
+
const code = await fs.readFile(file, "utf8");
|
|
238
|
+
const ast = parse2(code, {
|
|
239
|
+
sourceType: "module",
|
|
240
|
+
plugins: ["typescript", "jsx"]
|
|
241
|
+
});
|
|
242
|
+
traverse2(ast, {
|
|
243
|
+
JSXText(pathNode) {
|
|
244
|
+
const text = normalizeText(pathNode.node.value);
|
|
245
|
+
if (!text || text.length < 3) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
findings.push({
|
|
249
|
+
type: "jsx-text",
|
|
250
|
+
file: path2.relative(process.cwd(), file),
|
|
251
|
+
line: pathNode.node.loc?.start.line,
|
|
252
|
+
column: pathNode.node.loc?.start.column,
|
|
253
|
+
text
|
|
254
|
+
});
|
|
255
|
+
},
|
|
256
|
+
JSXAttribute(pathNode) {
|
|
257
|
+
const name = t2.isJSXIdentifier(pathNode.node.name) ? pathNode.node.name.name : "";
|
|
258
|
+
if (!["src", "href", "alt", "title"].includes(name)) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const valueNode = pathNode.node.value;
|
|
262
|
+
if (!valueNode || !t2.isStringLiteral(valueNode)) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
findings.push({
|
|
266
|
+
type: "jsx-attr",
|
|
267
|
+
attr: name,
|
|
268
|
+
file: path2.relative(process.cwd(), file),
|
|
269
|
+
line: valueNode.loc?.start.line,
|
|
270
|
+
column: valueNode.loc?.start.column,
|
|
271
|
+
text: valueNode.value
|
|
272
|
+
});
|
|
228
273
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
const output = path2.resolve(process.cwd(), opts.out);
|
|
277
|
+
const report = {
|
|
278
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
279
|
+
source: root,
|
|
280
|
+
totalFiles: files.length,
|
|
281
|
+
totalFindings: findings.length,
|
|
282
|
+
findings
|
|
283
|
+
};
|
|
284
|
+
await ensureDir(output);
|
|
285
|
+
await fs.writeFile(output, JSON.stringify(report, null, 2) + "\n", "utf8");
|
|
286
|
+
if (opts.json) {
|
|
287
|
+
emitCliEnvelope({
|
|
288
|
+
ok: true,
|
|
289
|
+
command: "scan",
|
|
290
|
+
version: CLI_VERSION,
|
|
291
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
292
|
+
data: {
|
|
293
|
+
reportPath: output,
|
|
294
|
+
source: root,
|
|
295
|
+
totalFiles: files.length,
|
|
296
|
+
totalFindings: findings.length
|
|
232
297
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
const output = path2.resolve(process.cwd(), opts.out);
|
|
245
|
-
await ensureDir(output);
|
|
246
|
-
await fs.writeFile(
|
|
247
|
-
output,
|
|
248
|
-
JSON.stringify(
|
|
298
|
+
});
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
console.log(`Scan complete. Findings: ${findings.length}. Report: ${output}`);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
if (!opts.json) {
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
emitCliEnvelope(
|
|
249
307
|
{
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
308
|
+
ok: false,
|
|
309
|
+
command: "scan",
|
|
310
|
+
version: CLI_VERSION,
|
|
311
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
312
|
+
errors: [errorToMessage(error)]
|
|
255
313
|
},
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
);
|
|
261
|
-
console.log(`Scan complete. Findings: ${findings.length}. Report: ${output}`);
|
|
314
|
+
true
|
|
315
|
+
);
|
|
316
|
+
process.exitCode = 1;
|
|
317
|
+
}
|
|
262
318
|
});
|
|
263
|
-
program.command("codemod").description("Apply deterministic JSX codemods to Editable components").argument("<srcDir>", "source directory").option("--apply", "write file changes", false).option("--out <file>", "report output", ".webmaster-droid/codemod-report.json").action(async (srcDir, opts) => {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const changed = [];
|
|
271
|
-
for (const file of files) {
|
|
272
|
-
const source = await fs.readFile(file, "utf8");
|
|
273
|
-
const transformed = transformEditableTextCodemod(source, file, process.cwd());
|
|
274
|
-
if (!transformed.changed) {
|
|
275
|
-
continue;
|
|
276
|
-
}
|
|
277
|
-
const next = transformed.next;
|
|
278
|
-
const relFile = path2.relative(process.cwd(), file);
|
|
279
|
-
changed.push({
|
|
280
|
-
file: relFile,
|
|
281
|
-
patch: createTwoFilesPatch(relFile, relFile, source, next)
|
|
319
|
+
program.command("codemod").description("Apply deterministic JSX codemods to Editable components").argument("<srcDir>", "source directory").option("--apply", "write file changes", false).option("--out <file>", "report output", ".webmaster-droid/codemod-report.json").option("--json", "emit machine-readable JSON output", false).action(async (srcDir, opts) => {
|
|
320
|
+
try {
|
|
321
|
+
const root = path2.resolve(process.cwd(), srcDir);
|
|
322
|
+
const files = await glob("**/*.{tsx,jsx}", {
|
|
323
|
+
cwd: root,
|
|
324
|
+
absolute: true,
|
|
325
|
+
ignore: ["**/node_modules/**", "**/.next/**", "**/dist/**"]
|
|
282
326
|
});
|
|
283
|
-
|
|
284
|
-
|
|
327
|
+
const changed = [];
|
|
328
|
+
for (const file of files) {
|
|
329
|
+
const source = await fs.readFile(file, "utf8");
|
|
330
|
+
const transformed = transformEditableTextCodemod(source, file, process.cwd());
|
|
331
|
+
if (!transformed.changed) {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
const next = transformed.next;
|
|
335
|
+
const relFile = path2.relative(process.cwd(), file);
|
|
336
|
+
changed.push({
|
|
337
|
+
file: relFile,
|
|
338
|
+
patch: createTwoFilesPatch(relFile, relFile, source, next)
|
|
339
|
+
});
|
|
340
|
+
if (opts.apply) {
|
|
341
|
+
await fs.writeFile(file, next, "utf8");
|
|
342
|
+
}
|
|
285
343
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
344
|
+
const output = path2.resolve(process.cwd(), opts.out);
|
|
345
|
+
const report = {
|
|
346
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
347
|
+
source: root,
|
|
348
|
+
apply: Boolean(opts.apply),
|
|
349
|
+
changedFiles: changed.length,
|
|
350
|
+
changes: changed
|
|
351
|
+
};
|
|
352
|
+
await ensureDir(output);
|
|
353
|
+
await fs.writeFile(output, JSON.stringify(report, null, 2) + "\n", "utf8");
|
|
354
|
+
if (opts.json) {
|
|
355
|
+
emitCliEnvelope({
|
|
356
|
+
ok: true,
|
|
357
|
+
command: "codemod",
|
|
358
|
+
version: CLI_VERSION,
|
|
359
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
360
|
+
data: {
|
|
361
|
+
reportPath: output,
|
|
362
|
+
source: root,
|
|
363
|
+
apply: Boolean(opts.apply),
|
|
364
|
+
changedFiles: changed.length
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
console.log(
|
|
370
|
+
`${opts.apply ? "Applied" : "Previewed"} codemod changes: ${changed.length}. Report: ${output}`
|
|
371
|
+
);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
if (!opts.json) {
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
emitCliEnvelope(
|
|
292
377
|
{
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
378
|
+
ok: false,
|
|
379
|
+
command: "codemod",
|
|
380
|
+
version: CLI_VERSION,
|
|
381
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
382
|
+
errors: [errorToMessage(error)]
|
|
298
383
|
},
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
);
|
|
304
|
-
console.log(`${opts.apply ? "Applied" : "Previewed"} codemod changes: ${changed.length}. Report: ${output}`);
|
|
384
|
+
true
|
|
385
|
+
);
|
|
386
|
+
process.exitCode = 1;
|
|
387
|
+
}
|
|
305
388
|
});
|
|
306
|
-
program.command("doctor").description("Validate local environment for webmaster-droid").action(async () => {
|
|
389
|
+
program.command("doctor").description("Validate local environment for webmaster-droid").option("--json", "emit machine-readable JSON output", false).action(async (opts) => {
|
|
307
390
|
const issues = [];
|
|
308
391
|
const major = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
|
|
309
392
|
if (!Number.isFinite(major) || major < 20) {
|
|
@@ -315,6 +398,23 @@ program.command("doctor").description("Validate local environment for webmaster-
|
|
|
315
398
|
issues.push("package.json missing in current working directory");
|
|
316
399
|
}
|
|
317
400
|
if (issues.length > 0) {
|
|
401
|
+
if (opts.json) {
|
|
402
|
+
emitCliEnvelope(
|
|
403
|
+
{
|
|
404
|
+
ok: false,
|
|
405
|
+
command: "doctor",
|
|
406
|
+
version: CLI_VERSION,
|
|
407
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
408
|
+
data: {
|
|
409
|
+
checksPassed: false
|
|
410
|
+
},
|
|
411
|
+
errors: issues
|
|
412
|
+
},
|
|
413
|
+
true
|
|
414
|
+
);
|
|
415
|
+
process.exitCode = 1;
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
318
418
|
console.error("Doctor found issues:");
|
|
319
419
|
for (const issue of issues) {
|
|
320
420
|
console.error(`- ${issue}`);
|
|
@@ -322,6 +422,18 @@ program.command("doctor").description("Validate local environment for webmaster-
|
|
|
322
422
|
process.exitCode = 1;
|
|
323
423
|
return;
|
|
324
424
|
}
|
|
425
|
+
if (opts.json) {
|
|
426
|
+
emitCliEnvelope({
|
|
427
|
+
ok: true,
|
|
428
|
+
command: "doctor",
|
|
429
|
+
version: CLI_VERSION,
|
|
430
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
431
|
+
data: {
|
|
432
|
+
checksPassed: true
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
325
437
|
console.log("Doctor checks passed.");
|
|
326
438
|
});
|
|
327
439
|
program.command("dev").description("Start project dev command (pass-through)").option("--cmd <command>", "command to run", "npm run dev").action(async (opts) => {
|
|
@@ -361,6 +473,38 @@ aws.requiredOption("--entry <file>", "entry TypeScript file").requiredOption("--
|
|
|
361
473
|
await run(`aws lambda wait function-updated --region ${opts.region} --function-name ${fn}`);
|
|
362
474
|
}
|
|
363
475
|
});
|
|
476
|
+
var supabase = deploy.command("supabase").description("Deploy Supabase edge functions");
|
|
477
|
+
supabase.requiredOption("--project-ref <ref>", "Supabase project reference").requiredOption("--functions <names>", "comma-separated function names").option("--env-file <path>", "path to env file for function deployment").option("--no-verify-jwt", "disable JWT verification for deployed functions").action(async (opts) => {
|
|
478
|
+
const functions = String(opts.functions).split(",").map((item) => item.trim()).filter(Boolean);
|
|
479
|
+
if (functions.length === 0) {
|
|
480
|
+
throw new Error("No function names provided.");
|
|
481
|
+
}
|
|
482
|
+
const run = (cmd) => new Promise((resolve, reject) => {
|
|
483
|
+
const child = spawn(cmd, { stdio: "inherit", shell: true });
|
|
484
|
+
child.on("exit", (code) => {
|
|
485
|
+
if (code === 0) {
|
|
486
|
+
resolve();
|
|
487
|
+
} else {
|
|
488
|
+
reject(new Error(`Command failed: ${cmd}`));
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
for (const fn of functions) {
|
|
493
|
+
const parts = [
|
|
494
|
+
"supabase functions deploy",
|
|
495
|
+
fn,
|
|
496
|
+
"--project-ref",
|
|
497
|
+
opts.projectRef
|
|
498
|
+
];
|
|
499
|
+
if (opts.envFile) {
|
|
500
|
+
parts.push("--env-file", opts.envFile);
|
|
501
|
+
}
|
|
502
|
+
if (opts.verifyJwt === false) {
|
|
503
|
+
parts.push("--no-verify-jwt");
|
|
504
|
+
}
|
|
505
|
+
await run(parts.join(" "));
|
|
506
|
+
}
|
|
507
|
+
});
|
|
364
508
|
var skill = program.command("skill").description("Skill helpers");
|
|
365
509
|
skill.command("install").description("Install bundled conversion skill into CODEX_HOME").option("--codex-home <dir>", "CODEX_HOME path override").option("--force", "overwrite existing", false).action(async (opts) => {
|
|
366
510
|
const cliDir = path2.dirname(fileURLToPath(import.meta.url));
|