@settlemint/sdk-cli 2.1.4-pr0619d764 → 2.1.4-pr09bd8ab0
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 +91 -56
- package/dist/cli.js.map +20 -19
- package/package.json +5 -5
package/dist/cli.js
CHANGED
@@ -245345,7 +245345,7 @@ function pruneCurrentEnv(currentEnv, env2) {
|
|
245345
245345
|
var package_default = {
|
245346
245346
|
name: "@settlemint/sdk-cli",
|
245347
245347
|
description: "Command-line interface for SettleMint SDK, providing development tools and project management capabilities",
|
245348
|
-
version: "2.1.4-
|
245348
|
+
version: "2.1.4-pr09bd8ab0",
|
245349
245349
|
type: "module",
|
245350
245350
|
private: false,
|
245351
245351
|
license: "FSL-1.1-MIT",
|
@@ -245393,10 +245393,10 @@ var package_default = {
|
|
245393
245393
|
"@inquirer/confirm": "5.1.9",
|
245394
245394
|
"@inquirer/input": "4.1.9",
|
245395
245395
|
"@inquirer/password": "4.0.12",
|
245396
|
-
"@inquirer/select": "4.
|
245397
|
-
"@settlemint/sdk-js": "2.1.4-
|
245398
|
-
"@settlemint/sdk-utils": "2.1.4-
|
245399
|
-
"@types/node": "22.
|
245396
|
+
"@inquirer/select": "4.2.0",
|
245397
|
+
"@settlemint/sdk-js": "2.1.4-pr09bd8ab0",
|
245398
|
+
"@settlemint/sdk-utils": "2.1.4-pr09bd8ab0",
|
245399
|
+
"@types/node": "22.15.1",
|
245400
245400
|
"@types/semver": "7.7.0",
|
245401
245401
|
"@types/which": "3.0.4",
|
245402
245402
|
"get-tsconfig": "4.10.0",
|
@@ -246553,13 +246553,21 @@ class Separator {
|
|
246553
246553
|
// ../utils/dist/terminal.mjs
|
246554
246554
|
import { spawn } from "node:child_process";
|
246555
246555
|
var import_console_table_printer2 = __toESM(require_dist2(), 1);
|
246556
|
-
|
246556
|
+
function shouldPrint() {
|
246557
|
+
return process.env.SETTLEMINT_DISABLE_TERMINAL !== "true";
|
246558
|
+
}
|
246559
|
+
var ascii = () => {
|
246560
|
+
if (!shouldPrint()) {
|
246561
|
+
return;
|
246562
|
+
}
|
246563
|
+
console.log(magentaBright(`
|
246557
246564
|
_________ __ __ .__ _____ .__ __
|
246558
246565
|
/ _____/ _____/ |__/ |_| | ____ / \\ |__| _____/ |_
|
246559
246566
|
\\_____ \\_/ __ \\ __\\ __\\ | _/ __ \\ / \\ / \\| |/ \\ __\\
|
246560
246567
|
/ \\ ___/| | | | | |_\\ ___// Y \\ | | \\ |
|
246561
246568
|
/_________/\\_____>__| |__| |____/\\_____>____|____/__|___|__/__|
|
246562
246569
|
`));
|
246570
|
+
};
|
246563
246571
|
var maskTokens2 = (output) => {
|
246564
246572
|
return output.replace(/sm_(pat|aat|sat)_[0-9a-zA-Z]+/g, "***");
|
246565
246573
|
};
|
@@ -246609,11 +246617,17 @@ async function executeCommand(command, args, options) {
|
|
246609
246617
|
});
|
246610
246618
|
}
|
246611
246619
|
var intro = (msg) => {
|
246620
|
+
if (!shouldPrint()) {
|
246621
|
+
return;
|
246622
|
+
}
|
246612
246623
|
console.log("");
|
246613
246624
|
console.log(magentaBright(maskTokens2(msg)));
|
246614
246625
|
console.log("");
|
246615
246626
|
};
|
246616
246627
|
var note = (message, level = "info") => {
|
246628
|
+
if (!shouldPrint()) {
|
246629
|
+
return;
|
246630
|
+
}
|
246617
246631
|
const maskedMessage = maskTokens2(message);
|
246618
246632
|
console.log("");
|
246619
246633
|
if (level === "warn") {
|
@@ -246638,6 +246652,9 @@ function list(title, items) {
|
|
246638
246652
|
${formatItems(items)}`);
|
246639
246653
|
}
|
246640
246654
|
var outro = (msg) => {
|
246655
|
+
if (!shouldPrint()) {
|
246656
|
+
return;
|
246657
|
+
}
|
246641
246658
|
console.log("");
|
246642
246659
|
console.log(inverse(greenBright(maskTokens2(msg))));
|
246643
246660
|
console.log("");
|
@@ -246657,7 +246674,7 @@ var spinner = async (options) => {
|
|
246657
246674
|
${error.stack}`));
|
246658
246675
|
throw new SpinnerError(errorMessage, error);
|
246659
246676
|
};
|
246660
|
-
if (is_in_ci_default) {
|
246677
|
+
if (is_in_ci_default || !shouldPrint()) {
|
246661
246678
|
try {
|
246662
246679
|
return await options.task();
|
246663
246680
|
} catch (err) {
|
@@ -246685,6 +246702,9 @@ function camelCaseToWords(s) {
|
|
246685
246702
|
return capitalized.replace(/\s+/g, " ").trim();
|
246686
246703
|
}
|
246687
246704
|
function table(title, data) {
|
246705
|
+
if (!shouldPrint()) {
|
246706
|
+
return;
|
246707
|
+
}
|
246688
246708
|
note(title);
|
246689
246709
|
if (!data || data.length === 0) {
|
246690
246710
|
note("No data to display");
|
@@ -250043,10 +250063,16 @@ async function getPackageManagerExecutable(targetDir) {
|
|
250043
250063
|
}
|
250044
250064
|
return { command: "npx", args: [] };
|
250045
250065
|
}
|
250066
|
+
function shouldPrint2() {
|
250067
|
+
return process.env.SETTLEMINT_DISABLE_TERMINAL !== "true";
|
250068
|
+
}
|
250046
250069
|
var maskTokens3 = (output) => {
|
250047
250070
|
return output.replace(/sm_(pat|aat|sat)_[0-9a-zA-Z]+/g, "***");
|
250048
250071
|
};
|
250049
250072
|
var note2 = (message, level = "info") => {
|
250073
|
+
if (!shouldPrint2()) {
|
250074
|
+
return;
|
250075
|
+
}
|
250050
250076
|
const maskedMessage = maskTokens3(message);
|
250051
250077
|
console.log("");
|
250052
250078
|
if (level === "warn") {
|
@@ -251609,6 +251635,9 @@ async function codegenTsconfig(env2, thegraphSubgraphNames) {
|
|
251609
251635
|
};
|
251610
251636
|
}
|
251611
251637
|
|
251638
|
+
// src/constants/default-subgraph.ts
|
251639
|
+
var DEFAULT_SUBGRAPH_NAME = "kit";
|
251640
|
+
|
251612
251641
|
// src/utils/subgraph/sanitize-name.ts
|
251613
251642
|
var import_slugify = __toESM(require_slugify(), 1);
|
251614
251643
|
function sanitizeName(value4, length = 35) {
|
@@ -251818,9 +251847,9 @@ var esm_default3 = createPrompt((config3, done) => {
|
|
251818
251847
|
firstRender.current = false;
|
251819
251848
|
if (items.length > pageSize) {
|
251820
251849
|
helpTipBottom = `
|
251821
|
-
${theme.style.help("
|
251850
|
+
${theme.style.help(`(${config3.instructions?.pager ?? "Use arrow keys to reveal more choices"})`)}`;
|
251822
251851
|
} else {
|
251823
|
-
helpTipTop = theme.style.help("
|
251852
|
+
helpTipTop = theme.style.help(`(${config3.instructions?.navigation ?? "Use arrow keys"})`);
|
251824
251853
|
}
|
251825
251854
|
}
|
251826
251855
|
const page = usePagination({
|
@@ -251874,8 +251903,8 @@ async function subgraphPrompt({
|
|
251874
251903
|
if (subgraphNames.length === 1) {
|
251875
251904
|
return subgraphNames;
|
251876
251905
|
}
|
251877
|
-
if (subgraphNames.includes(
|
251878
|
-
return [
|
251906
|
+
if (subgraphNames.includes(DEFAULT_SUBGRAPH_NAME)) {
|
251907
|
+
return [DEFAULT_SUBGRAPH_NAME];
|
251879
251908
|
}
|
251880
251909
|
return [];
|
251881
251910
|
}
|
@@ -253070,8 +253099,10 @@ async function getGraphEndpoint(settlemint, service, graphName) {
|
|
253070
253099
|
})
|
253071
253100
|
});
|
253072
253101
|
const endpoints = theGraphMiddleware.subgraphs.map(({ graphqlQueryEndpoint }) => graphqlQueryEndpoint?.displayValue);
|
253102
|
+
const hasKitSubgraph = endpoints.map((endpoint) => endpoint.split("/").pop()).some((endpoint) => endpoint === DEFAULT_SUBGRAPH_NAME);
|
253073
253103
|
return {
|
253074
|
-
SETTLEMINT_THEGRAPH_SUBGRAPHS_ENDPOINTS: endpoints
|
253104
|
+
SETTLEMINT_THEGRAPH_SUBGRAPHS_ENDPOINTS: endpoints,
|
253105
|
+
SETTLEMINT_THEGRAPH_DEFAULT_SUBGRAPH: hasKitSubgraph ? DEFAULT_SUBGRAPH_NAME : undefined
|
253075
253106
|
};
|
253076
253107
|
}
|
253077
253108
|
function getIpfsEndpoints(service) {
|
@@ -253228,7 +253259,7 @@ function connectCommand() {
|
|
253228
253259
|
env: { ...env2, ...graphEndpoints },
|
253229
253260
|
accept: acceptDefaults,
|
253230
253261
|
message: "Which The Graph subgraph do you want to use as the default?"
|
253231
|
-
}) : [];
|
253262
|
+
}) : [graphEndpoints.SETTLEMINT_THEGRAPH_DEFAULT_SUBGRAPH];
|
253232
253263
|
const portal = await portalPrompt({
|
253233
253264
|
env: env2,
|
253234
253265
|
middlewares,
|
@@ -257252,13 +257283,16 @@ function getCreateCommand({
|
|
257252
257283
|
region: selectedRegion?.id ?? ""
|
257253
257284
|
});
|
257254
257285
|
if (wait) {
|
257255
|
-
await waitForCompletion({
|
257286
|
+
const isDeployed = await waitForCompletion({
|
257256
257287
|
settlemint,
|
257257
257288
|
type: waitFor?.resourceType ?? type4,
|
257258
257289
|
uniqueName: waitFor?.uniqueName ?? result.uniqueName,
|
257259
257290
|
action: "deploy",
|
257260
257291
|
restartIfTimeout
|
257261
257292
|
});
|
257293
|
+
if (!isDeployed) {
|
257294
|
+
throw new Error(`Failed to deploy ${waitFor?.resourceType ?? type4} ${waitFor?.uniqueName ?? result.uniqueName}`);
|
257295
|
+
}
|
257262
257296
|
if (waitFor) {
|
257263
257297
|
outro(`${capitalizeFirstLetter2(waitFor.resourceType)} ${waitFor.name} created successfully`);
|
257264
257298
|
}
|
@@ -258702,7 +258736,15 @@ function getRestartCommand({
|
|
258702
258736
|
stopMessage: `${capitalizeFirstLetter2(type4)} restart initiated`
|
258703
258737
|
});
|
258704
258738
|
if (wait) {
|
258705
|
-
await waitForCompletion({
|
258739
|
+
const isRestarted = await waitForCompletion({
|
258740
|
+
settlemint,
|
258741
|
+
type: type4,
|
258742
|
+
uniqueName: serviceUniqueName,
|
258743
|
+
action: "restart"
|
258744
|
+
});
|
258745
|
+
if (!isRestarted) {
|
258746
|
+
throw new Error(`Failed to restart ${type4} ${uniqueName}`);
|
258747
|
+
}
|
258706
258748
|
}
|
258707
258749
|
outro(`${capitalizeFirstLetter2(type4)} ${result.name} restart initiated successfully`);
|
258708
258750
|
});
|
@@ -258958,10 +259000,7 @@ function yamlOutput(data) {
|
|
258958
259000
|
// src/commands/platform/config.ts
|
258959
259001
|
function configCommand() {
|
258960
259002
|
return new Command("config").alias("cfg").description("Get platform configuration").option("--prod", "Connect to your production environment").option("-i, --instance <instance>", "The instance to connect to (defaults to the instance in the .env file)").addOption(new Option("-o, --output <output>", "The output format").choices(["json", "yaml"])).action(async ({ prod, instance, output }) => {
|
258961
|
-
|
258962
|
-
if (printToTerminal) {
|
258963
|
-
intro("Getting platform configuration");
|
258964
|
-
}
|
259003
|
+
intro("Getting platform configuration");
|
258965
259004
|
const env2 = await loadEnv(false, !!prod);
|
258966
259005
|
const selectedInstance = instance ? sanitizeAndValidateInstanceUrl(instance) : await instancePrompt(env2, true);
|
258967
259006
|
const settlemint = createSettleMintClient({
|
@@ -258991,19 +259030,17 @@ function configCommand() {
|
|
258991
259030
|
}))).sort((a8, b4) => a8.providerId.localeCompare(b4.providerId) || a8.regionId.localeCompare(b4.regionId)),
|
258992
259031
|
preDeployedContracts: platformConfig.preDeployedContracts.sort()
|
258993
259032
|
};
|
258994
|
-
if (
|
259033
|
+
if (output === "json") {
|
259034
|
+
jsonOutput(platformConfigData);
|
259035
|
+
} else if (output === "yaml") {
|
259036
|
+
yamlOutput(platformConfigData);
|
259037
|
+
} else {
|
258995
259038
|
table("Templates (Kits)", platformConfigData.kits);
|
258996
259039
|
table("Use cases (Smart Contract Sets)", platformConfigData.useCases);
|
258997
259040
|
table("Providers and regions", platformConfigData.deploymentEngineTargets);
|
258998
259041
|
list("Pre-deployed abis (Smart Contract Portal)", platformConfigData.preDeployedContracts);
|
258999
|
-
} else if (output === "json") {
|
259000
|
-
jsonOutput(platformConfigData);
|
259001
|
-
} else if (output === "yaml") {
|
259002
|
-
yamlOutput(platformConfigData);
|
259003
|
-
}
|
259004
|
-
if (printToTerminal) {
|
259005
|
-
outro("Platform configuration retrieved");
|
259006
259042
|
}
|
259043
|
+
outro("Platform configuration retrieved");
|
259007
259044
|
});
|
259008
259045
|
}
|
259009
259046
|
|
@@ -259050,6 +259087,9 @@ function getUrlPathForService(service, serviceType) {
|
|
259050
259087
|
if (serviceType === "insights") {
|
259051
259088
|
return `insights/${encodeURIComponent(service.id)}/details`;
|
259052
259089
|
}
|
259090
|
+
if (serviceType === "load-balancer") {
|
259091
|
+
return `loadbalancers/${encodeURIComponent(service.id)}/details`;
|
259092
|
+
}
|
259053
259093
|
return "";
|
259054
259094
|
}
|
259055
259095
|
function getWorkspaceUrlPath(workspace) {
|
@@ -259079,10 +259119,7 @@ function applicationsListCommand() {
|
|
259079
259119
|
command: "platform list applications -o yaml > applications.yaml"
|
259080
259120
|
}
|
259081
259121
|
])).option("-w, --workspace <workspace>", "The workspace unique name to list applications for (defaults to workspace from env)").addOption(new Option("-o, --output <output>", "The output format").choices(["wide", "json", "yaml"])).action(async ({ workspace, output }) => {
|
259082
|
-
|
259083
|
-
if (printToTerminal) {
|
259084
|
-
intro("Listing applications");
|
259085
|
-
}
|
259122
|
+
intro("Listing applications");
|
259086
259123
|
const env2 = await loadEnv(false, false);
|
259087
259124
|
const selectedInstance = await instancePrompt(env2, true);
|
259088
259125
|
const personalAccessToken = await getInstanceCredentials(selectedInstance);
|
@@ -259095,13 +259132,12 @@ function applicationsListCommand() {
|
|
259095
259132
|
});
|
259096
259133
|
const workspaceUniqueName = workspace ?? env2.SETTLEMINT_WORKSPACE ?? await selectWorkspace(settlemint, env2);
|
259097
259134
|
const applications = await applicationsSpinner(settlemint, workspaceUniqueName);
|
259098
|
-
const wide = output === "wide";
|
259099
259135
|
const applicationsData = applications.map((application) => {
|
259100
259136
|
const basicFields = {
|
259101
259137
|
name: application.name,
|
259102
259138
|
uniqueName: application.uniqueName
|
259103
259139
|
};
|
259104
|
-
if (
|
259140
|
+
if (output) {
|
259105
259141
|
return {
|
259106
259142
|
...basicFields,
|
259107
259143
|
url: getApplicationUrl(selectedInstance, application)
|
@@ -259109,17 +259145,15 @@ function applicationsListCommand() {
|
|
259109
259145
|
}
|
259110
259146
|
return basicFields;
|
259111
259147
|
});
|
259112
|
-
|
259113
|
-
|
259114
|
-
table(`Applications for workspace ${selectedWorkspace.name} (${selectedWorkspace.uniqueName}) - ${getWorkspaceUrl(selectedInstance, selectedWorkspace)}`, applicationsData);
|
259115
|
-
} else if (output === "json") {
|
259148
|
+
const selectedWorkspace = await settlemint.workspace.read(workspaceUniqueName);
|
259149
|
+
if (output === "json") {
|
259116
259150
|
jsonOutput(applicationsData);
|
259117
259151
|
} else if (output === "yaml") {
|
259118
259152
|
yamlOutput(applicationsData);
|
259153
|
+
} else {
|
259154
|
+
table(`Applications for workspace ${selectedWorkspace.name} (${selectedWorkspace.uniqueName}) - ${getWorkspaceUrl(selectedInstance, selectedWorkspace)}`, applicationsData);
|
259119
259155
|
}
|
259120
|
-
|
259121
|
-
outro("Applications listed");
|
259122
|
-
}
|
259156
|
+
outro("Applications listed");
|
259123
259157
|
});
|
259124
259158
|
}
|
259125
259159
|
async function selectWorkspace(settlemint, env2) {
|
@@ -259133,7 +259167,7 @@ function formatServiceSubType(service, printToTerminal = true) {
|
|
259133
259167
|
if ("__typename" in service && typeof service.__typename === "string") {
|
259134
259168
|
return printToTerminal ? camelCaseToWords2(service.__typename) : service.__typename;
|
259135
259169
|
}
|
259136
|
-
return
|
259170
|
+
return "Unknown";
|
259137
259171
|
}
|
259138
259172
|
|
259139
259173
|
// src/commands/platform/utils/formatting/format-health-status.ts
|
@@ -259164,6 +259198,7 @@ function formatStatus(status, printToTerminal = true) {
|
|
259164
259198
|
var SERVICE_TYPES = [
|
259165
259199
|
"blockchain-network",
|
259166
259200
|
"blockchain-node",
|
259201
|
+
"load-balancer",
|
259167
259202
|
"custom-deployment",
|
259168
259203
|
"insights",
|
259169
259204
|
"integration-tool",
|
@@ -259202,10 +259237,7 @@ function servicesCommand() {
|
|
259202
259237
|
command: "platform list services --type blockchain-network blockchain-node middleware"
|
259203
259238
|
}
|
259204
259239
|
])).option("--app, --application <application>", "The application unique name to list the services in (defaults to application from env)").addOption(new Option("-t, --type <type...>", "The type(s) of service to list").choices(SERVICE_TYPES)).addOption(new Option("-o, --output <output>", "The output format").choices(["wide", "json", "yaml"])).action(async ({ application, type: type4, output }) => {
|
259205
|
-
|
259206
|
-
if (printToTerminal) {
|
259207
|
-
intro("Listing application services");
|
259208
|
-
}
|
259240
|
+
intro("Listing application services");
|
259209
259241
|
const env2 = await loadEnv(false, false);
|
259210
259242
|
const selectedInstance = await instancePrompt(env2, true);
|
259211
259243
|
const personalAccessToken = await getInstanceCredentials(selectedInstance);
|
@@ -259217,6 +259249,7 @@ function servicesCommand() {
|
|
259217
259249
|
accessToken,
|
259218
259250
|
instance: selectedInstance
|
259219
259251
|
});
|
259252
|
+
const printToTerminal = !output || output === "wide";
|
259220
259253
|
const applicationUniqueName = application ?? env2.SETTLEMINT_APPLICATION ?? (printToTerminal ? await selectApplication(settlemint, env2) : null);
|
259221
259254
|
if (!applicationUniqueName) {
|
259222
259255
|
return nothingSelectedError("application");
|
@@ -259244,16 +259277,14 @@ function servicesCommand() {
|
|
259244
259277
|
},
|
259245
259278
|
services: servicesToShow
|
259246
259279
|
};
|
259247
|
-
if (
|
259248
|
-
table(`Services for ${selectedApplication.name} (${applicationUniqueName}) - ${getApplicationUrl(selectedInstance, selectedApplication)}`, servicesToShow);
|
259249
|
-
} else if (output === "json") {
|
259280
|
+
if (output === "json") {
|
259250
259281
|
jsonOutput(data);
|
259251
259282
|
} else if (output === "yaml") {
|
259252
259283
|
yamlOutput(data);
|
259284
|
+
} else {
|
259285
|
+
table(`Services for ${selectedApplication.name} (${applicationUniqueName}) - ${getApplicationUrl(selectedInstance, selectedApplication)}`, servicesToShow);
|
259253
259286
|
}
|
259254
|
-
|
259255
|
-
outro("Application services listed");
|
259256
|
-
}
|
259287
|
+
outro("Application services listed");
|
259257
259288
|
});
|
259258
259289
|
}
|
259259
259290
|
async function selectApplication(settlemint, env2) {
|
@@ -260308,7 +260339,8 @@ function subgraphDeployCommand() {
|
|
260308
260339
|
await writeEnvSpinner(!!prod, {
|
260309
260340
|
...env2,
|
260310
260341
|
SETTLEMINT_THEGRAPH: theGraphMiddleware.uniqueName,
|
260311
|
-
...graphEndpoints
|
260342
|
+
...graphEndpoints,
|
260343
|
+
SETTLEMINT_THEGRAPH_DEFAULT_SUBGRAPH: env2.SETTLEMINT_THEGRAPH_DEFAULT_SUBGRAPH ?? graphName
|
260312
260344
|
});
|
260313
260345
|
outro(`Subgraph ${graphName} deployed successfully`);
|
260314
260346
|
});
|
@@ -260378,8 +260410,8 @@ function subgraphRemoveCommand() {
|
|
260378
260410
|
await writeEnvSpinner(!!prod, {
|
260379
260411
|
...env2,
|
260380
260412
|
SETTLEMINT_THEGRAPH: theGraphMiddleware.uniqueName,
|
260381
|
-
|
260382
|
-
|
260413
|
+
...graphEndpoints,
|
260414
|
+
SETTLEMINT_THEGRAPH_DEFAULT_SUBGRAPH: env2.SETTLEMINT_THEGRAPH_DEFAULT_SUBGRAPH === graphName ? undefined : env2.SETTLEMINT_THEGRAPH_DEFAULT_SUBGRAPH
|
260383
260415
|
});
|
260384
260416
|
outro(`Subgraph ${graphName} removed successfully`);
|
260385
260417
|
});
|
@@ -260441,7 +260473,10 @@ function addHooksToCommand(cmd2, rootCmd, argv) {
|
|
260441
260473
|
rootCmd._lastCommand = thisCommand;
|
260442
260474
|
rootCmd._lastCommand._commandPath = commandPath;
|
260443
260475
|
}
|
260444
|
-
if (
|
260476
|
+
if (isJsonOrYamlOutput(thisCommand)) {
|
260477
|
+
process.env.SETTLEMINT_DISABLE_TERMINAL = "true";
|
260478
|
+
}
|
260479
|
+
if (isLeafCommand(thisCommand)) {
|
260445
260480
|
ascii();
|
260446
260481
|
await validateSdkVersionFromCommand(thisCommand);
|
260447
260482
|
}
|
@@ -260525,4 +260560,4 @@ async function sdkCliCommand(argv = process.argv) {
|
|
260525
260560
|
// src/cli.ts
|
260526
260561
|
sdkCliCommand();
|
260527
260562
|
|
260528
|
-
//# debugId=
|
260563
|
+
//# debugId=1895DDFC302F0BFD64756E2164756E21
|