@druid-ui/build 1.0.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/gen-types.ts
4
+ import { generateTypes } from "@bytecodealliance/jco/component";
5
+ import { mkdir, rm, writeFile } from "fs/promises";
6
+ import { dirname, resolve as resolve2 } from "path";
7
+
8
+ // src/utils.ts
9
+ import path from "node:path";
10
+ import fsSync from "node:fs";
11
+ import fs from "node:fs/promises";
12
+ import { resolve, basename } from "node:path";
13
+ function getPackageJsonPath(startDir = import.meta.dirname) {
14
+ let dir = startDir;
15
+ while (dir !== path.parse(dir).root) {
16
+ const pkgJson = path.join(dir, "package.json");
17
+ if (fsSync.existsSync(pkgJson)) return dir;
18
+ dir = path.dirname(dir);
19
+ }
20
+ throw new Error("package.json not found");
21
+ }
22
+ async function getWitFolder(withFiles, witPath2 = "wit-out") {
23
+ await fs.mkdir(witPath2, { recursive: true });
24
+ const baseWitPath = getPackageJsonPath();
25
+ const witFiles = await fs.readdir(resolve(baseWitPath, "wit"));
26
+ for (const file of witFiles) {
27
+ await fs.copyFile(
28
+ resolve(baseWitPath, "wit", file),
29
+ resolve(witPath2, file)
30
+ );
31
+ }
32
+ for (const file of withFiles || []) {
33
+ const filename = basename(file);
34
+ await fs.copyFile(file, resolve(witPath2, filename));
35
+ }
36
+ }
37
+
38
+ // src/gen-types.ts
39
+ async function genTypes(withFiles, worldName2 = "druid-ui", typePath = "types", witPath2 = "wit-out") {
40
+ await getWitFolder(withFiles, witPath2);
41
+ const t = await generateTypes(worldName2, {
42
+ wit: {
43
+ tag: "path",
44
+ val: resolve2(witPath2)
45
+ },
46
+ world: worldName2,
47
+ instantiation: {
48
+ tag: "async"
49
+ },
50
+ guest: true
51
+ });
52
+ for (const [filename, contents] of t) {
53
+ console.log("Writing generated type:", typePath + "/" + filename);
54
+ await mkdir(resolve2(typePath, dirname(filename)), { recursive: true });
55
+ await writeFile(resolve2(typePath, filename), contents);
56
+ }
57
+ await rm(witPath2, { recursive: true });
58
+ }
59
+
60
+ // src/cmd/gen-types.ts
61
+ var args = process.argv.slice(2);
62
+ var witPath;
63
+ var outDir;
64
+ var additionalFiles = [];
65
+ args = args.filter((arg) => {
66
+ if (arg.startsWith("--temp-wit-path=")) {
67
+ witPath = arg.slice(16);
68
+ return false;
69
+ }
70
+ if (arg.startsWith("--out-dir=")) {
71
+ outDir = arg.slice(10);
72
+ return false;
73
+ }
74
+ return true;
75
+ });
76
+ if (args.length < 1) {
77
+ console.error(
78
+ "Usage: gen-types <world-name> [additional-wit-files...] [--wit-path=<path>] [--out-dir=<path>]"
79
+ );
80
+ process.exit(1);
81
+ }
82
+ var worldName = args[0];
83
+ additionalFiles.push(...args.slice(1));
84
+ await genTypes(additionalFiles, worldName, outDir, witPath);
package/bin/index.js ADDED
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { componentize } from "@bytecodealliance/componentize-js";
5
+ import { build } from "esbuild";
6
+ import fs3 from "node:fs/promises";
7
+ import { fileURLToPath } from "node:url";
8
+ import { createRequire } from "node:module";
9
+ import { basename as basename2 } from "node:path";
10
+
11
+ // src/utils.ts
12
+ import path from "node:path";
13
+ import fsSync from "node:fs";
14
+ import fs from "node:fs/promises";
15
+ import { resolve, basename } from "node:path";
16
+ function getPackageJsonPath(startDir = import.meta.dirname) {
17
+ let dir = startDir;
18
+ while (dir !== path.parse(dir).root) {
19
+ const pkgJson = path.join(dir, "package.json");
20
+ if (fsSync.existsSync(pkgJson)) return dir;
21
+ dir = path.dirname(dir);
22
+ }
23
+ throw new Error("package.json not found");
24
+ }
25
+ async function getWitFolder(withFiles, witPath = "wit-out") {
26
+ await fs.mkdir(witPath, { recursive: true });
27
+ const baseWitPath = getPackageJsonPath();
28
+ const witFiles2 = await fs.readdir(resolve(baseWitPath, "wit"));
29
+ for (const file of witFiles2) {
30
+ await fs.copyFile(
31
+ resolve(baseWitPath, "wit", file),
32
+ resolve(witPath, file)
33
+ );
34
+ }
35
+ for (const file of withFiles || []) {
36
+ const filename = basename(file);
37
+ await fs.copyFile(file, resolve(witPath, filename));
38
+ }
39
+ }
40
+
41
+ // src/esbuild-plugin.ts
42
+ import fs2 from "fs/promises";
43
+ function druidExtensionPlugin() {
44
+ return {
45
+ name: "druid-extension",
46
+ setup(build2) {
47
+ build2.onLoad({ filter: /\.[jt]sx?$/ }, async (args2) => {
48
+ let code = await fs2.readFile(args2.path, "utf8");
49
+ if (!code.includes("druid:ui/")) return;
50
+ const transformNamed = (_, vars, name) => {
51
+ const mapped = vars.split(",").map((v) => {
52
+ const [left, right] = v.split(/\s+as\s+/).map((s) => s.trim());
53
+ return right ? `${left}: ${right}` : left;
54
+ }).join(", ");
55
+ return `const { ${mapped} } = window["druid-extension"]["${name}"];`;
56
+ };
57
+ code = code.replace(
58
+ /import\s*{\s*([^}]+)\s*}\s*from\s*["'](druid:ui\/[^"']+)["'];?/g,
59
+ transformNamed
60
+ ).replace(
61
+ /import\s+\*\s+as\s+([\w$]+)\s+from\s*["'](druid:ui\/[^"']+)["'];?/g,
62
+ (_, id, name) => `const ${id} = window["druid-extension"]["${name}"];`
63
+ ).replace(
64
+ /import\s+([\w$]+)\s+from\s*["'](druid:ui\/[^"']+)["'];?/g,
65
+ (_, id, name) => `const ${id} = window["druid-extension"]["${name}"];`
66
+ );
67
+ let loader = "js";
68
+ if (args2.path.endsWith(".ts")) loader = "ts";
69
+ else if (args2.path.endsWith(".tsx")) loader = "tsx";
70
+ else if (args2.path.endsWith(".jsx")) loader = "jsx";
71
+ return { contents: code, loader };
72
+ });
73
+ }
74
+ };
75
+ }
76
+
77
+ // src/gen-types.ts
78
+ import { generateTypes } from "@bytecodealliance/jco/component";
79
+
80
+ // src/index.ts
81
+ var require2 = createRequire(import.meta.url);
82
+ var getBundleFileName = (entryFile2) => {
83
+ const name = basename2(entryFile2, ".tsx");
84
+ const outfilename = name + ".bundled.js";
85
+ return outfilename;
86
+ };
87
+ async function buildWasm(entryFile2, outfolder2 = "./dist", witExtension) {
88
+ await fs3.mkdir(outfolder2, { recursive: true });
89
+ const outfilename = getBundleFileName(entryFile2);
90
+ const outfile = outfolder2 + "/" + outfilename;
91
+ console.log("Building", entryFile2, "to", outfile);
92
+ await build({
93
+ entryPoints: [entryFile2],
94
+ bundle: true,
95
+ write: true,
96
+ format: "esm",
97
+ outfile,
98
+ external: ["druid:ui/*"]
99
+ });
100
+ const __filename = fileURLToPath(import.meta.url);
101
+ let witDist = outfolder2 + "/wit";
102
+ if (witExtension?.files?.length) {
103
+ await getWitFolder(witExtension.files, witDist);
104
+ } else {
105
+ witDist = getPackageJsonPath() + "/wit";
106
+ }
107
+ console.log("Generating WIT to", witDist);
108
+ const result = await componentize({
109
+ witPath: witDist,
110
+ worldName: witExtension?.worldName || "druid-ui",
111
+ sourcePath: outfile,
112
+ disableFeatures: ["clocks", "http", "random", "stdio", "fetch-event"]
113
+ });
114
+ const name = basename2(entryFile2, ".tsx");
115
+ await fs3.writeFile(outfolder2 + "/" + name + ".wasm", result.component);
116
+ }
117
+ async function buildRaw(entryFile2, outfolder2 = "./dist") {
118
+ const outfilename = getBundleFileName(entryFile2);
119
+ const outfile = outfolder2 + "/" + outfilename;
120
+ const rawPath = require2.resolve("@druid-ui/component/raw");
121
+ await build({
122
+ entryPoints: [entryFile2],
123
+ bundle: true,
124
+ write: true,
125
+ format: "esm",
126
+ outfile,
127
+ plugins: [druidExtensionPlugin()],
128
+ alias: {
129
+ "@druid-ui/component": rawPath
130
+ }
131
+ });
132
+ }
133
+
134
+ // src/cmd/index.ts
135
+ console.log("Druid UI Build Tool");
136
+ var args = process.argv;
137
+ var duBuildRaw = false;
138
+ var worldName = "druid-ui";
139
+ var witFiles = [];
140
+ args = args.filter((arg) => {
141
+ if (arg === "--raw") {
142
+ duBuildRaw = true;
143
+ return false;
144
+ }
145
+ if (arg.startsWith("--wit=")) {
146
+ witFiles.push(arg.slice(6));
147
+ return false;
148
+ }
149
+ if (arg.startsWith("--world-name=")) {
150
+ worldName = arg.slice(13);
151
+ return false;
152
+ }
153
+ return true;
154
+ });
155
+ var entryFile = args[2];
156
+ if (!entryFile) {
157
+ console.error(
158
+ "Usage: ./cli <entry-file> [out-folder] [--world-name=<name>] [--wit=<file>] [--raw]"
159
+ );
160
+ process.exit(1);
161
+ }
162
+ var outfolder = args[3] || "./dist";
163
+ console.log(`Building ${entryFile} to ${outfolder}...`);
164
+ if (duBuildRaw) {
165
+ await buildRaw(entryFile, outfolder);
166
+ } else {
167
+ await buildWasm(entryFile, outfolder, {
168
+ worldName,
169
+ files: witFiles
170
+ });
171
+ }
172
+ console.log("Build complete.");
package/dist/index.js ADDED
@@ -0,0 +1,157 @@
1
+ // src/index.ts
2
+ import { componentize } from "@bytecodealliance/componentize-js";
3
+ import { build } from "esbuild";
4
+ import fs3 from "node:fs/promises";
5
+ import { fileURLToPath } from "node:url";
6
+ import { createRequire } from "node:module";
7
+ import { basename as basename2 } from "node:path";
8
+
9
+ // src/utils.ts
10
+ import path from "node:path";
11
+ import fsSync from "node:fs";
12
+ import fs from "node:fs/promises";
13
+ import { resolve, basename } from "node:path";
14
+ function getPackageJsonPath(startDir = import.meta.dirname) {
15
+ let dir = startDir;
16
+ while (dir !== path.parse(dir).root) {
17
+ const pkgJson = path.join(dir, "package.json");
18
+ if (fsSync.existsSync(pkgJson)) return dir;
19
+ dir = path.dirname(dir);
20
+ }
21
+ throw new Error("package.json not found");
22
+ }
23
+ async function getWitFolder(withFiles, witPath = "wit-out") {
24
+ await fs.mkdir(witPath, { recursive: true });
25
+ const baseWitPath = getPackageJsonPath();
26
+ const witFiles = await fs.readdir(resolve(baseWitPath, "wit"));
27
+ for (const file of witFiles) {
28
+ await fs.copyFile(
29
+ resolve(baseWitPath, "wit", file),
30
+ resolve(witPath, file)
31
+ );
32
+ }
33
+ for (const file of withFiles || []) {
34
+ const filename = basename(file);
35
+ await fs.copyFile(file, resolve(witPath, filename));
36
+ }
37
+ }
38
+
39
+ // src/esbuild-plugin.ts
40
+ import fs2 from "fs/promises";
41
+ function druidExtensionPlugin() {
42
+ return {
43
+ name: "druid-extension",
44
+ setup(build2) {
45
+ build2.onLoad({ filter: /\.[jt]sx?$/ }, async (args) => {
46
+ let code = await fs2.readFile(args.path, "utf8");
47
+ if (!code.includes("druid:ui/")) return;
48
+ const transformNamed = (_, vars, name) => {
49
+ const mapped = vars.split(",").map((v) => {
50
+ const [left, right] = v.split(/\s+as\s+/).map((s) => s.trim());
51
+ return right ? `${left}: ${right}` : left;
52
+ }).join(", ");
53
+ return `const { ${mapped} } = window["druid-extension"]["${name}"];`;
54
+ };
55
+ code = code.replace(
56
+ /import\s*{\s*([^}]+)\s*}\s*from\s*["'](druid:ui\/[^"']+)["'];?/g,
57
+ transformNamed
58
+ ).replace(
59
+ /import\s+\*\s+as\s+([\w$]+)\s+from\s*["'](druid:ui\/[^"']+)["'];?/g,
60
+ (_, id, name) => `const ${id} = window["druid-extension"]["${name}"];`
61
+ ).replace(
62
+ /import\s+([\w$]+)\s+from\s*["'](druid:ui\/[^"']+)["'];?/g,
63
+ (_, id, name) => `const ${id} = window["druid-extension"]["${name}"];`
64
+ );
65
+ let loader = "js";
66
+ if (args.path.endsWith(".ts")) loader = "ts";
67
+ else if (args.path.endsWith(".tsx")) loader = "tsx";
68
+ else if (args.path.endsWith(".jsx")) loader = "jsx";
69
+ return { contents: code, loader };
70
+ });
71
+ }
72
+ };
73
+ }
74
+
75
+ // src/gen-types.ts
76
+ import { generateTypes } from "@bytecodealliance/jco/component";
77
+ import { mkdir, rm, writeFile } from "fs/promises";
78
+ import { dirname, resolve as resolve2 } from "path";
79
+ async function genTypes(withFiles, worldName = "druid-ui", typePath = "types", witPath = "wit-out") {
80
+ await getWitFolder(withFiles, witPath);
81
+ const t = await generateTypes(worldName, {
82
+ wit: {
83
+ tag: "path",
84
+ val: resolve2(witPath)
85
+ },
86
+ world: worldName,
87
+ instantiation: {
88
+ tag: "async"
89
+ },
90
+ guest: true
91
+ });
92
+ for (const [filename, contents] of t) {
93
+ console.log("Writing generated type:", typePath + "/" + filename);
94
+ await mkdir(resolve2(typePath, dirname(filename)), { recursive: true });
95
+ await writeFile(resolve2(typePath, filename), contents);
96
+ }
97
+ await rm(witPath, { recursive: true });
98
+ }
99
+
100
+ // src/index.ts
101
+ var require2 = createRequire(import.meta.url);
102
+ var getBundleFileName = (entryFile) => {
103
+ const name = basename2(entryFile, ".tsx");
104
+ const outfilename = name + ".bundled.js";
105
+ return outfilename;
106
+ };
107
+ async function buildWasm(entryFile, outfolder = "./dist", witExtension) {
108
+ await fs3.mkdir(outfolder, { recursive: true });
109
+ const outfilename = getBundleFileName(entryFile);
110
+ const outfile = outfolder + "/" + outfilename;
111
+ console.log("Building", entryFile, "to", outfile);
112
+ await build({
113
+ entryPoints: [entryFile],
114
+ bundle: true,
115
+ write: true,
116
+ format: "esm",
117
+ outfile,
118
+ external: ["druid:ui/*"]
119
+ });
120
+ const __filename = fileURLToPath(import.meta.url);
121
+ let witDist = outfolder + "/wit";
122
+ if (witExtension?.files?.length) {
123
+ await getWitFolder(witExtension.files, witDist);
124
+ } else {
125
+ witDist = getPackageJsonPath() + "/wit";
126
+ }
127
+ console.log("Generating WIT to", witDist);
128
+ const result = await componentize({
129
+ witPath: witDist,
130
+ worldName: witExtension?.worldName || "druid-ui",
131
+ sourcePath: outfile,
132
+ disableFeatures: ["clocks", "http", "random", "stdio", "fetch-event"]
133
+ });
134
+ const name = basename2(entryFile, ".tsx");
135
+ await fs3.writeFile(outfolder + "/" + name + ".wasm", result.component);
136
+ }
137
+ async function buildRaw(entryFile, outfolder = "./dist") {
138
+ const outfilename = getBundleFileName(entryFile);
139
+ const outfile = outfolder + "/" + outfilename;
140
+ const rawPath = require2.resolve("@druid-ui/component/raw");
141
+ await build({
142
+ entryPoints: [entryFile],
143
+ bundle: true,
144
+ write: true,
145
+ format: "esm",
146
+ outfile,
147
+ plugins: [druidExtensionPlugin()],
148
+ alias: {
149
+ "@druid-ui/component": rawPath
150
+ }
151
+ });
152
+ }
153
+ export {
154
+ buildRaw,
155
+ buildWasm,
156
+ genTypes
157
+ };
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=gen-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gen-types.d.ts","sourceRoot":"","sources":["../../../src/cmd/gen-types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cmd/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "esbuild";
2
+ export declare function druidExtensionPlugin(): Plugin;
3
+ //# sourceMappingURL=esbuild-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"esbuild-plugin.d.ts","sourceRoot":"","sources":["../../src/esbuild-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGtC,wBAAgB,oBAAoB,IAAI,MAAM,CAgD7C"}
@@ -0,0 +1,2 @@
1
+ export declare function genTypes(withFiles: string[], worldName?: string, typePath?: string, witPath?: string): Promise<void>;
2
+ //# sourceMappingURL=gen-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gen-types.d.ts","sourceRoot":"","sources":["../../src/gen-types.ts"],"names":[],"mappings":"AAKA,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,MAAM,EAAE,EACnB,SAAS,SAAa,EACtB,QAAQ,SAAU,EAClB,OAAO,SAAY,iBAqBpB"}
@@ -0,0 +1,8 @@
1
+ export interface WitExtension {
2
+ worldName: string;
3
+ files: string[];
4
+ }
5
+ export declare function buildWasm(entryFile: any, outfolder?: string, witExtension?: WitExtension): Promise<void>;
6
+ export declare function buildRaw(entryFile: any, outfolder?: string): Promise<void>;
7
+ export * from "./gen-types";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AASD,wBAAsB,SAAS,CAC7B,SAAS,KAAA,EACT,SAAS,SAAW,EACpB,YAAY,CAAC,EAAE,YAAY,iBAsC5B;AAED,wBAAsB,QAAQ,CAAC,SAAS,KAAA,EAAE,SAAS,SAAW,iBAiB7D;AAED,cAAc,aAAa,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function getPackageJsonPath(startDir?: string): string;
2
+ export declare function getWitFolder(withFiles: string[], witPath?: string): Promise<void>;
3
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAKA,wBAAgB,kBAAkB,CAChC,QAAQ,GAAE,MAA4B,GACrC,MAAM,CAQR;AAED,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,OAAO,SAAY,iBAiB1E"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@druid-ui/build",
3
+ "version": "1.0.0-next.1",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/types/index.d.ts",
7
+ "bin": {
8
+ "druid-ui-build": "./bin/index.js",
9
+ "druid-ui-gen-types": "./bin/gen-types.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/types/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "bin",
20
+ "wit",
21
+ "src"
22
+ ],
23
+ "scripts": {
24
+ "build": "npm run build:lib && npm run build:cli && npm run build:cli-types && npm run build:types && npm rebuild --ignore-scripts @druid-ui/build",
25
+ "build:lib": "esbuild src/index.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/index.js --external:@bytecodealliance/jco --external:esbuild --external:@bytecodealliance/componentize-js --external:@druid-ui/component",
26
+ "build:cli": "esbuild src/cmd/index.ts --bundle --platform=node --format=esm --target=node22 --outfile=bin/index.js --external:@bytecodealliance/jco --external:esbuild --external:@bytecodealliance/componentize-js --external:@druid-ui/component",
27
+ "build:cli-types": "esbuild src/cmd/gen-types.ts --bundle --platform=node --format=esm --target=node22 --outfile=bin/gen-types.js --external:@bytecodealliance/jco --external:esbuild --external:@bytecodealliance/componentize-js",
28
+ "build:types": "tsc --emitDeclarationOnly --outDir dist/types"
29
+ },
30
+ "dependencies": {
31
+ "@bytecodealliance/componentize-js": "^0.19.3",
32
+ "@bytecodealliance/jco": "^1.15.3",
33
+ "@bytecodealliance/preview2-shim": "^0.17.5",
34
+ "esbuild": "^0.25.11"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.0.0",
38
+ "typescript": "~5.8.3"
39
+ }
40
+ }
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ import { genTypes } from "../gen-types";
3
+
4
+ // Expected signature: genTypes(withFiles: string[], worldName: string, typePath: string, witPath: string)
5
+ // CLI usage: gen-types <world-name> [additional-wit-files...] [--wit-path=<path>] [--out-dir=<path>]
6
+ // <world-name>: WIT world name to generate types for
7
+ // [additional-wit-files...]: optional extra WIT files to include alongside built-ins
8
+ // [--wit-path=<path>]: directory to copy built-in WIT definitions into (default: "wit-out")
9
+ // [--out-dir=<path>]: output directory for generated TypeScript types (default: "types")
10
+
11
+ let args = process.argv.slice(2);
12
+ let witPath: string | undefined;
13
+ let outDir: string | undefined;
14
+ const additionalFiles: string[] = [];
15
+
16
+ args = args.filter((arg) => {
17
+ if (arg.startsWith("--temp-wit-path=")) {
18
+ witPath = arg.slice(16);
19
+ return false;
20
+ }
21
+ if (arg.startsWith("--out-dir=")) {
22
+ outDir = arg.slice(10);
23
+ return false;
24
+ }
25
+ return true;
26
+ });
27
+
28
+ if (args.length < 1) {
29
+ console.error(
30
+ "Usage: gen-types <world-name> [additional-wit-files...] [--wit-path=<path>] [--out-dir=<path>]",
31
+ );
32
+ process.exit(1);
33
+ }
34
+
35
+ // Non-null assertion safe due to length check above
36
+ const worldName = args[0]!;
37
+ additionalFiles.push(...args.slice(1));
38
+
39
+ await genTypes(additionalFiles, worldName, outDir, witPath);
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { buildWasm, buildRaw } from "../";
4
+ console.log("Druid UI Build Tool");
5
+ let args = process.argv;
6
+
7
+ let duBuildRaw = false;
8
+ let worldName = "druid-ui";
9
+ const witFiles: string[] = [];
10
+
11
+ args = args.filter((arg) => {
12
+ if (arg === "--raw") {
13
+ duBuildRaw = true;
14
+ return false;
15
+ }
16
+ if (arg.startsWith("--wit=")) {
17
+ witFiles.push(arg.slice(6));
18
+ return false;
19
+ }
20
+ if (arg.startsWith("--world-name=")) {
21
+ worldName = arg.slice(13);
22
+ return false;
23
+ }
24
+ return true;
25
+ });
26
+
27
+ const entryFile = args[2];
28
+
29
+ if (!entryFile) {
30
+ console.error(
31
+ "Usage: ./cli <entry-file> [out-folder] [--world-name=<name>] [--wit=<file>] [--raw]",
32
+ );
33
+ process.exit(1);
34
+ }
35
+
36
+ const outfolder = args[3] || "./dist";
37
+ console.log(`Building ${entryFile} to ${outfolder}...`);
38
+ if (duBuildRaw) {
39
+ await buildRaw(entryFile, outfolder);
40
+ } else {
41
+ await buildWasm(entryFile, outfolder, {
42
+ worldName,
43
+ files: witFiles,
44
+ });
45
+ }
46
+
47
+ console.log("Build complete.");
@@ -0,0 +1,52 @@
1
+ import type { Plugin } from "esbuild";
2
+ import fs from "fs/promises";
3
+
4
+ export function druidExtensionPlugin(): Plugin {
5
+ return {
6
+ name: "druid-extension",
7
+ setup(build) {
8
+ build.onLoad({ filter: /\.[jt]sx?$/ }, async (args) => {
9
+ let code = await fs.readFile(args.path, "utf8");
10
+ if (!code.includes("druid:ui/")) return;
11
+
12
+ const transformNamed = (_: string, vars: string, name: string) => {
13
+ const mapped = vars
14
+ .split(",")
15
+ .map((v) => {
16
+ const [left, right] = v.split(/\s+as\s+/).map((s) => s.trim());
17
+ return right ? `${left}: ${right}` : left;
18
+ })
19
+ .join(", ");
20
+ return `const { ${mapped} } = window["druid-extension"]["${name}"];`;
21
+ };
22
+
23
+ code = code
24
+ // named imports
25
+ .replace(
26
+ /import\s*{\s*([^}]+)\s*}\s*from\s*["'](druid:ui\/[^"']+)["'];?/g,
27
+ transformNamed
28
+ )
29
+ // namespace imports
30
+ .replace(
31
+ /import\s+\*\s+as\s+([\w$]+)\s+from\s*["'](druid:ui\/[^"']+)["'];?/g,
32
+ (_, id: string, name: string) =>
33
+ `const ${id} = window["druid-extension"]["${name}"];`
34
+ )
35
+ // default imports
36
+ .replace(
37
+ /import\s+([\w$]+)\s+from\s*["'](druid:ui\/[^"']+)["'];?/g,
38
+ (_, id: string, name: string) =>
39
+ `const ${id} = window["druid-extension"]["${name}"];`
40
+ );
41
+
42
+ // choose loader based on file extension
43
+ let loader: "ts" | "tsx" | "js" | "jsx" = "js";
44
+ if (args.path.endsWith(".ts")) loader = "ts";
45
+ else if (args.path.endsWith(".tsx")) loader = "tsx";
46
+ else if (args.path.endsWith(".jsx")) loader = "jsx";
47
+
48
+ return { contents: code, loader };
49
+ });
50
+ },
51
+ };
52
+ }
@@ -0,0 +1,31 @@
1
+ import { generateTypes } from "@bytecodealliance/jco/component";
2
+ import { mkdir, rm, writeFile } from "fs/promises";
3
+ import { dirname, resolve } from "path";
4
+ import { getWitFolder } from "./utils";
5
+
6
+ export async function genTypes(
7
+ withFiles: string[],
8
+ worldName = "druid-ui",
9
+ typePath = "types",
10
+ witPath = "wit-out"
11
+ ) {
12
+ await getWitFolder(withFiles, witPath);
13
+ const t = await generateTypes(worldName, {
14
+ wit: {
15
+ tag: "path",
16
+ val: resolve(witPath),
17
+ },
18
+ world: worldName,
19
+ instantiation: {
20
+ tag: "async",
21
+ },
22
+ guest: true,
23
+ });
24
+
25
+ for (const [filename, contents] of t) {
26
+ console.log("Writing generated type:", typePath + "/" + filename);
27
+ await mkdir(resolve(typePath, dirname(filename)), { recursive: true });
28
+ await writeFile(resolve(typePath, filename), contents);
29
+ }
30
+ await rm(witPath, { recursive: true });
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,87 @@
1
+ // @ts-nocheck
2
+ import { componentize } from "@bytecodealliance/componentize-js";
3
+ import { build } from "esbuild";
4
+ import fs from "node:fs/promises";
5
+ import { fileURLToPath } from "node:url";
6
+ import { createRequire } from "node:module";
7
+ import { dirname, basename } from "node:path";
8
+ import { getPackageJsonPath, getWitFolder } from "./utils";
9
+ import { druidExtensionPlugin } from "./esbuild-plugin";
10
+
11
+ const require = createRequire(import.meta.url);
12
+
13
+ export interface WitExtension {
14
+ worldName: string;
15
+ files: string[];
16
+ }
17
+
18
+ const getBundleFileName = (entryFile) => {
19
+ const name = basename(entryFile, ".tsx");
20
+
21
+ const outfilename = name + ".bundled.js";
22
+ return outfilename;
23
+ };
24
+
25
+ export async function buildWasm(
26
+ entryFile,
27
+ outfolder = "./dist",
28
+ witExtension?: WitExtension
29
+ ) {
30
+ await fs.mkdir(outfolder, { recursive: true });
31
+
32
+ const outfilename = getBundleFileName(entryFile);
33
+
34
+ const outfile = outfolder + "/" + outfilename;
35
+ console.log("Building", entryFile, "to", outfile);
36
+ await build({
37
+ entryPoints: [entryFile],
38
+ bundle: true,
39
+ write: true,
40
+ format: "esm",
41
+ outfile: outfile,
42
+ external: ["druid:ui/*"],
43
+ });
44
+
45
+ const __filename = fileURLToPath(import.meta.url);
46
+
47
+ let witDist = outfolder + "/wit";
48
+
49
+ //if we want to extend, we need to copy the wit files into a unified folder
50
+ if (witExtension?.files?.length) {
51
+ await getWitFolder(witExtension.files, witDist);
52
+ } else {
53
+ witDist = getPackageJsonPath() + "/wit";
54
+ }
55
+
56
+ console.log("Generating WIT to", witDist);
57
+ const result = await componentize({
58
+ witPath: witDist,
59
+ worldName: witExtension?.worldName || "druid-ui",
60
+ sourcePath: outfile,
61
+ disableFeatures: ["clocks", "http", "random", "stdio", "fetch-event"],
62
+ });
63
+
64
+ const name = basename(entryFile, ".tsx");
65
+ await fs.writeFile(outfolder + "/" + name + ".wasm", result.component);
66
+ }
67
+
68
+ export async function buildRaw(entryFile, outfolder = "./dist") {
69
+ const outfilename = getBundleFileName(entryFile);
70
+
71
+ const outfile = outfolder + "/" + outfilename;
72
+
73
+ const rawPath = require.resolve("@druid-ui/component/raw");
74
+ await build({
75
+ entryPoints: [entryFile],
76
+ bundle: true,
77
+ write: true,
78
+ format: "esm",
79
+ outfile: outfile,
80
+ plugins: [druidExtensionPlugin()],
81
+ alias: {
82
+ "@druid-ui/component": rawPath,
83
+ },
84
+ });
85
+ }
86
+
87
+ export * from "./gen-types";
package/src/utils.ts ADDED
@@ -0,0 +1,35 @@
1
+ import path from "node:path";
2
+ import fsSync from "node:fs";
3
+ import fs from "node:fs/promises";
4
+ import { resolve, basename } from "node:path";
5
+
6
+ export function getPackageJsonPath(
7
+ startDir: string = import.meta.dirname
8
+ ): string {
9
+ let dir = startDir;
10
+ while (dir !== path.parse(dir).root) {
11
+ const pkgJson = path.join(dir, "package.json");
12
+ if (fsSync.existsSync(pkgJson)) return dir;
13
+ dir = path.dirname(dir);
14
+ }
15
+ throw new Error("package.json not found");
16
+ }
17
+
18
+ export async function getWitFolder(withFiles: string[], witPath = "wit-out") {
19
+ //create outfolder/wit
20
+ await fs.mkdir(witPath, { recursive: true });
21
+
22
+ const baseWitPath = getPackageJsonPath();
23
+
24
+ const witFiles = await fs.readdir(resolve(baseWitPath, "wit"));
25
+ for (const file of witFiles) {
26
+ await fs.copyFile(
27
+ resolve(baseWitPath, "wit", file),
28
+ resolve(witPath, file)
29
+ );
30
+ }
31
+ for (const file of withFiles || []) {
32
+ const filename = basename(file);
33
+ await fs.copyFile(file, resolve(witPath, filename));
34
+ }
35
+ }
@@ -0,0 +1,16 @@
1
+ package druid:ui;
2
+
3
+
4
+ interface component {
5
+ use utils.{event, context};
6
+
7
+ init: func(ctx: context) -> string;
8
+ emit: func(nodeid: string, event: string, e: event);
9
+ async-complete: func(id: string, value: result<string>);
10
+ }
11
+
12
+
13
+ world druid-ui {
14
+ import ui;
15
+ export component;
16
+ }
@@ -0,0 +1,37 @@
1
+ package druid:ui;
2
+
3
+
4
+ interface ui {
5
+
6
+ record prop { key: string, value: string, }
7
+
8
+ record props {
9
+ prop: list<prop>,
10
+ on: list<string>,
11
+ }
12
+
13
+ type async-id = string;
14
+
15
+ type children = option<list<string>>;
16
+
17
+ d: func(element: string, props: props, children: children) -> string;
18
+ log: func(msg: string);
19
+ set-hook: func(id: async-id, hook: string);
20
+ rerender: func();
21
+ }
22
+
23
+ interface utils {
24
+
25
+ resource event {
26
+ constructor(value: string, checked: bool);
27
+ prevent-default: func();
28
+ stop-propagation: func();
29
+ value: func() -> string;
30
+ checked: func() -> bool;
31
+ }
32
+
33
+ record context {
34
+ path: string
35
+ }
36
+
37
+ }