diff --git a/.dockerignore b/.dockerignore index ac58817..2c56ca0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,16 +8,23 @@ # Node artifacts node_modules +**/node_modules npm-debug.log yarn-error.log # Build artifacts dist +**/dist +**/dist-* +**/npm-dist build *.tgz cli/src/server/playground-form-generated.ts cli/src/server/samples/generated.ts .facet +**/.facet +.gavel +**/.gavel .task *-cache/ compiled* @@ -57,6 +64,8 @@ data/ out.* # Temporary files +.tmp +**/.tmp tmp temp *.tmp diff --git a/.gitignore b/.gitignore index c3b8d6c..32fa720 100644 --- a/.gitignore +++ b/.gitignore @@ -179,3 +179,6 @@ cli/src/server/samples/generated.ts .grite/ .playwright-cli/ cli/src/server/playground-form-generated.ts +cli/dist-playground/ +examples/kitchen-sink/ +cli/npm-dist/ diff --git a/Dockerfile b/Dockerfile index 73219d4..5e48fd5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -132,16 +132,14 @@ LABEL org.opencontainers.image.title="Facet" \ org.opencontainers.image.vendor="Flanksource" \ org.opencontainers.image.version="${VERSION}" -# Warm the pnpm store by rendering a sample that imports @flanksource/facet, -# which also verifies the tarball above is complete (a dist-less package fails -# this build). HTML (not PDF) so the build never launches Chromium: buildkit -# RUN steps lack the dbus/userns environment a browser needs, while the pnpm -# store warming only requires the vite build. FACET_PACKAGE_PATH is scoped to -# this step because the image's own version may not be published to the -# registry at build time. -RUN cd /app/examples && \ - FACET_PACKAGE_PATH=/app/facet.tgz facet html FacetReport.tsx --data simple-data.json --output /tmp/warmup.html && \ - rm -f /tmp/warmup.html +# Populate the immutable shared module entry from the exact-version tarball and +# verify that the package can render. The cache rejects a tarball whose Facet +# version differs from the CLI version. +WORKDIR /app/examples +RUN mkdir -p /app/.tmp +RUN FACET_PACKAGE_PATH=/app/facet.tgz facet --skip-modules html FacetReport.tsx --data simple-data.json --output /app/.tmp/warmup.html +RUN rm -f /app/.tmp/warmup.html +WORKDIR /app HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ CMD curl -f http://localhost:3010/healthz || exit 1 diff --git a/Makefile b/Makefile index de9cfc7..8ec40d8 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build install test serve +.PHONY: build install lint test serve build: task build @@ -6,6 +6,8 @@ build: install: task install +lint: + task lint test: task test diff --git a/README.md b/README.md index 523a7e0..cc504d6 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,11 @@ npm install -g @flanksource/facet-cli # or: pnpm add -g @flanksource/facet-cli ``` -This installs the `facet` command, which runs on your Node.js (>=18). Rendering -additionally needs `pnpm` on PATH and, for PDF output, a system Chrome/Chromium — -run `facet doctor` to check. For a Node-free environment, use the standalone -binary below. +This installs the `facet` command, which runs on your Node.js (>=20.19). Rendering +uses `pnpm` to populate the shared module cache and reconcile project-specific +dependencies. PDF output also needs a system Chrome/Chromium. Run `facet doctor` +to check the environment. For a Node-free environment, use the standalone binary +below. ### Option 2: Standalone binary @@ -255,7 +256,7 @@ Keep the fixture, iteration count, worker settings, and environment unchanged. ### Build Process -**CLI mode** — `facet html` and `facet pdf` run the full pipeline locally: +**CLI mode** — without a Facet server URL, `facet html` and `facet pdf` run the full pipeline locally: 1. **Setup `.facet/`** — Creates an isolated build directory with symlinks to your sources 2. **Generate configs** — Auto-generates `vite.config.ts`, `tsconfig.json`, `entry.tsx` @@ -267,6 +268,8 @@ Keep the fixture, iteration count, worker settings, and environment unchanged. **Server mode** — `facet serve` wraps the same pipeline behind an HTTP API with a worker pool, LRU cache, optional S3 upload, and an interactive playground at `localhost:3010`. +When `FACET_URL` or `--facet-url` is set, the CLI loads and validates data locally, uploads the template project to `/render`, downloads the result, and writes it to the normal local output path. The upload excludes Git metadata, dependencies, Facet caches, temporary files, and build output. + ## Page Component API @@ -401,6 +404,43 @@ dropdown to try it. ## CLI Commands +### Remote rendering + +Set `FACET_URL` or pass the global `--facet-url` option to submit `html` and `pdf` jobs to a Facet server. The explicit flag takes precedence over the environment variable. + +```bash +FACET_URL=https://facet.example.com facet pdf MyDatasheet.tsx -d data.json -o report.pdf +facet --facet-url https://facet.example.com html MyDatasheet.tsx -o ./dist/ +``` + +Remote mode requires `tar` locally but does not require local Chromium or pnpm. Data loaders and schema validation still run locally. The server controls sandboxing, module mode, and cache lifecycle, so `--sandbox`, `--skip-modules`, and `--clear-cache` fail when combined with a Facet URL. A server or network error stops the command without falling back to local rendering. + +### Shared modules + +`--skip-modules` is a global option and may appear before or after any subcommand: + +```bash +facet --skip-modules html MyDatasheet.tsx -d data.json +facet pdf MyDatasheet.tsx --skip-modules -o out.pdf +facet serve --skip-modules --templates-dir ./templates +``` + +The first use installs a Facet-only module set pinned to the CLI version under +`${FACET_CACHE_DIR:-~/.facet/cache}/modules//--node`. +Later invocations link `.facet/node_modules` directly to that immutable entry, so +they do not read consumer or nested `package.json` files, package-manager pins, +lockfiles, overrides, `.npmrc`, or directory-based `FACET_PACKAGE_PATH` overrides. +Templates that import additional packages must run without `--skip-modules`. +Server requests containing `dependencies` receive HTTP 400 while the server uses +this mode. `facet doctor --skip-modules --fix` verifies or rebuilds the exact +shared entry selected by the current Facet and Node versions. + +Without `--skip-modules`, a new `.facet` install is seeded by cloning the shared +`node_modules` directory on APFS, then reconciled against the generated project +manifest with `pnpm install`. Facet logs and uses a fresh install when cloning is +unavailable, including on non-macOS filesystems. `--clear-cache` clears only the +project `.facet` scaffold; it does not remove the versioned shared module cache. + ### `facet html