You've already forked hugo-extended
mirror of
https://github.com/jakejarvis/hugo-extended.git
synced 2026-06-24 10:25:57 -04:00
refactor: full typescript migration (#174)
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import hugo, { execWithOutput } from "../../src/hugo";
|
||||
|
||||
describe("Hugo Commands Integration", () => {
|
||||
beforeAll(async () => {
|
||||
// Ensure Hugo is installed
|
||||
const bin = await hugo();
|
||||
expect(bin).toBeTruthy();
|
||||
});
|
||||
|
||||
describe("version command", () => {
|
||||
it("should return Hugo version", async () => {
|
||||
const { stdout } = await execWithOutput("version");
|
||||
expect(stdout).toContain("hugo v");
|
||||
});
|
||||
});
|
||||
|
||||
describe("env command", () => {
|
||||
it("should return environment info", async () => {
|
||||
const { stdout } = await execWithOutput("env");
|
||||
expect(stdout).toContain("GOOS");
|
||||
expect(stdout).toContain("GOARCH");
|
||||
});
|
||||
});
|
||||
|
||||
describe("config command", () => {
|
||||
it("should return default config when no config file exists", async () => {
|
||||
// As of Hugo v0.154.x, the config command returns default configuration
|
||||
// even when no config file exists, rather than throwing an error
|
||||
const { stdout } = await execWithOutput("config");
|
||||
expect(stdout).toContain("contentdir");
|
||||
expect(stdout).toContain("publishdir");
|
||||
expect(stdout).toContain("defaultcontentlanguage");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,135 @@
|
||||
import { access, mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { hugo } from "../../src/hugo";
|
||||
|
||||
describe("New Commands Integration", () => {
|
||||
let tempDir: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Create temp directory for each test
|
||||
tempDir = await mkdtemp(join(tmpdir(), "hugo-test-"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Cleanup
|
||||
await rm(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("new.site", () => {
|
||||
it("should create a new site with default format", async () => {
|
||||
const sitePath = join(tempDir, "test-site");
|
||||
await hugo.new.site(sitePath);
|
||||
|
||||
// Check that essential directories exist
|
||||
await expect(
|
||||
access(join(sitePath, "hugo.toml")),
|
||||
).resolves.toBeUndefined();
|
||||
await expect(access(join(sitePath, "content"))).resolves.toBeUndefined();
|
||||
await expect(access(join(sitePath, "themes"))).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("should create a new site with yaml format", async () => {
|
||||
const sitePath = join(tempDir, "test-site-yaml");
|
||||
await hugo.new.site(sitePath, { format: "yaml" });
|
||||
|
||||
await expect(
|
||||
access(join(sitePath, "hugo.yaml")),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("should create a new site with json format", async () => {
|
||||
const sitePath = join(tempDir, "test-site-json");
|
||||
await hugo.new.site(sitePath, { format: "json" });
|
||||
|
||||
await expect(
|
||||
access(join(sitePath, "hugo.json")),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("should respect force flag", async () => {
|
||||
const sitePath = join(tempDir, "test-site-force");
|
||||
|
||||
// Create site first time
|
||||
await hugo.new.site(sitePath);
|
||||
|
||||
// Hugo 0.154.x does not overwrite an existing site config file, even with --force.
|
||||
// Future versions may change this behavior, so test accepts either outcome.
|
||||
try {
|
||||
await hugo.new.site(sitePath, { force: true });
|
||||
// If it succeeds, verify the site still exists
|
||||
await expect(
|
||||
access(join(sitePath, "hugo.toml")),
|
||||
).resolves.toBeUndefined();
|
||||
} catch (error) {
|
||||
// If it fails, that's the current 0.154.x behavior
|
||||
expect(error).toBeDefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("new.theme", () => {
|
||||
it("should create a new theme", async () => {
|
||||
const sitePath = join(tempDir, "test-site");
|
||||
await hugo.new.site(sitePath);
|
||||
|
||||
// Use --source instead of process.chdir (not supported in worker threads)
|
||||
await hugo.new.theme("test-theme", { source: sitePath });
|
||||
|
||||
const themePath = join(sitePath, "themes", "test-theme");
|
||||
await expect(access(themePath)).resolves.toBeUndefined();
|
||||
await expect(
|
||||
access(join(themePath, "hugo.toml")),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("should create theme with yaml format", async () => {
|
||||
const sitePath = join(tempDir, "test-site");
|
||||
await hugo.new.site(sitePath);
|
||||
|
||||
await hugo.new.theme("test-theme-yaml", {
|
||||
format: "yaml",
|
||||
source: sitePath,
|
||||
});
|
||||
|
||||
const themePath = join(sitePath, "themes", "test-theme-yaml");
|
||||
await expect(
|
||||
access(join(themePath, "hugo.yaml")),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("new.content", () => {
|
||||
it("should create new content file", async () => {
|
||||
const sitePath = join(tempDir, "test-site");
|
||||
await hugo.new.site(sitePath);
|
||||
|
||||
await hugo.new.content("posts/my-post.md", { source: sitePath });
|
||||
|
||||
const postPath = join(sitePath, "content", "posts", "my-post.md");
|
||||
await expect(access(postPath)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("should create content with custom kind", async () => {
|
||||
const sitePath = join(tempDir, "test-site");
|
||||
await hugo.new.site(sitePath);
|
||||
|
||||
await hugo.new.content("pages/about.md", {
|
||||
kind: "page",
|
||||
source: sitePath,
|
||||
});
|
||||
|
||||
const pagePath = join(sitePath, "content", "pages", "about.md");
|
||||
await expect(access(pagePath)).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("backwards compatibility", () => {
|
||||
it("should work without positional arguments", async () => {
|
||||
// Commands that don't require positional args should still work
|
||||
await expect(hugo.version()).resolves.not.toThrow();
|
||||
await expect(hugo.env()).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,129 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildArgs } from "../../src/lib/args";
|
||||
|
||||
describe("buildArgs", () => {
|
||||
describe("basic command", () => {
|
||||
it("should handle command without options", () => {
|
||||
const args = buildArgs("build");
|
||||
expect(args).toEqual(["build"]);
|
||||
});
|
||||
|
||||
it("should handle multi-word command", () => {
|
||||
const args = buildArgs("mod clean");
|
||||
expect(args).toEqual(["mod", "clean"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("positional arguments", () => {
|
||||
it("should add positional arguments after command", () => {
|
||||
const args = buildArgs("new site", ["my-site"]);
|
||||
expect(args).toEqual(["new", "site", "my-site"]);
|
||||
});
|
||||
|
||||
it("should handle multiple positional arguments", () => {
|
||||
const args = buildArgs("command", ["arg1", "arg2", "arg3"]);
|
||||
expect(args).toEqual(["command", "arg1", "arg2", "arg3"]);
|
||||
});
|
||||
|
||||
it("should handle positional args with options", () => {
|
||||
const args = buildArgs("new site", ["my-site"], { format: "yaml" });
|
||||
expect(args).toEqual(["new", "site", "my-site", "--format", "yaml"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("boolean flags", () => {
|
||||
it("should add flag when true", () => {
|
||||
const args = buildArgs("build", undefined, { minify: true });
|
||||
expect(args).toContain("--minify");
|
||||
});
|
||||
|
||||
it("should not add flag when false", () => {
|
||||
const args = buildArgs("build", undefined, { minify: false });
|
||||
expect(args).not.toContain("--minify");
|
||||
});
|
||||
|
||||
it("should handle multiple boolean flags", () => {
|
||||
const args = buildArgs("build", undefined, {
|
||||
minify: true,
|
||||
buildDrafts: true,
|
||||
cleanDestinationDir: false,
|
||||
});
|
||||
expect(args).toContain("--minify");
|
||||
// Flags with an explicit spec keep Hugo's canonical casing.
|
||||
expect(args).toContain("--buildDrafts");
|
||||
expect(args).not.toContain("--cleanDestinationDir");
|
||||
});
|
||||
});
|
||||
|
||||
describe("string flags", () => {
|
||||
it("should add flag with value", () => {
|
||||
const args = buildArgs("server", undefined, { port: 1313 });
|
||||
expect(args).toContain("--port");
|
||||
expect(args).toContain("1313");
|
||||
});
|
||||
|
||||
it("should keep spec flag casing (e.g. baseURL)", () => {
|
||||
const args = buildArgs("server", undefined, {
|
||||
baseURL: "http://localhost",
|
||||
});
|
||||
expect(args).toContain("--baseURL");
|
||||
expect(args).toContain("http://localhost");
|
||||
});
|
||||
|
||||
it("should convert unknown camelCase to kebab-case", () => {
|
||||
const args = buildArgs("build", undefined, {
|
||||
someUnknownFlag: "value",
|
||||
});
|
||||
expect(args).toContain("--some-unknown-flag");
|
||||
expect(args).toContain("value");
|
||||
});
|
||||
});
|
||||
|
||||
describe("array flags", () => {
|
||||
it("should repeat flag for each array element", () => {
|
||||
const args = buildArgs("build", undefined, { theme: ["a", "b", "c"] });
|
||||
const themeIndices = args.reduce<number[]>((acc, arg, i) => {
|
||||
if (arg === "--theme") acc.push(i);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
expect(themeIndices).toHaveLength(3);
|
||||
expect(args[themeIndices[0] + 1]).toBe("a");
|
||||
expect(args[themeIndices[1] + 1]).toBe("b");
|
||||
expect(args[themeIndices[2] + 1]).toBe("c");
|
||||
});
|
||||
});
|
||||
|
||||
describe("complex scenarios", () => {
|
||||
it("should handle mixed flags and positional args", () => {
|
||||
const args = buildArgs("new content", ["posts/my-post.md"], {
|
||||
kind: "post",
|
||||
force: true,
|
||||
editor: "vim",
|
||||
});
|
||||
|
||||
expect(args).toEqual([
|
||||
"new",
|
||||
"content",
|
||||
"posts/my-post.md",
|
||||
"--kind",
|
||||
"post",
|
||||
"--force",
|
||||
"--editor",
|
||||
"vim",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should skip undefined and null values", () => {
|
||||
const args = buildArgs("build", undefined, {
|
||||
minify: true,
|
||||
baseURL: undefined,
|
||||
destination: null,
|
||||
});
|
||||
|
||||
expect(args).toContain("--minify");
|
||||
expect(args).not.toContain("--base-u-r-l");
|
||||
expect(args).not.toContain("--destination");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { describe, expectTypeOf, it } from "vitest";
|
||||
import type { HugoCommand, HugoOptionsFor } from "../../src/generated/types";
|
||||
|
||||
describe("Type Safety", () => {
|
||||
it("should have correct command types", () => {
|
||||
// Check that specific commands are valid HugoCommand values
|
||||
expectTypeOf<"build">().toExtend<HugoCommand>();
|
||||
expectTypeOf<"server">().toExtend<HugoCommand>();
|
||||
expectTypeOf<"new site">().toExtend<HugoCommand>();
|
||||
expectTypeOf<"new theme">().toExtend<HugoCommand>();
|
||||
expectTypeOf<"new content">().toExtend<HugoCommand>();
|
||||
});
|
||||
|
||||
it("should map commands to correct option types", () => {
|
||||
type BuildOpts = HugoOptionsFor<"build">;
|
||||
type ServerOpts = HugoOptionsFor<"server">;
|
||||
type NewSiteOpts = HugoOptionsFor<"new site">;
|
||||
type NewThemeOpts = HugoOptionsFor<"new theme">;
|
||||
|
||||
// Build options should have minify
|
||||
expectTypeOf<BuildOpts>().toHaveProperty("minify");
|
||||
|
||||
// Server options should have port
|
||||
expectTypeOf<ServerOpts>().toHaveProperty("port");
|
||||
|
||||
// New site options should have format
|
||||
expectTypeOf<NewSiteOpts>().toHaveProperty("format");
|
||||
|
||||
// New theme options should have format
|
||||
expectTypeOf<NewThemeOpts>().toHaveProperty("format");
|
||||
});
|
||||
|
||||
it("should inherit global options", () => {
|
||||
type BuildOpts = HugoOptionsFor<"build">;
|
||||
|
||||
// All commands should have global options
|
||||
expectTypeOf<BuildOpts>().toHaveProperty("source");
|
||||
expectTypeOf<BuildOpts>().toHaveProperty("destination");
|
||||
expectTypeOf<BuildOpts>().toHaveProperty("environment");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user