Skip to content
Merged
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
45 changes: 45 additions & 0 deletions src/commands/changes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {red} from 'kleur';
import {logHelpChangesApply} from '../help/changes.apply.help';
import {logHelpChanges} from '../help/changes.help';
import {logHelpChangesList} from '../help/changes.list.help';
import {logHelpChangesReject} from '../help/changes.reject.help';
import {applyChanges} from '../services/changes/changes.apply.services';
import {listChanges} from '../services/changes/changes.list.services';
import {rejectChanges} from '../services/changes/changes.reject.services';

export const changes = async (args?: string[]) => {
const [subCommand] = args ?? [];

switch (subCommand) {
case 'list':
await listChanges(args);
break;
case 'apply':
await applyChanges(args);
break;
case 'reject':
await rejectChanges(args);
break;
default:
console.log(red('Unknown subcommand.'));
logHelpChanges();
}
};

export const helpChanges = (args?: string[]) => {
const [subCommand] = args ?? [];

switch (subCommand) {
case 'list':
logHelpChangesList(args);
break;
case 'apply':
logHelpChangesApply(args);
break;
case 'reject':
logHelpChangesReject(args);
break;
default:
logHelpChanges(args);
}
};
7 changes: 5 additions & 2 deletions src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,17 @@ export const deploy = async (args?: string[]) => {
};

