gutterpress 0.0.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.
Files changed (230) hide show
  1. package/README.md +422 -0
  2. package/dist/CGATS21_CRPC1-g0e3k7kr.icc +0 -0
  3. package/dist/api/index.d.ts +76 -0
  4. package/dist/api/index.js +272 -0
  5. package/dist/assets/preview/scripts/pagedjs-bridge.d.ts +1 -0
  6. package/dist/assets/preview/scripts/pagedjs-interface.d.ts +1 -0
  7. package/dist/assets/preview/scripts/preview-shell.d.ts +1 -0
  8. package/dist/audit-1jrwwacz.js +66 -0
  9. package/dist/build-5s92kgpc.js +77 -0
  10. package/dist/chapter-01-6fn9pvd0.md +31 -0
  11. package/dist/chapter-01-7jwqg1ct.md +20 -0
  12. package/dist/chapter-01-jj8ftn4c.md +29 -0
  13. package/dist/chapter-01-rkpyvjna.md +26 -0
  14. package/dist/checks/asset/approved-fonts.d.ts +3 -0
  15. package/dist/checks/asset/extensions.d.ts +41 -0
  16. package/dist/checks/asset/font-license.d.ts +3 -0
  17. package/dist/checks/asset/image-alpha.d.ts +3 -0
  18. package/dist/checks/asset/image-color-space.d.ts +3 -0
  19. package/dist/checks/asset/image-file-size.d.ts +3 -0
  20. package/dist/checks/asset/image-resolution.d.ts +3 -0
  21. package/dist/checks/asset/image-tac.d.ts +3 -0
  22. package/dist/checks/asset/index.d.ts +7 -0
  23. package/dist/checks/formatter.d.ts +3 -0
  24. package/dist/checks/heuristic/index.d.ts +4 -0
  25. package/dist/checks/heuristic/layer-count.d.ts +3 -0
  26. package/dist/checks/heuristic/placement-variance.d.ts +3 -0
  27. package/dist/checks/heuristic/section-density.d.ts +3 -0
  28. package/dist/checks/heuristic/text-density.d.ts +3 -0
  29. package/dist/checks/pdf/bleed.d.ts +3 -0
  30. package/dist/checks/pdf/bookmarks.d.ts +3 -0
  31. package/dist/checks/pdf/color-spaces.d.ts +3 -0
  32. package/dist/checks/pdf/cross-refs.d.ts +3 -0
  33. package/dist/checks/pdf/embedded-fonts.d.ts +3 -0
  34. package/dist/checks/pdf/image-resolution.d.ts +3 -0
  35. package/dist/checks/pdf/index.d.ts +15 -0
  36. package/dist/checks/pdf/ink-coverage.d.ts +3 -0
  37. package/dist/checks/pdf/page-labels.d.ts +3 -0
  38. package/dist/checks/pdf/page-size.d.ts +3 -0
  39. package/dist/checks/pdf/pdfx-markers.d.ts +3 -0
  40. package/dist/checks/pdf/pdfx-metadata.d.ts +3 -0
  41. package/dist/checks/pdf/pdfx-structure.d.ts +6 -0
  42. package/dist/checks/pdf/qpdf-structure.d.ts +3 -0
  43. package/dist/checks/pdf/rasterized-pages.d.ts +3 -0
  44. package/dist/checks/pdf/toc-links.d.ts +3 -0
  45. package/dist/checks/pdf/transparency.d.ts +3 -0
  46. package/dist/checks/policy.d.ts +61 -0
  47. package/dist/checks/register-builtins.d.ts +17 -0
  48. package/dist/checks/registry.d.ts +40 -0
  49. package/dist/checks/runner.d.ts +25 -0
  50. package/dist/checks/source/accessibility-alt-text.d.ts +3 -0
  51. package/dist/checks/source/accessibility-heading-order.d.ts +3 -0
  52. package/dist/checks/source/config-file.d.ts +8 -0
  53. package/dist/checks/source/htmlhint.d.ts +3 -0
  54. package/dist/checks/source/index.d.ts +6 -0
  55. package/dist/checks/source/local-refs.d.ts +3 -0
  56. package/dist/checks/source/markdownlint.d.ts +3 -0
  57. package/dist/checks/source/stylelint.d.ts +3 -0
  58. package/dist/checks/tool-check.d.ts +23 -0
  59. package/dist/checks/types.d.ts +52 -0
  60. package/dist/cli-1tyj4bk3.js +124 -0
  61. package/dist/cli-37x76zdn.js +4 -0
  62. package/dist/cli-506tg37g.js +26 -0
  63. package/dist/cli-5bxx0c4y.js +464 -0
  64. package/dist/cli-7ykx7pp0.js +12378 -0
  65. package/dist/cli-h7phg3ka.js +746 -0
  66. package/dist/cli-hvqjs342.js +284 -0
  67. package/dist/cli-yja077f6.js +92 -0
  68. package/dist/cli.js +157 -0
  69. package/dist/constants.d.ts +17 -0
  70. package/dist/doctor-25zjt8dz.js +57 -0
  71. package/dist/favicon-wkbm9cjn.ico +0 -0
  72. package/dist/git-http-yrb4ag6z.js +17 -0
  73. package/dist/index-1tyj4bk3.js +124 -0
  74. package/dist/index-37x76zdn.js +4 -0
  75. package/dist/index-3h3fef7r.js +15218 -0
  76. package/dist/index-5bxx0c4y.js +464 -0
  77. package/dist/index-yja077f6.js +92 -0
  78. package/dist/index.d.ts +32 -0
  79. package/dist/index.js +310 -0
  80. package/dist/lib/app-heartbeat.d.ts +102 -0
  81. package/dist/lib/asset-inline.d.ts +73 -0
  82. package/dist/lib/browser-pool.d.ts +17 -0
  83. package/dist/lib/build-error.d.ts +52 -0
  84. package/dist/lib/build-fingerprint.d.ts +15 -0
  85. package/dist/lib/build-preflight.d.ts +43 -0
  86. package/dist/lib/build-runner.d.ts +161 -0
  87. package/dist/lib/build-staging.d.ts +64 -0
  88. package/dist/lib/chromium.d.ts +20 -0
  89. package/dist/lib/cli-args.d.ts +45 -0
  90. package/dist/lib/desktop.d.ts +14 -0
  91. package/dist/lib/diagnostics.d.ts +64 -0
  92. package/dist/lib/embedded-assets.d.ts +29 -0
  93. package/dist/lib/exec.d.ts +84 -0
  94. package/dist/lib/fetch-timeout.d.ts +41 -0
  95. package/dist/lib/ghostscript.d.ts +48 -0
  96. package/dist/lib/host-policy.d.ts +75 -0
  97. package/dist/lib/image-inspect.d.ts +32 -0
  98. package/dist/lib/install-hints.d.ts +21 -0
  99. package/dist/lib/lint-runner.d.ts +10 -0
  100. package/dist/lib/manifest-config.d.ts +31 -0
  101. package/dist/lib/manifest-doc.d.ts +25 -0
  102. package/dist/lib/manifest.d.ts +41 -0
  103. package/dist/lib/markdown/assemble.d.ts +78 -0
  104. package/dist/lib/markdown/chapter-id.d.ts +24 -0
  105. package/dist/lib/markdown/images.d.ts +34 -0
  106. package/dist/lib/markdown/index.d.ts +78 -0
  107. package/dist/lib/markdown/markdown-it-paged.d.ts +30 -0
  108. package/dist/lib/markdown/plugins.d.ts +69 -0
  109. package/dist/lib/markdown/renderer.d.ts +111 -0
  110. package/dist/lib/npm-plugin-installer.d.ts +33 -0
  111. package/dist/lib/open-path.d.ts +5 -0
  112. package/dist/lib/output-paths.d.ts +23 -0
  113. package/dist/lib/pagedjs-marker.d.ts +42 -0
  114. package/dist/lib/pagedjs.d.ts +26 -0
  115. package/dist/lib/pagination.d.ts +169 -0
  116. package/dist/lib/pdf-inspect.d.ts +123 -0
  117. package/dist/lib/pdf-parse.d.ts +57 -0
  118. package/dist/lib/plugin-manager.d.ts +108 -0
  119. package/dist/lib/plugin-vendor.d.ts +107 -0
  120. package/dist/lib/presets.d.ts +45 -0
  121. package/dist/lib/printsafe.d.ts +17 -0
  122. package/dist/lib/project-scaffold.d.ts +116 -0
  123. package/dist/lib/project-source.d.ts +140 -0
  124. package/dist/lib/project-templates.d.ts +49 -0
  125. package/dist/lib/publish/butler.d.ts +10 -0
  126. package/dist/lib/publish/command-runner.d.ts +18 -0
  127. package/dist/lib/publish/connect.d.ts +23 -0
  128. package/dist/lib/publish/manifest-publish.d.ts +9 -0
  129. package/dist/lib/publish/providers/azure-swa.d.ts +3 -0
  130. package/dist/lib/publish/providers/drivethrurpg.d.ts +10 -0
  131. package/dist/lib/publish/providers/guided.d.ts +3 -0
  132. package/dist/lib/publish/providers/itch.d.ts +5 -0
  133. package/dist/lib/publish/providers/kdp.d.ts +9 -0
  134. package/dist/lib/publish/providers/shopify.d.ts +5 -0
  135. package/dist/lib/publish/registry.d.ts +5 -0
  136. package/dist/lib/publish/run-publish.d.ts +30 -0
  137. package/dist/lib/publish/selections.d.ts +25 -0
  138. package/dist/lib/publish/types.d.ts +258 -0
  139. package/dist/lib/remote-auth/clone.d.ts +87 -0
  140. package/dist/lib/remote-auth/conflict-resolution.d.ts +29 -0
  141. package/dist/lib/remote-auth/diagnose.d.ts +77 -0
  142. package/dist/lib/remote-auth/generic-auth.d.ts +97 -0
  143. package/dist/lib/remote-auth/git-http.d.ts +85 -0
  144. package/dist/lib/remote-auth/github-auth.d.ts +69 -0
  145. package/dist/lib/remote-auth/github-repos.d.ts +64 -0
  146. package/dist/lib/remote-auth/operation-log.d.ts +66 -0
  147. package/dist/lib/remote-auth/recovery/abort-interrupted-operation.d.ts +103 -0
  148. package/dist/lib/remote-auth/recovery/backup.d.ts +113 -0
  149. package/dist/lib/remote-auth/recovery/classify.d.ts +109 -0
  150. package/dist/lib/remote-auth/recovery/context.d.ts +47 -0
  151. package/dist/lib/remote-auth/recovery/dispatch.d.ts +33 -0
  152. package/dist/lib/remote-auth/recovery/failsafe.d.ts +33 -0
  153. package/dist/lib/remote-auth/recovery/inspect.d.ts +74 -0
  154. package/dist/lib/remote-auth/recovery/manual-guidance.d.ts +28 -0
  155. package/dist/lib/remote-auth/recovery/outcome-mapping.d.ts +58 -0
  156. package/dist/lib/remote-auth/recovery/policy.d.ts +47 -0
  157. package/dist/lib/remote-auth/recovery/recover-auth.d.ts +42 -0
  158. package/dist/lib/remote-auth/recovery/recover-binary-conflict.d.ts +37 -0
  159. package/dist/lib/remote-auth/recovery/recover-corrupt-index.d.ts +40 -0
  160. package/dist/lib/remote-auth/recovery/recover-detached-head.d.ts +70 -0
  161. package/dist/lib/remote-auth/recovery/recover-interrupted-cherry-pick.d.ts +23 -0
  162. package/dist/lib/remote-auth/recovery/recover-interrupted-merge.d.ts +28 -0
  163. package/dist/lib/remote-auth/recovery/recover-interrupted-rebase.d.ts +39 -0
  164. package/dist/lib/remote-auth/recovery/recover-merge-conflict.d.ts +34 -0
  165. package/dist/lib/remote-auth/recovery/recover-missing-git-dir.d.ts +37 -0
  166. package/dist/lib/remote-auth/recovery/recover-missing-objects.d.ts +56 -0
  167. package/dist/lib/remote-auth/recovery/recover-network.d.ts +34 -0
  168. package/dist/lib/remote-auth/recovery/recover-non-fast-forward.d.ts +27 -0
  169. package/dist/lib/remote-auth/recovery/recover-stale-lock.d.ts +76 -0
  170. package/dist/lib/remote-auth/recovery/recover-unrelated-histories.d.ts +44 -0
  171. package/dist/lib/remote-auth/recovery/recover-wrong-remote.d.ts +35 -0
  172. package/dist/lib/remote-auth/recovery/types.d.ts +235 -0
  173. package/dist/lib/remote-auth/resolution-plan.d.ts +64 -0
  174. package/dist/lib/remote-auth/sync-messages.d.ts +26 -0
  175. package/dist/lib/remote-auth/sync-types.d.ts +225 -0
  176. package/dist/lib/remote-auth/sync.d.ts +45 -0
  177. package/dist/lib/remote-auth/test-access.d.ts +47 -0
  178. package/dist/lib/remote-auth/test-support/git-http-server.d.ts +71 -0
  179. package/dist/lib/remote-auth/token-store.d.ts +95 -0
  180. package/dist/lib/remote-auth/transport.d.ts +105 -0
  181. package/dist/lib/slug.d.ts +28 -0
  182. package/dist/lib/snippets.d.ts +39 -0
  183. package/dist/lib/source-provider.d.ts +244 -0
  184. package/dist/lib/static-serve.d.ts +54 -0
  185. package/dist/lib/style-resolver.d.ts +30 -0
  186. package/dist/lib/theme-import.d.ts +70 -0
  187. package/dist/lib/theme-manager.d.ts +128 -0
  188. package/dist/lib/tool-probe.d.ts +37 -0
  189. package/dist/lib/validation-exec.d.ts +47 -0
  190. package/dist/lib/validation-profile.d.ts +18 -0
  191. package/dist/lib/version.d.ts +9 -0
  192. package/dist/lint-f63nxjfj.js +65 -0
  193. package/dist/manifest-n1gh3qw5.yaml +9 -0
  194. package/dist/manifest-ryyg71nf.yaml +9 -0
  195. package/dist/manifest.schema-rn1dpdpn.json +564 -0
  196. package/dist/new-7b08a42h.js +105 -0
  197. package/dist/paged.polyfill-n95pbxfn.js +33288 -0
  198. package/dist/pagedjs-bridge-vn4hk9fx.js +111 -0
  199. package/dist/pagedjs-interface-80g3xa8p.js +498 -0
  200. package/dist/platform.d.ts +100 -0
  201. package/dist/plugin-wf2cbmwc.js +129 -0
  202. package/dist/preflight-tg4mpz1h.js +222 -0
  203. package/dist/preview/file-watcher.d.ts +174 -0
  204. package/dist/preview/http-server.d.ts +45 -0
  205. package/dist/preview/lifecycle.d.ts +67 -0
  206. package/dist/preview/server-context.d.ts +36 -0
  207. package/dist/preview-h2bwbb2x.js +102 -0
  208. package/dist/preview-shell-rghtfmc8.js +155 -0
  209. package/dist/project-source-0tg0r6vm.js +17 -0
  210. package/dist/publish-ztaet53r.js +233 -0
  211. package/dist/render.d.ts +20 -0
  212. package/dist/render.js +682 -0
  213. package/dist/repair-jkp4n3j8.js +171 -0
  214. package/dist/schema/manifest.types.d.ts +218 -0
  215. package/dist/server.d.ts +34 -0
  216. package/dist/source-provider-hp96b5jf.js +46 -0
  217. package/dist/test-helpers/testkit.d.ts +91 -0
  218. package/dist/theme-0yzvthe8.css +65 -0
  219. package/dist/theme-570zmh2t.css +58 -0
  220. package/dist/theme-6a2fngc8.json +6 -0
  221. package/dist/theme-cd6efzjw.json +6 -0
  222. package/dist/theme-nya4nqh6.css +70 -0
  223. package/dist/theme-tqfypc1w.json +6 -0
  224. package/dist/theme-xt0hjb1b.json +6 -0
  225. package/dist/theme-zz2ktzqs.css +60 -0
  226. package/dist/types.d.ts +31 -0
  227. package/dist/utils/file-utils.d.ts +53 -0
  228. package/dist/utils/logger.d.ts +59 -0
  229. package/dist/validate-k2drs19j.js +99 -0
  230. package/package.json +102 -0
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Publish provider contract (#35).
3
+ *
4
+ * A publish provider pushes a built artifact (PDF or static-site HTML) to a
5
+ * distribution platform — or, where the platform has no upload API
6
+ * (DriveThruRPG, Amazon KDP), stages a validated upload package and hands the
7
+ * author a checklist + the platform's upload URL ("guided" publishing).
8
+ *
9
+ * Architecture mirrors the remote-auth subsystem (ADR 0006):
10
+ * - The lib NEVER touches OS keychains, the network, or child processes
11
+ * directly through ambient globals — hosts inject a {@link TokenStore},
12
+ * and tests inject {@link PublishDeps.fetch} / {@link PublishDeps.runCommand}.
13
+ * - Credentials are keyed by provider host (e.g. "itch.io") in the same
14
+ * TokenStore the Git remote features use: the CLI's 0600 file store, the
15
+ * desktop's safeStorage-backed store.
16
+ *
17
+ * SECURITY INVARIANT: token values never appear in logs, error messages,
18
+ * spawned argv (process lists are world-readable — pass secrets via env), or
19
+ * host responses.
20
+ */
21
+ import type { HostCredential, TokenStore } from "../remote-auth/token-store.ts";
22
+ /**
23
+ * One author-editable, NON-SECRET settings field (stored in the manifest's
24
+ * `publish.<id>` section). Declared by each provider so settings UIs are
25
+ * fully data-driven — a new provider brings its own fields, no UI edits.
26
+ */
27
+ export interface PublishConfigField {
28
+ /** Manifest key under `publish.<id>` (e.g. "target"). */
29
+ key: string;
30
+ /** Author-facing label ("Project (user/game)"). */
31
+ label: string;
32
+ placeholder?: string;
33
+ }
34
+ /** Stable provider identifiers: the `--provider` values AND the manifest
35
+ * `publish.<id>` keys — one spelling everywhere. */
36
+ export type PublishProviderId = "itch" | "drivethrurpg" | "kdp" | "azure-swa" | "shopify";
37
+ /**
38
+ * How the provider integrates:
39
+ * - "api" — real programmatic upload (itch.io, Azure SWA, Shopify)
40
+ * - "guided" — no upload API exists; we validate + stage a package and open
41
+ * the platform's upload page with a checklist (DTRPG, KDP)
42
+ */
43
+ export type PublishProviderKind = "api" | "guided";
44
+ /** Which build output the provider consumes. */
45
+ export type PublishArtifactFormat = "pdf" | "html";
46
+ /** Static, UI-facing description of a provider. */
47
+ export interface PublishProviderInfo {
48
+ id: PublishProviderId;
49
+ /** Human name ("itch.io"). */
50
+ label: string;
51
+ kind: PublishProviderKind;
52
+ /** The artifact format this provider publishes. */
53
+ format: PublishArtifactFormat;
54
+ /** One-line author-facing description of what publishing here does. */
55
+ description: string;
56
+ /** The provider's author-editable manifest settings. */
57
+ configFields: PublishConfigField[];
58
+ /**
59
+ * Credential requirements. `host` keys the TokenStore entry; guided
60
+ * providers need no credential at all. `envVar` is the CI escape hatch —
61
+ * when set in the environment it wins over the stored credential.
62
+ */
63
+ credential: {
64
+ required: boolean;
65
+ host: string;
66
+ /** Environment variable honoured for headless/CI use. */
67
+ envVar?: string;
68
+ /** Where the author creates the key (shown in connect UIs). */
69
+ tokenUrl?: string;
70
+ /** Author-facing hint ("Paste an API key from …"). */
71
+ hint?: string;
72
+ };
73
+ }
74
+ /** The project being published (resolved from the manifest by the orchestrator). */
75
+ export interface PublishProject {
76
+ projectDir: string;
77
+ title: string;
78
+ authors: string[];
79
+ }
80
+ /** The built artifact to publish. For "html" this is the export directory. */
81
+ export interface PublishArtifact {
82
+ path: string;
83
+ format: PublishArtifactFormat;
84
+ }
85
+ /** Result of a spawned command (see {@link CommandRunner}). */
86
+ export interface CommandResult {
87
+ code: number;
88
+ stdout: string;
89
+ stderr: string;
90
+ }
91
+ /**
92
+ * Injectable child-process seam. The default implementation wraps
93
+ * `node:child_process.spawn`; tests substitute a fake. Secrets must only ever
94
+ * travel through `env`, never `args`.
95
+ */
96
+ export type CommandRunner = (cmd: string, args: string[], options?: {
97
+ cwd?: string;
98
+ env?: Record<string, string | undefined>;
99
+ /** Called once per output line — drives live progress logs. */
100
+ onOutput?: (line: string) => void;
101
+ /**
102
+ * Idle-kill budget in ms (audit B2). If the child produces no output and
103
+ * has not exited within this window, it is SIGKILL'd and the run rejects
104
+ * with a timeout error, so a stalled upload can't hang publish forever.
105
+ * Omitted / undefined = the runner's default (defaultCommandRunner applies
106
+ * PUBLISH_IDLE_TIMEOUT_MS); 0 = explicitly no timeout.
107
+ */
108
+ timeoutMs?: number;
109
+ }) => Promise<CommandResult>;
110
+ /**
111
+ * Host-injected dependencies for every provider call. Everything with a side
112
+ * effect enters through here, which is what makes providers unit-testable.
113
+ */
114
+ export interface PublishDeps {
115
+ tokenStore: TokenStore;
116
+ /** HTTP seam (itch.io check, Shopify, butler download). Defaults to global fetch. */
117
+ fetch?: typeof globalThis.fetch;
118
+ /** Child-process seam (butler, swa). Defaults to node spawn. */
119
+ runCommand?: CommandRunner;
120
+ /** Environment (CI credential overrides). Defaults to process.env. */
121
+ env?: Record<string, string | undefined>;
122
+ /** User config dir override (butler tool cache). Defaults to defaultConfigDir(). */
123
+ configDir?: string;
124
+ /** Live progress line sink (CLI logger / desktop progress log). */
125
+ onProgress?: (message: string) => void;
126
+ /**
127
+ * The selected NAMED credential (account label) for this operation, when the
128
+ * user has more than one saved credential for the provider. The store is
129
+ * keyed by a compound `<host>#<account>` for named accounts (see
130
+ * {@link publishCredentialKey}); empty/undefined resolves the legacy
131
+ * bare-host entry, so existing single-credential setups keep working. The
132
+ * orchestrator sets this from the effective selection (book manifest →
133
+ * project/global default); providers read it transparently via
134
+ * {@link resolvePublishCredential}.
135
+ */
136
+ credentialAccount?: string;
137
+ }
138
+ /**
139
+ * The TokenStore key for a publishing provider credential. Named accounts use a
140
+ * compound `<host>#<account>` key so MULTIPLE credentials can coexist under one
141
+ * provider host (two itch.io accounts, two Shopify stores) in the same flat
142
+ * store — WITHOUT changing the TokenStore contract or disturbing git-sync,
143
+ * which keeps using bare host keys. An empty/absent account is the bare host
144
+ * (the legacy single-credential entry). The account segment is trimmed; host
145
+ * normalisation (lower-casing) is left to the store's `normalizeHost`.
146
+ */
147
+ export declare function publishCredentialKey(host: string, account?: string | null): string;
148
+ /** Everything a provider method needs for one operation. */
149
+ export interface PublishRequest {
150
+ project: PublishProject;
151
+ /** The provider's manifest `publish.<id>` section (non-secret config). */
152
+ config: Record<string, unknown>;
153
+ artifact: PublishArtifact;
154
+ deps: PublishDeps;
155
+ }
156
+ /** A single preflight finding. `error` blocks publishing; others inform. */
157
+ export interface PreflightIssue {
158
+ severity: "error" | "warning" | "info";
159
+ /** Stable machine id, e.g. "itch/target-missing". */
160
+ id: string;
161
+ message: string;
162
+ }
163
+ /** Redacted authentication status — NEVER carries the token value. */
164
+ export interface PublishAuthStatus {
165
+ ok: boolean;
166
+ /** Where the accepted credential came from. */
167
+ source?: "env" | "store";
168
+ /** Friendly failure guidance when `ok` is false. */
169
+ message?: string;
170
+ }
171
+ /** A product/listing already on the platform (for update flows). */
172
+ export interface PublishProduct {
173
+ id: string;
174
+ title: string;
175
+ url?: string;
176
+ }
177
+ /** Listing metadata for {@link PublishProvider.updateListing}. */
178
+ export interface PublishListingMetadata {
179
+ title?: string;
180
+ description?: string;
181
+ }
182
+ /** What a publish produced. */
183
+ export type PublishOutcome = {
184
+ kind: "published";
185
+ /** Where the published work lives (page URL / deploy URL). */
186
+ url?: string;
187
+ /** Human summary ("Pushed build 42 to dimm-city/ops-manual:pdf"). */
188
+ detail?: string;
189
+ /** Remaining manual steps, when the API covers only part of the flow. */
190
+ followUp?: string[];
191
+ } | {
192
+ kind: "guided";
193
+ /** Directory containing the staged upload package. */
194
+ packageDir: string;
195
+ /** The platform upload page to open. */
196
+ openUrl: string;
197
+ /** Manual steps the author completes on the platform. */
198
+ checklist: string[];
199
+ detail?: string;
200
+ };
201
+ /**
202
+ * The publish provider contract (#35). `authenticate`/`listProducts`/
203
+ * `upload`/`updateListing` per the issue; guided providers implement
204
+ * `upload` as package staging and omit the listing operations.
205
+ */
206
+ export interface PublishProvider {
207
+ readonly info: PublishProviderInfo;
208
+ /**
209
+ * Verify that a usable credential exists (env var or token store) and is
210
+ * accepted by the platform. Guided providers resolve `{ ok: true }`.
211
+ */
212
+ authenticate(req: PublishRequest): Promise<PublishAuthStatus>;
213
+ /** Provider-specific checks run before upload (config, artifact, specs). */
214
+ preflight(req: PublishRequest): Promise<PreflightIssue[]>;
215
+ /** Publish the artifact (or stage the guided package). */
216
+ upload(req: PublishRequest): Promise<PublishOutcome>;
217
+ /** Existing products/listings, for update flows. API providers only. */
218
+ listProducts?(req: PublishRequest): Promise<PublishProduct[]>;
219
+ /** Update an existing listing's metadata. API providers only. */
220
+ updateListing?(req: PublishRequest, productId: string, metadata: PublishListingMetadata): Promise<PublishProduct>;
221
+ }
222
+ /**
223
+ * Resolve the credential for a provider: the CI env var wins, then the token
224
+ * store. Returns null when neither is present. The returned credential is a
225
+ * secret — callers must never log or echo it.
226
+ */
227
+ export declare function resolvePublishCredential(info: PublishProviderInfo, deps: PublishDeps, account?: string | undefined): Promise<{
228
+ credential: HostCredential;
229
+ source: "env" | "store";
230
+ } | null>;
231
+ /**
232
+ * Redacted connection status for a provider — the ONE definition of
233
+ * "connected" (env var or stored key) shared by the CLI's `--list` and the
234
+ * desktop's provider cards, so the two surfaces can never disagree.
235
+ */
236
+ export declare function publishConnectionStatus(info: PublishProviderInfo, deps: PublishDeps, account?: string | undefined): Promise<{
237
+ connected: boolean;
238
+ source?: "env" | "store";
239
+ }>;
240
+ /** A saved credential for a provider, REDACTED (no token) — for a picker. */
241
+ export interface PublishSavedAccount {
242
+ /**
243
+ * The account label (the compound-key `#<account>` segment). Empty string is
244
+ * the default (unnamed / bare-host) credential.
245
+ */
246
+ account: string;
247
+ /** Display name for the picker (the credential's label). */
248
+ label: string;
249
+ createdAt: number;
250
+ }
251
+ /**
252
+ * The saved credentials for a provider, redacted — one per named account plus
253
+ * the default (unnamed) entry, when present. Recovered from the store's flat
254
+ * {@link TokenStore.list} by matching the provider host; the account label is
255
+ * carried in each credential's `username` (see `connect.ts`), empty for the
256
+ * default entry. Never returns token values.
257
+ */
258
+ export declare function listPublishAccounts(info: PublishProviderInfo, deps: PublishDeps): Promise<PublishSavedAccount[]>;
@@ -0,0 +1,87 @@
1
+ import httpNode from "isomorphic-git/http/node";
2
+ import { type HostCredential, type TokenStore } from "./token-store.ts";
3
+ /** Coarse clone progress for host UIs. */
4
+ export interface CloneProgressEvent {
5
+ /** Human-readable phase from the git transport (e.g. "Receiving objects"). */
6
+ phase: string;
7
+ loaded: number;
8
+ total?: number;
9
+ }
10
+ /**
11
+ * Provider provenance recorded next to a cloned project (ADR 0006 D4):
12
+ * metadata for the repo picker / re-auth UX, never consulted by the
13
+ * editing/preview/build paths.
14
+ */
15
+ export interface ProjectProvenance {
16
+ provider: "github";
17
+ owner: string;
18
+ repo: string;
19
+ /**
20
+ * Legacy GitHub-App installation id. New clones never write it (the OAuth
21
+ * App model has no installations — ADR 0006 D1 amendment 2026-06-10); kept
22
+ * optional so provenance files written by 0.4.x betas still parse.
23
+ */
24
+ installationId?: string;
25
+ }
26
+ export interface CloneRepositoryOptions {
27
+ /** HTTPS clone URL. Tokens embedded in the URL are stripped (D7). */
28
+ url: string;
29
+ /** Absolute destination directory (created; must be absent or empty). */
30
+ dir: string;
31
+ /** Credential used for transport auth, if the remote needs one. */
32
+ credential?: HostCredential;
33
+ /** Branch to check out; the remote's default branch when omitted. */
34
+ branch?: string;
35
+ /**
36
+ * History depth. Defaults to a FULL clone (`undefined`).
37
+ *
38
+ * WHY full and not the ADR's `depth: 1` preference: the shallow-clone spike
39
+ * (clone.test.ts, "shallow clone spike") showed isomorphic-git CAN shallow
40
+ * clone and the existing source-provider ops (listHistory, snapshot) keep
41
+ * working on the shallow result — but `git.log` stops silently at the
42
+ * shallow boundary, so View History (#13) would show a single commit with no
43
+ * indication more history exists, and there is no deepen-on-demand surface
44
+ * yet. Until that lands, full clone is the honest default; pass `depth` to
45
+ * opt in to shallow (the plumbing is tested and works).
46
+ */
47
+ depth?: number;
48
+ /** Coarse progress callback for host UIs. */
49
+ onProgress?: (event: CloneProgressEvent) => void;
50
+ /** When provided, credentials embedded in `url` are migrated into it (D7). */
51
+ tokenStore?: TokenStore;
52
+ /** Provider provenance to record beside the clone (ADR 0006 D4). */
53
+ provenance?: ProjectProvenance;
54
+ /**
55
+ * Injectable git HTTP transport for tests (isomorphic-git's `http` client
56
+ * shape). Defaults to isomorphic-git's node client.
57
+ */
58
+ httpClient?: typeof httpNode;
59
+ }
60
+ export interface CloneRepositoryResult {
61
+ /** The directory the project was cloned into (same as options.dir). */
62
+ projectDir: string;
63
+ /** The checked-out branch. */
64
+ branch?: string;
65
+ }
66
+ export declare function provenancePath(projectDir: string): string;
67
+ /** Read recorded provider provenance for a project, if any. Never throws. */
68
+ export declare function readProjectProvenance(projectDir: string): Promise<ProjectProvenance | null>;
69
+ /**
70
+ * Reduce a (possibly renderer-/user-supplied) project folder name to a single
71
+ * safe path segment: path separators become dashes and leading dots are
72
+ * stripped, so `path.join(parentDir, sanitizeCloneFolderName(name))` can never
73
+ * escape `parentDir` (no `..` segments, no absolute paths, no hidden dirs).
74
+ * Returns `""` when nothing usable remains — callers must reject that.
75
+ */
76
+ export declare function sanitizeCloneFolderName(name: string): string;
77
+ /**
78
+ * Clone a remote repository over smart HTTPS into `dir` (ADR 0006 D2).
79
+ *
80
+ * - HTTPS only (isomorphic-git has no SSH — ADR 0006 D6).
81
+ * - `singleBranch` always; `depth` opts into shallow (full by default — see
82
+ * the WHY on {@link CloneRepositoryOptions.depth}).
83
+ * - Tokens embedded in the URL are stripped and (when a `tokenStore` is
84
+ * given) migrated into the store; the token never reaches logs (D7).
85
+ * - Serialized through the same per-repo lock as snapshot/restore.
86
+ */
87
+ export declare function cloneRepository(options: CloneRepositoryOptions): Promise<CloneRepositoryResult>;
@@ -0,0 +1,29 @@
1
+ import type { ResolveConflictsOptions, SyncOutcome } from "./sync-types.ts";
2
+ /**
3
+ * `chapter-01.md` → `chapter-01 (online copy).md` (next to the original).
4
+ * `counter` ≥ 2 produces `chapter-01 (online copy 2).md`, … — used to avoid
5
+ * clobbering a pre-existing file with the same name.
6
+ */
7
+ export declare function onlineCopyPath(filepath: string, counter?: number): string;
8
+ /**
9
+ * Apply the author's per-file choices and sync the combined result
10
+ * (ADR 0006 D5). The merge commit has TWO PARENTS — the local branch tip and
11
+ * the online tip — so both histories remain intact and View History stays
12
+ * honest about what was combined.
13
+ *
14
+ * How each choice is applied WITHOUT conflict markers:
15
+ *
16
+ * - Files edited in both copies are settled inside the merge itself by a
17
+ * custom `mergeDriver` that returns the chosen side's content ("Keep both
18
+ * copies" keeps mine and writes the online version to
19
+ * `<name> (online copy)<ext>` beforehand, committed on the local side so it
20
+ * is part of the merge). Undecided files auto-merge with the same diff3
21
+ * algorithm a plain merge uses.
22
+ * - Delete-involved conflicts never reach a merge driver, so they are settled
23
+ * by equalizing the local side BEFORE the merge (making both sides agree so
24
+ * the merge is clean) and, when the author chose the now-removed side, a
25
+ * small follow-up commit AFTER the merge restores their choice. The merge
26
+ * commit still carries both parents; the around-commits are visible,
27
+ * honestly labeled steps in View History.
28
+ */
29
+ export declare function resolveConflicts(options: ResolveConflictsOptions): Promise<SyncOutcome>;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Project remote diagnostics (#14, ADR 0006 D7).
3
+ *
4
+ * Pure-lib replacement for the issue's original shell diagnostics
5
+ * (`git status` / `git remote -v` / `git branch --show-current` — all
6
+ * forbidden by CLAUDE.md §7): folder classification reuses
7
+ * `detectProjectSource`, remote/branch come from the classification, and the
8
+ * stored-credential check goes through an injected {@link TokenStore}.
9
+ * Everything here is local reads — the only network diagnostic is the
10
+ * explicit, user-initiated `testRemoteAccess` probe (separate module).
11
+ *
12
+ * SECURITY INVARIANT (D7): `remoteUrl` is sanitized — credentials embedded in
13
+ * the clone URL never appear in the diagnosis (which the UI displays).
14
+ */
15
+ import { type ProjectSource } from "../project-source.ts";
16
+ import { type TokenStore } from "./token-store.ts";
17
+ /** How the project's remote is addressed. "https" covers smart HTTP(S). */
18
+ export type RemoteProtocol = "https" | "ssh" | "none";
19
+ /** Recognized forge families, for per-provider guidance copy. */
20
+ export type ForgeKind = "github" | "gitea" | "forgejo" | "gitlab" | "bitbucket" | "azure" | "generic";
21
+ /**
22
+ * Machine-readable next-step hint the UI maps to author copy:
23
+ * - `local-only` — no remote; everything is on this computer.
24
+ * - `connect-github-to-sync` — HTTPS github.com remote, no stored credential.
25
+ * - `https-connect-server` — HTTPS non-GitHub remote, no stored credential.
26
+ * - `ready-to-sync` — HTTPS remote with a stored credential (sync lands
27
+ * with #15's sync phase; the plumbing is in place).
28
+ * - `ssh-use-own-tools` — SSH remote: full local features, sync externally
29
+ * (ADR 0006 D6). The UI layers the "switch to HTTPS" hint on recognized hosts.
30
+ */
31
+ export type RemoteGuidanceId = "local-only" | "connect-github-to-sync" | "https-connect-server" | "ready-to-sync" | "ssh-use-own-tools";
32
+ export interface ProjectRemoteDiagnosis {
33
+ /** The #12 classification, unchanged. */
34
+ classification: ProjectSource;
35
+ /** Sanitized remote URL (no embedded credentials), when one exists. */
36
+ remoteUrl?: string;
37
+ /** Host of the remote (hostname[:port], lower-case), when parseable. */
38
+ remoteHost?: string;
39
+ remoteProtocol: RemoteProtocol;
40
+ branch?: string;
41
+ /** A credential for `remoteHost` exists in the injected store. */
42
+ credentialPresent: boolean;
43
+ /** Forge family of the remote host; null when there is no remote. */
44
+ provider: ForgeKind | null;
45
+ /** Token-settings deep link for recognized non-GitHub forges. */
46
+ tokenSettingsUrl: string | null;
47
+ /**
48
+ * ADR 0006 D4: hasRemote && smart-HTTPS && credential stored. The sync
49
+ * flow (#15 D5) is live, so this is the real "offer the Sync action"
50
+ * gate, not a future-capability hint.
51
+ */
52
+ canSync: boolean;
53
+ guidance: RemoteGuidanceId;
54
+ }
55
+ /** Parse any git remote URL (https, ssh://, scp-like) to protocol + host. */
56
+ export declare function parseRemoteOrigin(url: string): {
57
+ protocol: RemoteProtocol;
58
+ host?: string;
59
+ };
60
+ /** Classify a host into a forge family for guidance copy. Heuristic by name. */
61
+ export declare function forgeKindForHost(host: string): ForgeKind;
62
+ export interface DiagnoseProjectRemoteOptions {
63
+ /** Host-keyed credential store to check for a stored connection. */
64
+ tokenStore?: TokenStore;
65
+ /**
66
+ * Pre-classified source for `projectDir`, when the caller already ran
67
+ * detectProjectSource (e.g. buildRecoveryContext). Skips the redundant
68
+ * parent-dir walk; when omitted, classification runs here as before.
69
+ */
70
+ source?: ProjectSource;
71
+ }
72
+ /**
73
+ * Build the Advanced Setup environment status for an opened project folder.
74
+ * Local reads only; never throws (classification itself never throws, and a
75
+ * store failure degrades to `credentialPresent: false`).
76
+ */
77
+ export declare function diagnoseProjectRemote(projectDir: string, options?: DiagnoseProjectRemoteOptions): Promise<ProjectRemoteDiagnosis>;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Generic token auth provider (#14, ADR 0006 D3 layer 3).
3
+ *
4
+ * The universal floor for every smart-HTTPS Git host that is not github.com:
5
+ * the user pastes a host URL + (optional) username + access token collected by
6
+ * the Advanced Setup UI, and the credential is VERIFIED with a refs probe
7
+ * BEFORE it is saved — a bad paste fails immediately with friendly guidance,
8
+ * never later during a sync.
9
+ *
10
+ * The transport is identical for every forge (Gitea, Forgejo, GitLab,
11
+ * Bitbucket, Azure Repos, plain `git http-backend`), so a private Gitea works
12
+ * the day this lands. Per-forge sugar is limited to {@link knownForgeTokenUrl},
13
+ * the deep link to the host's token-settings page.
14
+ *
15
+ * SECURITY INVARIANT: token values never appear in error messages or logs.
16
+ */
17
+ import type httpNode from "isomorphic-git/http/node";
18
+ import type { HostCallbacks, RemoteAuthProvider } from "./github-auth.ts";
19
+ import { type HostCredential } from "./token-store.ts";
20
+ /** What the Advanced Setup UI collects for "Connect a Git server". */
21
+ export interface GenericTokenConnectInput {
22
+ /** The server, as a hostname ("git.example.com") or any URL on it. */
23
+ host: string;
24
+ /**
25
+ * Login associated with the token. Optional — many forges (Gitea, GitLab)
26
+ * accept the token alone as Basic auth; when omitted the transport sends the
27
+ * token-as-username convention.
28
+ */
29
+ username?: string;
30
+ /** The access token pasted by the user. NEVER log this. */
31
+ token: string;
32
+ /**
33
+ * A repository HTTPS URL on the host to validate against. When provided the
34
+ * probe must fully succeed; when omitted the probe runs against the host
35
+ * root, which verifies reachability and catches rejected tokens (401) but
36
+ * cannot prove repo access (most forge roots are not Git endpoints, so a
37
+ * not-found answer there is expected and accepted).
38
+ */
39
+ repoUrl?: string;
40
+ }
41
+ /**
42
+ * The {@link RemoteAuthProvider} `connect` input for the generic provider:
43
+ * the UI-collected fields plus the standard host callbacks (`onUserCode` is
44
+ * part of the shared contract but unused — there is no device code in the
45
+ * token flow; pass a no-op, or use {@link connectGenericHost} directly).
46
+ */
47
+ export interface GenericHostCallbacks extends HostCallbacks, GenericTokenConnectInput {
48
+ }
49
+ export interface GenericAuthOptions {
50
+ /** Injectable git HTTP transport for tests. */
51
+ httpClient?: typeof httpNode;
52
+ /** Probe timeout forwarded to {@link testRemoteAccess}. */
53
+ timeoutMs?: number;
54
+ }
55
+ /**
56
+ * Reduce user input ("https://git.example.com/some/repo", "Git.Example.com",
57
+ * "git.example.com:3000/x") to the normalized host the credential is keyed by.
58
+ * Delegates to {@link credentialHostKey} — the ONE canonical derivation shared
59
+ * by every credential writer and reader — so a host typed here always keys
60
+ * identically to the same host parsed out of a remote URL by diagnose or the
61
+ * sync transport. Returns "" when nothing usable remains.
62
+ */
63
+ export declare function normalizeForgeHost(input: string): string;
64
+ /**
65
+ * Deep link to the token-settings page for recognized forges; `null` for
66
+ * unknown hosts (the UI then shows generic "create an access token" guidance).
67
+ * github.com intentionally returns `null` — GitHub uses the managed device
68
+ * flow, never a pasted token.
69
+ */
70
+ export declare function knownForgeTokenUrl(host: string): string | null;
71
+ /**
72
+ * Validate-then-build a host credential from UI-collected input (the D3
73
+ * "Connect a Git server" flow). Throws a friendly error when validation
74
+ * fails; the caller stores the returned credential in its TokenStore.
75
+ *
76
+ * KNOWN LIMITATION (root probe): when `repoUrl` is omitted, the probe runs
77
+ * against `https://host/`, and most forge roots are not Git endpoints — they
78
+ * answer "not found" without ever evaluating the credential. That answer is
79
+ * accepted (it proves reachability and that the token was not actively
80
+ * rejected), which means **a wrong token can be accepted** in this mode. Only
81
+ * an explicit 401/403 rejects. Pass `repoUrl` whenever one is available to get
82
+ * a full end-to-end verification of the token against a real repository.
83
+ */
84
+ export declare function connectGenericHost(input: GenericTokenConnectInput, options?: GenericAuthOptions): Promise<HostCredential>;
85
+ /**
86
+ * {@link RemoteAuthProvider} wrapper for the generic token flow, so host apps
87
+ * can route any non-GitHub origin through the shared provider contract.
88
+ */
89
+ export declare class GenericTokenAuthProvider implements RemoteAuthProvider {
90
+ private readonly options;
91
+ constructor(options?: GenericAuthOptions);
92
+ /** Handles any smart-HTTP(S) host that is not github.com (device flow). */
93
+ matches(origin: URL): boolean;
94
+ connect(callbacks: GenericHostCallbacks): Promise<HostCredential>;
95
+ /** Revalidate a stored credential: false only on a definitive rejection. */
96
+ validate(credential: HostCredential): Promise<boolean>;
97
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Timeout wrapper for the isomorphic-git HTTP client (audit B1).
3
+ *
4
+ * Every git transport call in this subsystem (`git.fetch`/`git.push`/
5
+ * `git.clone`) is invoked with no timeout: if the TCP connection succeeds but
6
+ * the remote stalls, the call never resolves or rejects — sync/pull/push/clone
7
+ * hangs forever, and because the operation still holds the per-repo
8
+ * `withRepoLock` FIFO, every subsequent git op for that project wedges too.
9
+ *
10
+ * The REST calls in the same subsystem (github-repos.ts, github-auth.ts) already
11
+ * guard every fetch with `AbortSignal.timeout`; this brings the same discipline
12
+ * to the git transport, at the one place a default client is chosen.
13
+ *
14
+ * Design (review finding: a naive deadline on `request()` is WRONG for pushes):
15
+ * isomorphic-git passes upload bodies as an ARRAY of buffers, which the node
16
+ * client collects and sends with Content-Length — `request()` then resolves
17
+ * only when the response HEADERS arrive, i.e. after the ENTIRE pack upload.
18
+ * There is no per-chunk progress signal for uploads, so an idle deadline on the
19
+ * request phase of a push would be a TOTAL cap that kills legitimately slow
20
+ * large pushes. Therefore:
21
+ *
22
+ * - Body-less requests (the info/refs discovery GET — the classic
23
+ * "connected but silent" stall) get the short IDLE deadline.
24
+ * - Requests WITH a body (push receive-pack / fetch negotiation POSTs) get a
25
+ * LONG total backstop instead: generous enough that no realistic transfer
26
+ * hits it, but bounded so a truly dead connection can never wedge the repo
27
+ * lock forever.
28
+ * - Every response-body chunk read re-arms the short idle deadline, so a
29
+ * stall after headers is caught quickly on every request type while a
30
+ * slow-but-progressing download streams for as long as it needs.
31
+ *
32
+ * One timer serves the whole request (re-armed via `refresh()`; no per-chunk
33
+ * allocation). On timeout the thrown error's message classifies as offline in
34
+ * recovery/classify.ts. Known limitation (documented, not fixable at this
35
+ * layer): isomorphic-git's client accepts no AbortSignal, so an abandoned
36
+ * timed-out transfer's socket is left to the OS/agent to reap.
37
+ *
38
+ * Test HTTP clients injected via `httpClient`/`ctx.httpClient` are NOT wrapped
39
+ * (they talk to in-memory fixtures that never stall); only the production
40
+ * default is.
41
+ */
42
+ import httpNode from "isomorphic-git/http/node";
43
+ /**
44
+ * Idle deadline for silent phases: the wait for response headers on body-less
45
+ * requests, and the gap between response-body chunks. A healthy transfer
46
+ * produces SOMETHING within a minute; total silence for 60s means the
47
+ * connection is dead, not slow.
48
+ */
49
+ export declare const GIT_HTTP_IDLE_TIMEOUT_MS = 60000;
50
+ /**
51
+ * Total backstop for requests that upload a LARGE body (a push pack), whose
52
+ * request phase exposes no progress signal (see header). 30 minutes
53
+ * accommodates a multi-GB initial push on a slow uplink while still
54
+ * guaranteeing the per-repo lock can never be wedged forever.
55
+ */
56
+ export declare const GIT_HTTP_UPLOAD_TIMEOUT_MS: number;
57
+ /**
58
+ * Bodies at or below this size are "not an upload": they transfer in seconds
59
+ * even on a very slow uplink (256 KiB ≈ 21s at 100 kbit/s), so the wait for
60
+ * response headers is server silence, governed by the short idle deadline. A
61
+ * pull's fetch-negotiation POST (want/have lines) is a few KB; push packs are
62
+ * MBs+ and get the long backstop.
63
+ */
64
+ export declare const SMALL_BODY_MAX_BYTES: number;
65
+ /**
66
+ * True when `body` is an array of sizable chunks totalling at most
67
+ * {@link SMALL_BODY_MAX_BYTES}. isomorphic-git always passes bodies as arrays
68
+ * of byte chunks, so small ones are sizable synchronously. Any chunk WITHOUT
69
+ * a numeric byteLength makes the body unsizable → NOT small, so it gets the
70
+ * long upload backstop rather than the idle deadline as a total cap (review
71
+ * finding: the old reduce counted unsizable chunks as 0 bytes — the exact
72
+ * opposite of that intent).
73
+ */
74
+ export declare function isSmallBody(body: unknown): boolean;
75
+ /**
76
+ * Return a client that behaves like `http` but rejects when the transfer goes
77
+ * silent (see the header for exactly which phases are guarded and why).
78
+ */
79
+ export declare function withIdleTimeout(http: typeof httpNode, idleMs?: number, uploadMs?: number): typeof httpNode;
80
+ /**
81
+ * The production default git HTTP client: `httpNode` with the timeout policy
82
+ * above. Use this in place of a bare `httpNode` wherever a caller did not
83
+ * inject its own `httpClient`.
84
+ */
85
+ export declare const defaultGitHttp: typeof httpNode;