@membranehq/cli 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/index.js +140 -140
  2. package/package.json +16 -4
  3. package/.turbo/turbo-build.log +0 -9
  4. package/CHANGELOG.md +0 -7
  5. package/scripts/add-shebang.sh +0 -6
  6. package/scripts/prepare-package-json.ts +0 -29
  7. package/src/agent.tsx +0 -50
  8. package/src/cli.ts +0 -72
  9. package/src/commands/open.command.ts +0 -51
  10. package/src/commands/pull.command.ts +0 -75
  11. package/src/commands/push.command.ts +0 -79
  12. package/src/commands/test.command.ts +0 -99
  13. package/src/components/AddMcpServerScreen.tsx +0 -215
  14. package/src/components/AgentStatus.tsx +0 -15
  15. package/src/components/Main.tsx +0 -64
  16. package/src/components/OverviewSection.tsx +0 -24
  17. package/src/components/PersonalAccessTokenInput.tsx +0 -56
  18. package/src/components/RecentChanges.tsx +0 -65
  19. package/src/components/SelectWorkspace.tsx +0 -112
  20. package/src/components/Setup.tsx +0 -121
  21. package/src/components/WorkspaceStatus.tsx +0 -61
  22. package/src/contexts/FileWatcherContext.tsx +0 -81
  23. package/src/index.ts +0 -27
  24. package/src/legacy/commands/pullWorkspace.ts +0 -70
  25. package/src/legacy/commands/pushWorkspace.ts +0 -246
  26. package/src/legacy/integrationElements.ts +0 -78
  27. package/src/legacy/push/types.ts +0 -17
  28. package/src/legacy/reader/index.ts +0 -113
  29. package/src/legacy/types.ts +0 -17
  30. package/src/legacy/util.ts +0 -149
  31. package/src/legacy/workspace-elements/connectors.ts +0 -397
  32. package/src/legacy/workspace-elements/index.ts +0 -27
  33. package/src/legacy/workspace-tools/commands/pullWorkspace.ts +0 -70
  34. package/src/legacy/workspace-tools/integrationElements.ts +0 -78
  35. package/src/legacy/workspace-tools/util.ts +0 -149
  36. package/src/mcp/server-status.ts +0 -27
  37. package/src/mcp/server.ts +0 -36
  38. package/src/mcp/tools/getTestAccessToken.ts +0 -32
  39. package/src/modules/api/account-api-client.ts +0 -89
  40. package/src/modules/api/index.ts +0 -3
  41. package/src/modules/api/membrane-api-client.ts +0 -116
  42. package/src/modules/api/workspace-api-client.ts +0 -11
  43. package/src/modules/config/cwd-context.tsx +0 -11
  44. package/src/modules/config/project/getAgentVersion.ts +0 -16
  45. package/src/modules/config/project/index.ts +0 -8
  46. package/src/modules/config/project/paths.ts +0 -25
  47. package/src/modules/config/project/readProjectConfig.ts +0 -27
  48. package/src/modules/config/project/useProjectConfig.tsx +0 -103
  49. package/src/modules/config/system/index.ts +0 -35
  50. package/src/modules/file-watcher/index.ts +0 -166
  51. package/src/modules/file-watcher/types.ts +0 -14
  52. package/src/modules/setup/steps.ts +0 -9
  53. package/src/modules/setup/useSetup.ts +0 -16
  54. package/src/modules/status/useStatus.ts +0 -16
  55. package/src/modules/workspace-element-service/constants.ts +0 -121
  56. package/src/modules/workspace-element-service/getTypeAndKeyFromPath.ts +0 -69
  57. package/src/modules/workspace-element-service/index.ts +0 -304
  58. package/src/testing/environment.ts +0 -172
  59. package/src/testing/runners/base.runner.ts +0 -27
  60. package/src/testing/runners/test.runner.ts +0 -123
  61. package/src/testing/scripts/generate-test-report.ts +0 -757
  62. package/src/testing/test-suites/base.ts +0 -92
  63. package/src/testing/test-suites/data-collection.ts +0 -128
  64. package/src/testing/testers/base.ts +0 -115
  65. package/src/testing/testers/create.ts +0 -273
  66. package/src/testing/testers/delete.ts +0 -155
  67. package/src/testing/testers/find-by-id.ts +0 -135
  68. package/src/testing/testers/list.ts +0 -110
  69. package/src/testing/testers/match.ts +0 -149
  70. package/src/testing/testers/search.ts +0 -148
  71. package/src/testing/testers/spec.ts +0 -30
  72. package/src/testing/testers/update.ts +0 -284
  73. package/src/utils/auth.ts +0 -19
  74. package/src/utils/constants.ts +0 -27
  75. package/src/utils/fields.ts +0 -83
  76. package/src/utils/logger.ts +0 -106
  77. package/src/utils/templating.ts +0 -50
  78. package/tsconfig.json +0 -21
