@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.
Files changed (2) hide show
  1. package/dist/index.js +252 -108
  2. 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 traverse2 from "@babel/traverse";
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("0.1.0-alpha.0");
117
- program.command("init").description("Initialize optional webmaster-droid config in current project").option("--framework <framework>", "framework", "next").option("--backend <backend>", "backend", "aws").option("--out <dir>", "output dir", ".").action(async (opts) => {
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: "${opts.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
- "CMS_S3_BUCKET=",
143
- "CMS_S3_REGION=",
144
- "CMS_PUBLIC_BASE_URL=https://your-domain.example",
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
- const root = path2.resolve(process.cwd(), srcDir);
198
- const files = await glob("**/*.{ts,tsx,js,jsx}", {
199
- cwd: root,
200
- absolute: true,
201
- ignore: ["**/*.d.ts", "**/node_modules/**", "**/.next/**", "**/dist/**"]
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
- traverse2(ast, {
211
- JSXText(pathNode) {
212
- const text = normalizeText(pathNode.node.value);
213
- if (!text || text.length < 3) {
214
- return;
215
- }
216
- findings.push({
217
- type: "jsx-text",
218
- file: path2.relative(process.cwd(), file),
219
- line: pathNode.node.loc?.start.line,
220
- column: pathNode.node.loc?.start.column,
221
- text
222
- });
223
- },
224
- JSXAttribute(pathNode) {
225
- const name = t2.isJSXIdentifier(pathNode.node.name) ? pathNode.node.name.name : "";
226
- if (!["src", "href", "alt", "title"].includes(name)) {
227
- return;
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
- const valueNode = pathNode.node.value;
230
- if (!valueNode || !t2.isStringLiteral(valueNode)) {
231
- return;
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
- findings.push({
234
- type: "jsx-attr",
235
- attr: name,
236
- file: path2.relative(process.cwd(), file),
237
- line: valueNode.loc?.start.line,
238
- column: valueNode.loc?.start.column,
239
- text: valueNode.value
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
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
251
- source: root,
252
- totalFiles: files.length,
253
- totalFindings: findings.length,
254
- findings
308
+ ok: false,
309
+ command: "scan",
310
+ version: CLI_VERSION,
311
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
312
+ errors: [errorToMessage(error)]
255
313
  },
256
- null,
257
- 2
258
- ) + "\n",
259
- "utf8"
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
- const root = path2.resolve(process.cwd(), srcDir);
265
- const files = await glob("**/*.{tsx,jsx}", {
266
- cwd: root,
267
- absolute: true,
268
- ignore: ["**/node_modules/**", "**/.next/**", "**/dist/**"]
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
- if (opts.apply) {
284
- await fs.writeFile(file, next, "utf8");
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
- const output = path2.resolve(process.cwd(), opts.out);
288
- await ensureDir(output);
289
- await fs.writeFile(
290
- output,
291
- JSON.stringify(
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
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
294
- source: root,
295
- apply: Boolean(opts.apply),
296
- changedFiles: changed.length,
297
- changes: changed
378
+ ok: false,
379
+ command: "codemod",
380
+ version: CLI_VERSION,
381
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
382
+ errors: [errorToMessage(error)]
298
383
  },
299
- null,
300
- 2
301
- ) + "\n",
302
- "utf8"
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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmaster-droid/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "webmaster-droid": "dist/index.js"