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