Skip to content

Commit 9c6f0fe

Browse files
authored
feat: Initial work on shell. (#1334)
1 parent b418ca5 commit 9c6f0fe

File tree

32 files changed

+715
-16
lines changed

32 files changed

+715
-16
lines changed

deno.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"./packages/cli",
77
"./packages/deno-vite-plugin",
88
"./packages/deno-web-test",
9+
"./packages/felt",
910
"./packages/html",
1011
"./packages/identity",
1112
"./packages/iframe-sandbox",
@@ -16,6 +17,7 @@
1617
"./packages/memory",
1718
"./packages/runner",
1819
"./packages/seeder",
20+
"./packages/shell",
1921
"./packages/static",
2022
"./packages/toolshed",
2123
"./packages/ui",
@@ -39,7 +41,8 @@
3941
],
4042
"types": [
4143
"./packages/jumble/src/global.d.ts"
42-
]
44+
],
45+
"experimentalDecorators": true
4346
},
4447
"exclude": [
4548
"packages/jumble/.vite/deps/",
@@ -121,4 +124,4 @@
121124
"zod-to-json-schema": "npm:zod-to-json-schema@^3.24.1",
122125
"zod": "npm:zod@^3.24.1"
123126
}
124-
}
127+
}

deno.lock

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/test/dev.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { describe, it } from "@std/testing/bdd";
22
import { expect } from "@std/expect";
3-
import { bytesToLines, ct } from "./utils.ts";
3+
import { bytesToLines, checkStderr, ct } from "./utils.ts";
44

