@effing/create 0.14.1 → 0.15.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/dist/index.js CHANGED
@@ -61,8 +61,8 @@ Creating a new Effing project in ${root}...
61
61
  await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
62
62
  console.log("Done! To get started:\n");
63
63
  console.log(` cd ${projectName}`);
64
- console.log(" npm install");
65
- console.log(" npm run dev\n");
64
+ console.log(" npm install # or: pnpm install");
65
+ console.log(" npm run dev # or: pnpm run dev\n");
66
66
  console.log("Then open http://localhost:3839 to see your project.\n");
67
67
  }
68
68
  main().catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effing/create",
3
- "version": "0.14.1",
3
+ "version": "0.15.0",
4
4
  "description": "Create a new Effing project",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.js",
@@ -0,0 +1,38 @@
1
+ FROM node:22-alpine AS base
2
+ RUN corepack enable
3
+ WORKDIR /app
4
+
5
+ # Stage: install all deps and build
6
+ FROM base AS build
7
+ COPY package.json pnpm-lock.yaml* package-lock.json* ./
8
+ RUN if [ -f pnpm-lock.yaml ]; then \
9
+ pnpm install --frozen-lockfile; \
10
+ elif [ -f package-lock.json ]; then \
11
+ npm ci; \
12
+ else \
13
+ npm install; \
14
+ fi
15
+ COPY . .
16
+ RUN npm run build
17
+
18
+ # Stage: production deps only
19
+ FROM base AS prod-deps
20
+ COPY package.json pnpm-lock.yaml* package-lock.json* ./
21
+ RUN if [ -f pnpm-lock.yaml ]; then \
22
+ pnpm install --frozen-lockfile --prod; \
23
+ elif [ -f package-lock.json ]; then \
24
+ npm ci --omit=dev; \
25
+ else \
26
+ npm install --omit=dev; \
27
+ fi
28
+
29
+ # Stage: final runtime image
30
+ FROM node:22-alpine
31
+ WORKDIR /app
32
+ ENV NODE_ENV=production
33
+ ENV PORT=8080
34
+ COPY --from=prod-deps /app/node_modules ./node_modules
35
+ COPY --from=build /app/package.json ./package.json
36
+ COPY --from=build /app/build ./build
37
+ EXPOSE $PORT
38
+ CMD ["./node_modules/.bin/react-router-serve", "./build/server/index.js"]
@@ -0,0 +1,6 @@
1
+ node_modules
2
+ build
3
+ .react-router
4
+ .git
5
+ .env
6
+ .env.*
@@ -5,7 +5,7 @@ import {
5
5
  useNavigation,
6
6
  type ShouldRevalidateFunctionArgs,
7
7
  } from "react-router";
8
- import { useEffect, useReducer, useState } from "react";
8
+ import { useEffect, useReducer, useRef, useState } from "react";
9
9
  import invariant from "tiny-invariant";
10
10
  import { serialize } from "@effing/serde";
