Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 24 additions & 13 deletions src/cmd/lib/create.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Command } from "@cliffy/command";
import { Command, EnumType } from "@cliffy/command";
import { basename } from "@std/path";
import VTClient, { assertSafeDirectory } from "~/vt/vt/VTClient.ts";
import { getCurrentUser } from "~/sdk.ts";
import { APIError } from "@valtown/sdk";
import { doWithSpinner, getClonePath } from "~/cmd/utils.ts";
import { ensureAddEditorFiles } from "~/cmd/lib/utils/messages.ts";
import { Confirm } from "@cliffy/prompt";
import { Confirm, Select } from "@cliffy/prompt";
import { DEFAULT_EDITOR_TEMPLATE } from "~/consts.ts";

export const createCmd = new Command()
Expand All @@ -21,11 +21,12 @@ export const createCmd = new Command()
.option("--unlisted", "Create as unlisted Val", {
conflicts: ["public", "private"],
})
.option("--no-editor-files", "Skip creating editor configuration files")
.type("if-exists", new EnumType(["continue", "yes-and-upload", "cancel"]))
.option(
"--upload-if-exists", // useful for testing
"--if-exists <action:if-exists>", // useful for testing
"Upload existing files to the new Val if the directory is not empty",
)
.option("--no-editor-files", "Skip creating editor configuration files")
.option("-d, --description <desc:string>", "Val description")
.example(
"Start fresh",
Expand Down Expand Up @@ -62,14 +63,14 @@ vt checkout main`,
unlisted,
description,
editorFiles,
uploadIfExists,
ifExistsAction,
}: {
public?: boolean;
private?: boolean;
unlisted?: boolean;
description?: string;
editorFiles?: boolean;
uploadIfExists?: boolean;
ifExistsAction?: "continue" | "yes-and-upload" | "cancel";
},
valName: string,
targetDir?: string,
Expand All @@ -80,23 +81,32 @@ vt checkout main`,

// Determine privacy setting (defaults to public)
const privacy = isPrivate ? "private" : unlisted ? "unlisted" : "public";
let doUpload = ifExistsAction === "yes-and-upload";

try {
try {
await assertSafeDirectory(clonePath);
} catch (e) {
if (e instanceof Error && e.message.includes("not empty")) {
if (!uploadIfExists) {
if (ifExistsAction !== "cancel") {
spinner.stop();
const confirmContinue = await Confirm.prompt(
`The directory "${
basename(clonePath)
}" already exists and is not empty. Do you want to continue?` +
" Existing files will be uploaded to the new Val.",
const confirmContinue = await Select.prompt(
{
message: `The directory "${
basename(clonePath)
}" already exists and is not empty. Do you want to continue?`,
options: [
{ name: "Yes", value: "yes" },
{ name: "Yes, and upload files", value: "yes_and_upload" },
{ name: "No, cancel", value: "no" },
],
},
);

if (!confirmContinue) {
if (confirmContinue === "no") {
Deno.exit(0);
} else {
doUpload = confirmContinue === "yes_and_upload";
}
}
} else {
Expand All @@ -111,6 +121,7 @@ vt checkout main`,
privacy,
description,
skipSafeDirCheck: true,
doUpload,
});

if (editorFiles) {
Expand Down
11 changes: 6 additions & 5 deletions src/cmd/lib/utils/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ export const noChangesDryRunMsg = "Dry run completed. " +

export function ensureAddEditorFiles(editorTemplate: string) {
if (editorTemplate === DEFAULT_EDITOR_TEMPLATE) {
return wrap(
"Would you like `vt` to add editor files to this Val?\n" +
return `Would you like \`vt\` to add editor files to this Val?\n${
wrap(
"\nThis will add files like a deno.json and .vscode folder with " +
"default editor configuration for Deno.",
{ width: DEFAULT_WRAP_WIDTH },
);
"default editor configuration for Deno.",
{ width: DEFAULT_WRAP_WIDTH },
)
}`;
} else {
// If they aren't using the default for the editor file template they
// probably know what this means
Expand Down
85 changes: 83 additions & 2 deletions src/cmd/tests/create_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,9 @@ Deno.test({
"create",
nonEmptyDirValName,
], tmpDir);
console.log(stdout);
assertStringIncludes(
stdout,
"files will be uploaded",
"already exists and is not empty",
);
},
);
Expand Down Expand Up @@ -263,3 +262,85 @@ Deno.test({
},
sanitizeResources: false,
});

Deno.test({
name: "create Val with --if-exists continue option",
permissions: "inherit",
async fn(t) {
const user = await getCurrentUser();
const newValName = randomValName();
let newVal: ValTown.Val | null = null;

await doWithTempDir(async (tmpDir) => {
await t.step("create files in target directory", async () => {
const targetDir = join(tmpDir, newValName);
await Deno.mkdir(targetDir);

// Create some files in the target directory
await Deno.writeTextFile(
join(targetDir, "existing-file.js"),
"console.log('Existing file content');",
);
await Deno.writeTextFile(
join(targetDir, "another-file.ts"),
"export const value = 42;",
);
});

await t.step("create Val with --if-exists continue", async () => {
// Create Val with --if-exists continue option
await runVtCommand([
"create",
newValName,
"--if-exists",
"continue",
"--no-editor-files",
], tmpDir);

newVal = await sdk.alias.username.valName.retrieve(
user.username!,
newValName,
);

assertEquals(newVal.name, newValName);
assertEquals(newVal.author.username, user.username);
});

await t.step("verify files were NOT uploaded to the Val", async () => {
// Check that the existing files were NOT uploaded to the Val
const valItems = await listValItems(
newVal!.id,
(await branchNameToBranch(newVal!.id, DEFAULT_BRANCH_NAME)).id,
await getLatestVersion(
newVal!.id,
(await branchNameToBranch(newVal!.id, DEFAULT_BRANCH_NAME)).id,
),
);

const fileNames = valItems.map((item) => item.path);
assert(
!fileNames.includes("existing-file.js"),
"existing-file.js should NOT be uploaded to Val with continue option",
);
assert(
!fileNames.includes("another-file.ts"),
"another-file.ts should NOT be uploaded to Val with continue option",
);
});

await t.step("verify local files still exist", async () => {
// Verify the local files still exist in the directory
const targetDir = join(tmpDir, newValName);
assert(
await exists(join(targetDir, "existing-file.js")),
"existing-file.js should still exist locally",
);
assert(
await exists(join(targetDir, "another-file.ts")),
"another-file.ts should still exist locally",
);
});
});
},
sanitizeResources: false,
});
23 changes: 15 additions & 8 deletions src/vt/lib/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import sdk, { branchNameToBranch } from "~/sdk.ts";
import type { ValPrivacy } from "~/types.ts";
import { DEFAULT_BRANCH_NAME } from "~/consts.ts";
import { ensureDir } from "@std/fs";
import type { ItemStatusManager } from "~/vt/lib/utils/ItemStatusManager.ts";
import { ItemStatusManager } from "~/vt/lib/utils/ItemStatusManager.ts";

/**
* Result of a checkout operation containing branch information and file
Expand All @@ -26,6 +26,8 @@ export interface CreateParams {
sourceDir: string;
/** The name for the new val. */
valName: string;
/** Whether to upload after creating. */
doUpload?: boolean;
/** Optional Val description. Defaults to that of the Val being remixed. */
description?: string;
/** Privacy setting for the val. Defaults to that of the Val being remixed. */
Expand All @@ -49,6 +51,7 @@ export async function create(
description = "",
privacy = "private",
gitignoreRules,
doUpload = true,
} = params;

await ensureDir(sourceDir);
Expand All @@ -64,13 +67,17 @@ export async function create(
DEFAULT_BRANCH_NAME,
);

// Push the local directory contents to the new val
const { itemStateChanges } = await push({
targetDir: sourceDir,
valId: newVal.id,
branchId: newBranch.id,
gitignoreRules,
});
// Push the local directory contents to the new val if requested
let itemStateChanges = new ItemStatusManager();
if (doUpload) {
const { itemStateChanges: changes } = await push({
targetDir: sourceDir,
valId: newVal.id,
branchId: newBranch.id,
gitignoreRules,
});
itemStateChanges = changes;
}

return {
itemStateChanges: itemStateChanges,
Expand Down
3 changes: 3 additions & 0 deletions src/vt/vt/VTClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,13 +267,15 @@ export default class VTClient {
username,
privacy,
description,
doUpload = true,
skipSafeDirCheck = false,
}: {
rootPath: string;
valName: string;
username: string;
privacy: "public" | "private" | "unlisted";
description?: string;
doUpload?: boolean;
skipSafeDirCheck: boolean;
}): Promise<VTClient> {
if (!skipSafeDirCheck) {
Expand All @@ -286,6 +288,7 @@ export default class VTClient {
valName,
privacy,
description,
doUpload,
});

// Get the Val branch
Expand Down
Loading