hyperframes 0.5.0-alpha.3 → 0.5.0-alpha.5
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/dist/cli.js +490 -183
- package/dist/hyperframe-runtime.js +172 -7
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +172 -7
- package/dist/skills/hyperframes/scripts/animation-map.mjs +7 -2
- package/dist/skills/hyperframes/scripts/contrast-report.mjs +7 -4
- package/dist/skills/hyperframes/scripts/package-loader.mjs +269 -0
- package/dist/skills/hyperframes-cli/SKILL.md +14 -0
- package/dist/studio/assets/{index-Z9LjUi1W.js → index-Ba6SZOXW.js} +17 -17
- package/dist/studio/index.html +1 -1
- package/package.json +1 -1
|
@@ -11,14 +11,19 @@
|
|
|
11
11
|
|
|
12
12
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
13
13
|
import { resolve, join } from "node:path";
|
|
14
|
+
import { hyperframesPackageSpec, importPackagesOrBootstrap } from "./package-loader.mjs";
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
const {
|
|
16
17
|
createFileServer,
|
|
17
18
|
createCaptureSession,
|
|
18
19
|
initializeSession,
|
|
19
20
|
closeCaptureSession,
|
|
20
21
|
getCompositionDuration,
|
|
21
|
-
}
|
|
22
|
+
} = (
|
|
23
|
+
await importPackagesOrBootstrap(["@hyperframes/producer"], {
|
|
24
|
+
npmPackages: [hyperframesPackageSpec("@hyperframes/producer")],
|
|
25
|
+
})
|
|
26
|
+
)["@hyperframes/producer"];
|
|
22
27
|
|
|
23
28
|
// ─── CLI ─────────────────────────────────────────────────────────────────────
|
|
24
29
|
|
|
@@ -18,19 +18,22 @@
|
|
|
18
18
|
|
|
19
19
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
20
20
|
import { resolve } from "node:path";
|
|
21
|
-
|
|
22
|
-
import sharp from "sharp";
|
|
21
|
+
import { hyperframesPackageSpec, importPackagesOrBootstrap } from "./package-loader.mjs";
|
|
23
22
|
|
|
24
23
|
// Use the producer's file server — it auto-injects the HyperFrames runtime
|
|
25
24
|
// and render-seek bridge, so raw authoring HTML works without a build step.
|
|
26
|
-
|
|
25
|
+
const packages = await importPackagesOrBootstrap(["@hyperframes/producer", "sharp"], {
|
|
26
|
+
npmPackages: [hyperframesPackageSpec("@hyperframes/producer"), "sharp@0.34.5"],
|
|
27
|
+
});
|
|
28
|
+
const sharp = packages.sharp.default;
|
|
29
|
+
const {
|
|
27
30
|
createFileServer,
|
|
28
31
|
createCaptureSession,
|
|
29
32
|
initializeSession,
|
|
30
33
|
closeCaptureSession,
|
|
31
34
|
captureFrameToBuffer,
|
|
32
35
|
getCompositionDuration,
|
|
33
|
-
}
|
|
36
|
+
} = packages["@hyperframes/producer"];
|
|
34
37
|
|
|
35
38
|
// ─── CLI ─────────────────────────────────────────────────────────────────────
|
|
36
39
|
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { basename, delimiter, dirname, join, parse, resolve } from "node:path";
|
|
6
|
+
import { createInterface } from "node:readline/promises";
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
+
|
|
9
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const BOOTSTRAP_ENV = "HYPERFRAMES_SKILL_DEPS_BOOTSTRAPPED";
|
|
11
|
+
const BOOTSTRAP_CONFIRM_ENV = "HYPERFRAMES_SKILL_BOOTSTRAP_DEPS";
|
|
12
|
+
const NODE_MODULES_ENV = "HYPERFRAMES_SKILL_NODE_MODULES";
|
|
13
|
+
|
|
14
|
+
export async function importPackagesOrBootstrap(packageNames, options = {}) {
|
|
15
|
+
const entries = new Map();
|
|
16
|
+
const missing = [];
|
|
17
|
+
|
|
18
|
+
for (const packageName of packageNames) {
|
|
19
|
+
const entry = resolvePackageEntry(packageName);
|
|
20
|
+
if (entry) entries.set(packageName, entry);
|
|
21
|
+
else missing.push(packageName);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (missing.length > 0 && !process.env[BOOTSTRAP_ENV]) {
|
|
25
|
+
const npmPackages = options.npmPackages ?? missing;
|
|
26
|
+
assertPinnedPackageSpecs(npmPackages);
|
|
27
|
+
await confirmBootstrap(npmPackages);
|
|
28
|
+
bootstrapWithNpmInstall(npmPackages);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (missing.length > 0) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
[
|
|
34
|
+
`Could not resolve required package(s): ${missing.join(", ")}`,
|
|
35
|
+
"Install them in this project, for example:",
|
|
36
|
+
` npm install --save-dev ${packageNames.map(shellQuote).join(" ")}`,
|
|
37
|
+
].join("\n"),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const modules = {};
|
|
42
|
+
for (const [packageName, entry] of entries) {
|
|
43
|
+
modules[packageName] = await import(pathToFileURL(entry).href);
|
|
44
|
+
}
|
|
45
|
+
return modules;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function hyperframesPackageSpec(packageName) {
|
|
49
|
+
const version = readBundledHyperframesVersion();
|
|
50
|
+
if (!version) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
[
|
|
53
|
+
`Could not determine the bundled HyperFrames version for ${packageName}.`,
|
|
54
|
+
"Install the package yourself or pass a pinned options.npmPackages entry.",
|
|
55
|
+
].join("\n"),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return `${packageName}@${version}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolvePackageEntry(packageName) {
|
|
62
|
+
const bases = [process.cwd(), HERE, ...envNodeModulesDirs(), ...nodeModulesDirsFromPath()];
|
|
63
|
+
|
|
64
|
+
const seen = new Set();
|
|
65
|
+
for (const base of bases) {
|
|
66
|
+
const normalized = resolve(base);
|
|
67
|
+
if (seen.has(normalized)) continue;
|
|
68
|
+
seen.add(normalized);
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
return createRequire(join(normalized, "__hyperframes_skill_loader__.cjs")).resolve(
|
|
72
|
+
packageName,
|
|
73
|
+
);
|
|
74
|
+
} catch {
|
|
75
|
+
const packageDir = findPackageDir(normalized, packageName);
|
|
76
|
+
const packageEntry = packageDir ? readPackageEntry(packageDir) : null;
|
|
77
|
+
if (packageEntry) return packageEntry;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function readBundledHyperframesVersion() {
|
|
85
|
+
for (const ancestor of ancestors(HERE)) {
|
|
86
|
+
const directVersion = readPackageVersion(join(ancestor, "package.json"));
|
|
87
|
+
if (directVersion) return directVersion;
|
|
88
|
+
|
|
89
|
+
const monorepoCliVersion = readPackageVersion(
|
|
90
|
+
join(ancestor, "packages", "cli", "package.json"),
|
|
91
|
+
);
|
|
92
|
+
if (monorepoCliVersion) return monorepoCliVersion;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function readPackageVersion(packageJsonPath) {
|
|
98
|
+
try {
|
|
99
|
+
const manifest = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
100
|
+
if (manifest.name === "hyperframes" || manifest.name === "@hyperframes/cli") {
|
|
101
|
+
return typeof manifest.version === "string" ? manifest.version : null;
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// Keep searching ancestor package manifests.
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function envNodeModulesDirs() {
|
|
110
|
+
return (process.env[NODE_MODULES_ENV] ?? "").split(delimiter).filter(Boolean);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function nodeModulesDirsFromPath() {
|
|
114
|
+
const dirs = [];
|
|
115
|
+
for (const entry of (process.env.PATH ?? "").split(delimiter)) {
|
|
116
|
+
if (!entry.endsWith(`${join("node_modules", ".bin")}`)) continue;
|
|
117
|
+
dirs.push(dirname(entry));
|
|
118
|
+
}
|
|
119
|
+
return dirs;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function findPackageDir(base, packageName) {
|
|
123
|
+
const packageSegments = packageName.split("/");
|
|
124
|
+
const roots =
|
|
125
|
+
basename(base) === "node_modules"
|
|
126
|
+
? [base]
|
|
127
|
+
: ancestors(base).map((ancestor) => join(ancestor, "node_modules"));
|
|
128
|
+
|
|
129
|
+
for (const root of roots) {
|
|
130
|
+
const packageDir = join(root, ...packageSegments);
|
|
131
|
+
if (existsSync(join(packageDir, "package.json"))) return packageDir;
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function readPackageEntry(packageDir) {
|
|
137
|
+
try {
|
|
138
|
+
const manifest = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8"));
|
|
139
|
+
const entry = exportEntry(manifest.exports) ?? manifest.module ?? manifest.main ?? "index.js";
|
|
140
|
+
const entryPath = join(packageDir, entry);
|
|
141
|
+
return existsSync(entryPath) ? entryPath : null;
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function exportEntry(exports) {
|
|
148
|
+
const root =
|
|
149
|
+
typeof exports === "object" && exports !== null ? (exports["."] ?? exports) : exports;
|
|
150
|
+
if (typeof root === "string") return root;
|
|
151
|
+
if (typeof root !== "object" || root === null) return null;
|
|
152
|
+
if (typeof root.import === "string") return root.import;
|
|
153
|
+
if (typeof root.default === "string") return root.default;
|
|
154
|
+
if (typeof root.node === "string") return root.node;
|
|
155
|
+
if (typeof root.node === "object" && root.node !== null) {
|
|
156
|
+
return root.node.import ?? root.node.default ?? null;
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function assertPinnedPackageSpecs(packageSpecs) {
|
|
162
|
+
const unpinned = packageSpecs.filter((spec) => !hasVersionSpec(spec));
|
|
163
|
+
if (unpinned.length === 0) return;
|
|
164
|
+
throw new Error(
|
|
165
|
+
[
|
|
166
|
+
`Refusing to bootstrap unpinned package spec(s): ${unpinned.join(", ")}`,
|
|
167
|
+
"Pass pinned npm package specs, for example:",
|
|
168
|
+
` ${packageSpecs.map((spec) => (hasVersionSpec(spec) ? spec : `${spec}@<version>`)).join(" ")}`,
|
|
169
|
+
].join("\n"),
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function hasVersionSpec(packageSpec) {
|
|
174
|
+
if (packageSpec.startsWith("@")) {
|
|
175
|
+
const slash = packageSpec.indexOf("/");
|
|
176
|
+
return slash !== -1 && packageSpec.indexOf("@", slash + 1) !== -1;
|
|
177
|
+
}
|
|
178
|
+
return packageSpec.includes("@");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function confirmBootstrap(packageSpecs) {
|
|
182
|
+
if (process.env[BOOTSTRAP_CONFIRM_ENV] === "1") return;
|
|
183
|
+
|
|
184
|
+
const installLine = `npm install --ignore-scripts --no-save ${packageSpecs.map(shellQuote).join(" ")}`;
|
|
185
|
+
if (!process.stdin.isTTY) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
[
|
|
188
|
+
"Required helper package(s) are missing.",
|
|
189
|
+
"To allow a one-time temporary dependency bootstrap for this run, set:",
|
|
190
|
+
` ${BOOTSTRAP_CONFIRM_ENV}=1`,
|
|
191
|
+
"The bootstrap command will be:",
|
|
192
|
+
` ${installLine}`,
|
|
193
|
+
].join("\n"),
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
198
|
+
try {
|
|
199
|
+
const answer = await rl.question(
|
|
200
|
+
[
|
|
201
|
+
"HyperFrames helper package(s) are missing.",
|
|
202
|
+
`Run a temporary install with lifecycle scripts disabled?`,
|
|
203
|
+
` ${installLine}`,
|
|
204
|
+
"Proceed? [y/N] ",
|
|
205
|
+
].join("\n"),
|
|
206
|
+
);
|
|
207
|
+
if (!/^(y|yes)$/i.test(answer.trim())) {
|
|
208
|
+
throw new Error("Dependency bootstrap cancelled.");
|
|
209
|
+
}
|
|
210
|
+
} finally {
|
|
211
|
+
rl.close();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function ancestors(start) {
|
|
216
|
+
const dirs = [];
|
|
217
|
+
let current = resolve(start);
|
|
218
|
+
const root = parse(current).root;
|
|
219
|
+
while (current && current !== root) {
|
|
220
|
+
dirs.push(current);
|
|
221
|
+
current = dirname(current);
|
|
222
|
+
}
|
|
223
|
+
dirs.push(root);
|
|
224
|
+
return dirs;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function bootstrapWithNpmInstall(packageNames) {
|
|
228
|
+
const installRoot = mkdtempSync(join(tmpdir(), "hyperframes-skill-deps-"));
|
|
229
|
+
const installResult = spawnSync(
|
|
230
|
+
process.platform === "win32" ? "npm.cmd" : "npm",
|
|
231
|
+
[
|
|
232
|
+
"install",
|
|
233
|
+
"--silent",
|
|
234
|
+
"--no-audit",
|
|
235
|
+
"--no-fund",
|
|
236
|
+
"--ignore-scripts",
|
|
237
|
+
"--no-save",
|
|
238
|
+
"--prefix",
|
|
239
|
+
installRoot,
|
|
240
|
+
...packageNames,
|
|
241
|
+
],
|
|
242
|
+
{ stdio: "inherit" },
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
if (installResult.error) throw installResult.error;
|
|
246
|
+
if (installResult.status !== 0) {
|
|
247
|
+
rmSync(installRoot, { recursive: true, force: true });
|
|
248
|
+
process.exit(installResult.status ?? 1);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const args = [...process.argv.slice(1)];
|
|
252
|
+
const result = spawnSync(process.execPath, args, {
|
|
253
|
+
stdio: "inherit",
|
|
254
|
+
env: {
|
|
255
|
+
...process.env,
|
|
256
|
+
[BOOTSTRAP_ENV]: "1",
|
|
257
|
+
[NODE_MODULES_ENV]: join(installRoot, "node_modules"),
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
rmSync(installRoot, { recursive: true, force: true });
|
|
262
|
+
if (result.error) throw result.error;
|
|
263
|
+
process.exit(result.status ?? 1);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function shellQuote(value) {
|
|
267
|
+
if (/^[A-Za-z0-9_./:@=-]+$/.test(value)) return value;
|
|
268
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
269
|
+
}
|
|
@@ -73,6 +73,20 @@ npx hyperframes preview --port 4567 # custom port (default 3002)
|
|
|
73
73
|
|
|
74
74
|
Hot-reloads on file changes. Opens the studio in your browser automatically.
|
|
75
75
|
|
|
76
|
+
When handing a project back to the user, use the Studio project URL, not the
|
|
77
|
+
source `index.html` path:
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
http://localhost:<port>/#project/<project-name>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Use the actual port from the preview output and the project directory name. For
|
|
84
|
+
example, after `npx hyperframes preview --port 3017` in `codex-openai-video`,
|
|
85
|
+
report `http://localhost:3017/#project/codex-openai-video`.
|
|
86
|
+
|
|
87
|
+
Treat `index.html` as source-code context only. It is fine to link it as an
|
|
88
|
+
implementation file, but do not label it as the project or preview surface.
|
|
89
|
+
|
|
76
90
|
## Rendering
|
|
77
91
|
|
|
78
92
|
```bash
|