Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions bin/pos-cli-exec-graphql.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env node

const { program } = require('commander');
const prompts = require('prompts');
const Gateway = require('../lib/proxy');
const fetchAuthData = require('../lib/settings').fetchSettings;
const logger = require('../lib/logger');

const isProductionEnvironment = (environment) => {
return environment && (environment.toLowerCase().includes('prod') || environment.toLowerCase().includes('production'));
};

const confirmProductionExecution = async (environment) => {
logger.Warn(`WARNING: You are executing GraphQL on a production environment: ${environment}`);
logger.Warn('This could potentially modify production data or cause unintended side effects.');
logger.Warn('');

const response = await prompts({
type: 'confirm',
name: 'confirmed',
message: `Are you sure you want to continue executing on ${environment}?`,
initial: false
});

return response.confirmed;
};

program
.name('pos-cli exec graphql')
.argument('<environment>', 'name of environment. Example: staging')
.argument('<graphql>', 'graphql query to execute as string')
.action(async (environment, graphql) => {
const authData = fetchAuthData(environment, program);
const gateway = new Gateway(authData);

if (isProductionEnvironment(environment)) {
const confirmed = await confirmProductionExecution(environment);
if (!confirmed) {
logger.Info('Execution cancelled.');
process.exit(0);
}
}

try {
const response = await gateway.graph({ query: graphql });

if (response.errors) {
logger.Error(`GraphQL execution error: ${JSON.stringify(response.errors, null, 2)}`);
process.exit(1);
}

if (response.data) {
logger.Print(JSON.stringify(response, null, 2));
}
} catch (error) {
logger.Error(`Failed to execute graphql: ${error.message}`);
process.exit(1);
}
});

program.parse(process.argv);
61 changes: 61 additions & 0 deletions bin/pos-cli-exec-liquid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env node

const { program } = require('commander');
const prompts = require('prompts');
const Gateway = require('../lib/proxy');
const fetchAuthData = require('../lib/settings').fetchSettings;
const logger = require('../lib/logger');

const isProductionEnvironment = (environment) => {
return environment && (environment.toLowerCase().includes('prod') || environment.toLowerCase().includes('production'));
};

const confirmProductionExecution = async (environment) => {
logger.Warn(`WARNING: You are executing liquid code on a production environment: ${environment}`);
logger.Warn('This could potentially modify production data or cause unintended side effects.');
logger.Warn('');

const response = await prompts({
type: 'confirm',
name: 'confirmed',
message: `Are you sure you want to continue executing on ${environment}?`,
initial: false
});

return response.confirmed;
};

program
.name('pos-cli exec liquid')
.argument('<environment>', 'name of environment. Example: staging')
.argument('<code>', 'liquid code to execute as string')
.action(async (environment, code) => {
const authData = fetchAuthData(environment, program);
const gateway = new Gateway(authData);

if (isProductionEnvironment(environment)) {
const confirmed = await confirmProductionExecution(environment);
if (!confirmed) {
logger.Info('Execution cancelled.');
process.exit(0);
}
}

try {
const response = await gateway.liquid({ content: code });

if (response.error) {
logger.Error(`Liquid execution error: ${response.error}`);
process.exit(1);
}

if (response.result) {
logger.Print(response.result);
}
} catch (error) {
logger.Error(`Failed to execute liquid: ${error.message}`);
process.exit(1);
}
});

program.parse(process.argv);
9 changes: 9 additions & 0 deletions bin/pos-cli-exec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env node

const { program } = require('commander');

