Deno 2.1.7, Project Idioms

I’ve noticed a collection of file and code idioms I’ve been used in my recent Deno+TypeScript projects at work. I’ve captured them here as a future reference.

Project files

My project generally have the following files, these are derived from the CodeMeta file using CMTools.

codemeta.json
Primary source of project metadata, used to generate various files
CITATION.cff
used by GitHub for citations. version, dateModified, datePublished and releaseNotes
about.md
A project about page. This is generated based on the codemeta.json file.
README.md, README
A general read me describing the project and pointing to INSTALL.md, user_manual.md as appropriate
INSTALL.md
These are boiler plate description of how to install and compile the software
user_manual.md
This is an index document, a table of contents. It points to other document including Markdown versions of the man page(s).

For TypeScript projects I also include a following

version.ts
This hold project version information used in the TypeScript co-debase. It is generated from the codemeta.json via CMTools.
helptext.ts
This is where I place a function, fmtHelp(), for rendering response to the “help” command line option.

I’m currently ambivalent about “main.ts” file which is created by deno init. My ambivalent is that most of my projects wind up producing more than one program from a shared code base. a single “main.ts” doesn’t really fit that situation.

The command line tool will have a TypeScript with it’s name. Inside this file I’ll have a main function and use the Deno idiom if (import.meta.main) main(); to invoke it. I don’t generally put the command line TypeScript in my “mod.ts” file since it’s not going to work in a browser or be useful outside my specific project.

mod.ts
I usually re-export modules here that maybe useful outside my project (or in the web browser).
deps.ts
I use this if there are allot of files consistently being imported across the project, otherwise I skip it.

What I put in Main

I use the main function to define command line options, handle parameters such as data input, output and errors. It usually invokes a primary function modeled in the rest of the project code.

Here is an example Main for a simple “cat” like program.

  1. import { parseArgs } from "jsr:@std/cli";
  2. import { licenseText, releaseDate, releaseHash, version } from "./version.ts";
  3. import { fmtHelp, helpText } from "./helptext.ts";
  4. const appName = "mycat";
  5. async function main() {
  6. const app = parseArgs(Deno.args, {
  7. alias: {
  8. help: "h",
  9. license: "l",
  10. version: "v",
  11. },
  12. default: {
  13. help: false,
  14. version: false,
  15. license: false,
  16. },
  17. });
  18. const args = app._;
  19. if (app.help) {
  20. console.log(fmtHelp(helpText, appName, version, releaseDate, releaseHash));
  21. Deno.exit(0);
  22. }
  23. if (app.license) {
  24. console.log(licenseText);
  25. Deno.exit(0);
  26. }
  27. if (app.version) {
  28. console.log(`${appName} ${version} ${releaseHash}`);
  29. Deno.exit(0);
  30. }
  31. let input: Deno.FsFile | any = Deno.stdin;
  32. // handle case of many file names
  33. if (args.length > 1) {
  34. for (const arg of args) {
  35. input = await Deno.open(`${arg}`);
  36. for await (const chunk of input.readable) {
  37. const decoder = new TextDecoder();
  38. console.log(decoder.decode(chunk));
  39. }
  40. }
  41. Deno.exit(0);
  42. }
  43. if (args.length > 0) {
  44. input = await Deno.open(Deno.args[0]);
  45. }
  46. for await (const chunk of input.readable) {
  47. const decoder = new TextDecoder();
  48. console.log(decoder.decode(chunk));
  49. }
  50. }
  51. if (import.meta.main) main();

helptext.ts

The following is an example of the helptext.ts file for the demo mycat.ts.

  1. export function fmtHelp(
  2. txt: string,
  3. appName: string,
  4. version: string,
  5. releaseDate: string,
  6. releaseHash: string,
  7. ): string {
  8. return txt.replaceAll("{app_name}", appName).replaceAll("{version}", version)
  9. .replaceAll("{release_date}", releaseDate).replaceAll(
  10. "{release_hash}",
  11. releaseHash,
  12. );
  13. }
  14. export const helpText =
  15. `%{app_name}(1) user manual | version {version} {release_hash}
  16. % R. S. Doiel
  17. % {release_date}
  18. # NAME
  19. {app_name}
  20. # SYNOPSIS
  21. {app_name} FILE [FILE ...] [OPTIONS]
  22. # DESCRIPTION
  23. {app_name} implements a "cat" like program.
  24. # OPTIONS
  25. Options come as the last parameter(s) on the command line.
  26. -h, --help
  27. : display help
  28. -v, --version
  29. : display version
  30. -l, --license
  31. : display license
  32. # EXAMPLES
  33. ~~~shell
  34. {app_name} README.md
  35. {app_name} README.md INSTALL.md
  36. ~~~
  37. `;

Generating version.ts

The version.ts is generated form two files, [codemeta.json] and [LICENSE] using the CMTools, cmt command.

  1. cmt codemeta.json veresion.ts