mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 09:27:29 +00:00
refactor(cli): group plugin and skill implementation owners (#41549)
This commit is contained in:
+1
-1
@@ -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.
|
||||
|
||||
+1
-1
@@ -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",
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)']
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
+13
-22
@@ -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<string, string | boolean>, name: string): string | undefined {
|
||||
const value = flags.get(name)
|
||||
+1
-1
@@ -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'
|
||||
+1
-1
@@ -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[] = []
|
||||
|
||||
+2
-2
@@ -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 = {
|
||||
+1
-1
@@ -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,
|
||||
+8
-8
@@ -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
|
||||
+1
-1
@@ -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,
|
||||
+3
-3
@@ -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'
|
||||
+2
-17
@@ -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'])
|
||||
+1
-1
@@ -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 & {
|
||||
+2
-2
@@ -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 }
|
||||
+2
-2
@@ -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')
|
||||
+1
-1
@@ -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()) {
|
||||
+1
-1
@@ -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 () => {
|
||||
+3
-3
@@ -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 <Resources>/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')
|
||||
+1
-1
@@ -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 () => {
|
||||
+1
-1
@@ -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'
|
||||
+3
-3
@@ -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<boolean>
|
||||
+2
-66
@@ -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<NonNullable<PluginDevWatchHost['check']>>()
|
||||
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<void>((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)
|
||||
})
|
||||
})
|
||||
+3
-3
@@ -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 = {
|
||||
+3
-3
@@ -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). */
|
||||
+2
-40
@@ -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)
|
||||
})
|
||||
})
|
||||
+2
-2
@@ -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,
|
||||
+4
-74
@@ -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 = <T extends PluginDevWatchEvent['type']>(events: PluginDevWatchEve
|
||||
events.filter((event): event is Extract<PluginDevWatchEvent, { type: T }> => 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<ReturnType<Take>>)[] = [
|
||||
() => {
|
||||
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'
|
||||
)
|
||||
})
|
||||
})
|
||||
+2
-2
@@ -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. */
|
||||
+7
-6
@@ -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<typeof vi.fn>, rawArgs: string[] = []): HandlerContext {
|
||||
+17
-25
@@ -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 {}
|
||||
-5
@@ -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)
|
||||
})
|
||||
})
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { LogsResult } from '../handlers/plugin-runtime'
|
||||
import type { LogsResult } from '../plugin-runtime'
|
||||
|
||||
export type PluginLogLine = LogsResult['lines'][number]
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
@@ -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,
|
||||
@@ -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 []
|
||||
}
|
||||
+5
-4
@@ -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<typeof vi.fn>, cwd: string): HandlerContext {
|
||||
@@ -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'
|
||||
+5
-4
@@ -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.
|
||||
@@ -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. */
|
||||
+8
-7
@@ -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=='
|
||||
+8
-8
@@ -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))
|
||||
})
|
||||
@@ -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.
|
||||
@@ -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'
|
||||
@@ -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 " +
|
||||
|
||||
+2
-2
@@ -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<BundledSkillGuide[]> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
+2
-2
@@ -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,
|
||||
@@ -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,
|
||||
-32
@@ -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(
|
||||
/<!-- (\/?)host-capability(?:: ([a-z-]+))? -->/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)
|
||||
})
|
||||
})
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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', () => {
|
||||
@@ -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<string, SkillInstallProviderId>([
|
||||
['claude-code', 'claude'],
|
||||
@@ -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<typeof LocalHost>()
|
||||
return {
|
||||
localBundledSkillInput: async (...args: Parameters<typeof actual.localBundledSkillInput>) => ({
|
||||
...(await actual.localBundledSkillInput(...args)),
|
||||
lockTimeoutMs: 0
|
||||
})
|
||||
vi.mock(
|
||||
'../../main/skills/bundled-skill-install/bundled-skill-local-host',
|
||||
async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof LocalHost>()
|
||||
return {
|
||||
localBundledSkillInput: async (
|
||||
...args: Parameters<typeof actual.localBundledSkillInput>
|
||||
) => ({
|
||||
...(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 }
|
||||
|
||||
+3
-3
@@ -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<typeof vi.spyOn>): string {
|
||||
return spy.mock.calls.map((call) => String(call[0])).join('')
|
||||
@@ -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('|')
|
||||
|
||||
@@ -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'
|
||||
@@ -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'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user