@membranehq/cli 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -4
- package/.turbo/turbo-build.log +0 -9
- package/CHANGELOG.md +0 -7
- package/scripts/add-shebang.sh +0 -6
- package/scripts/prepare-package-json.ts +0 -29
- package/src/agent.tsx +0 -50
- package/src/cli.ts +0 -72
- package/src/commands/open.command.ts +0 -51
- package/src/commands/pull.command.ts +0 -75
- package/src/commands/push.command.ts +0 -79
- package/src/commands/test.command.ts +0 -99
- package/src/components/AddMcpServerScreen.tsx +0 -215
- package/src/components/AgentStatus.tsx +0 -15
- package/src/components/Main.tsx +0 -64
- package/src/components/OverviewSection.tsx +0 -24
- package/src/components/PersonalAccessTokenInput.tsx +0 -56
- package/src/components/RecentChanges.tsx +0 -65
- package/src/components/SelectWorkspace.tsx +0 -112
- package/src/components/Setup.tsx +0 -121
- package/src/components/WorkspaceStatus.tsx +0 -61
- package/src/contexts/FileWatcherContext.tsx +0 -81
- package/src/index.ts +0 -27
- package/src/legacy/commands/pullWorkspace.ts +0 -70
- package/src/legacy/commands/pushWorkspace.ts +0 -246
- package/src/legacy/integrationElements.ts +0 -78
- package/src/legacy/push/types.ts +0 -17
- package/src/legacy/reader/index.ts +0 -113
- package/src/legacy/types.ts +0 -17
- package/src/legacy/util.ts +0 -149
- package/src/legacy/workspace-elements/connectors.ts +0 -397
- package/src/legacy/workspace-elements/index.ts +0 -27
- package/src/legacy/workspace-tools/commands/pullWorkspace.ts +0 -70
- package/src/legacy/workspace-tools/integrationElements.ts +0 -78
- package/src/legacy/workspace-tools/util.ts +0 -149
- package/src/mcp/server-status.ts +0 -27
- package/src/mcp/server.ts +0 -36
- package/src/mcp/tools/getTestAccessToken.ts +0 -32
- package/src/modules/api/account-api-client.ts +0 -89
- package/src/modules/api/index.ts +0 -3
- package/src/modules/api/membrane-api-client.ts +0 -116
- package/src/modules/api/workspace-api-client.ts +0 -11
- package/src/modules/config/cwd-context.tsx +0 -11
- package/src/modules/config/project/getAgentVersion.ts +0 -16
- package/src/modules/config/project/index.ts +0 -8
- package/src/modules/config/project/paths.ts +0 -25
- package/src/modules/config/project/readProjectConfig.ts +0 -27
- package/src/modules/config/project/useProjectConfig.tsx +0 -103
- package/src/modules/config/system/index.ts +0 -35
- package/src/modules/file-watcher/index.ts +0 -166
- package/src/modules/file-watcher/types.ts +0 -14
- package/src/modules/setup/steps.ts +0 -9
- package/src/modules/setup/useSetup.ts +0 -16
- package/src/modules/status/useStatus.ts +0 -16
- package/src/modules/workspace-element-service/constants.ts +0 -121
- package/src/modules/workspace-element-service/getTypeAndKeyFromPath.ts +0 -69
- package/src/modules/workspace-element-service/index.ts +0 -304
- package/src/testing/environment.ts +0 -172
- package/src/testing/runners/base.runner.ts +0 -27
- package/src/testing/runners/test.runner.ts +0 -123
- package/src/testing/scripts/generate-test-report.ts +0 -757
- package/src/testing/test-suites/base.ts +0 -92
- package/src/testing/test-suites/data-collection.ts +0 -128
- package/src/testing/testers/base.ts +0 -115
- package/src/testing/testers/create.ts +0 -273
- package/src/testing/testers/delete.ts +0 -155
- package/src/testing/testers/find-by-id.ts +0 -135
- package/src/testing/testers/list.ts +0 -110
- package/src/testing/testers/match.ts +0 -149
- package/src/testing/testers/search.ts +0 -148
- package/src/testing/testers/spec.ts +0 -30
- package/src/testing/testers/update.ts +0 -284
- package/src/utils/auth.ts +0 -19
- package/src/utils/constants.ts +0 -27
- package/src/utils/fields.ts +0 -83
- package/src/utils/logger.ts +0 -106
- package/src/utils/templating.ts +0 -50
- package/tsconfig.json +0 -21
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { DataCollectionSpec } from '@integration-app/sdk'
|
|
2
|
-
|
|
3
|
-
import { TestEnvironment } from '../environment'
|
|
4
|
-
import { BaseTester } from './base'
|
|
5
|
-
|
|
6
|
-
export class DataCollectionSpecTester extends BaseTester<undefined> {
|
|
7
|
-
private dataCollection: DataCollectionSpec
|
|
8
|
-
|
|
9
|
-
constructor({
|
|
10
|
-
environment,
|
|
11
|
-
dataCollectionKey,
|
|
12
|
-
dataCollection,
|
|
13
|
-
}: {
|
|
14
|
-
environment: TestEnvironment
|
|
15
|
-
dataCollectionKey: string
|
|
16
|
-
dataCollection: DataCollectionSpec
|
|
17
|
-
}) {
|
|
18
|
-
super({
|
|
19
|
-
environment,
|
|
20
|
-
path: `data/${dataCollectionKey}/spec`,
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
this.dataCollection = dataCollection
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async run(): Promise<void> {
|
|
27
|
-
const fieldCount = Object.keys(this.dataCollection.fieldsSchema).length
|
|
28
|
-
await this.assert(() => fieldCount > 0, 'Fields schema has field definitions')
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DataCollectionSpec,
|
|
3
|
-
excludeWriteOnlyFieldsFromSchema,
|
|
4
|
-
getDataCollectionUpdateFields,
|
|
5
|
-
valueToSchema,
|
|
6
|
-
extractIntegrationAppErrorData,
|
|
7
|
-
walkSchema,
|
|
8
|
-
makeDataLocationPath,
|
|
9
|
-
} from '@integration-app/sdk'
|
|
10
|
-
import chalk from 'chalk'
|
|
11
|
-
|
|
12
|
-
import { TestEnvironment } from '../environment'
|
|
13
|
-
import { BaseTester } from './base'
|
|
14
|
-
import { getNotMatchingSubFields } from '../../utils/fields'
|
|
15
|
-
|
|
16
|
-
interface DataCollectionUpdateTestConfig {
|
|
17
|
-
input: {
|
|
18
|
-
id: string
|
|
19
|
-
fields: Record<string, any>
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface ReferenceCollection {
|
|
24
|
-
key: string
|
|
25
|
-
parameters?: Record<string, any>
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface ExampleRecord {
|
|
29
|
-
id: string
|
|
30
|
-
fields: Record<string, any>
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export class DataCollectionUpdateTester extends BaseTester<DataCollectionUpdateTestConfig> {
|
|
34
|
-
private dataCollectionKey: string
|
|
35
|
-
private dataCollection: DataCollectionSpec
|
|
36
|
-
|
|
37
|
-
constructor({
|
|
38
|
-
environment,
|
|
39
|
-
dataCollectionKey,
|
|
40
|
-
dataCollection,
|
|
41
|
-
}: {
|
|
42
|
-
environment: TestEnvironment
|
|
43
|
-
dataCollectionKey: string
|
|
44
|
-
dataCollection: DataCollectionSpec
|
|
45
|
-
}) {
|
|
46
|
-
super({
|
|
47
|
-
environment,
|
|
48
|
-
path: `data/${dataCollectionKey}/update`,
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
this.dataCollectionKey = dataCollectionKey
|
|
52
|
-
this.dataCollection = dataCollection
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async run(config: DataCollectionUpdateTestConfig): Promise<void> {
|
|
56
|
-
const id =
|
|
57
|
-
(
|
|
58
|
-
this.environment.state[this.dataCollectionKey] as {
|
|
59
|
-
createdRecordId: string
|
|
60
|
-
}
|
|
61
|
-
)?.createdRecordId || config?.input?.id
|
|
62
|
-
if (!id) {
|
|
63
|
-
throw new Error(`No ID found in state or config for ${this.dataCollectionKey} update`)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const updateResponse = await this.environment.client
|
|
67
|
-
.connection(this.environment.connectionId)
|
|
68
|
-
.dataCollection(this.dataCollectionKey)
|
|
69
|
-
.update({ ...config.input, id })
|
|
70
|
-
|
|
71
|
-
await this.assert(() => !!updateResponse.id, 'Returned ID of updated record')
|
|
72
|
-
|
|
73
|
-
if (!this.environment.state[this.dataCollectionKey]) {
|
|
74
|
-
this.environment.state[this.dataCollectionKey] = {}
|
|
75
|
-
}
|
|
76
|
-
;(
|
|
77
|
-
this.environment.state[this.dataCollectionKey] as {
|
|
78
|
-
createdRecordId?: string
|
|
79
|
-
}
|
|
80
|
-
).createdRecordId = updateResponse.id
|
|
81
|
-
|
|
82
|
-
if (this.dataCollection.findById) {
|
|
83
|
-
const findByIdResponse = await this.environment.client
|
|
84
|
-
.connection(this.environment.connectionId)
|
|
85
|
-
.dataCollection(this.dataCollectionKey)
|
|
86
|
-
.findById({
|
|
87
|
-
id: updateResponse.id,
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
await this.assert(() => !!findByIdResponse.record, 'Record is returned from findById')
|
|
91
|
-
|
|
92
|
-
const updateFieldsSchema = getDataCollectionUpdateFields(this.dataCollection)
|
|
93
|
-
const schemaToCompare = excludeWriteOnlyFieldsFromSchema(updateFieldsSchema)
|
|
94
|
-
const sentFieldsToCheck = valueToSchema(config.input.fields, schemaToCompare, {
|
|
95
|
-
skipUnknownFields: true,
|
|
96
|
-
})
|
|
97
|
-
const receivedFieldsToCheck = valueToSchema(findByIdResponse.record.fields, schemaToCompare)
|
|
98
|
-
|
|
99
|
-
const diff = getNotMatchingSubFields(receivedFieldsToCheck, sentFieldsToCheck)
|
|
100
|
-
|
|
101
|
-
await this.assert(() => !diff, 'Returned fields match updated fields', {
|
|
102
|
-
difference: diff,
|
|
103
|
-
sentFields: sentFieldsToCheck,
|
|
104
|
-
receivedFields: receivedFieldsToCheck,
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async generateConfig(): Promise<DataCollectionUpdateTestConfig> {
|
|
110
|
-
let id = (
|
|
111
|
-
this.environment.state[this.dataCollectionKey] as {
|
|
112
|
-
createdRecordId: string
|
|
113
|
-
}
|
|
114
|
-
)?.createdRecordId
|
|
115
|
-
|
|
116
|
-
if (!id) {
|
|
117
|
-
if (!this.dataCollection.list) {
|
|
118
|
-
throw new Error(
|
|
119
|
-
`Can't find a record to test update operation for ${this.dataCollectionKey}. List operation is not implemented for this data collection`,
|
|
120
|
-
)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const listResponse = await this.environment.client
|
|
124
|
-
.connection(this.environment.connectionId)
|
|
125
|
-
.dataCollection(this.dataCollectionKey)
|
|
126
|
-
.list({})
|
|
127
|
-
|
|
128
|
-
if (!listResponse?.records?.length) {
|
|
129
|
-
throw new Error(`No records found to test update for ${this.dataCollectionKey}`)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
id = listResponse.records[0].id
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const updateFieldsSchema = getDataCollectionUpdateFields(this.dataCollection)
|
|
136
|
-
|
|
137
|
-
if (!updateFieldsSchema?.properties) {
|
|
138
|
-
throw new Error('No fields schema found for data collection')
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const fields = await this.generateFieldsWithLLM(updateFieldsSchema)
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
input: {
|
|
145
|
-
id,
|
|
146
|
-
fields,
|
|
147
|
-
},
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
protected async fixTestCase({
|
|
152
|
-
config,
|
|
153
|
-
error,
|
|
154
|
-
}: {
|
|
155
|
-
config: DataCollectionUpdateTestConfig
|
|
156
|
-
error: any
|
|
157
|
-
}): Promise<DataCollectionUpdateTestConfig> {
|
|
158
|
-
const errorData = extractIntegrationAppErrorData(error)
|
|
159
|
-
|
|
160
|
-
const updateFieldsSchema = getDataCollectionUpdateFields(this.dataCollection)
|
|
161
|
-
|
|
162
|
-
const examples = await this.getExampleRecordsForSchema(updateFieldsSchema)
|
|
163
|
-
|
|
164
|
-
const prompt = `I'm trying to update a data record in a data collection with the following fields schema:
|
|
165
|
-
|
|
166
|
-
${JSON.stringify(updateFieldsSchema, null, 2)}
|
|
167
|
-
|
|
168
|
-
I tried to update a record with ID "${config.input?.id}" and these fields:
|
|
169
|
-
|
|
170
|
-
${JSON.stringify(config.input?.fields, null, 2)}
|
|
171
|
-
|
|
172
|
-
But got this error:
|
|
173
|
-
|
|
174
|
-
${JSON.stringify(errorData)}
|
|
175
|
-
|
|
176
|
-
${this.getExampleRecordsPrompt(examples)}
|
|
177
|
-
|
|
178
|
-
Please analyze the error and provide:
|
|
179
|
-
1. A fixed fields object that would work (as a JSON object)
|
|
180
|
-
2. A short explanation of what was wrong with the original fields
|
|
181
|
-
|
|
182
|
-
Format your response as a JSON object with two fields:
|
|
183
|
-
{
|
|
184
|
-
"explanation": "short explanation of what was wrong and how you fixed it",
|
|
185
|
-
"fields": { ... fixed fields ... }
|
|
186
|
-
}.
|
|
187
|
-
|
|
188
|
-
Only return the JSON object, no other text or wrapping (like \`\`\`json or \`\`\`).`
|
|
189
|
-
|
|
190
|
-
const response = await this.environment.llm.complete({
|
|
191
|
-
prompt,
|
|
192
|
-
maxTokens: 10000,
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
const result = JSON.parse(response.trim())
|
|
196
|
-
|
|
197
|
-
console.warn(chalk.bold.yellow('[auto-fix]'), `${this.path}:`, result.explanation)
|
|
198
|
-
|
|
199
|
-
return {
|
|
200
|
-
input: {
|
|
201
|
-
id: config.input.id,
|
|
202
|
-
fields: result.fields,
|
|
203
|
-
},
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
private async generateFieldsWithLLM(schema: any): Promise<Record<string, any>> {
|
|
208
|
-
const examples = await this.getExampleRecordsForSchema(schema)
|
|
209
|
-
|
|
210
|
-
const prompt = `Generate a valid JSON object that matches this JSONSchema. Return only the JSON object, no other text.
|
|
211
|
-
|
|
212
|
-
${this.getExampleRecordsPrompt(examples)}
|
|
213
|
-
|
|
214
|
-
JSONSchema:
|
|
215
|
-
${JSON.stringify(schema, null, 2)}.
|
|
216
|
-
|
|
217
|
-
Do not populate fields with null or empty strings, unless the schema explicitly requires it.
|
|
218
|
-
|
|
219
|
-
Only return the JSON object, no other text or wrapping (like \`\`\`json or \`\`\`).`
|
|
220
|
-
|
|
221
|
-
const response = await this.environment.llm.complete({
|
|
222
|
-
prompt,
|
|
223
|
-
maxTokens: 10000,
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
const fields = JSON.parse(response.trim())
|
|
227
|
-
return fields
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
private getExampleRecordsPrompt(examples: Record<string, ExampleRecord[]>): string {
|
|
231
|
-
const examplesText = Object.entries(examples)
|
|
232
|
-
.map(([key, records]) => {
|
|
233
|
-
return `Example records from collection "${key}":\n${JSON.stringify(records, null, 2)}`
|
|
234
|
-
})
|
|
235
|
-
.join('\n\n')
|
|
236
|
-
|
|
237
|
-
return `When a field has a "referenceCollection" property, its value should be an ID of a record from that collection. Here are some example records you can use as references:
|
|
238
|
-
|
|
239
|
-
${examplesText}.
|
|
240
|
-
|
|
241
|
-
If you don't have an example for a given collection, leave the field empty instead of coming up with a fake record id.`
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
private async findReferenceCollections(schema: any): Promise<ReferenceCollection[]> {
|
|
245
|
-
const references = new Set<string>()
|
|
246
|
-
const result: ReferenceCollection[] = []
|
|
247
|
-
|
|
248
|
-
walkSchema(schema, (obj) => {
|
|
249
|
-
if (obj.referenceCollection) {
|
|
250
|
-
const key = obj.referenceCollection.key
|
|
251
|
-
const parameters = obj.referenceCollection.parameters
|
|
252
|
-
const refKey = `${key}:${JSON.stringify(parameters || {})}`
|
|
253
|
-
|
|
254
|
-
if (!references.has(refKey)) {
|
|
255
|
-
references.add(refKey)
|
|
256
|
-
result.push({ key, parameters })
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return obj
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
return result
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
private async fetchExampleRecords(collection: ReferenceCollection): Promise<ExampleRecord[]> {
|
|
266
|
-
const response = await this.environment.client
|
|
267
|
-
.connection(this.environment.connectionId)
|
|
268
|
-
.dataCollection(collection.key)
|
|
269
|
-
.list({
|
|
270
|
-
parameters: collection.parameters,
|
|
271
|
-
})
|
|
272
|
-
return response.records.map((r) => ({ id: r.id, fields: r.fields }))
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
private async getExampleRecordsForSchema(schema: any): Promise<Record<string, ExampleRecord[]>> {
|
|
276
|
-
const collections = await this.findReferenceCollections(schema)
|
|
277
|
-
const result: Record<string, ExampleRecord[]> = {}
|
|
278
|
-
for (const collection of collections) {
|
|
279
|
-
const locationPath = makeDataLocationPath(collection)
|
|
280
|
-
result[locationPath] = await this.fetchExampleRecords(collection)
|
|
281
|
-
}
|
|
282
|
-
return result
|
|
283
|
-
}
|
|
284
|
-
}
|
package/src/utils/auth.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import jwt from 'jsonwebtoken'
|
|
2
|
-
|
|
3
|
-
export async function generateAccessToken(key: string, secret: string): Promise<string> {
|
|
4
|
-
try {
|
|
5
|
-
return jwt.sign(
|
|
6
|
-
{
|
|
7
|
-
name: 'Membrane CLI',
|
|
8
|
-
isAdmin: true,
|
|
9
|
-
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
10
|
-
iss: key,
|
|
11
|
-
},
|
|
12
|
-
secret,
|
|
13
|
-
{ algorithm: 'HS512' },
|
|
14
|
-
)
|
|
15
|
-
} catch (error) {
|
|
16
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
17
|
-
throw new Error(`Failed to generate access token: ${errorMessage}`)
|
|
18
|
-
}
|
|
19
|
-
}
|
package/src/utils/constants.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import * as path from 'node:path'
|
|
2
|
-
|
|
3
|
-
export const MEMBRANE_DIR_NAME = 'membrane'
|
|
4
|
-
|
|
5
|
-
export const CONFIG_FILE_NAME = 'membrane.config.yml'
|
|
6
|
-
|
|
7
|
-
export function getPaths() {
|
|
8
|
-
const baseDir = process.cwd()
|
|
9
|
-
|
|
10
|
-
const membraneDirPath = path.join(baseDir, MEMBRANE_DIR_NAME)
|
|
11
|
-
const configPath = path.join(baseDir, CONFIG_FILE_NAME)
|
|
12
|
-
|
|
13
|
-
const payloadDirPath = membraneDirPath
|
|
14
|
-
const workspaceDataFilePath = path.join(payloadDirPath, 'workspace.yaml')
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
membraneDirPath,
|
|
18
|
-
configPath,
|
|
19
|
-
payloadDirPath,
|
|
20
|
-
workspaceDataFilePath,
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function getBasePath() {
|
|
25
|
-
const paths = getPaths()
|
|
26
|
-
return paths.membraneDirPath
|
|
27
|
-
}
|
package/src/utils/fields.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Returns a subset of subFields that have values that do not match the values in allFields.
|
|
3
|
-
* For nested objects and arrays, returns only items that do not match.
|
|
4
|
-
*/
|
|
5
|
-
export function getNotMatchingSubFields(
|
|
6
|
-
allFields: Record<string, any>,
|
|
7
|
-
subFields: Record<string, any>,
|
|
8
|
-
): Record<string, any> | null {
|
|
9
|
-
const result: Record<string, any> = {}
|
|
10
|
-
for (const key in subFields) {
|
|
11
|
-
if (!(key in allFields)) {
|
|
12
|
-
result[key] = subFields[key]
|
|
13
|
-
continue
|
|
14
|
-
}
|
|
15
|
-
const origVal = allFields[key]
|
|
16
|
-
const subVal = subFields[key]
|
|
17
|
-
if (origVal && subVal && typeof origVal === 'object' && typeof subVal === 'object') {
|
|
18
|
-
if (Array.isArray(origVal) && Array.isArray(subVal)) {
|
|
19
|
-
const notMatchingValues = subVal.filter(
|
|
20
|
-
(subItem) =>
|
|
21
|
-
!origVal.some((origItem) => {
|
|
22
|
-
if (
|
|
23
|
-
typeof origItem === 'object' &&
|
|
24
|
-
typeof subItem === 'object' &&
|
|
25
|
-
origItem !== null &&
|
|
26
|
-
subItem !== null &&
|
|
27
|
-
!Array.isArray(origItem) &&
|
|
28
|
-
!Array.isArray(subItem)
|
|
29
|
-
) {
|
|
30
|
-
for (const subKey in subItem) {
|
|
31
|
-
if (!(subKey in origItem) || !softCompare(origItem[subKey], subItem[subKey])) {
|
|
32
|
-
return false
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return true
|
|
36
|
-
} else {
|
|
37
|
-
return JSON.stringify(origItem) === JSON.stringify(subItem)
|
|
38
|
-
}
|
|
39
|
-
}),
|
|
40
|
-
)
|
|
41
|
-
if (notMatchingValues.length > 0) {
|
|
42
|
-
result[key] = notMatchingValues
|
|
43
|
-
}
|
|
44
|
-
} else if (!Array.isArray(origVal) && !Array.isArray(subVal)) {
|
|
45
|
-
const subResult = getNotMatchingSubFields(origVal, subVal)
|
|
46
|
-
if (subResult !== null) {
|
|
47
|
-
result[key] = subResult
|
|
48
|
-
}
|
|
49
|
-
} else {
|
|
50
|
-
result[key] = subVal
|
|
51
|
-
}
|
|
52
|
-
continue
|
|
53
|
-
}
|
|
54
|
-
if (!softCompare(origVal, subVal)) {
|
|
55
|
-
result[key] = subVal
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return Object.keys(result).length === 0 ? null : result
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// it shouldnt fail because one is string and other is number in the account create etc.
|
|
62
|
-
// more details in the match test operation.
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Compare two values in a way that is forgiving for typical API quirks.
|
|
66
|
-
*/
|
|
67
|
-
export function softCompare(a: any, b: any) {
|
|
68
|
-
if (a == b) {
|
|
69
|
-
return true
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (a?.toString?.() === b?.toString?.()) {
|
|
73
|
-
return true
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const dateA = new Date(a)
|
|
77
|
-
const dateB = new Date(b)
|
|
78
|
-
if (!isNaN(dateA.getTime()) && !isNaN(dateB.getTime())) {
|
|
79
|
-
return dateA.getTime() === dateB.getTime()
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return false
|
|
83
|
-
}
|
package/src/utils/logger.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
|
|
3
|
-
type LogLevel = 'info' | 'success' | 'warning' | 'error' | 'debug'
|
|
4
|
-
|
|
5
|
-
interface LogOptions {
|
|
6
|
-
prefix?: string
|
|
7
|
-
suffix?: string
|
|
8
|
-
timestamp?: boolean
|
|
9
|
-
error?: string
|
|
10
|
-
color?: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const LOG_ICONS = {
|
|
14
|
-
info: '📝',
|
|
15
|
-
success: '✨',
|
|
16
|
-
warning: '⚠️',
|
|
17
|
-
error: '❌',
|
|
18
|
-
debug: '🔍',
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const LOG_STYLES = {
|
|
22
|
-
info: chalk.blue,
|
|
23
|
-
success: chalk.green,
|
|
24
|
-
warning: chalk.yellow,
|
|
25
|
-
error: chalk.red,
|
|
26
|
-
debug: chalk.gray,
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class Logger {
|
|
30
|
-
private static formatMessage(message: string, level: LogLevel, options: LogOptions = {}) {
|
|
31
|
-
const timestamp = options.timestamp ? `${chalk.gray(new Date().toISOString())} ` : ''
|
|
32
|
-
const prefix = options.prefix ? `${chalk.gray(options.prefix)} ` : ''
|
|
33
|
-
const suffix = options.suffix ? ` ${chalk.gray(options.suffix)}` : ''
|
|
34
|
-
const icon = LOG_ICONS[level]
|
|
35
|
-
const style = LOG_STYLES[level]
|
|
36
|
-
|
|
37
|
-
return `${timestamp}${icon} ${prefix}${style(message)}${suffix}`
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
static info(message: string, options?: LogOptions) {
|
|
41
|
-
let chalkMethod = chalk.blue
|
|
42
|
-
|
|
43
|
-
if (options?.color) {
|
|
44
|
-
chalkMethod = chalk[options.color.toLowerCase()] || chalk.blue
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (options?.timestamp) {
|
|
48
|
-
const timestamp = new Date().toLocaleTimeString()
|
|
49
|
-
console.debug(`${chalk.gray(`[${timestamp}]`)} ${chalkMethod(message)}`)
|
|
50
|
-
} else {
|
|
51
|
-
console.debug(chalkMethod(message))
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
static group(_message: string, _options?: LogOptions) {}
|
|
56
|
-
|
|
57
|
-
static groupEnd() {}
|
|
58
|
-
static newLine() {}
|
|
59
|
-
|
|
60
|
-
static success(message: string, options?: LogOptions) {
|
|
61
|
-
console.debug(this.formatMessage(message, 'success', options))
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
static warning(message: string, options?: LogOptions) {
|
|
65
|
-
console.debug(this.formatMessage(message, 'warning', options))
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
static error(message: string, options?: LogOptions) {
|
|
69
|
-
console.error(this.formatMessage(message, 'error', options))
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
static debug(message: string, options?: LogOptions) {
|
|
73
|
-
if (options?.prefix) {
|
|
74
|
-
console.debug(chalk.gray(`[${options.prefix}] ${message}`), options.error ? `\n${chalk.red(options.error)}` : '')
|
|
75
|
-
} else {
|
|
76
|
-
console.debug(chalk.gray(message), options?.error ? `\n${chalk.red(options.error)}` : '')
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
static step(_stepNumber: number, _message: string) {}
|
|
81
|
-
|
|
82
|
-
static header(message: string) {
|
|
83
|
-
console.debug()
|
|
84
|
-
console.debug(chalk.bold.cyan(`▶ ${message}`))
|
|
85
|
-
console.debug()
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
static table(data: Record<string, any>[], columns?: string[]) {
|
|
89
|
-
if (data.length === 0) {
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const columnsToShow = columns || Object.keys(data[0])
|
|
94
|
-
const _formattedData = data.map((item) => {
|
|
95
|
-
const formatted: Record<string, any> = {}
|
|
96
|
-
columnsToShow.forEach((col) => {
|
|
97
|
-
formatted[col] = item[col]
|
|
98
|
-
})
|
|
99
|
-
return formatted
|
|
100
|
-
})
|
|
101
|
-
// eslint-disable-next-line no-console
|
|
102
|
-
console.table(_formattedData, columnsToShow)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
static divider() {}
|
|
106
|
-
}
|
package/src/utils/templating.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { faker } from '@faker-js/faker'
|
|
2
|
-
import template from 'lodash/template.js'
|
|
3
|
-
import templateSettings from 'lodash/templateSettings.js'
|
|
4
|
-
|
|
5
|
-
templateSettings.interpolate = /{{([\s\S]+?)}}/g
|
|
6
|
-
|
|
7
|
-
function processNode(node: any, state: any): any {
|
|
8
|
-
if (typeof node === 'string') {
|
|
9
|
-
const compiled = template(node)
|
|
10
|
-
const templateData = {
|
|
11
|
-
state,
|
|
12
|
-
random: {
|
|
13
|
-
number: () => faker.number.int(),
|
|
14
|
-
alphaNumeric: (count: number) => faker.string.alphanumeric(count),
|
|
15
|
-
},
|
|
16
|
-
faker: {
|
|
17
|
-
company: {
|
|
18
|
-
name: () => faker.company.name(),
|
|
19
|
-
catchPhrase: () => faker.company.catchPhrase(),
|
|
20
|
-
},
|
|
21
|
-
internet: {
|
|
22
|
-
email: () => faker.internet.email(),
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
entries: state['journal-entries'],
|
|
26
|
-
orders: state['purchase-orders'] || state['sales-orders'],
|
|
27
|
-
bills: state['vendor-bills'],
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const result = compiled(templateData)
|
|
31
|
-
|
|
32
|
-
if (node.includes('{{') && node.includes('}}')) {
|
|
33
|
-
console.debug(`[TEMPLATE] Input: ${node}`)
|
|
34
|
-
console.debug(`[TEMPLATE] Output: ${result}`)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return result
|
|
38
|
-
}
|
|
39
|
-
if (Array.isArray(node)) {
|
|
40
|
-
return node.map((item) => processNode(item, state))
|
|
41
|
-
}
|
|
42
|
-
if (typeof node === 'object' && node !== null) {
|
|
43
|
-
return Object.fromEntries(Object.entries(node).map(([key, value]) => [key, processNode(value, state)]))
|
|
44
|
-
}
|
|
45
|
-
return node
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function handleTemplate(config: any, state: any) {
|
|
49
|
-
return processNode(config, state)
|
|
50
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"target": "ESNext",
|
|
5
|
-
"module": "ESNext",
|
|
6
|
-
"moduleResolution": "bundler",
|
|
7
|
-
"types": ["node", "vitest/globals"],
|
|
8
|
-
"allowImportingTsExtensions": true,
|
|
9
|
-
"allowJs": true,
|
|
10
|
-
"strict": true,
|
|
11
|
-
"noEmit": true,
|
|
12
|
-
"skipLibCheck": true,
|
|
13
|
-
"esModuleInterop": true,
|
|
14
|
-
"resolveJsonModule": true,
|
|
15
|
-
"jsx": "react-jsx",
|
|
16
|
-
"outDir": "dist",
|
|
17
|
-
"rootDir": "src"
|
|
18
|
-
},
|
|
19
|
-
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
|
20
|
-
"exclude": ["node_modules"]
|
|
21
|
-
}
|