55
describe("cli dev", () => {
66
it("Executes a package", async () => {
77
const { code, stdout, stderr } = await ct("dev fixtures/pow-5.tsx");
8-
expect(stderr.length).toBe(1); // deno run etc.
8+
checkStderr(stderr);
99
expect(stdout[stdout.length - 1]).toBe("25");
1010
expect(code).toBe(0);
1111
});
1212

1313
it("Runs a recipe with commontools+3P modules", async () => {
1414
const { code, stdout, stderr } = await ct("dev fixtures/3p-modules.tsx");
15-
expect(stderr.length).toBe(1); // deno run etc.
15+
checkStderr(stderr);
1616
expect(JSON.parse(stdout.join("\n")).argumentSchema).toBeTruthy();
1717
expect(code).toBe(0);
1818
});
@@ -22,7 +22,7 @@ describe("cli dev", () => {
2222
const { code, stdout, stderr } = await ct(
2323
`dev fixtures/recipe.tsx --no-run --filename test-file.js --output ${temp}`,
2424
);
25-
expect(stderr.length).toBe(1); // deno run etc.
25+
checkStderr(stderr);
2626
expect(stdout.length).toBe(0);
2727
expect(code).toBe(0);
2828
const rendered = bytesToLines(await Deno.readFile(temp));

packages/cli/test/id.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { describe, it } from "@std/testing/bdd";
22
import { expect } from "@std/expect";
33
import { encode } from "@commontools/utils/encoding";
4-
import { ct } from "./utils.ts";
4+
import { checkStderr, ct } from "./utils.ts";
55
import { Identity } from "@commontools/identity";
66

77
const PKCS8_KEY = `-----BEGIN PRIVATE KEY-----
@@ -20,7 +20,7 @@ describe("cli id", () => {
2020
const keyBuffer = encode(stdout.join("\n"));
2121
await Identity.fromPkcs8(keyBuffer);
2222
expect(code).toBe(0);
23-
expect(stderr.length).toBe(1); // deno run etc.
23+
checkStderr(stderr);
2424
});
2525

2626
it("Reads DID from key", async () => {
@@ -30,7 +30,7 @@ describe("cli id", () => {
3030
const did = stdout.join("\n");
3131
expect(did).toBe(PKCS8_KEY_DID);
3232
expect(code).toBe(0);
33-
expect(stderr.length).toBe(1); // deno run etc.
33+
checkStderr(stderr);
3434
});
3535

3636
it("Reads DID from key", async () => {
@@ -39,6 +39,6 @@ describe("cli id", () => {
3939
const identity = await Identity.fromPkcs8(keyBuffer);
4040
expect(identity.did()).toBe(COMMON_USER_DID);
4141
expect(code).toBe(0);
42-
expect(stderr.length).toBe(1); // deno run etc.
42+
checkStderr(stderr);
4343
});
4444
});

packages/cli/test/init.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, it } from "@std/testing/bdd";
22
import { expect } from "@std/expect";
33
import { join } from "@std/path";
44
import { exists } from "@std/fs/exists";
5-
import { ct } from "./utils.ts";
5+
import { checkStderr, ct } from "./utils.ts";
66

77
describe("cli init", () => {
88
it("Initializes workspace", async () => {
@@ -14,7 +14,7 @@ describe("cli init", () => {
1414

1515
try {
1616
const { code, stdout, stderr } = await ct("init");
17-
expect(stderr.length).toBe(1); // deno run etc.
17+
checkStderr(stderr);
1818
expect(stdout.length).toBe(0);
1919
expect(code).toBe(0);
2020

packages/cli/test/utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import { decode } from "@commontools/utils/encoding";
22
import { join } from "@std/path";
3+
import { expect } from "@std/expect/expect";
34

45
// Decodes a `Uint8Array` into an array of strings for each line.
56
export function bytesToLines(stream: Uint8Array): string[] {
67
return decode(stream).split("\n").filter(Boolean);
78
}
89

10+
export function checkStderr(stderr: string[]) {
11+
expect(stderr.length).toBe(2);
12+
expect(stderr[0]).toMatch(/deno run /);
13+
expect(stderr[1]).toMatch(/experimentalDecorators compiler option/);
14+
}
15+
916
// Executes the `ct` command via CLI
1017
// `const { stdout, stderr, code } = ct("dev --no-run ./recipe.tsx")`
1118
export async function ct(

packages/deno-web-test/test/base.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Deno.test("smoke test", async function () {
2020
assert(/add-async ... ok/.test(stdoutText), "test output ok");
2121
assert(/ok | 2 passed | 0 failed/.test(stdoutText), "test output ok");
2222
assert(/deno run/.test(stderrText), "stderr has deno task run");
23-
assert(stderrText.split("\n").length === 2, "stderr has no other messages");
24-
assert(stderrText.split("\n")[1] === "", "stderr has no other messages");
23+
assert(/experimentalDecorators/.test(stderrText), "stderr has compiler options warning");
24+
assert(stderrText.split("\n").length === 3, "stderr has no other messages");
25+
assert(stderrText.split("\n")[2] === "", "stderr has no other messages");
2526
});

packages/deno-web-test/test/config.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Deno.test("config is applied", async function () {
1515
assert(/LOG FROM TEST/.test(stdoutText), "console output propagated");
1616

1717
assert(/deno run/.test(stderrText), "stderr has deno task run");
18-
assert(stderrText.split("\n").length === 2, "stderr has no other messages");
19-
assert(stderrText.split("\n")[1] === "", "stderr has no other messages");
18+
assert(/experimentalDecorators/.test(stderrText), "stderr has compiler options warning");
19+
assert(stderrText.split("\n").length === 3, "stderr has no other messages");
20+
assert(stderrText.split("\n")[2] === "", "stderr has no other messages");
2021
});

packages/felt/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# @commontools/felt
2+
3+
**F**ront**e**nd **L**ightweight **T**ooling
4+
5+
A lightweight frontend build tool.
6+
7+
## Config
8+
9+
Configuration can be stored in a project root's `felt.config.ts` file. Only
10+
`entry` and `out` are required.
11+
12+
```ts
13+
// felt.config.ts
14+
export default {
15+
entry: "src/index.ts",
16+
out: "public/scripts/index.js",
17+
hostname: "127.0.0.1",
18+
port: 5173,
19+
publicDir: "public",
20+
watchDir: "src",
21+
esbuild: {
22+
sourcemap: true,
23+
minify: false,
24+
// https://esbuild.github.io/api/#external
25+
external: ["some-package-that-cant-be-resolved"],
26+
// Global variables to be replaced with static values.
27+
define: {
28+
"$DEBUG": Deno.env.get("DEBUG"),
29+
},
30+
},
31+
};
32+
```
33+
34+
## Commands
35+
36+
- `felt build .`: Build current project.
37+
- `felt serve .`: Serve current project.
38+
- `felt dev .`: Serves current project, rebuilding and reloading on source
39+
change.

packages/felt/builder.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import * as esbuild from "esbuild";
2+
import { denoPlugins } from "@luca/esbuild-deno-loader";
3+
import { debounce } from "@std/async/debounce";
4+
import { ResolvedConfig } from "./interface.ts";
5+
6+
export class Builder extends EventTarget {
7+
constructor(public manifest: ResolvedConfig) {
8+
super();
9+
}
10+
11+
async watch(watchRoot: string, debounceTimeout: number = 200) {
12+
const fn = debounce(this.build.bind(this), debounceTimeout);
13+
const watcher = Deno.watchFs(watchRoot);
14+
for await (const _ of watcher) {
15+
await fn();
16+
}
17+
}
18+
19+
async build() {
20+
console.log("Building...");
21+
22+
const config: Partial<Parameters<typeof esbuild.build>[0]> = {
23+
define: resolveDefines(this.manifest),
24+
sourcemap: this.manifest.esbuild.sourcemap,
25+
minify: this.manifest.esbuild.minify,
26+
plugins: [...denoPlugins()],
27+
platform: "browser",
28+
entryPoints: [this.manifest.entry],
29+
outfile: this.manifest.out,
30+
external: this.manifest.esbuild.external,
31+
bundle: true,
32+
format: "esm",
33+
// Explicitly compile decorators, as this what Jumble->Vite
34+
// does, and no browsers currently support (any form of) decorators,
35+
// and if we're bundling, we're probably running in a browser.
36+
tsconfigRaw: {
37+
compilerOptions: {
38+
experimentalDecorators: true,
39+
},
40+
},
41+
};
42+
43+
if (this.manifest.esbuild.metafile) {
44+
config.metafile = true;
45+
}
46+
47+
const result = await esbuild.build(config);
48+
esbuild.stop();
49+
50+
if (this.manifest.esbuild.metafile && result.metafile) {
51+
await Deno.writeTextFile(
52+
this.manifest.esbuild.metafile,
53+
JSON.stringify(result.metafile),
54+
);
55+
56+
console.log(await esbuild.analyzeMetafile(result.metafile));
57+
}
58+
this.dispatchEvent(new CustomEvent("build"));
59+
}
60+
}
61+
62+
function resolveDefines(
63+
manifest: ResolvedConfig,
64+
): Record<string, string> {
65+
return Object.keys(manifest.esbuild.define).reduce((defines, envName) => {
66+
const value = manifest.esbuild.define[envName];
67+
defines[envName] = typeof value === "string" ? `"${value}"` : `undefined`;
68+
return defines;
69+
}, {} as Record<string, string>);
70+
}

0 commit comments

Comments
 (0)