11
11
  import {
@@ -59,20 +59,28 @@ type ActionResult =
59
59
  type RenderState =
60
60
  | { step: "idle" }
61
61
  | { step: "started"; startedAt: number }
62
- | { step: "ready"; startedAt: number; videoUrl: string; scale: number }
62
+ | {
63
+ step: "ready";
64
+ startedAt: number;
65
+ videoUrl: string;
66
+ scale: number;
67
+ downloadUrl?: string;
68
+ }
63
69
  | {
64
70
  step: "done";
65
71
  startedAt: number;
66
72
  videoUrl: string;
67
73
  scale: number;
68
74
  playbackAt: number;
75
+ downloadUrl?: string;
69
76
  };
70
77
 
71
78
  type RenderAction =
72
79
  | { type: "start" }
73
80
  | { type: "ready"; videoUrl: string; scale: number }
74
81
  | { type: "play" }
75
- | { type: "error" };
82
+ | { type: "error" }
83
+ | { type: "buffered"; downloadUrl: string };
76
84
 
77
85
  function renderReducer(state: RenderState, action: RenderAction): RenderState {
78
86
  switch (action.type) {
@@ -89,6 +97,10 @@ function renderReducer(state: RenderState, action: RenderAction): RenderState {
89
97
  case "play":
90
98
  if (state.step !== "ready") return state;
91
99
  return { ...state, step: "done", playbackAt: Date.now() };
100
+ case "buffered":
101
+ if (state.step === "ready" || state.step === "done")
102
+ return { ...state, downloadUrl: action.downloadUrl };
103
+ return state;
92
104
  case "error":
93
105
  return { step: "idle" };
94
106
  }
@@ -286,6 +298,7 @@ export default function EffiePreviewPage() {
286
298
 
287
299
  const [render, dispatch] = useReducer(renderReducer, { step: "idle" });
288
300
  const [elapsedToPlay, setElapsedToPlay] = useState<number | null>(null);
301
+ const prevDownloadUrlRef = useRef<string | null>(null);
289
302
 
290
303
  // Update elapsed time while rendering is in progress
291
304
  useEffect(() => {
@@ -381,6 +394,19 @@ export default function EffiePreviewPage() {
381
394
 
382
395
  const handleRenderSubmit = () => {
383
396
  dispatch({ type: "start" });
397
+ if (prevDownloadUrlRef.current) {
398
+ URL.revokeObjectURL(prevDownloadUrlRef.current);
399
+ prevDownloadUrlRef.current = null;
400
+ }
401
+ };
402
+
403
+ const handleFullyBuffered = (blob: Blob) => {
404
+ if (prevDownloadUrlRef.current) {
405
+ URL.revokeObjectURL(prevDownloadUrlRef.current);
406
+ }
407
+ const downloadUrl = URL.createObjectURL(blob);
408
+ prevDownloadUrlRef.current = downloadUrl;
409
+ dispatch({ type: "buffered", downloadUrl });
384
410
  };
385
411
 
386
412
  const formatSourceUrl = (url: string, maxLen = 70) => {
@@ -461,6 +487,7 @@ export default function EffiePreviewPage() {
461
487
  resolution={coverResolution}
462
488
  video={"videoUrl" in render ? render.videoUrl : null}
463
489
  onPlay={handleVideoPlay}
490
+ onFullyBuffered={handleFullyBuffered}
464
491
  style={{
465
492
  border: "1px solid black",
466
493
  backgroundColor: "#eee",
@@ -594,13 +621,50 @@ export default function EffiePreviewPage() {
594
621
  />
595
622
  )}
596
623
 
624
+ {/* Render Progress */}
625
+ {(render.step === "started" || render.step === "ready") &&
626
+ elapsedToPlay !== null && (
627
+ <div style={{ color: "#4CAE4C" }}>
628
+ Rendering... {elapsedToPlay.toFixed(1)}s
629
+ </div>
630
+ )}
631
+
597
632
  {/* Render Success */}
598
- {render.step === "done" && elapsedToPlay !== null && (
599
- <div style={{ color: "#4CAE4C" }}>
600
- Started playing after {elapsedToPlay.toFixed(1)}s (at{" "}
601
- {Math.round(render.scale * 100)}%)
602
- </div>
603
- )}
633
+ {(render.step === "ready" || render.step === "done") &&
634
+ elapsedToPlay !== null && (
635
+ <div
636
+ style={{
637
+ display: "flex",
638
+ flexDirection: "column",
639
+ alignItems: "flex-start",
640
+ gap: "1rem",
641
+ }}
642
+ >
643
+ {render.step === "done" && (
644
+ <span style={{ color: "#4CAE4C" }}>
645
+ Started playing after {elapsedToPlay.toFixed(1)}s (at{" "}
646
+ {Math.round(render.scale * 100)}%)
647
+ </span>
648
+ )}
649
+ {render.downloadUrl && (
650
+ <a
651
+ href={render.downloadUrl}
652
+ download={`${effieId}-${width}x${height}.mp4`}
653
+ style={{
654
+ padding: "0.4rem 0.75rem",
655
+ backgroundColor: "#fff",
656
+ color: "#4CAE4C",
657
+ border: "1px solid #4CAE4C",
658
+ borderRadius: 4,
659
+ fontSize: "14px",
660
+ textDecoration: "none",
661
+ }}
662
+ >
663
+ Download video
664
+ </a>
665
+ )}
666
+ </div>
667
+ )}
604
668
 
605
669
  {/* Downloading Status */}
606
670
  {warmupDownloadingItems.length > 0 && (
@@ -11,15 +11,15 @@
11
11
  "typecheck": "react-router typegen && tsc"
12
12
  },
13
13
  "dependencies": {
14
- "@effing/annie": "^0.14.1",
15
- "@effing/annie-player": "^0.14.1",
16
- "@effing/effie": "^0.14.1",
17
- "@effing/effie-preview": "^0.14.1",
18
- "@effing/ffs": "^0.14.1",
19
- "@effing/satori": "^0.14.1",
14
+ "@effing/annie": "^0.15.0",
15
+ "@effing/annie-player": "^0.15.0",
16
+ "@effing/effie": "^0.15.0",
17
+ "@effing/effie-preview": "^0.15.0",
18
+ "@effing/ffs": "^0.15.0",
19
+ "@effing/satori": "^0.15.0",
20
20
  "@resvg/resvg-js": "^2.6.2",
21
- "@effing/serde": "^0.14.1",
22
- "@effing/tween": "^0.14.1",
21
+ "@effing/serde": "^0.15.0",
22
+ "@effing/tween": "^0.15.0",
23
23
  "@react-router/node": "^7.0.0",
24
24
  "@react-router/serve": "^7.0.0",
25
25
  "cross-env": "^7.0.3",
@@ -1,11 +1,10 @@
1
1
  import { reactRouter } from "@react-router/dev/vite";
2
- import { satoriPoolPlugin } from "@effing/satori/vite";
3
2
  import { defineConfig } from "vite";
4
3
  import tsconfigPaths from "vite-tsconfig-paths";
5
4
 
6
5
  export default defineConfig({
7
6
  server: { port: 3839 }, // 3839 = 0xEFF, how effing cool is that? ʘ‿ʘ
8
- plugins: [reactRouter(), tsconfigPaths(), satoriPoolPlugin()],
7
+ plugins: [reactRouter(), tsconfigPaths()],
9
8
  ssr: {
10
9
  noExternal: ["yoga-wasm-web", "yoga-wasm-web/asm", "satori", "emoji-regex"],
11
10
  external: ["@resvg/resvg-js"],