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.
- package/README.md +422 -0
- package/dist/CGATS21_CRPC1-g0e3k7kr.icc +0 -0
- package/dist/api/index.d.ts +76 -0
- package/dist/api/index.js +272 -0
- package/dist/assets/preview/scripts/pagedjs-bridge.d.ts +1 -0
- package/dist/assets/preview/scripts/pagedjs-interface.d.ts +1 -0
- package/dist/assets/preview/scripts/preview-shell.d.ts +1 -0
- package/dist/audit-1jrwwacz.js +66 -0
- package/dist/build-5s92kgpc.js +77 -0
- package/dist/chapter-01-6fn9pvd0.md +31 -0
- package/dist/chapter-01-7jwqg1ct.md +20 -0
- package/dist/chapter-01-jj8ftn4c.md +29 -0
- package/dist/chapter-01-rkpyvjna.md +26 -0
- package/dist/checks/asset/approved-fonts.d.ts +3 -0
- package/dist/checks/asset/extensions.d.ts +41 -0
- package/dist/checks/asset/font-license.d.ts +3 -0
- package/dist/checks/asset/image-alpha.d.ts +3 -0
- package/dist/checks/asset/image-color-space.d.ts +3 -0
- package/dist/checks/asset/image-file-size.d.ts +3 -0
- package/dist/checks/asset/image-resolution.d.ts +3 -0
- package/dist/checks/asset/image-tac.d.ts +3 -0
- package/dist/checks/asset/index.d.ts +7 -0
- package/dist/checks/formatter.d.ts +3 -0
- package/dist/checks/heuristic/index.d.ts +4 -0
- package/dist/checks/heuristic/layer-count.d.ts +3 -0
- package/dist/checks/heuristic/placement-variance.d.ts +3 -0
- package/dist/checks/heuristic/section-density.d.ts +3 -0
- package/dist/checks/heuristic/text-density.d.ts +3 -0
- package/dist/checks/pdf/bleed.d.ts +3 -0
- package/dist/checks/pdf/bookmarks.d.ts +3 -0
- package/dist/checks/pdf/color-spaces.d.ts +3 -0
- package/dist/checks/pdf/cross-refs.d.ts +3 -0
- package/dist/checks/pdf/embedded-fonts.d.ts +3 -0
- package/dist/checks/pdf/image-resolution.d.ts +3 -0
- package/dist/checks/pdf/index.d.ts +15 -0
- package/dist/checks/pdf/ink-coverage.d.ts +3 -0
- package/dist/checks/pdf/page-labels.d.ts +3 -0
- package/dist/checks/pdf/page-size.d.ts +3 -0
- package/dist/checks/pdf/pdfx-markers.d.ts +3 -0
- package/dist/checks/pdf/pdfx-metadata.d.ts +3 -0
- package/dist/checks/pdf/pdfx-structure.d.ts +6 -0
- package/dist/checks/pdf/qpdf-structure.d.ts +3 -0
- package/dist/checks/pdf/rasterized-pages.d.ts +3 -0
- package/dist/checks/pdf/toc-links.d.ts +3 -0
- package/dist/checks/pdf/transparency.d.ts +3 -0
- package/dist/checks/policy.d.ts +61 -0
- package/dist/checks/register-builtins.d.ts +17 -0
- package/dist/checks/registry.d.ts +40 -0
- package/dist/checks/runner.d.ts +25 -0
- package/dist/checks/source/accessibility-alt-text.d.ts +3 -0
- package/dist/checks/source/accessibility-heading-order.d.ts +3 -0
- package/dist/checks/source/config-file.d.ts +8 -0
- package/dist/checks/source/htmlhint.d.ts +3 -0
- package/dist/checks/source/index.d.ts +6 -0
- package/dist/checks/source/local-refs.d.ts +3 -0
- package/dist/checks/source/markdownlint.d.ts +3 -0
- package/dist/checks/source/stylelint.d.ts +3 -0
- package/dist/checks/tool-check.d.ts +23 -0
- package/dist/checks/types.d.ts +52 -0
- package/dist/cli-1tyj4bk3.js +124 -0
- package/dist/cli-37x76zdn.js +4 -0
- package/dist/cli-506tg37g.js +26 -0
- package/dist/cli-5bxx0c4y.js +464 -0
- package/dist/cli-7ykx7pp0.js +12378 -0
- package/dist/cli-h7phg3ka.js +746 -0
- package/dist/cli-hvqjs342.js +284 -0
- package/dist/cli-yja077f6.js +92 -0
- package/dist/cli.js +157 -0
- package/dist/constants.d.ts +17 -0
- package/dist/doctor-25zjt8dz.js +57 -0
- package/dist/favicon-wkbm9cjn.ico +0 -0
- package/dist/git-http-yrb4ag6z.js +17 -0
- package/dist/index-1tyj4bk3.js +124 -0
- package/dist/index-37x76zdn.js +4 -0
- package/dist/index-3h3fef7r.js +15218 -0
- package/dist/index-5bxx0c4y.js +464 -0
- package/dist/index-yja077f6.js +92 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +310 -0
- package/dist/lib/app-heartbeat.d.ts +102 -0
- package/dist/lib/asset-inline.d.ts +73 -0
- package/dist/lib/browser-pool.d.ts +17 -0
- package/dist/lib/build-error.d.ts +52 -0
- package/dist/lib/build-fingerprint.d.ts +15 -0
- package/dist/lib/build-preflight.d.ts +43 -0
- package/dist/lib/build-runner.d.ts +161 -0
- package/dist/lib/build-staging.d.ts +64 -0
- package/dist/lib/chromium.d.ts +20 -0
- package/dist/lib/cli-args.d.ts +45 -0
- package/dist/lib/desktop.d.ts +14 -0
- package/dist/lib/diagnostics.d.ts +64 -0
- package/dist/lib/embedded-assets.d.ts +29 -0
- package/dist/lib/exec.d.ts +84 -0
- package/dist/lib/fetch-timeout.d.ts +41 -0
- package/dist/lib/ghostscript.d.ts +48 -0
- package/dist/lib/host-policy.d.ts +75 -0
- package/dist/lib/image-inspect.d.ts +32 -0
- package/dist/lib/install-hints.d.ts +21 -0
- package/dist/lib/lint-runner.d.ts +10 -0
- package/dist/lib/manifest-config.d.ts +31 -0
- package/dist/lib/manifest-doc.d.ts +25 -0
- package/dist/lib/manifest.d.ts +41 -0
- package/dist/lib/markdown/assemble.d.ts +78 -0
- package/dist/lib/markdown/chapter-id.d.ts +24 -0
- package/dist/lib/markdown/images.d.ts +34 -0
- package/dist/lib/markdown/index.d.ts +78 -0
- package/dist/lib/markdown/markdown-it-paged.d.ts +30 -0
- package/dist/lib/markdown/plugins.d.ts +69 -0
- package/dist/lib/markdown/renderer.d.ts +111 -0
- package/dist/lib/npm-plugin-installer.d.ts +33 -0
- package/dist/lib/open-path.d.ts +5 -0
- package/dist/lib/output-paths.d.ts +23 -0
- package/dist/lib/pagedjs-marker.d.ts +42 -0
- package/dist/lib/pagedjs.d.ts +26 -0
- package/dist/lib/pagination.d.ts +169 -0
- package/dist/lib/pdf-inspect.d.ts +123 -0
- package/dist/lib/pdf-parse.d.ts +57 -0
- package/dist/lib/plugin-manager.d.ts +108 -0
- package/dist/lib/plugin-vendor.d.ts +107 -0
- package/dist/lib/presets.d.ts +45 -0
- package/dist/lib/printsafe.d.ts +17 -0
- package/dist/lib/project-scaffold.d.ts +116 -0
- package/dist/lib/project-source.d.ts +140 -0
- package/dist/lib/project-templates.d.ts +49 -0
- package/dist/lib/publish/butler.d.ts +10 -0
- package/dist/lib/publish/command-runner.d.ts +18 -0
- package/dist/lib/publish/connect.d.ts +23 -0
- package/dist/lib/publish/manifest-publish.d.ts +9 -0
- package/dist/lib/publish/providers/azure-swa.d.ts +3 -0
- package/dist/lib/publish/providers/drivethrurpg.d.ts +10 -0
- package/dist/lib/publish/providers/guided.d.ts +3 -0
- package/dist/lib/publish/providers/itch.d.ts +5 -0
- package/dist/lib/publish/providers/kdp.d.ts +9 -0
- package/dist/lib/publish/providers/shopify.d.ts +5 -0
- package/dist/lib/publish/registry.d.ts +5 -0
- package/dist/lib/publish/run-publish.d.ts +30 -0
- package/dist/lib/publish/selections.d.ts +25 -0
- package/dist/lib/publish/types.d.ts +258 -0
- package/dist/lib/remote-auth/clone.d.ts +87 -0
- package/dist/lib/remote-auth/conflict-resolution.d.ts +29 -0
- package/dist/lib/remote-auth/diagnose.d.ts +77 -0
- package/dist/lib/remote-auth/generic-auth.d.ts +97 -0
- package/dist/lib/remote-auth/git-http.d.ts +85 -0
- package/dist/lib/remote-auth/github-auth.d.ts +69 -0
- package/dist/lib/remote-auth/github-repos.d.ts +64 -0
- package/dist/lib/remote-auth/operation-log.d.ts +66 -0
- package/dist/lib/remote-auth/recovery/abort-interrupted-operation.d.ts +103 -0
- package/dist/lib/remote-auth/recovery/backup.d.ts +113 -0
- package/dist/lib/remote-auth/recovery/classify.d.ts +109 -0
- package/dist/lib/remote-auth/recovery/context.d.ts +47 -0
- package/dist/lib/remote-auth/recovery/dispatch.d.ts +33 -0
- package/dist/lib/remote-auth/recovery/failsafe.d.ts +33 -0
- package/dist/lib/remote-auth/recovery/inspect.d.ts +74 -0
- package/dist/lib/remote-auth/recovery/manual-guidance.d.ts +28 -0
- package/dist/lib/remote-auth/recovery/outcome-mapping.d.ts +58 -0
- package/dist/lib/remote-auth/recovery/policy.d.ts +47 -0
- package/dist/lib/remote-auth/recovery/recover-auth.d.ts +42 -0
- package/dist/lib/remote-auth/recovery/recover-binary-conflict.d.ts +37 -0
- package/dist/lib/remote-auth/recovery/recover-corrupt-index.d.ts +40 -0
- package/dist/lib/remote-auth/recovery/recover-detached-head.d.ts +70 -0
- package/dist/lib/remote-auth/recovery/recover-interrupted-cherry-pick.d.ts +23 -0
- package/dist/lib/remote-auth/recovery/recover-interrupted-merge.d.ts +28 -0
- package/dist/lib/remote-auth/recovery/recover-interrupted-rebase.d.ts +39 -0
- package/dist/lib/remote-auth/recovery/recover-merge-conflict.d.ts +34 -0
- package/dist/lib/remote-auth/recovery/recover-missing-git-dir.d.ts +37 -0
- package/dist/lib/remote-auth/recovery/recover-missing-objects.d.ts +56 -0
- package/dist/lib/remote-auth/recovery/recover-network.d.ts +34 -0
- package/dist/lib/remote-auth/recovery/recover-non-fast-forward.d.ts +27 -0
- package/dist/lib/remote-auth/recovery/recover-stale-lock.d.ts +76 -0
- package/dist/lib/remote-auth/recovery/recover-unrelated-histories.d.ts +44 -0
- package/dist/lib/remote-auth/recovery/recover-wrong-remote.d.ts +35 -0
- package/dist/lib/remote-auth/recovery/types.d.ts +235 -0
- package/dist/lib/remote-auth/resolution-plan.d.ts +64 -0
- package/dist/lib/remote-auth/sync-messages.d.ts +26 -0
- package/dist/lib/remote-auth/sync-types.d.ts +225 -0
- package/dist/lib/remote-auth/sync.d.ts +45 -0
- package/dist/lib/remote-auth/test-access.d.ts +47 -0
- package/dist/lib/remote-auth/test-support/git-http-server.d.ts +71 -0
- package/dist/lib/remote-auth/token-store.d.ts +95 -0
- package/dist/lib/remote-auth/transport.d.ts +105 -0
- package/dist/lib/slug.d.ts +28 -0
- package/dist/lib/snippets.d.ts +39 -0
- package/dist/lib/source-provider.d.ts +244 -0
- package/dist/lib/static-serve.d.ts +54 -0
- package/dist/lib/style-resolver.d.ts +30 -0
- package/dist/lib/theme-import.d.ts +70 -0
- package/dist/lib/theme-manager.d.ts +128 -0
- package/dist/lib/tool-probe.d.ts +37 -0
- package/dist/lib/validation-exec.d.ts +47 -0
- package/dist/lib/validation-profile.d.ts +18 -0
- package/dist/lib/version.d.ts +9 -0
- package/dist/lint-f63nxjfj.js +65 -0
- package/dist/manifest-n1gh3qw5.yaml +9 -0
- package/dist/manifest-ryyg71nf.yaml +9 -0
- package/dist/manifest.schema-rn1dpdpn.json +564 -0
- package/dist/new-7b08a42h.js +105 -0
- package/dist/paged.polyfill-n95pbxfn.js +33288 -0
- package/dist/pagedjs-bridge-vn4hk9fx.js +111 -0
- package/dist/pagedjs-interface-80g3xa8p.js +498 -0
- package/dist/platform.d.ts +100 -0
- package/dist/plugin-wf2cbmwc.js +129 -0
- package/dist/preflight-tg4mpz1h.js +222 -0
- package/dist/preview/file-watcher.d.ts +174 -0
- package/dist/preview/http-server.d.ts +45 -0
- package/dist/preview/lifecycle.d.ts +67 -0
- package/dist/preview/server-context.d.ts +36 -0
- package/dist/preview-h2bwbb2x.js +102 -0
- package/dist/preview-shell-rghtfmc8.js +155 -0
- package/dist/project-source-0tg0r6vm.js +17 -0
- package/dist/publish-ztaet53r.js +233 -0
- package/dist/render.d.ts +20 -0
- package/dist/render.js +682 -0
- package/dist/repair-jkp4n3j8.js +171 -0
- package/dist/schema/manifest.types.d.ts +218 -0
- package/dist/server.d.ts +34 -0
- package/dist/source-provider-hp96b5jf.js +46 -0
- package/dist/test-helpers/testkit.d.ts +91 -0
- package/dist/theme-0yzvthe8.css +65 -0
- package/dist/theme-570zmh2t.css +58 -0
- package/dist/theme-6a2fngc8.json +6 -0
- package/dist/theme-cd6efzjw.json +6 -0
- package/dist/theme-nya4nqh6.css +70 -0
- package/dist/theme-tqfypc1w.json +6 -0
- package/dist/theme-xt0hjb1b.json +6 -0
- package/dist/theme-zz2ktzqs.css +60 -0
- package/dist/types.d.ts +31 -0
- package/dist/utils/file-utils.d.ts +53 -0
- package/dist/utils/logger.d.ts +59 -0
- package/dist/validate-k2drs19j.js +99 -0
- 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;
|