bun-assemblyscript 0.1.0
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.
- package/.vscode/as.code-snippets +32 -0
- package/.vscode/extensions.json +5 -0
- package/.vscode/settings.json +5 -0
- package/README.md +34 -0
- package/app.ts +2 -0
- package/assemblyscript.d.ts +113 -0
- package/build-app.ts +10 -0
- package/bun.lock +54 -0
- package/bunfig.toml +9 -0
- package/debug_asc.ts +11 -0
- package/dist/app.js +1 -0
- package/index.ts +19 -0
- package/package.json +34 -0
- package/preload.ts +4 -0
- package/src/bin/watch.ts +14 -0
- package/src/cache/index.ts +135 -0
- package/src/compiler.ts +177 -0
- package/src/install/bunfig.ts +45 -0
- package/src/install/index.ts +142 -0
- package/src/install/tsconfig.ts +61 -0
- package/src/install/vscode.ts +80 -0
- package/src/instantiator.ts +90 -0
- package/src/plugin.ts +189 -0
- package/src/typegen/generator.ts +62 -0
- package/src/typegen/global.d.ts +26 -0
- package/src/typegen/parser.ts +118 -0
- package/src/typegen/typemap.ts +51 -0
- package/src/watcher/index.ts +117 -0
- package/src/watcher/logger.ts +26 -0
- package/test/fixtures/math.as +14 -0
- package/test/fixtures/math.as.d.ts +5 -0
- package/test/math.test.ts +22 -0
- package/tsconfig.json +33 -0
package/src/compiler.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { resolve, basename } from "path";
|
|
2
|
+
import { tmpdir } from "os";
|
|
3
|
+
|
|
4
|
+
export interface CompilerOptions {
|
|
5
|
+
optimizeLevel?: 0 | 1 | 2 | 3;
|
|
6
|
+
shrinkLevel?: 0 | 1 | 2;
|
|
7
|
+
runtime?: "minimal" | "stub" | "full" | "incremental";
|
|
8
|
+
sourceMap?: boolean;
|
|
9
|
+
debug?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CompilerResult {
|
|
13
|
+
success: boolean;
|
|
14
|
+
wasmBytes: Uint8Array | null;
|
|
15
|
+
sourceMapBytes: Uint8Array | null;
|
|
16
|
+
errors: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Résout le chemin du binaire `asc.js` depuis node_modules.
|
|
21
|
+
* Lève une erreur claire si assemblyscript n'est pas installé.
|
|
22
|
+
*/
|
|
23
|
+
function resolveAscJs(cwd: string): string {
|
|
24
|
+
const candidates = [
|
|
25
|
+
resolve(cwd, "node_modules", "assemblyscript", "bin", "asc.js"),
|
|
26
|
+
resolve(cwd, "..", "node_modules", "assemblyscript", "bin", "asc.js"),
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
for (const candidate of candidates) {
|
|
30
|
+
try {
|
|
31
|
+
if (Bun.file(candidate).size > 0) return candidate;
|
|
32
|
+
} catch {}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
throw new Error(
|
|
36
|
+
[
|
|
37
|
+
"AssemblyScript compiler (asc) introuvable.",
|
|
38
|
+
"Installez-le avec :",
|
|
39
|
+
" bun add -d assemblyscript",
|
|
40
|
+
].join("\n")
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Crée un fichier `.ts` temporaire qui est une copie exacte du fichier `.as`.
|
|
46
|
+
*
|
|
47
|
+
* Pont .as → .ts : asc v0.28 n'accepte que les extensions `.ts`.
|
|
48
|
+
* On copie le source dans un fichier temporaire `.ts`, on compile,
|
|
49
|
+
* puis on supprime le temporaire.
|
|
50
|
+
*
|
|
51
|
+
* @returns Chemin absolu vers le fichier temporaire `.ts`.
|
|
52
|
+
*/
|
|
53
|
+
async function createTsBridge(asFilePath: string): Promise<string> {
|
|
54
|
+
const source = await Bun.file(asFilePath).text();
|
|
55
|
+
const baseName = basename(asFilePath, ".as");
|
|
56
|
+
const tmpPath = resolve(tmpdir(), `asc_bridge_${baseName}_${Date.now()}.ts`);
|
|
57
|
+
await Bun.write(tmpPath, source);
|
|
58
|
+
return tmpPath;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Compile un fichier `.as` en bytes WASM.
|
|
63
|
+
*
|
|
64
|
+
* Stratégie :
|
|
65
|
+
* 1. Copier le `.as` en `.ts` temporaire (pont d'extension).
|
|
66
|
+
* 2. Invoquer `node asc.js` via Bun.spawn() — on utilise node car asc v0.28
|
|
67
|
+
* appelle WebAssembly.instantiateStreaming() en interne, non supporté par Bun.
|
|
68
|
+
* 3. Lire le fichier WASM généré.
|
|
69
|
+
* 4. Nettoyer les fichiers temporaires.
|
|
70
|
+
*
|
|
71
|
+
* @param filename Chemin absolu vers le fichier AssemblyScript source.
|
|
72
|
+
* @param options Options de compilation.
|
|
73
|
+
*/
|
|
74
|
+
export async function compile(
|
|
75
|
+
filename: string,
|
|
76
|
+
options: CompilerOptions = {}
|
|
77
|
+
): Promise<CompilerResult> {
|
|
78
|
+
const errors: string[] = [];
|
|
79
|
+
|
|
80
|
+
// 1. Résoudre asc.js
|
|
81
|
+
let ascJs: string;
|
|
82
|
+
try {
|
|
83
|
+
ascJs = resolveAscJs(process.cwd());
|
|
84
|
+
} catch (e) {
|
|
85
|
+
return {
|
|
86
|
+
success: false,
|
|
87
|
+
wasmBytes: null,
|
|
88
|
+
sourceMapBytes: null,
|
|
89
|
+
errors: [e instanceof Error ? e.message : String(e)],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 2. Créer le pont .as → .ts
|
|
94
|
+
let tsBridgePath: string | null = null;
|
|
95
|
+
let outWasmPath: string | null = null;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
tsBridgePath = await createTsBridge(filename);
|
|
99
|
+
outWasmPath = tsBridgePath.replace(/\.ts$/, ".wasm");
|
|
100
|
+
|
|
101
|
+
// 3. Arguments CLI pour asc
|
|
102
|
+
const args = [
|
|
103
|
+
ascJs,
|
|
104
|
+
tsBridgePath,
|
|
105
|
+
"--outFile", outWasmPath,
|
|
106
|
+
"--optimizeLevel", String(options.optimizeLevel ?? 0),
|
|
107
|
+
"--runtime", options.runtime ?? "stub",
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
if (options.shrinkLevel !== undefined) {
|
|
111
|
+
args.push("--shrinkLevel", String(options.shrinkLevel));
|
|
112
|
+
}
|
|
113
|
+
if (options.debug) args.push("--debug");
|
|
114
|
+
if (options.sourceMap) args.push("--sourceMap");
|
|
115
|
+
|
|
116
|
+
// 4. Lancer `node asc.js ...` — Node.js gère correctement WebAssembly.instantiateStreaming
|
|
117
|
+
const proc = Bun.spawn(["node", ...args], {
|
|
118
|
+
stdout: "pipe",
|
|
119
|
+
stderr: "pipe",
|
|
120
|
+
cwd: process.cwd(),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const [stderrText] = await Promise.all([
|
|
124
|
+
new Response(proc.stderr).text(),
|
|
125
|
+
proc.exited,
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
const exitCode = proc.exitCode;
|
|
129
|
+
|
|
130
|
+
// Collecter les erreurs stderr
|
|
131
|
+
for (const line of stderrText.split("\n")) {
|
|
132
|
+
const trimmed = line.trim();
|
|
133
|
+
if (trimmed.length > 0) errors.push(trimmed);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (exitCode !== 0) {
|
|
137
|
+
return { success: false, wasmBytes: null, sourceMapBytes: null, errors };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 5. Lire le WASM généré
|
|
141
|
+
const wasmFile = Bun.file(outWasmPath);
|
|
142
|
+
const wasmBytes = new Uint8Array(await wasmFile.arrayBuffer());
|
|
143
|
+
|
|
144
|
+
let sourceMapBytes: Uint8Array | null = null;
|
|
145
|
+
if (options.sourceMap) {
|
|
146
|
+
const mapPath = outWasmPath + ".map";
|
|
147
|
+
if (await Bun.file(mapPath).exists()) {
|
|
148
|
+
sourceMapBytes = new Uint8Array(await Bun.file(mapPath).arrayBuffer());
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (wasmBytes.byteLength === 0) {
|
|
153
|
+
return {
|
|
154
|
+
success: false,
|
|
155
|
+
wasmBytes: null,
|
|
156
|
+
sourceMapBytes: null,
|
|
157
|
+
errors: [...errors, "Aucun output WASM produit par asc."],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return { success: true, wasmBytes, sourceMapBytes, errors };
|
|
162
|
+
|
|
163
|
+
} catch (err) {
|
|
164
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
165
|
+
errors.push(message);
|
|
166
|
+
return { success: false, wasmBytes: null, sourceMapBytes: null, errors };
|
|
167
|
+
|
|
168
|
+
} finally {
|
|
169
|
+
// 6. Nettoyer les fichiers temporaires dans tous les cas
|
|
170
|
+
const fs = await import("fs/promises");
|
|
171
|
+
for (const tmpFile of [tsBridgePath, outWasmPath, outWasmPath ? outWasmPath + ".map" : null]) {
|
|
172
|
+
if (tmpFile) {
|
|
173
|
+
try { await fs.unlink(tmpFile); } catch {}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lit le bunfig.toml du projet hôte et injecte "bun-plugin-assemblyscript"
|
|
6
|
+
* dans le tableau preload s'il n'est pas déjà présent.
|
|
7
|
+
*
|
|
8
|
+
* @param projectRoot Chemin racine du projet hôte
|
|
9
|
+
* @returns boolean indiquant si le fichier a été modifié
|
|
10
|
+
*/
|
|
11
|
+
export async function patchBunfig(projectRoot: string): Promise<boolean> {
|
|
12
|
+
const bunfigPath = join(projectRoot, "bunfig.toml");
|
|
13
|
+
let content = "";
|
|
14
|
+
let changed = false;
|
|
15
|
+
|
|
16
|
+
if (existsSync(bunfigPath)) {
|
|
17
|
+
content = await Bun.file(bunfigPath).text();
|
|
18
|
+
|
|
19
|
+
// Si déjà présent, on ne fait rien pour ne pas dupliquer
|
|
20
|
+
if (!content.includes("bun-plugin-assemblyscript")) {
|
|
21
|
+
const preloadRegex = /preload\s*=\s*\[(.*?)\]/s;
|
|
22
|
+
const match = content.match(preloadRegex);
|
|
23
|
+
|
|
24
|
+
if (match) {
|
|
25
|
+
const inner = match[1].trim();
|
|
26
|
+
const addition = inner.length > 0 ? `, "bun-plugin-assemblyscript"` : `"bun-plugin-assemblyscript"`;
|
|
27
|
+
content = content.replace(preloadRegex, `preload = [${inner}${addition}]`);
|
|
28
|
+
changed = true;
|
|
29
|
+
} else {
|
|
30
|
+
content += `\npreload = ["bun-plugin-assemblyscript"]\n`;
|
|
31
|
+
changed = true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
// Fichier absent : on créé une version vierge minimale
|
|
36
|
+
content = `preload = ["bun-plugin-assemblyscript"]\n`;
|
|
37
|
+
changed = true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (changed) {
|
|
41
|
+
await Bun.write(bunfigPath, content);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return changed;
|
|
45
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { existsSync, mkdirSync } from "fs";
|
|
3
|
+
import { patchTsConfig } from "./tsconfig";
|
|
4
|
+
import { patchBunfig } from "./bunfig";
|
|
5
|
+
import { patchVsCode } from "./vscode";
|
|
6
|
+
import { Glob } from "bun";
|
|
7
|
+
|
|
8
|
+
async function run() {
|
|
9
|
+
// En mode postinstall NPM/Bun, INIT_CWD pointe vers le dossier du projet final installant le package
|
|
10
|
+
const hostRoot = process.env.INIT_CWD || process.cwd();
|
|
11
|
+
|
|
12
|
+
// Anti-boucle : si on est dans le dépôt originel de dev, on limite certains comportements
|
|
13
|
+
const isSelf = hostRoot === process.cwd() && existsSync(join(hostRoot, "src", "compiler.ts"));
|
|
14
|
+
|
|
15
|
+
console.log("");
|
|
16
|
+
console.log("\x1b[36m┌──────────────────────────────────────────────┐\x1b[0m");
|
|
17
|
+
console.log("\x1b[36m│ │\x1b[0m");
|
|
18
|
+
console.log("\x1b[36m│ \x1b[1m🚀 Installation bun-assemblyscript\x1b[0;36m │\x1b[0m");
|
|
19
|
+
console.log("\x1b[36m│ │\x1b[0m");
|
|
20
|
+
|
|
21
|
+
const results = {
|
|
22
|
+
ascParams: false,
|
|
23
|
+
bunfig: false,
|
|
24
|
+
globalDts: false,
|
|
25
|
+
tsconfig: false,
|
|
26
|
+
vscode: false,
|
|
27
|
+
example: false,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// 1. Détecter assemblyscript
|
|
32
|
+
const pkgPath = join(hostRoot, "package.json");
|
|
33
|
+
if (existsSync(pkgPath)) {
|
|
34
|
+
const pkg = await Bun.file(pkgPath).json();
|
|
35
|
+
const hasAsc =
|
|
36
|
+
(pkg.dependencies && pkg.dependencies.assemblyscript) ||
|
|
37
|
+
(pkg.devDependencies && pkg.devDependencies.assemblyscript);
|
|
38
|
+
if (hasAsc) {
|
|
39
|
+
results.ascParams = true;
|
|
40
|
+
} else {
|
|
41
|
+
console.log(
|
|
42
|
+
"\x1b[33m[!] Warning: 'assemblyscript' n'est pas installé dans votre projet.\x1b[0m"
|
|
43
|
+
);
|
|
44
|
+
console.log(
|
|
45
|
+
"\x1b[33m => Lancez : bun add -d assemblyscript\x1b[0m\n"
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 2. bunfig.toml
|
|
51
|
+
await patchBunfig(hostRoot);
|
|
52
|
+
results.bunfig = true;
|
|
53
|
+
|
|
54
|
+
// 3. assemblyscript.d.ts (global)
|
|
55
|
+
const globalDest = join(hostRoot, "assemblyscript.d.ts");
|
|
56
|
+
if (!existsSync(globalDest) && !isSelf) {
|
|
57
|
+
// Résout depuis __dirname vers src/typegen/global.d.ts
|
|
58
|
+
const sourceGlobal = join(__dirname, "..", "typegen", "global.d.ts");
|
|
59
|
+
try {
|
|
60
|
+
const content = await Bun.file(sourceGlobal).text();
|
|
61
|
+
await Bun.write(globalDest, content);
|
|
62
|
+
} catch (e) {}
|
|
63
|
+
}
|
|
64
|
+
results.globalDts = true;
|
|
65
|
+
|
|
66
|
+
// 4. tsconfig.json
|
|
67
|
+
try {
|
|
68
|
+
await patchTsConfig(hostRoot);
|
|
69
|
+
results.tsconfig = true;
|
|
70
|
+
} catch (e) {}
|
|
71
|
+
|
|
72
|
+
// 5. VSCode Integration
|
|
73
|
+
try {
|
|
74
|
+
await patchVsCode(hostRoot, isSelf);
|
|
75
|
+
results.vscode = true;
|
|
76
|
+
} catch (e) {}
|
|
77
|
+
|
|
78
|
+
// 6. Créer example.as s'il n'y a pas de fichier .as dans le projet
|
|
79
|
+
let hasAsFile = false;
|
|
80
|
+
const asGlob = new Glob("**/*.as");
|
|
81
|
+
for (const file of asGlob.scanSync({ cwd: hostRoot })) {
|
|
82
|
+
if (!file.includes("node_modules") && !file.includes(".cache")) {
|
|
83
|
+
hasAsFile = true;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let createdExample = false;
|
|
89
|
+
if (!hasAsFile && !isSelf) {
|
|
90
|
+
const wasmDir = join(hostRoot, "src", "wasm");
|
|
91
|
+
if (!existsSync(wasmDir)) {
|
|
92
|
+
mkdirSync(wasmDir, { recursive: true });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const asFile = join(wasmDir, "example.as");
|
|
96
|
+
const asCode = `// Exemple de fonction AssemblyScript
|
|
97
|
+
export function add(a: i32, b: i32): i32 {
|
|
98
|
+
return a + b;
|
|
99
|
+
}
|
|
100
|
+
`;
|
|
101
|
+
await Bun.write(asFile, asCode);
|
|
102
|
+
|
|
103
|
+
const tsFile = join(wasmDir, "example.ts");
|
|
104
|
+
const tsCode = `import { add } from "./example.as";
|
|
105
|
+
|
|
106
|
+
console.log("AssemblyScript add(10, 20) =", add(10, 20));
|
|
107
|
+
`;
|
|
108
|
+
await Bun.write(tsFile, tsCode);
|
|
109
|
+
|
|
110
|
+
createdExample = true;
|
|
111
|
+
}
|
|
112
|
+
results.example = true;
|
|
113
|
+
|
|
114
|
+
// Formatter de résultat [✓] et [✗]
|
|
115
|
+
const check = (ok: boolean) => (ok ? "\x1b[32m✓\x1b[36m" : "\x1b[31m✗\x1b[36m");
|
|
116
|
+
|
|
117
|
+
console.log("\x1b[36m│ " + check(results.ascParams) + " Vérification dépendance AssemblyScript │\x1b[0m");
|
|
118
|
+
console.log("\x1b[36m│ " + check(results.bunfig) + " Configuration automatique bunfig.toml │\x1b[0m");
|
|
119
|
+
console.log("\x1b[36m│ " + check(results.globalDts) + " Copie de assemblyscript.d.ts global │\x1b[0m");
|
|
120
|
+
console.log("\x1b[36m│ " + check(results.tsconfig) + " Configuration auto tsconfig.json │\x1b[0m");
|
|
121
|
+
console.log("\x1b[36m│ " + check(results.vscode) + " Intégration VSCode (.vscode) │\x1b[0m");
|
|
122
|
+
console.log("\x1b[36m│ " + check(results.example) + (createdExample ? " Fichier d'exemple généré " : " Projet contenant déjà des fichiers .as ") + "│\x1b[0m");
|
|
123
|
+
console.log("\x1b[36m│ │\x1b[0m");
|
|
124
|
+
console.log("\x1b[36m└──────────────────────────────────────────────┘\x1b[0m\n");
|
|
125
|
+
|
|
126
|
+
if (createdExample) {
|
|
127
|
+
console.log(
|
|
128
|
+
"\x1b[1m\x1b[32mInstallation réussie !\x1b[0m Testez l'exemple avec la commande ci-dessous :\n"
|
|
129
|
+
);
|
|
130
|
+
console.log(" \x1b[36mbun run src/wasm/example.ts\x1b[0m\n");
|
|
131
|
+
} else {
|
|
132
|
+
console.log(
|
|
133
|
+
"\x1b[1m\x1b[32mInstallation réussie !\x1b[0m Votre projet est prêt.\n"
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error("\x1b[31m[bun-as] Erreur pendant l'installation:\x1b[0m", err);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
run().catch(() => process.exit(0));
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Nettoie une chaîne JSON de ses commentaires (// et \/\* \*\/)
|
|
5
|
+
* afin de la rendre compatible avec JSON.parse().
|
|
6
|
+
*/
|
|
7
|
+
function stripJsonComments(jsonStr: string): string {
|
|
8
|
+
return jsonStr.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Lit, nettoie et parse le \`tsconfig.json\` du projet hôte.
|
|
13
|
+
* Fusionne les options pour inclure les dépendances gérées par le plugin.
|
|
14
|
+
* Ne supprime pas les configurations existantes de l'utilisateur.
|
|
15
|
+
*
|
|
16
|
+
* @param projectRoot Chemin vers la racine du projet hôte. (Par défaut \`process.cwd()\`)
|
|
17
|
+
*/
|
|
18
|
+
export async function patchTsConfig(projectRoot: string = process.cwd()): Promise<void> {
|
|
19
|
+
const tsconfigPath = join(projectRoot, "tsconfig.json");
|
|
20
|
+
let config: any = {};
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const rawContent = await Bun.file(tsconfigPath).text();
|
|
24
|
+
config = JSON.parse(stripJsonComments(rawContent));
|
|
25
|
+
} catch (err) {
|
|
26
|
+
// Si absent ou invalide, on génère une configuration par défaut minimale
|
|
27
|
+
config = {
|
|
28
|
+
compilerOptions: {
|
|
29
|
+
target: "ESNext",
|
|
30
|
+
moduleResolution: "bundler",
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// S'assurer que les clés minimales existent
|
|
36
|
+
if (!config.compilerOptions) {
|
|
37
|
+
config.compilerOptions = {};
|
|
38
|
+
}
|
|
39
|
+
// S'assurer que le tableau include existe à la racine
|
|
40
|
+
if (!Array.isArray(config.include)) {
|
|
41
|
+
if (config.include) config.include = [config.include];
|
|
42
|
+
else config.include = [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const includes: string[] = config.include;
|
|
46
|
+
let changed = false;
|
|
47
|
+
|
|
48
|
+
const requiredIncludes = ["assemblyscript.d.ts", "**/*.as.d.ts"];
|
|
49
|
+
|
|
50
|
+
for (const req of requiredIncludes) {
|
|
51
|
+
if (!includes.includes(req)) {
|
|
52
|
+
includes.push(req);
|
|
53
|
+
changed = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (changed) {
|
|
58
|
+
// Écriture du fichier modifié
|
|
59
|
+
await Bun.write(tsconfigPath, JSON.stringify(config, null, 2) + "\n");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { existsSync, mkdirSync } from "fs";
|
|
3
|
+
|
|
4
|
+
export async function patchVsCode(hostRoot: string, isSelf: boolean) {
|
|
5
|
+
// Optionnel: on ne le fait pas si on est dans notre propre repo de dev, sauf si demandé.
|
|
6
|
+
// Mais c'est pratique de l'avoir même en dev. On va l'exécuter dans tous les cas.
|
|
7
|
+
|
|
8
|
+
const vscodeDir = join(hostRoot, ".vscode");
|
|
9
|
+
if (!existsSync(vscodeDir)) {
|
|
10
|
+
mkdirSync(vscodeDir, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// 6.1 settings.json
|
|
14
|
+
const settingsPath = join(vscodeDir, "settings.json");
|
|
15
|
+
let settings: any = {};
|
|
16
|
+
if (existsSync(settingsPath)) {
|
|
17
|
+
try {
|
|
18
|
+
settings = await Bun.file(settingsPath).json();
|
|
19
|
+
} catch (e) {
|
|
20
|
+
// Ignorer si le JSON est invalide
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (!settings["files.associations"]) {
|
|
24
|
+
settings["files.associations"] = {};
|
|
25
|
+
}
|
|
26
|
+
settings["files.associations"]["*.as"] = "typescript";
|
|
27
|
+
await Bun.write(settingsPath, JSON.stringify(settings, null, 2));
|
|
28
|
+
|
|
29
|
+
// 6.2 as.code-snippets
|
|
30
|
+
const snippetsPath = join(vscodeDir, "as.code-snippets");
|
|
31
|
+
const snippets = {
|
|
32
|
+
"AssemblyScript Export": {
|
|
33
|
+
"prefix": "asexport",
|
|
34
|
+
"scope": "typescript",
|
|
35
|
+
"body": [
|
|
36
|
+
"export function ${1:name}(${2:a}: ${3:i32}): ${4:i32} {",
|
|
37
|
+
" $0",
|
|
38
|
+
"}"
|
|
39
|
+
],
|
|
40
|
+
"description": "Exported function for AssemblyScript"
|
|
41
|
+
},
|
|
42
|
+
"AssemblyScript Class": {
|
|
43
|
+
"prefix": "asclass",
|
|
44
|
+
"scope": "typescript",
|
|
45
|
+
"body": [
|
|
46
|
+
"export class ${1:Name} {",
|
|
47
|
+
" constructor() {",
|
|
48
|
+
" $0",
|
|
49
|
+
" }",
|
|
50
|
+
"}"
|
|
51
|
+
],
|
|
52
|
+
"description": "Exported class with constructor for AssemblyScript"
|
|
53
|
+
},
|
|
54
|
+
"AssemblyScript Import": {
|
|
55
|
+
"prefix": "asimport",
|
|
56
|
+
"scope": "typescript",
|
|
57
|
+
"body": [
|
|
58
|
+
"import { ${2:func} } from \"${1:./module.as}\";$0"
|
|
59
|
+
],
|
|
60
|
+
"description": "Import pattern from an .as file"
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
await Bun.write(snippetsPath, JSON.stringify(snippets, null, 2));
|
|
64
|
+
|
|
65
|
+
// 6.3 extensions.json
|
|
66
|
+
const extensionsPath = join(vscodeDir, "extensions.json");
|
|
67
|
+
let extensions: any = {};
|
|
68
|
+
if (existsSync(extensionsPath)) {
|
|
69
|
+
try {
|
|
70
|
+
extensions = await Bun.file(extensionsPath).json();
|
|
71
|
+
} catch (e) {}
|
|
72
|
+
}
|
|
73
|
+
if (!extensions.recommendations) {
|
|
74
|
+
extensions.recommendations = [];
|
|
75
|
+
}
|
|
76
|
+
if (!extensions.recommendations.includes("saulecabrera.vscode-assemblyscript")) {
|
|
77
|
+
extensions.recommendations.push("saulecabrera.vscode-assemblyscript");
|
|
78
|
+
}
|
|
79
|
+
await Bun.write(extensionsPath, JSON.stringify(extensions, null, 2));
|
|
80
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export interface ASExports {
|
|
2
|
+
[key: string]: (...args: number[]) => number | void;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export class AbortError extends Error {
|
|
6
|
+
constructor(
|
|
7
|
+
message: string,
|
|
8
|
+
public readonly file: string,
|
|
9
|
+
public readonly line: number,
|
|
10
|
+
public readonly column: number
|
|
11
|
+
) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "AbortError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Lit une String AssemblyScript depuis la mémoire linéaire.
|
|
19
|
+
*
|
|
20
|
+
* Format mémoire AS (runtime stub / minimal / full) :
|
|
21
|
+
* ptr - 16 : ClassID (4 octets)
|
|
22
|
+
* ptr - 12 : reserved (4 octets)
|
|
23
|
+
* ptr - 8 : rtSize (4 octets) ← taille du buffer en octets
|
|
24
|
+
* ptr - 4 : length (4 octets) ← nombre de code units UTF-16
|
|
25
|
+
* ptr : données UTF-16 ← longueur = length * 2 octets
|
|
26
|
+
*
|
|
27
|
+
* Si ptr est 0 ou invalide, retourne la chaîne vide.
|
|
28
|
+
*/
|
|
29
|
+
function readASString(memory: WebAssembly.Memory, ptr: number): string {
|
|
30
|
+
if (ptr === 0) return "";
|
|
31
|
+
|
|
32
|
+
const buf = memory.buffer;
|
|
33
|
+
const dataView = new DataView(buf);
|
|
34
|
+
|
|
35
|
+
// Lire la longueur (code units UTF-16) stockée à ptr - 4
|
|
36
|
+
const byteLength = dataView.getInt32(ptr - 4, true); // en octets
|
|
37
|
+
const length = byteLength >>> 1; // en code-units
|
|
38
|
+
|
|
39
|
+
if (length <= 0 || ptr + byteLength > buf.byteLength) return "";
|
|
40
|
+
|
|
41
|
+
const u16 = new Uint16Array(buf, ptr, length);
|
|
42
|
+
return String.fromCharCode(...u16);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Instancie un module WASM AssemblyScript.
|
|
47
|
+
*
|
|
48
|
+
* Fournit les imports obligatoires :
|
|
49
|
+
* - `env.memory` : WebAssembly.Memory (initial 1 page = 64 Ko)
|
|
50
|
+
* - `env.abort` : handler qui lève une AbortError lisible
|
|
51
|
+
*
|
|
52
|
+
* @returns Un objet plat ne contenant que les exports de type `function`.
|
|
53
|
+
*/
|
|
54
|
+
export async function instantiate(wasmBytes: Uint8Array): Promise<ASExports> {
|
|
55
|
+
const memory = new WebAssembly.Memory({ initial: 1 });
|
|
56
|
+
|
|
57
|
+
const imports = {
|
|
58
|
+
env: {
|
|
59
|
+
memory,
|
|
60
|
+
abort(
|
|
61
|
+
msgPtr: number,
|
|
62
|
+
filePtr: number,
|
|
63
|
+
line: number,
|
|
64
|
+
col: number
|
|
65
|
+
): void {
|
|
66
|
+
const message = readASString(memory, msgPtr);
|
|
67
|
+
const file = readASString(memory, filePtr);
|
|
68
|
+
throw new AbortError(
|
|
69
|
+
`AbortError: ${message || "(no message)"} — ${file || "(unknown file)"}:${line}:${col}`,
|
|
70
|
+
file,
|
|
71
|
+
line,
|
|
72
|
+
col
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const { instance } = await WebAssembly.instantiate(wasmBytes, imports);
|
|
79
|
+
const rawExports = instance.exports as Record<string, unknown>;
|
|
80
|
+
|
|
81
|
+
// Ré-exporter nommément chaque fonction (objet plat, pas instance.exports)
|
|
82
|
+
const flatExports: ASExports = {};
|
|
83
|
+
for (const [key, value] of Object.entries(rawExports)) {
|
|
84
|
+
if (typeof value === "function") {
|
|
85
|
+
flatExports[key] = value as (...args: number[]) => number | void;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return flatExports;
|
|
90
|
+
}
|