@@ -1,103 +0,0 @@
1
- import * as fs from 'node:fs'
2
- import * as path from 'node:path'
3
-
4
- import { createContext, useCallback, useContext, useEffect, useState } from 'react'
5
- import type { PropsWithChildren } from 'react'
6
- import YAML from 'js-yaml'
7
- import { z } from 'zod'
8
-
9
- import { useCwd } from '../cwd-context'
10
- import { readProjectConfig } from './readProjectConfig'
11
-
12
- const CONFIG_FILE_NAME = 'membrane.config.yml'
13
-
14
- export const configSchema = z.object({
15
- workspaceKey: z.string().optional(),
16
- workspaceSecret: z.string().optional(),
17
- apiUri: z.string().optional(),
18
- testCustomerId: z.string().optional(),
19
- anthropicApiKey: z.string().optional(),
20
- })
21
-
22
- export type ProjectConfig = z.infer<typeof configSchema>
23
-
24
- export const commandSchema = z.enum(['pull'])
25
- export type Command = z.infer<typeof commandSchema>
26
-
27
- interface IProjectConfigContext {
28
- readonly config: ProjectConfig | null
29
- readonly isLoading: boolean
30
- readonly updateConfig: (newConfig: Partial<ProjectConfig>) => boolean
31
- readonly validateConfig: () => boolean
32
- }
33
-
34
- const ProjectConfigContext = createContext<IProjectConfigContext | undefined>(undefined)
35
-
36
- export function ProjectConfigProvider({ children }: PropsWithChildren) {
37
- const cwd = useCwd()
38
- const [config, setConfig] = useState<ProjectConfig | null>(null)
39
- const [isLoading, setIsLoading] = useState(true)
40
-
41
- useEffect(() => {
42
- const config = readProjectConfig(cwd)
43
- setConfig(config)
44
- setIsLoading(false)
45
- }, [cwd])
46
-
47
- const updateConfig = useCallback(
48
- (newConfig: Partial<ProjectConfig>): boolean => {
49
- const configPath = path.join(cwd, CONFIG_FILE_NAME)
50
- const currentConfig = readProjectConfig(cwd) ?? {}
51
- const newConfigData = {
52
- ...currentConfig,
53
- ...newConfig,
54
- }
55
-
56
- const validationResult = configSchema.safeParse(newConfigData)
57
- if (!validationResult.success) {
58
- return false
59
- }
60
-
61
- fs.writeFileSync(configPath, YAML.dump(validationResult.data))
62
- setConfig(validationResult.data)
63
- return true
64
- },
65
- [cwd],
66
- )
67
-
68
- const validateConfig = useCallback((): boolean => {
69
- return !!(config?.workspaceKey && config?.workspaceSecret)
70
- }, [config])
71
-
72
- const value: IProjectConfigContext = {
73
- config,
74
- isLoading,
75
- updateConfig,
76
- validateConfig,
77
- }
78
-
79
- return <ProjectConfigContext.Provider value={value}>{children}</ProjectConfigContext.Provider>
80
- }
81
-
82
- export function useProjectConfig(): IProjectConfigContext {
83
- const context = useContext(ProjectConfigContext)
84
- if (context === undefined) {
85
- throw new Error('useProjectConfig must be used within a ProjectConfigProvider')
86
- }
87
- return context
88
- }
89
-
90
- export function clearWorkspaceConfig(): void {
91
- const cwd = process.cwd()
92
- const configPath = path.join(cwd, CONFIG_FILE_NAME)
93
- const currentConfig = readProjectConfig(cwd) ?? {}
94
- const newConfigData = {
95
- ...currentConfig,
96
- workspaceKey: undefined,
97
- workspaceSecret: undefined,
98
- }
99
- // Remove keys if present
100
- delete newConfigData.workspaceKey
101
- delete newConfigData.workspaceSecret
102
- fs.writeFileSync(configPath, YAML.dump(newConfigData))
103
- }
@@ -1,35 +0,0 @@
1
- import os from 'os'
2
- import path from 'path'
3
-
4
- import Conf from 'conf'
5
-
6
- const schema = {
7
- pat: {
8
- type: 'string',
9
- },
10
- workspace: {
11
- type: 'object',
12
- },
13
- }
14
-
15
- const config = new Conf({
16
- schema,
17
- configName: 'config',
18
- cwd: path.join(os.homedir(), '.membrane'),
19
- })
20
-
21
- export const setPat = (token: string): void => {
22
- config.set('pat', token)
23
- }
24
-
25
- export const getPat = (): string | undefined => {
26
- return config.get('pat') as string | undefined
27
- }
28
-
29
- export const hasPat = (): boolean => {
30
- return !!config.get('pat')
31
- }
32
-
33
- export const clearPat = (): void => {
34
- config.delete('pat')
35
- }
@@ -1,166 +0,0 @@
1
- import * as crypto from 'node:crypto'
2
- import { EventEmitter } from 'node:events'
3
- import * as fs from 'node:fs'
4
- import * as path from 'node:path'
5
-
6
- import chokidar from 'chokidar'
7
- import yaml from 'yaml'
8
-
9
- import { FileChangeEventType, FileChangeEvent, FileChangeEventMap } from './types'
10
- import { getMembraneDir, IGNORED_FILES } from '../config/project/paths'
11
-
12
- import type { FSWatcher } from 'chokidar'
13
-
14
- type WatchOptions = Parameters<typeof chokidar.watch>[1]
15
-
16
- const DEFAULT_WATCHER_CONFIG: WatchOptions = {
17
- ignored: IGNORED_FILES,
18
- persistent: true,
19
- ignoreInitial: true,
20
- followSymlinks: false,
21
- depth: 10,
22
- awaitWriteFinish: {
23
- stabilityThreshold: 500,
24
- pollInterval: 100,
25
- },
26
- ignorePermissionErrors: true,
27
- atomic: true,
28
- usePolling: false,
29
- alwaysStat: false,
30
- interval: 1000,
31
- binaryInterval: 300,
32
- }
33
-
34
- export class FileWatcher extends EventEmitter<FileChangeEventMap> {
35
- private isWatching = false
36
- private watcher?: FSWatcher
37
- private readonly membraneDir: string
38
- private readonly contentCache: Record<string, string> = {}
39
-
40
- constructor(private readonly options: { cwd: string }) {
41
- super()
42
- this.membraneDir = getMembraneDir(options.cwd)
43
- }
44
-
45
- /**
46
- * Starts the file watcher
47
- */
48
- async start(): Promise<void> {
49
- if (this.isWatching) {
50
- return
51
- }
52
- if (!fs.existsSync(this.membraneDir)) {
53
- fs.mkdirSync(this.membraneDir, { recursive: true })
54
- }
55
-
56
- this.watcher = chokidar.watch(this.membraneDir, DEFAULT_WATCHER_CONFIG)
57
- this.watcher
58
- .on('add', (filePath) => this.handleFileSystemEvent('added', filePath))
59
- .on('change', (filePath) => this.handleFileSystemEvent('changed', filePath))
60
- .on('unlink', (filePath) => this.handleFileSystemEvent('deleted', filePath))
61
- .on('ready', () => (this.isWatching = true))
62
- }
63
-
64
- /**
65
- * Stops the file watcher
66
- */
67
- async stop(): Promise<void> {
68
- if (!this.isWatching || !this.watcher) {
69
- return
70
- }
71
-
72
- await this.watcher.close()
73
- this.isWatching = false
74
- this.watcher = undefined
75
- this.clearCache()
76
- this.emit('stopped')
77
- }
78
-
79
- getCwd(): string {
80
- return this.options.cwd
81
- }
82
-
83
- /**
84
- * Handles file system events and only emits when content actually changes
85
- */
86
- private handleFileSystemEvent(eventType: FileChangeEventType, filePath: string): void {
87
- const relativePath = path.relative(this.membraneDir, filePath)
88
-
89
- if (eventType === 'deleted') {
90
- this.removeFromCache(relativePath)
91
- const event: FileChangeEvent = {
92
- filePath,
93
- relativePath,
94
- data: undefined,
95
- }
96
- this.emit(eventType, event)
97
- return
98
- }
99
-
100
- const fileContent = this.readFileContent(filePath)
101
-
102
- const hasContentChanged = this.hasContentChanged(relativePath, fileContent, eventType)
103
- if (!hasContentChanged) {
104
- return
105
- }
106
-
107
- const event = this.processFileEvent(filePath, fileContent)
108
- this.updateCache(relativePath, fileContent)
109
- this.emit(eventType, event)
110
- }
111
-
112
- private readFileContent(filePath: string): string {
113
- return fs.readFileSync(filePath, 'utf8')
114
- }
115
-
116
- private processFileEvent(filePath: string, fileContent: string): FileChangeEvent | null {
117
- const relativePath = path.relative(this.membraneDir, filePath)
118
-
119
- let data: any
120
- try {
121
- data = fileContent ? yaml.parse(fileContent) : undefined
122
- } catch (_error) {
123
- data = undefined
124
- }
125
-
126
- return { filePath, relativePath, data }
127
- }
128
-
129
- private hasContentChanged(relativePath: string, fileContent: string, eventType: FileChangeEventType): boolean {
130
- if (eventType === 'added') {
131
- return true
132
- }
133
-
134
- if (!fileContent) {
135
- return this.contentCache[relativePath] !== undefined
136
- }
137
-
138
- const contentHash = this.getContentHash(fileContent)
139
- const cachedHash = this.contentCache[relativePath]
140
-
141
- return cachedHash !== contentHash
142
- }
143
-
144
- private getContentHash(fileContent: string): string {
145
- return crypto.createHash('sha256').update(fileContent).digest('hex')
146
- }
147
-
148
- private updateCache(relativePath: string, fileContent: string | null): void {
149
- if (!fileContent) {
150
- delete this.contentCache[relativePath]
151
- return
152
- }
153
-
154
- this.contentCache[relativePath] = this.getContentHash(fileContent)
155
- }
156
-
157
- private removeFromCache(relativePath: string): void {
158
- delete this.contentCache[relativePath]
159
- }
160
-
161
- private clearCache(): void {
162
- Object.keys(this.contentCache).forEach((key) => {
163
- delete this.contentCache[key]
164
- })
165
- }
166
- }
@@ -1,14 +0,0 @@
1
- export interface FileChangeEvent {
2
- filePath: string
3
- relativePath: string
4
- data?: any
5
- }
6
-
7
- export type FileChangeEventMap = {
8
- added: [FileChangeEvent]
9
- changed: [FileChangeEvent]
10
- deleted: [FileChangeEvent]
11
- stopped: []
12
- }
13
-
14
- export type FileChangeEventType = keyof FileChangeEventMap
@@ -1,9 +0,0 @@
1
- export enum Step {
2
- Authenticate,
3
- ConnectWorkspace,
4
- }
5
-
6
- export const STEP_LABELS: Record<Step, string> = {
7
- [Step.Authenticate]: 'Authenticate in Membrane',
8
- [Step.ConnectWorkspace]: 'Connect a Membrane Workspace',
9
- }
@@ -1,16 +0,0 @@
1
- import { useProjectConfig } from '../config/project'
2
- import { hasPat } from '../config/system'
3
-
4
- export const useSetup = () => {
5
- const { config } = useProjectConfig()
6
-
7
- const isAuthenticated = hasPat()
8
- const workspaceIsConfigured = !!(config?.workspaceKey && config?.workspaceSecret)
9
- const isSetupComplete = isAuthenticated && workspaceIsConfigured
10
-
11
- return {
12
- isAuthenticated,
13
- workspaceIsConfigured,
14
- isSetupComplete,
15
- }
16
- }
@@ -1,16 +0,0 @@
1
- import { useCwd } from '../config/cwd-context'
2
- import { useProjectConfig } from '../config/project'
3
- import { useSetup } from '../setup/useSetup'
4
-
5
- export const useStatus = () => {
6
- const { isSetupComplete } = useSetup()
7
- const { config } = useProjectConfig()
8
- const cwd = useCwd()
9
-
10
- return {
11
- isSetupComplete,
12
- workspaceKey: config?.workspaceKey,
13
- config,
14
- cwd,
15
- }
16
- }
@@ -1,121 +0,0 @@
1
- import { WorkspaceElementType } from '@integration-app/sdk'
2
-
3
- export interface ElementConfig {
4
- element: string
5
- elements: string
6
- exportable?: boolean
7
- integrationSpecific?: boolean
8
- }
9
-
10
- export const INTEGRATION_ELEMENTS: Record<WorkspaceElementType, ElementConfig> = {
11
- [WorkspaceElementType.Integration]: {
12
- element: 'integration',
13
- elements: 'integrations',
14
- exportable: false,
15
- },
16
- [WorkspaceElementType.Connector]: {
17
- element: 'connector',
18
- elements: 'connectors',
19
- exportable: false,
20
- },
21
- [WorkspaceElementType.Action]: {
22
- element: 'action',
23
- elements: 'actions',
24
- integrationSpecific: true,
25
- },
26
- [WorkspaceElementType.AppDataSchema]: {
27
- element: 'appDataSchema',
28
- elements: 'appDataSchemas',
29
- },
30
- [WorkspaceElementType.AppEventType]: {
31
- element: 'appEventType',
32
- elements: 'appEventTypes',
33
- },
34
- [WorkspaceElementType.DataLinkTable]: {
35
- element: 'dataLinkTable',
36
- elements: 'dataLinkTables',
37
- },
38
- [WorkspaceElementType.DataSource]: {
39
- element: 'dataSource',
40
- elements: 'dataSources',
41
- integrationSpecific: true,
42
- },
43
- [WorkspaceElementType.FieldMapping]: {
44
- element: 'fieldMapping',
45
- elements: 'fieldMappings',
46
- integrationSpecific: true,
47
- },
48
- [WorkspaceElementType.Flow]: {
49
- element: 'flow',
50
- elements: 'flows',
51
- integrationSpecific: true,
52
- },
53
- [WorkspaceElementType.Customer]: {
54
- element: 'customer',
55
- elements: 'customers',
56
- },
57
- [WorkspaceElementType.FlowInstance]: {
58
- element: 'flowInstance',
59
- elements: 'flowInstances',
60
- },
61
- [WorkspaceElementType.FlowRun]: {
62
- element: 'flowRun',
63
- elements: 'flowRuns',
64
- },
65
- [WorkspaceElementType.Scenario]: {
66
- element: 'scenario',
67
- elements: 'scenarios',
68
- },
69
- [WorkspaceElementType.ActionInstance]: {
70
- element: 'actionInstance',
71
- elements: 'actionInstances',
72
- },
73
- [WorkspaceElementType.Connection]: {
74
- element: 'connection',
75
- elements: 'connections',
76
- },
77
- [WorkspaceElementType.FieldMappingInstance]: {
78
- element: 'fieldMappingInstance',
79
- elements: 'fieldMappingInstances',
80
- },
81
- [WorkspaceElementType.DataSourceInstance]: {
82
- element: 'dataSourceInstance',
83
- elements: 'dataSourceInstances',
84
- },
85
- [WorkspaceElementType.DataLinkTableInstance]: {
86
- element: 'dataLinkTableInstance',
87
- elements: 'dataLinkTableInstances',
88
- },
89
- [WorkspaceElementType.AppEventSubscription]: {
90
- element: 'appEventSubscription',
91
- elements: 'appEventSubscriptions',
92
- },
93
- [WorkspaceElementType.AppDataSchemaInstance]: {
94
- element: 'appDataSchemaInstance',
95
- elements: 'appDataSchemaInstances',
96
- },
97
- [WorkspaceElementType.ExternalEventSubscription]: {
98
- element: 'externalEventSubscription',
99
- elements: 'externalEventSubscriptions',
100
- },
101
- [WorkspaceElementType.ExternalEventLogRecord]: {
102
- element: 'externalEventLogRecord',
103
- elements: 'externalEventLogRecords',
104
- },
105
- [WorkspaceElementType.ExternalEventPull]: {
106
- element: 'externalEventPull',
107
- elements: 'externalEventPulls',
108
- },
109
- [WorkspaceElementType.DataCollection]: {
110
- element: 'dataCollection',
111
- elements: 'dataCollections',
112
- },
113
- [WorkspaceElementType.Screen]: {
114
- element: 'screen',
115
- elements: 'screens',
116
- },
117
- [WorkspaceElementType.ActionRunLogRecord]: {
118
- element: 'actionRunLogRecord',
119
- elements: 'actionRunLogRecords',
120
- },
121
- }
@@ -1,69 +0,0 @@
1
- import * as path from 'node:path'
2
-
3
- import { WorkspaceElementSpecs, WorkspaceElementType } from '@integration-app/sdk'
4
-
5
- export interface WorkspaceElementKeyReference {
6
- readonly type: WorkspaceElementType
7
- readonly key: string
8
- }
9
-
10
- export function getTypeAndKeyFromPath(relativePath: string): WorkspaceElementKeyReference | undefined {
11
- // NOTE: skip connector files for now
12
- if (isConnectorFile(relativePath)) {
13
- return undefined
14
- }
15
-
16
- if (isWorkspaceElementFile(relativePath)) {
17
- return extractWorkspaceElementKeyReference(relativePath)
18
- }
19
- return undefined
20
- }
21
-
22
- function extractWorkspaceElementKeyReference(relativePath: string): WorkspaceElementKeyReference | undefined {
23
- const elementTypes = Object.values(WorkspaceElementSpecs).map((spec) => spec.apiPath)
24
-
25
- // <element-type>/<element-key>/spec.yml or .yaml
26
- const universalElementPattern = new RegExp(
27
- `^(?<elementTypePlural>${elementTypes.join('|')})\/(?<elementKey>[^\/]+)\/spec\.y[a]*ml$`,
28
- )
29
- const universalMatch = relativePath.match(universalElementPattern)
30
- if (universalMatch && universalMatch.groups) {
31
- const { elementTypePlural, elementKey } = universalMatch.groups
32
- const type = getWorkspaceElementTypeFromFolderName(elementTypePlural)
33
- if (type) {
34
- return { key: elementKey, type }
35
- }
36
- }
37
-
38
- // integrations/<integration-key>/<element-type>/<element-key>/spec.yml or .yaml
39
- const integratedElementPattern = new RegExp(
40
- `^integrations\/(?<integrationKey>[^\/]+)\/(?<elementTypePlural>${elementTypes.join('|')})\/(?<elementKey>[^\/]+)\/spec\.y[a]*ml$`,
41
- )
42
- const integratedMatch = relativePath.match(integratedElementPattern)
43
- if (integratedMatch && integratedMatch.groups) {
44
- const { elementTypePlural, elementKey } = integratedMatch.groups
45
- const type = getWorkspaceElementTypeFromFolderName(elementTypePlural)
46
- if (type) {
47
- return { key: elementKey, type }
48
- }
49
- }
50
-
51
- return undefined
52
- }
53
-
54
- function isWorkspaceElementFile(relativePath: string): boolean {
55
- return relativePath.endsWith('.yml') || relativePath.endsWith('.yaml')
56
- }
57
-
58
- function isConnectorFile(relativePath: string): boolean {
59
- const [rootDir] = relativePath.split(path.sep)
60
- return rootDir === WorkspaceElementSpecs[WorkspaceElementType.Connector].apiPath
61
- }
62
-
63
- function getWorkspaceElementTypeFromFolderName(folderName: string): WorkspaceElementType | undefined {
64
- return Object.values(WorkspaceElementType).find((type) => getWorkspaceElementTypeFolderName(type) === folderName)
65
- }
66
-
67
- function getWorkspaceElementTypeFolderName(type: WorkspaceElementType): string {
68
- return WorkspaceElementSpecs[type].apiPath
69
- }