import { extensionArtifactCreator } from "./artifacts/extension.ts";
import { projectArtifactCreator } from "./artifacts/project.ts";
import { kEditorInfos, scanForEditors } from "./editor.ts";
import {
isInteractiveTerminal,
isPositWorkbench,
} from "../../core/platform.ts";
import { runningInCI } from "../../core/ci-info.ts";
import { Command } from "cliffy/command/mod.ts";
import { prompt, Select, SelectValueOptions } from "cliffy/prompt/mod.ts";
import { readLines } from "../../deno_ral/io.ts";
import { info } from "../../deno_ral/log.ts";
import { ArtifactCreator, CreateDirective, CreateResult } from "./cmd-types.ts";
const kArtifactCreators: ArtifactCreator[] = [
projectArtifactCreator,
extensionArtifactCreator,
];
export const createCommand = new Command()
.name("create")
.description("Create a Quarto project or extension")
.option(
"--open [editor:string]",
`Open new artifact in this editor (${
kEditorInfos.map((info) => info.id).join(", ")
})`,
)
.option("--no-open", "Do not open in an editor")
.option("--no-prompt", "Do not prompt to confirm actions")
.option("--json", "Pass serialized creation options via stdin", {
hidden: true,
})
.arguments("[type] [commands...]")
.action(
async (
options: {
prompt: boolean;
json?: boolean;
open?: string | boolean;
},
type?: string,
...commands: string[]
) => {
if (options.json) {
await createFromStdin();
} else {
const isInteractive = isInteractiveTerminal() && !runningInCI();
const allowPrompt = isInteractive && !!options.prompt && !options.json;
if (options.open !== false && isPositWorkbench()) {
if (options.open !== undefined) {
info(
`The --open option is not supported in Posit Workbench - ignoring`,
);
}
options.open = false;
}
const resolved = await resolveArtifact(
type,
options.prompt,
);
const resolvedArtifact = resolved.artifact;
if (resolvedArtifact) {
const args = commands;
const commandOpts = resolvedArtifact.resolveOptions(args);
const createOptions = {
cwd: Deno.cwd(),
options: commandOpts,
};
if (allowPrompt) {
let nextPrompt = resolvedArtifact.nextPrompt(createOptions);
while (nextPrompt !== undefined) {
if (nextPrompt) {
const result = await prompt([nextPrompt]);
createOptions.options = {
...createOptions.options,
...result,
};
}
nextPrompt = resolvedArtifact.nextPrompt(createOptions);
}
}
const createDirective = resolvedArtifact.finalizeOptions(
createOptions,
);
const createResult = await resolvedArtifact.createArtifact(
createDirective,
);
if (allowPrompt && options.open !== false) {
const resolvedEditor = await resolveEditor(
createResult,
typeof (options.open) === "string" ? options.open : undefined,
);
if (resolvedEditor) {
resolvedEditor.open();
}
}
}
}
},
);
const resolveArtifact = async (type?: string, prompt?: boolean) => {
const findArtifact = (type: string) => {
return kArtifactCreators.find((artifact) =>
artifact.type === type && artifact.enabled !== false
);
};
let artifact = type ? findArtifact(type) : undefined;
while (artifact === undefined) {
if (!prompt) {
if (type) {
throw new Error(`Failed to create ${type} - the type isn't recognized`);
} else {
throw new Error(
`Creation failed - you must provide a type to create when using '--no-prompt'`,
);
}
}
if (type) {
info(`Unknown type ${type} - please select from the following:`);
}
type = await promptForType();
artifact = findArtifact(type);
}
return {
artifact,
};
};
async function promptSelect(
message: string,
options: SelectValueOptions,
) {
return await Select.prompt({
message,
options,
});
}
const promptForType = async () => {
return await promptSelect(
"Create",
kArtifactCreators.map((artifact) => {
return {
name: artifact.displayName.toLowerCase(),
value: artifact.type,
};
}),
);
};
const resolveEditor = async (createResult: CreateResult, editor?: string) => {
const editors = await scanForEditors(kEditorInfos, createResult);
const defaultEditor = editors.find((ed) => {
return ed.id === editor;
});
if (defaultEditor) {
return defaultEditor;
} else {
const inEditor = editors.find((ed) => ed.inEditor);
if (inEditor) {
return inEditor;
} else if (editors.length > 0) {
const editorOptions = editors.map((editor) => {
return {
name: editor.name.toLowerCase(),
value: editor.name,
};
});
const options = [...editorOptions, {
name: "(don't open)",
value: "do not open",
}];
const name = await promptSelect("Open With", options);
const selectedEditor = editors.find((edit) => edit.name === name);
return selectedEditor;
} else {
return undefined;
}
}
};
async function createFromStdin() {
const { value: input } = await readLines(Deno.stdin).next();
Deno.stdin.close();
const jsonOptions = JSON.parse(input);
const type = jsonOptions.type;
if (!type) {
throw new Error(
"The provided json for create artifacts must include a valid type",
);
}
if (
!jsonOptions.directive || !jsonOptions.directive.name ||
!jsonOptions.directive.directory || !jsonOptions.directive.template
) {
throw new Error(
"The provided json for create artifacts must include a directive with a name, directory, and template.",
);
}
const resolved = await resolveArtifact(
type,
false,
);
const createDirective = jsonOptions.directive as CreateDirective;
const createResult = await resolved.artifact.createArtifact(
createDirective,
true,
);
const resultJSON = JSON.stringify(createResult, undefined);
Deno.stdout.writeSync(new TextEncoder().encode(resultJSON));
}