Skip to content

Commit 310d026

Browse files
authored
Merge pull request #273 from coder/mes/readme-update
fix: add missing README information and clean up types
2 parents f446fbd + 982c75e commit 310d026

File tree

8 files changed

+110
-38
lines changed

8 files changed

+110
-38
lines changed

CONTRIBUTING.md

+50-6
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,47 @@
11
# Contributing
22

3-
To create a new module, clone this repository and run:
3+
## Getting started
4+
5+
This repo uses the [Bun runtime](https://bun.sh/) to to run all code and tests. To install Bun, you can run this command on Linux/MacOS:
6+
7+
```shell
8+
curl -fsSL https://bun.sh/install | bash
9+
```
10+
11+
Or this command on Windows:
412

513
```shell
6-
./new.sh MODULE_NAME
14+
powershell -c "irm bun.sh/install.ps1 | iex"
15+
```
16+
17+
Follow the instructions to ensure that Bun is available globally. Once Bun has been installed, clone this repository. From there, run this script to create a new module:
18+
19+
```shell
20+
./new.sh NAME_OF_NEW_MODULE
721
```
822

923
## Testing a Module
1024

25+
> **Note:** It is the responsibility of the module author to implement tests for their module. The author must test the module locally before submitting a PR.
26+
1127
A suite of test-helpers exists to run `terraform apply` on modules with variables, and test script output against containers.
1228

13-
The testing suite must be able to run docker containers with the `--network=host` flag, which typically requires running the tests on Linux as this flag does not apply to Docker Desktop for MacOS and Windows. MacOS users can work around this by using something like [colima](https://github.com/abiosoft/colima) or [Orbstack](https://orbstack.dev/) instead of Docker Desktop.
29+
The testing suite must be able to run docker containers with the `--network=host` flag. This typically requires running the tests on Linux as this flag does not apply to Docker Desktop for MacOS and Windows. MacOS users can work around this by using something like [colima](https://github.com/abiosoft/colima) or [Orbstack](https://orbstack.dev/) instead of Docker Desktop.
30+
31+
Reference the existing `*.test.ts` files to get an idea for how to set up tests.
1432

15-
Reference existing `*.test.ts` files for implementation.
33+
You can run all tests in a specific file with this command:
1634

1735
```shell
18-
# Run tests for a specific module!
1936
$ bun test -t '<module>'
2037
```
2138

39+
Or run all tests by running this command:
40+
41+
```shell
42+
$ bun test
43+
```
44+
2245
You can test a module locally by updating the source as follows
2346

2447
```tf
@@ -27,4 +50,25 @@ module "example" {
2750
}
2851
```
2952

30-
> **Note:** This is the responsibility of the module author to implement tests for their module. and test the module locally before submitting a PR.
53+
## Releases
54+
55+
> [!WARNING]
56+
> When creating a new release, make sure that your new version number is fully accurate. If a version number is incorrect or does not exist, we may end up serving incorrect/old data for our various tools and providers.
57+
58+
Much of our release process is automated. To cut a new release:
59+
60+
1. Navigate to [GitHub's Releases page](https://github.com/coder/modules/releases)
61+
2. Click "Draft a new release"
62+
3. Click the "Choose a tag" button and type a new release number in the format `v<major>.<minor>.<patch>` (e.g., `v1.18.0`). Then click "Create new tag".
63+
4. Click the "Generate release notes" button, and clean up the resulting README. Be sure to remove any notes that would not be relevant to end-users (e.g., bumping dependencies).
64+
5. Once everything looks good, click the "Publish release" button.
65+
66+
Once the release has been cut, a script will run to check whether there are any modules that will require that the new release number be published to Terraform. If there are any, a new pull request will automatically be generated. Be sure to approve this PR and merge it into the `main` branch.
67+
68+
Following that, our automated processes will handle publishing new data for [`registry.coder.com`](https://github.com/coder/registry.coder.com/):
69+
70+
1. Publishing new versions to Coder's [Terraform Registry](https://registry.terraform.io/providers/coder/coder/latest)
71+
2. Publishing new data to the [Coder Registry](https://registry.coder.com)
72+
73+
> [!NOTE]
74+
> Some data in `registry.coder.com` is fetched on demand from the Module repo's main branch. This data should be updated almost immediately after a new release, but other changes will take some time to propagate.

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
Modules
44
</h1>
55

6-
[Registry](https://registry.coder.com) | [Coder Docs](https://coder.com/docs) | [Why Coder](https://coder.com/why) | [Coder Enterprise](https://coder.com/docs/v2/latest/enterprise)
6+
[Module Registry](https://registry.coder.com) | [Coder Docs](https://coder.com/docs) | [Why Coder](https://coder.com/why) | [Coder Enterprise](https://coder.com/docs/v2/latest/enterprise)
77

88
[![discord](https://img.shields.io/discord/747933592273027093?label=discord)](https://discord.gg/coder)
99
[![license](https://img.shields.io/github/license/coder/modules)](./LICENSE)
1010

1111
</div>
1212

13-
Modules extend Templates to create reusable components for your development environment.
13+
Modules extend Coder Templates to create reusable components for your development environment.
1414

1515
e.g.
1616

lint.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import grayMatter from "gray-matter";
55

66
const files = await readdir(".", { withFileTypes: true });
77
const dirs = files.filter(
8-
(f) => f.isDirectory() && !f.name.startsWith(".") && f.name !== "node_modules"
8+
(f) =>
9+
f.isDirectory() && !f.name.startsWith(".") && f.name !== "node_modules",
910
);
1011

1112
let badExit = false;
1213

1314
// error reports an error to the console and sets badExit to true
1415
// so that the process will exit with a non-zero exit code.
15-
const error = (...data: any[]) => {
16+
const error = (...data: unknown[]) => {
1617
console.error(...data);
1718
badExit = true;
1819
};
@@ -22,15 +23,20 @@ const verifyCodeBlocks = (
2223
res = {
2324
codeIsTF: false,
2425
codeIsHCL: false,
25-
}
26+
},
2627
) => {
2728
for (const token of tokens) {
2829
// Check in-depth.
2930
if (token.type === "list") {
3031
verifyCodeBlocks(token.items, res);
3132
continue;
3233
}
34+
3335
if (token.type === "list_item") {
36+
if (token.tokens === undefined) {
37+
throw new Error("Tokens are missing for type list_item");
38+
}
39+
3440
verifyCodeBlocks(token.tokens, res);
3541
continue;
3642
}
@@ -80,8 +86,9 @@ for (const dir of dirs) {
8086
if (!data.maintainer_github) {
8187
error(dir.name, "missing maintainer_github");
8288
}
89+
8390
try {
84-
await stat(path.join(".", dir.name, data.icon));
91+
await stat(path.join(".", dir.name, data.icon ?? ""));
8592
} catch (ex) {
8693
error(dir.name, "icon does not exist", data.icon);
8794
}

slackme/main.test.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,10 @@ const assertSlackMessage = async (opts: {
126126
durationMS?: number;
127127
output: string;
128128
}) => {
129-
let url: URL;
129+
// Have to use non-null assertion because TS can't tell when the fetch
130+
// function will run
131+
let url!: URL;
132+
130133
const fakeSlackHost = serve({
131134
fetch: (req) => {
132135
url = new URL(req.url);
@@ -138,22 +141,24 @@ const assertSlackMessage = async (opts: {
138141
},
139142
port: 0,
140143
});
144+
141145
const { instance, id } = await setupContainer(
142146
"alpine/curl",
143-
opts.format && {
144-
slack_message: opts.format,
145-
},
147+
opts.format ? { slack_message: opts.format } : undefined,
146148
);
149+
147150
await writeCoder(id, "echo 'token'");
148151
let exec = await execContainer(id, ["sh", "-c", instance.script]);
149152
expect(exec.exitCode).toBe(0);
153+
150154
exec = await execContainer(id, [
151155
"sh",
152156
"-c",
153157
`DURATION_MS=${opts.durationMS || 0} SLACK_URL="http://${
154158
fakeSlackHost.hostname
155159
}:${fakeSlackHost.port}" slackme ${opts.command}`,
156160
]);
161+
157162
expect(exec.stderr.trim()).toBe("");
158163
expect(url.pathname).toEqual("/api/chat.postMessage");
159164
expect(url.searchParams.get("channel")).toEqual("token");

test.ts

+19-9
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,21 @@ type TerraformStateResource = {
9090
type: string;
9191
name: string;
9292
provider: string;
93-
instances: [{ attributes: Record<string, any> }];
93+
94+
instances: [
95+
{
96+
attributes: Record<string, JsonValue>;
97+
},
98+
];
9499
};
95100

96-
export interface TerraformState {
97-
outputs: {
98-
[key: string]: {
99-
type: string;
100-
value: any;
101-
};
102-
};
101+
type TerraformOutput = {
102+
type: string;
103+
value: JsonValue;
104+
};
103105

106+
export interface TerraformState {
107+
outputs: Record<string, TerraformOutput>;
104108
resources: [TerraformStateResource, ...TerraformStateResource[]];
105109
}
106110

@@ -149,19 +153,25 @@ export const testRequiredVariables = <TVars extends Record<string, string>>(
149153
it("required variables", async () => {
150154
await runTerraformApply(dir, vars);
151155
});
156+
152157
const varNames = Object.keys(vars);
153158
varNames.forEach((varName) => {
154159
// Ensures that every variable provided is required!
155160
it("missing variable " + varName, async () => {
156-
const localVars = {};
161+
const localVars: Record<string, string> = {};
157162
varNames.forEach((otherVarName) => {
158163
if (otherVarName !== varName) {
159164
localVars[otherVarName] = vars[otherVarName];
160165
}
161166
});
167+
162168
try {
163169
await runTerraformApply(dir, localVars);
164170
} catch (ex) {
171+
if (!(ex instanceof Error)) {
172+
throw new Error("Unknown error generated");
173+
}
174+
165175
expect(ex.message).toContain(
166176
`input variable \"${varName}\" is not set`,
167177
);

tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"compilerOptions": {
33
"target": "esnext",
44
"module": "esnext",
5+
"strict": true,
56
"allowSyntheticDefaultImports": true,
67
"moduleResolution": "nodenext",
78
"types": ["bun-types"]

vscode-desktop/main.test.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ describe("vscode-desktop", async () => {
2424
const coder_app = state.resources.find(
2525
(res) => res.type == "coder_app" && res.name == "vscode",
2626
);
27+
2728
expect(coder_app).not.toBeNull();
28-
expect(coder_app.instances.length).toBe(1);
29-
expect(coder_app.instances[0].attributes.order).toBeNull();
29+
expect(coder_app?.instances.length).toBe(1);
30+
expect(coder_app?.instances[0].attributes.order).toBeNull();
3031
});
3132

3233
it("adds folder", async () => {
@@ -80,8 +81,9 @@ describe("vscode-desktop", async () => {
8081
const coder_app = state.resources.find(
8182
(res) => res.type == "coder_app" && res.name == "vscode",
8283
);
84+
8385
expect(coder_app).not.toBeNull();
84-
expect(coder_app.instances.length).toBe(1);
85-
expect(coder_app.instances[0].attributes.order).toBe(22);
86+
expect(coder_app?.instances.length).toBe(1);
87+
expect(coder_app?.instances[0].attributes.order).toBe(22);
8688
});
8789
});

windows-rdp/main.test.ts

+12-9
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ function findWindowsRdpScript(state: TerraformState): string | null {
2424
}
2525

2626
for (const instance of resource.instances) {
27-
if (instance.attributes.display_name === "windows-rdp") {
27+
if (
28+
instance.attributes.display_name === "windows-rdp" &&
29+
typeof instance.attributes.script === "string"
30+
) {
2831
return instance.attributes.script;
2932
}
3033
}
@@ -100,11 +103,11 @@ describe("Web RDP", async () => {
100103
const defaultRdpScript = findWindowsRdpScript(defaultState);
101104
expect(defaultRdpScript).toBeString();
102105

103-
const { username: defaultUsername, password: defaultPassword } =
104-
formEntryValuesRe.exec(defaultRdpScript)?.groups ?? {};
106+
const defaultResultsGroup =
107+
formEntryValuesRe.exec(defaultRdpScript ?? "")?.groups ?? {};
105108

106-
expect(defaultUsername).toBe("Administrator");
107-
expect(defaultPassword).toBe("coderRDP!");
109+
expect(defaultResultsGroup.username).toBe("Administrator");
110+
expect(defaultResultsGroup.password).toBe("coderRDP!");
108111

109112
// Test that custom usernames/passwords are also forwarded correctly
110113
const customAdminUsername = "crouton";
@@ -122,10 +125,10 @@ describe("Web RDP", async () => {
122125
const customRdpScript = findWindowsRdpScript(customizedState);
123126
expect(customRdpScript).toBeString();
124127

125-
const { username: customUsername, password: customPassword } =
126-
formEntryValuesRe.exec(customRdpScript)?.groups ?? {};
128+
const customResultsGroup =
129+
formEntryValuesRe.exec(customRdpScript ?? "")?.groups ?? {};
127130

128-
expect(customUsername).toBe(customAdminUsername);
129-
expect(customPassword).toBe(customAdminPassword);
131+
expect(customResultsGroup.username).toBe(customAdminUsername);
132+
expect(customResultsGroup.password).toBe(customAdminPassword);
130133
});
131134
});

0 commit comments

Comments
 (0)