Skip to content

Commit

Permalink
feat: add support for passing params directly
Browse files Browse the repository at this point in the history
  • Loading branch information
3rd committed Nov 29, 2024
1 parent b25d8a9 commit b9a7d77
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 111 deletions.
235 changes: 139 additions & 96 deletions src/commands/run.ts
Original file line number Diff line number Diff line change
@@ -1,122 +1,165 @@
/* eslint-disable no-await-in-loop */
import * as fs from "node:fs";
import { command } from "cleye";
import chalk from "chalk";
import { globSync } from "glob";
import * as inquirer from "@inquirer/prompts";
import { Project } from "../Project";
import { AutoReturnType } from "../types";
import { tildify } from "../utils/path";
import { dirname, resolve } from "node:path";
import { globSync } from "glob";
import * as inquirer from "@inquirer/prompts";
import * as log from "../utils/logger";

const toKebabCase = (str: string) => {
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
};

const createRunCommand = (project: Project, scripts: AutoReturnType[]) =>
command({ name: "run", alias: "r", parameters: ["<script id>"] }, async (argv) => {
const { scriptId } = argv._;
const script = scripts.find((t) => t.id === scriptId);
command(
{
name: "run",
alias: "r",
parameters: ["<script id>"],
flags: {
help: { type: Boolean, alias: "h" },
},
allowUnknownFlags: true,
},
async (argv) => {
const { scriptId } = argv._;
const script = scripts.find((t) => t.id === scriptId);

if (!script) {
console.error(chalk.red(`Error: script "%s" not found.`), scriptId);
process.exit(1);
}
if (script.isValid && !script.isValid(project)) {
console.error(chalk.red(`Error: script "%s" is not valid for this context.`), scriptId);
process.exit(1);
}
if (!script) {
console.error(chalk.red(`Error: script "%s" not found.`), scriptId);
process.exit(1);
}
if (script.isValid && !script.isValid(project)) {
console.error(chalk.red(`Error: script "%s" is not valid for this context.`), scriptId);
process.exit(1);
}

console.log(chalk.blue("Info:"), "Running", chalk.magenta(tildify(script.path)));
console.log(chalk.blue("Info:"), "Running", chalk.magenta(tildify(script.path)));

// gather params
const scriptParams = script.bootstrapParams();
for (const [_, param] of Object.entries(scriptParams)) {
// dynamic default values
if (typeof param.defaultValue === "function") {
const value = param.defaultValue({
project,
params: Object.fromEntries(
Object.entries(scriptParams).map(([key, currentParam]) => {
return [key, currentParam.value];
})
),
});
if (value !== undefined) param.value = value;
}
// gather params
const scriptParams = script.bootstrapParams();

// eslint-disable-next-line default-case
switch (param.type) {
case "boolean": {
param.value = await inquirer.confirm({
message: param.title,
default: param.value as boolean,
});
break;
// get CLI-passed params
const cliParams: Record<string, any> = {};

for (const [key, param] of Object.entries(scriptParams)) {
// cli values
const cliValue = (argv.unknownFlags[key] ?? argv.unknownFlags[toKebabCase(key)] ?? [])[0];
if (cliValue !== undefined) {
switch (param.type) {
case "boolean":
param.value = cliValue === "true" || cliValue === true;
break;
case "number":
param.value = Number(cliValue);
break;
case "string":
param.value = cliValue;
break;
}
cliParams[key] = cliValue;
log.debug("CLI value", key, cliValue);
continue;
}
case "number": {
const stringValue = await inquirer.input({
message: param.title,
default: param.value.toString(),

// dynamic default values
if (typeof param.defaultValue === "function") {
const value = param.defaultValue({
project,
params: Object.fromEntries(
Object.entries(scriptParams).map(([key, currentParam]) => {
return [key, currentParam.value];
})
),
});
param.value = Number(stringValue);
break;
if (value !== undefined) param.value = value;
}
case "string": {
param.value = await inquirer.input({
message: param.title,
default: param.value as string,
});
if (param.required && param.value === "") {
console.error(chalk.red(`Error: Parameter "%s" is required.`), param.title);
process.exit(1);

// input values
if (!(key in cliParams)) {
switch (param.type) {
case "boolean": {
param.value = await inquirer.confirm({
message: param.title,
default: param.value as boolean,
});
break;
}
case "number": {
const stringValue = await inquirer.input({
message: param.title,
default: param.value.toString(),
});
param.value = Number(stringValue);
break;
}
case "string": {
param.value = await inquirer.input({
message: param.title,
default: param.value as string,
});
if (param.required && param.value === "") {
console.error(chalk.red(`Error: Parameter "%s" is required.`), param.title);
process.exit(1);
}
break;
}
}
break;
}
}
}
const paramValues = Object.fromEntries(
Object.entries(scriptParams).map(([key, param]) => {
return [key, param.value];
})
);

const t = (text: string, params: Record<string, boolean | number | string> = paramValues) => {
let result = text;
for (const [key, value] of Object.entries(params)) {
result = result.replace(new RegExp(`__${key}__`, "g"), String(value));
}
const paramValues = Object.fromEntries(
Object.entries(scriptParams).map(([key, param]) => {
return [key, param.value];
})
);

return result;
};
const t = (text: string, params: Record<string, boolean | number | string> = paramValues) => {
let result = text;
for (const [key, value] of Object.entries(params)) {
result = result.replace(new RegExp(`__${key}__`, "g"), String(value));
}

// collect script files
const scriptDir = dirname(script.path);
const files = globSync("**/*", { cwd: scriptDir, dot: true, nodir: true, ignore: script.path }).map((path) => {
return {
path,
get content() {
return fs.readFileSync(resolve(scriptDir, path), "utf8");
},
return result;
};
});

// run the script
script.run({
cwd: process.cwd(),
files,
params: paramValues as Parameters<typeof script.run>[0]["params"],
project: Project.resolveFromPath(process.cwd()),
self: script,
get fileMap() {
return new Proxy(
{},
{
get(_, path) {
if (typeof path !== "string") throw new Error("Invalid path.");
if (!fs.existsSync(resolve(scriptDir, path))) throw new Error(`File "${path}" not found.`);
return fs.readFileSync(resolve(scriptDir, path as string), "utf8");
},
}
);
},
t,
});
});
// collect script files
const scriptDir = dirname(script.path);
const files = globSync("**/*", { cwd: scriptDir, dot: true, nodir: true, ignore: script.path }).map((path) => {
return {
path,
get content() {
return fs.readFileSync(resolve(scriptDir, path), "utf8");
},
};
});

// run the script
script.run({
cwd: process.cwd(),
files,
params: paramValues as Parameters<typeof script.run>[0]["params"],
project: Project.resolveFromPath(process.cwd()),
self: script,
get fileMap() {
return new Proxy(
{},
{
get(_, path) {
if (typeof path !== "string") throw new Error("Invalid path.");
if (!fs.existsSync(resolve(scriptDir, path))) throw new Error(`File "${path}" not found.`);
return fs.readFileSync(resolve(scriptDir, path as string), "utf8");
},
}
);
},
t,
});
}
);

export { createRunCommand };
2 changes: 2 additions & 0 deletions src/e2e/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ for (const [name, test] of Object.entries(tests)) {
assert.equal(result.stdout.trim(), expectedStdout.trim(), `Test "${test.name ?? name}" stdout is invalid.`);
}
if (test.expected.files) {
const existingFiles = await fs.readdir(cwd);
console.log("Files in cwd:", existingFiles);
for (const [path, expectedContent] of Object.entries(test.expected.files)) {
const filePath = resolve(cwd, path);
const actualContent = await fs.readFile(filePath, "utf8");
Expand Down
29 changes: 24 additions & 5 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* eslint-disable no-await-in-loop */
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";

import { cli as cleye } from "cleye";
import chalk from "chalk";
import fs from "fs-extra";
Expand All @@ -10,34 +9,50 @@ import { globSync } from "glob";
import * as inquirer from "@inquirer/prompts";

import packageJson from "../package.json";
import { Project } from "./Project";
import { getGlobalRepositoryPath, resolveProjectRoot, tildify } from "./utils/path";
import * as log from "./utils/logger";
import { createListCommand } from "./commands/list";
import { createRunCommand } from "./commands/run";
import { createReplCommand } from "./commands/repl";
import { AutoReturnType, autoSymbol } from "./types";
import { setupPackage, setupTSConfig } from "./setup";
import { Project } from "./Project";

const main = async () => {
log.debug("Starting auto...");
const isParentProcess = typeof process.send !== "function";
log.debug("Is parent process:", isParentProcess);

// main repo
const developmentRepositoryPath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "examples");
const globalRepositoryPath = getGlobalRepositoryPath();
const envRepositoryPath = process.env.AUTO_REPO;
log.debug("Repository paths:", {
development: developmentRepositoryPath,
global: globalRepositoryPath,
env: envRepositoryPath,
});

let mainRepositoryPath =
envRepositoryPath ??
globalRepositoryPath ??
(fs.existsSync(developmentRepositoryPath) ? developmentRepositoryPath : null);
const hasMainRepository = fs.existsSync(mainRepositoryPath);
log.debug("Selected main repository:", mainRepositoryPath);
log.debug("Main repository exists:", hasMainRepository);

if (hasMainRepository && isParentProcess) {
console.log(chalk.blue("Info:"), "Using main repository:", chalk.magenta(tildify(mainRepositoryPath)));
}

// local repo
const projectRoot = resolveProjectRoot(process.cwd());
log.debug("Project root:", projectRoot);
const localRepositoryPaths = ["./auto", "./.auto"].map((p) => resolve(projectRoot, p));
log.debug("Checking local repository paths:", localRepositoryPaths);
const localRepositoryPath = localRepositoryPaths.find((p) => fs.existsSync(p));
log.debug("Local repository:", localRepositoryPath);

if (localRepositoryPath && isParentProcess) {
console.log(chalk.blue("Info:"), "Using local repository:", chalk.magenta(tildify(localRepositoryPath)));
}
Expand All @@ -46,10 +61,12 @@ const main = async () => {
const repositoryPaths: string[] = [];
if (hasMainRepository) repositoryPaths.push(mainRepositoryPath);
if (localRepositoryPath) repositoryPaths.push(localRepositoryPath);
log.debug("Final repository paths:", repositoryPaths);

// no repo found
if (repositoryPaths.length === 0) {
console.error(chalk.red("Error:"), "Cannot resolve repository directory, to fix this either:");
log.debug("No repositories found");
log.error("Cannot resolve repository directory, to fix this either:");
console.log(`- Create a directory at: ${chalk.magenta(tildify(globalRepositoryPath))}`);
console.log(
`- Create a directory at:\n ${chalk.magenta(resolve(projectRoot, "auto"))}\nor\n ${chalk.magenta(
Expand Down Expand Up @@ -182,12 +199,14 @@ const main = async () => {
}

scriptMap[script.id] = script;
// console.log(chalk.green("Success:"), "Loaded:", chalk.magenta(path));
log.debug("Loaded:", path);
} else {
// console.log(chalk.yellow("Skipped:"), "Not a module:", chalk.magenta(file.path));
log.debug("Skipped:", "Not a module:", file.path);
}
}

log.debug("Loaded scripts:", Object.keys(scriptMap));

const project = Project.resolveFromPath(process.cwd());
const scripts = Object.values(scriptMap);

Expand Down
Loading

0 comments on commit b9a7d77

Please sign in to comment.