plotlink-ows 1.0.32 → 1.2.94
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/README.md +4 -0
- package/app/lib/agent-command.ts +85 -0
- package/app/lib/agent-readiness.ts +133 -0
- package/app/lib/apply-schema.ts +55 -0
- package/app/lib/bubble-text.ts +160 -0
- package/app/lib/cartoon-coach.ts +198 -0
- package/app/lib/cartoon-markdown.ts +83 -0
- package/app/lib/cartoon-prompt.ts +122 -0
- package/app/lib/cartoon-readiness.ts +811 -0
- package/app/lib/clean-image-sync.ts +245 -0
- package/app/lib/codex-images.ts +152 -0
- package/app/lib/cut-asset-diagnostics.ts +120 -0
- package/app/lib/cuts.ts +302 -0
- package/app/lib/fonts.ts +109 -0
- package/app/lib/generate-claude-md.ts +10 -3
- package/app/lib/generate-story-instructions.ts +731 -0
- package/app/lib/image-asset-validate.ts +123 -0
- package/app/lib/lettering-status.ts +133 -0
- package/app/lib/overlays.ts +637 -0
- package/app/lib/paths.ts +10 -0
- package/app/lib/public-title.ts +65 -0
- package/app/lib/publish.ts +16 -2
- package/app/lib/story-progress.ts +243 -0
- package/app/lib/terminal-protocol.ts +16 -0
- package/app/lib/terminal-redact.ts +50 -0
- package/app/prisma/schema.sql +25 -0
- package/app/routes/agent.ts +42 -0
- package/app/routes/codex-images.ts +67 -0
- package/app/routes/publish.ts +209 -28
- package/app/routes/stories.ts +961 -5
- package/app/routes/terminal.ts +383 -31
- package/app/server.ts +47 -12
- package/app/vite.config.ts +6 -0
- package/app/web/components/CartoonPreview.tsx +267 -0
- package/app/web/components/CartoonPublishPage.tsx +407 -0
- package/app/web/components/CartoonPublishPreview.tsx +121 -0
- package/app/web/components/CartoonStepGuide.tsx +90 -0
- package/app/web/components/CartoonWorkflowNav.tsx +68 -0
- package/app/web/components/CodexImportPicker.tsx +230 -0
- package/app/web/components/CutListPanel.tsx +1299 -0
- package/app/web/components/EpisodesPage.tsx +80 -0
- package/app/web/components/FinishEpisodePanel.tsx +151 -0
- package/app/web/components/Layout.tsx +7 -4
- package/app/web/components/LetteringEditor.tsx +1141 -0
- package/app/web/components/PreviewPanel.tsx +1017 -144
- package/app/web/components/Settings.tsx +63 -0
- package/app/web/components/StoriesPage.tsx +710 -33
- package/app/web/components/StoryBrowser.tsx +22 -14
- package/app/web/components/StoryInfoPage.tsx +266 -0
- package/app/web/components/StoryProgressPanel.tsx +516 -0
- package/app/web/components/TerminalPanel.tsx +233 -11
- package/app/web/components/WorkflowCoach.tsx +128 -0
- package/app/web/components/asset-image.tsx +114 -0
- package/app/web/components/asset-test-utils.ts +44 -0
- package/app/web/components/export-cut.ts +320 -0
- package/app/web/dist/assets/export-cut-nKQ_n2-J.js +1 -0
- package/app/web/dist/assets/index-BAZGwVwj.js +143 -0
- package/app/web/dist/assets/index-DoXH2OlP.css +32 -0
- package/app/web/dist/index.html +2 -2
- package/app/web/lib/cartoon-publish-summary.ts +43 -0
- package/app/web/lib/codex-import.ts +94 -0
- package/app/web/lib/image-compress.ts +53 -0
- package/app/web/lib/import-image.ts +58 -0
- package/app/web/lib/publish-helpers.ts +385 -0
- package/app/web/lib/upload-retry.ts +130 -0
- package/app/web/lib/verify-public-title.ts +105 -0
- package/app/web/styles.css +9 -0
- package/bin/plotlink-ows.js +53 -16
- package/bin/startup-plan.cjs +58 -0
- package/lib/genres.ts +92 -0
- package/package.json +60 -20
- package/scripts/gen-schema-sql.mjs +49 -0
- package/scripts/package-hygiene.mjs +116 -0
- package/scripts/preflight.mjs +173 -0
- package/scripts/start-smoke.mjs +128 -0
- package/app/node_modules/.prisma/local-client/client.d.ts +0 -1
- package/app/node_modules/.prisma/local-client/client.js +0 -5
- package/app/node_modules/.prisma/local-client/default.d.ts +0 -1
- package/app/node_modules/.prisma/local-client/default.js +0 -5
- package/app/node_modules/.prisma/local-client/edge.d.ts +0 -1
- package/app/node_modules/.prisma/local-client/edge.js +0 -184
- package/app/node_modules/.prisma/local-client/index-browser.js +0 -173
- package/app/node_modules/.prisma/local-client/index.d.ts +0 -3304
- package/app/node_modules/.prisma/local-client/index.js +0 -207
- package/app/node_modules/.prisma/local-client/libquery_engine-darwin-arm64.dylib.node +0 -0
- package/app/node_modules/.prisma/local-client/package.json +0 -183
- package/app/node_modules/.prisma/local-client/query_engine_bg.js +0 -2
- package/app/node_modules/.prisma/local-client/query_engine_bg.wasm +0 -0
- package/app/node_modules/.prisma/local-client/runtime/edge-esm.js +0 -35
- package/app/node_modules/.prisma/local-client/runtime/edge.js +0 -35
- package/app/node_modules/.prisma/local-client/runtime/index-browser.d.ts +0 -370
- package/app/node_modules/.prisma/local-client/runtime/index-browser.js +0 -17
- package/app/node_modules/.prisma/local-client/runtime/library.d.ts +0 -3982
- package/app/node_modules/.prisma/local-client/runtime/library.js +0 -147
- package/app/node_modules/.prisma/local-client/runtime/react-native.js +0 -84
- package/app/node_modules/.prisma/local-client/runtime/wasm-compiler-edge.js +0 -85
- package/app/node_modules/.prisma/local-client/runtime/wasm-engine-edge.js +0 -38
- package/app/node_modules/.prisma/local-client/schema.prisma +0 -21
- package/app/node_modules/.prisma/local-client/wasm-edge-light-loader.mjs +0 -5
- package/app/node_modules/.prisma/local-client/wasm-worker-loader.mjs +0 -5
- package/app/node_modules/.prisma/local-client/wasm.d.ts +0 -1
- package/app/node_modules/.prisma/local-client/wasm.js +0 -191
- package/app/web/dist/assets/index-B-2Ft7Yv.css +0 -32
- package/app/web/dist/assets/index-BFw-v-OZ.js +0 -134
- package/packages/cli/node_modules/commander/LICENSE +0 -22
- package/packages/cli/node_modules/commander/Readme.md +0 -1149
- package/packages/cli/node_modules/commander/esm.mjs +0 -16
- package/packages/cli/node_modules/commander/index.js +0 -24
- package/packages/cli/node_modules/commander/lib/argument.js +0 -149
- package/packages/cli/node_modules/commander/lib/command.js +0 -2662
- package/packages/cli/node_modules/commander/lib/error.js +0 -39
- package/packages/cli/node_modules/commander/lib/help.js +0 -709
- package/packages/cli/node_modules/commander/lib/option.js +0 -367
- package/packages/cli/node_modules/commander/lib/suggestSimilar.js +0 -101
- package/packages/cli/node_modules/commander/package-support.json +0 -16
- package/packages/cli/node_modules/commander/package.json +0 -82
- package/packages/cli/node_modules/commander/typings/esm.d.mts +0 -3
- package/packages/cli/node_modules/commander/typings/index.d.ts +0 -1045
- package/packages/cli/node_modules/resolve-from/index.d.ts +0 -31
- package/packages/cli/node_modules/resolve-from/index.js +0 -47
- package/packages/cli/node_modules/resolve-from/license +0 -9
- package/packages/cli/node_modules/resolve-from/package.json +0 -36
- package/packages/cli/node_modules/resolve-from/readme.md +0 -72
- package/packages/cli/node_modules/tsup/LICENSE +0 -21
- package/packages/cli/node_modules/tsup/README.md +0 -75
- package/packages/cli/node_modules/tsup/assets/cjs_shims.js +0 -13
- package/packages/cli/node_modules/tsup/assets/esm_shims.js +0 -9
- package/packages/cli/node_modules/tsup/assets/package.json +0 -3
- package/packages/cli/node_modules/tsup/dist/chunk-DI5BO6XE.js +0 -153
- package/packages/cli/node_modules/tsup/dist/chunk-JZ25TPTY.js +0 -42
- package/packages/cli/node_modules/tsup/dist/chunk-PEEXUWMS.js +0 -6
- package/packages/cli/node_modules/tsup/dist/chunk-TWFEYLU4.js +0 -352
- package/packages/cli/node_modules/tsup/dist/chunk-VGC3FXLU.js +0 -203
- package/packages/cli/node_modules/tsup/dist/cli-default.js +0 -12
- package/packages/cli/node_modules/tsup/dist/cli-main.js +0 -8
- package/packages/cli/node_modules/tsup/dist/cli-node.js +0 -14
- package/packages/cli/node_modules/tsup/dist/index.d.ts +0 -511
- package/packages/cli/node_modules/tsup/dist/index.js +0 -1711
- package/packages/cli/node_modules/tsup/dist/rollup.js +0 -6949
- package/packages/cli/node_modules/tsup/package.json +0 -99
- package/packages/cli/node_modules/tsup/schema.json +0 -362
- package/public/screenshot-1.png +0 -0
- package/public/screenshot-2.png +0 -0
- package/public/screenshot-3.png +0 -0
- package/scripts/e2e-verify.ts +0 -1100
package/bin/plotlink-ows.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// PlotLink OWS — CLI Wizard
|
|
3
|
-
// Zero external dependencies — Node builtins only
|
|
3
|
+
// Zero external dependencies — Node builtins + one in-package helper only.
|
|
4
4
|
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const path = require("path");
|
|
7
7
|
const readline = require("readline");
|
|
8
8
|
const { execSync, spawn } = require("child_process");
|
|
9
9
|
const crypto = require("crypto");
|
|
10
|
+
const { planStartup, shouldAutoOpen } = require("./startup-plan.cjs");
|
|
10
11
|
|
|
11
12
|
const CONFIG_DIR = path.join(require("os").homedir(), ".plotlink-ows");
|
|
12
13
|
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
@@ -203,6 +204,19 @@ async function cmdInit() {
|
|
|
203
204
|
process.exit(0);
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
// Are the runtime dependencies resolvable? Resolve a known runtime dep rather
|
|
208
|
+
// than probing `PROJECT_DIR/node_modules` directly — under a global (`-g`)
|
|
209
|
+
// install the package's deps are hoisted to a sibling `node_modules`, so that
|
|
210
|
+
// directory may not exist even though the deps resolve fine up the tree.
|
|
211
|
+
function runtimeDepsInstalled() {
|
|
212
|
+
try {
|
|
213
|
+
require.resolve("tsx", { paths: [PROJECT_DIR] });
|
|
214
|
+
return true;
|
|
215
|
+
} catch {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
206
220
|
function cmdStart() {
|
|
207
221
|
const config = readConfig();
|
|
208
222
|
if (!config) {
|
|
@@ -211,28 +225,51 @@ function cmdStart() {
|
|
|
211
225
|
process.exit(1);
|
|
212
226
|
}
|
|
213
227
|
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
228
|
+
// Runtime/build-time boundary (#470, EPIC #465): an installed package ships
|
|
229
|
+
// prebuilt assets and only runtime deps, so the start path must NOT run a web
|
|
230
|
+
// build or `npm install` here — that would fetch the build toolchain from the
|
|
231
|
+
// network (an unexpected rebuild on a user's machine). Only a source checkout
|
|
232
|
+
// (detected by `src/`, which is never in the published tarball) may build.
|
|
233
|
+
// Missing assets in an installed package mean a corrupted install.
|
|
234
|
+
const plan = planStartup({
|
|
235
|
+
isSourceCheckout: fs.existsSync(path.join(PROJECT_DIR, "src")),
|
|
236
|
+
depsInstalled: runtimeDepsInstalled(),
|
|
237
|
+
distBuilt: fs.existsSync(path.join(PROJECT_DIR, "app", "web", "dist", "index.html")),
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
if (plan.error) {
|
|
241
|
+
const what = plan.error === "deps"
|
|
242
|
+
? "Runtime dependencies are missing (node_modules)"
|
|
243
|
+
: "Prebuilt web assets are missing (app/web/dist)";
|
|
244
|
+
error(`${what} — this looks like a corrupted install.`);
|
|
245
|
+
log("Reinstall and try again:");
|
|
246
|
+
log(" \x1b[1mnpx plotlink-ows@latest\x1b[0m (or)");
|
|
247
|
+
log(" \x1b[1mnpm install -g plotlink-ows@latest\x1b[0m");
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
if (plan.install) {
|
|
251
|
+
log("Installing dependencies (source checkout)...");
|
|
217
252
|
execSync("npm install", { cwd: PROJECT_DIR, stdio: "inherit" });
|
|
218
253
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const distDir = path.join(PROJECT_DIR, "app", "web", "dist");
|
|
222
|
-
if (!fs.existsSync(distDir)) {
|
|
223
|
-
log("Building frontend...");
|
|
254
|
+
if (plan.build) {
|
|
255
|
+
log("Building frontend (source checkout)...");
|
|
224
256
|
execSync("npx vite build --config app/vite.config.ts", { cwd: PROJECT_DIR, stdio: "inherit" });
|
|
225
257
|
}
|
|
226
258
|
|
|
227
259
|
const port = config.port || 7777;
|
|
228
260
|
|
|
229
|
-
// Auto-open browser after a short delay
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
261
|
+
// Auto-open browser after a short delay. Skipped for non-interactive runs
|
|
262
|
+
// (the release start smoke / preflight set PLOTLINK_OWS_NO_OPEN=1) so a publish
|
|
263
|
+
// check never pops a browser tab on the operator machine (#481). Normal
|
|
264
|
+
// `npx plotlink-ows` startup is unaffected.
|
|
265
|
+
if (shouldAutoOpen(process.env)) {
|
|
266
|
+
setTimeout(() => {
|
|
267
|
+
try {
|
|
268
|
+
const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
269
|
+
execSync(`${openCmd} http://localhost:${port}`, { stdio: "ignore" });
|
|
270
|
+
} catch { /* ignore */ }
|
|
271
|
+
}, 2000);
|
|
272
|
+
}
|
|
236
273
|
|
|
237
274
|
// Run server in foreground with visible logs
|
|
238
275
|
const server = spawn("npx", ["tsx", "app/server.ts"], {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Runtime/build-time boundary planner for the PlotLink OWS CLI (#470, EPIC #465).
|
|
2
|
+
//
|
|
3
|
+
// The published `plotlink-ows` package ships PREBUILT runtime assets
|
|
4
|
+
// (`app/web/dist`) and installs only runtime `dependencies`. All build tooling
|
|
5
|
+
// (vite, tailwind, react, …) lives in `devDependencies` (see #469) and is
|
|
6
|
+
// therefore ABSENT from an installed package. Consequence: the `start` path must
|
|
7
|
+
// NEVER run a web build or `npm install` on a user's machine — doing so would
|
|
8
|
+
// fetch the build toolchain from the network (an unexpected rebuild). Missing
|
|
9
|
+
// prebuilt assets in an installed package mean a CORRUPTED install, which we
|
|
10
|
+
// surface loudly instead of silently trying to rebuild.
|
|
11
|
+
//
|
|
12
|
+
// A *source checkout* (the repo) is the only place a (re)build is allowed. It is
|
|
13
|
+
// detected by the presence of `src/` (the Next.js web app), which is NOT part of
|
|
14
|
+
// the published `files` allowlist and so never exists in an installed package.
|
|
15
|
+
//
|
|
16
|
+
// This module is intentionally PURE (no fs/process access) so the boundary
|
|
17
|
+
// policy is unit-tested in isolation; `bin/plotlink-ows.js` probes the
|
|
18
|
+
// environment and feeds the facts in.
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Decide what the `start` command must do before launching the server.
|
|
22
|
+
*
|
|
23
|
+
* @param {object} env
|
|
24
|
+
* @param {boolean} env.isSourceCheckout running from the repo (has `src/`)
|
|
25
|
+
* @param {boolean} env.depsInstalled runtime deps resolvable
|
|
26
|
+
* @param {boolean} env.distBuilt prebuilt web UI present (app/web/dist/index.html)
|
|
27
|
+
* @returns {{ install: boolean, build: boolean, error: null | "deps" | "dist" }}
|
|
28
|
+
* install/build are only ever `true` in a source checkout (dev convenience).
|
|
29
|
+
* `error` is a fatal broken-install condition for an installed package.
|
|
30
|
+
*/
|
|
31
|
+
function planStartup({ isSourceCheckout, depsInstalled, distBuilt }) {
|
|
32
|
+
if (isSourceCheckout) {
|
|
33
|
+
// Dev convenience: bring a fresh checkout up without manual build steps.
|
|
34
|
+
return { install: !depsInstalled, build: !distBuilt, error: null };
|
|
35
|
+
}
|
|
36
|
+
// Installed package: never pull build tooling. Missing assets ⇒ broken install.
|
|
37
|
+
if (!depsInstalled) return { install: false, build: false, error: "deps" };
|
|
38
|
+
if (!distBuilt) return { install: false, build: false, error: "dist" };
|
|
39
|
+
return { install: false, build: false, error: null };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Decide whether `start` should auto-open the local app in a browser (#481).
|
|
44
|
+
*
|
|
45
|
+
* Normal `npx plotlink-ows` startup auto-opens for convenience. Non-interactive
|
|
46
|
+
* release checks (the packed start smoke / `npm run preflight`) set
|
|
47
|
+
* `PLOTLINK_OWS_NO_OPEN=1` so they never pop a browser tab on the operator
|
|
48
|
+
* machine. Only the exact string "1" disables it; any other value keeps the
|
|
49
|
+
* default open behavior.
|
|
50
|
+
*
|
|
51
|
+
* @param {Record<string, string | undefined>} env a process.env-shaped object
|
|
52
|
+
* @returns {boolean}
|
|
53
|
+
*/
|
|
54
|
+
function shouldAutoOpen(env) {
|
|
55
|
+
return env.PLOTLINK_OWS_NO_OPEN !== "1";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { planStartup, shouldAutoOpen };
|
package/lib/genres.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export const GENRES = [
|
|
2
|
+
"Romance",
|
|
3
|
+
"Fantasy",
|
|
4
|
+
"Science Fiction",
|
|
5
|
+
"Mystery",
|
|
6
|
+
"Thriller",
|
|
7
|
+
"Horror",
|
|
8
|
+
"Adventure",
|
|
9
|
+
"Historical Fiction",
|
|
10
|
+
"Contemporary Lit",
|
|
11
|
+
"Humor",
|
|
12
|
+
"Poetry",
|
|
13
|
+
"Non-Fiction",
|
|
14
|
+
"Fanfiction",
|
|
15
|
+
"Short Story",
|
|
16
|
+
"Paranormal",
|
|
17
|
+
"Werewolf",
|
|
18
|
+
"LGBTQ+",
|
|
19
|
+
"New Adult",
|
|
20
|
+
"Teen Fiction",
|
|
21
|
+
"Diverse Lit",
|
|
22
|
+
"Others",
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
export const LANGUAGES = [
|
|
26
|
+
"English",
|
|
27
|
+
"Chinese",
|
|
28
|
+
"Korean",
|
|
29
|
+
"Japanese",
|
|
30
|
+
"Spanish",
|
|
31
|
+
"French",
|
|
32
|
+
"Hindi",
|
|
33
|
+
"Arabic",
|
|
34
|
+
"Portuguese",
|
|
35
|
+
"Russian",
|
|
36
|
+
"Others",
|
|
37
|
+
] as const;
|
|
38
|
+
|
|
39
|
+
export const CONTENT_TYPES = ["fiction", "cartoon"] as const;
|
|
40
|
+
|
|
41
|
+
export type Genre = (typeof GENRES)[number];
|
|
42
|
+
export type Language = (typeof LANGUAGES)[number];
|
|
43
|
+
export type ContentType = (typeof CONTENT_TYPES)[number];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Punctuation/spacing/case-insensitive key for matching a free-form genre label
|
|
47
|
+
* to a canonical value. Strips everything but letters, digits and `+` (so
|
|
48
|
+
* `LGBTQ+` survives), which collapses `Sci-Fi`, `sci fi`, `SciFi` → `scifi` and
|
|
49
|
+
* `Science Fiction` / `science-fiction` → `sciencefiction`.
|
|
50
|
+
*/
|
|
51
|
+
function genreKey(input: string): string {
|
|
52
|
+
return input.toLowerCase().replace(/[^a-z0-9+]/g, "");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const CANONICAL_GENRE_BY_KEY: Record<string, Genre> = Object.fromEntries(
|
|
56
|
+
GENRES.map((g) => [genreKey(g), g]),
|
|
57
|
+
) as Record<string, Genre>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Common natural-language genre aliases → canonical PlotLink value (#412). Keyed
|
|
61
|
+
* by `genreKey`, so each entry already covers punctuation/spacing/case variants
|
|
62
|
+
* (e.g. the `scifi` key matches `Sci-Fi`, `Sci Fi`, `SciFi`). Canonical labels and
|
|
63
|
+
* their punctuation variants (e.g. `non fiction` → `Non-Fiction`) are handled by
|
|
64
|
+
* `CANONICAL_GENRE_BY_KEY` and don't need an alias here.
|
|
65
|
+
*/
|
|
66
|
+
const GENRE_ALIAS_BY_KEY: Record<string, Genre> = {
|
|
67
|
+
scifi: "Science Fiction",
|
|
68
|
+
sf: "Science Fiction",
|
|
69
|
+
comedy: "Humor",
|
|
70
|
+
humour: "Humor",
|
|
71
|
+
ya: "Teen Fiction",
|
|
72
|
+
youngadult: "Teen Fiction",
|
|
73
|
+
lgbt: "LGBTQ+",
|
|
74
|
+
lgbtq: "LGBTQ+",
|
|
75
|
+
"lgbtqia+": "LGBTQ+",
|
|
76
|
+
historical: "Historical Fiction",
|
|
77
|
+
scary: "Horror",
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Map a free-form genre label to its canonical PlotLink value, or `null` if it
|
|
82
|
+
* can't be resolved (#412). PlotLink's metadata update rejects non-canonical
|
|
83
|
+
* genres (e.g. `Sci-Fi` → `Invalid genre`), which once left a published pilot
|
|
84
|
+
* `UNCATEGORIZED`; callers normalize through this before sending metadata and
|
|
85
|
+
* surface a clear local error when it returns `null`. Empty/blank input → `null`.
|
|
86
|
+
*/
|
|
87
|
+
export function canonicalizeGenre(input: string | null | undefined): Genre | null {
|
|
88
|
+
if (!input) return null;
|
|
89
|
+
const key = genreKey(input.trim());
|
|
90
|
+
if (!key) return null;
|
|
91
|
+
return CANONICAL_GENRE_BY_KEY[key] ?? GENRE_ALIAS_BY_KEY[key] ?? null;
|
|
92
|
+
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plotlink-ows",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.94",
|
|
4
|
+
"packageManager": "npm@10.9.8",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": "20.x",
|
|
7
|
+
"npm": "10.x"
|
|
8
|
+
},
|
|
4
9
|
"bin": {
|
|
5
|
-
"plotlink-ows": "
|
|
10
|
+
"plotlink-ows": "bin/plotlink-ows.js"
|
|
6
11
|
},
|
|
7
12
|
"files": [
|
|
8
13
|
"bin/",
|
|
9
14
|
"app/",
|
|
10
15
|
"lib/ows/",
|
|
16
|
+
"lib/genres.ts",
|
|
11
17
|
"packages/",
|
|
12
18
|
"public/",
|
|
13
|
-
"scripts/"
|
|
19
|
+
"scripts/",
|
|
20
|
+
"!**/*.test.ts",
|
|
21
|
+
"!**/*.test.tsx",
|
|
22
|
+
"!**/*.test.js",
|
|
23
|
+
"!**/*.test.jsx",
|
|
24
|
+
"!**/*.test.mjs",
|
|
25
|
+
"!**/*.spec.ts",
|
|
26
|
+
"!**/*.spec.tsx",
|
|
27
|
+
"!**/*.spec.js",
|
|
28
|
+
"!**/__tests__/**",
|
|
29
|
+
"!**/node_modules/**",
|
|
30
|
+
"!**/*.tgz",
|
|
31
|
+
"!**/.next/cache/**",
|
|
32
|
+
"!**/.turbo/**",
|
|
33
|
+
"!**/.vite/**",
|
|
34
|
+
"!**/.cache/**",
|
|
35
|
+
"!**/*.tsbuildinfo",
|
|
36
|
+
"!**/coverage/**",
|
|
37
|
+
"!**/.nyc_output/**",
|
|
38
|
+
"!**/__fixtures__/**",
|
|
39
|
+
"!**/fixtures/**",
|
|
40
|
+
"!**/*.fixture.*",
|
|
41
|
+
"!**/*.snap",
|
|
42
|
+
"!**/screenshot-*",
|
|
43
|
+
"!**/screenshots/**",
|
|
44
|
+
"!**/e2e-verify.*",
|
|
45
|
+
"!**/tmp/**",
|
|
46
|
+
"!**/temp/**",
|
|
47
|
+
"!**/*.log",
|
|
48
|
+
"!**/*.tmp",
|
|
49
|
+
"!**/*.bak",
|
|
50
|
+
"!**/*.swp"
|
|
14
51
|
],
|
|
15
52
|
"workspaces": [
|
|
16
53
|
"packages/*"
|
|
@@ -26,39 +63,31 @@
|
|
|
26
63
|
"app:dev": "concurrently \"tsx watch app/server.ts\" \"vite --config app/vite.config.ts\"",
|
|
27
64
|
"app:build": "vite build --config app/vite.config.ts",
|
|
28
65
|
"app:start": "tsx app/server.ts",
|
|
66
|
+
"preflight": "node scripts/preflight.mjs",
|
|
67
|
+
"smoke:start": "node scripts/start-smoke.mjs",
|
|
29
68
|
"prepublishOnly": "npm run app:build",
|
|
30
69
|
"postinstall": "prisma generate --schema app/prisma/schema.prisma",
|
|
31
70
|
"prisma:local": "prisma generate --schema app/prisma/schema.prisma && prisma db push --schema app/prisma/schema.prisma",
|
|
71
|
+
"prisma:sql": "node scripts/gen-schema-sql.mjs",
|
|
32
72
|
"release:patch": "npm version patch && git push origin main --follow-tags && VERSION=$(node -p 'require(\"./package.json\").version') && gh release create \"v$VERSION\" --generate-notes --latest && npm publish",
|
|
33
73
|
"release:minor": "npm version minor && git push origin main --follow-tags && VERSION=$(node -p 'require(\"./package.json\").version') && gh release create \"v$VERSION\" --generate-notes --latest && npm publish",
|
|
34
74
|
"release:major": "npm version major && git push origin main --follow-tags && VERSION=$(node -p 'require(\"./package.json\").version') && gh release create \"v$VERSION\" --generate-notes --latest && npm publish"
|
|
35
75
|
},
|
|
36
76
|
"dependencies": {
|
|
37
|
-
"@
|
|
38
|
-
"@hono/node-server": "^1.19.12",
|
|
77
|
+
"@hono/node-server": "^1.19.14",
|
|
39
78
|
"@open-wallet-standard/core": "^1.2.4",
|
|
40
79
|
"@prisma/client": "^6.19.3",
|
|
41
80
|
"@supabase/supabase-js": "^2.99.1",
|
|
42
|
-
"@xterm/addon-fit": "^0.11.0",
|
|
43
|
-
"@xterm/addon-serialize": "^0.14.0",
|
|
44
|
-
"@xterm/xterm": "^6.0.0",
|
|
45
81
|
"dotenv": "^17.4.0",
|
|
46
|
-
"hono": "^4.12.
|
|
82
|
+
"hono": "^4.12.23",
|
|
47
83
|
"node-pty": "^1.2.0-beta.12",
|
|
48
84
|
"prisma": "^6.19.3",
|
|
49
|
-
"react": "19.2.3",
|
|
50
|
-
"react-dom": "19.2.3",
|
|
51
|
-
"react-markdown": "^10.1.0",
|
|
52
|
-
"rehype-sanitize": "^6.0.0",
|
|
53
|
-
"remark-breaks": "^4.0.0",
|
|
54
|
-
"remark-gfm": "^4.0.1",
|
|
55
|
-
"tailwindcss": "^4",
|
|
56
85
|
"tsx": "^4.21.0",
|
|
57
86
|
"viem": "^2.47.2",
|
|
58
|
-
"
|
|
59
|
-
"ws": "^8.20.0"
|
|
87
|
+
"ws": "^8.20.1"
|
|
60
88
|
},
|
|
61
89
|
"devDependencies": {
|
|
90
|
+
"@aws-sdk/client-s3": "^3.1009.0",
|
|
62
91
|
"@farcaster/miniapp-node": "^0.1.13",
|
|
63
92
|
"@farcaster/miniapp-sdk": "^0.3.0",
|
|
64
93
|
"@farcaster/miniapp-wagmi-connector": "^2.0.0",
|
|
@@ -77,13 +106,24 @@
|
|
|
77
106
|
"@types/ws": "^8.18.1",
|
|
78
107
|
"@vercel/analytics": "^2.0.1",
|
|
79
108
|
"@vitejs/plugin-react": "^4.7.0",
|
|
109
|
+
"@xterm/addon-fit": "^0.11.0",
|
|
110
|
+
"@xterm/addon-serialize": "^0.14.0",
|
|
111
|
+
"@xterm/xterm": "^6.0.0",
|
|
80
112
|
"concurrently": "^9.2.1",
|
|
81
113
|
"eslint": "^9",
|
|
82
|
-
"eslint-config-next": "16.
|
|
114
|
+
"eslint-config-next": "16.2.6",
|
|
83
115
|
"jsdom": "^27.0.1",
|
|
84
|
-
"next": "16.
|
|
116
|
+
"next": "16.2.6",
|
|
85
117
|
"ox": "^0.14.8",
|
|
118
|
+
"react": "19.2.3",
|
|
119
|
+
"react-dom": "19.2.3",
|
|
120
|
+
"react-markdown": "^10.1.0",
|
|
121
|
+
"rehype-sanitize": "^6.0.0",
|
|
122
|
+
"remark-breaks": "^4.0.0",
|
|
123
|
+
"remark-gfm": "^4.0.1",
|
|
124
|
+
"tailwindcss": "^4",
|
|
86
125
|
"typescript": "^5",
|
|
126
|
+
"vite": "^6.4.3",
|
|
87
127
|
"vitest": "^3.2.4",
|
|
88
128
|
"wagmi": "^2.19.5"
|
|
89
129
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Regenerate app/prisma/schema.sql from app/prisma/schema.prisma (#484).
|
|
3
|
+
//
|
|
4
|
+
// The installed app applies this committed DDL at startup via the Prisma client
|
|
5
|
+
// (app/lib/apply-schema.ts) instead of running `prisma db push`, so the native
|
|
6
|
+
// Prisma schema-engine is never needed at runtime. Run this (and commit the
|
|
7
|
+
// result) after ANY change to schema.prisma: npm run prisma:sql
|
|
8
|
+
//
|
|
9
|
+
// This uses the schema-engine via `prisma migrate diff` — that's fine here
|
|
10
|
+
// because it runs at DEV/build time on a developer machine, not at user runtime.
|
|
11
|
+
|
|
12
|
+
import { execFileSync } from "node:child_process";
|
|
13
|
+
import { writeFileSync, readFileSync } from "node:fs";
|
|
14
|
+
import { join, dirname } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
import { createRequire } from "node:module";
|
|
17
|
+
|
|
18
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
19
|
+
const schemaPath = join(root, "app", "prisma", "schema.prisma");
|
|
20
|
+
const outPath = join(root, "app", "prisma", "schema.sql");
|
|
21
|
+
|
|
22
|
+
// Resolve the local Prisma CLI (same robust resolution the runtime once used).
|
|
23
|
+
const requireFrom = createRequire(join(root, "__resolver__.js"));
|
|
24
|
+
const prismaPkg = requireFrom.resolve("prisma/package.json");
|
|
25
|
+
const pkg = JSON.parse(readFileSync(prismaPkg, "utf8"));
|
|
26
|
+
const binRel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin.prisma;
|
|
27
|
+
const prismaCli = join(dirname(prismaPkg), binRel);
|
|
28
|
+
|
|
29
|
+
const ddl = execFileSync(
|
|
30
|
+
process.execPath,
|
|
31
|
+
[prismaCli, "migrate", "diff", "--from-empty", "--to-schema-datamodel", schemaPath, "--script"],
|
|
32
|
+
{ encoding: "utf8" },
|
|
33
|
+
).trim();
|
|
34
|
+
|
|
35
|
+
const header = [
|
|
36
|
+
"-- Canonical SQLite DDL for the local writer database.",
|
|
37
|
+
"-- GENERATED from app/prisma/schema.prisma — do not edit by hand.",
|
|
38
|
+
"-- Regenerate after any schema change: npm run prisma:sql",
|
|
39
|
+
"--",
|
|
40
|
+
"-- Applied idempotently at startup via the Prisma client's library query engine",
|
|
41
|
+
"-- (app/lib/apply-schema.ts) so the installed package never invokes the native",
|
|
42
|
+
"-- Prisma schema-engine (`prisma db push`), which fails to spawn in some packed",
|
|
43
|
+
"-- prod-only environments (#484, EPIC #465).",
|
|
44
|
+
"",
|
|
45
|
+
"",
|
|
46
|
+
].join("\n");
|
|
47
|
+
|
|
48
|
+
writeFileSync(outPath, header + ddl + "\n");
|
|
49
|
+
console.log(`Wrote ${outPath}`);
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Suspicious-file detection for the release preflight (#466, EPIC #465).
|
|
2
|
+
//
|
|
3
|
+
// Any packed file whose path matches one of these rules must NEVER ship in the
|
|
4
|
+
// published `plotlink-ows` package. The package.json `files` allowlist already
|
|
5
|
+
// excludes them (negation patterns); this is the preflight's belt-and-suspenders
|
|
6
|
+
// detection so a regression is caught before publish. Keep the two in sync.
|
|
7
|
+
|
|
8
|
+
export const SUSPICIOUS_RULES = [
|
|
9
|
+
{ re: /(^|\/)node_modules\//, label: "bundled node_modules" },
|
|
10
|
+
{ re: /\.(test|spec)\.[cm]?[jt]sx?$/, label: "test/spec file" },
|
|
11
|
+
{ re: /(^|\/)(__fixtures__|fixtures)\/|\.fixture\./, label: "test fixture" },
|
|
12
|
+
{ re: /\.snap$/, label: "test snapshot" },
|
|
13
|
+
{ re: /(^|\/)e2e[-/]/, label: "e2e/test tooling" },
|
|
14
|
+
{ re: /\.tgz$/, label: "packed tarball" },
|
|
15
|
+
{ re: /(^|\/)(\.next\/cache|\.turbo|\.vite|\.cache|coverage|\.nyc_output)\//, label: "build/coverage cache" },
|
|
16
|
+
{ re: /(^|\/)screenshots?\/|(^|\/)screenshot-/, label: "screenshot/marketing image" },
|
|
17
|
+
{ re: /(^|\/)(tmp|temp)\/|\.(log|tmp|bak|swp)$/, label: "temp/log file" },
|
|
18
|
+
{ re: /(^|\/)\.env(\..+)?$|\.(pem|key)$/, label: "possible secret/credential file" },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// The runtime files the published package MUST contain. A `files`-allowlist
|
|
22
|
+
// change that drops one of these fails the preflight (#468). `app/web/dist` is
|
|
23
|
+
// required because the CLI serves the prebuilt web UI from it.
|
|
24
|
+
export const REQUIRED_PACK_FILES = [
|
|
25
|
+
"package.json",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"bin/plotlink-ows.js",
|
|
29
|
+
// The bin requires this in-package helper at runtime (start-path boundary
|
|
30
|
+
// planner); `files` ships all of `bin/`, but listing it here fails preflight
|
|
31
|
+
// if a future exclusion drops it (#470).
|
|
32
|
+
"bin/startup-plan.cjs",
|
|
33
|
+
"app/server.ts",
|
|
34
|
+
// Imported by app/server.ts at boot to apply the local SQLite schema without
|
|
35
|
+
// the native Prisma schema-engine (#484).
|
|
36
|
+
"app/lib/apply-schema.ts",
|
|
37
|
+
// The committed DDL apply-schema reads at startup — the installed app applies
|
|
38
|
+
// this instead of running `prisma db push` (#484).
|
|
39
|
+
"app/prisma/schema.sql",
|
|
40
|
+
"app/prisma/schema.prisma",
|
|
41
|
+
"app/web/dist/index.html",
|
|
42
|
+
// Root-lib file the server runtime imports at boot (publish route →
|
|
43
|
+
// `../../lib/genres`); `files` packs only `lib/ows/`, so it must be listed
|
|
44
|
+
// explicitly or the published CLI fails to start (#469).
|
|
45
|
+
"lib/genres.ts",
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// The published OWS CLI runtime install path (`dependencies`). EPIC #465 keeps
|
|
49
|
+
// this set MINIMAL — only packages the CLI actually loads at runtime (the server
|
|
50
|
+
// in `app/`, the `bin/` wizard, and the runtime helpers in `lib/`). Web-app
|
|
51
|
+
// (`src/`), build-time, and direct-upload-only packages belong in
|
|
52
|
+
// `devDependencies` (see DEPENDENCIES.md): React/Vite/etc. (#469) and
|
|
53
|
+
// `@aws-sdk/client-s3` (#471 — OWS uploads go through the PlotLink API, so the
|
|
54
|
+
// S3/Filebase client is web-app-only). A new entry here must be a genuine OWS
|
|
55
|
+
// runtime import; add it consciously (and document it) rather than by accident.
|
|
56
|
+
export const ALLOWED_RUNTIME_DEPS = [
|
|
57
|
+
"@hono/node-server",
|
|
58
|
+
"@open-wallet-standard/core",
|
|
59
|
+
"@prisma/client",
|
|
60
|
+
"@supabase/supabase-js",
|
|
61
|
+
"dotenv",
|
|
62
|
+
"hono",
|
|
63
|
+
"node-pty",
|
|
64
|
+
"prisma",
|
|
65
|
+
"tsx",
|
|
66
|
+
"viem",
|
|
67
|
+
"ws",
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Runtime `dependencies` that are NOT in the OWS runtime allowlist — i.e. a
|
|
72
|
+
* web-app/build-time/upload-only package that leaked into the published install
|
|
73
|
+
* path (#471, EPIC #465). An empty array means the install path is clean.
|
|
74
|
+
*/
|
|
75
|
+
export function findRuntimeDepLeaks(pkg) {
|
|
76
|
+
const allowed = new Set(ALLOWED_RUNTIME_DEPS);
|
|
77
|
+
return Object.keys(pkg.dependencies ?? {}).filter((d) => !allowed.has(d));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Return the REQUIRED_PACK_FILES that are NOT in the packed path list. */
|
|
81
|
+
export function findMissingRequired(paths) {
|
|
82
|
+
const set = new Set(paths);
|
|
83
|
+
return REQUIRED_PACK_FILES.filter((req) => !set.has(req));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Return `[{ label, path }]` for every path matching a suspicious rule (first
|
|
88
|
+
* match wins per path). An empty array means the file list is clean.
|
|
89
|
+
*/
|
|
90
|
+
export function findSuspicious(paths) {
|
|
91
|
+
const out = [];
|
|
92
|
+
for (const path of paths) {
|
|
93
|
+
for (const rule of SUSPICIOUS_RULES) {
|
|
94
|
+
if (rule.re.test(path)) { out.push({ label: rule.label, path }); break; }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The files a freshly-installed package MUST contain to function: the bin(s),
|
|
102
|
+
* the app entrypoints, AND every file the install LIFECYCLE references — notably
|
|
103
|
+
* the `--schema <path>` the `postinstall` runs `prisma generate` against (#466,
|
|
104
|
+
* re1). The smoke test asserts these are present, so a `files`-allowlist
|
|
105
|
+
* regression that drops a postinstall prerequisite fails the preflight instead
|
|
106
|
+
* of silently breaking a real `npm install` of the published tarball.
|
|
107
|
+
*/
|
|
108
|
+
export function requiredInstalledFiles(pkg) {
|
|
109
|
+
const required = ["package.json", "app/server.ts", "app/web/dist/index.html"];
|
|
110
|
+
const binPaths = typeof pkg.bin === "string" ? [pkg.bin] : Object.values(pkg.bin ?? {});
|
|
111
|
+
for (const b of binPaths) if (b) required.push(String(b).replace(/^\.?\//, ""));
|
|
112
|
+
// Every `--schema <path>` referenced by the postinstall lifecycle.
|
|
113
|
+
const postinstall = pkg.scripts?.postinstall ?? "";
|
|
114
|
+
for (const m of postinstall.matchAll(/--schema[= ]+(\S+)/g)) required.push(m[1]);
|
|
115
|
+
return [...new Set(required)];
|
|
116
|
+
}
|