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,146 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
|
|
2
|
-
import { execFileSync } from 'child_process';
|
|
3
|
-
import { mkdtempSync, rmSync } from 'fs';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
import { tmpdir } from 'os';
|
|
6
|
-
import { ensureGitIdentity, __testing } from '../identity.js';
|
|
7
|
-
const tempDirs = [];
|
|
8
|
-
// Isolate every git invocation in this file from the developer's real
|
|
9
|
-
// `~/.gitconfig` (and any system-level config). Without this, anyone running
|
|
10
|
-
// the suite locally with `git config --global user.email` already set would
|
|
11
|
-
// see the helper short-circuit (correctly) and the "fresh machine" assertions
|
|
12
|
-
// would never exercise the write path. We point GIT_CONFIG_GLOBAL/SYSTEM at
|
|
13
|
-
// paths that don't exist; git treats missing config files as empty.
|
|
14
|
-
const ISOLATION_DIR = mkdtempSync(join(tmpdir(), 'runwork-identity-isolation-'));
|
|
15
|
-
const NON_EXISTENT = join(ISOLATION_DIR, 'no-such-config');
|
|
16
|
-
const PREVIOUS_ENV = {};
|
|
17
|
-
beforeAll(() => {
|
|
18
|
-
for (const key of ['GIT_CONFIG_GLOBAL', 'GIT_CONFIG_SYSTEM', 'HOME', 'XDG_CONFIG_HOME']) {
|
|
19
|
-
PREVIOUS_ENV[key] = process.env[key];
|
|
20
|
-
}
|
|
21
|
-
process.env.GIT_CONFIG_GLOBAL = NON_EXISTENT;
|
|
22
|
-
process.env.GIT_CONFIG_SYSTEM = NON_EXISTENT;
|
|
23
|
-
// Some git builds still consult $HOME/.gitconfig even with GIT_CONFIG_GLOBAL
|
|
24
|
-
// set. Pointing HOME at the empty isolation directory closes that hole.
|
|
25
|
-
process.env.HOME = ISOLATION_DIR;
|
|
26
|
-
process.env.XDG_CONFIG_HOME = ISOLATION_DIR;
|
|
27
|
-
});
|
|
28
|
-
afterAll(() => {
|
|
29
|
-
for (const [key, value] of Object.entries(PREVIOUS_ENV)) {
|
|
30
|
-
if (value === undefined)
|
|
31
|
-
delete process.env[key];
|
|
32
|
-
else
|
|
33
|
-
process.env[key] = value;
|
|
34
|
-
}
|
|
35
|
-
try {
|
|
36
|
-
rmSync(ISOLATION_DIR, { recursive: true, force: true });
|
|
37
|
-
}
|
|
38
|
-
catch { /* best-effort */ }
|
|
39
|
-
});
|
|
40
|
-
function makeRepo() {
|
|
41
|
-
const dir = mkdtempSync(join(tmpdir(), 'runwork-identity-test-'));
|
|
42
|
-
tempDirs.push(dir);
|
|
43
|
-
execFileSync('git', ['init', dir]);
|
|
44
|
-
return dir;
|
|
45
|
-
}
|
|
46
|
-
function readConfig(cwd, key) {
|
|
47
|
-
try {
|
|
48
|
-
return execFileSync('git', ['config', '--local', '--get', key], { cwd }).toString('utf-8').trim();
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return '';
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
afterEach(() => {
|
|
55
|
-
for (const dir of tempDirs) {
|
|
56
|
-
try {
|
|
57
|
-
rmSync(dir, { recursive: true, force: true });
|
|
58
|
-
}
|
|
59
|
-
catch { /* best-effort */ }
|
|
60
|
-
}
|
|
61
|
-
tempDirs.length = 0;
|
|
62
|
-
});
|
|
63
|
-
describe('deriveNameFromEmail', () => {
|
|
64
|
-
it('uppercases the local part', () => {
|
|
65
|
-
expect(__testing.deriveNameFromEmail('oytun@motaword.com')).toBe('Oytun');
|
|
66
|
-
});
|
|
67
|
-
it('splits separators into words', () => {
|
|
68
|
-
expect(__testing.deriveNameFromEmail('anna.maria@example.com')).toBe('Anna Maria');
|
|
69
|
-
expect(__testing.deriveNameFromEmail('jean-luc@example.com')).toBe('Jean Luc');
|
|
70
|
-
expect(__testing.deriveNameFromEmail('user_name@example.com')).toBe('User Name');
|
|
71
|
-
});
|
|
72
|
-
it('falls back to default when local part is empty', () => {
|
|
73
|
-
expect(__testing.deriveNameFromEmail('@example.com')).toBe(__testing.FALLBACK_NAME);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
describe('pickIdentity', () => {
|
|
77
|
-
it('uses the credentials email when it looks like an email', () => {
|
|
78
|
-
expect(__testing.pickIdentity({ apiKey: 'k', email: 'oytun@motaword.com', baseUrl: '' }))
|
|
79
|
-
.toEqual({ email: 'oytun@motaword.com', name: 'Oytun' });
|
|
80
|
-
});
|
|
81
|
-
it('falls back when email is the api-key-auth placeholder', () => {
|
|
82
|
-
expect(__testing.pickIdentity({ apiKey: 'k', email: 'api-key-auth', baseUrl: '' }))
|
|
83
|
-
.toEqual({ email: __testing.FALLBACK_EMAIL, name: __testing.FALLBACK_NAME });
|
|
84
|
-
});
|
|
85
|
-
it('falls back when no credentials are passed', () => {
|
|
86
|
-
expect(__testing.pickIdentity(null))
|
|
87
|
-
.toEqual({ email: __testing.FALLBACK_EMAIL, name: __testing.FALLBACK_NAME });
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
describe('ensureGitIdentity', () => {
|
|
91
|
-
it('writes local user.email and user.name when none are configured', () => {
|
|
92
|
-
const dir = makeRepo();
|
|
93
|
-
expect(readConfig(dir, 'user.email')).toBe('');
|
|
94
|
-
expect(readConfig(dir, 'user.name')).toBe('');
|
|
95
|
-
ensureGitIdentity(dir, { apiKey: 'k', email: 'oytun@motaword.com', baseUrl: '' });
|
|
96
|
-
expect(readConfig(dir, 'user.email')).toBe('oytun@motaword.com');
|
|
97
|
-
expect(readConfig(dir, 'user.name')).toBe('Oytun');
|
|
98
|
-
});
|
|
99
|
-
it('overwrites local config with Runwork credentials when they are usable', () => {
|
|
100
|
-
// Commits to a Runwork remote should always be attributed to the
|
|
101
|
-
// authenticated Runwork user, even if the developer set a local identity
|
|
102
|
-
// earlier (e.g. a stale value from a previous account).
|
|
103
|
-
const dir = makeRepo();
|
|
104
|
-
execFileSync('git', ['config', '--local', 'user.email', 'preset@example.com'], { cwd: dir });
|
|
105
|
-
execFileSync('git', ['config', '--local', 'user.name', 'Preset User'], { cwd: dir });
|
|
106
|
-
ensureGitIdentity(dir, { apiKey: 'k', email: 'oytun@motaword.com', baseUrl: '' });
|
|
107
|
-
expect(readConfig(dir, 'user.email')).toBe('oytun@motaword.com');
|
|
108
|
-
expect(readConfig(dir, 'user.name')).toBe('Oytun');
|
|
109
|
-
});
|
|
110
|
-
it('respects existing local config when no Runwork credentials are available', () => {
|
|
111
|
-
// Without a real Runwork identity to attribute the commit to, leave any
|
|
112
|
-
// pre-existing local identity (or anything inherited from --global) alone.
|
|
113
|
-
const dir = makeRepo();
|
|
114
|
-
execFileSync('git', ['config', '--local', 'user.email', 'preset@example.com'], { cwd: dir });
|
|
115
|
-
execFileSync('git', ['config', '--local', 'user.name', 'Preset User'], { cwd: dir });
|
|
116
|
-
ensureGitIdentity(dir, null);
|
|
117
|
-
expect(readConfig(dir, 'user.email')).toBe('preset@example.com');
|
|
118
|
-
expect(readConfig(dir, 'user.name')).toBe('Preset User');
|
|
119
|
-
});
|
|
120
|
-
it('respects existing local config when credentials carry only a placeholder email', () => {
|
|
121
|
-
const dir = makeRepo();
|
|
122
|
-
execFileSync('git', ['config', '--local', 'user.email', 'preset@example.com'], { cwd: dir });
|
|
123
|
-
execFileSync('git', ['config', '--local', 'user.name', 'Preset User'], { cwd: dir });
|
|
124
|
-
ensureGitIdentity(dir, { apiKey: 'k', email: 'api-key-auth', baseUrl: '' });
|
|
125
|
-
expect(readConfig(dir, 'user.email')).toBe('preset@example.com');
|
|
126
|
-
expect(readConfig(dir, 'user.name')).toBe('Preset User');
|
|
127
|
-
});
|
|
128
|
-
it('falls back to a synthetic identity when credentials are missing', () => {
|
|
129
|
-
const dir = makeRepo();
|
|
130
|
-
ensureGitIdentity(dir, null);
|
|
131
|
-
expect(readConfig(dir, 'user.email')).toBe(__testing.FALLBACK_EMAIL);
|
|
132
|
-
expect(readConfig(dir, 'user.name')).toBe(__testing.FALLBACK_NAME);
|
|
133
|
-
});
|
|
134
|
-
it('lets a real commit succeed on a fresh repo (windows-style "no identity" path)', () => {
|
|
135
|
-
const dir = makeRepo();
|
|
136
|
-
ensureGitIdentity(dir, { apiKey: 'k', email: 'oytun@motaword.com', baseUrl: '' });
|
|
137
|
-
execFileSync('git', ['commit', '--allow-empty', '-m', 'identity probe'], { cwd: dir, stdio: 'pipe' });
|
|
138
|
-
const log = execFileSync('git', ['log', '--pretty=%an <%ae>'], { cwd: dir }).toString('utf-8').trim();
|
|
139
|
-
expect(log).toContain('Oytun <oytun@motaword.com>');
|
|
140
|
-
});
|
|
141
|
-
it('is a no-op for non-git directories', () => {
|
|
142
|
-
const dir = mkdtempSync(join(tmpdir(), 'runwork-identity-non-git-'));
|
|
143
|
-
tempDirs.push(dir);
|
|
144
|
-
expect(() => ensureGitIdentity(dir, null)).not.toThrow();
|
|
145
|
-
});
|
|
146
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,446 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
-
import { execFileSync } from 'child_process';
|
|
3
|
-
import { mkdtempSync, writeFileSync, rmSync, existsSync, mkdirSync, symlinkSync, } from 'fs';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
import { tmpdir } from 'os';
|
|
6
|
-
import { generateManifest, saveManifest, loadManifest, detectUserEdits, } from '../../template/manifest.js';
|
|
7
|
-
const tempDirs = [];
|
|
8
|
-
function makeTempDir(prefix) {
|
|
9
|
-
const dir = mkdtempSync(join(tmpdir(), `runwork-manifest-test-${prefix}-`));
|
|
10
|
-
tempDirs.push(dir);
|
|
11
|
-
return dir;
|
|
12
|
-
}
|
|
13
|
-
function initGitRepo(dir) {
|
|
14
|
-
execFileSync('git', ['init', dir]);
|
|
15
|
-
execFileSync('git', ['config', 'user.email', 'test@test.com'], { cwd: dir });
|
|
16
|
-
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: dir });
|
|
17
|
-
}
|
|
18
|
-
function commitFile(repoDir, filename, content, message) {
|
|
19
|
-
const fullPath = join(repoDir, filename);
|
|
20
|
-
const dir = fullPath.substring(0, fullPath.lastIndexOf('/'));
|
|
21
|
-
if (dir !== repoDir && !existsSync(dir)) {
|
|
22
|
-
mkdirSync(dir, { recursive: true });
|
|
23
|
-
}
|
|
24
|
-
writeFileSync(fullPath, content);
|
|
25
|
-
execFileSync('git', ['add', filename], { cwd: repoDir });
|
|
26
|
-
execFileSync('git', ['commit', '-m', message], { cwd: repoDir });
|
|
27
|
-
}
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
for (const dir of tempDirs) {
|
|
30
|
-
try {
|
|
31
|
-
rmSync(dir, { recursive: true, force: true });
|
|
32
|
-
}
|
|
33
|
-
catch { /* best effort */ }
|
|
34
|
-
}
|
|
35
|
-
tempDirs.length = 0;
|
|
36
|
-
});
|
|
37
|
-
describe('generateManifest()', () => {
|
|
38
|
-
it('generates manifest for a simple directory', async () => {
|
|
39
|
-
const dir = makeTempDir('simple');
|
|
40
|
-
writeFileSync(join(dir, 'a.txt'), 'hello');
|
|
41
|
-
writeFileSync(join(dir, 'b.txt'), 'world');
|
|
42
|
-
const manifest = await generateManifest(dir);
|
|
43
|
-
expect(manifest.version).toBe(1);
|
|
44
|
-
expect(Object.keys(manifest.files)).toHaveLength(2);
|
|
45
|
-
expect(manifest.files['a.txt']).toMatch(/^sha256:/);
|
|
46
|
-
expect(manifest.files['b.txt']).toMatch(/^sha256:/);
|
|
47
|
-
});
|
|
48
|
-
it('skips .git, node_modules, and .runwork directories', async () => {
|
|
49
|
-
const dir = makeTempDir('skip');
|
|
50
|
-
writeFileSync(join(dir, 'a.txt'), 'hello');
|
|
51
|
-
mkdirSync(join(dir, '.git'));
|
|
52
|
-
writeFileSync(join(dir, '.git', 'config'), 'git config');
|
|
53
|
-
mkdirSync(join(dir, 'node_modules'));
|
|
54
|
-
writeFileSync(join(dir, 'node_modules', 'pkg.js'), 'module');
|
|
55
|
-
mkdirSync(join(dir, '.runwork'));
|
|
56
|
-
writeFileSync(join(dir, '.runwork', 'manifest.json'), '{}');
|
|
57
|
-
const manifest = await generateManifest(dir);
|
|
58
|
-
expect(Object.keys(manifest.files)).toHaveLength(1);
|
|
59
|
-
expect(manifest.files['a.txt']).toBeDefined();
|
|
60
|
-
});
|
|
61
|
-
it('skips .dev.vars and .env files', async () => {
|
|
62
|
-
const dir = makeTempDir('skipfiles');
|
|
63
|
-
writeFileSync(join(dir, 'a.txt'), 'hello');
|
|
64
|
-
writeFileSync(join(dir, '.dev.vars'), 'SECRET=123');
|
|
65
|
-
writeFileSync(join(dir, '.env'), 'OTHER=456');
|
|
66
|
-
const manifest = await generateManifest(dir);
|
|
67
|
-
expect(Object.keys(manifest.files)).toHaveLength(1);
|
|
68
|
-
expect(manifest.files['a.txt']).toBeDefined();
|
|
69
|
-
});
|
|
70
|
-
it('skips .bun-cache directory by default (regression: hung dev startup)', async () => {
|
|
71
|
-
// Bun's per-project cache used to be walked recursively here. When it
|
|
72
|
-
// grew to ~1 GB / 25k+ files, generateManifest spent minutes hashing
|
|
73
|
-
// every file and starved the rest of `runwork dev`. The default
|
|
74
|
-
// ignore-matcher now skips .bun-cache/ even without a .gitignore.
|
|
75
|
-
const dir = makeTempDir('bun-cache');
|
|
76
|
-
writeFileSync(join(dir, 'a.txt'), 'hello');
|
|
77
|
-
mkdirSync(join(dir, '.bun-cache'));
|
|
78
|
-
writeFileSync(join(dir, '.bun-cache', 'pkg.tgz'), 'tarball');
|
|
79
|
-
mkdirSync(join(dir, '.bun-cache', 'react@19.0.0'));
|
|
80
|
-
writeFileSync(join(dir, '.bun-cache', 'react@19.0.0', 'index.js'), 'r');
|
|
81
|
-
const manifest = await generateManifest(dir);
|
|
82
|
-
expect(Object.keys(manifest.files)).toEqual(['a.txt']);
|
|
83
|
-
});
|
|
84
|
-
it('honors simple .gitignore directory entries (e.g. dist/, .next/, coverage)', async () => {
|
|
85
|
-
const dir = makeTempDir('gitignored-dirs');
|
|
86
|
-
writeFileSync(join(dir, '.gitignore'), 'dist/\n.next/\ncoverage\n');
|
|
87
|
-
writeFileSync(join(dir, 'a.txt'), 'hello');
|
|
88
|
-
mkdirSync(join(dir, 'dist'));
|
|
89
|
-
writeFileSync(join(dir, 'dist', 'bundle.js'), 'compiled');
|
|
90
|
-
mkdirSync(join(dir, '.next'));
|
|
91
|
-
writeFileSync(join(dir, '.next', 'build.json'), '{}');
|
|
92
|
-
mkdirSync(join(dir, 'coverage'));
|
|
93
|
-
writeFileSync(join(dir, 'coverage', 'lcov.info'), 'data');
|
|
94
|
-
const manifest = await generateManifest(dir);
|
|
95
|
-
const keys = Object.keys(manifest.files).sort();
|
|
96
|
-
expect(keys).toEqual(['.gitignore', 'a.txt']);
|
|
97
|
-
});
|
|
98
|
-
it('falls through to defaults when .gitignore patterns are too complex', async () => {
|
|
99
|
-
// The walker only honors simple directory/basename entries. Globbed
|
|
100
|
-
// patterns are skipped, so files matching them still appear in the
|
|
101
|
-
// manifest. This is acceptable: the goal is to avoid the runaway
|
|
102
|
-
// cases (huge gitignored caches), not to be a full gitignore matcher.
|
|
103
|
-
const dir = makeTempDir('complex-gitignore');
|
|
104
|
-
writeFileSync(join(dir, '.gitignore'), '*.local\nsrc/generated/\n');
|
|
105
|
-
writeFileSync(join(dir, 'a.txt'), 'hello');
|
|
106
|
-
writeFileSync(join(dir, 'config.local'), 'should-still-appear');
|
|
107
|
-
const manifest = await generateManifest(dir);
|
|
108
|
-
expect(manifest.files['a.txt']).toBeDefined();
|
|
109
|
-
expect(manifest.files['config.local']).toBeDefined();
|
|
110
|
-
});
|
|
111
|
-
it('handles files with spaces in names', async () => {
|
|
112
|
-
const dir = makeTempDir('spaces');
|
|
113
|
-
writeFileSync(join(dir, 'hello world.txt'), 'content');
|
|
114
|
-
writeFileSync(join(dir, 'normal.txt'), 'content');
|
|
115
|
-
const manifest = await generateManifest(dir);
|
|
116
|
-
expect(Object.keys(manifest.files)).toHaveLength(2);
|
|
117
|
-
expect(manifest.files['hello world.txt']).toBeDefined();
|
|
118
|
-
});
|
|
119
|
-
it('handles nested directories', async () => {
|
|
120
|
-
const dir = makeTempDir('nested');
|
|
121
|
-
mkdirSync(join(dir, 'src', 'components'), { recursive: true });
|
|
122
|
-
writeFileSync(join(dir, 'src', 'index.ts'), 'export {}');
|
|
123
|
-
writeFileSync(join(dir, 'src', 'components', 'App.tsx'), '<App/>');
|
|
124
|
-
const manifest = await generateManifest(dir);
|
|
125
|
-
expect(manifest.files['src/index.ts']).toBeDefined();
|
|
126
|
-
expect(manifest.files['src/components/App.tsx']).toBeDefined();
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
describe('walkDir() symlink edge cases', () => {
|
|
130
|
-
it('BUG PROBE: symlink cycle causes infinite recursion in walkDir/generateManifest', async () => {
|
|
131
|
-
// walkDir uses readdirSync with withFileTypes: true.
|
|
132
|
-
// Dirent.isDirectory() follows symlinks, so a symlink pointing
|
|
133
|
-
// to a parent directory will cause infinite recursion.
|
|
134
|
-
const dir = makeTempDir('symlink-cycle');
|
|
135
|
-
writeFileSync(join(dir, 'a.txt'), 'hello');
|
|
136
|
-
mkdirSync(join(dir, 'subdir'));
|
|
137
|
-
writeFileSync(join(dir, 'subdir', 'b.txt'), 'world');
|
|
138
|
-
// Create a symlink cycle: subdir/link -> parent dir
|
|
139
|
-
symlinkSync(dir, join(dir, 'subdir', 'link'));
|
|
140
|
-
// This should not hang or crash, but walkDir follows symlinks
|
|
141
|
-
// via isDirectory() which returns true for symlink-to-directory.
|
|
142
|
-
// Expected: either it handles symlinks gracefully, or it crashes
|
|
143
|
-
// with max call stack exceeded.
|
|
144
|
-
let threw = false;
|
|
145
|
-
let errorMessage = '';
|
|
146
|
-
try {
|
|
147
|
-
// Set a timeout to detect infinite loops - if this takes too long, it's a bug
|
|
148
|
-
await generateManifest(dir);
|
|
149
|
-
}
|
|
150
|
-
catch (error) {
|
|
151
|
-
threw = true;
|
|
152
|
-
errorMessage = error instanceof Error ? error.message : String(error);
|
|
153
|
-
}
|
|
154
|
-
// If we get here without hanging, check if it threw
|
|
155
|
-
// The bug is that walkDir WILL follow the symlink and recurse infinitely
|
|
156
|
-
// until it hits ENAMETOOLONG or max call stack.
|
|
157
|
-
// We expect this to either throw or produce duplicate entries.
|
|
158
|
-
// If it doesn't throw, it somehow handled it (unlikely).
|
|
159
|
-
if (threw) {
|
|
160
|
-
// Bug confirmed: walkDir crashes on symlink cycles
|
|
161
|
-
expect(errorMessage).toBeTruthy();
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
// If it somehow succeeds, check that it didn't include
|
|
165
|
-
// infinitely nested paths
|
|
166
|
-
expect(true).toBe(true); // pass - it handled it somehow
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
it('symlinked directories are silently skipped by walkDir (not followed)', async () => {
|
|
170
|
-
// Node.js readdirSync withFileTypes: Dirent.isDirectory() returns false for symlinks.
|
|
171
|
-
// So walkDir silently skips symlinked directories entirely.
|
|
172
|
-
// This is safe (no infinite recursion) but means symlinked content is invisible.
|
|
173
|
-
const dir = makeTempDir('symlink-dir');
|
|
174
|
-
const targetDir = makeTempDir('symlink-target');
|
|
175
|
-
writeFileSync(join(targetDir, 'external.txt'), 'external content');
|
|
176
|
-
writeFileSync(join(dir, 'local.txt'), 'local');
|
|
177
|
-
symlinkSync(targetDir, join(dir, 'linked'));
|
|
178
|
-
const manifest = await generateManifest(dir);
|
|
179
|
-
const keys = Object.keys(manifest.files);
|
|
180
|
-
const hasLinkedFile = keys.some(k => k.includes('linked'));
|
|
181
|
-
// Symlinked directories are NOT followed -- content is invisible
|
|
182
|
-
expect(hasLinkedFile).toBe(false);
|
|
183
|
-
expect(keys).toEqual(['local.txt']);
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
describe('saveManifest() and loadManifest()', () => {
|
|
187
|
-
it('creates .runwork directory if it does not exist', async () => {
|
|
188
|
-
const dir = makeTempDir('norunwork');
|
|
189
|
-
const manifest = { version: 1, files: { 'a.txt': 'sha256:abc' } };
|
|
190
|
-
expect(existsSync(join(dir, '.runwork'))).toBe(false);
|
|
191
|
-
await saveManifest(dir, manifest);
|
|
192
|
-
expect(existsSync(join(dir, '.runwork', 'template-manifest.json'))).toBe(true);
|
|
193
|
-
const loaded = await loadManifest(dir);
|
|
194
|
-
expect(loaded).toEqual(manifest);
|
|
195
|
-
});
|
|
196
|
-
it('returns null when .runwork directory does not exist', async () => {
|
|
197
|
-
const dir = makeTempDir('nomanifest');
|
|
198
|
-
const result = await loadManifest(dir);
|
|
199
|
-
expect(result).toBeNull();
|
|
200
|
-
});
|
|
201
|
-
it('returns null for corrupted manifest file', async () => {
|
|
202
|
-
const dir = makeTempDir('corrupt');
|
|
203
|
-
mkdirSync(join(dir, '.runwork'));
|
|
204
|
-
writeFileSync(join(dir, '.runwork', 'template-manifest.json'), 'not json{{{');
|
|
205
|
-
const result = await loadManifest(dir);
|
|
206
|
-
expect(result).toBeNull();
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
describe('detectUserEdits() edge cases', () => {
|
|
210
|
-
it('returns empty array when git diff and ls-files return nothing', async () => {
|
|
211
|
-
// Edge case: empty strings from git commands.
|
|
212
|
-
// "".split('\n') returns [''] (array with one empty string).
|
|
213
|
-
// The code should handle this gracefully.
|
|
214
|
-
const dir = makeTempDir('clean');
|
|
215
|
-
initGitRepo(dir);
|
|
216
|
-
const content = 'hello';
|
|
217
|
-
writeFileSync(join(dir, 'a.txt'), content);
|
|
218
|
-
execFileSync('git', ['add', 'a.txt'], { cwd: dir });
|
|
219
|
-
execFileSync('git', ['commit', '-m', 'init'], { cwd: dir });
|
|
220
|
-
// Use correct hash so manifest scan doesn't flag it as modified
|
|
221
|
-
const { createHash } = await import('crypto');
|
|
222
|
-
const hash = 'sha256:' + createHash('sha256').update(Buffer.from(content)).digest('hex');
|
|
223
|
-
const manifest = {
|
|
224
|
-
version: 1,
|
|
225
|
-
files: { 'a.txt': hash },
|
|
226
|
-
};
|
|
227
|
-
const edits = await detectUserEdits(dir, manifest);
|
|
228
|
-
expect(edits).toEqual([]);
|
|
229
|
-
});
|
|
230
|
-
it('BUG PROBE: files with spaces in names from git diff output', async () => {
|
|
231
|
-
// git diff --name-only returns filenames with spaces as-is.
|
|
232
|
-
// The code splits by \n and processes each line.
|
|
233
|
-
// This should work unless there's quoting involved.
|
|
234
|
-
const dir = makeTempDir('spaces');
|
|
235
|
-
initGitRepo(dir);
|
|
236
|
-
writeFileSync(join(dir, 'hello world.txt'), 'original');
|
|
237
|
-
execFileSync('git', ['add', 'hello world.txt'], { cwd: dir });
|
|
238
|
-
execFileSync('git', ['commit', '-m', 'init'], { cwd: dir });
|
|
239
|
-
// Modify the file with spaces
|
|
240
|
-
writeFileSync(join(dir, 'hello world.txt'), 'modified');
|
|
241
|
-
const manifest = { version: 1, files: {} };
|
|
242
|
-
const edits = await detectUserEdits(dir, manifest);
|
|
243
|
-
// Should contain the file with spaces
|
|
244
|
-
expect(edits).toContain('hello world.txt');
|
|
245
|
-
});
|
|
246
|
-
it('returns correct unicode filenames (not octal-escaped)', async () => {
|
|
247
|
-
const dir = makeTempDir('unicode');
|
|
248
|
-
initGitRepo(dir);
|
|
249
|
-
const filename = 'caf\u00e9.txt';
|
|
250
|
-
writeFileSync(join(dir, filename), 'original');
|
|
251
|
-
execFileSync('git', ['add', filename], { cwd: dir });
|
|
252
|
-
execFileSync('git', ['commit', '-m', 'init'], { cwd: dir });
|
|
253
|
-
writeFileSync(join(dir, filename), 'modified');
|
|
254
|
-
const manifest = { version: 1, files: {} };
|
|
255
|
-
const edits = await detectUserEdits(dir, manifest);
|
|
256
|
-
expect(edits).toContain(filename);
|
|
257
|
-
// Should NOT contain octal-escaped garbage
|
|
258
|
-
expect(edits.some(e => e.includes('\\303'))).toBe(false);
|
|
259
|
-
});
|
|
260
|
-
it('BUG PROBE: file deleted by user that was in manifest (untracked section)', async () => {
|
|
261
|
-
// If a user deletes a file that was in the manifest, it won't
|
|
262
|
-
// appear in git ls-files --others (because it doesn't exist on disk).
|
|
263
|
-
// It also won't appear in git diff --name-only HEAD because it was
|
|
264
|
-
// never committed. So detectUserEdits will miss it entirely.
|
|
265
|
-
// This is arguably correct behavior (delete = no edit to detect),
|
|
266
|
-
// but worth documenting.
|
|
267
|
-
const dir = makeTempDir('deleted');
|
|
268
|
-
initGitRepo(dir);
|
|
269
|
-
writeFileSync(join(dir, 'committed.txt'), 'hello');
|
|
270
|
-
execFileSync('git', ['add', '.'], { cwd: dir });
|
|
271
|
-
execFileSync('git', ['commit', '-m', 'init'], { cwd: dir });
|
|
272
|
-
// Template manifest includes both a committed file and a template file
|
|
273
|
-
const manifest = {
|
|
274
|
-
version: 1,
|
|
275
|
-
files: {
|
|
276
|
-
'committed.txt': 'sha256:abc',
|
|
277
|
-
'template-only.txt': 'sha256:def',
|
|
278
|
-
},
|
|
279
|
-
};
|
|
280
|
-
// template-only.txt never existed on disk (or was deleted)
|
|
281
|
-
// detectUserEdits should not crash
|
|
282
|
-
const edits = await detectUserEdits(dir, manifest);
|
|
283
|
-
// No crash is the main assertion
|
|
284
|
-
expect(Array.isArray(edits)).toBe(true);
|
|
285
|
-
});
|
|
286
|
-
it('detects untracked files that are not in the manifest', async () => {
|
|
287
|
-
const dir = makeTempDir('newfile');
|
|
288
|
-
initGitRepo(dir);
|
|
289
|
-
writeFileSync(join(dir, 'a.txt'), 'hello');
|
|
290
|
-
execFileSync('git', ['add', '.'], { cwd: dir });
|
|
291
|
-
execFileSync('git', ['commit', '-m', 'init'], { cwd: dir });
|
|
292
|
-
// User creates a new file
|
|
293
|
-
writeFileSync(join(dir, 'user-created.txt'), 'my new file');
|
|
294
|
-
const manifest = {
|
|
295
|
-
version: 1,
|
|
296
|
-
files: { 'a.txt': 'sha256:abc' },
|
|
297
|
-
};
|
|
298
|
-
const edits = await detectUserEdits(dir, manifest);
|
|
299
|
-
expect(edits).toContain('user-created.txt');
|
|
300
|
-
});
|
|
301
|
-
it('skips untracked file that matches template manifest hash (pristine template file)', async () => {
|
|
302
|
-
const dir = makeTempDir('pristine');
|
|
303
|
-
initGitRepo(dir);
|
|
304
|
-
writeFileSync(join(dir, 'committed.txt'), 'hello');
|
|
305
|
-
execFileSync('git', ['add', '.'], { cwd: dir });
|
|
306
|
-
execFileSync('git', ['commit', '-m', 'init'], { cwd: dir });
|
|
307
|
-
// Create an untracked file that matches the template hash exactly
|
|
308
|
-
const content = 'template content';
|
|
309
|
-
writeFileSync(join(dir, 'template-file.txt'), content);
|
|
310
|
-
// Generate the correct hash for this content
|
|
311
|
-
const { createHash } = await import('crypto');
|
|
312
|
-
const hash = 'sha256:' + createHash('sha256').update(Buffer.from(content)).digest('hex');
|
|
313
|
-
const manifest = {
|
|
314
|
-
version: 1,
|
|
315
|
-
files: {
|
|
316
|
-
'committed.txt': 'sha256:abc',
|
|
317
|
-
'template-file.txt': hash,
|
|
318
|
-
},
|
|
319
|
-
};
|
|
320
|
-
const edits = await detectUserEdits(dir, manifest);
|
|
321
|
-
// Pristine template file should NOT be in edits
|
|
322
|
-
expect(edits).not.toContain('template-file.txt');
|
|
323
|
-
});
|
|
324
|
-
it('BUG PROBE: detects edit when untracked file has different content than manifest hash', async () => {
|
|
325
|
-
const dir = makeTempDir('modified-template');
|
|
326
|
-
initGitRepo(dir);
|
|
327
|
-
writeFileSync(join(dir, 'committed.txt'), 'hello');
|
|
328
|
-
execFileSync('git', ['add', '.'], { cwd: dir });
|
|
329
|
-
execFileSync('git', ['commit', '-m', 'init'], { cwd: dir });
|
|
330
|
-
// Create an untracked file with content that differs from manifest
|
|
331
|
-
writeFileSync(join(dir, 'template-file.txt'), 'user modified this');
|
|
332
|
-
const manifest = {
|
|
333
|
-
version: 1,
|
|
334
|
-
files: {
|
|
335
|
-
'committed.txt': 'sha256:abc',
|
|
336
|
-
'template-file.txt': 'sha256:different_hash_from_template',
|
|
337
|
-
},
|
|
338
|
-
};
|
|
339
|
-
const edits = await detectUserEdits(dir, manifest);
|
|
340
|
-
// Modified template file SHOULD be in edits
|
|
341
|
-
expect(edits).toContain('template-file.txt');
|
|
342
|
-
});
|
|
343
|
-
it('does not duplicate files that appear in both git diff and git diff --cached', async () => {
|
|
344
|
-
const dir = makeTempDir('dedup');
|
|
345
|
-
initGitRepo(dir);
|
|
346
|
-
writeFileSync(join(dir, 'a.txt'), 'original');
|
|
347
|
-
execFileSync('git', ['add', '.'], { cwd: dir });
|
|
348
|
-
execFileSync('git', ['commit', '-m', 'init'], { cwd: dir });
|
|
349
|
-
// Stage a change AND have unstaged changes to same file
|
|
350
|
-
writeFileSync(join(dir, 'a.txt'), 'staged change');
|
|
351
|
-
execFileSync('git', ['add', 'a.txt'], { cwd: dir });
|
|
352
|
-
writeFileSync(join(dir, 'a.txt'), 'unstaged further change');
|
|
353
|
-
const manifest = { version: 1, files: {} };
|
|
354
|
-
const edits = await detectUserEdits(dir, manifest);
|
|
355
|
-
// File should appear only once even though it's in both diff outputs
|
|
356
|
-
const count = edits.filter(f => f === 'a.txt').length;
|
|
357
|
-
expect(count).toBe(1);
|
|
358
|
-
});
|
|
359
|
-
it('BUG PROBE: git diff --name-only HEAD with no commits yet', async () => {
|
|
360
|
-
// When repo has no commits, git rev-parse HEAD fails.
|
|
361
|
-
// The code wraps this in try/catch, so it should skip the git diff section.
|
|
362
|
-
// But what about git ls-files --others?
|
|
363
|
-
const dir = makeTempDir('nocommits');
|
|
364
|
-
initGitRepo(dir);
|
|
365
|
-
// Create a file but don't commit
|
|
366
|
-
writeFileSync(join(dir, 'new-file.txt'), 'hello');
|
|
367
|
-
const manifest = { version: 1, files: {} };
|
|
368
|
-
const edits = await detectUserEdits(dir, manifest);
|
|
369
|
-
// Should detect the untracked file via ls-files --others
|
|
370
|
-
expect(edits).toContain('new-file.txt');
|
|
371
|
-
});
|
|
372
|
-
it('detects modified gitignored files that are in the template manifest', async () => {
|
|
373
|
-
// Real-world scenario: template ships with a config file, user adds it
|
|
374
|
-
// to .gitignore (e.g., to keep local secrets), then modifies it.
|
|
375
|
-
// detectUserEdits should still detect it by scanning manifest files on disk.
|
|
376
|
-
const dir = makeTempDir('gitignore');
|
|
377
|
-
initGitRepo(dir);
|
|
378
|
-
writeFileSync(join(dir, 'a.txt'), 'hello');
|
|
379
|
-
writeFileSync(join(dir, '.gitignore'), 'ignored.txt\n');
|
|
380
|
-
execFileSync('git', ['add', '.'], { cwd: dir });
|
|
381
|
-
execFileSync('git', ['commit', '-m', 'init'], { cwd: dir });
|
|
382
|
-
// Create a file that's gitignored but was in the template manifest
|
|
383
|
-
writeFileSync(join(dir, 'ignored.txt'), 'user modified this ignored file');
|
|
384
|
-
const manifest = {
|
|
385
|
-
version: 1,
|
|
386
|
-
files: {
|
|
387
|
-
'a.txt': 'sha256:abc',
|
|
388
|
-
'ignored.txt': 'sha256:original_template_hash',
|
|
389
|
-
},
|
|
390
|
-
};
|
|
391
|
-
const edits = await detectUserEdits(dir, manifest);
|
|
392
|
-
// Fixed: gitignored file IS detected because we scan manifest files on disk
|
|
393
|
-
expect(edits).toContain('ignored.txt');
|
|
394
|
-
});
|
|
395
|
-
it('does not flag gitignored file if it matches template manifest hash', async () => {
|
|
396
|
-
// If the gitignored file has NOT been modified (hash matches manifest),
|
|
397
|
-
// it should NOT appear in edits.
|
|
398
|
-
const dir = makeTempDir('gitignore-pristine');
|
|
399
|
-
initGitRepo(dir);
|
|
400
|
-
writeFileSync(join(dir, 'a.txt'), 'hello');
|
|
401
|
-
writeFileSync(join(dir, '.gitignore'), 'config.txt\n');
|
|
402
|
-
execFileSync('git', ['add', '.'], { cwd: dir });
|
|
403
|
-
execFileSync('git', ['commit', '-m', 'init'], { cwd: dir });
|
|
404
|
-
const content = 'pristine template content';
|
|
405
|
-
writeFileSync(join(dir, 'config.txt'), content);
|
|
406
|
-
const { createHash } = await import('crypto');
|
|
407
|
-
const hash = 'sha256:' + createHash('sha256').update(Buffer.from(content)).digest('hex');
|
|
408
|
-
const manifest = {
|
|
409
|
-
version: 1,
|
|
410
|
-
files: {
|
|
411
|
-
'a.txt': 'sha256:abc',
|
|
412
|
-
'config.txt': hash,
|
|
413
|
-
},
|
|
414
|
-
};
|
|
415
|
-
const edits = await detectUserEdits(dir, manifest);
|
|
416
|
-
expect(edits).not.toContain('config.txt');
|
|
417
|
-
});
|
|
418
|
-
it('does not flag tracked file whose committed content differs from template manifest', async () => {
|
|
419
|
-
// Reproduces the post-`runwork clone` scenario: the manifest was
|
|
420
|
-
// generated from the pristine template, then the AI-customized
|
|
421
|
-
// version was overlaid via `git fetch` + `git checkout .` and is now
|
|
422
|
-
// committed at HEAD with no local edits. Method 3's scan would
|
|
423
|
-
// false-flag every such file on every clone if it didn't skip
|
|
424
|
-
// tracked files.
|
|
425
|
-
const dir = makeTempDir('clone-overlay');
|
|
426
|
-
initGitRepo(dir);
|
|
427
|
-
// Simulate: AI-customized version is committed at HEAD.
|
|
428
|
-
const overlaidContent = 'AI-customized content';
|
|
429
|
-
writeFileSync(join(dir, 'worker/agents.ts'.replace('/', '-')), 'placeholder');
|
|
430
|
-
const aiFilePath = 'agents.ts';
|
|
431
|
-
writeFileSync(join(dir, aiFilePath), overlaidContent);
|
|
432
|
-
execFileSync('git', ['add', '.'], { cwd: dir });
|
|
433
|
-
execFileSync('git', ['commit', '-m', 'overlay'], { cwd: dir });
|
|
434
|
-
// Manifest captured the PRISTINE template hash (different from the
|
|
435
|
-
// overlaid HEAD content). detectUserEdits must not treat this as a
|
|
436
|
-
// user edit.
|
|
437
|
-
const manifest = {
|
|
438
|
-
version: 1,
|
|
439
|
-
files: {
|
|
440
|
-
[aiFilePath]: 'sha256:pristine_template_hash_unrelated_to_disk',
|
|
441
|
-
},
|
|
442
|
-
};
|
|
443
|
-
const edits = await detectUserEdits(dir, manifest);
|
|
444
|
-
expect(edits).not.toContain(aiFilePath);
|
|
445
|
-
});
|
|
446
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|