const deployWithProposal = async ({args, clearOption}: {args?: string[]; clearOption: boolean}) => {
const noCommit = hasArgs({args, options: ['-n', '--no-commit']});
const noCommit = hasArgs({args, options: ['-n', '--no-apply']});

const deployFn = async ({
deploy,
satellite
}: DeployFnParams<UploadFileWithProposal>): Promise<DeployResultWithProposal> =>
await cliDeployWithProposal({
deploy,
deploy: {
...deploy,
includeAllFiles: clearOption
},
proposal: {
clearAssets: clearOption,
autoCommit: !noCommit,
Expand Down
5 changes: 5 additions & 0 deletions src/constants/help.constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {magenta} from 'kleur';

export const CHANGES_DESCRIPTION = 'Review and apply changes submitted to your module.';
export const CLEAR_DESCRIPTION =
'Clear existing app code by removing JavaScript, HTML, CSS, and other files from your satellite.';
export const CONFIG_DESCRIPTION = 'Apply configuration to satellite.';
Expand Down Expand Up @@ -30,3 +31,7 @@ export const DEV_BUILD_NOTES = `- If no language is provided, the CLI attempts t
- Language can be shortened to ${magenta('rs')} for Rust, ${magenta('ts')} for TypeScript and ${magenta('mjs')} for JavaScript.
- The path option maps to ${magenta('--manifest-path')} for Rust (Cargo) or to the source file for TypeScript and JavaScript (e.g. ${magenta('index.ts')} or ${magenta('index.mjs')}).
- The watch option rebuilds when source files change, with a default debounce delay of 10 seconds; optionally, pass a delay in milliseconds.`;

export const CHANGES_LIST_DESCRIPTION = 'List all submitted or applied changes.';
export const CHANGES_APPLY_DESCRIPTION = 'Apply a submitted change.';
export const CHANGES_REJECT_DESCRIPTION = 'Reject a change.';
29 changes: 29 additions & 0 deletions src/help/changes.apply.help.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {cyan, green, magenta, yellow} from 'kleur';
import {CHANGES_APPLY_DESCRIPTION} from '../constants/help.constants';
import {helpOutput} from './common.help';
import {TITLE} from './help';

const usage = `Usage: ${green('juno')} ${cyan('changes')} ${magenta('apply')} ${yellow('[options]')}

Options:
${yellow('-i, --id')} The ID of the change to apply.
${yellow('-s, --hash')} The expected hash of all included changes (for verification).
${yellow('-h, --help')} Output usage information.`;

const doc = `${CHANGES_APPLY_DESCRIPTION}

\`\`\`
${usage}
\`\`\`
`;

const help = `${TITLE}

${CHANGES_APPLY_DESCRIPTION}

${usage}
`;

export const logHelpChangesApply = (args?: string[]) => {
console.log(helpOutput(args) === 'doc' ? doc : help);
};
38 changes: 38 additions & 0 deletions src/help/changes.help.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {cyan, green, magenta, yellow} from 'kleur';
import {
CHANGES_APPLY_DESCRIPTION,
CHANGES_DESCRIPTION,
CHANGES_LIST_DESCRIPTION,
CHANGES_REJECT_DESCRIPTION
} from '../constants/help.constants';
import {helpOutput} from './common.help';
import {TITLE} from './help';

const helpChangesList = `${magenta('list')} ${CHANGES_LIST_DESCRIPTION}`;
const helpChangesApply = `${magenta('apply')} ${CHANGES_APPLY_DESCRIPTION}`;
const helpChangesReject = `${magenta('reject')} ${CHANGES_REJECT_DESCRIPTION}`;

const usage = `Usage: ${green('juno')} ${cyan('changes')} ${magenta('<subcommand>')} ${yellow('[options]')}

Subcommands:
${helpChangesApply}
${helpChangesList}
${helpChangesReject}`;

const doc = `${CHANGES_DESCRIPTION}

\`\`\`
${usage}
\`\`\`
`;

const help = `${TITLE}

${CHANGES_DESCRIPTION}

${usage}
`;

export const logHelpChanges = (args?: string[]) => {
console.log(helpOutput(args) === 'doc' ? doc : help);
};
29 changes: 29 additions & 0 deletions src/help/changes.list.help.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {cyan, green, magenta, yellow} from 'kleur';
import {CHANGES_LIST_DESCRIPTION} from '../constants/help.constants';
import {helpOutput} from './common.help';
import {TITLE} from './help';

const usage = `Usage: ${green('juno')} ${cyan('changes')} ${magenta('list')} ${yellow('[options]')}

Options:
${yellow('-a, --all')} Search through all changes, not just the 100 most recent.
${yellow('-e, --every')} Include changes of any status (default is only submitted ones).
${yellow('-h, --help')} Output usage information.`;

const doc = `${CHANGES_LIST_DESCRIPTION}

\`\`\`
${usage}
\`\`\`
`;

const help = `${TITLE}

${CHANGES_LIST_DESCRIPTION}

${usage}
`;

export const logHelpChangesList = (args?: string[]) => {
console.log(helpOutput(args) === 'doc' ? doc : help);
};
29 changes: 29 additions & 0 deletions src/help/changes.reject.help.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {cyan, green, magenta, yellow} from 'kleur';
import {CHANGES_REJECT_DESCRIPTION} from '../constants/help.constants';
import {helpOutput} from './common.help';
import {TITLE} from './help';

const usage = `Usage: ${green('juno')} ${cyan('changes')} ${magenta('reject')} ${yellow('[options]')}

Options:
${yellow('-i, --id')} The ID of the change to reject.
${yellow('-s, --hash')} The expected hash of all included changes (for verification).
${yellow('-h, --help')} Output usage information.`;

const doc = `${CHANGES_REJECT_DESCRIPTION}

\`\`\`
${usage}
\`\`\`
`;

const help = `${TITLE}

${CHANGES_REJECT_DESCRIPTION}

${usage}
`;

export const logHelpChangesReject = (args?: string[]) => {
console.log(helpOutput(args) === 'doc' ? doc : help);
};
2 changes: 1 addition & 1 deletion src/help/deploy.help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const usage = `Usage: ${green('juno')} ${cyan('deploy')} ${yellow('[options]')}

Options:
${yellow('-c, --clear')} Clear existing app files before proceeding with deployment.
${yellow('-n, --no-commit')} Submit the deployment as a change but do not apply it yet.
${yellow('-n, --no-apply')} Submit the deployment as a change but do not apply it yet.
${yellow('-i, --immediate')} Deploy files instantly (bypasses the change workflow).
${helpMode}
${yellow('-h, --help')} Output usage information.`;
Expand Down
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {hasArgs} from '@junobuild/cli-tools';
import {red} from 'kleur';
import {login, logout} from './commands/auth';
import {changes, helpChanges} from './commands/changes';
import {clear} from './commands/clear';
import {config} from './commands/config';
import {deploy} from './commands/deploy';
Expand Down Expand Up @@ -103,6 +104,9 @@ export const run = async () => {
case 'start':
logHelpStart(args);
break;
case 'changes':
helpChanges(args);
break;
default:
console.log(red('Unknown command.'));
console.log(help);
Expand Down Expand Up @@ -156,6 +160,9 @@ export const run = async () => {
case 'snapshot':
await snapshot(args);
break;
case 'changes':
await changes(args);
break;
case 'help':
console.log(help);
break;
Expand Down
29 changes: 29 additions & 0 deletions src/services/changes/changes.apply.services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {hexStringToUint8Array} from '@dfinity/utils';
import {commitProposal} from '@junobuild/cdn';
import ora from 'ora';
import {readChangesIdAndHash} from '../../utils/changes.utils';
import {assertConfigAndLoadSatelliteContext} from '../../utils/satellite.utils';

export const applyChanges = async (args?: string[]) => {
const {satellite} = await assertConfigAndLoadSatelliteContext(args);

const {proposalId, hash} = readChangesIdAndHash(args);

const spinner = ora('Applying...').start();

try {
await commitProposal({
cdn: {
satellite
},
proposal: {
proposal_id: proposalId,
sha256: hexStringToUint8Array(hash)
}
});

console.log(`\n🎯 Change ID ${proposalId} applied.`);
} finally {
spinner.stop();
}
};
83 changes: 83 additions & 0 deletions src/services/changes/changes.list.services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {fromNullable, nonNullish, toNullable, uint8ArrayToHexString} from '@dfinity/utils';
import {listProposals as listProposalsLib, type Proposal, type ProposalKey} from '@junobuild/cdn';
import {hasArgs} from '@junobuild/cli-tools';
import {type SatelliteParametersWithId} from '../../types/satellite';
import {formatDate} from '../../utils/format.utils';
import {assertConfigAndLoadSatelliteContext} from '../../utils/satellite.utils';

export const listChanges = async (args?: string[]) => {
const {satellite} = await assertConfigAndLoadSatelliteContext(args);

const all = hasArgs({args, options: ['-a', '--all']});
const every = hasArgs({args, options: ['-e', '--every']});

const items = await listProposals({
satellite,
traverseAll: all
});

const changes = items
.filter(([_, {status}]) => 'Open' in status || every)
.reduce<Record<string, {hash: string; created_at: string}>>(
(acc, [{proposal_id}, {sha256, created_at}]) => {
const hash: Uint8Array | number[] | undefined = fromNullable(sha256);

return {
...acc,
[`${proposal_id}`]: {
hash: nonNullish(hash) ? uint8ArrayToHexString(hash) : '',
created_at: formatDate(new Date(Number(created_at / 1_000_000n)))
}
};
},
{}
);

if (Object.keys(changes).length === 0) {
console.log('There are no open changes right now.');
return;
}

console.table(changes);
};

const listProposals = async ({
startAfter,
satellite,
traverseAll
}: {
startAfter?: bigint;
satellite: SatelliteParametersWithId;
traverseAll: boolean;
}): Promise<Array<[ProposalKey, Proposal]>> => {
const {items, items_length, matches_length} = await listProposalsLib({
cdn: {satellite},
filter: {
order: toNullable({
desc: true
}),
paginate: nonNullish(startAfter)
? toNullable({
start_after: toNullable(startAfter),
limit: toNullable()
})
: toNullable()
}
});

const last = <T>(elements: T[]): T | undefined => {
const {length, [length - 1]: last} = elements;
return last;
};

if (items_length > matches_length && traverseAll) {
const nextItems = await listProposals({
startAfter: last(items)?.[0].proposal_id,
satellite,
traverseAll
});
return [...items, ...nextItems];
}

return items;
};
29 changes: 29 additions & 0 deletions src/services/changes/changes.reject.services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {hexStringToUint8Array} from '@dfinity/utils';
import {rejectProposal} from '@junobuild/cdn';
import ora from 'ora';
import {readChangesIdAndHash} from '../../utils/changes.utils';
import {assertConfigAndLoadSatelliteContext} from '../../utils/satellite.utils';

export const rejectChanges = async (args?: string[]) => {
const {satellite} = await assertConfigAndLoadSatelliteContext(args);

const {proposalId, hash} = readChangesIdAndHash(args);

const spinner = ora('Rejecting...').start();

try {
await rejectProposal({
cdn: {
satellite
},
proposal: {
proposal_id: proposalId,
sha256: hexStringToUint8Array(hash)
}
});

console.log(`\n🚫 Change ID ${proposalId} rejected.`);
} finally {
spinner.stop();
}
};
Loading