quantum-forge 2.0.2
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/LICENSE-BINARY.md +57 -0
- package/LICENSE.md +21 -0
- package/LICENSING.md +33 -0
- package/dist/lib/logging.d.ts +76 -0
- package/dist/lib/logging.js +198 -0
- package/dist/lib/logging.js.map +1 -0
- package/dist/lib/quantum.d.ts +370 -0
- package/dist/lib/quantum.js +535 -0
- package/dist/lib/quantum.js.map +1 -0
- package/dist/lib/vite-plugin.d.ts +32 -0
- package/dist/lib/vite-plugin.js +93 -0
- package/dist/lib/vite-plugin.js.map +1 -0
- package/dist/quantum-forge-web-0.3.0.tgz +0 -0
- package/dist/quantum-forge-web-api.d.mts +61 -0
- package/dist/quantum-forge-web-api.mjs +202 -0
- package/dist/quantum-forge-web-esm.mjs +14 -0
- package/dist/quantum-forge-web-esm.wasm +0 -0
- package/package.json +70 -0
- package/scripts/build-quantum-forge-variant.mjs +177 -0
- package/scripts/copy-quantum-forge.mjs +61 -0
- package/scripts/prepare.mjs +296 -0
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "quantum-forge",
|
|
3
|
+
"version": "2.0.2",
|
|
4
|
+
"description": "Quantum Forge WASM loader, property manager, and Vite plugin for quantum game development (Qutrit Edition d3n12 + Qubit Edition d2n20). See LICENSE-BINARY.md for binary terms.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./quantum": {
|
|
8
|
+
"types": "./dist/lib/quantum.d.ts",
|
|
9
|
+
"import": "./dist/lib/quantum.js"
|
|
10
|
+
},
|
|
11
|
+
"./logging": {
|
|
12
|
+
"types": "./dist/lib/logging.d.ts",
|
|
13
|
+
"import": "./dist/lib/logging.js"
|
|
14
|
+
},
|
|
15
|
+
"./vite-plugin": {
|
|
16
|
+
"types": "./dist/lib/vite-plugin.d.ts",
|
|
17
|
+
"import": "./dist/lib/vite-plugin.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/",
|
|
22
|
+
"scripts/prepare.mjs",
|
|
23
|
+
"scripts/copy-quantum-forge.mjs",
|
|
24
|
+
"scripts/build-quantum-forge-variant.mjs",
|
|
25
|
+
"LICENSE.md",
|
|
26
|
+
"LICENSE-BINARY.md",
|
|
27
|
+
"LICENSING.md"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"prepare": "node scripts/prepare.mjs",
|
|
31
|
+
"build:lib": "tsup",
|
|
32
|
+
"build:quantum-forge": "cd ../../wrappers/web && npm install && npm run build:medium && cd ../../framework/packages/core && node scripts/copy-quantum-forge.mjs",
|
|
33
|
+
"build:quantum-forge:small": "cd ../../wrappers/web && npm install && npm run build:small && cd ../../framework/packages/core && node scripts/copy-quantum-forge.mjs",
|
|
34
|
+
"build:quantum-forge:custom": "cd ../../wrappers/web && npm install && npm run build:custom -- --max-dimension 3 --max-qudits 32 && npm run build && cd ../../framework/packages/core && node scripts/copy-quantum-forge.mjs",
|
|
35
|
+
"build:quantum-forge:variant": "node scripts/build-quantum-forge-variant.mjs"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
39
|
+
},
|
|
40
|
+
"peerDependenciesMeta": {
|
|
41
|
+
"vite": {
|
|
42
|
+
"optional": true
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"quantum",
|
|
47
|
+
"quantum-forge",
|
|
48
|
+
"quantum-computing",
|
|
49
|
+
"wasm",
|
|
50
|
+
"webassembly",
|
|
51
|
+
"game",
|
|
52
|
+
"vite-plugin",
|
|
53
|
+
"qubit",
|
|
54
|
+
"qutrit",
|
|
55
|
+
"free"
|
|
56
|
+
],
|
|
57
|
+
"repository": {
|
|
58
|
+
"type": "git",
|
|
59
|
+
"url": "https://github.com/quantum-native/quantum-forge.git"
|
|
60
|
+
},
|
|
61
|
+
"author": "Chris Cantwell",
|
|
62
|
+
"license": "MIT",
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"registry": "https://registry.npmjs.org",
|
|
65
|
+
"access": "public"
|
|
66
|
+
},
|
|
67
|
+
"engines": {
|
|
68
|
+
"node": ">=18.0.0"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build a named Quantum Forge WASM variant with custom dimension/qudit limits.
|
|
5
|
+
*
|
|
6
|
+
* Artifacts land in dist/quantum-forge-{name}/ so multiple configurations
|
|
7
|
+
* can coexist. The Vite plugin auto-discovers them.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node scripts/build-quantum-forge-variant.mjs --name d7n10 --max-dimension 7 --max-qudits 10
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from "fs";
|
|
14
|
+
import path from "path";
|
|
15
|
+
import { fileURLToPath } from "url";
|
|
16
|
+
import { execSync } from "child_process";
|
|
17
|
+
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = path.dirname(__filename);
|
|
20
|
+
const ROOT = path.resolve(__dirname, "..");
|
|
21
|
+
|
|
22
|
+
// ---- Argument parsing ----
|
|
23
|
+
|
|
24
|
+
const args = process.argv.slice(2);
|
|
25
|
+
let name = "";
|
|
26
|
+
let maxDimension = "";
|
|
27
|
+
let maxQudits = "";
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < args.length; i++) {
|
|
30
|
+
if (args[i] === "--name" && args[i + 1]) {
|
|
31
|
+
name = args[i + 1];
|
|
32
|
+
i++;
|
|
33
|
+
} else if (args[i] === "--max-dimension" && args[i + 1]) {
|
|
34
|
+
maxDimension = args[i + 1];
|
|
35
|
+
i++;
|
|
36
|
+
} else if (args[i] === "--max-qudits" && args[i + 1]) {
|
|
37
|
+
maxQudits = args[i + 1];
|
|
38
|
+
i++;
|
|
39
|
+
} else if (args[i] === "--help" || args[i] === "-h") {
|
|
40
|
+
console.log(`Usage: node scripts/build-quantum-forge-variant.mjs [options]
|
|
41
|
+
|
|
42
|
+
Options:
|
|
43
|
+
--name <string> Variant name (lowercase alphanumeric + hyphens, e.g. "d7n10")
|
|
44
|
+
--max-dimension <number> Maximum quantum property dimension (>= 2)
|
|
45
|
+
--max-qudits <number> Maximum number of qudits (>= 1)
|
|
46
|
+
--help, -h Show this help message
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
node scripts/build-quantum-forge-variant.mjs --name d7n10 --max-dimension 7 --max-qudits 10
|
|
50
|
+
`);
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---- Validation ----
|
|
56
|
+
|
|
57
|
+
if (!name) {
|
|
58
|
+
console.error("❌ --name is required");
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!maxDimension) {
|
|
63
|
+
console.error("❌ --max-dimension is required");
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!maxQudits) {
|
|
68
|
+
console.error("❌ --max-qudits is required");
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
73
|
+
if (!NAME_PATTERN.test(name)) {
|
|
74
|
+
console.error(`❌ Invalid variant name "${name}". Must match ${NAME_PATTERN} (e.g. "d7n10", "high-dim")`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const RESERVED_NAMES = ["lib", "core", "packages", "systems", "src", "scripts", "esm", "cjs"];
|
|
79
|
+
if (RESERVED_NAMES.includes(name)) {
|
|
80
|
+
console.error(`❌ "${name}" is a reserved name. Choose a different variant name.`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const maxDimensionNum = parseInt(maxDimension, 10);
|
|
85
|
+
const maxQuditsNum = parseInt(maxQudits, 10);
|
|
86
|
+
|
|
87
|
+
if (isNaN(maxDimensionNum) || maxDimensionNum < 2) {
|
|
88
|
+
console.error("❌ --max-dimension must be a number >= 2");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (isNaN(maxQuditsNum) || maxQuditsNum < 1) {
|
|
93
|
+
console.error("❌ --max-qudits must be a number >= 1");
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---- Paths ----
|
|
98
|
+
|
|
99
|
+
const webWrapperDir = path.join(ROOT, "..", "..", "..", "wrappers", "web");
|
|
100
|
+
const cmakeBuildDir = path.join(webWrapperDir, "build");
|
|
101
|
+
const esmOutputDir = path.join(webWrapperDir, "dist", "esm");
|
|
102
|
+
const variantDir = path.join(ROOT, "dist", `quantum-forge-${name}`);
|
|
103
|
+
|
|
104
|
+
// Artifacts to copy from the ESM build
|
|
105
|
+
const ARTIFACTS = [
|
|
106
|
+
"quantum-forge-web-esm.mjs",
|
|
107
|
+
"quantum-forge-web-esm.wasm",
|
|
108
|
+
"quantum-forge-web-api.mjs",
|
|
109
|
+
"quantum-forge-web-api.d.mts",
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
// ---- Build ----
|
|
113
|
+
|
|
114
|
+
function run(cmd, opts = {}) {
|
|
115
|
+
console.log(`> ${cmd}`);
|
|
116
|
+
execSync(cmd, { stdio: "inherit", ...opts });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function rmrf(dir) {
|
|
120
|
+
if (fs.existsSync(dir)) {
|
|
121
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log(`\n🔧 Building Quantum Forge variant "${name}"`);
|
|
126
|
+
console.log(` MAX_DIMENSION: ${maxDimension}`);
|
|
127
|
+
console.log(` MAX_QUDITS: ${maxQudits}`);
|
|
128
|
+
console.log(` Output: dist/quantum-forge-${name}/\n`);
|
|
129
|
+
|
|
130
|
+
// Clean cmake build dir and esm output to force fresh build with new params
|
|
131
|
+
console.log("🧹 Cleaning previous build artifacts...");
|
|
132
|
+
rmrf(cmakeBuildDir);
|
|
133
|
+
rmrf(esmOutputDir);
|
|
134
|
+
|
|
135
|
+
// Run the WASM build via build-dist.js
|
|
136
|
+
console.log("\n📦 Building WASM with custom parameters...");
|
|
137
|
+
run(
|
|
138
|
+
`node scripts/build-dist.js --max-dimension ${maxDimension} --max-qudits ${maxQudits}`,
|
|
139
|
+
{ cwd: webWrapperDir },
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// Run tsup for TypeScript wrapper
|
|
143
|
+
console.log("\n📦 Building TypeScript wrapper...");
|
|
144
|
+
run("npx tsup", { cwd: webWrapperDir });
|
|
145
|
+
|
|
146
|
+
// Copy artifacts to variant directory
|
|
147
|
+
console.log(`\n📁 Copying artifacts to dist/quantum-forge-${name}/...`);
|
|
148
|
+
if (!fs.existsSync(variantDir)) {
|
|
149
|
+
fs.mkdirSync(variantDir, { recursive: true });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let copied = 0;
|
|
153
|
+
for (const file of ARTIFACTS) {
|
|
154
|
+
const src = path.join(esmOutputDir, file);
|
|
155
|
+
const dest = path.join(variantDir, file);
|
|
156
|
+
if (fs.existsSync(src)) {
|
|
157
|
+
fs.copyFileSync(src, dest);
|
|
158
|
+
console.log(` ✅ ${file}`);
|
|
159
|
+
copied++;
|
|
160
|
+
} else {
|
|
161
|
+
console.warn(` ⚠️ Not found: ${file}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (copied === 0) {
|
|
166
|
+
console.error("\n❌ No artifacts were copied. Build may have failed.");
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ---- Done ----
|
|
171
|
+
|
|
172
|
+
console.log(`\n🎉 Variant "${name}" built successfully!`);
|
|
173
|
+
console.log(`\n📖 Usage in your game:\n`);
|
|
174
|
+
console.log(` import { useQuantumForgeBuild, ensureLoaded } from "@quantum-native/quantum-forge/quantum";`);
|
|
175
|
+
console.log(` useQuantumForgeBuild("${name}");`);
|
|
176
|
+
console.log(` await ensureLoaded();\n`);
|
|
177
|
+
console.log(` The Vite plugin will serve it automatically at /quantum-forge-${name}/\n`);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Copy quantum-forge build artifacts to dist directory
|
|
5
|
+
*/
|
|
6
|
+
import { copyFileSync, existsSync, mkdirSync } from "fs";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
|
|
9
|
+
console.log("🔧 Copying Quantum Forge artifacts...");
|
|
10
|
+
|
|
11
|
+
// Define source and destination paths
|
|
12
|
+
const quantumSrcDir = "../../../wrappers/web/dist/esm";
|
|
13
|
+
const distDir = "dist";
|
|
14
|
+
|
|
15
|
+
// Check if quantum forge is built
|
|
16
|
+
if (!existsSync(quantumSrcDir)) {
|
|
17
|
+
console.error(
|
|
18
|
+
"❌ Quantum Forge not built yet. Run 'npm run build:quantum-forge' first.",
|
|
19
|
+
);
|
|
20
|
+
console.error(" Build output not found at:", quantumSrcDir);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Ensure dist directory exists
|
|
25
|
+
if (!existsSync(distDir)) {
|
|
26
|
+
mkdirSync(distDir, { recursive: true });
|
|
27
|
+
console.log(`✅ Created directory: ${distDir}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Files to copy from the ESM build directory
|
|
31
|
+
const quantumFiles = [
|
|
32
|
+
"quantum-forge-web-esm.mjs",
|
|
33
|
+
"quantum-forge-web-esm.wasm",
|
|
34
|
+
"quantum-forge-web-api.mjs",
|
|
35
|
+
"quantum-forge-web-api.d.mts", // TypeScript definitions
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
// Copy to dist
|
|
39
|
+
for (const file of quantumFiles) {
|
|
40
|
+
const srcPath = join(quantumSrcDir, file);
|
|
41
|
+
const destPath = join(distDir, file);
|
|
42
|
+
|
|
43
|
+
if (existsSync(srcPath)) {
|
|
44
|
+
copyFileSync(srcPath, destPath);
|
|
45
|
+
console.log(`✅ Copied ${file} to ${distDir}`);
|
|
46
|
+
} else {
|
|
47
|
+
console.warn(`⚠️ File not found: ${srcPath}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Also copy type declarations to core/quantum/ so they ship with the package
|
|
52
|
+
const typeDeclSrc = join(distDir, "quantum-forge-web-api.d.mts");
|
|
53
|
+
const typeDeclDest = join("src", "quantum", "quantum-forge-api.d.mts");
|
|
54
|
+
if (existsSync(typeDeclSrc)) {
|
|
55
|
+
copyFileSync(typeDeclSrc, typeDeclDest);
|
|
56
|
+
console.log(`✅ Copied type declarations to ${typeDeclDest}`);
|
|
57
|
+
} else {
|
|
58
|
+
console.warn(`⚠️ Type declarations not found at ${typeDeclSrc}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log("🎉 Quantum Forge artifacts copied successfully!");
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Smart prepare script for @quantum-native/quantum-forge
|
|
5
|
+
*
|
|
6
|
+
* When run in the dev repo (monorepo with wrappers present): builds TypeScript library only.
|
|
7
|
+
* When run as a dependency (installed via npm): builds TypeScript library, then
|
|
8
|
+
* downloads pre-built WASM artifacts from the matching GitHub release.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
import { spawnSync } from "child_process";
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
const ROOT = path.resolve(__dirname, "..");
|
|
19
|
+
|
|
20
|
+
const colors = {
|
|
21
|
+
reset: "\x1b[0m",
|
|
22
|
+
bright: "\x1b[1m",
|
|
23
|
+
dim: "\x1b[2m",
|
|
24
|
+
cyan: "\x1b[36m",
|
|
25
|
+
green: "\x1b[32m",
|
|
26
|
+
yellow: "\x1b[33m",
|
|
27
|
+
red: "\x1b[31m",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function log(message, color = "reset") {
|
|
31
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function run(cmd, opts = {}) {
|
|
35
|
+
try {
|
|
36
|
+
const result = spawnSync("sh", ["-c", cmd], {
|
|
37
|
+
cwd: opts.cwd || ROOT,
|
|
38
|
+
env: { ...process.env, ...opts.env },
|
|
39
|
+
stdio: opts.stdio || "pipe",
|
|
40
|
+
encoding: "utf-8",
|
|
41
|
+
timeout: opts.timeout || 120_000,
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
ok: result.status === 0,
|
|
45
|
+
stdout: (result.stdout || "").trim(),
|
|
46
|
+
stderr: (result.stderr || "").trim(),
|
|
47
|
+
};
|
|
48
|
+
} catch {
|
|
49
|
+
return { ok: false, stdout: "", stderr: "command failed" };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ─── TypeScript library build ────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
function buildLib() {
|
|
56
|
+
log(" Building TypeScript library...", "dim");
|
|
57
|
+
const result = run("npm run build:lib", { timeout: 60_000 });
|
|
58
|
+
if (!result.ok) {
|
|
59
|
+
log(" TypeScript build failed.", "red");
|
|
60
|
+
if (result.stderr) log(` ${result.stderr}`, "red");
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
log(" TypeScript library built.", "green");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ─── Dev detection ───────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
function isDevRepo() {
|
|
69
|
+
// When installed as a dependency, INIT_CWD is the consumer's project dir
|
|
70
|
+
const initCwd = process.env.INIT_CWD;
|
|
71
|
+
if (initCwd && path.resolve(initCwd) !== ROOT) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
// ROOT is packages/core/, monorepo root is ../../.. (framework/../)
|
|
75
|
+
return fs.existsSync(
|
|
76
|
+
path.join(ROOT, "..", "..", "..", "wrappers"),
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─── WASM file list ──────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
const WASM_FILES = [
|
|
83
|
+
"quantum-forge-web-esm.mjs",
|
|
84
|
+
"quantum-forge-web-esm.wasm",
|
|
85
|
+
"quantum-forge-web-api.mjs",
|
|
86
|
+
"quantum-forge-web-api.d.mts",
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
function wasmFilesExist() {
|
|
90
|
+
const distDir = path.join(ROOT, "dist");
|
|
91
|
+
return WASM_FILES.every((f) => fs.existsSync(path.join(distDir, f)));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─── Repository info ─────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
function getRepoInfo() {
|
|
97
|
+
try {
|
|
98
|
+
const pkg = JSON.parse(
|
|
99
|
+
fs.readFileSync(path.join(ROOT, "package.json"), "utf-8"),
|
|
100
|
+
);
|
|
101
|
+
const url = pkg.repository?.url || "";
|
|
102
|
+
// Parse owner/repo from git URL formats:
|
|
103
|
+
// git+ssh://git@github.com/owner/repo.git
|
|
104
|
+
// https://github.com/owner/repo.git
|
|
105
|
+
// git@github.com:owner/repo.git
|
|
106
|
+
const match = url.match(/github\.com[/:]([\w.-]+)\/([\w.-]+?)(?:\.git)?$/);
|
|
107
|
+
if (match) {
|
|
108
|
+
return { owner: match[1], repo: match[2], version: pkg.version };
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
// ignore
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ─── Release tag resolution ──────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
function resolveTag(version) {
|
|
119
|
+
// Try exact tag at HEAD
|
|
120
|
+
const git = run("git describe --tags --exact-match HEAD 2>/dev/null");
|
|
121
|
+
if (git.ok && git.stdout.startsWith("v")) {
|
|
122
|
+
return git.stdout;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Fall back to version from package.json
|
|
126
|
+
if (version) {
|
|
127
|
+
return `v${version}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return "latest";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ─── WASM download: gh CLI (primary) ─────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
function hasGhCli() {
|
|
136
|
+
return run("gh --version").ok;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function downloadWithGh(owner, repo, tag, distDir) {
|
|
140
|
+
// Try exact tag first
|
|
141
|
+
let result = run(
|
|
142
|
+
`gh release download "${tag}" --repo "${owner}/${repo}" --pattern "quantum-forge-web-*" --dir "${distDir}"`,
|
|
143
|
+
{ timeout: 60_000 },
|
|
144
|
+
);
|
|
145
|
+
if (result.ok) return true;
|
|
146
|
+
|
|
147
|
+
// If exact tag fails, try latest
|
|
148
|
+
if (tag !== "latest") {
|
|
149
|
+
log(` Release ${tag} not found, trying latest...`, "yellow");
|
|
150
|
+
result = run(
|
|
151
|
+
`gh release download --repo "${owner}/${repo}" --pattern "quantum-forge-web-*" --dir "${distDir}"`,
|
|
152
|
+
{ timeout: 60_000 },
|
|
153
|
+
);
|
|
154
|
+
if (result.ok) return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ─── WASM download: fetch API (fallback) ─────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
function getToken() {
|
|
163
|
+
return process.env.GITHUB_TOKEN || process.env.GH_TOKEN || null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function getAuthHeaders() {
|
|
167
|
+
const token = getToken();
|
|
168
|
+
const headers = {
|
|
169
|
+
Accept: "application/vnd.github.v3+json",
|
|
170
|
+
"User-Agent": "quantum-forge-prepare",
|
|
171
|
+
};
|
|
172
|
+
if (token) {
|
|
173
|
+
headers.Authorization = `token ${token}`;
|
|
174
|
+
}
|
|
175
|
+
return headers;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function downloadWithFetch(owner, repo, tag, distDir) {
|
|
179
|
+
const headers = getAuthHeaders();
|
|
180
|
+
|
|
181
|
+
// Try exact tag first
|
|
182
|
+
const url =
|
|
183
|
+
tag === "latest"
|
|
184
|
+
? `https://api.github.com/repos/${owner}/${repo}/releases/latest`
|
|
185
|
+
: `https://api.github.com/repos/${owner}/${repo}/releases/tags/${tag}`;
|
|
186
|
+
|
|
187
|
+
let response = await fetch(url, { headers });
|
|
188
|
+
|
|
189
|
+
// If exact tag fails, try latest
|
|
190
|
+
if (!response.ok && tag !== "latest") {
|
|
191
|
+
log(` Release ${tag} not found, trying latest...`, "yellow");
|
|
192
|
+
const latestUrl = `https://api.github.com/repos/${owner}/${repo}/releases/latest`;
|
|
193
|
+
response = await fetch(latestUrl, { headers });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!response.ok) return false;
|
|
197
|
+
|
|
198
|
+
const release = await response.json();
|
|
199
|
+
let downloaded = 0;
|
|
200
|
+
|
|
201
|
+
for (const fileName of WASM_FILES) {
|
|
202
|
+
const asset = release.assets?.find((a) => a.name === fileName);
|
|
203
|
+
if (!asset) {
|
|
204
|
+
log(` Asset not found in release: ${fileName}`, "yellow");
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const dlHeaders = { ...getAuthHeaders(), Accept: "application/octet-stream" };
|
|
209
|
+
const dlResponse = await fetch(asset.url, { headers: dlHeaders });
|
|
210
|
+
if (!dlResponse.ok) {
|
|
211
|
+
log(` Failed to download ${fileName}: ${dlResponse.status}`, "yellow");
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const buffer = Buffer.from(await dlResponse.arrayBuffer());
|
|
216
|
+
fs.writeFileSync(path.join(distDir, fileName), buffer);
|
|
217
|
+
downloaded++;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return downloaded === WASM_FILES.length;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ─── WASM download flow ─────────────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
async function downloadWasm() {
|
|
226
|
+
if (wasmFilesExist()) {
|
|
227
|
+
log(" WASM files already present in dist/, skipping download.", "green");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const info = getRepoInfo();
|
|
232
|
+
if (!info) {
|
|
233
|
+
log(" Could not determine repository from package.json.", "yellow");
|
|
234
|
+
log(" Quantum features will not be available.", "yellow");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const tag = resolveTag(info.version);
|
|
239
|
+
const distDir = path.join(ROOT, "dist");
|
|
240
|
+
if (!fs.existsSync(distDir)) {
|
|
241
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
log(` Downloading WASM from ${info.owner}/${info.repo} release ${tag}...`, "cyan");
|
|
245
|
+
|
|
246
|
+
// Try gh CLI first (uses its own auth, works with private repos)
|
|
247
|
+
if (hasGhCli()) {
|
|
248
|
+
log(" Using gh CLI...", "dim");
|
|
249
|
+
if (downloadWithGh(info.owner, info.repo, tag, distDir)) {
|
|
250
|
+
if (wasmFilesExist()) {
|
|
251
|
+
log(" All WASM assets downloaded.", "green");
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
log(" gh CLI download failed, trying fetch API...", "yellow");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Fall back to fetch with GITHUB_TOKEN/GH_TOKEN
|
|
259
|
+
try {
|
|
260
|
+
if (await downloadWithFetch(info.owner, info.repo, tag, distDir)) {
|
|
261
|
+
log(" All WASM assets downloaded.", "green");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
} catch (err) {
|
|
265
|
+
log(` Fetch failed: ${err.message}`, "yellow");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (wasmFilesExist()) {
|
|
269
|
+
log(" WASM assets downloaded (partial methods).", "green");
|
|
270
|
+
} else {
|
|
271
|
+
log(" Could not download WASM assets.", "yellow");
|
|
272
|
+
log(" Quantum features will not be available.", "yellow");
|
|
273
|
+
log(" Ensure 'gh' is authenticated or set GITHUB_TOKEN.", "dim");
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
278
|
+
|
|
279
|
+
async function main() {
|
|
280
|
+
log("@quantum-native/quantum-forge — prepare", "bright");
|
|
281
|
+
|
|
282
|
+
if (isDevRepo()) {
|
|
283
|
+
log(" Dev repository detected (monorepo).", "dim");
|
|
284
|
+
buildLib();
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
log(" Dependency install detected.", "dim");
|
|
289
|
+
// Library is pre-built in the published tarball — only download WASM
|
|
290
|
+
await downloadWasm();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
main().catch((err) => {
|
|
294
|
+
log(` Prepare error: ${err.message}`, "red");
|
|
295
|
+
// Don't exit(1) — non-quantum features should still work
|
|
296
|
+
});
|