infra-kit 0.1.57 → 0.1.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +15 -14
- package/dist/cli.js.map +4 -4
- package/dist/mcp.js +15 -14
- package/dist/mcp.js.map +4 -4
- package/package.json +7 -6
- package/src/commands/{gh-release-deploy/gh-release-deploy.ts → gh-release-deploy-all/gh-release-deploy-all.ts} +5 -5
- package/src/commands/gh-release-deploy-all/index.ts +1 -0
- package/src/commands/gh-release-deploy-service/gh-release-deploy-service.ts +159 -0
- package/src/commands/gh-release-deploy-service/index.ts +1 -0
- package/src/commands/release-create/release-create.ts +21 -3
- package/src/commands/release-create-batch/release-create-batch.ts +42 -10
- package/src/entry/cli.ts +14 -3
- package/src/lib/git-utils/git-utils.ts +1 -0
- package/src/lib/load-env/load-env.ts +4 -4
- package/src/mcp/tools/index.ts +4 -2
- package/src/commands/gh-release-deploy/index.ts +0 -1
|
@@ -8,7 +8,7 @@ import { ENVs } from 'src/lib/constants'
|
|
|
8
8
|
import { logger } from 'src/lib/logger'
|
|
9
9
|
import type { ToolsExecutionResult } from 'src/types'
|
|
10
10
|
|
|
11
|
-
interface
|
|
11
|
+
interface GhReleaseDeployAllArgs {
|
|
12
12
|
version: string
|
|
13
13
|
env: string
|
|
14
14
|
}
|
|
@@ -16,7 +16,7 @@ interface GhReleaseDeployArgs {
|
|
|
16
16
|
/**
|
|
17
17
|
* Deploy a release branch to an environment
|
|
18
18
|
*/
|
|
19
|
-
export const
|
|
19
|
+
export const ghReleaseDeployAll = async (args: GhReleaseDeployAllArgs): Promise<ToolsExecutionResult> => {
|
|
20
20
|
const { version, env } = args
|
|
21
21
|
|
|
22
22
|
// TODO: add validation for semver version for version variable
|
|
@@ -100,8 +100,8 @@ export const ghReleaseDeploy = async (args: GhReleaseDeployArgs): Promise<ToolsE
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
// MCP Tool Registration
|
|
103
|
-
export const
|
|
104
|
-
name: 'gh-release-deploy',
|
|
103
|
+
export const ghReleaseDeployAllMcpTool = {
|
|
104
|
+
name: 'gh-release-deploy-all',
|
|
105
105
|
description: 'Deploy a release branch to a specified environment',
|
|
106
106
|
inputSchema: {
|
|
107
107
|
version: z.string().describe('Version to deploy (e.g., "1.2.5")'),
|
|
@@ -113,5 +113,5 @@ export const ghReleaseDeployMcpTool = {
|
|
|
113
113
|
environment: z.string().describe('The environment deployed to'),
|
|
114
114
|
success: z.boolean().describe('Whether the deployment was successful'),
|
|
115
115
|
},
|
|
116
|
-
handler:
|
|
116
|
+
handler: ghReleaseDeployAll,
|
|
117
117
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ghReleaseDeployAll, ghReleaseDeployAllMcpTool } from './gh-release-deploy-all'
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import select from '@inquirer/select'
|
|
2
|
+
import fs from 'node:fs/promises'
|
|
3
|
+
import { resolve } from 'node:path'
|
|
4
|
+
import process from 'node:process'
|
|
5
|
+
import yaml from 'yaml'
|
|
6
|
+
import { z } from 'zod'
|
|
7
|
+
import { $ } from 'zx'
|
|
8
|
+
|
|
9
|
+
import { getReleasePRs } from 'src/integrations/gh'
|
|
10
|
+
import { ENVs } from 'src/lib/constants'
|
|
11
|
+
import { getProjectRoot } from 'src/lib/git-utils'
|
|
12
|
+
import { logger } from 'src/lib/logger'
|
|
13
|
+
import type { ToolsExecutionResult } from 'src/types'
|
|
14
|
+
|
|
15
|
+
interface GhReleaseDeployServiceArgs {
|
|
16
|
+
version: string
|
|
17
|
+
env: string
|
|
18
|
+
service: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Deploy a specific service in a release branch to an environment
|
|
23
|
+
*/
|
|
24
|
+
export const ghReleaseDeployService = async (args: GhReleaseDeployServiceArgs): Promise<ToolsExecutionResult> => {
|
|
25
|
+
const { version, env, service } = args
|
|
26
|
+
|
|
27
|
+
// TODO: add validation for semver version for version variable
|
|
28
|
+
|
|
29
|
+
const releasePRsList: string[] = ['dev'] // ["release/v1.8.0", "release/v1.9.0"]
|
|
30
|
+
|
|
31
|
+
const releasePRs = await getReleasePRs()
|
|
32
|
+
|
|
33
|
+
releasePRsList.push(...releasePRs)
|
|
34
|
+
|
|
35
|
+
let selectedReleaseBranch = '' // "release/v1.8.0"
|
|
36
|
+
|
|
37
|
+
if (version) {
|
|
38
|
+
selectedReleaseBranch = `release/v${version}`
|
|
39
|
+
} else {
|
|
40
|
+
selectedReleaseBranch = await select({
|
|
41
|
+
message: '🌿 Select release branch',
|
|
42
|
+
choices: releasePRsList.map((pr) => ({
|
|
43
|
+
name: pr.replace('release/v', ''),
|
|
44
|
+
value: pr,
|
|
45
|
+
})),
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check if release branch exists in the list
|
|
50
|
+
if (!releasePRsList.includes(selectedReleaseBranch)) {
|
|
51
|
+
logger.error(`❌ Release branch ${selectedReleaseBranch} not found in open PRs. Exiting...`)
|
|
52
|
+
process.exit(1)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let selectedEnv = ''
|
|
56
|
+
|
|
57
|
+
if (env) {
|
|
58
|
+
selectedEnv = env
|
|
59
|
+
} else {
|
|
60
|
+
selectedEnv = await select({
|
|
61
|
+
message: '🧪 Select environment',
|
|
62
|
+
choices: ENVs.map((env) => ({
|
|
63
|
+
name: env,
|
|
64
|
+
value: env,
|
|
65
|
+
})),
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!ENVs.includes(selectedEnv)) {
|
|
70
|
+
logger.error(`❌ Invalid environment: ${selectedEnv}. Exiting...`)
|
|
71
|
+
process.exit(1)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Parse available services from workflow file
|
|
75
|
+
const availableServices = await parseServicesFromWorkflow()
|
|
76
|
+
|
|
77
|
+
let selectedService = ''
|
|
78
|
+
|
|
79
|
+
if (service) {
|
|
80
|
+
selectedService = service
|
|
81
|
+
} else {
|
|
82
|
+
selectedService = await select({
|
|
83
|
+
message: '🚀 Select service to deploy',
|
|
84
|
+
choices: availableServices.map((svc) => ({
|
|
85
|
+
name: svc,
|
|
86
|
+
value: svc,
|
|
87
|
+
})),
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!availableServices.includes(selectedService)) {
|
|
92
|
+
logger.error(`❌ Invalid service: ${selectedService}. Available services: ${availableServices.join(', ')}`)
|
|
93
|
+
process.exit(1)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
$.quiet = true
|
|
98
|
+
|
|
99
|
+
await $`gh workflow run deploy-single-service.yml --ref ${selectedReleaseBranch} -f environment=${selectedEnv} -f service=${selectedService}`
|
|
100
|
+
|
|
101
|
+
$.quiet = false
|
|
102
|
+
|
|
103
|
+
logger.info(
|
|
104
|
+
`Successfully launched deploy-single-service workflow_dispatch for release branch: ${selectedReleaseBranch}, environment: ${selectedEnv}, service: ${selectedService}`,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
const structuredContent = {
|
|
108
|
+
releaseBranch: selectedReleaseBranch,
|
|
109
|
+
version: selectedReleaseBranch.replace('release/v', ''),
|
|
110
|
+
environment: selectedEnv,
|
|
111
|
+
service: selectedService,
|
|
112
|
+
success: true,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
content: [
|
|
117
|
+
{
|
|
118
|
+
type: 'text',
|
|
119
|
+
text: JSON.stringify(structuredContent, null, 2),
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
structuredContent,
|
|
123
|
+
}
|
|
124
|
+
} catch (error: unknown) {
|
|
125
|
+
logger.error({ error }, '❌ Error launching workflow')
|
|
126
|
+
process.exit(1)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Parse available services from the workflow file
|
|
132
|
+
*/
|
|
133
|
+
const parseServicesFromWorkflow = async (): Promise<string[]> => {
|
|
134
|
+
const projectRoot = await getProjectRoot()
|
|
135
|
+
const workflowPath = resolve(projectRoot, '.github/workflows/deploy-single-service.yml')
|
|
136
|
+
const content = await fs.readFile(workflowPath, 'utf-8')
|
|
137
|
+
const parsed = yaml.parse(content)
|
|
138
|
+
|
|
139
|
+
return parsed.on.workflow_dispatch.inputs.service.options
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// MCP Tool Registration
|
|
143
|
+
export const ghReleaseDeployServiceMcpTool = {
|
|
144
|
+
name: 'gh-release-deploy-service',
|
|
145
|
+
description: 'Deploy a specific service in a release branch to a specified environment',
|
|
146
|
+
inputSchema: {
|
|
147
|
+
version: z.string().describe('Version to deploy (e.g., "1.2.5")'),
|
|
148
|
+
env: z.string().describe('Environment to deploy to (e.g., "dev", "renana", "oriana")'),
|
|
149
|
+
service: z.string().describe('Service to deploy (e.g., "client-be", "client-fe")'),
|
|
150
|
+
},
|
|
151
|
+
outputSchema: {
|
|
152
|
+
releaseBranch: z.string().describe('The release branch that was deployed'),
|
|
153
|
+
version: z.string().describe('The version that was deployed'),
|
|
154
|
+
environment: z.string().describe('The environment deployed to'),
|
|
155
|
+
service: z.string().describe('The service that was deployed'),
|
|
156
|
+
success: z.boolean().describe('Whether the deployment was successful'),
|
|
157
|
+
},
|
|
158
|
+
handler: ghReleaseDeployService,
|
|
159
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ghReleaseDeployService, ghReleaseDeployServiceMcpTool } from './gh-release-deploy-service'
|
|
@@ -9,7 +9,7 @@ import { createSingleRelease, prepareGitForRelease } from 'src/lib/release-utils
|
|
|
9
9
|
import type { RequiredConfirmedOptionArg, ToolsExecutionResult } from 'src/types'
|
|
10
10
|
|
|
11
11
|
interface ReleaseCreateArgs extends RequiredConfirmedOptionArg {
|
|
12
|
-
version
|
|
12
|
+
version?: string
|
|
13
13
|
description?: string
|
|
14
14
|
checkout?: boolean
|
|
15
15
|
}
|
|
@@ -19,9 +19,11 @@ interface ReleaseCreateArgs extends RequiredConfirmedOptionArg {
|
|
|
19
19
|
* Includes Jira version creation and GitHub release branch creation
|
|
20
20
|
*/
|
|
21
21
|
export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecutionResult> => {
|
|
22
|
-
const { version: inputVersion, description, confirmedCommand, checkout
|
|
22
|
+
const { version: inputVersion, description: inputDescription, confirmedCommand, checkout: inputCheckout } = args
|
|
23
23
|
|
|
24
24
|
let version = inputVersion
|
|
25
|
+
let description = inputDescription
|
|
26
|
+
let checkout = inputCheckout
|
|
25
27
|
|
|
26
28
|
// Load Jira config - it is now mandatory
|
|
27
29
|
const jiraConfig = await loadJiraConfig()
|
|
@@ -30,7 +32,7 @@ export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecu
|
|
|
30
32
|
version = await question('Enter version (e.g. 1.2.5): ')
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
// Validate input
|
|
35
|
+
// Validate input (validate the version is a valid semver)
|
|
34
36
|
if (!version || version.trim() === '') {
|
|
35
37
|
logger.error('No version provided. Exiting...')
|
|
36
38
|
process.exit(1)
|
|
@@ -38,6 +40,14 @@ export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecu
|
|
|
38
40
|
|
|
39
41
|
const trimmedVersion = version.trim()
|
|
40
42
|
|
|
43
|
+
if (description === undefined) {
|
|
44
|
+
description = await question('Enter description (optional, press Enter to skip): ')
|
|
45
|
+
|
|
46
|
+
if (description.trim() === '') {
|
|
47
|
+
description = ''
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
41
51
|
const answer = confirmedCommand
|
|
42
52
|
? true
|
|
43
53
|
: await confirm({
|
|
@@ -58,6 +68,14 @@ export const releaseCreate = async (args: ReleaseCreateArgs): Promise<ToolsExecu
|
|
|
58
68
|
logger.info(`🔗 GitHub PR: ${release.prUrl}`)
|
|
59
69
|
logger.info(`🔗 Jira Version: ${release.jiraVersionUrl}`)
|
|
60
70
|
|
|
71
|
+
// Ask about checkout if not specified
|
|
72
|
+
if (checkout === undefined) {
|
|
73
|
+
checkout = await confirm({
|
|
74
|
+
message: `Do you want to checkout to the created branch ${release.branchName}?`,
|
|
75
|
+
default: true,
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
61
79
|
// Checkout to the created branch by default
|
|
62
80
|
if (checkout) {
|
|
63
81
|
$.quiet = true
|
|
@@ -57,24 +57,47 @@ export const releaseCreateBatch = async (args: ReleaseCreateBatchArgs): Promise<
|
|
|
57
57
|
await prepareGitForRelease()
|
|
58
58
|
|
|
59
59
|
const releases: ReleaseCreationResult[] = []
|
|
60
|
+
const failedReleases: Array<{ version: string; error: string }> = []
|
|
60
61
|
|
|
61
62
|
for (const version of versionsList) {
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
try {
|
|
64
|
+
// Create each release
|
|
65
|
+
const release = await createSingleRelease(version, jiraConfig, description)
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
releases.push(release)
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
logger.info(`✅ Successfully created release: v${version}`)
|
|
70
|
+
logger.info(`🔗 GitHub PR: ${release.prUrl}`)
|
|
71
|
+
logger.info(`🔗 Jira Version: ${release.jiraVersionUrl}\n`)
|
|
72
|
+
} catch (error) {
|
|
73
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
74
|
+
|
|
75
|
+
failedReleases.push({ version, error: errorMessage })
|
|
76
|
+
|
|
77
|
+
logger.error(`❌ Failed to create release: v${version}`)
|
|
78
|
+
logger.error(` Error: ${errorMessage}\n`)
|
|
79
|
+
}
|
|
70
80
|
}
|
|
71
81
|
|
|
72
|
-
|
|
82
|
+
// Final summary
|
|
83
|
+
const successCount = releases.length
|
|
84
|
+
const failureCount = failedReleases.length
|
|
85
|
+
|
|
86
|
+
if (successCount === versionsList.length) {
|
|
87
|
+
logger.info(`✅ All ${versionsList.length} release branches were created successfully.`)
|
|
88
|
+
} else if (successCount > 0) {
|
|
89
|
+
logger.warn(`⚠️ ${successCount} of ${versionsList.length} release branches were created successfully.`)
|
|
90
|
+
logger.warn(`❌ ${failureCount} release(s) failed.`)
|
|
91
|
+
} else {
|
|
92
|
+
logger.error(`❌ All ${versionsList.length} release branches failed to create.`)
|
|
93
|
+
}
|
|
73
94
|
|
|
74
95
|
const structuredContent = {
|
|
75
|
-
createdBranches:
|
|
76
|
-
|
|
96
|
+
createdBranches: releases.map((r) => r.branchName),
|
|
97
|
+
successCount,
|
|
98
|
+
failureCount,
|
|
77
99
|
releases,
|
|
100
|
+
failedReleases,
|
|
78
101
|
}
|
|
79
102
|
|
|
80
103
|
return {
|
|
@@ -98,7 +121,8 @@ export const releaseCreateBatchMcpTool = {
|
|
|
98
121
|
},
|
|
99
122
|
outputSchema: {
|
|
100
123
|
createdBranches: z.array(z.string()).describe('List of created release branches'),
|
|
101
|
-
|
|
124
|
+
successCount: z.number().describe('Number of releases created successfully'),
|
|
125
|
+
failureCount: z.number().describe('Number of releases that failed'),
|
|
102
126
|
releases: z
|
|
103
127
|
.array(
|
|
104
128
|
z.object({
|
|
@@ -109,6 +133,14 @@ export const releaseCreateBatchMcpTool = {
|
|
|
109
133
|
}),
|
|
110
134
|
)
|
|
111
135
|
.describe('Detailed information for each created release with URLs'),
|
|
136
|
+
failedReleases: z
|
|
137
|
+
.array(
|
|
138
|
+
z.object({
|
|
139
|
+
version: z.string().describe('Version number that failed'),
|
|
140
|
+
error: z.string().describe('Error message'),
|
|
141
|
+
}),
|
|
142
|
+
)
|
|
143
|
+
.describe('List of releases that failed with error messages'),
|
|
112
144
|
},
|
|
113
145
|
handler: releaseCreateBatch,
|
|
114
146
|
}
|
package/src/entry/cli.ts
CHANGED
|
@@ -3,7 +3,8 @@ import { Command } from 'commander'
|
|
|
3
3
|
// Commands
|
|
4
4
|
import { ghMergeDev } from 'src/commands/gh-merge-dev'
|
|
5
5
|
import { ghReleaseDeliver } from 'src/commands/gh-release-deliver'
|
|
6
|
-
import {
|
|
6
|
+
import { ghReleaseDeployAll } from 'src/commands/gh-release-deploy-all'
|
|
7
|
+
import { ghReleaseDeployService } from 'src/commands/gh-release-deploy-service'
|
|
7
8
|
import { ghReleaseList } from 'src/commands/gh-release-list'
|
|
8
9
|
import { releaseCreate } from 'src/commands/release-create'
|
|
9
10
|
import { releaseCreateBatch } from 'src/commands/release-create-batch'
|
|
@@ -67,12 +68,22 @@ program
|
|
|
67
68
|
})
|
|
68
69
|
|
|
69
70
|
program
|
|
70
|
-
.command('release-deploy')
|
|
71
|
+
.command('release-deploy-all')
|
|
71
72
|
.description('Deploy any release branch to any environment')
|
|
72
73
|
.option('-v, --version <version>', 'Specify the version to deploy, e.g. 1.2.5')
|
|
73
74
|
.option('-e, --env <env>', 'Specify the environment to deploy to, e.g. dev')
|
|
74
75
|
.action(async (options) => {
|
|
75
|
-
await
|
|
76
|
+
await ghReleaseDeployAll({ version: options.version, env: options.env })
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
program
|
|
80
|
+
.command('release-deploy-service')
|
|
81
|
+
.description('Deploy specific service in release branch to any environment')
|
|
82
|
+
.option('-v, --version <version>', 'Specify the version to deploy, e.g. 1.2.5')
|
|
83
|
+
.option('-e, --env <env>', 'Specify the environment to deploy to, e.g. dev')
|
|
84
|
+
.option('-s, --service <service>', 'Specify the service to deploy, e.g. client-be')
|
|
85
|
+
.action(async (options) => {
|
|
86
|
+
await ghReleaseDeployService({ version: options.version, env: options.env, service: options.service })
|
|
76
87
|
})
|
|
77
88
|
|
|
78
89
|
program
|
|
@@ -2,18 +2,18 @@ import { config } from 'dotenv'
|
|
|
2
2
|
import { resolve } from 'node:path'
|
|
3
3
|
import { $ } from 'zx'
|
|
4
4
|
|
|
5
|
+
import { getProjectRoot } from 'src/lib/git-utils'
|
|
5
6
|
import { logger } from 'src/lib/logger'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Load .env file from git repository root
|
|
9
|
-
* Uses
|
|
10
|
+
* Uses getProjectRoot to find the repository root, works regardless of where package is installed
|
|
10
11
|
*/
|
|
11
|
-
export async
|
|
12
|
+
export const loadEnvFromGitRoot = async (): Promise<void> => {
|
|
12
13
|
try {
|
|
13
14
|
$.quiet = true
|
|
14
15
|
|
|
15
|
-
const
|
|
16
|
-
const gitRoot = result.stdout.trim()
|
|
16
|
+
const gitRoot = await getProjectRoot()
|
|
17
17
|
|
|
18
18
|
config({ path: resolve(gitRoot, '.env'), quiet: true })
|
|
19
19
|
|
package/src/mcp/tools/index.ts
CHANGED
|
@@ -2,7 +2,8 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
|
2
2
|
|
|
3
3
|
import { ghMergeDevMcpTool } from 'src/commands/gh-merge-dev'
|
|
4
4
|
import { ghReleaseDeliverMcpTool } from 'src/commands/gh-release-deliver'
|
|
5
|
-
import {
|
|
5
|
+
import { ghReleaseDeployAllMcpTool } from 'src/commands/gh-release-deploy-all'
|
|
6
|
+
import { ghReleaseDeployServiceMcpTool } from 'src/commands/gh-release-deploy-service'
|
|
6
7
|
import { ghReleaseListMcpTool } from 'src/commands/gh-release-list'
|
|
7
8
|
import { releaseCreateMcpTool } from 'src/commands/release-create'
|
|
8
9
|
import { releaseCreateBatchMcpTool } from 'src/commands/release-create-batch'
|
|
@@ -17,7 +18,8 @@ const tools = [
|
|
|
17
18
|
releaseCreateMcpTool,
|
|
18
19
|
releaseCreateBatchMcpTool,
|
|
19
20
|
ghReleaseDeliverMcpTool,
|
|
20
|
-
|
|
21
|
+
ghReleaseDeployAllMcpTool,
|
|
22
|
+
ghReleaseDeployServiceMcpTool,
|
|
21
23
|
ghReleaseListMcpTool,
|
|
22
24
|
worktreesAddMcpTool,
|
|
23
25
|
worktreesListMcpTool,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { ghReleaseDeploy, ghReleaseDeployMcpTool } from './gh-release-deploy'
|