fuzzyworld-tools / commit
Add fuzzyworld.keep to protect server files from deploys; v1.0.4
README.md | 5 +++-- lib/deploy.mjs | 12 ++++++++++-- package.json | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 11b5bcb..68ba297 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ Add to the app repo's `package.json`: "prebuild": ["pnpm gen-assets"], // commands before checks and build "checks": ["typecheck", "test"], // scripts that must pass (default: whichever of typecheck/lint/test exist) "build": "make public", // default: `<pm> run build` - "out": "dist" // default: dist, else public, else build + "out": "dist", // default: dist, else public, else build + "keep": ["my-game.apk"] // files on the server a deploy must not delete or replace (e.g. a download built elsewhere) }, "devDependencies": { "fuzzyworld-tools": "git+https://code.fuzzyworld.net/fuzzyworld-tools.git#semver:^1.0.0" } ``` @@ -39,7 +40,7 @@ Then `pnpm run deploy` / `npm run deploy`. **`fw deploy`**: 2. installs with the lockfile's package manager (`pnpm install --frozen-lockfile`, `npm ci`, `yarn install --frozen-lockfile`) 3. runs `prebuild`, then the `checks`, then the build. Any failure stops the deploy. 4. checks the output: every same-site URL in `index.html`, the web manifest's `id`/`scope`/`start_url`, and every `serviceWorker.register(...)` script and scope must stay inside `/<app>/`. That keeps apps on the shared origin from capturing each other, and makes each one install separately on Android. Any problem stops the deploy. -5. `rsync --delete` to `apps.fuzzyworld.net/<app>/`, writes `.fuzzyworld.json` (commit, time; not web-readable), and refreshes the apps index and the repo's code page +5. `rsync --delete` to `apps.fuzzyworld.net/<app>/` (except `keep` paths), writes `.fuzzyworld.json` (commit, time; not web-readable), and refreshes the apps index and the repo's code page 6. prints the URL `--dry-run` does everything except upload. diff --git a/lib/deploy.mjs b/lib/deploy.mjs index 677740e..67df114 100644 --- a/lib/deploy.mjs +++ b/lib/deploy.mjs @@ -14,7 +14,8 @@ // "prebuild": ["pnpm gen-assets"], // shell commands before checks and build // "checks": ["typecheck", "test"], // package scripts that must pass (default: those of typecheck/lint/test that exist) // "build": "make public", // build command (default: `<pm> run build`) -// "out": "dist" // output directory (default: dist, else public, else build) +// "out": "dist", // output directory (default: dist, else public, else build) +// "keep": ["app.apk"] // files already on the server that a deploy must not delete or replace // } import { spawnSync, execFileSync } from 'node:child_process'; import fs from 'node:fs'; @@ -69,6 +70,11 @@ function readConfig(dir) { if (!cfg) die(`no "fuzzyworld" section in ${file}`); if (!cfg.app || !NAME_RE.test(cfg.app)) die(`invalid or missing fuzzyworld.app in ${file}`); if (cfg.repo && !NAME_RE.test(cfg.repo)) die(`invalid fuzzyworld.repo in ${file}`); + for (const k of cfg.keep || []) { + if (typeof k !== 'string' || !k || k.startsWith('/') || k.split('/').some((p) => p === '..' || p === '')) { + die(`invalid fuzzyworld.keep entry ${JSON.stringify(k)} in ${file}`); + } + } return { pkg, cfg }; } @@ -225,7 +231,9 @@ export function deploy(argv) { } step(`uploading to apps.fuzzyworld.net/${app}/`); run('ssh', [HOST, `mkdir -p apps.fuzzyworld.net/${app}`]); - run('rsync', ['-rlptz', '--delete', `${out}/`, `${HOST}:apps.fuzzyworld.net/${app}/`]); + // `keep` paths are excluded, so rsync --delete neither removes nor overwrites them. + const keep = (cfg.keep || []).flatMap((k) => ['--exclude', `/${k}`]); + run('rsync', ['-rlptz', '--delete', ...keep, `${out}/`, `${HOST}:apps.fuzzyworld.net/${app}/`]); run('ssh', [HOST, `~/bin/fw app-deployed ${app} ${repo}`]); console.log(`\n✅ ${APPS_URL}/${app}/ (commit ${commit.slice(0, 12)})`); } finally { diff --git a/package.json b/package.json index 2cd7923..6c29f66 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fuzzyworld-tools", - "version": "1.0.3", + "version": "1.0.4", "description": "Tooling for code.fuzzyworld.net and apps.fuzzyworld.net: fw command, deploy, vite plugin.", "type": "module", "bin": {