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.
Files changed (145) hide show
  1. package/README.md +4 -0
  2. package/app/lib/agent-command.ts +85 -0
  3. package/app/lib/agent-readiness.ts +133 -0
  4. package/app/lib/apply-schema.ts +55 -0
  5. package/app/lib/bubble-text.ts +160 -0
  6. package/app/lib/cartoon-coach.ts +198 -0
  7. package/app/lib/cartoon-markdown.ts +83 -0
  8. package/app/lib/cartoon-prompt.ts +122 -0
  9. package/app/lib/cartoon-readiness.ts +811 -0
  10. package/app/lib/clean-image-sync.ts +245 -0
  11. package/app/lib/codex-images.ts +152 -0
  12. package/app/lib/cut-asset-diagnostics.ts +120 -0
  13. package/app/lib/cuts.ts +302 -0
  14. package/app/lib/fonts.ts +109 -0
  15. package/app/lib/generate-claude-md.ts +10 -3
  16. package/app/lib/generate-story-instructions.ts +731 -0
  17. package/app/lib/image-asset-validate.ts +123 -0
  18. package/app/lib/lettering-status.ts +133 -0
  19. package/app/lib/overlays.ts +637 -0
  20. package/app/lib/paths.ts +10 -0
  21. package/app/lib/public-title.ts +65 -0
  22. package/app/lib/publish.ts +16 -2
  23. package/app/lib/story-progress.ts +243 -0
  24. package/app/lib/terminal-protocol.ts +16 -0
  25. package/app/lib/terminal-redact.ts +50 -0
  26. package/app/prisma/schema.sql +25 -0
  27. package/app/routes/agent.ts +42 -0
  28. package/app/routes/codex-images.ts +67 -0
  29. package/app/routes/publish.ts +209 -28
  30. package/app/routes/stories.ts +961 -5
  31. package/app/routes/terminal.ts +383 -31
  32. package/app/server.ts +47 -12
  33. package/app/vite.config.ts +6 -0
  34. package/app/web/components/CartoonPreview.tsx +267 -0
  35. package/app/web/components/CartoonPublishPage.tsx +407 -0
  36. package/app/web/components/CartoonPublishPreview.tsx +121 -0
  37. package/app/web/components/CartoonStepGuide.tsx +90 -0
  38. package/app/web/components/CartoonWorkflowNav.tsx +68 -0
  39. package/app/web/components/CodexImportPicker.tsx +230 -0
  40. package/app/web/components/CutListPanel.tsx +1299 -0
  41. package/app/web/components/EpisodesPage.tsx +80 -0
  42. package/app/web/components/FinishEpisodePanel.tsx +151 -0
  43. package/app/web/components/Layout.tsx +7 -4
  44. package/app/web/components/LetteringEditor.tsx +1141 -0
  45. package/app/web/components/PreviewPanel.tsx +1017 -144
  46. package/app/web/components/Settings.tsx +63 -0
  47. package/app/web/components/StoriesPage.tsx +710 -33
  48. package/app/web/components/StoryBrowser.tsx +22 -14
  49. package/app/web/components/StoryInfoPage.tsx +266 -0
  50. package/app/web/components/StoryProgressPanel.tsx +516 -0
  51. package/app/web/components/TerminalPanel.tsx +233 -11
  52. package/app/web/components/WorkflowCoach.tsx +128 -0
  53. package/app/web/components/asset-image.tsx +114 -0
  54. package/app/web/components/asset-test-utils.ts +44 -0
  55. package/app/web/components/export-cut.ts +320 -0
  56. package/app/web/dist/assets/export-cut-nKQ_n2-J.js +1 -0
  57. package/app/web/dist/assets/index-BAZGwVwj.js +143 -0
  58. package/app/web/dist/assets/index-DoXH2OlP.css +32 -0
  59. package/app/web/dist/index.html +2 -2
  60. package/app/web/lib/cartoon-publish-summary.ts +43 -0
  61. package/app/web/lib/codex-import.ts +94 -0
  62. package/app/web/lib/image-compress.ts +53 -0
  63. package/app/web/lib/import-image.ts +58 -0
  64. package/app/web/lib/publish-helpers.ts +385 -0
  65. package/app/web/lib/upload-retry.ts +130 -0
  66. package/app/web/lib/verify-public-title.ts +105 -0
  67. package/app/web/styles.css +9 -0
  68. package/bin/plotlink-ows.js +53 -16
  69. package/bin/startup-plan.cjs +58 -0
  70. package/lib/genres.ts +92 -0
  71. package/package.json +60 -20
  72. package/scripts/gen-schema-sql.mjs +49 -0
  73. package/scripts/package-hygiene.mjs +116 -0
  74. package/scripts/preflight.mjs +173 -0
  75. package/scripts/start-smoke.mjs +128 -0
  76. package/app/node_modules/.prisma/local-client/client.d.ts +0 -1
  77. package/app/node_modules/.prisma/local-client/client.js +0 -5
  78. package/app/node_modules/.prisma/local-client/default.d.ts +0 -1
  79. package/app/node_modules/.prisma/local-client/default.js +0 -5
  80. package/app/node_modules/.prisma/local-client/edge.d.ts +0 -1
  81. package/app/node_modules/.prisma/local-client/edge.js +0 -184
  82. package/app/node_modules/.prisma/local-client/index-browser.js +0 -173
  83. package/app/node_modules/.prisma/local-client/index.d.ts +0 -3304
  84. package/app/node_modules/.prisma/local-client/index.js +0 -207
  85. package/app/node_modules/.prisma/local-client/libquery_engine-darwin-arm64.dylib.node +0 -0
  86. package/app/node_modules/.prisma/local-client/package.json +0 -183
  87. package/app/node_modules/.prisma/local-client/query_engine_bg.js +0 -2
  88. package/app/node_modules/.prisma/local-client/query_engine_bg.wasm +0 -0
  89. package/app/node_modules/.prisma/local-client/runtime/edge-esm.js +0 -35
  90. package/app/node_modules/.prisma/local-client/runtime/edge.js +0 -35
  91. package/app/node_modules/.prisma/local-client/runtime/index-browser.d.ts +0 -370
  92. package/app/node_modules/.prisma/local-client/runtime/index-browser.js +0 -17
  93. package/app/node_modules/.prisma/local-client/runtime/library.d.ts +0 -3982
  94. package/app/node_modules/.prisma/local-client/runtime/library.js +0 -147
  95. package/app/node_modules/.prisma/local-client/runtime/react-native.js +0 -84
  96. package/app/node_modules/.prisma/local-client/runtime/wasm-compiler-edge.js +0 -85
  97. package/app/node_modules/.prisma/local-client/runtime/wasm-engine-edge.js +0 -38
  98. package/app/node_modules/.prisma/local-client/schema.prisma +0 -21
  99. package/app/node_modules/.prisma/local-client/wasm-edge-light-loader.mjs +0 -5
  100. package/app/node_modules/.prisma/local-client/wasm-worker-loader.mjs +0 -5
  101. package/app/node_modules/.prisma/local-client/wasm.d.ts +0 -1
  102. package/app/node_modules/.prisma/local-client/wasm.js +0 -191
  103. package/app/web/dist/assets/index-B-2Ft7Yv.css +0 -32
  104. package/app/web/dist/assets/index-BFw-v-OZ.js +0 -134
  105. package/packages/cli/node_modules/commander/LICENSE +0 -22
  106. package/packages/cli/node_modules/commander/Readme.md +0 -1149
  107. package/packages/cli/node_modules/commander/esm.mjs +0 -16
  108. package/packages/cli/node_modules/commander/index.js +0 -24
  109. package/packages/cli/node_modules/commander/lib/argument.js +0 -149
  110. package/packages/cli/node_modules/commander/lib/command.js +0 -2662
  111. package/packages/cli/node_modules/commander/lib/error.js +0 -39
  112. package/packages/cli/node_modules/commander/lib/help.js +0 -709
  113. package/packages/cli/node_modules/commander/lib/option.js +0 -367
  114. package/packages/cli/node_modules/commander/lib/suggestSimilar.js +0 -101
  115. package/packages/cli/node_modules/commander/package-support.json +0 -16
  116. package/packages/cli/node_modules/commander/package.json +0 -82
  117. package/packages/cli/node_modules/commander/typings/esm.d.mts +0 -3
  118. package/packages/cli/node_modules/commander/typings/index.d.ts +0 -1045
  119. package/packages/cli/node_modules/resolve-from/index.d.ts +0 -31
  120. package/packages/cli/node_modules/resolve-from/index.js +0 -47
  121. package/packages/cli/node_modules/resolve-from/license +0 -9
  122. package/packages/cli/node_modules/resolve-from/package.json +0 -36
  123. package/packages/cli/node_modules/resolve-from/readme.md +0 -72
  124. package/packages/cli/node_modules/tsup/LICENSE +0 -21
  125. package/packages/cli/node_modules/tsup/README.md +0 -75
  126. package/packages/cli/node_modules/tsup/assets/cjs_shims.js +0 -13
  127. package/packages/cli/node_modules/tsup/assets/esm_shims.js +0 -9
  128. package/packages/cli/node_modules/tsup/assets/package.json +0 -3
  129. package/packages/cli/node_modules/tsup/dist/chunk-DI5BO6XE.js +0 -153
  130. package/packages/cli/node_modules/tsup/dist/chunk-JZ25TPTY.js +0 -42
  131. package/packages/cli/node_modules/tsup/dist/chunk-PEEXUWMS.js +0 -6
  132. package/packages/cli/node_modules/tsup/dist/chunk-TWFEYLU4.js +0 -352
  133. package/packages/cli/node_modules/tsup/dist/chunk-VGC3FXLU.js +0 -203
  134. package/packages/cli/node_modules/tsup/dist/cli-default.js +0 -12
  135. package/packages/cli/node_modules/tsup/dist/cli-main.js +0 -8
  136. package/packages/cli/node_modules/tsup/dist/cli-node.js +0 -14
  137. package/packages/cli/node_modules/tsup/dist/index.d.ts +0 -511
  138. package/packages/cli/node_modules/tsup/dist/index.js +0 -1711
  139. package/packages/cli/node_modules/tsup/dist/rollup.js +0 -6949
  140. package/packages/cli/node_modules/tsup/package.json +0 -99
  141. package/packages/cli/node_modules/tsup/schema.json +0 -362
  142. package/public/screenshot-1.png +0 -0
  143. package/public/screenshot-2.png +0 -0
  144. package/public/screenshot-3.png +0 -0
  145. package/scripts/e2e-verify.ts +0 -1100
@@ -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
- // Ensure deps installed
215
- if (!fs.existsSync(path.join(PROJECT_DIR, "node_modules"))) {
216
- log("Installing dependencies...");
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
- // Ensure frontend is built
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
- setTimeout(() => {
231
- try {
232
- const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
233
- execSync(`${openCmd} http://localhost:${port}`, { stdio: "ignore" });
234
- } catch { /* ignore */ }
235
- }, 2000);
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.0.32",
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": "./bin/plotlink-ows.js"
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
- "@aws-sdk/client-s3": "^3.1009.0",
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.10",
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
- "vite": "^6.4.1",
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.1.6",
114
+ "eslint-config-next": "16.2.6",
83
115
  "jsdom": "^27.0.1",
84
- "next": "16.1.6",
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
+ }