runwork 0.15.0 → 0.15.2
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/dist/index.js +19272 -116
- package/package.json +4 -4
- package/dist/__tests__/curl-parser.test.d.ts +0 -1
- package/dist/__tests__/curl-parser.test.js +0 -250
- package/dist/__tests__/install-scripts.test.d.ts +0 -1
- package/dist/__tests__/install-scripts.test.js +0 -280
- package/dist/__tests__/types-manager.test.d.ts +0 -1
- package/dist/__tests__/types-manager.test.js +0 -89
- package/dist/agents/__tests__/chatgpt-registry.test.d.ts +0 -1
- package/dist/agents/__tests__/chatgpt-registry.test.js +0 -61
- package/dist/agents/__tests__/claude-code-managed-block.test.d.ts +0 -1
- package/dist/agents/__tests__/claude-code-managed-block.test.js +0 -97
- package/dist/agents/__tests__/claude-code-plugin.test.d.ts +0 -1
- package/dist/agents/__tests__/claude-code-plugin.test.js +0 -47
- package/dist/agents/__tests__/claude-code-stats.test.d.ts +0 -1
- package/dist/agents/__tests__/claude-code-stats.test.js +0 -390
- package/dist/agents/__tests__/claude-desktop-memory.test.d.ts +0 -1
- package/dist/agents/__tests__/claude-desktop-memory.test.js +0 -154
- package/dist/agents/__tests__/claude-desktop-plugin-tree.test.d.ts +0 -1
- package/dist/agents/__tests__/claude-desktop-plugin-tree.test.js +0 -161
- package/dist/agents/__tests__/claude-desktop-plugin.test.d.ts +0 -1
- package/dist/agents/__tests__/claude-desktop-plugin.test.js +0 -117
- package/dist/agents/__tests__/claude-desktop-rpm.test.d.ts +0 -1
- package/dist/agents/__tests__/claude-desktop-rpm.test.js +0 -126
- package/dist/agents/__tests__/claude-desktop-stats.test.d.ts +0 -1
- package/dist/agents/__tests__/claude-desktop-stats.test.js +0 -280
- package/dist/agents/__tests__/codex-instructions.test.d.ts +0 -1
- package/dist/agents/__tests__/codex-instructions.test.js +0 -57
- package/dist/agents/__tests__/codex-minimum-permissions.test.d.ts +0 -1
- package/dist/agents/__tests__/codex-minimum-permissions.test.js +0 -82
- package/dist/agents/__tests__/codex-stats.test.d.ts +0 -1
- package/dist/agents/__tests__/codex-stats.test.js +0 -370
- package/dist/agents/__tests__/cursor-merge.test.d.ts +0 -1
- package/dist/agents/__tests__/cursor-merge.test.js +0 -66
- package/dist/agents/__tests__/cursor-stats.test.d.ts +0 -1
- package/dist/agents/__tests__/cursor-stats.test.js +0 -183
- package/dist/agents/__tests__/defaults-merge.test.d.ts +0 -1
- package/dist/agents/__tests__/defaults-merge.test.js +0 -268
- package/dist/agents/__tests__/detection.test.d.ts +0 -1
- package/dist/agents/__tests__/detection.test.js +0 -89
- package/dist/agents/__tests__/graceful-degradation.test.d.ts +0 -1
- package/dist/agents/__tests__/graceful-degradation.test.js +0 -129
- package/dist/agents/__tests__/instruction-hint.test.d.ts +0 -1
- package/dist/agents/__tests__/instruction-hint.test.js +0 -69
- package/dist/agents/__tests__/intro-skill.test.d.ts +0 -1
- package/dist/agents/__tests__/intro-skill.test.js +0 -193
- package/dist/agents/__tests__/schema-validation.test.d.ts +0 -1
- package/dist/agents/__tests__/schema-validation.test.js +0 -212
- package/dist/agents/__tests__/skill-slug.test.d.ts +0 -1
- package/dist/agents/__tests__/skill-slug.test.js +0 -77
- package/dist/agents/claude-code.d.ts +0 -53
- package/dist/agents/claude-code.js +0 -754
- package/dist/agents/claude-desktop-plugin-tree.d.ts +0 -59
- package/dist/agents/claude-desktop-plugin-tree.js +0 -143
- package/dist/agents/claude-desktop.d.ts +0 -47
- package/dist/agents/claude-desktop.js +0 -684
- package/dist/agents/cline.d.ts +0 -15
- package/dist/agents/cline.js +0 -130
- package/dist/agents/codex.d.ts +0 -44
- package/dist/agents/codex.js +0 -453
- package/dist/agents/conversation-skills.d.ts +0 -18
- package/dist/agents/conversation-skills.js +0 -229
- package/dist/agents/cursor.d.ts +0 -48
- package/dist/agents/cursor.js +0 -324
- package/dist/agents/default-config.d.ts +0 -30
- package/dist/agents/default-config.js +0 -67
- package/dist/agents/defaults-merge.d.ts +0 -72
- package/dist/agents/defaults-merge.js +0 -131
- package/dist/agents/detect.d.ts +0 -4
- package/dist/agents/detect.js +0 -67
- package/dist/agents/detection.d.ts +0 -18
- package/dist/agents/detection.js +0 -94
- package/dist/agents/gemini.d.ts +0 -15
- package/dist/agents/gemini.js +0 -122
- package/dist/agents/generic-adapter.d.ts +0 -25
- package/dist/agents/generic-adapter.js +0 -151
- package/dist/agents/intro-skill.d.ts +0 -45
- package/dist/agents/intro-skill.js +0 -447
- package/dist/agents/registry-data.d.ts +0 -192
- package/dist/agents/registry-data.js +0 -669
- package/dist/agents/registry.d.ts +0 -13
- package/dist/agents/registry.js +0 -36
- package/dist/agents/runtime-detection.d.ts +0 -82
- package/dist/agents/runtime-detection.js +0 -271
- package/dist/agents/session-start-hook.d.ts +0 -37
- package/dist/agents/session-start-hook.js +0 -159
- package/dist/agents/types.d.ts +0 -171
- package/dist/agents/types.js +0 -75
- package/dist/agents/utils/instruction-hint.d.ts +0 -21
- package/dist/agents/utils/instruction-hint.js +0 -108
- package/dist/agents/utils/json-config.d.ts +0 -19
- package/dist/agents/utils/json-config.js +0 -69
- package/dist/agents/utils/toml-config.d.ts +0 -8
- package/dist/agents/utils/toml-config.js +0 -27
- package/dist/agents/windsurf.d.ts +0 -14
- package/dist/agents/windsurf.js +0 -102
- package/dist/api/__tests__/client.test.d.ts +0 -1
- package/dist/api/__tests__/client.test.js +0 -272
- package/dist/api/client.d.ts +0 -326
- package/dist/api/client.js +0 -427
- package/dist/auth/__tests__/login-flow.test.d.ts +0 -1
- package/dist/auth/__tests__/login-flow.test.js +0 -101
- package/dist/auth/__tests__/store.test.d.ts +0 -1
- package/dist/auth/__tests__/store.test.js +0 -132
- package/dist/auth/login-flow.d.ts +0 -26
- package/dist/auth/login-flow.js +0 -137
- package/dist/auth/store.d.ts +0 -5
- package/dist/auth/store.js +0 -62
- package/dist/commands/__tests__/clone-args.test.d.ts +0 -1
- package/dist/commands/__tests__/clone-args.test.js +0 -51
- package/dist/commands/__tests__/clone-source-result.test.d.ts +0 -1
- package/dist/commands/__tests__/clone-source-result.test.js +0 -45
- package/dist/commands/__tests__/info-merge.test.d.ts +0 -1
- package/dist/commands/__tests__/info-merge.test.js +0 -55
- package/dist/commands/__tests__/mcp-entries.test.d.ts +0 -1
- package/dist/commands/__tests__/mcp-entries.test.js +0 -48
- package/dist/commands/__tests__/setup-persona.test.d.ts +0 -1
- package/dist/commands/__tests__/setup-persona.test.js +0 -31
- package/dist/commands/__tests__/setup.test.d.ts +0 -1
- package/dist/commands/__tests__/setup.test.js +0 -81
- package/dist/commands/__tests__/sync-redetect.test.d.ts +0 -1
- package/dist/commands/__tests__/sync-redetect.test.js +0 -96
- package/dist/commands/__tests__/sync-telemetry.test.d.ts +0 -1
- package/dist/commands/__tests__/sync-telemetry.test.js +0 -281
- package/dist/commands/__tests__/upgrade.test.d.ts +0 -1
- package/dist/commands/__tests__/upgrade.test.js +0 -157
- package/dist/commands/agents.d.ts +0 -2
- package/dist/commands/agents.js +0 -160
- package/dist/commands/api-keys.d.ts +0 -2
- package/dist/commands/api-keys.js +0 -95
- package/dist/commands/apps.d.ts +0 -2
- package/dist/commands/apps.js +0 -82
- package/dist/commands/build-plugin.d.ts +0 -2
- package/dist/commands/build-plugin.js +0 -135
- package/dist/commands/clone.d.ts +0 -60
- package/dist/commands/clone.js +0 -271
- package/dist/commands/components.d.ts +0 -2
- package/dist/commands/components.js +0 -43
- package/dist/commands/deploy.d.ts +0 -2
- package/dist/commands/deploy.js +0 -279
- package/dist/commands/dev.d.ts +0 -11
- package/dist/commands/dev.js +0 -1011
- package/dist/commands/doctor.d.ts +0 -2
- package/dist/commands/doctor.js +0 -230
- package/dist/commands/endpoints.d.ts +0 -2
- package/dist/commands/endpoints.js +0 -189
- package/dist/commands/entities.d.ts +0 -2
- package/dist/commands/entities.js +0 -198
- package/dist/commands/files.d.ts +0 -2
- package/dist/commands/files.js +0 -193
- package/dist/commands/inbox.d.ts +0 -11
- package/dist/commands/inbox.js +0 -60
- package/dist/commands/info.d.ts +0 -178
- package/dist/commands/info.js +0 -455
- package/dist/commands/init.d.ts +0 -21
- package/dist/commands/init.js +0 -185
- package/dist/commands/integrations.d.ts +0 -2
- package/dist/commands/integrations.js +0 -233
- package/dist/commands/login.d.ts +0 -2
- package/dist/commands/login.js +0 -44
- package/dist/commands/logout.d.ts +0 -2
- package/dist/commands/logout.js +0 -13
- package/dist/commands/logs.d.ts +0 -2
- package/dist/commands/logs.js +0 -232
- package/dist/commands/mcp-entries.d.ts +0 -13
- package/dist/commands/mcp-entries.js +0 -26
- package/dist/commands/mcp.d.ts +0 -2
- package/dist/commands/mcp.js +0 -245
- package/dist/commands/open.d.ts +0 -2
- package/dist/commands/open.js +0 -53
- package/dist/commands/resume.d.ts +0 -2
- package/dist/commands/resume.js +0 -289
- package/dist/commands/save-convo.d.ts +0 -8
- package/dist/commands/save-convo.js +0 -20
- package/dist/commands/schedules.d.ts +0 -2
- package/dist/commands/schedules.js +0 -221
- package/dist/commands/setup.d.ts +0 -27
- package/dist/commands/setup.js +0 -177
- package/dist/commands/share-convo.d.ts +0 -24
- package/dist/commands/share-convo.js +0 -167
- package/dist/commands/skills.d.ts +0 -2
- package/dist/commands/skills.js +0 -352
- package/dist/commands/sync-telemetry.d.ts +0 -33
- package/dist/commands/sync-telemetry.js +0 -186
- package/dist/commands/sync.d.ts +0 -37
- package/dist/commands/sync.js +0 -702
- package/dist/commands/uninstall.d.ts +0 -2
- package/dist/commands/uninstall.js +0 -145
- package/dist/commands/upgrade.d.ts +0 -14
- package/dist/commands/upgrade.js +0 -217
- package/dist/commands/validate.d.ts +0 -2
- package/dist/commands/validate.js +0 -89
- package/dist/commands/welcome.d.ts +0 -1
- package/dist/commands/welcome.js +0 -86
- package/dist/commands/workflows.d.ts +0 -2
- package/dist/commands/workflows.js +0 -187
- package/dist/deploy/__tests__/deploy-state.test.d.ts +0 -1
- package/dist/deploy/__tests__/deploy-state.test.js +0 -80
- package/dist/deploy/__tests__/deploy-status.test.d.ts +0 -1
- package/dist/deploy/__tests__/deploy-status.test.js +0 -41
- package/dist/deploy/__tests__/detach-args.test.d.ts +0 -1
- package/dist/deploy/__tests__/detach-args.test.js +0 -30
- package/dist/deploy/deploy-state.d.ts +0 -36
- package/dist/deploy/deploy-state.js +0 -63
- package/dist/deploy/deploy-status.d.ts +0 -21
- package/dist/deploy/deploy-status.js +0 -36
- package/dist/deploy/detach.d.ts +0 -32
- package/dist/deploy/detach.js +0 -71
- package/dist/dev/__tests__/attach.test.d.ts +0 -1
- package/dist/dev/__tests__/attach.test.js +0 -296
- package/dist/dev/__tests__/detach.test.d.ts +0 -1
- package/dist/dev/__tests__/detach.test.js +0 -404
- package/dist/dev/__tests__/preview-url-poller.test.d.ts +0 -1
- package/dist/dev/__tests__/preview-url-poller.test.js +0 -149
- package/dist/dev/__tests__/session.test.d.ts +0 -1
- package/dist/dev/__tests__/session.test.js +0 -446
- package/dist/dev/__tests__/startup-sync.test.d.ts +0 -1
- package/dist/dev/__tests__/startup-sync.test.js +0 -32
- package/dist/dev/__tests__/stop.test.d.ts +0 -1
- package/dist/dev/__tests__/stop.test.js +0 -172
- package/dist/dev/attach.d.ts +0 -120
- package/dist/dev/attach.js +0 -269
- package/dist/dev/detach.d.ts +0 -187
- package/dist/dev/detach.js +0 -292
- package/dist/dev/preview-url-poller.d.ts +0 -35
- package/dist/dev/preview-url-poller.js +0 -50
- package/dist/dev/session.d.ts +0 -175
- package/dist/dev/session.js +0 -258
- package/dist/dev/startup-sync.d.ts +0 -23
- package/dist/dev/startup-sync.js +0 -14
- package/dist/dev/stop.d.ts +0 -52
- package/dist/dev/stop.js +0 -101
- package/dist/devtools/registry-data.d.ts +0 -46
- package/dist/devtools/registry-data.js +0 -40
- package/dist/generated/bundled-types.d.ts +0 -1
- package/dist/generated/bundled-types.js +0 -46
- package/dist/generated/version.d.ts +0 -1
- package/dist/generated/version.js +0 -2
- package/dist/git/__tests__/auto-commit.test.d.ts +0 -1
- package/dist/git/__tests__/auto-commit.test.js +0 -431
- package/dist/git/__tests__/classify-sync-error.test.d.ts +0 -1
- package/dist/git/__tests__/classify-sync-error.test.js +0 -57
- package/dist/git/__tests__/credential-helper-e2e.test.d.ts +0 -21
- package/dist/git/__tests__/credential-helper-e2e.test.js +0 -195
- package/dist/git/__tests__/credentials.test.d.ts +0 -1
- package/dist/git/__tests__/credentials.test.js +0 -141
- package/dist/git/__tests__/deploy-guard.test.d.ts +0 -1
- package/dist/git/__tests__/deploy-guard.test.js +0 -61
- package/dist/git/__tests__/ensure-git-credential-helper.test.d.ts +0 -1
- package/dist/git/__tests__/ensure-git-credential-helper.test.js +0 -133
- package/dist/git/__tests__/ensure-runwork-remote.test.d.ts +0 -1
- package/dist/git/__tests__/ensure-runwork-remote.test.js +0 -109
- package/dist/git/__tests__/identity.test.d.ts +0 -1
- package/dist/git/__tests__/identity.test.js +0 -146
- package/dist/git/__tests__/manifest.test.d.ts +0 -1
- package/dist/git/__tests__/manifest.test.js +0 -446
- package/dist/git/__tests__/preflight-resolution.test.d.ts +0 -1
- package/dist/git/__tests__/preflight-resolution.test.js +0 -366
- package/dist/git/__tests__/preflight.test.d.ts +0 -1
- package/dist/git/__tests__/preflight.test.js +0 -36
- package/dist/git/__tests__/repo-config.test.d.ts +0 -1
- package/dist/git/__tests__/repo-config.test.js +0 -26
- package/dist/git/__tests__/sync.test.d.ts +0 -1
- package/dist/git/__tests__/sync.test.js +0 -405
- package/dist/git/auto-commit.d.ts +0 -35
- package/dist/git/auto-commit.js +0 -293
- package/dist/git/classify-sync-error.d.ts +0 -21
- package/dist/git/classify-sync-error.js +0 -111
- package/dist/git/credentials.d.ts +0 -75
- package/dist/git/credentials.js +0 -164
- package/dist/git/critical-files.d.ts +0 -20
- package/dist/git/critical-files.js +0 -68
- package/dist/git/deploy-guard.d.ts +0 -53
- package/dist/git/deploy-guard.js +0 -78
- package/dist/git/identity.d.ts +0 -44
- package/dist/git/identity.js +0 -133
- package/dist/git/preflight.d.ts +0 -57
- package/dist/git/preflight.js +0 -276
- package/dist/git/remote.d.ts +0 -37
- package/dist/git/remote.js +0 -75
- package/dist/git/repo-config.d.ts +0 -8
- package/dist/git/repo-config.js +0 -29
- package/dist/git/sync.d.ts +0 -15
- package/dist/git/sync.js +0 -159
- package/dist/health/__tests__/checks.test.d.ts +0 -1
- package/dist/health/__tests__/checks.test.js +0 -357
- package/dist/health/__tests__/cli-distribution-checks.test.d.ts +0 -1
- package/dist/health/__tests__/cli-distribution-checks.test.js +0 -196
- package/dist/health/__tests__/fix.test.d.ts +0 -1
- package/dist/health/__tests__/fix.test.js +0 -72
- package/dist/health/__tests__/runner-filter.test.d.ts +0 -1
- package/dist/health/__tests__/runner-filter.test.js +0 -61
- package/dist/health/checks.d.ts +0 -42
- package/dist/health/checks.js +0 -636
- package/dist/health/fix.d.ts +0 -22
- package/dist/health/fix.js +0 -62
- package/dist/health/runner.d.ts +0 -8
- package/dist/health/runner.js +0 -47
- package/dist/health/types.d.ts +0 -12
- package/dist/health/types.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/logs/__tests__/tailer-format.test.d.ts +0 -1
- package/dist/logs/__tests__/tailer-format.test.js +0 -43
- package/dist/logs/tailer.d.ts +0 -17
- package/dist/logs/tailer.js +0 -217
- package/dist/sync/__tests__/change-detect.test.d.ts +0 -1
- package/dist/sync/__tests__/change-detect.test.js +0 -123
- package/dist/sync/__tests__/hash.test.d.ts +0 -1
- package/dist/sync/__tests__/hash.test.js +0 -18
- package/dist/sync/change-detect.d.ts +0 -19
- package/dist/sync/change-detect.js +0 -136
- package/dist/sync/conflict-ui.d.ts +0 -5
- package/dist/sync/conflict-ui.js +0 -78
- package/dist/sync/executor.d.ts +0 -23
- package/dist/sync/executor.js +0 -102
- package/dist/sync/hash.d.ts +0 -1
- package/dist/sync/hash.js +0 -4
- package/dist/sync/types.d.ts +0 -24
- package/dist/sync/types.js +0 -1
- package/dist/template/manifest.d.ts +0 -17
- package/dist/template/manifest.js +0 -145
- package/dist/tools/types.d.ts +0 -52
- package/dist/tools/types.js +0 -8
- package/dist/types-manager.d.ts +0 -1
- package/dist/types-manager.js +0 -35
- package/dist/types.d.ts +0 -349
- package/dist/types.js +0 -1
- package/dist/ui/__tests__/banner.test.d.ts +0 -1
- package/dist/ui/__tests__/banner.test.js +0 -98
- package/dist/ui/__tests__/colors.test.d.ts +0 -1
- package/dist/ui/__tests__/colors.test.js +0 -22
- package/dist/ui/__tests__/keyboard.test.d.ts +0 -1
- package/dist/ui/__tests__/keyboard.test.js +0 -34
- package/dist/ui/__tests__/status-line.test.d.ts +0 -1
- package/dist/ui/__tests__/status-line.test.js +0 -54
- package/dist/ui/banner.d.ts +0 -29
- package/dist/ui/banner.js +0 -153
- package/dist/ui/colors.d.ts +0 -4
- package/dist/ui/colors.js +0 -7
- package/dist/ui/keyboard.d.ts +0 -12
- package/dist/ui/keyboard.js +0 -61
- package/dist/ui/status-line.d.ts +0 -6
- package/dist/ui/status-line.js +0 -53
- package/dist/utils/__tests__/format-error.test.d.ts +0 -1
- package/dist/utils/__tests__/format-error.test.js +0 -43
- package/dist/utils/__tests__/help-json.test.d.ts +0 -1
- package/dist/utils/__tests__/help-json.test.js +0 -39
- package/dist/utils/__tests__/http.test.d.ts +0 -1
- package/dist/utils/__tests__/http.test.js +0 -381
- package/dist/utils/__tests__/ignore-matcher.test.d.ts +0 -1
- package/dist/utils/__tests__/ignore-matcher.test.js +0 -205
- package/dist/utils/__tests__/output.test.d.ts +0 -1
- package/dist/utils/__tests__/output.test.js +0 -38
- package/dist/utils/__tests__/prompt.test.d.ts +0 -1
- package/dist/utils/__tests__/prompt.test.js +0 -27
- package/dist/utils/__tests__/resolve.test.d.ts +0 -1
- package/dist/utils/__tests__/resolve.test.js +0 -95
- package/dist/utils/__tests__/sqlite.test.d.ts +0 -1
- package/dist/utils/__tests__/sqlite.test.js +0 -89
- package/dist/utils/agent-guidance.d.ts +0 -47
- package/dist/utils/agent-guidance.js +0 -128
- package/dist/utils/app-info.d.ts +0 -70
- package/dist/utils/app-info.js +0 -167
- package/dist/utils/curl-parser.d.ts +0 -12
- package/dist/utils/curl-parser.js +0 -162
- package/dist/utils/data-input.d.ts +0 -1
- package/dist/utils/data-input.js +0 -38
- package/dist/utils/format-error.d.ts +0 -10
- package/dist/utils/format-error.js +0 -38
- package/dist/utils/fs.d.ts +0 -5
- package/dist/utils/fs.js +0 -23
- package/dist/utils/help-json.d.ts +0 -70
- package/dist/utils/help-json.js +0 -88
- package/dist/utils/http.d.ts +0 -46
- package/dist/utils/http.js +0 -421
- package/dist/utils/ignore-matcher.d.ts +0 -47
- package/dist/utils/ignore-matcher.js +0 -132
- package/dist/utils/output.d.ts +0 -17
- package/dist/utils/output.js +0 -27
- package/dist/utils/prompt.d.ts +0 -5
- package/dist/utils/prompt.js +0 -42
- package/dist/utils/resolve.d.ts +0 -12
- package/dist/utils/resolve.js +0 -52
- package/dist/utils/setup-state.d.ts +0 -3
- package/dist/utils/setup-state.js +0 -19
- package/dist/utils/sqlite-adapter.d.ts +0 -48
- package/dist/utils/sqlite-adapter.js +0 -147
- package/dist/utils/sqlite.d.ts +0 -14
- package/dist/utils/sqlite.js +0 -14
- package/dist/utils/subprocess.d.ts +0 -19
- package/dist/utils/subprocess.js +0 -27
- package/dist/utils/which.d.ts +0 -14
- package/dist/utils/which.js +0 -47
- package/dist/utils/zip.d.ts +0 -7
- package/dist/utils/zip.js +0 -42
- package/dist/validate/__tests__/validate.test.d.ts +0 -1
- package/dist/validate/__tests__/validate.test.js +0 -42
- package/dist/validate/freshness.d.ts +0 -28
- package/dist/validate/freshness.js +0 -48
- package/dist/validate/preview.d.ts +0 -21
- package/dist/validate/preview.js +0 -39
- package/dist/workspace/resolve.d.ts +0 -19
- package/dist/workspace/resolve.js +0 -100
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* End-to-end auth test.
|
|
3
|
-
*
|
|
4
|
-
* Spins up a tiny HTTP server that mimics the runwork.ai git endpoint's
|
|
5
|
-
* auth contract (HTTP Basic, any username, password = API key) and runs a
|
|
6
|
-
* real `git ls-remote` against it through a credential-helper script. This
|
|
7
|
-
* verifies the entire chain we depend on:
|
|
8
|
-
*
|
|
9
|
-
* git -> credential.<origin>.helper -> our helper script -> our helper's
|
|
10
|
-
* protocol output -> git constructs Basic auth -> server accepts it.
|
|
11
|
-
*
|
|
12
|
-
* Catches the class of bugs we've burned hours on (Pass 1's PATH issues,
|
|
13
|
-
* Pass 2's "logged in but no helper registered") *before* a user hits them.
|
|
14
|
-
*
|
|
15
|
-
* Note: this test exercises the contract our credential helper depends on,
|
|
16
|
-
* not the helper function itself -- credentials.test.ts already covers
|
|
17
|
-
* `handleGitCredentialRequest()` in isolation. Together they prove that
|
|
18
|
-
* (a) the helper produces the right protocol output and (b) git+server
|
|
19
|
-
* accept that output as Basic auth.
|
|
20
|
-
*/
|
|
21
|
-
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
22
|
-
import { execFile } from 'child_process';
|
|
23
|
-
import { promisify } from 'util';
|
|
24
|
-
import { mkdtempSync, writeFileSync, rmSync, chmodSync, existsSync } from 'fs';
|
|
25
|
-
import { join } from 'path';
|
|
26
|
-
import { tmpdir } from 'os';
|
|
27
|
-
import { createServer } from 'http';
|
|
28
|
-
import { probeGit } from '../preflight.js';
|
|
29
|
-
const execFileAsync = promisify(execFile);
|
|
30
|
-
const EXPECTED_API_KEY = 'test-api-key-abcd1234';
|
|
31
|
-
/**
|
|
32
|
-
* Minimal HTTP server that:
|
|
33
|
-
* - Returns 401 + WWW-Authenticate when no Basic auth is supplied
|
|
34
|
-
* - Returns 401 when password (the API key) is wrong; username is ignored
|
|
35
|
-
* - Returns 200 + a valid empty-refs pkt-line response when auth is correct
|
|
36
|
-
*
|
|
37
|
-
* The pkt-line response is the smallest valid output for `info/refs?
|
|
38
|
-
* service=git-upload-pack` -- the service announcement plus a flush packet.
|
|
39
|
-
* That's enough for `git ls-remote` to terminate successfully.
|
|
40
|
-
*/
|
|
41
|
-
function createBasicAuthGitServer(attempts) {
|
|
42
|
-
return new Promise((resolve) => {
|
|
43
|
-
const server = createServer((req, res) => {
|
|
44
|
-
const authHeader = req.headers['authorization'];
|
|
45
|
-
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
|
46
|
-
attempts.push({ hasAuthHeader: false });
|
|
47
|
-
res.writeHead(401, {
|
|
48
|
-
'WWW-Authenticate': 'Basic realm="Test"',
|
|
49
|
-
'Content-Type': 'text/plain',
|
|
50
|
-
});
|
|
51
|
-
res.end('Authentication required');
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
const decoded = Buffer.from(authHeader.slice(6), 'base64').toString('utf-8');
|
|
55
|
-
const colonIdx = decoded.indexOf(':');
|
|
56
|
-
const username = colonIdx >= 0 ? decoded.slice(0, colonIdx) : decoded;
|
|
57
|
-
const password = colonIdx >= 0 ? decoded.slice(colonIdx + 1) : '';
|
|
58
|
-
attempts.push({ hasAuthHeader: true, username, password });
|
|
59
|
-
if (password !== EXPECTED_API_KEY) {
|
|
60
|
-
res.writeHead(401, {
|
|
61
|
-
'WWW-Authenticate': 'Basic realm="Test"',
|
|
62
|
-
'Content-Type': 'text/plain',
|
|
63
|
-
});
|
|
64
|
-
res.end('Invalid API key');
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
// Minimal valid info/refs response for git-upload-pack with no refs.
|
|
68
|
-
// Shape:
|
|
69
|
-
// 001e# service=git-upload-pack\n
|
|
70
|
-
// 0000
|
|
71
|
-
// 0000 (no refs)
|
|
72
|
-
const service = '# service=git-upload-pack\n';
|
|
73
|
-
const pktServiceLength = (service.length + 4).toString(16).padStart(4, '0');
|
|
74
|
-
const flush = '0000';
|
|
75
|
-
const body = `${pktServiceLength}${service}${flush}${flush}`;
|
|
76
|
-
res.writeHead(200, {
|
|
77
|
-
'Content-Type': 'application/x-git-upload-pack-advertisement',
|
|
78
|
-
'Cache-Control': 'no-cache',
|
|
79
|
-
});
|
|
80
|
-
res.end(body);
|
|
81
|
-
});
|
|
82
|
-
server.listen(0, '127.0.0.1', () => resolve(server));
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Write a credential helper script into `dir` and return its path. The
|
|
87
|
-
* script ignores stdin (test fixtures don't need to honour git's
|
|
88
|
-
* action/host inputs) and prints the test's API key. Bash on macOS/Linux
|
|
89
|
-
* and Git Bash on Windows both run the resulting `!"<path>"` invocation
|
|
90
|
-
* the same way.
|
|
91
|
-
*/
|
|
92
|
-
function writeHelperScript(dir, apiKey) {
|
|
93
|
-
const scriptPath = join(dir, 'fake-helper.sh');
|
|
94
|
-
const body = [
|
|
95
|
-
'#!/usr/bin/env bash',
|
|
96
|
-
'# Test fixture: emits the git credential helper "get" protocol output',
|
|
97
|
-
'# with a hardcoded API key. The "get" arg is the only one git invokes',
|
|
98
|
-
'# during a fetch; "store"/"erase" are no-ops.',
|
|
99
|
-
'if [ "$1" != "get" ]; then exit 0; fi',
|
|
100
|
-
'# Drain stdin so git\'s pipe doesn\'t stall.',
|
|
101
|
-
'cat > /dev/null',
|
|
102
|
-
'echo "username=runwork"',
|
|
103
|
-
`echo "password=${apiKey}"`,
|
|
104
|
-
'',
|
|
105
|
-
].join('\n');
|
|
106
|
-
writeFileSync(scriptPath, body, { mode: 0o755 });
|
|
107
|
-
// chmod is no-op on Windows but won't error.
|
|
108
|
-
chmodSync(scriptPath, 0o755);
|
|
109
|
-
return scriptPath;
|
|
110
|
-
}
|
|
111
|
-
describe('git credential helper e2e (Basic auth contract)', () => {
|
|
112
|
-
const gitProbe = probeGit();
|
|
113
|
-
const skipReason = !gitProbe.installed ? 'git not installed' : null;
|
|
114
|
-
let tmpDir;
|
|
115
|
-
let homeDir;
|
|
116
|
-
let xdgConfigHome;
|
|
117
|
-
let scriptPath;
|
|
118
|
-
let server;
|
|
119
|
-
let serverUrl;
|
|
120
|
-
let attempts = [];
|
|
121
|
-
beforeAll(async () => {
|
|
122
|
-
if (skipReason)
|
|
123
|
-
return;
|
|
124
|
-
tmpDir = mkdtempSync(join(tmpdir(), 'runwork-helper-e2e-'));
|
|
125
|
-
homeDir = join(tmpDir, 'home');
|
|
126
|
-
xdgConfigHome = join(homeDir, '.config');
|
|
127
|
-
// git -c gc.auto=0 needs ~/.gitconfig to exist when GIT_CONFIG_GLOBAL is
|
|
128
|
-
// pointed at a path that doesn't yet exist; pre-create the dir and
|
|
129
|
-
// empty file.
|
|
130
|
-
const gitConfigGlobal = join(homeDir, '.gitconfig');
|
|
131
|
-
require('fs').mkdirSync(homeDir, { recursive: true });
|
|
132
|
-
writeFileSync(gitConfigGlobal, '');
|
|
133
|
-
scriptPath = writeHelperScript(tmpDir, EXPECTED_API_KEY);
|
|
134
|
-
server = await createBasicAuthGitServer(attempts);
|
|
135
|
-
const addr = server.address();
|
|
136
|
-
serverUrl = `http://127.0.0.1:${addr.port}`;
|
|
137
|
-
// Register the credential helper globally in the isolated git config.
|
|
138
|
-
// We use the unscoped `credential.helper` (not `credential.<url>.helper`)
|
|
139
|
-
// because (a) the test config is fully isolated so we don't risk
|
|
140
|
-
// bleeding into other host auth flows and (b) git's URL matching for
|
|
141
|
-
// scoped helpers has subtleties that aren't what we're testing here --
|
|
142
|
-
// we're testing the helper *contract*, not git's URL matcher.
|
|
143
|
-
const gitBin = gitProbe.path ?? 'git';
|
|
144
|
-
await execFileAsync(gitBin, [
|
|
145
|
-
'config', '--file', gitConfigGlobal,
|
|
146
|
-
'credential.helper', `!"${scriptPath}"`,
|
|
147
|
-
]);
|
|
148
|
-
});
|
|
149
|
-
afterAll(() => {
|
|
150
|
-
if (server)
|
|
151
|
-
server.close();
|
|
152
|
-
if (tmpDir && existsSync(tmpDir))
|
|
153
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
154
|
-
});
|
|
155
|
-
it.skipIf(skipReason)('git ls-remote authenticates via the credential helper end-to-end', async () => {
|
|
156
|
-
const gitBin = gitProbe.path ?? 'git';
|
|
157
|
-
// Use GIT_CONFIG_GLOBAL to isolate from the user's real ~/.gitconfig
|
|
158
|
-
// (which may have an auth helper registered for some other host).
|
|
159
|
-
const env = {
|
|
160
|
-
...process.env,
|
|
161
|
-
GIT_CONFIG_GLOBAL: join(homeDir, '.gitconfig'),
|
|
162
|
-
GIT_CONFIG_SYSTEM: '/dev/null',
|
|
163
|
-
XDG_CONFIG_HOME: xdgConfigHome,
|
|
164
|
-
HOME: homeDir,
|
|
165
|
-
// Force git to never prompt -- if the helper fails, we want the
|
|
166
|
-
// command to exit non-zero, not hang.
|
|
167
|
-
GIT_TERMINAL_PROMPT: '0',
|
|
168
|
-
};
|
|
169
|
-
const path = '/test-workspace/test-app';
|
|
170
|
-
const url = `${serverUrl}${path}`;
|
|
171
|
-
// ls-remote against the fake server. Should exit 0 (server accepted
|
|
172
|
-
// auth and returned a valid -- empty -- ref advertisement).
|
|
173
|
-
const result = await execFileAsync(gitBin, ['ls-remote', url], { env });
|
|
174
|
-
expect(result.stdout).toBe('');
|
|
175
|
-
// No refs in the test response, so stdout is empty.
|
|
176
|
-
// Verify the server actually saw a Basic-auth attempt with our key.
|
|
177
|
-
const successAttempt = attempts.find((a) => a.hasAuthHeader && a.password === EXPECTED_API_KEY);
|
|
178
|
-
expect(successAttempt).toBeDefined();
|
|
179
|
-
expect(successAttempt.username).toBe('runwork');
|
|
180
|
-
}, 20_000);
|
|
181
|
-
it.skipIf(skipReason)('server rejects requests with no auth (sanity: contract is enforced)', async () => {
|
|
182
|
-
// Direct fetch with no Authorization header should 401.
|
|
183
|
-
const response = await fetch(`${serverUrl}/whatever`);
|
|
184
|
-
expect(response.status).toBe(401);
|
|
185
|
-
expect(response.headers.get('www-authenticate')).toContain('Basic');
|
|
186
|
-
});
|
|
187
|
-
it.skipIf(skipReason)('server rejects requests with the wrong API key (sanity: contract is enforced)', async () => {
|
|
188
|
-
const response = await fetch(`${serverUrl}/whatever`, {
|
|
189
|
-
headers: {
|
|
190
|
-
authorization: `Basic ${Buffer.from('runwork:wrong-key').toString('base64')}`,
|
|
191
|
-
},
|
|
192
|
-
});
|
|
193
|
-
expect(response.status).toBe(401);
|
|
194
|
-
});
|
|
195
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
vi.mock('child_process');
|
|
3
|
-
vi.mock('../../auth/store.js', () => ({
|
|
4
|
-
getCredentials: vi.fn(),
|
|
5
|
-
}));
|
|
6
|
-
const mockExecFileSync = (await import('child_process')).execFileSync;
|
|
7
|
-
const { getCredentials: mockGetCredentials } = await import('../../auth/store.js');
|
|
8
|
-
const { configureGitCredentials, removeGitCredentials, handleGitCredentialRequest, buildHelperValue, } = await import('../credentials.js');
|
|
9
|
-
describe('git/credentials', () => {
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
vi.clearAllMocks();
|
|
12
|
-
});
|
|
13
|
-
describe('buildHelperValue()', () => {
|
|
14
|
-
it('wraps the execPath in quotes and prefixes with !', () => {
|
|
15
|
-
expect(buildHelperValue('/usr/local/bin/runwork')).toBe('!"/usr/local/bin/runwork" git-credential-helper');
|
|
16
|
-
});
|
|
17
|
-
it('normalises Windows backslashes to forward slashes for .gitconfig safety', () => {
|
|
18
|
-
expect(buildHelperValue('C:\\Users\\test\\.runwork\\bin\\runwork.exe')).toBe('!"C:/Users/test/.runwork/bin/runwork.exe" git-credential-helper');
|
|
19
|
-
});
|
|
20
|
-
it('survives spaces in install paths via quoting', () => {
|
|
21
|
-
expect(buildHelperValue('C:\\Program Files\\Runwork\\runwork.exe')).toBe('!"C:/Program Files/Runwork/runwork.exe" git-credential-helper');
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
describe('configureGitCredentials()', () => {
|
|
25
|
-
it('registers the helper using process.execPath, not bare "runwork"', async () => {
|
|
26
|
-
await configureGitCredentials('https://runwork.ai/api/git/ws/app');
|
|
27
|
-
// Don't pin process.execPath itself (varies per machine), but
|
|
28
|
-
// assert the value follows the absolute-path shape so PATH-relative
|
|
29
|
-
// regressions are caught.
|
|
30
|
-
const call = mockExecFileSync.mock.calls.find((c) => c[0] === 'git' && c[1]?.includes('--global'));
|
|
31
|
-
expect(call).toBeDefined();
|
|
32
|
-
const args = call[1];
|
|
33
|
-
expect(args).toContain('config');
|
|
34
|
-
expect(args).toContain('--global');
|
|
35
|
-
expect(args).toContain('credential.https://runwork.ai.helper');
|
|
36
|
-
const helperValue = args[args.length - 1];
|
|
37
|
-
expect(helperValue).toMatch(/^!"[^"]+" git-credential-helper$/);
|
|
38
|
-
expect(helperValue).not.toBe('!runwork git-credential-helper');
|
|
39
|
-
// The path inside the quotes should be process.execPath, normalised.
|
|
40
|
-
expect(helperValue).toContain(process.execPath.replace(/\\/g, '/'));
|
|
41
|
-
});
|
|
42
|
-
it('extracts origin from full URL', async () => {
|
|
43
|
-
await configureGitCredentials('https://custom.runwork.dev/api/git/ws-1/app-1');
|
|
44
|
-
const call = mockExecFileSync.mock.calls[0];
|
|
45
|
-
const args = call[1];
|
|
46
|
-
expect(args[2]).toBe('credential.https://custom.runwork.dev.helper');
|
|
47
|
-
});
|
|
48
|
-
it('handles URL with port', async () => {
|
|
49
|
-
await configureGitCredentials('https://localhost:8787/api/git/ws/app');
|
|
50
|
-
const call = mockExecFileSync.mock.calls[0];
|
|
51
|
-
const args = call[1];
|
|
52
|
-
expect(args[2]).toBe('credential.https://localhost:8787.helper');
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
describe('removeGitCredentials()', () => {
|
|
56
|
-
it('calls git config --unset', async () => {
|
|
57
|
-
await removeGitCredentials('https://runwork.ai');
|
|
58
|
-
expect(mockExecFileSync).toHaveBeenCalledWith('git', [
|
|
59
|
-
'config',
|
|
60
|
-
'--global',
|
|
61
|
-
'--unset',
|
|
62
|
-
'credential.https://runwork.ai.helper',
|
|
63
|
-
], { stdio: 'pipe', windowsHide: true });
|
|
64
|
-
});
|
|
65
|
-
it('ignores errors silently', async () => {
|
|
66
|
-
mockExecFileSync.mockImplementation(() => {
|
|
67
|
-
throw new Error('config key not found');
|
|
68
|
-
});
|
|
69
|
-
// Should not throw
|
|
70
|
-
await expect(removeGitCredentials('https://runwork.ai')).resolves.toBeUndefined();
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
describe('handleGitCredentialRequest()', () => {
|
|
74
|
-
it('ignores non-get actions (store)', async () => {
|
|
75
|
-
const writeSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
76
|
-
await handleGitCredentialRequest('store');
|
|
77
|
-
expect(writeSpy).not.toHaveBeenCalled();
|
|
78
|
-
writeSpy.mockRestore();
|
|
79
|
-
});
|
|
80
|
-
it('ignores non-get actions (erase)', async () => {
|
|
81
|
-
const writeSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
82
|
-
await handleGitCredentialRequest('erase');
|
|
83
|
-
expect(writeSpy).not.toHaveBeenCalled();
|
|
84
|
-
writeSpy.mockRestore();
|
|
85
|
-
});
|
|
86
|
-
it('exits with code 1 when no credentials stored', async () => {
|
|
87
|
-
vi.mocked(mockGetCredentials).mockReturnValue(null);
|
|
88
|
-
// Mock stdin to provide input then end
|
|
89
|
-
const mockStdin = {
|
|
90
|
-
setEncoding: vi.fn(),
|
|
91
|
-
on: vi.fn((event, cb) => {
|
|
92
|
-
if (event === 'data')
|
|
93
|
-
cb('protocol=https\nhost=runwork.ai\n');
|
|
94
|
-
if (event === 'end')
|
|
95
|
-
cb();
|
|
96
|
-
}),
|
|
97
|
-
readableEnded: false,
|
|
98
|
-
};
|
|
99
|
-
const originalStdin = process.stdin;
|
|
100
|
-
Object.defineProperty(process, 'stdin', { value: mockStdin, writable: true });
|
|
101
|
-
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {
|
|
102
|
-
throw new Error('process.exit');
|
|
103
|
-
}));
|
|
104
|
-
await expect(handleGitCredentialRequest('get')).rejects.toThrow('process.exit');
|
|
105
|
-
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
106
|
-
exitSpy.mockRestore();
|
|
107
|
-
Object.defineProperty(process, 'stdin', { value: originalStdin, writable: true });
|
|
108
|
-
});
|
|
109
|
-
it('outputs correct credential format when credentials exist', async () => {
|
|
110
|
-
vi.mocked(mockGetCredentials).mockReturnValue({
|
|
111
|
-
apiKey: 'my-api-key',
|
|
112
|
-
email: 'test@example.com',
|
|
113
|
-
baseUrl: 'https://runwork.ai',
|
|
114
|
-
});
|
|
115
|
-
const mockStdin = {
|
|
116
|
-
setEncoding: vi.fn(),
|
|
117
|
-
on: vi.fn((event, cb) => {
|
|
118
|
-
if (event === 'data')
|
|
119
|
-
cb('protocol=https\nhost=runwork.ai\n');
|
|
120
|
-
if (event === 'end')
|
|
121
|
-
cb();
|
|
122
|
-
}),
|
|
123
|
-
readableEnded: false,
|
|
124
|
-
};
|
|
125
|
-
const originalStdin = process.stdin;
|
|
126
|
-
Object.defineProperty(process, 'stdin', { value: mockStdin, writable: true });
|
|
127
|
-
const writes = [];
|
|
128
|
-
const writeSpy = vi.spyOn(process.stdout, 'write').mockImplementation((data) => {
|
|
129
|
-
writes.push(data.toString());
|
|
130
|
-
return true;
|
|
131
|
-
});
|
|
132
|
-
await handleGitCredentialRequest('get');
|
|
133
|
-
expect(writes).toContain('protocol=https\n');
|
|
134
|
-
expect(writes).toContain('host=runwork.ai\n');
|
|
135
|
-
expect(writes).toContain('username=runwork\n');
|
|
136
|
-
expect(writes).toContain('password=my-api-key\n');
|
|
137
|
-
writeSpy.mockRestore();
|
|
138
|
-
Object.defineProperty(process, 'stdin', { value: originalStdin, writable: true });
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { isDirtyOrAhead, shouldWarnBeforeDeploy, } from '../deploy-guard.js';
|
|
3
|
-
import { SESSION_FILE_SCHEMA_VERSION, } from '../../dev/session.js';
|
|
4
|
-
function makeSessionFile(overrides = {}) {
|
|
5
|
-
return {
|
|
6
|
-
version: SESSION_FILE_SCHEMA_VERSION,
|
|
7
|
-
pid: process.pid,
|
|
8
|
-
sessionId: 'sess_test',
|
|
9
|
-
appId: 'test-app',
|
|
10
|
-
previewUrl: 'https://test.preview.runwork.dev',
|
|
11
|
-
startedAt: 1_700_000_000_000,
|
|
12
|
-
bootTime: 1_600_000_000_000,
|
|
13
|
-
cliVersion: '0.10.2',
|
|
14
|
-
mode: 'foreground',
|
|
15
|
-
...overrides,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
const noSession = { state: 'none' };
|
|
19
|
-
const aliveSyncing = { state: 'alive', file: makeSessionFile({ noSync: false }) };
|
|
20
|
-
const aliveNoSync = { state: 'alive', file: makeSessionFile({ noSync: true }) };
|
|
21
|
-
const aliveLegacy = { state: 'alive', file: makeSessionFile() }; // noSync undefined
|
|
22
|
-
const staleSession = { state: 'stale', reason: 'pid-dead', file: makeSessionFile() };
|
|
23
|
-
const dirty = { dirty: true, ahead: false };
|
|
24
|
-
const ahead = { dirty: false, ahead: true };
|
|
25
|
-
const clean = { dirty: false, ahead: false };
|
|
26
|
-
describe('isDirtyOrAhead()', () => {
|
|
27
|
-
it('is true when dirty', () => {
|
|
28
|
-
expect(isDirtyOrAhead(dirty)).toBe(true);
|
|
29
|
-
});
|
|
30
|
-
it('is true when ahead', () => {
|
|
31
|
-
expect(isDirtyOrAhead(ahead)).toBe(true);
|
|
32
|
-
});
|
|
33
|
-
it('is false when clean and not ahead', () => {
|
|
34
|
-
expect(isDirtyOrAhead(clean)).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
describe('shouldWarnBeforeDeploy()', () => {
|
|
38
|
-
it('warns: dirty tree + no session', () => {
|
|
39
|
-
expect(shouldWarnBeforeDeploy(dirty, noSession)).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
it('does not warn: dirty tree + active auto-syncing session', () => {
|
|
42
|
-
expect(shouldWarnBeforeDeploy(dirty, aliveSyncing)).toBe(false);
|
|
43
|
-
});
|
|
44
|
-
it('does not warn: dirty tree + active legacy session (noSync undefined treated as syncing)', () => {
|
|
45
|
-
expect(shouldWarnBeforeDeploy(dirty, aliveLegacy)).toBe(false);
|
|
46
|
-
});
|
|
47
|
-
it('warns: dirty tree + active no-sync session', () => {
|
|
48
|
-
expect(shouldWarnBeforeDeploy(dirty, aliveNoSync)).toBe(true);
|
|
49
|
-
});
|
|
50
|
-
it('warns: ahead tree + no session', () => {
|
|
51
|
-
expect(shouldWarnBeforeDeploy(ahead, noSession)).toBe(true);
|
|
52
|
-
});
|
|
53
|
-
it('warns: dirty tree + stale session (watcher not actually running)', () => {
|
|
54
|
-
expect(shouldWarnBeforeDeploy(dirty, staleSession)).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
it('does not warn: clean + not ahead, regardless of session', () => {
|
|
57
|
-
expect(shouldWarnBeforeDeploy(clean, noSession)).toBe(false);
|
|
58
|
-
expect(shouldWarnBeforeDeploy(clean, aliveSyncing)).toBe(false);
|
|
59
|
-
expect(shouldWarnBeforeDeploy(clean, aliveNoSync)).toBe(false);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
vi.mock('child_process');
|
|
3
|
-
vi.mock('fs', () => ({
|
|
4
|
-
existsSync: vi.fn(),
|
|
5
|
-
}));
|
|
6
|
-
vi.mock('../../auth/store.js', () => ({
|
|
7
|
-
getCredentials: vi.fn(),
|
|
8
|
-
}));
|
|
9
|
-
const mockExecFileSync = (await import('child_process')).execFileSync;
|
|
10
|
-
const { existsSync: mockExistsSync } = await import('fs');
|
|
11
|
-
const { lookupCredentialHelper, helperBinaryStatus, ensureGitCredentialHelper, } = await import('../credentials.js');
|
|
12
|
-
/** Build a `git config --get-regexp` stdout block from full config lines. */
|
|
13
|
-
function gitConfigOutput(...lines) {
|
|
14
|
-
return Buffer.from(lines.join('\n') + '\n');
|
|
15
|
-
}
|
|
16
|
-
/** True when a recorded execFileSync call wrote a credential helper config. */
|
|
17
|
-
function isHelperWrite(call) {
|
|
18
|
-
const args = call[1];
|
|
19
|
-
return call[0] === 'git'
|
|
20
|
-
&& Array.isArray(args)
|
|
21
|
-
&& args.includes('--global')
|
|
22
|
-
&& args.some(a => /^credential\..*\.helper$/.test(a))
|
|
23
|
-
&& !args.includes('--get-regexp')
|
|
24
|
-
&& !args.includes('--unset');
|
|
25
|
-
}
|
|
26
|
-
describe('git/credentials lazy-ensure', () => {
|
|
27
|
-
beforeEach(() => {
|
|
28
|
-
vi.clearAllMocks();
|
|
29
|
-
});
|
|
30
|
-
describe('lookupCredentialHelper()', () => {
|
|
31
|
-
it('returns "none" when git has no matching entries (get-regexp exits non-zero)', () => {
|
|
32
|
-
mockExecFileSync.mockImplementation(() => {
|
|
33
|
-
throw new Error('exit 1'); // git config --get-regexp: no match
|
|
34
|
-
});
|
|
35
|
-
expect(lookupCredentialHelper('https://runwork.ai')).toEqual({ status: 'none' });
|
|
36
|
-
});
|
|
37
|
-
it('returns "unscoped" when entries exist but none match the origin', () => {
|
|
38
|
-
mockExecFileSync.mockReturnValue(gitConfigOutput('credential.https://runwork-staging.example.com.helper !runwork git-credential-helper'));
|
|
39
|
-
expect(lookupCredentialHelper('https://runwork.ai')).toEqual({
|
|
40
|
-
status: 'unscoped',
|
|
41
|
-
otherCount: 1,
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
it('returns "registered" with the helper value when a scoped entry exists', () => {
|
|
45
|
-
mockExecFileSync.mockReturnValue(gitConfigOutput('credential.https://runwork.ai.helper !"/usr/local/bin/runwork" git-credential-helper'));
|
|
46
|
-
expect(lookupCredentialHelper('https://runwork.ai')).toEqual({
|
|
47
|
-
status: 'registered',
|
|
48
|
-
value: '!"/usr/local/bin/runwork" git-credential-helper',
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
it('matches the correct origin among multiple entries', () => {
|
|
52
|
-
mockExecFileSync.mockReturnValue(gitConfigOutput('credential.https://runwork-staging.example.com.helper !runwork git-credential-helper', 'credential.https://runwork.ai.helper !"/usr/local/bin/runwork" git-credential-helper'));
|
|
53
|
-
const result = lookupCredentialHelper('https://runwork.ai');
|
|
54
|
-
expect(result.status).toBe('registered');
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
describe('helperBinaryStatus()', () => {
|
|
58
|
-
it('trusts PATH-relative helper values (no on-disk check)', () => {
|
|
59
|
-
expect(helperBinaryStatus('!runwork git-credential-helper')).toEqual({ ok: true, path: null });
|
|
60
|
-
expect(mockExistsSync).not.toHaveBeenCalled();
|
|
61
|
-
});
|
|
62
|
-
it('reports ok when an absolute-path binary exists', () => {
|
|
63
|
-
vi.mocked(mockExistsSync).mockReturnValue(true);
|
|
64
|
-
expect(helperBinaryStatus('!"/usr/local/bin/runwork" git-credential-helper')).toEqual({
|
|
65
|
-
ok: true,
|
|
66
|
-
path: '/usr/local/bin/runwork',
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
it('reports not-ok with the path when an absolute-path binary is missing', () => {
|
|
70
|
-
vi.mocked(mockExistsSync).mockReturnValue(false);
|
|
71
|
-
expect(helperBinaryStatus('!"/old/path/runwork" git-credential-helper')).toEqual({
|
|
72
|
-
ok: false,
|
|
73
|
-
path: '/old/path/runwork',
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
it('handles Windows absolute paths', () => {
|
|
77
|
-
vi.mocked(mockExistsSync).mockReturnValue(true);
|
|
78
|
-
const result = helperBinaryStatus('!"C:/Users/test/.runwork/bin/runwork.exe" git-credential-helper');
|
|
79
|
-
expect(result.ok).toBe(true);
|
|
80
|
-
expect(result.path).toBe('C:/Users/test/.runwork/bin/runwork.exe');
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
describe('ensureGitCredentialHelper()', () => {
|
|
84
|
-
it('is a no-op when a valid scoped helper is already registered', async () => {
|
|
85
|
-
mockExecFileSync.mockImplementation((_file, args) => {
|
|
86
|
-
if (args.includes('--get-regexp')) {
|
|
87
|
-
return gitConfigOutput('credential.https://runwork.ai.helper !runwork git-credential-helper');
|
|
88
|
-
}
|
|
89
|
-
return Buffer.from('');
|
|
90
|
-
});
|
|
91
|
-
await ensureGitCredentialHelper('https://runwork.ai/api/git/ws/app');
|
|
92
|
-
// No registration write should have happened.
|
|
93
|
-
expect(mockExecFileSync.mock.calls.some(isHelperWrite)).toBe(false);
|
|
94
|
-
});
|
|
95
|
-
it('registers the helper when none is present (the get_cli_setup path)', async () => {
|
|
96
|
-
mockExecFileSync.mockImplementation((_file, args) => {
|
|
97
|
-
if (args.includes('--get-regexp')) {
|
|
98
|
-
throw new Error('exit 1'); // nothing registered yet
|
|
99
|
-
}
|
|
100
|
-
return Buffer.from('');
|
|
101
|
-
});
|
|
102
|
-
await ensureGitCredentialHelper('https://runwork.ai/api/git/ws/app');
|
|
103
|
-
const write = mockExecFileSync.mock.calls.find(isHelperWrite);
|
|
104
|
-
expect(write).toBeDefined();
|
|
105
|
-
expect(write[1]).toContain('credential.https://runwork.ai.helper');
|
|
106
|
-
});
|
|
107
|
-
it('registers when entries exist but none are scoped to this origin', async () => {
|
|
108
|
-
mockExecFileSync.mockImplementation((_file, args) => {
|
|
109
|
-
if (args.includes('--get-regexp')) {
|
|
110
|
-
return gitConfigOutput('credential.https://other.example.com.helper !runwork git-credential-helper');
|
|
111
|
-
}
|
|
112
|
-
return Buffer.from('');
|
|
113
|
-
});
|
|
114
|
-
await ensureGitCredentialHelper('https://runwork.ai');
|
|
115
|
-
expect(mockExecFileSync.mock.calls.some(isHelperWrite)).toBe(true);
|
|
116
|
-
});
|
|
117
|
-
it('repairs a stale helper that points at a missing binary', async () => {
|
|
118
|
-
vi.mocked(mockExistsSync).mockReturnValue(false);
|
|
119
|
-
mockExecFileSync.mockImplementation((_file, args) => {
|
|
120
|
-
if (args.includes('--get-regexp')) {
|
|
121
|
-
return gitConfigOutput('credential.https://runwork.ai.helper !"/old/path/runwork" git-credential-helper');
|
|
122
|
-
}
|
|
123
|
-
return Buffer.from('');
|
|
124
|
-
});
|
|
125
|
-
await ensureGitCredentialHelper('https://runwork.ai');
|
|
126
|
-
expect(mockExecFileSync.mock.calls.some(isHelperWrite)).toBe(true);
|
|
127
|
-
});
|
|
128
|
-
it('is a no-op for an unparseable baseUrl', async () => {
|
|
129
|
-
await ensureGitCredentialHelper('not a url');
|
|
130
|
-
expect(mockExecFileSync).not.toHaveBeenCalled();
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
vi.mock('child_process');
|
|
3
|
-
const mockExecFileSync = (await import('child_process')).execFileSync;
|
|
4
|
-
const { normalizeRemoteUrl, listRemotes, ensureRunworkRemote, } = await import('../remote.js');
|
|
5
|
-
/** Build `git remote -v` stdout from name->url pairs (fetch + push lines). */
|
|
6
|
-
function remoteVerbose(pairs) {
|
|
7
|
-
const lines = [];
|
|
8
|
-
for (const [name, url] of Object.entries(pairs)) {
|
|
9
|
-
lines.push(`${name}\t${url} (fetch)`);
|
|
10
|
-
lines.push(`${name}\t${url} (push)`);
|
|
11
|
-
}
|
|
12
|
-
return Buffer.from(lines.join('\n') + '\n');
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Drive ensureRunworkRemote/listRemotes against a fixed `git remote -v`
|
|
16
|
-
* result, recording any set-url/add mutations.
|
|
17
|
-
*/
|
|
18
|
-
function withRemotes(pairs) {
|
|
19
|
-
const setUrl = [];
|
|
20
|
-
const add = [];
|
|
21
|
-
mockExecFileSync.mockImplementation((_file, args) => {
|
|
22
|
-
if (args[0] === 'remote' && args[1] === '-v') {
|
|
23
|
-
return remoteVerbose(pairs);
|
|
24
|
-
}
|
|
25
|
-
if (args[0] === 'remote' && args[1] === 'set-url') {
|
|
26
|
-
setUrl.push(args);
|
|
27
|
-
return Buffer.from('');
|
|
28
|
-
}
|
|
29
|
-
if (args[0] === 'remote' && args[1] === 'add') {
|
|
30
|
-
add.push(args);
|
|
31
|
-
return Buffer.from('');
|
|
32
|
-
}
|
|
33
|
-
return Buffer.from('');
|
|
34
|
-
});
|
|
35
|
-
return { setUrl, add };
|
|
36
|
-
}
|
|
37
|
-
const CANONICAL = 'https://runwork.ai/api/git/ws/app';
|
|
38
|
-
describe('git/remote', () => {
|
|
39
|
-
beforeEach(() => {
|
|
40
|
-
vi.clearAllMocks();
|
|
41
|
-
});
|
|
42
|
-
describe('normalizeRemoteUrl()', () => {
|
|
43
|
-
it('strips a trailing .git', () => {
|
|
44
|
-
expect(normalizeRemoteUrl('https://runwork.ai/api/git/ws/app.git')).toBe(CANONICAL);
|
|
45
|
-
});
|
|
46
|
-
it('strips trailing slashes and lower-cases the host (path preserved)', () => {
|
|
47
|
-
expect(normalizeRemoteUrl('https://RunWork.ai/api/git/ws/app/')).toBe(CANONICAL);
|
|
48
|
-
});
|
|
49
|
-
it('drops userinfo so embedded creds do not break equality', () => {
|
|
50
|
-
expect(normalizeRemoteUrl('https://x-access-token:KEY@runwork.ai/api/git/ws/app')).toBe(CANONICAL);
|
|
51
|
-
});
|
|
52
|
-
it('falls back to a lower-cased trim for scp-style remotes', () => {
|
|
53
|
-
expect(normalizeRemoteUrl('git@GitHub.com:Foo/Bar.git')).toBe('git@github.com:foo/bar');
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
describe('listRemotes()', () => {
|
|
57
|
-
it('parses name -> fetch URL, ignoring push duplicates', () => {
|
|
58
|
-
withRemotes({ origin: 'https://example.com/a', runwork: CANONICAL });
|
|
59
|
-
expect(listRemotes('/repo')).toEqual({
|
|
60
|
-
origin: 'https://example.com/a',
|
|
61
|
-
runwork: CANONICAL,
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
it('returns {} when git fails (not a repo)', () => {
|
|
65
|
-
mockExecFileSync.mockImplementation(() => { throw new Error('not a git repo'); });
|
|
66
|
-
expect(listRemotes('/repo')).toEqual({});
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
describe('ensureRunworkRemote()', () => {
|
|
70
|
-
it('is a no-op when runwork already points at the canonical URL', () => {
|
|
71
|
-
const { setUrl, add } = withRemotes({ runwork: CANONICAL });
|
|
72
|
-
expect(ensureRunworkRemote('/repo', CANONICAL)).toEqual({ action: 'noop' });
|
|
73
|
-
expect(setUrl).toHaveLength(0);
|
|
74
|
-
expect(add).toHaveLength(0);
|
|
75
|
-
});
|
|
76
|
-
it('treats a .git-suffixed existing runwork remote as a match (no rewrite)', () => {
|
|
77
|
-
const { setUrl, add } = withRemotes({ runwork: `${CANONICAL}.git` });
|
|
78
|
-
expect(ensureRunworkRemote('/repo', CANONICAL)).toEqual({ action: 'noop' });
|
|
79
|
-
expect(setUrl).toHaveLength(0);
|
|
80
|
-
expect(add).toHaveLength(0);
|
|
81
|
-
});
|
|
82
|
-
it('repoints an existing runwork remote that points elsewhere', () => {
|
|
83
|
-
const { setUrl, add } = withRemotes({ runwork: 'https://runwork.ai/api/git/OLD/app' });
|
|
84
|
-
expect(ensureRunworkRemote('/repo', CANONICAL)).toEqual({ action: 'updated' });
|
|
85
|
-
expect(setUrl).toHaveLength(1);
|
|
86
|
-
expect(setUrl[0]).toEqual(['remote', 'set-url', 'runwork', CANONICAL]);
|
|
87
|
-
expect(add).toHaveLength(0);
|
|
88
|
-
});
|
|
89
|
-
it('adds the runwork remote when missing (clean repo)', () => {
|
|
90
|
-
const { setUrl, add } = withRemotes({});
|
|
91
|
-
expect(ensureRunworkRemote('/repo', CANONICAL)).toEqual({ action: 'added' });
|
|
92
|
-
expect(add).toHaveLength(1);
|
|
93
|
-
expect(add[0]).toEqual(['remote', 'add', 'runwork', CANONICAL]);
|
|
94
|
-
expect(setUrl).toHaveLength(0);
|
|
95
|
-
});
|
|
96
|
-
it('recognizes a matching origin (manual git clone) via reusedFrom and still adds the runwork alias', () => {
|
|
97
|
-
const { setUrl, add } = withRemotes({ origin: CANONICAL });
|
|
98
|
-
expect(ensureRunworkRemote('/repo', CANONICAL)).toEqual({ action: 'added', reusedFrom: 'origin' });
|
|
99
|
-
expect(add).toHaveLength(1);
|
|
100
|
-
expect(add[0]).toEqual(['remote', 'add', 'runwork', CANONICAL]);
|
|
101
|
-
expect(setUrl).toHaveLength(0);
|
|
102
|
-
});
|
|
103
|
-
it('does not set reusedFrom when no existing remote matches', () => {
|
|
104
|
-
withRemotes({ origin: 'https://github.com/someone/fork' });
|
|
105
|
-
const result = ensureRunworkRemote('/repo', CANONICAL);
|
|
106
|
-
expect(result).toEqual({ action: 'added' });
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|