Skip to content
Draft
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
5 changes: 3 additions & 2 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
"tasks": {
"upgrade-deps": "deno cache --frozen-lockfile=false 'packages/*/src/index.ts' test example npm:@types/node",
"check": "deno fmt --check && deno lint && deno check 'packages/**/*.ts' && deno doc --lint 'packages/**/*.ts'",
"test": "deno test --allow-net --no-check ./test",
"test:ci": "deno test --doc --allow-net --frozen-lockfile ./test './**/*.md'",
"test": "deno test --allow-net --allow-read --allow-write=tmp --allow-run=deno --no-check ./test",
"test:ci": "deno test --doc --allow-net --allow-read --allow-write=tmp --allow-run=deno --frozen-lockfile ./test './**/*.md'",
"bump-package": "deno run --allow-read=. --allow-write=. --allow-run=deno,git-cliff tools/bump-package.ts"
},
"workspace": {
"members": [
"./packages/create-nestjs",
"./packages/nestjs-denokv",
"./packages/nestjs-platform-hono",
"./packages/nestjs-platform-oak"
Expand Down
9 changes: 8 additions & 1 deletion deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

137 changes: 137 additions & 0 deletions packages/create-nestjs/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import type { $Type } from "@david/dax";
import { $ } from "@david/dax";
import { parseArgs } from "node:util";

import denoJson from "./deno.json" with { type: "json" };
import denoJsonTemplate from "./deno.template.json" with { type: "json" };
import { join } from "jsr:@std/path@^1.0/join";

type Framework = "oak" | "hono";
const supportedFrameworks: ReadonlyArray<Framework> = ["oak", "hono"];
const oakAdapterDeps = {
"@uki00a/nestjs-platform-hono": "jsr:@uki00a/nestjs-platform-hono",
"@oak/oak": "jsr:@oak/oak@^17",
};
const honoAdapterDeps = {
"@uki00a/nestjs-platform-hono": "jsr:@uki00a/nestjs-platform-hono",
"@hono/hono": "jsr:@hono/hono@^4",
};

function isSupportedFramework(
maybeFramework: string,
): maybeFramework is Framework {
return supportedFrameworks.includes(maybeFramework as Framework);
}

interface CreateOptions {
framework: Framework;
targetDir: string;
}
async function create(
options: CreateOptions,
) {
const additionalPackages = determineAdditionalPackages(options.framework);
const denoJson = {
...denoJsonTemplate,
imports: {
...denoJsonTemplate.imports,
...additionalPackages,
},
};
await Deno.writeTextFile(
join(options.targetDir, "deno.json"),
JSON.stringify(denoJson, null, 2),
);
}

function determineAdditionalPackages(
framework: Framework,
): Record<string, string> {
switch (framework) {
case "hono":
return honoAdapterDeps;
case "oak":
return oakAdapterDeps;
}
}

interface MainOptions {
framework?: string;
targetDir?: string;
}
async function main(options: MainOptions) {
const targetDir = options.targetDir ||
await $.prompt("What directory do you want to create a new project in?", {
default: "nestjs-project",
});
const framework = await selectFramework($, options);
await $.path(targetDir).mkdir({ recursive: true });
await create({
...options,
framework,
targetDir,
});
}
async function selectFramework(
$: $Type,
options: MainOptions,
): Promise<Framework> {
if (options.framework) {
if (!isSupportedFramework(options.framework)) {
throw new Error(
`--framework should be one of ${
supportedFrameworks.map((x) => `"${x}"`).join(", ")
}`,
);
}
return options.framework;
}
const indexOfFramework = await $.select({
message: "Which framework will you use?",
options: [...supportedFrameworks],
});
const framework = supportedFrameworks[indexOfFramework];
return framework;
}

if (import.meta.main) {
const parseArgsOptions = {
allowPositionals: true,
options: {
"framework": {
description: `A framework you want to use [${
supportedFrameworks.join(", ")
}]`,
type: "string",
},
"help": {
description: "Show this help text",
type: "boolean",
short: "h",
},
},
} as const;
const args = parseArgs(parseArgsOptions);
if (args.values.help) {
$.log(`${denoJson.name} - Creates a new NestJS project for Deno.

Usage:
deno run jsr:${denoJson.name} [TARGET_DIRECTORY]

Options:
${
Object.entries(parseArgsOptions.options).map((
[option, { description }],
) => ` ${option} - ${description}`).join("\n")
}`);
Deno.exit(0);
}

main({
framework: args.values.framework,
targetDir: args.positionals[0],
}).catch((error) => {
$.logError("Failed", error);
Deno.exit(1);
});
}
10 changes: 10 additions & 0 deletions packages/create-nestjs/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@uki00a/create-nestjs",
"version": "0.1.0",
"exports": {
".": "./cli.ts"
},
"imports": {
"@david/dax": "jsr:@david/dax@0.42.0"
}
}
17 changes: 17 additions & 0 deletions packages/create-nestjs/deno.template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"imports": {
"@nestjs/common": "npm:@nestjs/common@^10",
"@nestjs/core": "npm:@nestjs/core@^10"
},
"lint": {
"rules": {
"include": [
"no-console"
]
}
}
}
76 changes: 76 additions & 0 deletions test/create-nestjs/cli.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { mkdtemp, rm } from "node:fs/promises";
import { dirname, join } from "node:path";
import assert from "node:assert/strict";
import rootDenoJson from "../../deno.json" with { type: "json" };