program
.name('pos-cli exec')
.command('liquid <environment> <code>', 'execute liquid code on instance')
.command('graphql <environment> <graphql>', 'execute graphql query on instance')
.parse(process.argv);
1 change: 1 addition & 0 deletions bin/pos-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ program
.command('data', 'export, import or clean data on instance')
.command('deploy <environment>', 'deploy code to environment').alias('d')
.command('env', 'manage environments')
.command('exec', 'execute code on instance')
.command('gui', 'gui for content editor, graphql, logs')
.command('generate', 'generates files')
.command('init', 'initialize directory structure')
Expand Down
2 changes: 1 addition & 1 deletion test/deploy.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ describe('Server errors', () => {
test('Error in form', async () => {
const { stderr } = await run('incorrect_form');
expect(stderr).toMatch(
'Unknown properties: hello. Available properties are: api_call_notifications, async_callback_actions, authorization_policies, body, callback_actions, default_payload, email_notifications, fields, flash_alert, flash_notice, live_reindex, metadata, name, redirect_to, request_allowed, resource, resource_owner, response_headers, return_to, sms_notifications, spam_protection.'
'Unknown properties in `form_configurations/hello.liquid`: hello. Available properties are: api_call_notifications, async_callback_actions, authorization_policies, body, callback_actions, default_payload, email_notifications, fields, flash_alert, flash_notice, live_reindex, metadata, name, redirect_to, request_allowed, resource, resource_owner, response_headers, return_to, sms_notifications, spam_protection.'
);
});

Expand Down
131 changes: 131 additions & 0 deletions test/exec-graphql.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
jest.mock('../lib/apiRequest', () => ({
apiRequest: jest.fn()
}));

const Gateway = require('../lib/proxy');

describe('Gateway graph method', () => {
const { apiRequest } = require('../lib/apiRequest');

test('calls apiRequest with correct parameters', async () => {
const mockResponse = {
"data": {
"records": {
"results": []
}
}
};
apiRequest.mockResolvedValue(mockResponse);

const gateway = new Gateway({ url: 'http://example.com', token: '1234', email: 'test@example.com' });
const query = '{ records(per_page: 20) { results { id } } }';
const result = await gateway.graph({ query });

expect(apiRequest).toHaveBeenCalledWith({
method: 'POST',
uri: 'http://example.com/api/graph',
json: { query },
forever: true,
request: expect.any(Function)
});
expect(result).toEqual(mockResponse);
});

test('handles graphql execution error', async () => {
const mockErrorResponse = {
errors: [
{
message: 'Syntax Error: Expected Name, found <EOF>',
locations: [{ line: 1, column: 40 }]
}
]
};
apiRequest.mockResolvedValue(mockErrorResponse);

const gateway = new Gateway({ url: 'http://example.com', token: '1234', email: 'test@example.com' });
const query = '{ records(per_page: 20) { results { id } '; // Missing closing brace
const result = await gateway.graph({ query });

expect(result).toEqual(mockErrorResponse);
});
});

describe('exec graphql CLI', () => {
const exec = require('./utils/exec');
const cliPath = require('./utils/cliPath');

const env = Object.assign(process.env, {
CI: true,
MPKIT_URL: 'http://example.com',
MPKIT_TOKEN: '1234',
MPKIT_EMAIL: 'foo@example.com'
});

test('requires graphql argument', async () => {
const { code, stderr } = await exec(`${cliPath} exec graphql staging`, { env });

expect(code).toEqual(1);
expect(stderr).toMatch("error: missing required argument 'graphql'");
});

test('cancels execution on production environment when user says no', async () => {
const { code, stdout, stderr } = await exec(`echo "n" | ${cliPath} exec graphql production "{ records { results { id } } }"`, { env });

expect(code).toEqual(0);
expect(stdout).toMatch('Execution cancelled.');
});

test('proceeds with execution on production environment when user confirms', async () => {
const { code, stdout, stderr } = await exec(`echo "y" | ${cliPath} exec graphql production "{ records { results { id } } }"`, { env });

// This will fail because the mock API isn't set up, but we want to check it doesn't cancel
expect(stdout).not.toMatch('Execution cancelled.');
expect(stderr).not.toMatch('Execution cancelled.');
});

test('does not prompt for non-production environments', async () => {
const { code, stdout, stderr } = await exec(`${cliPath} exec graphql staging "{ records { results { id } } }"`, { env });

expect(stdout).not.toMatch('WARNING: You are executing GraphQL on a production environment');
expect(stdout).not.toMatch('Execution cancelled.');
});
});

// Integration test - requires real platformOS instance
describe('exec graphql integration', () => {
const exec = require('./utils/exec');
const cliPath = require('./utils/cliPath');

// Only run if real credentials are available
const hasRealCredentials = process.env.MPKIT_URL &&
process.env.MPKIT_TOKEN &&
!process.env.MPKIT_URL.includes('example.com');

(hasRealCredentials ? test : test.skip)('executes graphql query on real instance', async () => {
const query = '{ records(per_page: 20) { results { id } } }';
const { stdout, stderr, code } = await exec(`${cliPath} exec graphql dev "${query}"`, {
env: process.env,
timeout: 30000
});

expect(code).toEqual(0);
expect(stderr).toBe('');

// Parse JSON response
const response = JSON.parse(stdout);
expect(response).toHaveProperty('data');
expect(response.data).toHaveProperty('records');
expect(Array.isArray(response.data.records.results)).toBe(true);
}, 30000);

(hasRealCredentials ? test : test.skip)('handles graphql syntax error on real instance', async () => {
const invalidQuery = '{ records(per_page: 20) { results { id } '; // Missing closing brace
const { stdout, stderr, code } = await exec(`${cliPath} exec graphql dev "${invalidQuery}"`, {
env: process.env,
timeout: 30000
});

expect(code).toEqual(1);
expect(stderr).toMatch('GraphQL execution error');
}, 30000);
});
Loading