diff --git a/.gitattributes b/.gitattributes index b7ca04d95ed..378b7ab94aa 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,7 +4,7 @@ /skill-stubs/*.md text eol=lf /skill-stubs/_shared/*.md text eol=lf /skills/*/SKILL.md text eol=lf -/src/cli/bundled-skill-guides.ts text eol=lf +/src/cli/skills/bundled-skill-guides.ts text eol=lf # SVG bytes feed embedded data URLs and asset hashes on every build host. *.svg text eol=lf # Compiler input line breaks must not depend on the checkout OS. diff --git a/.oxlintrc.json b/.oxlintrc.json index 9601fee85cc..a2f65ca794b 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -308,7 +308,7 @@ "ignorePatterns": [ "src/main/acp/generated/acp-protocol.generated.ts", "src/shared/rpc-contract/rpc-params-catalog.generated.ts", - "src/cli/bundled-skill-guides.ts", + "src/cli/skills/bundled-skill-guides.ts", "resources/plugins/declarative/*.mjs", "**/node_modules", "**/dist", diff --git a/config/build-plugins/cli-main-entry-names.ts b/config/build-plugins/cli-main-entry-names.ts index a5489423531..5e2e25422d9 100644 --- a/config/build-plugins/cli-main-entry-names.ts +++ b/config/build-plugins/cli-main-entry-names.ts @@ -11,7 +11,9 @@ export const CLI_MAIN_ENTRY_NAMES = [ 'filesystem/macos-tree-clone', 'orcad/orcad-stale-lock-reclaim', 'orcad/orcad-host-identity-reset', - 'plugins/plugin-authoring-checks', + 'plugins/plugin-artifact-validation', + 'plugins/plugin-content-hash', + 'plugins/plugin-catalog-release-entry', // `orca vm env doctor` runs its checks in the CLI process. 'environment-doctor/environment-doctor', 'environment-doctor/environment-doctor-host', diff --git a/config/electron-builder.config.cjs b/config/electron-builder.config.cjs index 2df9f8177df..c3ee3f287ed 100644 --- a/config/electron-builder.config.cjs +++ b/config/electron-builder.config.cjs @@ -348,7 +348,9 @@ module.exports = { 'out/main/filesystem/macos-tree-clone.js', 'out/main/orca-profiles/profile-index-store.js', 'out/main/persistence/profile-state/**', - 'out/main/plugins/plugin-authoring-checks.js', + 'out/main/plugins/plugin-artifact-validation.js', + 'out/main/plugins/plugin-content-hash.js', + 'out/main/plugins/plugin-catalog-release-entry.js', 'out/main/environment-doctor/environment-doctor.js', 'out/main/environment-doctor/environment-doctor-host.js', // `orca plugin test` and `orca plugin check` load the runtime, kit and hooks in Node. diff --git a/config/flaky-test-pattern-baseline.txt b/config/flaky-test-pattern-baseline.txt index af6ef5c37d5..42bceb8f00f 100644 --- a/config/flaky-test-pattern-baseline.txt +++ b/config/flaky-test-pattern-baseline.txt @@ -572,7 +572,7 @@ 1 test-no-wall-clock:tests/e2e/host-cold-park-remote-subscriber.unit.test.ts 1 test-timeout-needs-reason:src/cli/agents/agent-launch-process.test.ts 1 test-timeout-needs-reason:src/cli/runtime/serve-pid1-init-reaper.test.ts -1 test-timeout-needs-reason:src/cli/skills-install.test.ts +1 test-timeout-needs-reason:src/cli/skills/skills-install.test.ts 2 test-timeout-needs-reason:src/main/acp/journal/acp-structured-broken-stream.test.ts 4 test-timeout-needs-reason:src/main/agent-hooks/server/testing/http/server-transport-interference.test.ts 1 test-timeout-needs-reason:src/main/agent-hooks/wsl-relay/testing/wsl-hook-relay-live.integration.test.ts diff --git a/config/scripts/ci/unit-selection/check-test-file-runners.mjs b/config/scripts/ci/unit-selection/check-test-file-runners.mjs index 09f620398b7..c47bc154254 100644 --- a/config/scripts/ci/unit-selection/check-test-file-runners.mjs +++ b/config/scripts/ci/unit-selection/check-test-file-runners.mjs @@ -84,7 +84,7 @@ const NON_UNIT_TEST_RUNNERS = [ // `orca plugin test` runs every example's own tests in Node; the unit test drives it. name: 'example plugin tests (orca plugin test)', include: ['examples/plugins/*/src/**/*.test.ts', 'examples/plugins/*/src/**/*.test.tsx'], - sources: ['src/cli/plugin-authoring/plugin-test-runner.test.ts'], + sources: ['src/cli/plugins/authoring/plugin-test-runner.test.ts'], mirrors: ['readdirSync(EXAMPLES)'] }, { diff --git a/config/scripts/plugin-authoring/content-validation/validate-plugin-files.mjs b/config/scripts/plugin-authoring/content-validation/validate-plugin-files.mjs index bbdb13719aa..45164a171a7 100644 --- a/config/scripts/plugin-authoring/content-validation/validate-plugin-files.mjs +++ b/config/scripts/plugin-authoring/content-validation/validate-plugin-files.mjs @@ -17,11 +17,12 @@ const { validatePluginFile, pluginManifestWarnings } = loadPluginSchemaModule( const { parsePluginManifest } = loadPluginSchemaModule('manifest/plugin-manifest.ts') // Why the CLI's main entry: `orca plugin validate` and `release-entry` run these same checks, and // the printed contentHash must be what Orca computes at install. -const { - pluginCatalogReleaseForHead, - validateDeclaredPluginArtifacts, - validatePluginInstallContent -} = loadPluginSchemaModule('../../main/plugins/plugin-authoring-checks.ts') +const { pluginCatalogReleaseForHead } = loadPluginSchemaModule( + '../../main/plugins/plugin-catalog-release-entry.ts' +) +const { validateDeclaredPluginArtifacts, validatePluginInstallContent } = loadPluginSchemaModule( + '../../main/plugins/plugin-artifact-validation.ts' +) // Why shared: `orca plugin validate` runs these same strict artifact checks. const { strictPluginArtifactErrors, strictPluginArtifactWarnings } = loadPluginSchemaModule( 'authoring/plugin-strict-artifact-checks.ts' diff --git a/config/scripts/plugin-authoring/content-validation/validate-plugin-files.test.mjs b/config/scripts/plugin-authoring/content-validation/validate-plugin-files.test.mjs index f9ddb0587af..18ce2787ee4 100644 --- a/config/scripts/plugin-authoring/content-validation/validate-plugin-files.test.mjs +++ b/config/scripts/plugin-authoring/content-validation/validate-plugin-files.test.mjs @@ -4,7 +4,7 @@ import path from 'node:path' import process from 'node:process' import { runProcess } from '@orca/process-host' import { afterEach, describe, expect, it } from 'vitest' -import { createPluginFromTemplate } from '../../../../src/cli/plugin-authoring/plugin-create' +import { createPluginFromTemplate } from '../../../../src/cli/plugins/authoring/plugin-create' const SCRIPT = path.join(import.meta.dirname, './validate-plugin-files.mjs') const roots = [] diff --git a/config/scripts/skills/bundle-generation/generate-bundled-skill-guides.mjs b/config/scripts/skills/bundle-generation/generate-bundled-skill-guides.mjs index 40056f21e91..7cb75b19306 100644 --- a/config/scripts/skills/bundle-generation/generate-bundled-skill-guides.mjs +++ b/config/scripts/skills/bundle-generation/generate-bundled-skill-guides.mjs @@ -179,7 +179,7 @@ function serializeEmbeddedModule(guides) { }) .join(',\n') - return `// Generated by config/scripts/generate-bundled-skill-guides.mjs. Do not edit.\n\nexport type BundledSkillGuideReference = {\n readonly name: string\n readonly markdown: string\n}\n\nexport type BundledSkillGuide = {\n readonly name: string\n readonly description: string\n readonly markdown: string\n readonly aliases: readonly string[]\n readonly references: readonly BundledSkillGuideReference[]\n}\n\n${markdownConstants}\n\n// oxfmt-ignore\nexport const BUNDLED_SKILL_GUIDES = [\n${guideEntries}\n] as const satisfies readonly BundledSkillGuide[]\n` + return `// Generated by config/scripts/skills/bundle-generation/generate-bundled-skill-guides.mjs. Do not edit.\n\nexport type BundledSkillGuideReference = {\n readonly name: string\n readonly markdown: string\n}\n\nexport type BundledSkillGuide = {\n readonly name: string\n readonly description: string\n readonly markdown: string\n readonly aliases: readonly string[]\n readonly references: readonly BundledSkillGuideReference[]\n}\n\n${markdownConstants}\n\n// oxfmt-ignore\nexport const BUNDLED_SKILL_GUIDES = [\n${guideEntries}\n] as const satisfies readonly BundledSkillGuide[]\n` } async function readGuideReferences(repoRoot, guideName) { @@ -317,7 +317,7 @@ async function buildArtifacts(repoRoot = REPO_ROOT) { return [ { - path: path.join(repoRoot, 'src', 'cli', 'bundled-skill-guides.ts'), + path: path.join(repoRoot, 'src', 'cli', 'skills', 'bundled-skill-guides.ts'), content: serializeEmbeddedModule(guides) }, ...projections diff --git a/config/scripts/workflow/source-routing/pr-plugin-e2e-source-routes.mjs b/config/scripts/workflow/source-routing/pr-plugin-e2e-source-routes.mjs index 4b911af7b5c..1a5dcbe854b 100644 --- a/config/scripts/workflow/source-routing/pr-plugin-e2e-source-routes.mjs +++ b/config/scripts/workflow/source-routing/pr-plugin-e2e-source-routes.mjs @@ -165,7 +165,7 @@ const SPECIFIC_PLUGIN_ROUTES = [ 'src\\/main\\/plugins\\/panels\\/(?:frame-serving\\/)?plugin-frame-(?:module-|source-serving|protocol\\.ts$)', 'src\\/main\\/plugins\\/plugin-content-hash', 'src\\/shared\\/plugins\\/frames\\/plugin-frame-source-modules', - 'src\\/cli\\/plugin-authoring\\/plugin-(?:type-files|components-template)', + 'src\\/cli\\/plugins\\/authoring\\/plugin-(?:type-files|components-template)', 'examples\\/plugins\\/hello-tsx\\/' ].join('|'), 'plugin-frame-guest|plugin-smoke' @@ -218,7 +218,7 @@ const SPECIFIC_PLUGIN_ROUTES = [ 'src\\/(?:main\\/plugins\\/view\\/(?!plugin-view-replay)|shared\\/plugins\\/view\\/)', 'src\\/main\\/runtime\\/rpc\\/methods\\/plugin-view', // Every `plugin shot` checks for a chrome region and resolves the default shots folder. - 'src\\/cli\\/(?:handlers\\/plugin-(?:view|shot-chrome|shot-default-dir)|specs\\/plugin-view)', + 'src\\/cli\\/(?:plugins\\/view\\/plugin-(?:view|shot-chrome|shot-default-dir)|specs\\/plugin-view)', 'src\\/renderer\\/src\\/hooks\\/(?:ipc-events\\/plugin-view-ipc-bridge|use-document-dark-theme)', 'src\\/renderer\\/src\\/components\\/right-sidebar\\/(?:plugin-panels\\/health\\/(?:plugin-panel-draw-status|use-plugin-panel-draw-report)|right-sidebar-panel-content)' ].join('|'), @@ -233,14 +233,14 @@ const SPECIFIC_PLUGIN_ROUTES = [ `${STORE}shot-replay`, 'src\\/main\\/plugins\\/view\\/plugin-view-replay', 'src\\/main\\/plugins\\/testing\\/plugin-test-(?:hooks|register)', - 'src\\/cli\\/(?:handlers|specs)\\/plugin-shot-scenario', - 'src\\/cli\\/plugin-authoring\\/plugin-(?:panel-check|test-runner|test-node)' + 'src\\/cli\\/(?:plugins\\/view|specs)\\/plugin-shot-scenario', + 'src\\/cli\\/plugins\\/authoring\\/plugin-(?:panel-check|test-runner|test-node)' ].join('|') ), pluginRoute( 'plugins.view-shot-editor', ['plugin-shot-editor'], - 'src\\/cli\\/handlers\\/plugin-shot-editor' + 'src\\/cli\\/plugins\\/view\\/plugin-shot-editor' ), pluginRoute( 'plugins.startup-and-manifest', diff --git a/src/cli/handler-group-manifest.ts b/src/cli/handler-group-manifest.ts index 7a72401588c..f19db15d20c 100644 --- a/src/cli/handler-group-manifest.ts +++ b/src/cli/handler-group-manifest.ts @@ -4,7 +4,7 @@ import { JIRA_HANDLER_GROUP } from './jira/jira-handler-group' import { LINEAR_HANDLER_GROUP } from './linear/linear-handler-group' import { REFERENCE_HANDLER_GROUP } from './reference-handler-group' import { SERVE_PAIRING_HANDLER_GROUPS } from './server/serve-pairing-handler-groups' -import { PLUGIN_HANDLER_GROUPS } from './plugin-handler-groups' +import { PLUGIN_HANDLER_GROUPS } from './plugins/plugin-handler-groups' import { HOST_HANDLER_GROUPS } from './hosts/host-handler-groups' import { WORKTREE_HANDLER_GROUPS } from './workspaces/worktree-handler-groups' @@ -248,13 +248,13 @@ export const HANDLER_GROUPS: readonly HandlerGroup[] = [ { name: 'skill-sharing', keys: ['skills installed', 'skills share'], - load: async () => (await import('./handlers/skill-sharing.js')).SKILL_SHARING_HANDLERS + load: async () => (await import('./skills/skill-sharing.js')).SKILL_SHARING_HANDLERS }, ...PLUGIN_HANDLER_GROUPS, { name: 'skills', keys: ['skills list', 'skills get', 'skills install', 'skills update'], - load: async () => (await import('./handlers/skills.js')).SKILL_HANDLERS + load: async () => (await import('./skills/skills.js')).SKILL_HANDLERS }, { name: 'search', diff --git a/src/cli/plugin-dev-watch/plugin-dev-watch-process.test.ts b/src/cli/plugin-dev-watch/plugin-dev-watch-process.test.ts deleted file mode 100644 index 3c2f9cfa5fc..00000000000 --- a/src/cli/plugin-dev-watch/plugin-dev-watch-process.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { EventEmitter } from 'node:events' -import { afterEach, describe, expect, it, vi } from 'vitest' -import { onPluginDevWatchInterrupt, waitUnlessAborted } from './plugin-dev-watch-process' - -function fakeProcess(platform: NodeJS.Platform) { - const emitter = new EventEmitter() - return Object.assign(emitter, { platform }) -} - -afterEach(() => vi.useRealTimers()) - -describe('onPluginDevWatchInterrupt', () => { - it('listens for Ctrl-C and Ctrl-Break on Windows, and unsubscribes', () => { - const target = fakeProcess('win32') - const listener = vi.fn() - const unsubscribe = onPluginDevWatchInterrupt(listener, target) - target.emit('SIGINT') - target.emit('SIGBREAK') - target.emit('SIGTERM') - expect(listener).toHaveBeenCalledTimes(2) - unsubscribe() - target.emit('SIGINT') - expect(listener).toHaveBeenCalledTimes(2) - }) - - it('also stops on SIGTERM and SIGHUP on POSIX', () => { - const target = fakeProcess('linux') - const listener = vi.fn() - onPluginDevWatchInterrupt(listener, target) - for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) { - target.emit(signal) - } - expect(listener).toHaveBeenCalledTimes(3) - }) -}) - -describe('waitUnlessAborted', () => { - it('resolves after the delay, or at once when aborted', async () => { - vi.useFakeTimers() - const stop = new AbortController() - let done = false - void waitUnlessAborted(500, stop.signal).then(() => { - done = true - }) - await vi.advanceTimersByTimeAsync(499) - expect(done).toBe(false) - stop.abort() - await Promise.resolve() - expect(done).toBe(true) - await expect(waitUnlessAborted(500, stop.signal)).resolves.toBeUndefined() - }) -}) diff --git a/src/cli/handlers/plugin-authoring.ts b/src/cli/plugins/authoring/plugin-authoring.ts similarity index 88% rename from src/cli/handlers/plugin-authoring.ts rename to src/cli/plugins/authoring/plugin-authoring.ts index 6558fbec64a..95338ce0679 100644 --- a/src/cli/handlers/plugin-authoring.ts +++ b/src/cli/plugins/authoring/plugin-authoring.ts @@ -1,34 +1,25 @@ -import type { CommandHandler } from '../dispatch' -import { createPluginFromTemplate, PluginCreateError } from '../plugin-authoring/plugin-create' +import type { CommandHandler } from '../../dispatch' +import { createPluginFromTemplate, PluginCreateError } from './plugin-create' import { inspectPluginFolder, validatePluginTarget, type PluginInspection, type PluginValidationReport -} from '../plugin-authoring/plugin-folder-validation' -import { - isPluginTemplateName, - PLUGIN_TEMPLATE_NAMES -} from '../plugin-authoring/plugin-template-names' -import { - formatPluginReleaseEntry, - pluginReleaseEntryForFolder -} from '../plugin-authoring/plugin-release-entry' +} from './plugin-folder-validation' +import { isPluginTemplateName, PLUGIN_TEMPLATE_NAMES } from './plugin-template-names' +import { formatPluginReleaseEntry, pluginReleaseEntryForFolder } from './plugin-release-entry' import { printPluginResult, requirePluginPathArg, resolveLocalPluginPath -} from '../plugin-authoring/plugin-cli-output' -import { writePluginTypeFiles } from '../plugin-authoring/plugin-type-files' -import { findPluginTestFiles, runPluginTests } from '../plugin-authoring/plugin-test-runner' -import { checkPluginPanels, formatPluginPanelCheck } from '../plugin-authoring/plugin-panel-check' -import { - checkPluginEditors, - formatPluginEditorCheck -} from '../plugin-authoring/plugin-editor-check' -import { RuntimeClientError } from '../runtime/types' -import { PLUGIN_INVALID_CODE } from '../../shared/plugins/approval/plugin-cli-approval' -import { formatPluginGrantLines } from '../plugin-authoring/plugin-grant-lines' +} from '../plugin-cli-output' +import { writePluginTypeFiles } from './plugin-type-files' +import { findPluginTestFiles, runPluginTests } from './plugin-test-runner' +import { checkPluginPanels, formatPluginPanelCheck } from './plugin-panel-check' +import { checkPluginEditors, formatPluginEditorCheck } from './plugin-editor-check' +import { RuntimeClientError } from '../../runtime/types' +import { PLUGIN_INVALID_CODE } from '../../../shared/plugins/approval/plugin-cli-approval' +import { formatPluginGrantLines } from '../plugin-grant-lines' function optionalString(flags: Map, name: string): string | undefined { const value = flags.get(name) diff --git a/src/cli/plugin-authoring/plugin-components-template.ts b/src/cli/plugins/authoring/plugin-components-template.ts similarity index 100% rename from src/cli/plugin-authoring/plugin-components-template.ts rename to src/cli/plugins/authoring/plugin-components-template.ts diff --git a/src/cli/plugin-authoring/plugin-content-templates.ts b/src/cli/plugins/authoring/plugin-content-templates.ts similarity index 100% rename from src/cli/plugin-authoring/plugin-content-templates.ts rename to src/cli/plugins/authoring/plugin-content-templates.ts diff --git a/src/cli/plugin-authoring/plugin-create.ts b/src/cli/plugins/authoring/plugin-create.ts similarity index 97% rename from src/cli/plugin-authoring/plugin-create.ts rename to src/cli/plugins/authoring/plugin-create.ts index bdc3c60a0c9..5af2ec3bf5b 100644 --- a/src/cli/plugin-authoring/plugin-create.ts +++ b/src/cli/plugins/authoring/plugin-create.ts @@ -1,7 +1,7 @@ import { existsSync, readdirSync, statSync } from 'node:fs' import { mkdir, writeFile } from 'node:fs/promises' import { basename, dirname, join, resolve } from 'node:path' -import { parsePluginManifest } from '../../shared/plugins/manifest/plugin-manifest' +import { parsePluginManifest } from '../../../shared/plugins/manifest/plugin-manifest' import { pluginDisplayNameFromId } from './plugin-display-name' import { PLUGIN_TEMPLATE_NAMES, type PluginTemplateName } from './plugin-template-names' import { pluginTemplateKey } from './plugin-template-files' diff --git a/src/cli/plugin-authoring/plugin-display-name.ts b/src/cli/plugins/authoring/plugin-display-name.ts similarity index 100% rename from src/cli/plugin-authoring/plugin-display-name.ts rename to src/cli/plugins/authoring/plugin-display-name.ts diff --git a/src/cli/plugin-authoring/plugin-editor-check-loader-rules.test.ts b/src/cli/plugins/authoring/plugin-editor-check-loader-rules.test.ts similarity index 100% rename from src/cli/plugin-authoring/plugin-editor-check-loader-rules.test.ts rename to src/cli/plugins/authoring/plugin-editor-check-loader-rules.test.ts diff --git a/src/cli/plugin-authoring/plugin-editor-check.test.ts b/src/cli/plugins/authoring/plugin-editor-check.test.ts similarity index 98% rename from src/cli/plugin-authoring/plugin-editor-check.test.ts rename to src/cli/plugins/authoring/plugin-editor-check.test.ts index f39e207ba25..25c42fd272c 100644 --- a/src/cli/plugin-authoring/plugin-editor-check.test.ts +++ b/src/cli/plugins/authoring/plugin-editor-check.test.ts @@ -5,7 +5,7 @@ import { afterEach, describe, expect, it } from 'vitest' import { checkPluginEditors } from './plugin-editor-check' import { createPluginFromTemplate } from './plugin-create' import { validatePluginTarget } from './plugin-folder-validation' -import { checkPluginDevFolder } from '../plugin-dev-watch/plugin-dev-watch-check' +import { checkPluginDevFolder } from '../dev-watch/plugin-dev-watch-check' const roots: string[] = [] diff --git a/src/cli/plugin-authoring/plugin-editor-check.ts b/src/cli/plugins/authoring/plugin-editor-check.ts similarity index 93% rename from src/cli/plugin-authoring/plugin-editor-check.ts rename to src/cli/plugins/authoring/plugin-editor-check.ts index db21881764d..42cdef7fea3 100644 --- a/src/cli/plugin-authoring/plugin-editor-check.ts +++ b/src/cli/plugins/authoring/plugin-editor-check.ts @@ -4,8 +4,8 @@ import { pathToFileURL } from 'node:url' import type { PluginEditorCheckIssue, PluginEditorStaticCheck -} from '../../main/plugins/testing/plugin-editor-static-check' -import { parsePluginManifest } from '../../shared/plugins/manifest/plugin-manifest' +} from '../../../main/plugins/testing/plugin-editor-static-check' +import { parsePluginManifest } from '../../../shared/plugins/manifest/plugin-manifest' import { pluginTestingFiles } from './plugin-test-runner' export type PluginEditorCheck = { diff --git a/src/cli/plugin-authoring/plugin-editor-template.test.ts b/src/cli/plugins/authoring/plugin-editor-template.test.ts similarity index 100% rename from src/cli/plugin-authoring/plugin-editor-template.test.ts rename to src/cli/plugins/authoring/plugin-editor-template.test.ts diff --git a/src/cli/plugin-authoring/plugin-editor-template.ts b/src/cli/plugins/authoring/plugin-editor-template.ts similarity index 97% rename from src/cli/plugin-authoring/plugin-editor-template.ts rename to src/cli/plugins/authoring/plugin-editor-template.ts index 255f012ce3c..5dabba74e73 100644 --- a/src/cli/plugin-authoring/plugin-editor-template.ts +++ b/src/cli/plugins/authoring/plugin-editor-template.ts @@ -1,4 +1,4 @@ -import { PLUGIN_EDITOR_FRAME_SINCE } from '../../shared/plugins/manifest/plugin-schema-field-docs' +import { PLUGIN_EDITOR_FRAME_SINCE } from '../../../shared/plugins/manifest/plugin-schema-field-docs' import { PLUGIN_TYPES_DIR } from './plugin-type-files' import { COMMON_FILES, diff --git a/src/cli/plugin-authoring/plugin-folder-validation.ts b/src/cli/plugins/authoring/plugin-folder-validation.ts similarity index 89% rename from src/cli/plugin-authoring/plugin-folder-validation.ts rename to src/cli/plugins/authoring/plugin-folder-validation.ts index 088440dde21..7757e65a91f 100644 --- a/src/cli/plugin-authoring/plugin-folder-validation.ts +++ b/src/cli/plugins/authoring/plugin-folder-validation.ts @@ -1,32 +1,32 @@ import { createHash } from 'node:crypto' import { existsSync, readFileSync, statSync } from 'node:fs' import { basename, dirname, join, resolve, sep } from 'node:path' +import { hashPluginTree } from '../../../main/plugins/plugin-content-hash' import { - hashPluginTree, validateDeclaredPluginArtifacts, validatePluginInstallContent -} from '../../main/plugins/plugin-authoring-checks' -import { PLUGIN_CATALOG_FILENAME } from '../../shared/plugins/catalog/plugin-catalog' +} from '../../../main/plugins/plugin-artifact-validation' +import { PLUGIN_CATALOG_FILENAME } from '../../../shared/plugins/catalog/plugin-catalog' import { pluginApprovalGrants, pluginNeedsCliApproval, type PluginApprovalGrants -} from '../../shared/plugins/approval/plugin-cli-approval' +} from '../../../shared/plugins/approval/plugin-cli-approval' import { strictPluginArtifactErrors, strictPluginArtifactWarnings -} from '../../shared/plugins/authoring/plugin-strict-artifact-checks' -import { validatePluginFile } from '../../shared/plugins/installation/plugin-file-validation' +} from '../../../shared/plugins/authoring/plugin-strict-artifact-checks' +import { validatePluginFile } from '../../../shared/plugins/installation/plugin-file-validation' import { localPluginPublisher, withPluginPublisher -} from '../../shared/plugins/installation/plugin-publisher-derivation' +} from '../../../shared/plugins/installation/plugin-publisher-derivation' import { PLUGIN_MANIFEST_FILENAME, parsePluginManifest, qualifiedPluginKey, type PluginManifest -} from '../../shared/plugins/manifest/plugin-manifest' +} from '../../../shared/plugins/manifest/plugin-manifest' export type PluginFolderSummary = { pluginKey: string diff --git a/src/cli/plugin-authoring/plugin-frame-templates.ts b/src/cli/plugins/authoring/plugin-frame-templates.ts similarity index 100% rename from src/cli/plugin-authoring/plugin-frame-templates.ts rename to src/cli/plugins/authoring/plugin-frame-templates.ts diff --git a/src/cli/plugin-authoring/plugin-hook-template.test.ts b/src/cli/plugins/authoring/plugin-hook-template.test.ts similarity index 100% rename from src/cli/plugin-authoring/plugin-hook-template.test.ts rename to src/cli/plugins/authoring/plugin-hook-template.test.ts diff --git a/src/cli/plugin-authoring/plugin-hook-template.ts b/src/cli/plugins/authoring/plugin-hook-template.ts similarity index 97% rename from src/cli/plugin-authoring/plugin-hook-template.ts rename to src/cli/plugins/authoring/plugin-hook-template.ts index 825ebd109f8..a4b08e0d64e 100644 --- a/src/cli/plugin-authoring/plugin-hook-template.ts +++ b/src/cli/plugins/authoring/plugin-hook-template.ts @@ -1,4 +1,4 @@ -import { PLUGIN_HOOKS_SINCE } from '../../shared/plugins/manifest/plugin-schema-field-docs' +import { PLUGIN_HOOKS_SINCE } from '../../../shared/plugins/manifest/plugin-schema-field-docs' import { COMMON_FILES, json, diff --git a/src/cli/plugin-authoring/plugin-panel-check-script.ts b/src/cli/plugins/authoring/plugin-panel-check-script.ts similarity index 100% rename from src/cli/plugin-authoring/plugin-panel-check-script.ts rename to src/cli/plugins/authoring/plugin-panel-check-script.ts diff --git a/src/cli/plugin-authoring/plugin-panel-check.ts b/src/cli/plugins/authoring/plugin-panel-check.ts similarity index 97% rename from src/cli/plugin-authoring/plugin-panel-check.ts rename to src/cli/plugins/authoring/plugin-panel-check.ts index b8d1d8d9219..d69236cccae 100644 --- a/src/cli/plugin-authoring/plugin-panel-check.ts +++ b/src/cli/plugins/authoring/plugin-panel-check.ts @@ -4,9 +4,9 @@ import { join, relative, sep } from 'node:path' import { fileURLToPath, pathToFileURL } from 'node:url' import { runProcess } from '@orca/process-host' import { z } from 'zod' -import type { PluginDeclarativeNode } from '../../shared/plugins/declarative/plugin-declarative-catalog' -import { parsePluginDeclarativeTree } from '../../shared/plugins/declarative/plugin-declarative-tree' -import { parsePluginManifest } from '../../shared/plugins/manifest/plugin-manifest' +import type { PluginDeclarativeNode } from '../../../shared/plugins/declarative/plugin-declarative-catalog' +import { parsePluginDeclarativeTree } from '../../../shared/plugins/declarative/plugin-declarative-tree' +import { parsePluginManifest } from '../../../shared/plugins/manifest/plugin-manifest' import { PLUGIN_PANEL_CHECK_SCRIPT } from './plugin-panel-check-script' import { selectPluginTestNode } from './plugin-test-node' import { pluginTestingFiles } from './plugin-test-runner' diff --git a/src/cli/plugin-authoring/plugin-release-entry.test.ts b/src/cli/plugins/authoring/plugin-release-entry.test.ts similarity index 77% rename from src/cli/plugin-authoring/plugin-release-entry.test.ts rename to src/cli/plugins/authoring/plugin-release-entry.test.ts index 99c95095830..942f0d473d0 100644 --- a/src/cli/plugin-authoring/plugin-release-entry.test.ts +++ b/src/cli/plugins/authoring/plugin-release-entry.test.ts @@ -1,9 +1,9 @@ -import { mkdtemp, rm, writeFile } from 'node:fs/promises' +import { mkdtemp, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { runProcess } from '@orca/process-host' import { afterEach, describe, expect, it } from 'vitest' -import { hashPluginTree } from '../../main/plugins/plugin-authoring-checks' +import { hashPluginTree } from '../../../main/plugins/plugin-content-hash' import { createPluginFromTemplate } from './plugin-create' import { validatePluginTarget } from './plugin-folder-validation' import { formatPluginReleaseEntry, pluginReleaseEntryForFolder } from './plugin-release-entry' @@ -64,21 +64,6 @@ describe('orca plugin release-entry', () => { expect(text).toContain(`"commit": "${result.entry?.commit}"`) }) - it('pins the committed files, not uncommitted edits', async () => { - const repo = await tempRoot() - await git(repo, ['init', '--quiet']) - await createPluginFromTemplate({ dir: repo, template: 'theme', publisher: 'acme' }) - await git(repo, ['add', '.']) - await git(repo, ['commit', '--quiet', '-m', 'init']) - const committed = await releaseFor(repo) - await writeFile(join(repo, 'NOTES.txt'), 'draft') - - const result = await releaseFor(repo) - expect(result.sourcePath).toBeUndefined() - expect(result.entry?.contentHash).toBe(committed.entry?.contentHash) - expect(result.note).toContain('uncommitted changes') - }) - it('gives no entry when the commit lacks a file the manifest declares', async () => { const repo = await tempRoot() await git(repo, ['init', '--quiet']) diff --git a/src/cli/plugin-authoring/plugin-release-entry.ts b/src/cli/plugins/authoring/plugin-release-entry.ts similarity index 95% rename from src/cli/plugin-authoring/plugin-release-entry.ts rename to src/cli/plugins/authoring/plugin-release-entry.ts index 3a878b13c48..954a3bac92e 100644 --- a/src/cli/plugin-authoring/plugin-release-entry.ts +++ b/src/cli/plugins/authoring/plugin-release-entry.ts @@ -1,7 +1,7 @@ import { pluginCatalogReleaseForHead, type PluginCatalogReleaseReport -} from '../../main/plugins/plugin-authoring-checks' +} from '../../../main/plugins/plugin-catalog-release-entry' import type { PluginValidationReport } from './plugin-folder-validation' export type PluginReleaseEntryResult = PluginCatalogReleaseReport & { diff --git a/src/cli/plugin-authoring/plugin-template-files.ts b/src/cli/plugins/authoring/plugin-template-files.ts similarity index 90% rename from src/cli/plugin-authoring/plugin-template-files.ts rename to src/cli/plugins/authoring/plugin-template-files.ts index 7ca8c984453..e2cf2c96fdc 100644 --- a/src/cli/plugin-authoring/plugin-template-files.ts +++ b/src/cli/plugins/authoring/plugin-template-files.ts @@ -1,7 +1,7 @@ /** File helpers shared by the `orca plugin create` templates. */ -import { localPluginPublisher } from '../../shared/plugins/installation/plugin-publisher-derivation' -import { PLUGIN_DERIVED_PUBLISHER_SINCE } from '../../shared/plugins/manifest/plugin-schema-field-docs' +import { localPluginPublisher } from '../../../shared/plugins/installation/plugin-publisher-derivation' +import { PLUGIN_DERIVED_PUBLISHER_SINCE } from '../../../shared/plugins/manifest/plugin-schema-field-docs' /** `publisher` is deprecated: set only for authors who still support Orca that requires it. */ export type PluginTemplateInput = { publisher?: string; id: string; name: string } diff --git a/src/cli/plugin-authoring/plugin-template-names.ts b/src/cli/plugins/authoring/plugin-template-names.ts similarity index 100% rename from src/cli/plugin-authoring/plugin-template-names.ts rename to src/cli/plugins/authoring/plugin-template-names.ts diff --git a/src/cli/plugin-authoring/plugin-templates.test.ts b/src/cli/plugins/authoring/plugin-templates.test.ts similarity index 100% rename from src/cli/plugin-authoring/plugin-templates.test.ts rename to src/cli/plugins/authoring/plugin-templates.test.ts diff --git a/src/cli/plugin-authoring/plugin-templates.ts b/src/cli/plugins/authoring/plugin-templates.ts similarity index 100% rename from src/cli/plugin-authoring/plugin-templates.ts rename to src/cli/plugins/authoring/plugin-templates.ts diff --git a/src/cli/plugin-authoring/plugin-test-node.test.ts b/src/cli/plugins/authoring/plugin-test-node.test.ts similarity index 100% rename from src/cli/plugin-authoring/plugin-test-node.test.ts rename to src/cli/plugins/authoring/plugin-test-node.test.ts diff --git a/src/cli/plugin-authoring/plugin-test-node.ts b/src/cli/plugins/authoring/plugin-test-node.ts similarity index 100% rename from src/cli/plugin-authoring/plugin-test-node.ts rename to src/cli/plugins/authoring/plugin-test-node.ts diff --git a/src/cli/plugin-authoring/plugin-test-runner.test.ts b/src/cli/plugins/authoring/plugin-test-runner.test.ts similarity index 98% rename from src/cli/plugin-authoring/plugin-test-runner.test.ts rename to src/cli/plugins/authoring/plugin-test-runner.test.ts index 90d44316c48..0ed825bc9e3 100644 --- a/src/cli/plugin-authoring/plugin-test-runner.test.ts +++ b/src/cli/plugins/authoring/plugin-test-runner.test.ts @@ -4,7 +4,7 @@ import { tmpdir } from 'node:os' import { join, resolve } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' import { createPluginFromTemplate } from './plugin-create' -import { parsePluginDeclarativeTree } from '../../shared/plugins/declarative/plugin-declarative-tree' +import { parsePluginDeclarativeTree } from '../../../shared/plugins/declarative/plugin-declarative-tree' import { checkPluginPanels, formatPluginPanelCheck, @@ -102,7 +102,7 @@ describe('orca plugin test', () => { ) }) -const EXAMPLES = resolve(__dirname, '../../../examples/plugins') +const EXAMPLES = resolve(__dirname, '../../../../examples/plugins') // Every example with its own tests (examples/plugins/*/src/*.test.tsx). const TESTED_EXAMPLES = readdirSync(EXAMPLES).filter((name) => { const src = join(EXAMPLES, name, 'src') diff --git a/src/cli/plugin-authoring/plugin-test-runner.ts b/src/cli/plugins/authoring/plugin-test-runner.ts similarity index 99% rename from src/cli/plugin-authoring/plugin-test-runner.ts rename to src/cli/plugins/authoring/plugin-test-runner.ts index 8bab07b4ce4..e0ae4c4b500 100644 --- a/src/cli/plugin-authoring/plugin-test-runner.ts +++ b/src/cli/plugins/authoring/plugin-test-runner.ts @@ -10,7 +10,7 @@ export const PLUGIN_TEST_FILE = /\.test\.(?:tsx?|mts|jsx?|mjs)$/i // Not plugin sources: dependencies, Orca's own generated files, VCS and build output. const SKIPPED_DIRS = new Set(['node_modules', '.orca', '.git', 'dist', 'out', 'coverage']) -const defaultAppRoot = (): string => join(__dirname, '..', '..', '..') +const defaultAppRoot = (): string => join(__dirname, '..', '..', '..', '..') /** The files Orca ships for testing components panels, from the app this CLI belongs to. */ export function pluginTestingFiles(appRoot = defaultAppRoot()) { diff --git a/src/cli/plugin-authoring/plugin-type-files-symlink.test.ts b/src/cli/plugins/authoring/plugin-type-files-symlink.test.ts similarity index 94% rename from src/cli/plugin-authoring/plugin-type-files-symlink.test.ts rename to src/cli/plugins/authoring/plugin-type-files-symlink.test.ts index 8e5a418ec74..f33ac689415 100644 --- a/src/cli/plugin-authoring/plugin-type-files-symlink.test.ts +++ b/src/cli/plugins/authoring/plugin-type-files-symlink.test.ts @@ -5,7 +5,7 @@ import { runProcess } from '@orca/process-host' import { afterEach, describe, expect, it } from 'vitest' import { createPluginFromTemplate } from './plugin-create' -const TSC = resolve(__dirname, '../../../node_modules/typescript/bin/tsc') +const TSC = resolve(__dirname, '../../../../node_modules/typescript/bin/tsc') const cleanup: string[] = [] afterEach(async () => { diff --git a/src/cli/plugin-authoring/plugin-type-files.ts b/src/cli/plugins/authoring/plugin-type-files.ts similarity index 94% rename from src/cli/plugin-authoring/plugin-type-files.ts rename to src/cli/plugins/authoring/plugin-type-files.ts index e167435b228..3be57534f7c 100644 --- a/src/cli/plugin-authoring/plugin-type-files.ts +++ b/src/cli/plugins/authoring/plugin-type-files.ts @@ -1,8 +1,8 @@ import { existsSync } from 'node:fs' import { mkdir, readFile, writeFile } from 'node:fs/promises' import { basename, dirname, join } from 'node:path' -import { isPluginSourceModulePath } from '../../shared/plugins/frames/plugin-frame-source-modules' -import { parsePluginManifest } from '../../shared/plugins/manifest/plugin-manifest' +import { isPluginSourceModulePath } from '../../../shared/plugins/frames/plugin-frame-source-modules' +import { parsePluginManifest } from '../../../shared/plugins/manifest/plugin-manifest' /** Plugin-relative folder `orca plugin types` owns; dev hashing and loading skip it. */ export const PLUGIN_TYPES_DIR = '.orca/types' @@ -52,7 +52,7 @@ const GENERATED_NOTE = /** Packaged apps ship typings as extraResources at /plugins; dev: resources/plugins. */ function defaultTypingsRoot(): string { - const appRoot = join(__dirname, '..', '..', '..') + const appRoot = join(__dirname, '..', '..', '..', '..') return basename(appRoot) === 'app.asar.unpacked' ? join(dirname(appRoot), 'plugins') : join(appRoot, 'resources', 'plugins') diff --git a/src/cli/plugin-authoring/plugin-worker-panel-template.test.ts b/src/cli/plugins/authoring/plugin-worker-panel-template.test.ts similarity index 96% rename from src/cli/plugin-authoring/plugin-worker-panel-template.test.ts rename to src/cli/plugins/authoring/plugin-worker-panel-template.test.ts index a205c52b156..fa0ecf6373b 100644 --- a/src/cli/plugin-authoring/plugin-worker-panel-template.test.ts +++ b/src/cli/plugins/authoring/plugin-worker-panel-template.test.ts @@ -6,7 +6,7 @@ import { afterEach, describe, expect, it } from 'vitest' import { createPluginFromTemplate } from './plugin-create' import { findPluginTestFiles, runPluginTests } from './plugin-test-runner' -const TSC = resolve(__dirname, '../../../node_modules/typescript/bin/tsc') +const TSC = resolve(__dirname, '../../../../node_modules/typescript/bin/tsc') const roots: string[] = [] afterEach(async () => { diff --git a/src/cli/plugin-authoring/plugin-worker-panel-template.ts b/src/cli/plugins/authoring/plugin-worker-panel-template.ts similarity index 100% rename from src/cli/plugin-authoring/plugin-worker-panel-template.ts rename to src/cli/plugins/authoring/plugin-worker-panel-template.ts diff --git a/src/cli/plugin-dev-watch/plugin-dev-watch-approval.test.ts b/src/cli/plugins/dev-watch/plugin-dev-watch-approval.test.ts similarity index 96% rename from src/cli/plugin-dev-watch/plugin-dev-watch-approval.test.ts rename to src/cli/plugins/dev-watch/plugin-dev-watch-approval.test.ts index 3e73439d328..44d81f8a5bc 100644 --- a/src/cli/plugin-dev-watch/plugin-dev-watch-approval.test.ts +++ b/src/cli/plugins/dev-watch/plugin-dev-watch-approval.test.ts @@ -1,7 +1,7 @@ import { PassThrough } from 'node:stream' import { describe, expect, it } from 'vitest' -import '../runtime/types' +import '../../runtime/types' import { createTerminalConfirm } from './plugin-dev-watch-approval' import './plugin-dev-watch-loop' diff --git a/src/cli/plugin-dev-watch/plugin-dev-watch-approval.ts b/src/cli/plugins/dev-watch/plugin-dev-watch-approval.ts similarity index 95% rename from src/cli/plugin-dev-watch/plugin-dev-watch-approval.ts rename to src/cli/plugins/dev-watch/plugin-dev-watch-approval.ts index 0c629229830..27212f7c01b 100644 --- a/src/cli/plugin-dev-watch/plugin-dev-watch-approval.ts +++ b/src/cli/plugins/dev-watch/plugin-dev-watch-approval.ts @@ -1,8 +1,8 @@ import { createInterface } from 'node:readline' import { z } from 'zod' -import { formatPluginGrantLines } from '../plugin-authoring/plugin-grant-lines' -import { RuntimeClientError } from '../runtime/types' -import type { DevLoadResult } from '../handlers/plugin-runtime' +import { formatPluginGrantLines } from '../plugin-grant-lines' +import { RuntimeClientError } from '../../runtime/types' +import type { DevLoadResult } from '../plugin-runtime' /** Asks a yes/no question; false on anything but yes, end of input or Ctrl-C. */ export type PluginDevWatchConfirm = (question: string) => Promise diff --git a/src/cli/plugin-dev-watch/plugin-dev-watch-check.test.ts b/src/cli/plugins/dev-watch/plugin-dev-watch-check.test.ts similarity index 52% rename from src/cli/plugin-dev-watch/plugin-dev-watch-check.test.ts rename to src/cli/plugins/dev-watch/plugin-dev-watch-check.test.ts index 3415de2b7d8..9dbf9536c24 100644 --- a/src/cli/plugin-dev-watch/plugin-dev-watch-check.test.ts +++ b/src/cli/plugins/dev-watch/plugin-dev-watch-check.test.ts @@ -1,9 +1,6 @@ import { describe, expect, it, vi } from 'vitest' -import type { PluginHostListEntry } from '../../shared/plugins/plugin-list-entry' -import { - createPluginDevWatchCheckQueue, - type PluginDevWatchCheckResult -} from './plugin-dev-watch-check' +import type { PluginHostListEntry } from '../../../shared/plugins/plugin-list-entry' +import type { PluginDevWatchCheckResult } from './plugin-dev-watch-check' import { formatPluginDevWatchEvent, type PluginDevWatchEvent } from './plugin-dev-watch-events' import { runPluginDevWatch, type PluginDevWatchHost } from './plugin-dev-watch-loop' @@ -48,21 +45,6 @@ const passed: PluginDevWatchCheckResult = { ], editors: [] } -const failed: PluginDevWatchCheckResult = { - ok: false, - invalid: [], - panels: [ - { - panel: 'todos', - entry: 'src/panel.tsx', - elements: 0, - summary: [], - issues: [{ kind: 'plugin', message: 'boom', location: 'src/panel.tsx:4:7' }], - warnings: [] - } - ], - editors: [] -} /** Polls: 1 baseline, 2 status (unchanged), 3 logs, 4 status (one save), then Ctrl-C. */ async function watch( @@ -107,22 +89,6 @@ async function watch( } describe('plugin dev watch --check', () => { - it('checks after the load and after each reload, before the unload line', async () => { - const check = vi.fn>() - check.mockResolvedValueOnce(passed).mockResolvedValueOnce(failed) - const events = await watch(check) - expect(check).toHaveBeenCalledTimes(2) - const types = events.map((event) => event.type) - expect(types[0]).toBe('loaded') - expect(types.filter((type) => type === 'check')).toHaveLength(2) - expect(types.indexOf('check', types.indexOf('reloaded'))).toBeGreaterThan(-1) - expect(types.at(-1)).toBe('unloaded') - const last = events.findLast((event) => event.type === 'check') - expect(last && formatPluginDevWatchEvent(last)).toBe( - '✗ check failed for acme.todos\n todos (src/panel.tsx): failed\n src/panel.tsx:4:7: boom' - ) - }) - it('fails a check that renders while Orca does not run the plugin', async () => { const pending = entry(1, 'pending') const events = await watch(vi.fn().mockResolvedValue(passed), [[entry(0)], [pending]]) @@ -135,34 +101,4 @@ describe('plugin dev watch --check', () => { '✗ check passed, but in Orca acme.todos is pending approval' ) }) - - it('reports a check that cannot run as a warning and keeps watching', async () => { - const events = await watch(async () => { - throw new Error('no node') - }) - expect(events).toContainEqual({ type: 'warning', message: 'check could not run: no node' }) - expect(events.at(-1)?.type).toBe('unloaded') - }) - - it('does not check without --check', async () => { - const events = await watch(undefined) - expect(events.some((event) => event.type === 'check')).toBe(false) - }) -}) - -describe('createPluginDevWatchCheckQueue', () => { - it('runs one check at a time and folds requests made meanwhile into one rerun', async () => { - const releases: (() => void)[] = [] - const run = vi.fn(() => new Promise((resolve) => releases.push(resolve))) - const queue = createPluginDevWatchCheckQueue(run) - queue.request() - queue.request() - queue.request() - expect(run).toHaveBeenCalledTimes(1) - releases.shift()?.() - await vi.waitFor(() => expect(run).toHaveBeenCalledTimes(2)) - releases.shift()?.() - await queue.idle() - expect(run).toHaveBeenCalledTimes(2) - }) }) diff --git a/src/cli/plugin-dev-watch/plugin-dev-watch-check.ts b/src/cli/plugins/dev-watch/plugin-dev-watch-check.ts similarity index 85% rename from src/cli/plugin-dev-watch/plugin-dev-watch-check.ts rename to src/cli/plugins/dev-watch/plugin-dev-watch-check.ts index bc540563b59..0a6a9daf0a1 100644 --- a/src/cli/plugin-dev-watch/plugin-dev-watch-check.ts +++ b/src/cli/plugins/dev-watch/plugin-dev-watch-check.ts @@ -1,6 +1,6 @@ -import { validatePluginTarget } from '../plugin-authoring/plugin-folder-validation' -import { checkPluginPanels, type PluginPanelCheck } from '../plugin-authoring/plugin-panel-check' -import { checkPluginEditors, type PluginEditorCheck } from '../plugin-authoring/plugin-editor-check' +import { validatePluginTarget } from '../authoring/plugin-folder-validation' +import { checkPluginPanels, type PluginPanelCheck } from '../authoring/plugin-panel-check' +import { checkPluginEditors, type PluginEditorCheck } from '../authoring/plugin-editor-check' /** `orca plugin check` for one folder, as data: validation errors, else each panel's render. */ export type PluginDevWatchCheckResult = { diff --git a/src/cli/plugin-dev-watch/plugin-dev-watch-events.ts b/src/cli/plugins/dev-watch/plugin-dev-watch-events.ts similarity index 96% rename from src/cli/plugin-dev-watch/plugin-dev-watch-events.ts rename to src/cli/plugins/dev-watch/plugin-dev-watch-events.ts index 3f426a405d5..bfdc30ad34a 100644 --- a/src/cli/plugin-dev-watch/plugin-dev-watch-events.ts +++ b/src/cli/plugins/dev-watch/plugin-dev-watch-events.ts @@ -1,7 +1,7 @@ -import type { PluginHostStatus } from '../../shared/plugins/plugin-list-entry' -import { formatPluginPanelCheck } from '../plugin-authoring/plugin-panel-check' +import type { PluginHostStatus } from '../../../shared/plugins/plugin-list-entry' +import { formatPluginPanelCheck } from '../authoring/plugin-panel-check' import type { PluginDevWatchCheckResult } from './plugin-dev-watch-check' -import { formatPluginEditorCheck } from '../plugin-authoring/plugin-editor-check' +import { formatPluginEditorCheck } from '../authoring/plugin-editor-check' import { PLUGIN_DEV_WATCH_SHOOTABLE, type PluginDevWatchShot } from './plugin-dev-watch-shot' /** `missing`: the host lists no plugin for the folder (for example, its id changed). */ diff --git a/src/cli/plugin-dev-watch/plugin-dev-watch-loop.test.ts b/src/cli/plugins/dev-watch/plugin-dev-watch-loop.test.ts similarity index 80% rename from src/cli/plugin-dev-watch/plugin-dev-watch-loop.test.ts rename to src/cli/plugins/dev-watch/plugin-dev-watch-loop.test.ts index b0919cf39d9..c3f4ba74854 100644 --- a/src/cli/plugin-dev-watch/plugin-dev-watch-loop.test.ts +++ b/src/cli/plugins/dev-watch/plugin-dev-watch-loop.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest' -import type { PluginHostListEntry } from '../../shared/plugins/plugin-list-entry' -import type { LogsResult } from '../handlers/plugin-runtime' +import type { PluginHostListEntry } from '../../../shared/plugins/plugin-list-entry' +import type { LogsResult } from '../plugin-runtime' import type { PluginDevWatchEvent } from './plugin-dev-watch-events' import { formatPluginDevWatchEvent } from './plugin-dev-watch-events' import { runPluginDevWatch, type PluginDevWatchHost } from './plugin-dev-watch-loop' @@ -184,27 +184,6 @@ describe('runPluginDevWatch', () => { }) }) - it('prints the approval command once when an edit asks for new access', async () => { - const approvalNeeded = vi.fn(async () => ({ - message: 'acme.notes asks for new access', - fingerprint: 'ap1-x', - command: 'orca plugin dev reload /work/notes --approve ap1-x' - })) - const { run, events } = harness({ - stopAfterPolls: 6, - approvalNeeded, - lists: [ - [entry({ devChangeCount: 1, status: 'pending', needsReconsent: true })], - [entry({ devChangeCount: 1, status: 'pending', needsReconsent: true })] - ] - }) - await run - expect(approvalNeeded).toHaveBeenCalledTimes(1) - expect(events.filter((event) => event.type === 'approval')).toEqual([ - expect.objectContaining({ fingerprint: 'ap1-x', command: expect.stringContaining('reload') }) - ]) - }) - it('exits 1 without unloading when the host connection drops', async () => { const { run, events, host } = harness({ failAt: { poll: 1, error: new LostError('connect ECONNREFUSED') } @@ -222,21 +201,4 @@ describe('runPluginDevWatch', () => { await expect(run).resolves.toBe(0) expect(events.filter((event) => event.type === 'warning')).toHaveLength(1) }) - - it('leaves a folder loaded before the session in place', async () => { - const { run, events, host } = harness({ added: false }) - await expect(run).resolves.toBe(0) - expect(host.devUnload).not.toHaveBeenCalled() - expect(events.at(-1)).toMatchObject({ type: 'unloaded', removed: false }) - }) - - it('exits at once on a second Ctrl-C while unloading', async () => { - let secondInterrupt: () => void = () => {} - const devUnload = vi.fn(async () => secondInterrupt()) - const h = harness({ devUnload }) - secondInterrupt = h.interrupt - await h.run - expect(devUnload).toHaveBeenCalledTimes(1) - expect(h.exitNow).toHaveBeenCalledWith(130) - }) }) diff --git a/src/cli/plugin-dev-watch/plugin-dev-watch-loop.ts b/src/cli/plugins/dev-watch/plugin-dev-watch-loop.ts similarity index 98% rename from src/cli/plugin-dev-watch/plugin-dev-watch-loop.ts rename to src/cli/plugins/dev-watch/plugin-dev-watch-loop.ts index c33786253c8..815f3953332 100644 --- a/src/cli/plugin-dev-watch/plugin-dev-watch-loop.ts +++ b/src/cli/plugins/dev-watch/plugin-dev-watch-loop.ts @@ -1,5 +1,5 @@ -import type { DevLoadResult, LogsResult } from '../handlers/plugin-runtime' -import type { PluginHostListEntry } from '../../shared/plugins/plugin-list-entry' +import type { DevLoadResult, LogsResult } from '../plugin-runtime' +import type { PluginHostListEntry } from '../../../shared/plugins/plugin-list-entry' import type { PluginDevWatchApproval, PluginDevWatchEvent, diff --git a/src/cli/plugin-dev-watch/plugin-dev-watch-process.ts b/src/cli/plugins/dev-watch/plugin-dev-watch-process.ts similarity index 100% rename from src/cli/plugin-dev-watch/plugin-dev-watch-process.ts rename to src/cli/plugins/dev-watch/plugin-dev-watch-process.ts diff --git a/src/cli/plugin-dev-watch/plugin-dev-watch-shot.test.ts b/src/cli/plugins/dev-watch/plugin-dev-watch-shot.test.ts similarity index 63% rename from src/cli/plugin-dev-watch/plugin-dev-watch-shot.test.ts rename to src/cli/plugins/dev-watch/plugin-dev-watch-shot.test.ts index cba755ad422..e4127fa0cff 100644 --- a/src/cli/plugin-dev-watch/plugin-dev-watch-shot.test.ts +++ b/src/cli/plugins/dev-watch/plugin-dev-watch-shot.test.ts @@ -1,6 +1,8 @@ import { describe, expect, it, vi } from 'vitest' -import type { PluginHostListEntry, PluginHostStatus } from '../../shared/plugins/plugin-list-entry' -import { RuntimeClientError } from '../runtime/types' +import type { + PluginHostListEntry, + PluginHostStatus +} from '../../../shared/plugins/plugin-list-entry' import { formatPluginDevWatchEvent, type PluginDevWatchEvent } from './plugin-dev-watch-events' import { runPluginDevWatch, type PluginDevWatchHost } from './plugin-dev-watch-loop' import { @@ -102,40 +104,6 @@ const ofType = (events: PluginDevWatchEve events.filter((event): event is Extract => event.type === type) describe('plugin dev watch --shot', () => { - it('opens the panel once, then shoots it after the load and each reload, after the check', async () => { - const order: string[] = [] - const { host, open } = shotHost(async (pluginKey, panelId) => { - order.push(`shot ${pluginKey}/${panelId}`) - return SHOT - }) - const events = await watch( - { - check: async () => { - order.push('check') - return { ok: true, invalid: [], panels: [], editors: [] } - }, - shot: host - }, - [entry(1), entry(2)] - ) - expect(open).toHaveBeenCalledTimes(1) - expect(open).toHaveBeenCalledWith(KEY, 'todos') - expect(order).toEqual([ - 'check', - `shot ${KEY}/todos`, - 'check', - `shot ${KEY}/todos`, - 'check', - `shot ${KEY}/todos` - ]) - const shots = ofType(events, 'shot') - expect(shots).toHaveLength(3) - expect(events.at(-1)?.type).toBe('unloaded') - expect(formatPluginDevWatchEvent(shots[0]!)).toBe( - 'shot /work/shots/acme.todos-todos.png (360x700)' - ) - }) - it('shoots --shot-panel and fails at start, unloading, on an unknown id', async () => { const { host, open } = shotHost(async () => ({ ...SHOT, panelId: 'stats' }), 'stats') await watch({ shot: host }) @@ -178,42 +146,4 @@ describe('plugin dev watch --shot', () => { 'shot skipped: the right sidebar is closed, so todos is not on screen; open it again to resume' ) }) - - it('skips a reload that left the plugin errored', async () => { - const { host, take } = shotHost(async () => SHOT) - await watch({ shot: host }, [entry(1, 'errored')]) - expect(take).toHaveBeenCalledTimes(1) - }) - - it('turns itself off when the host can never capture', async () => { - const { host, take } = shotHost(async () => { - throw new RuntimeClientError('runtime_unsupported', 'too old') - }) - host.open = async () => { - throw new RuntimeClientError('runtime_unsupported', 'too old') - } - const events = await watch({ shot: host }, [entry(1), entry(2)]) - expect(take).not.toHaveBeenCalled() - expect(events).toContainEqual({ - type: 'warning', - message: 'could not show todos for --shot, so --shot is off: too old' - }) - }) - - it('keeps going after a one-off failure', async () => { - const outcomes: (() => Awaited>)[] = [ - () => { - throw new Error('timeout') - }, - () => ({ ...SHOT, settled: false, errors: ['boom'] }) - ] - const { host, take } = shotHost(async () => (outcomes.shift() ?? (() => SHOT))()) - const events = await watch({ shot: host }) - expect(take).toHaveBeenCalledTimes(2) - expect(events).toContainEqual({ type: 'warning', message: 'shot could not run: timeout' }) - const last = ofType(events, 'shot').at(-1) - expect(last && formatPluginDevWatchEvent(last)).toBe( - 'shot /work/shots/acme.todos-todos.png (360x700), not finished drawing\n error: boom' - ) - }) }) diff --git a/src/cli/plugin-dev-watch/plugin-dev-watch-shot.ts b/src/cli/plugins/dev-watch/plugin-dev-watch-shot.ts similarity index 95% rename from src/cli/plugin-dev-watch/plugin-dev-watch-shot.ts rename to src/cli/plugins/dev-watch/plugin-dev-watch-shot.ts index cda01518dfb..89520132ed6 100644 --- a/src/cli/plugin-dev-watch/plugin-dev-watch-shot.ts +++ b/src/cli/plugins/dev-watch/plugin-dev-watch-shot.ts @@ -1,5 +1,5 @@ -import type { PluginHostListEntry } from '../../shared/plugins/plugin-list-entry' -import { RuntimeClientError, RuntimeRpcFailureError } from '../runtime/types' +import type { PluginHostListEntry } from '../../../shared/plugins/plugin-list-entry' +import { RuntimeClientError, RuntimeRpcFailureError } from '../../runtime/types' import type { PluginDevWatchEvent } from './plugin-dev-watch-events' /** One `--shot` PNG written after a load or reload. */ diff --git a/src/cli/handlers/plugin-dev-watch.test.ts b/src/cli/plugins/dev-watch/plugin-dev-watch.test.ts similarity index 94% rename from src/cli/handlers/plugin-dev-watch.test.ts rename to src/cli/plugins/dev-watch/plugin-dev-watch.test.ts index aa630722d00..b15ef177a3b 100644 --- a/src/cli/handlers/plugin-dev-watch.test.ts +++ b/src/cli/plugins/dev-watch/plugin-dev-watch.test.ts @@ -2,12 +2,13 @@ import { mkdtemp, readFile, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { describe, expect, it, vi } from 'vitest' -import { COMMAND_SPECS } from '../specs/index' -import { findCommandSpec, normalizeCommandPositionals, parseArgs, specPaths } from '../args' -import type { HandlerContext } from '../dispatch' -import { reportCliError } from '../cli-error' -import type { RuntimeClient } from '../runtime-client' -import { RuntimeClientError, RuntimeRpcFailureError } from '../runtime/types' +import { COMMAND_SPECS } from '../../specs/index' +import { findCommandSpec, normalizeCommandPositionals, parseArgs } from '../../args' +import { specPaths } from '../../command-spec' +import type { HandlerContext } from '../../dispatch' +import { reportCliError } from '../../cli-error' +import type { RuntimeClient } from '../../runtime/client' +import { RuntimeClientError, RuntimeRpcFailureError } from '../../runtime/types' import { bindPluginDevWatchHost, PluginHostUnreachableError } from './plugin-dev-watch' function context(call: ReturnType, rawArgs: string[] = []): HandlerContext { diff --git a/src/cli/handlers/plugin-dev-watch.ts b/src/cli/plugins/dev-watch/plugin-dev-watch.ts similarity index 83% rename from src/cli/handlers/plugin-dev-watch.ts rename to src/cli/plugins/dev-watch/plugin-dev-watch.ts index 59c84af725e..94497fde6d1 100644 --- a/src/cli/handlers/plugin-dev-watch.ts +++ b/src/cli/plugins/dev-watch/plugin-dev-watch.ts @@ -1,28 +1,20 @@ -import type { CommandHandler, HandlerContext } from '../dispatch' -import { requirePluginPathArg } from '../plugin-authoring/plugin-cli-output' +import type { CommandHandler, HandlerContext } from '../../dispatch' +import { requirePluginPathArg } from '../plugin-cli-output' +import { formatPluginDevWatchEvent, type PluginDevWatchApproval } from './plugin-dev-watch-events' +import { runPluginDevWatch, type PluginDevWatchHost } from './plugin-dev-watch-loop' +import { onPluginDevWatchInterrupt, waitUnlessAborted } from './plugin-dev-watch-process' +import { RuntimeClientError, RuntimeRpcFailureError } from '../../runtime/types' +import { writeStdoutLine } from '../../stdout-line' +import type { PluginHostListEntry } from '../../../shared/plugins/plugin-list-entry' +import { CLI_BOOLEAN_FLAGS } from '../../../shared/cli-argument-boundary' +import { checkPluginDevFolder } from './plugin-dev-watch-check' +import { pluginDevWatchConfirmFor } from './plugin-dev-watch-approval' +import { pluginDevWatchShotPanel, type PluginDevWatchShotHost } from './plugin-dev-watch-shot' import { - formatPluginDevWatchEvent, - type PluginDevWatchApproval -} from '../plugin-dev-watch/plugin-dev-watch-events' -import { - runPluginDevWatch, - type PluginDevWatchHost -} from '../plugin-dev-watch/plugin-dev-watch-loop' -import { - onPluginDevWatchInterrupt, - waitUnlessAborted -} from '../plugin-dev-watch/plugin-dev-watch-process' -import { RuntimeClientError, RuntimeRpcFailureError } from '../runtime/types' -import { writeStdoutLine } from '../stdout-line' -import type { PluginHostListEntry } from '../../shared/plugins/plugin-list-entry' -import { CLI_BOOLEAN_FLAGS } from '../../shared/cli-argument-boundary' -import { checkPluginDevFolder } from '../plugin-dev-watch/plugin-dev-watch-check' -import { pluginDevWatchConfirmFor } from '../plugin-dev-watch/plugin-dev-watch-approval' -import { - pluginDevWatchShotPanel, - type PluginDevWatchShotHost -} from '../plugin-dev-watch/plugin-dev-watch-shot' -import { defaultPluginShotPath, resolveLocalShotPath, takePluginShotIfShown } from './plugin-view' + defaultPluginShotPath, + resolveLocalShotPath, + takePluginShotIfShown +} from '../view/plugin-view' import { callPlugins, refreshDevTypes, @@ -30,7 +22,7 @@ import { translatePluginRpcError, type DevLoadResult, type LogsResult -} from './plugin-runtime' +} from '../plugin-runtime' /** The host did not answer: Orca quit, or the network to a remote server dropped. */ export class PluginHostUnreachableError extends Error {} diff --git a/src/cli/plugin-dev-watch/plugin-frame-log-dedupe.test.ts b/src/cli/plugins/dev-watch/plugin-frame-log-dedupe.test.ts similarity index 84% rename from src/cli/plugin-dev-watch/plugin-frame-log-dedupe.test.ts rename to src/cli/plugins/dev-watch/plugin-frame-log-dedupe.test.ts index 9811c47bedc..9647ea4e819 100644 --- a/src/cli/plugin-dev-watch/plugin-frame-log-dedupe.test.ts +++ b/src/cli/plugins/dev-watch/plugin-frame-log-dedupe.test.ts @@ -19,9 +19,4 @@ describe('PluginFrameLogDedupe', () => { expect(dedupe.take([line(7, KEY_WARNING)])).toEqual([line(7, KEY_WARNING)]) expect(dedupe.flush()).toEqual([]) }) - - it('keeps the same text at another level apart', () => { - const dedupe = new PluginFrameLogDedupe() - expect(dedupe.take([line(1, '[frame] boom'), line(2, '[frame] boom', 'error')])).toHaveLength(2) - }) }) diff --git a/src/cli/plugin-dev-watch/plugin-frame-log-dedupe.ts b/src/cli/plugins/dev-watch/plugin-frame-log-dedupe.ts similarity index 100% rename from src/cli/plugin-dev-watch/plugin-frame-log-dedupe.ts rename to src/cli/plugins/dev-watch/plugin-frame-log-dedupe.ts diff --git a/src/cli/plugin-dev-watch/plugin-log-follower.ts b/src/cli/plugins/dev-watch/plugin-log-follower.ts similarity index 95% rename from src/cli/plugin-dev-watch/plugin-log-follower.ts rename to src/cli/plugins/dev-watch/plugin-log-follower.ts index f5f6860137e..30a1dc9c346 100644 --- a/src/cli/plugin-dev-watch/plugin-log-follower.ts +++ b/src/cli/plugins/dev-watch/plugin-log-follower.ts @@ -1,4 +1,4 @@ -import type { LogsResult } from '../handlers/plugin-runtime' +import type { LogsResult } from '../plugin-runtime' export type PluginLogLine = LogsResult['lines'][number] diff --git a/src/cli/handlers/plugin-approval-rerun.ts b/src/cli/plugins/plugin-approval-rerun.ts similarity index 100% rename from src/cli/handlers/plugin-approval-rerun.ts rename to src/cli/plugins/plugin-approval-rerun.ts diff --git a/src/cli/plugin-authoring/plugin-cli-output.ts b/src/cli/plugins/plugin-cli-output.ts similarity index 100% rename from src/cli/plugin-authoring/plugin-cli-output.ts rename to src/cli/plugins/plugin-cli-output.ts diff --git a/src/cli/handlers/plugin-dev-emit.ts b/src/cli/plugins/plugin-dev-emit.ts similarity index 95% rename from src/cli/handlers/plugin-dev-emit.ts rename to src/cli/plugins/plugin-dev-emit.ts index ce25e338888..f120b6123ee 100644 --- a/src/cli/handlers/plugin-dev-emit.ts +++ b/src/cli/plugins/plugin-dev-emit.ts @@ -1,5 +1,5 @@ import type { CommandHandler, HandlerContext } from '../dispatch' -import { printPluginResult, requirePluginPathArg } from '../plugin-authoring/plugin-cli-output' +import { printPluginResult, requirePluginPathArg } from './plugin-cli-output' import { RuntimeClientError } from '../runtime/types' import { callPlugins, devTargetParams, stringFlag } from './plugin-runtime' diff --git a/src/cli/handlers/plugin-dev-request.ts b/src/cli/plugins/plugin-dev-request.ts similarity index 92% rename from src/cli/handlers/plugin-dev-request.ts rename to src/cli/plugins/plugin-dev-request.ts index 64391e02a08..3bae2c6c616 100644 --- a/src/cli/handlers/plugin-dev-request.ts +++ b/src/cli/plugins/plugin-dev-request.ts @@ -1,6 +1,6 @@ import type { CommandHandler } from '../dispatch' import { getOptionalPositiveIntegerFlag } from '../flags' -import { printPluginResult, requirePluginPathArg } from '../plugin-authoring/plugin-cli-output' +import { printPluginResult, requirePluginPathArg } from './plugin-cli-output' import { parseDataFlag } from './plugin-dev-emit' import { callPlugins, devTargetParams, stringFlag } from './plugin-runtime' diff --git a/src/cli/plugin-authoring/plugin-grant-lines.ts b/src/cli/plugins/plugin-grant-lines.ts similarity index 100% rename from src/cli/plugin-authoring/plugin-grant-lines.ts rename to src/cli/plugins/plugin-grant-lines.ts diff --git a/src/cli/plugin-handler-groups.ts b/src/cli/plugins/plugin-handler-groups.ts similarity index 63% rename from src/cli/plugin-handler-groups.ts rename to src/cli/plugins/plugin-handler-groups.ts index 24ae0b21cc6..40ea805311b 100644 --- a/src/cli/plugin-handler-groups.ts +++ b/src/cli/plugins/plugin-handler-groups.ts @@ -1,4 +1,4 @@ -import type { HandlerGroup } from './handler-group-manifest' +import type { HandlerGroup } from '../handler-group-manifest' // Authoring runs offline; runtime commands reach the Orca host. Split so authoring never loads the RPC graph. export const PLUGIN_HANDLER_GROUPS: readonly HandlerGroup[] = [ @@ -13,7 +13,7 @@ export const PLUGIN_HANDLER_GROUPS: readonly HandlerGroup[] = [ 'plugin test', 'plugin check' ], - load: async () => (await import('./handlers/plugin-authoring.js')).PLUGIN_AUTHORING_HANDLERS + load: async () => (await import('./authoring/plugin-authoring.js')).PLUGIN_AUTHORING_HANDLERS }, { name: 'plugin-runtime', @@ -30,27 +30,27 @@ export const PLUGIN_HANDLER_GROUPS: readonly HandlerGroup[] = [ 'plugin dev inspect', 'plugin dev unload' ], - load: async () => (await import('./handlers/plugin-runtime.js')).PLUGIN_RUNTIME_HANDLERS + load: async () => (await import('./plugin-runtime.js')).PLUGIN_RUNTIME_HANDLERS }, { name: 'plugin-dev-watch', keys: ['plugin dev watch'], - load: async () => (await import('./handlers/plugin-dev-watch.js')).PLUGIN_DEV_WATCH_HANDLERS + load: async () => (await import('./dev-watch/plugin-dev-watch.js')).PLUGIN_DEV_WATCH_HANDLERS }, { name: 'plugin-dev-emit', keys: ['plugin dev emit', 'plugin dev hook'], - load: async () => (await import('./handlers/plugin-dev-emit.js')).PLUGIN_DEV_EMIT_HANDLERS + load: async () => (await import('./plugin-dev-emit.js')).PLUGIN_DEV_EMIT_HANDLERS }, { name: 'plugin-dev-request', keys: ['plugin dev request'], - load: async () => (await import('./handlers/plugin-dev-request.js')).PLUGIN_DEV_REQUEST_HANDLERS + load: async () => (await import('./plugin-dev-request.js')).PLUGIN_DEV_REQUEST_HANDLERS }, { name: 'plugin-view', keys: ['plugin open', 'plugin shot'], - load: async () => (await import('./handlers/plugin-view.js')).PLUGIN_VIEW_HANDLERS + load: async () => (await import('./view/plugin-view.js')).PLUGIN_VIEW_HANDLERS }, { name: 'plugin-install', @@ -62,6 +62,6 @@ export const PLUGIN_HANDLER_GROUPS: readonly HandlerGroup[] = [ 'plugin catalog add', 'plugin catalog rm' ], - load: async () => (await import('./handlers/plugin-install.js')).PLUGIN_INSTALL_HANDLERS + load: async () => (await import('./plugin-install.js')).PLUGIN_INSTALL_HANDLERS } ] diff --git a/src/cli/handlers/plugin-install.ts b/src/cli/plugins/plugin-install.ts similarity index 96% rename from src/cli/handlers/plugin-install.ts rename to src/cli/plugins/plugin-install.ts index 1720dcff3a8..026ccab9f74 100644 --- a/src/cli/handlers/plugin-install.ts +++ b/src/cli/plugins/plugin-install.ts @@ -1,12 +1,10 @@ import { existsSync } from 'node:fs' import { isAbsolute, join, resolve } from 'node:path' import type { CommandHandler, HandlerContext } from '../dispatch' -import { printPluginResult, requirePluginPathArg } from '../plugin-authoring/plugin-cli-output' +import { printPluginResult, requirePluginPathArg } from './plugin-cli-output' import { RuntimeClientError } from '../runtime/types' -import { - isQualifiedPluginKey, - PLUGIN_MANIFEST_FILENAME -} from '../../shared/plugins/manifest/plugin-manifest' +import { isQualifiedPluginKey } from '../../shared/plugins/frames/plugin-tab-key' +import { PLUGIN_MANIFEST_FILENAME } from '../../shared/plugins/manifest/plugin-manifest' import type { PluginHostListEntry } from '../../shared/plugins/plugin-list-entry' import { pluginInstallSourceLines, diff --git a/src/cli/handlers/plugin-runtime.ts b/src/cli/plugins/plugin-runtime.ts similarity index 97% rename from src/cli/handlers/plugin-runtime.ts rename to src/cli/plugins/plugin-runtime.ts index a65f9f2f085..448f15b29e9 100644 --- a/src/cli/handlers/plugin-runtime.ts +++ b/src/cli/plugins/plugin-runtime.ts @@ -1,7 +1,7 @@ import { existsSync } from 'node:fs' import { isAbsolute, resolve } from 'node:path' import type { CommandHandler, HandlerContext } from '../dispatch' -import { printPluginResult, requirePluginPathArg } from '../plugin-authoring/plugin-cli-output' +import { printPluginResult, requirePluginPathArg } from './plugin-cli-output' import { RuntimeClientError, RuntimeRpcFailureError } from '../runtime/types' import { approvalFingerprintOf, @@ -10,7 +10,7 @@ import { pendingApprovalsOf, perPluginApprovalCommands } from './plugin-approval-rerun' -import { isQualifiedPluginKey } from '../../shared/plugins/manifest/plugin-manifest' +import { isQualifiedPluginKey } from '../../shared/plugins/frames/plugin-tab-key' import type { PluginHostListEntry } from '../../shared/plugins/plugin-list-entry' export type DevLoadResult = { @@ -156,7 +156,7 @@ export async function refreshDevTypes(ctx: HandlerContext, path: string): Promis return [] } // Lazy: the manifest schema it parses with is heavy for every other runtime command. - const types = await import('../plugin-authoring/plugin-type-files.js') + const types = await import('./authoring/plugin-type-files.js') if (!(await types.pluginFolderWantsTypes(path))) { return [] } diff --git a/src/cli/handlers/plugin-shot-chrome.test.ts b/src/cli/plugins/view/plugin-shot-chrome.test.ts similarity index 86% rename from src/cli/handlers/plugin-shot-chrome.test.ts rename to src/cli/plugins/view/plugin-shot-chrome.test.ts index 8650c5f9a57..1bfe370505c 100644 --- a/src/cli/handlers/plugin-shot-chrome.test.ts +++ b/src/cli/plugins/view/plugin-shot-chrome.test.ts @@ -2,10 +2,11 @@ import { mkdtemp, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { COMMAND_SPECS } from '../specs' -import { normalizeCommandPositionals, parseArgs, specPaths } from '../args' -import type { HandlerContext } from '../dispatch' -import type { RuntimeClient } from '../runtime-client' +import { COMMAND_SPECS } from '../../specs/index' +import { normalizeCommandPositionals, parseArgs } from '../../args' +import { specPaths } from '../../command-spec' +import type { HandlerContext } from '../../dispatch' +import type { RuntimeClient } from '../../runtime/client' import { PLUGIN_VIEW_HANDLERS } from './plugin-view' function context(argv: string[], call: ReturnType, cwd: string): HandlerContext { diff --git a/src/cli/handlers/plugin-shot-chrome.ts b/src/cli/plugins/view/plugin-shot-chrome.ts similarity index 93% rename from src/cli/handlers/plugin-shot-chrome.ts rename to src/cli/plugins/view/plugin-shot-chrome.ts index a73eb4af8d0..eec6d69df99 100644 --- a/src/cli/handlers/plugin-shot-chrome.ts +++ b/src/cli/plugins/view/plugin-shot-chrome.ts @@ -1,9 +1,9 @@ import { mkdir, writeFile } from 'node:fs/promises' import { dirname, join } from 'node:path' -import type { HandlerContext } from '../dispatch' -import { printPluginResult } from '../plugin-authoring/plugin-cli-output' -import { RuntimeClientError } from '../runtime/types' -import { callPlugins } from './plugin-runtime' +import type { HandlerContext } from '../../dispatch' +import { printPluginResult } from '../plugin-cli-output' +import { RuntimeClientError } from '../../runtime/types' +import { callPlugins } from '../plugin-runtime' import { editorShotPluginKey } from './plugin-shot-editor' type ChromeRegion = 'status-bar' | 'window' diff --git a/src/cli/handlers/plugin-shot-default-dir.test.ts b/src/cli/plugins/view/plugin-shot-default-dir.test.ts similarity index 100% rename from src/cli/handlers/plugin-shot-default-dir.test.ts rename to src/cli/plugins/view/plugin-shot-default-dir.test.ts diff --git a/src/cli/handlers/plugin-shot-default-dir.ts b/src/cli/plugins/view/plugin-shot-default-dir.ts similarity index 100% rename from src/cli/handlers/plugin-shot-default-dir.ts rename to src/cli/plugins/view/plugin-shot-default-dir.ts diff --git a/src/cli/handlers/plugin-shot-editor.test.ts b/src/cli/plugins/view/plugin-shot-editor.test.ts similarity index 94% rename from src/cli/handlers/plugin-shot-editor.test.ts rename to src/cli/plugins/view/plugin-shot-editor.test.ts index 0eeb65a5f45..e61e0d7a8b5 100644 --- a/src/cli/handlers/plugin-shot-editor.test.ts +++ b/src/cli/plugins/view/plugin-shot-editor.test.ts @@ -2,10 +2,11 @@ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { COMMAND_SPECS } from '../specs' -import { normalizeCommandPositionals, parseArgs, specPaths } from '../args' -import type { HandlerContext } from '../dispatch' -import type { RuntimeClient } from '../runtime-client' +import { COMMAND_SPECS } from '../../specs/index' +import { normalizeCommandPositionals, parseArgs } from '../../args' +import { specPaths } from '../../command-spec' +import type { HandlerContext } from '../../dispatch' +import type { RuntimeClient } from '../../runtime/client' import { PLUGIN_VIEW_HANDLERS } from './plugin-view' // A 1x1 PNG. diff --git a/src/cli/handlers/plugin-shot-editor.ts b/src/cli/plugins/view/plugin-shot-editor.ts similarity index 92% rename from src/cli/handlers/plugin-shot-editor.ts rename to src/cli/plugins/view/plugin-shot-editor.ts index 9f12a76d4d3..14494126a72 100644 --- a/src/cli/handlers/plugin-shot-editor.ts +++ b/src/cli/plugins/view/plugin-shot-editor.ts @@ -1,22 +1,22 @@ import { existsSync } from 'node:fs' import { mkdir, writeFile } from 'node:fs/promises' import { dirname, isAbsolute, join, resolve } from 'node:path' -import type { HandlerContext } from '../dispatch' -import { getOptionalPositiveIntegerFlag } from '../flags' -import { printPluginResult } from '../plugin-authoring/plugin-cli-output' -import { RuntimeClientError } from '../runtime/types' -import { isQualifiedPluginKey } from '../../shared/plugins/manifest/plugin-manifest' +import type { HandlerContext } from '../../dispatch' +import { getOptionalPositiveIntegerFlag } from '../../flags' +import { printPluginResult } from '../plugin-cli-output' +import { RuntimeClientError } from '../../runtime/types' +import { isQualifiedPluginKey } from '../../../shared/plugins/frames/plugin-tab-key' import { parsePluginEditorShotActs, type PluginEditorShotAct -} from '../../shared/plugins/view/plugin-editor-shot-acts' +} from '../../../shared/plugins/view/plugin-editor-shot-acts' import { PLUGIN_SHOT_MAX_HEIGHT, PLUGIN_SHOT_MAX_WIDTH, PLUGIN_SHOT_MIN_HEIGHT, PLUGIN_SHOT_MIN_WIDTH -} from '../../shared/rpc-contract/plugins-params' -import { callPlugins, resolveHostPluginPath, stringFlag } from './plugin-runtime' +} from '../../../shared/rpc-contract/plugins-params' +import { callPlugins, resolveHostPluginPath, stringFlag } from '../plugin-runtime' import { readPluginFolderKey } from './plugin-shot-scenario' /** What `plugins.shotEditor` answers; `png` is base64. */ diff --git a/src/cli/handlers/plugin-shot-scenario.test.ts b/src/cli/plugins/view/plugin-shot-scenario.test.ts similarity index 93% rename from src/cli/handlers/plugin-shot-scenario.test.ts rename to src/cli/plugins/view/plugin-shot-scenario.test.ts index e9dad26fe28..32121e75882 100644 --- a/src/cli/handlers/plugin-shot-scenario.test.ts +++ b/src/cli/plugins/view/plugin-shot-scenario.test.ts @@ -2,12 +2,13 @@ import { mkdtemp, readFile, realpath, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { COMMAND_SPECS } from '../specs/index' -import { normalizeCommandPositionals, parseArgs, specPaths } from '../args' -import type { HandlerContext } from '../dispatch' -import type { RuntimeClient } from '../runtime-client' -import { RuntimeRpcFailureError } from '../runtime/types' -import { createPluginFromTemplate } from '../plugin-authoring/plugin-create' +import { COMMAND_SPECS } from '../../specs/index' +import { normalizeCommandPositionals, parseArgs } from '../../args' +import { specPaths } from '../../command-spec' +import type { HandlerContext } from '../../dispatch' +import type { RuntimeClient } from '../../runtime/client' +import { RuntimeRpcFailureError } from '../../runtime/types' +import { createPluginFromTemplate } from '../authoring/plugin-create' import { PLUGIN_VIEW_HANDLERS, type PluginShotReply } from './plugin-view' const check = vi.hoisted(() => ({ @@ -15,7 +16,7 @@ const check = vi.hoisted(() => ({ checkPluginPanels: vi.fn(), formatPluginPanelCheck: vi.fn(() => [' src/panel.tsx: boom']) })) -vi.mock('../plugin-authoring/plugin-panel-check', () => check) +vi.mock('../authoring/plugin-panel-check', () => check) const PNG_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==' diff --git a/src/cli/handlers/plugin-shot-scenario.ts b/src/cli/plugins/view/plugin-shot-scenario.ts similarity index 94% rename from src/cli/handlers/plugin-shot-scenario.ts rename to src/cli/plugins/view/plugin-shot-scenario.ts index 5af3507e09e..ac95cba7fe6 100644 --- a/src/cli/handlers/plugin-shot-scenario.ts +++ b/src/cli/plugins/view/plugin-shot-scenario.ts @@ -1,20 +1,20 @@ import { existsSync } from 'node:fs' import { mkdir, readFile, writeFile } from 'node:fs/promises' import { dirname, isAbsolute, join, resolve } from 'node:path' -import type { HandlerContext } from '../dispatch' -import { RuntimeClientError } from '../runtime/types' -import type { PluginHostListEntry } from '../../shared/plugins/plugin-list-entry' +import type { HandlerContext } from '../../dispatch' +import { RuntimeClientError } from '../../runtime/types' +import type { PluginHostListEntry } from '../../../shared/plugins/plugin-list-entry' +import { isQualifiedPluginKey } from '../../../shared/plugins/frames/plugin-tab-key' import { - isQualifiedPluginKey, parsePluginManifest, qualifiedPluginKey, type PluginManifest -} from '../../shared/plugins/manifest/plugin-manifest' +} from '../../../shared/plugins/manifest/plugin-manifest' import { localPluginPublisher, withPluginPublisher -} from '../../shared/plugins/installation/plugin-publisher-derivation' -import { callPlugins } from './plugin-runtime' +} from '../../../shared/plugins/installation/plugin-publisher-derivation' +import { callPlugins } from '../plugin-runtime' import { enclosingPluginFolder } from './plugin-shot-default-dir' import type { PluginShotReply, PluginShotSaved } from './plugin-view' @@ -162,7 +162,7 @@ export async function takePluginScenarioShot( const { dir, manifest, pluginKey } = await resolveFolder(ctx, request.target) const panel = scenarioPanel(manifest, pluginKey, request.panelId) // Lazy: the check pulls in the declarative tree validator and the Node selection. - const check = await import('../plugin-authoring/plugin-panel-check.js') + const check = await import('../authoring/plugin-panel-check.js') const names = await check.readPluginCheckScenarioNames(dir, panel.entry).catch((error) => { throw refuse('invalid_argument', error instanceof Error ? error.message : String(error)) }) diff --git a/src/cli/handlers/plugin-view.test.ts b/src/cli/plugins/view/plugin-view.test.ts similarity index 94% rename from src/cli/handlers/plugin-view.test.ts rename to src/cli/plugins/view/plugin-view.test.ts index bfaceadce2d..d235abefcf8 100644 --- a/src/cli/handlers/plugin-view.test.ts +++ b/src/cli/plugins/view/plugin-view.test.ts @@ -2,11 +2,12 @@ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { COMMAND_SPECS } from '../specs/index' -import { normalizeCommandPositionals, parseArgs, specPaths } from '../args' -import type { HandlerContext } from '../dispatch' -import type { RuntimeClient } from '../runtime-client' -import type { PluginShotActResult } from '../../shared/plugins/view/plugin-shot-acts' +import { COMMAND_SPECS } from '../../specs/index' +import { normalizeCommandPositionals, parseArgs } from '../../args' +import { specPaths } from '../../command-spec' +import type { HandlerContext } from '../../dispatch' +import type { RuntimeClient } from '../../runtime/client' +import type { PluginShotActResult } from '../../../shared/plugins/view/plugin-shot-acts' import { PLUGIN_VIEW_HANDLERS, type PluginShotReply } from './plugin-view' // A 1x1 PNG. diff --git a/src/cli/handlers/plugin-view.ts b/src/cli/plugins/view/plugin-view.ts similarity index 96% rename from src/cli/handlers/plugin-view.ts rename to src/cli/plugins/view/plugin-view.ts index 1fdb5121811..693cbd5483b 100644 --- a/src/cli/handlers/plugin-view.ts +++ b/src/cli/plugins/view/plugin-view.ts @@ -1,19 +1,19 @@ import { mkdir, writeFile } from 'node:fs/promises' import { dirname, isAbsolute, join, resolve } from 'node:path' -import type { CommandHandler, HandlerContext } from '../dispatch' -import { getOptionalPositiveIntegerFlag } from '../flags' -import { printPluginResult } from '../plugin-authoring/plugin-cli-output' -import { RuntimeClientError } from '../runtime/types' +import type { CommandHandler, HandlerContext } from '../../dispatch' +import { getOptionalPositiveIntegerFlag } from '../../flags' +import { printPluginResult } from '../plugin-cli-output' +import { RuntimeClientError } from '../../runtime/types' import { PLUGIN_SHOT_MAX_WIDTH, PLUGIN_SHOT_MIN_WIDTH -} from '../../shared/rpc-contract/plugins-params' +} from '../../../shared/rpc-contract/plugins-params' import { parsePluginShotActs, type PluginShotAct, type PluginShotActResult -} from '../../shared/plugins/view/plugin-shot-acts' -import { callPlugins, requirePluginKey, resolveHostPluginPath, stringFlag } from './plugin-runtime' +} from '../../../shared/plugins/view/plugin-shot-acts' +import { callPlugins, requirePluginKey, resolveHostPluginPath, stringFlag } from '../plugin-runtime' import { defaultPluginShotsDir } from './plugin-shot-default-dir' import { takePluginScenarioShot } from './plugin-shot-scenario' import { runPluginEditorShot } from './plugin-shot-editor' diff --git a/src/cli/server/serve-reset-identity.ts b/src/cli/server/serve-reset-identity.ts index 310b27eac9e..15d17f5d8bc 100644 --- a/src/cli/server/serve-reset-identity.ts +++ b/src/cli/server/serve-reset-identity.ts @@ -1,7 +1,7 @@ import type { CommandHandler } from '../dispatch' import { RuntimeClientError } from '../runtime/types' import { resolveLaunchUserDataPath } from '../runtime/launch-user-data-path' -import { pluginDevWatchConfirmFor } from '../plugin-dev-watch/plugin-dev-watch-approval' +import { pluginDevWatchConfirmFor } from '../plugins/dev-watch/plugin-dev-watch-approval' const RESET_WARNING = "This gives this machine's Orca server a new pairing key and installation id and removes every " + diff --git a/src/cli/handlers/bundled-skill-guide-table.ts b/src/cli/skills/bundled-skill-guide-table.ts similarity index 92% rename from src/cli/handlers/bundled-skill-guide-table.ts rename to src/cli/skills/bundled-skill-guide-table.ts index f8924246cf5..4d9bff4c11b 100644 --- a/src/cli/handlers/bundled-skill-guide-table.ts +++ b/src/cli/skills/bundled-skill-guide-table.ts @@ -1,4 +1,4 @@ -import { RuntimeClientError } from '../runtime-client' +import { RuntimeClientError } from '../runtime/types' export type BundledSkillGuideReference = { name: string @@ -21,7 +21,7 @@ function canonicalGuides(guides: readonly BundledSkillGuide[]): BundledSkillGuid /** Defer the large guide table so unrelated commands avoid its startup cost. */ export async function loadCanonicalGuides(): Promise { - const { BUNDLED_SKILL_GUIDES } = await import('../bundled-skill-guides.js') + const { BUNDLED_SKILL_GUIDES } = await import('./bundled-skill-guides.js') return canonicalGuides(BUNDLED_SKILL_GUIDES) } diff --git a/src/cli/bundled-skill-guides.ts b/src/cli/skills/bundled-skill-guides.ts similarity index 99% rename from src/cli/bundled-skill-guides.ts rename to src/cli/skills/bundled-skill-guides.ts index cbba38aa6cc..9f467eefb02 100644 --- a/src/cli/bundled-skill-guides.ts +++ b/src/cli/skills/bundled-skill-guides.ts @@ -1,4 +1,4 @@ -// Generated by config/scripts/generate-bundled-skill-guides.mjs. Do not edit. +// Generated by config/scripts/skills/bundle-generation/generate-bundled-skill-guides.mjs. Do not edit. export type BundledSkillGuideReference = { readonly name: string diff --git a/src/cli/handlers/bundled-skill-install-command.ts b/src/cli/skills/bundled-skill-install-command.ts similarity index 98% rename from src/cli/handlers/bundled-skill-install-command.ts rename to src/cli/skills/bundled-skill-install-command.ts index c4db81b3d10..0692c948401 100644 --- a/src/cli/handlers/bundled-skill-install-command.ts +++ b/src/cli/skills/bundled-skill-install-command.ts @@ -1,11 +1,11 @@ import { homedir } from 'node:os' import { realpath } from 'node:fs/promises' import type { CommandHandler } from '../dispatch' -import { RuntimeClientError } from '../runtime-client' +import { RuntimeClientError } from '../runtime/types' import { getDefaultUserDataPath } from '../runtime/metadata' import { getRepeatedStringFlag } from '../flags' import { writeStdoutLine } from '../stdout-line' -import { skillsCliAgentAliases } from '../skills-cli-agent-aliases' +import { skillsCliAgentAliases } from './skills-cli-agent-aliases' import { loadCanonicalGuides, type BundledSkillGuide } from './bundled-skill-guide-table' import type { BundledSkillOp, diff --git a/src/cli/handlers/skill-guide-get.ts b/src/cli/skills/skill-guide-get.ts similarity index 99% rename from src/cli/handlers/skill-guide-get.ts rename to src/cli/skills/skill-guide-get.ts index fff9c684d65..98c4b3e79ce 100644 --- a/src/cli/handlers/skill-guide-get.ts +++ b/src/cli/skills/skill-guide-get.ts @@ -1,5 +1,5 @@ import type { CommandHandler, HandlerContext } from '../dispatch' -import { RuntimeClientError } from '../runtime-client' +import { RuntimeClientError } from '../runtime/types' import { writeStdoutLine } from '../stdout-line' import { loadCanonicalGuides, diff --git a/src/cli/handlers/skill-guide-host-gate.test.ts b/src/cli/skills/skill-guide-host-gate.test.ts similarity index 81% rename from src/cli/handlers/skill-guide-host-gate.test.ts rename to src/cli/skills/skill-guide-host-gate.test.ts index f4b222f0d25..ae9ab6fff5a 100644 --- a/src/cli/handlers/skill-guide-host-gate.test.ts +++ b/src/cli/skills/skill-guide-host-gate.test.ts @@ -41,9 +41,7 @@ vi.mock('../runtime-client', async () => { } }) -import { BUNDLED_SKILL_GUIDES } from '../bundled-skill-guides' import { main } from '../index' -import { GUIDE_HOST_CAPABILITIES } from './skill-guide-host-gate' const PUBLISHING_TEXT = /artifacts share|skills share|## Artifacts|## Skill Sharing/ @@ -75,29 +73,6 @@ describe('skills get host-capability gating', () => { process.exitCode = undefined }) - it('only ships closed, unnested blocks that name a known capability', () => { - const documents = BUNDLED_SKILL_GUIDES.flatMap((guide) => [ - guide.markdown, - ...guide.references.map((reference) => reference.markdown) - ]) - for (const markdown of documents) { - let open: string | null = null - for (const [, closing, name] of markdown.matchAll( - //g - )) { - if (closing) { - expect(open).not.toBeNull() - open = null - continue - } - expect(open).toBeNull() - expect(GUIDE_HOST_CAPABILITIES).toContain(name) - open = name - } - expect(open).toBeNull() - } - }) - it.each<[string, Host]>([ ['publishing is off', { settings: { artifactSharingEnabled: false } }], ['the host omits the settings (headless or older)', { settings: {} }], @@ -160,11 +135,4 @@ describe('skills get host-capability gating', () => { expect(guide.stdout).toContain('## Artifacts') expect(guide.stdout).not.toContain('## Skill Sharing') }) - - it('serves an ungated reference without contacting the host', async () => { - const { stdout, stderr } = await skillsGet(['--reference', 'browser'], 'no-access') - - expect(stderr).toBe('') - expect(stdout.length).toBeGreaterThan(0) - }) }) diff --git a/src/cli/handlers/skill-guide-host-gate.ts b/src/cli/skills/skill-guide-host-gate.ts similarity index 100% rename from src/cli/handlers/skill-guide-host-gate.ts rename to src/cli/skills/skill-guide-host-gate.ts diff --git a/src/cli/handlers/skill-sharing.test.ts b/src/cli/skills/skill-sharing.test.ts similarity index 56% rename from src/cli/handlers/skill-sharing.test.ts rename to src/cli/skills/skill-sharing.test.ts index 08e31d496ce..47118221f00 100644 --- a/src/cli/handlers/skill-sharing.test.ts +++ b/src/cli/skills/skill-sharing.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it, vi } from 'vitest' -import { REPEATED_FLAG_SEPARATOR } from '../args' -import { RuntimeClient, RuntimeRpcFailureError } from '../runtime-client' +import { RuntimeClient } from '../runtime/client' +import { RuntimeRpcFailureError } from '../runtime/types' import type { HandlerContext } from '../dispatch' import { SKILL_SHARING_HANDLERS } from './skill-sharing' @@ -85,123 +85,6 @@ describe('skill sharing CLI handlers', () => { expect(call).toHaveBeenCalledExactlyOnceWith('settings.get') }) - it('publishes multiple explicit skills and prints only public output', async () => { - const call = vi - .fn() - .mockResolvedValueOnce({ - id: 'settings', - ok: true, - result: { settings: { agentSkillSharingEnabled: true } }, - _meta: successMeta - }) - .mockResolvedValueOnce({ - id: 'share', - ok: true, - result: { - status: 'ok', - value: { - share: { id: 'shr_public', url: 'https://share.onorca.dev/skills/share/shr_public' }, - version: { - packageId: 'pkg_public', - versionId: 'ver_public', - name: 'team-skills' - }, - selectedSkills: [ - { id: 'alpha-id', name: 'alpha', description: null }, - { id: 'beta-id', name: 'beta', description: null } - ] - } - }, - _meta: successMeta - }) - const log = vi.spyOn(console, 'log').mockImplementation(() => undefined) - - await SKILL_SHARING_HANDLERS['skills share']!( - context( - call, - new Map([ - ['skill', `alpha${REPEATED_FLAG_SEPARATOR}beta`], - ['bundle-name', 'team-skills'], - ['release-notes', 'Initial bundle'] - ]), - { json: true } - ) - ) - - expect(call).toHaveBeenLastCalledWith( - 'skills.share', - { - skillSelectors: ['alpha', 'beta'], - bundleName: 'team-skills', - releaseNotes: 'Initial bundle', - target: { cwd: '/repo' } - }, - { timeoutMs: 600_000 } - ) - const output = String(log.mock.calls[0][0]) - expect(JSON.parse(output).result).toEqual({ - url: 'https://share.onorca.dev/skills/share/shr_public', - shareId: 'shr_public', - packageId: 'pkg_public', - versionId: 'ver_public', - bundleName: 'team-skills', - skills: [ - { id: 'alpha-id', name: 'alpha', description: null }, - { id: 'beta-id', name: 'beta', description: null } - ] - }) - }) - - it('normalizes a human-readable bundle name before publishing', async () => { - const call = vi - .fn() - .mockResolvedValueOnce({ - id: 'settings', - ok: true, - result: { settings: { agentSkillSharingEnabled: true } }, - _meta: successMeta - }) - .mockResolvedValueOnce({ - id: 'share', - ok: true, - result: { - status: 'ok', - value: { - share: { id: 'shr_public', url: 'https://share.onorca.dev/skills/share/shr_public' }, - version: { - packageId: 'pkg_public', - versionId: 'ver_public', - name: 'team-skills-v2.0' - }, - selectedSkills: [{ id: 'alpha-id', name: 'alpha', description: null }] - } - }, - _meta: successMeta - }) - vi.spyOn(console, 'log').mockImplementation(() => undefined) - - await SKILL_SHARING_HANDLERS['skills share']!( - context( - call, - new Map([ - ['skill', 'alpha'], - ['bundle-name', 'Téam Skills -- v2.0'] - ]) - ) - ) - - expect(call).toHaveBeenLastCalledWith( - 'skills.share', - { - skillSelectors: ['alpha'], - bundleName: 'team-skills-v2.0', - releaseNotes: '', - target: { cwd: '/repo' } - }, - { timeoutMs: 600_000 } - ) - }) - it('rejects a bundle name that cannot produce a valid package name', async () => { const call = vi.fn() diff --git a/src/cli/handlers/skill-sharing.ts b/src/cli/skills/skill-sharing.ts similarity index 97% rename from src/cli/handlers/skill-sharing.ts rename to src/cli/skills/skill-sharing.ts index c122e4aa4ba..17f2d7b9030 100644 --- a/src/cli/handlers/skill-sharing.ts +++ b/src/cli/skills/skill-sharing.ts @@ -13,11 +13,8 @@ import type { SkillDiscoveryResult } from '../../shared/skills' import type { CommandHandler, HandlerContext } from '../dispatch' import { getRepeatedStringFlag } from '../flags' import { printResult } from '../command-result-output' -import { - RuntimeClientError, - RuntimeRpcFailureError, - type RuntimeRpcSuccess -} from '../runtime-client' +import { RuntimeClientError, RuntimeRpcFailureError } from '../runtime/types' +import type { RuntimeRpcSuccess } from '../../shared/runtime-rpc-envelope' const SHARE_TIMEOUT_MS = 10 * 60_000 diff --git a/src/cli/skills-cli-agent-aliases.test.ts b/src/cli/skills/skills-cli-agent-aliases.test.ts similarity index 91% rename from src/cli/skills-cli-agent-aliases.test.ts rename to src/cli/skills/skills-cli-agent-aliases.test.ts index c9d79a7aafa..feaaf598b67 100644 --- a/src/cli/skills-cli-agent-aliases.test.ts +++ b/src/cli/skills/skills-cli-agent-aliases.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { SKILLS_CLI_AGENT_KEY_BY_TUI_AGENT } from '../shared/skills-cli-agent-keys' +import { SKILLS_CLI_AGENT_KEY_BY_TUI_AGENT } from '../../shared/skills-cli-agent-keys' import { skillsCliAgentAliases } from './skills-cli-agent-aliases' describe('skills CLI agent aliases', () => { diff --git a/src/cli/skills-cli-agent-aliases.ts b/src/cli/skills/skills-cli-agent-aliases.ts similarity index 96% rename from src/cli/skills-cli-agent-aliases.ts rename to src/cli/skills/skills-cli-agent-aliases.ts index 91b26e9560f..22bfc288566 100644 --- a/src/cli/skills-cli-agent-aliases.ts +++ b/src/cli/skills/skills-cli-agent-aliases.ts @@ -1,7 +1,7 @@ import { isSkillInstallProviderId, type SkillInstallProviderId -} from '../shared/skill-install-providers' +} from '../../shared/skill-install-providers' const CANONICAL_PROVIDER_BY_SKILLS_CLI_KEY = new Map([ ['claude-code', 'claude'], diff --git a/src/cli/skills-install.test.ts b/src/cli/skills/skills-install.test.ts similarity index 89% rename from src/cli/skills-install.test.ts rename to src/cli/skills/skills-install.test.ts index 56330fd7d13..c24cfa360fb 100644 --- a/src/cli/skills-install.test.ts +++ b/src/cli/skills/skills-install.test.ts @@ -13,7 +13,7 @@ import { import { tmpdir } from 'node:os' import { isAbsolute, join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { BundledSkillInstallResult, BundledSkillPlan } from '../shared/bundled-skill-plan' +import type { BundledSkillInstallResult, BundledSkillPlan } from '../../shared/bundled-skill-plan' const NAME = 'orca-cli' const OLD_TEXT = `---\nname: ${NAME}\ndescription: Old\n---\n\nOld body\n` @@ -24,31 +24,36 @@ const sha = (text: string) => createHash('sha256').update(text).digest('hex') const { detectAgentsMock } = vi.hoisted(() => ({ detectAgentsMock: vi.fn() })) // Use the real installer in a temporary home; pin bundled bytes and agent detection for portability. -vi.mock('../main/skills/bundled-skill-install/bundled-skill-sources', () => ({ +vi.mock('../../main/skills/bundled-skill-install/bundled-skill-sources', () => ({ BUNDLED_SKILL_SOURCES: [ { name: NAME, text: NEW_TEXT, releasedDigests: [sha(OLD_TEXT), sha(NEW_TEXT)] } ] })) -vi.mock('../main/preflight/installed-agent-detection', () => ({ +vi.mock('../../main/preflight/installed-agent-detection', () => ({ detectInstalledAgentsWithShellPathHydration: detectAgentsMock })) // Avoid the production five-second lock wait in tests. -vi.mock('../main/skills/bundled-skill-install/bundled-skill-local-host', async (importOriginal) => { - const actual = await importOriginal() - return { - localBundledSkillInput: async (...args: Parameters) => ({ - ...(await actual.localBundledSkillInput(...args)), - lockTimeoutMs: 0 - }) +vi.mock( + '../../main/skills/bundled-skill-install/bundled-skill-local-host', + async (importOriginal) => { + const actual = await importOriginal() + return { + localBundledSkillInput: async ( + ...args: Parameters + ) => ({ + ...(await actual.localBundledSkillInput(...args)), + lockTimeoutMs: 0 + }) + } } -}) +) -import type * as LocalHost from '../main/skills/bundled-skill-install/bundled-skill-local-host' -import { main } from './index' +import type * as LocalHost from '../../main/skills/bundled-skill-install/bundled-skill-local-host' +import { main } from '../index' import { acquireSkillInstallLock, skillInstallLockPath -} from '../main/skills/transaction-state/skill-install-lock' +} from '../../main/skills/transaction-state/skill-install-lock' type Paths = { root: string; home: string; userData: string; shared: string; claude: string } diff --git a/src/cli/skills-reference-selector.test.ts b/src/cli/skills/skills-reference-selector.test.ts similarity index 97% rename from src/cli/skills-reference-selector.test.ts rename to src/cli/skills/skills-reference-selector.test.ts index c61cb239e46..f98696917ce 100644 --- a/src/cli/skills-reference-selector.test.ts +++ b/src/cli/skills/skills-reference-selector.test.ts @@ -22,8 +22,8 @@ vi.mock('./bundled-skill-guides.js', () => ({ ] })) -vi.mock('./runtime-client', async () => { - const { RuntimeClientError, RuntimeRpcFailureError } = await import('./runtime/types.js') +vi.mock('../runtime-client', async () => { + const { RuntimeClientError, RuntimeRpcFailureError } = await import('../runtime/types.js') class RuntimeClient { constructor() { throw new Error('skills get constructed a RuntimeClient') @@ -38,7 +38,7 @@ vi.mock('./runtime-client', async () => { } }) -import { main } from './index' +import { main } from '../index' function stdoutText(spy: ReturnType): string { return spy.mock.calls.map((call) => String(call[0])).join('') diff --git a/src/cli/handlers/skills.ts b/src/cli/skills/skills.ts similarity index 100% rename from src/cli/handlers/skills.ts rename to src/cli/skills/skills.ts diff --git a/src/cli/specs/plugin.ts b/src/cli/specs/plugin.ts index 6244da79229..3dd02f75db5 100644 --- a/src/cli/specs/plugin.ts +++ b/src/cli/specs/plugin.ts @@ -1,6 +1,6 @@ import type { CommandSpec } from '../args' import { GLOBAL_FLAGS, LOCAL_COMMAND_FLAGS } from '../args' -import { PLUGIN_TEMPLATE_NAMES } from '../plugin-authoring/plugin-template-names' +import { PLUGIN_TEMPLATE_NAMES } from '../plugins/authoring/plugin-template-names' import { PLUGIN_DEV_HOOK_COMMAND_SPEC, PLUGIN_DEV_REQUEST_COMMAND_SPEC } from './plugin-dev-request' const TEMPLATES = PLUGIN_TEMPLATE_NAMES.join('|') diff --git a/src/main/plugins/plugin-authoring-checks.ts b/src/main/plugins/plugin-authoring-checks.ts deleted file mode 100644 index fbdcaeb6d28..00000000000 --- a/src/main/plugins/plugin-authoring-checks.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** The main-process plugin checks `orca plugin validate|inspect|release-entry` runs. One CLI entry so the - * packaged CLI loads them after electron-vite replaces out/main; keep it free of Electron. */ -export { - validateDeclaredPluginArtifacts, - validatePluginInstallContent -} from './plugin-artifact-validation' -export { hashPluginTree } from './plugin-content-hash' -export { - pluginCatalogReleaseForHead, - type PluginCatalogReleaseReport -} from './plugin-catalog-release-entry' diff --git a/tests/e2e/plugin-shot.spec.ts b/tests/e2e/plugin-shot.spec.ts index ffda4eee1d4..b1d73af78a0 100644 --- a/tests/e2e/plugin-shot.spec.ts +++ b/tests/e2e/plugin-shot.spec.ts @@ -14,7 +14,7 @@ import { expect, test } from './helpers/orca-app' import { orcaCliResult, runCompiledOrcaCli } from './helpers/compiled-orca-cli' import { RuntimeClient } from '../../src/cli/runtime/client' import { disablePluginSystem, enableDevPlugins } from './helpers/plugin-smoke' -import { createPluginFromTemplate } from '../../src/cli/plugin-authoring/plugin-create' +import { createPluginFromTemplate } from '../../src/cli/plugins/authoring/plugin-create' import { ISOLATED_FRAMES_E2E } from './helpers/plugin-frame-guest' import { waitForActiveWorktree, waitForSessionReady } from './helpers/store'