async function withTempDir(
prefix: string,
thunk: (tmpdir: string) => Promise<void>,
): Promise<void> {
const tempDir = await mkdtemp(prefix);
try {
await thunk(tempDir);
} finally {
await rm(tempDir, { recursive: true, force: true });
}
}

const rootDir = dirname(dirname(dirname(new URL(import.meta.url).pathname)));
const tmpDir = join(rootDir, "tmp");

Deno.test({
name: "create-nestjs",
fn: async (t) => {
async function createNestJS(
targetDir: string,
framework: "hono" | "oak",
) {
const result = await new Deno.Command(Deno.execPath(), {
args: [
"run",
"--no-lock",
`--allow-read=${targetDir}`,
"--allow-write",
"--allow-run=deno",
join(rootDir, "packages/create-nestjs/cli.ts"),
"--framework",
framework,
targetDir,
],
}).output();
if (!result.success) {
throw new Error(new TextDecoder().decode(result.stderr));
}
}
const tempDirPrefix = join(tmpDir, "/create-nestjs-");
await t.step("hono", () =>
withTempDir(tempDirPrefix, async (tempDir) => {
await createNestJS(tempDir, "hono");
const denoJson = JSON.parse(
await Deno.readTextFile(join(tempDir, "deno.json")),
);
assert.ok(denoJson.imports);
assert.equal(
denoJson.imports["@hono/hono"],
rootDenoJson.imports["@hono/hono"],
);
assert.equal(
denoJson.imports["@uki00a/nestjs-platform-hono"],
"jsr:@uki00a/nestjs-platform-hono",
);
assert.equal(
denoJson.imports["@nestjs/common"],
rootDenoJson.imports["@nestjs/common"],
);
assert.equal(
denoJson.imports["@nestjs/core"],
rootDenoJson.imports["@nestjs/core"],
);
}));
},
permissions: {
read: true,
write: [tmpDir],
run: [Deno.execPath()],
},
});
1 change: 1 addition & 0 deletions tools/bump-package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ async function main() {
const tmpDir = join(rootDir, "tmp");
const cliffConfigsDir = join(tmpDir, "cliff");
const allowedPackages = [
"create-nestjs",
"nestjs-denokv",
"nestjs-platform-hono",
"nestjs-platform-oak",
Expand Down