Skip to content

Commit

Permalink
feat(cli): support multiple files on pull/push
Browse files Browse the repository at this point in the history
Support pulling/pushing multiple files at once with the
`@mermaidchart/cli` CLI, e.g. like `mermaid-chart pull file1 file2`.
  • Loading branch information
aloisklink committed Nov 28, 2023
1 parent 08f9cf9 commit dbf3c50
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 67 deletions.
44 changes: 29 additions & 15 deletions packages/cli/src/commander.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,13 @@ describe('link', () => {

describe('pull', () => {
const diagram = 'test/output/connected-diagram.mmd';
const diagram2 = 'test/output/connected-diagram-2.mmd';

beforeEach(async () => {
await copyFile('test/fixtures/connected-diagram.mmd', diagram);
await Promise.all([
copyFile('test/fixtures/connected-diagram.mmd', diagram),
copyFile('test/fixtures/connected-diagram.mmd', diagram2),
]);
});

it('should fail if MermaidChart document has not yet been linked', async () => {
Expand All @@ -230,7 +234,7 @@ describe('pull', () => {
program.parseAsync(['--config', CONFIG_AUTHED, 'pull', 'test/fixtures/unsynced.mmd'], {
from: 'user',
}),
).rejects.toThrowError('Diagram has no id');
).rejects.toThrowError(`Diagram at test/fixtures/unsynced.mmd has no id`);
});

it('should fail if MermaidChart document has no code', async () => {
Expand All @@ -240,10 +244,10 @@ describe('pull', () => {

await expect(
program.parseAsync(['--config', CONFIG_AUTHED, 'pull', diagram], { from: 'user' }),
).rejects.toThrowError('Diagram has no code');
).rejects.toThrowError(`Diagram at ${diagram} has no code`);
});

it('should pull document and add a `id:` field to frontmatter', async () => {
it('should pull documents and add a `id:` field to frontmatter', async () => {
const { program } = mockedProgram();

const mockedDiagram = {
Expand All @@ -255,22 +259,30 @@ title: My cool flowchart
A[I've been updated!]`,
};

vi.mocked(MermaidChart.prototype.getDocument).mockResolvedValueOnce(mockedDiagram);
vi.mocked(MermaidChart.prototype.getDocument).mockResolvedValue(mockedDiagram);

await program.parseAsync(['--config', CONFIG_AUTHED, 'pull', diagram], { from: 'user' });
await program.parseAsync(['--config', CONFIG_AUTHED, 'pull', diagram, diagram2], {
from: 'user',
});

const diagramContents = await readFile(diagram, { encoding: 'utf8' });
for (const file of [diagram, diagram2]) {
const diagramContents = await readFile(file, { encoding: 'utf8' });

expect(diagramContents).toContain(`id: ${mockedDiagram.documentID}`);
expect(diagramContents).toContain("flowchart TD\n A[I've been updated!]");
expect(diagramContents).toContain(`id: ${mockedDiagram.documentID}`);
expect(diagramContents).toContain("flowchart TD\n A[I've been updated!]");
}
});
});

describe('push', () => {
const diagram = 'test/output/connected-diagram.mmd';
const diagram2 = 'test/output/connected-diagram-2.mmd';

beforeEach(async () => {
await copyFile('test/fixtures/connected-diagram.mmd', diagram);
await Promise.all([
copyFile('test/fixtures/connected-diagram.mmd', diagram),
copyFile('test/fixtures/connected-diagram.mmd', diagram2),
]);
});

it('should fail if MermaidChart document has not yet been linked', async () => {
Expand All @@ -280,19 +292,21 @@ describe('push', () => {
program.parseAsync(['--config', CONFIG_AUTHED, 'push', 'test/fixtures/unsynced.mmd'], {
from: 'user',
}),
).rejects.toThrowError('Diagram has no id');
).rejects.toThrowError(`Diagram at test/fixtures/unsynced.mmd has no id`);
});

it('should push document and remove the `id:` field front frontmatter', async () => {
it('should push documents and remove the `id:` field front frontmatter', async () => {
const { program } = mockedProgram();

vi.mocked(MermaidChart.prototype.getDocument).mockResolvedValueOnce(mockedEmptyDiagram);
vi.mocked(MermaidChart.prototype.getDocument).mockResolvedValue(mockedEmptyDiagram);

await expect(readFile(diagram, { encoding: 'utf8' })).resolves.not.toContain(/^id:/);

await program.parseAsync(['--config', CONFIG_AUTHED, 'push', diagram], { from: 'user' });
await program.parseAsync(['--config', CONFIG_AUTHED, 'push', diagram, diagram2], {
from: 'user',
});

expect(vi.mocked(MermaidChart.prototype.setDocument)).toHaveBeenCalledOnce();
expect(vi.mocked(MermaidChart.prototype.setDocument)).toHaveBeenCalledTimes(2);
expect(vi.mocked(MermaidChart.prototype.setDocument)).toHaveBeenCalledWith(
expect.objectContaining({
code: expect.not.stringContaining('id:'),
Expand Down
114 changes: 62 additions & 52 deletions packages/cli/src/commander.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,75 +209,85 @@ function link() {

function pull() {
return createCommand('pull')
.description('Pulls a document from from Mermaid Chart')
.addArgument(new Argument('<path>', 'The path of the file to pull.'))
.option('--check', 'Check whether the local file would be overwrited')
.action(async (path, options, command) => {
.description('Pulls documents from Mermaid Chart')
.addArgument(new Argument('<path...>', 'The paths of the files to pull.'))
.option('--check', 'Check whether the local files would be overwrited')
.action(async (paths, options, command) => {
const optsWithGlobals = command.optsWithGlobals<CommonOptions>();
const client = await createClient(optsWithGlobals);
const text = await readFile(path, { encoding: 'utf8' });
const frontmatter = extractFrontMatter(text);

if (frontmatter.metadata.id === undefined) {
throw new Error('Diagram has no id, have you run `link` yet?');
}
await Promise.all(
paths.map(async (path) => {
const text = await readFile(path, { encoding: 'utf8' });
const frontmatter = extractFrontMatter(text);

const uploadedFile = await client.getDocument({
documentID: frontmatter.metadata.id,
});
if (frontmatter.metadata.id === undefined) {
throw new Error(`Diagram at ${path} has no id, have you run \`link\` yet?`);
}

if (uploadedFile.code === undefined) {
throw new Error('Diagram has no code, please use push first');
}
const uploadedFile = await client.getDocument({
documentID: frontmatter.metadata.id,
});

const newFile = injectFrontMatter(uploadedFile.code, { id: frontmatter.metadata.id });
if (uploadedFile.code === undefined) {
throw new Error(`Diagram at ${path} has no code, please use \`push\` first.`);
}

if (text === newFile) {
console.log(`✅ - ${path} is up to date`);
} else {
if (options['check']) {
console.log(`❌ - ${path} would be updated`);
process.exitCode = 1;
} else {
await writeFile(path, newFile, { encoding: 'utf8' });
console.log(`✅ - ${path} was updated`);
}
}
const newFile = injectFrontMatter(uploadedFile.code, { id: frontmatter.metadata.id });

if (text === newFile) {
console.log(`✅ - ${path} is up to date`);

Check warning on line 239 in packages/cli/src/commander.ts

View workflow job for this annotation

GitHub Actions / test (18.18.x, cli)

Unexpected console statement
} else {
if (options['check']) {
console.log(`❌ - ${path} would be updated`);

Check warning on line 242 in packages/cli/src/commander.ts

View workflow job for this annotation

GitHub Actions / test (18.18.x, cli)

Unexpected console statement
process.exitCode = 1;
} else {
await writeFile(path, newFile, { encoding: 'utf8' });
console.log(`✅ - ${path} was updated`);

Check warning on line 246 in packages/cli/src/commander.ts

View workflow job for this annotation

GitHub Actions / test (18.18.x, cli)

Unexpected console statement
}
}
}),
);
});
}

function push() {
return createCommand('push')
.description('Push a local diagram to Mermaid Chart')
.addArgument(new Argument('<path>', 'The path of the file to push.'))
.action(async (path, _options, command) => {
.description('Push local diagrams to Mermaid Chart')
.addArgument(new Argument('<path...>', 'The paths of the files to push.'))
.action(async (paths, _options, command) => {
const optsWithGlobals = command.optsWithGlobals<CommonOptions>();
const client = await createClient(optsWithGlobals);
const text = await readFile(path, { encoding: 'utf8' });
const frontmatter = extractFrontMatter(text);

if (frontmatter.metadata.id === undefined) {
throw new Error('Diagram has no id, have you run `link` yet?');
}
await Promise.all(
paths.map(async (path) => {
const text = await readFile(path, { encoding: 'utf8' });
const frontmatter = extractFrontMatter(text);

// TODO: check if file has changed since last push and print a warning
const existingDiagram = await client.getDocument({
documentID: frontmatter.metadata.id,
});
if (frontmatter.metadata.id === undefined) {
throw new Error(`Diagram at ${path} has no id, have you run \`link\` yet?`);
}

// due to MC-1056, try to remove YAML frontmatter if we can
const diagramToUpload = removeFrontMatterKeys(text, new Set(['id']));

if (existingDiagram.code === diagramToUpload) {
console.log(`✅ - ${path} is up to date`);
} else {
await client.setDocument({
id: existingDiagram.id,
documentID: existingDiagram.documentID,
code: diagramToUpload,
});
console.log(`✅ - ${path} was pushed`);
}
// TODO: check if file has changed since last push and print a warning
const existingDiagram = await client.getDocument({
documentID: frontmatter.metadata.id,
});

// due to MC-1056, try to remove YAML frontmatter if we can
const diagramToUpload = removeFrontMatterKeys(text, new Set(['id']));

if (existingDiagram.code === diagramToUpload) {
console.log(`✅ - ${path} is up to date`);

Check warning on line 280 in packages/cli/src/commander.ts

View workflow job for this annotation

GitHub Actions / test (18.18.x, cli)

Unexpected console statement
} else {
await client.setDocument({
id: existingDiagram.id,
documentID: existingDiagram.documentID,
code: diagramToUpload,
});
console.log(`✅ - ${path} was pushed`);

Check warning on line 287 in packages/cli/src/commander.ts

View workflow job for this annotation

GitHub Actions / test (18.18.x, cli)

Unexpected console statement
}
}),
);
});
}

Expand Down

0 comments on commit dbf3c50

Please sign in to comment.