-
Notifications
You must be signed in to change notification settings - Fork 8
Allow workspace creation by name #244
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
e8d6bbe
0b4d8f2
69aaa25
ecab131
7fb27c8
4ee4c1f
f90b379
452f312
32126d7
da420ef
15840f0
38f5ef6
85b4301
3b936b5
dfa0e25
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| /* | ||
| Copyright 2026 Adobe. All rights reserved. | ||
| This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. You may obtain a copy | ||
| of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software distributed under | ||
| the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
| OF ANY KIND, either express or implied. See the License for the specific language | ||
| governing permissions and limitations under the License. | ||
| */ | ||
| const { Flags } = require('@oclif/core') | ||
| const ConsoleCommand = require('../index') | ||
|
|
||
| class CreateCommand extends ConsoleCommand { | ||
| async run () { | ||
| const { flags } = await this.parse(CreateCommand) | ||
| const orgId = flags.orgId || this.getConfig('org.id') | ||
| if (!orgId) { | ||
| this.log('You have not selected an Organization. Please select one first.') | ||
| this.printConsoleConfig() | ||
| this.exit(1) | ||
| } | ||
|
|
||
| const workspaceDetails = { | ||
| name: flags.name, | ||
| title: flags.title || flags.name | ||
| } | ||
|
|
||
| // Workspace name allows only alphanumeric values | ||
| if (!/^[a-zA-Z0-9]+$/.test(workspaceDetails.name)) { | ||
| this.error(`Workspace name ${workspaceDetails.name} is invalid. It should only contain alphanumeric values.`) | ||
| } | ||
| if (workspaceDetails.name.length < 3 || workspaceDetails.name.length > 45) { | ||
| this.error('Workspace name must be between 3 and 45 characters long.') | ||
| } | ||
|
|
||
| // Workspace title should only contain alphanumeric characters and spaces | ||
| if (!/^[a-zA-Z0-9 ]+$/.test(workspaceDetails.title)) { | ||
| this.error(`Workspace title ${workspaceDetails.title} is invalid. It should only contain alphanumeric characters and spaces.`) | ||
| } | ||
| if (workspaceDetails.title.length < 3 || workspaceDetails.title.length > 45) { | ||
| this.error('Workspace title must be between 3 and 45 characters long.') | ||
| } | ||
|
|
||
| await this.initSdk() | ||
|
|
||
| try { | ||
| // resolve project by name or title to project id | ||
| const projects = await this.consoleCLI.getProjects(orgId) | ||
| const project = projects.find(p => p.name === flags.projectName || p.title === flags.projectName) | ||
| if (!project) { | ||
| this.error(`Project ${flags.projectName} not found in the Organization.`) | ||
| } | ||
| const projectId = project.id | ||
|
|
||
| const workspaces = await this.consoleCLI.getWorkspaces(orgId, projectId) | ||
| if (workspaces.find(ws => ws.name === workspaceDetails.name)) { | ||
| this.error(`Workspace ${workspaceDetails.name} already exists. Please choose a different name.`) | ||
| } | ||
|
|
||
| const workspace = await this.consoleCLI.createWorkspace(orgId, projectId, workspaceDetails) | ||
| if (flags.json) { | ||
| this.printJson(workspace) | ||
| } else if (flags.yml) { | ||
| this.printYaml(workspace) | ||
| } else { | ||
| this.log(`Workspace ${workspace.name} created successfully.`) | ||
| } | ||
| return workspace | ||
| } catch (err) { | ||
| this.error(err.message) | ||
| } finally { | ||
| this.cleanOutput() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| CreateCommand.description = 'Create a new Workspace in the specified Project' | ||
|
|
||
| CreateCommand.flags = { | ||
| ...ConsoleCommand.flags, | ||
| orgId: Flags.string({ | ||
| description: 'OrgID of the organization that contains the project to create the workspace in' | ||
| }), | ||
| projectName: Flags.string({ | ||
| description: 'Name or title of the project to create the workspace in', | ||
| required: true | ||
| }), | ||
| name: Flags.string({ | ||
| description: 'Name of the workspace', | ||
| required: true | ||
| }), | ||
| title: Flags.string({ | ||
| description: 'Title of the workspace, defaults to the name' | ||
| }), | ||
| json: Flags.boolean({ | ||
| description: 'Output json', | ||
| char: 'j', | ||
| exclusive: ['yml'] | ||
| }), | ||
| yml: Flags.boolean({ | ||
| description: 'Output yml', | ||
| char: 'y', | ||
| exclusive: ['json'] | ||
| }) | ||
| } | ||
|
|
||
| CreateCommand.aliases = [ | ||
| 'console:workspace:create', | ||
| 'console:workspace:init', | ||
| 'console:ws:create', | ||
| 'console:ws:init' | ||
| ] | ||
|
|
||
| module.exports = CreateCommand | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,213 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
| /* | ||||||||||||||||||||||||||||||||||||||||||||||||
| Copyright 2026 Adobe. All rights reserved. | ||||||||||||||||||||||||||||||||||||||||||||||||
| This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||||||||||||||||||||||||||||||||||||||||||||||||
| you may not use this file except in compliance with the License. You may obtain a copy | ||||||||||||||||||||||||||||||||||||||||||||||||
| of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| Unless required by applicable law or agreed to in writing, software distributed under | ||||||||||||||||||||||||||||||||||||||||||||||||
| the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||||||||||||||||||||||||||||||||||||||||||||||||
| OF ANY KIND, either express or implied. See the License for the specific language | ||||||||||||||||||||||||||||||||||||||||||||||||
| governing permissions and limitations under the License. | ||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const mockProject = { | ||||||||||||||||||||||||||||||||||||||||||||||||
| id: '9999', | ||||||||||||||||||||||||||||||||||||||||||||||||
| name: 'myproject', | ||||||||||||||||||||||||||||||||||||||||||||||||
| title: 'My Project' | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const mockWorkspace = { | ||||||||||||||||||||||||||||||||||||||||||||||||
| id: '1000000001', | ||||||||||||||||||||||||||||||||||||||||||||||||
| name: 'TestWorkspace', | ||||||||||||||||||||||||||||||||||||||||||||||||
| title: 'Test Workspace', | ||||||||||||||||||||||||||||||||||||||||||||||||
| enabled: 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const mockConsoleCLIInstance = { | ||||||||||||||||||||||||||||||||||||||||||||||||
| getProjects: jest.fn().mockResolvedValue([mockProject]), | ||||||||||||||||||||||||||||||||||||||||||||||||
| getWorkspaces: jest.fn().mockResolvedValue([]), | ||||||||||||||||||||||||||||||||||||||||||||||||
| createWorkspace: jest.fn().mockResolvedValue(mockWorkspace) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| jest.mock('@adobe/aio-cli-lib-console', () => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| init: jest.fn().mockResolvedValue(mockConsoleCLIInstance), | ||||||||||||||||||||||||||||||||||||||||||||||||
| cleanStdOut: jest.fn() | ||||||||||||||||||||||||||||||||||||||||||||||||
| })) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const TheCommand = require('../../../../src/commands/console/workspace/create') | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| describe('console:workspace:create', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| let command | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| beforeEach(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| command = new TheCommand([]) | ||||||||||||||||||||||||||||||||||||||||||||||||
| mockConsoleCLIInstance.createWorkspace.mockReset() | ||||||||||||||||||||||||||||||||||||||||||||||||
| mockConsoleCLIInstance.createWorkspace.mockResolvedValue(mockWorkspace) | ||||||||||||||||||||||||||||||||||||||||||||||||
| mockConsoleCLIInstance.getWorkspaces.mockReset() | ||||||||||||||||||||||||||||||||||||||||||||||||
| mockConsoleCLIInstance.getWorkspaces.mockResolvedValue([]) | ||||||||||||||||||||||||||||||||||||||||||||||||
| mockConsoleCLIInstance.getProjects.mockReset() | ||||||||||||||||||||||||||||||||||||||||||||||||
| mockConsoleCLIInstance.getProjects.mockResolvedValue([mockProject]) | ||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| afterEach(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| command = null | ||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| it('should create a workspace', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| command.argv = ['--name', 'testworkspace', '--title', 'Test Workspace', '--projectName', 'myproject', '--orgId', '1234567890'] | ||||||||||||||||||||||||||||||||||||||||||||||||
| const result = await command.run() | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(mockConsoleCLIInstance.getProjects).toHaveBeenCalledWith('1234567890') | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(mockConsoleCLIInstance.createWorkspace).toHaveBeenCalledWith('1234567890', '9999', { | ||||||||||||||||||||||||||||||||||||||||||||||||
| name: 'testworkspace', | ||||||||||||||||||||||||||||||||||||||||||||||||
| title: 'Test Workspace' | ||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(result).toEqual(mockWorkspace) | ||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
purplecabbage marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||
| it('should output JSON when --json is used', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const stdoutSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true) | ||||||||||||||||||||||||||||||||||||||||||||||||
| const stderrSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||
| command.argv = ['--name', 'testworkspace', '--title', 'Test Workspace', '--projectName', 'myproject', '--orgId', '1234567890', '--json'] | ||||||||||||||||||||||||||||||||||||||||||||||||
| await command.run() | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const stdout = stdoutSpy.mock.calls.map(args => args[0]).join('') | ||||||||||||||||||||||||||||||||||||||||||||||||
| const stderr = stderrSpy.mock.calls.map(args => args[0]).join('') | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| expect(stderr).toBe('') | ||||||||||||||||||||||||||||||||||||||||||||||||
| let parsed | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(() => { parsed = JSON.parse(stdout) }).not.toThrow() | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(parsed).toMatchObject(mockWorkspace) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||||||||||||||
| stdoutSpy.mockRestore() | ||||||||||||||||||||||||||||||||||||||||||||||||
| stderrSpy.mockRestore() | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| it('should output YAML when --yml is used', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const stdoutSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true) | ||||||||||||||||||||||||||||||||||||||||||||||||
| const stderrSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||
| command.argv = ['--name', 'testworkspace', '--title', 'Test Workspace', '--projectName', 'myproject', '--orgId', '1234567890', '--yml'] | ||||||||||||||||||||||||||||||||||||||||||||||||
| await command.run() | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const stdout = stdoutSpy.mock.calls.map(args => args[0]).join('') | ||||||||||||||||||||||||||||||||||||||||||||||||
| const stderr = stderrSpy.mock.calls.map(args => args[0]).join('') | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| expect(stderr).toBe('') | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(stdout).toContain('id:') | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(stdout).toContain('name:') | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(stdout).toContain('title:') | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(stdout).toContain('Test Workspace') | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(stdout).toContain('TestWorkspace') | ||||||||||||||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||||||||||||||
| stdoutSpy.mockRestore() | ||||||||||||||||||||||||||||||||||||||||||||||||
| stderrSpy.mockRestore() | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||
| it('should not create a workspace if the name is not provided', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| command.argv = ['--projectName', 'myproject', '--orgId', '1234567890'] | ||||||||||||||||||||||||||||||||||||||||||||||||
| await expect(command.run()).rejects.toThrow('Missing required flag name') | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(mockConsoleCLIInstance.createWorkspace).not.toHaveBeenCalled() | ||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| it('should not create a workspace if the projectName is not provided', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| command.argv = ['--name', 'testworkspace', '--orgId', '1234567890'] | ||||||||||||||||||||||||||||||||||||||||||||||||
| await expect(command.run()).rejects.toThrow('Missing required flag projectName') | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(mockConsoleCLIInstance.createWorkspace).not.toHaveBeenCalled() | ||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| it('should not create a workspace if the orgId is not provided and no config', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| command.argv = ['--name', 'testworkspace', '--projectName', 'myproject'] | ||||||||||||||||||||||||||||||||||||||||||||||||
| command.getConfig = jest.fn().mockReturnValue(null) | ||||||||||||||||||||||||||||||||||||||||||||||||
| await expect(command.run()).rejects.toThrow('You have not selected an Organization. Please select first.') | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(mockConsoleCLIInstance.createWorkspace).not.toHaveBeenCalled() | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+122
to
+125
|
||||||||||||||||||||||||||||||||||||||||||||||||
| command.argv = ['--name', 'testworkspace', '--projectName', 'myproject'] | |
| command.getConfig = jest.fn().mockReturnValue(null) | |
| await expect(command.run()).rejects.toThrow('You have not selected an Organization. Please select first.') | |
| expect(mockConsoleCLIInstance.createWorkspace).not.toHaveBeenCalled() | |
| const stdoutSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true) | |
| const stderrSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true) | |
| try { | |
| command.argv = ['--name', 'testworkspace', '--projectName', 'myproject'] | |
| command.getConfig = jest.fn().mockReturnValue(null) | |
| await expect(command.run()).rejects.toThrow() | |
| const stdout = stdoutSpy.mock.calls.map(args => args[0]).join('') | |
| const stderr = stderrSpy.mock.calls.map(args => args[0]).join('') | |
| const combinedOutput = stdout + stderr | |
| expect(combinedOutput).toContain('You have not selected an Organization. Please select first.') | |
| expect(mockConsoleCLIInstance.createWorkspace).not.toHaveBeenCalled() | |
| } finally { | |
| stdoutSpy.mockRestore() | |
| stderrSpy.mockRestore() | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CreateCommand.aliases includes 'console:workspace:create', which is already the command’s canonical id derived from the file path. Other commands only list alternate spellings (e.g. :ls, :sel, :init). Consider removing the canonical name from aliases to avoid redundancy/possible registration conflicts.