@runa-ai/runa-cli 0.7.2 → 0.8.0
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/{chunk-Z7A4BEWF.js → chunk-3JO6YP3T.js} +1 -1
- package/dist/chunk-6E2DRXIL.js +452 -0
- package/dist/{chunk-PMXE5XOJ.js → chunk-GHQH6UC5.js} +1 -1
- package/dist/{chunk-LCK2LGVR.js → chunk-PAWNJA3N.js} +1 -1
- package/dist/{chunk-FWMGC5FP.js → chunk-RB2ZUS76.js} +249 -12
- package/dist/{chunk-CKRLVEIO.js → chunk-ZYT7OQJB.js} +16 -11
- package/dist/{ci-Z4525QW6.js → ci-ZK3LKYFX.js} +305 -429
- package/dist/{cli-Q2XIQDRS.js → cli-ZY5VRIJA.js} +13 -13
- package/dist/commands/ci/commands/ci-resolvers.d.ts +1 -2
- package/dist/commands/ci/machine/actors/setup/pr-common.d.ts +1 -1
- package/dist/commands/ci/machine/contract.d.ts +6 -1
- package/dist/commands/ci/machine/guards.d.ts +16 -0
- package/dist/commands/ci/machine/machine.d.ts +11 -3
- package/dist/commands/db/apply/actors/seed-actors.d.ts +1 -0
- package/dist/commands/db/apply/contract.d.ts +23 -0
- package/dist/commands/db/apply/helpers/fresh-db-handler.d.ts +2 -1
- package/dist/commands/db/apply/helpers/hazard-handler.d.ts +19 -8
- package/dist/commands/db/apply/helpers/index.d.ts +2 -1
- package/dist/commands/db/apply/helpers/no-change-plan.d.ts +2 -0
- package/dist/commands/db/apply/helpers/plan-check-filter.d.ts +11 -0
- package/dist/commands/db/apply/machine.d.ts +52 -1
- package/dist/commands/db/utils/boundary-policy/types.d.ts +2 -0
- package/dist/commands/db/utils/duplicate-function-ownership.d.ts +35 -0
- package/dist/commands/db/utils/plan-size-guard.d.ts +16 -0
- package/dist/commands/db/utils/preflight-checks/duplicate-function-ownership-checks.d.ts +4 -0
- package/dist/constants/versions.d.ts +1 -1
- package/dist/{db-BPQ2TEQM.js → db-EPI2DQYN.js} +1203 -410
- package/dist/{dev-MLRKIP7F.js → dev-GB5ERUVR.js} +1 -1
- package/dist/{env-WNHJVLOT.js → env-WP74UUMO.js} +1 -1
- package/dist/{hotfix-Z5EGVSMH.js → hotfix-TOSGTVCW.js} +1 -1
- package/dist/index.js +3 -3
- package/dist/{init-S2ATHLJ6.js → init-35JLDFHI.js} +1 -1
- package/dist/{risk-detector-VO5HJR4R.js → risk-detector-S7XQF4I2.js} +1 -1
- package/dist/{risk-detector-core-7WZJZ5ZI.js → risk-detector-core-TGFKWHRS.js} +1 -1
- package/dist/{risk-detector-plpgsql-ULV7NLDB.js → risk-detector-plpgsql-O32TUR34.js} +103 -5
- package/dist/{upgrade-BDUWBRT5.js → upgrade-7L4JIE4K.js} +1 -1
- package/dist/{vuln-check-66RXX3TO.js → vuln-check-G6I4YYDC.js} +1 -1
- package/dist/{vuln-checker-FFOGOJPT.js → vuln-checker-CT2AYPIS.js} +1 -1
- package/dist/{watch-ITYW57SL.js → watch-AL4LCBRM.js} +1 -1
- package/package.json +3 -3
- package/dist/chunk-4XHZQRRK.js +0 -215
|
@@ -1,14 +1,256 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
|
+
import { detectEnvironment } from './chunk-JMJP4A47.js';
|
|
3
4
|
import { init_esm_shims, __require } from './chunk-VRXHCR5K.js';
|
|
5
|
+
import { appendFile, writeFile, readFile } from 'fs/promises';
|
|
6
|
+
import { CLIError, formatDuration, loadRunaConfig } from '@runa-ai/runa';
|
|
7
|
+
import { z } from 'zod';
|
|
4
8
|
import { existsSync, createWriteStream } from 'fs';
|
|
5
9
|
import path from 'path';
|
|
6
|
-
import { loadRunaConfig, CLIError } from '@runa-ai/runa';
|
|
7
|
-
import { writeFile, readFile } from 'fs/promises';
|
|
8
|
-
import { z } from 'zod';
|
|
9
10
|
|
|
10
11
|
createRequire(import.meta.url);
|
|
11
12
|
|
|
13
|
+
// src/commands/ci/utils/github.ts
|
|
14
|
+
init_esm_shims();
|
|
15
|
+
async function appendGithubStepSummary(markdown) {
|
|
16
|
+
const file = process.env.GITHUB_STEP_SUMMARY;
|
|
17
|
+
if (!file) return;
|
|
18
|
+
await appendFile(file, `${markdown.trimEnd()}
|
|
19
|
+
|
|
20
|
+
`, "utf-8");
|
|
21
|
+
}
|
|
22
|
+
function addGithubMask(value) {
|
|
23
|
+
if (!value) return;
|
|
24
|
+
console.log(`::add-mask::${value}`);
|
|
25
|
+
}
|
|
26
|
+
z.object({
|
|
27
|
+
action: z.string().optional(),
|
|
28
|
+
pull_request: z.object({
|
|
29
|
+
number: z.number().int()
|
|
30
|
+
})
|
|
31
|
+
}).passthrough();
|
|
32
|
+
|
|
33
|
+
// src/commands/ci/commands/ci-prod-utils.ts
|
|
34
|
+
init_esm_shims();
|
|
35
|
+
function requireEnv(name) {
|
|
36
|
+
const v = process.env[name];
|
|
37
|
+
if (v && v.trim().length > 0) return v.trim();
|
|
38
|
+
throw new CLIError(
|
|
39
|
+
`Missing required environment variable: ${name}`,
|
|
40
|
+
"CI_INPUT_MISSING",
|
|
41
|
+
[`Set ${name} in GitHub Actions secrets/env`, "If unsure, run: runa check"],
|
|
42
|
+
void 0,
|
|
43
|
+
10
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
function resolveRepoKind() {
|
|
47
|
+
const env = detectEnvironment(process.cwd());
|
|
48
|
+
const result = env === "runa-repo" ? "monorepo" : env === "pj-repo" ? "pj-repo" : "unknown";
|
|
49
|
+
if (process.env.RUNA_DEBUG === "true") {
|
|
50
|
+
console.error("[DEBUG:resolveRepoKind]", {
|
|
51
|
+
cwd: process.cwd(),
|
|
52
|
+
detectedEnv: env,
|
|
53
|
+
result
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
function createInitialSummary(params) {
|
|
59
|
+
return {
|
|
60
|
+
version: "1.0",
|
|
61
|
+
mode: params.mode,
|
|
62
|
+
command: "ci prod-apply",
|
|
63
|
+
status: "failure",
|
|
64
|
+
startedAt: params.startedAt.toISOString(),
|
|
65
|
+
endedAt: params.startedAt.toISOString(),
|
|
66
|
+
durationMs: 0,
|
|
67
|
+
repoKind: resolveRepoKind(),
|
|
68
|
+
detected: {},
|
|
69
|
+
diagnostics: {},
|
|
70
|
+
steps: {},
|
|
71
|
+
layers: {},
|
|
72
|
+
errors: []
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function requireCiAutoApprove(params) {
|
|
76
|
+
if (params.mode !== "github-actions") return;
|
|
77
|
+
if (params.autoApprove === true) return;
|
|
78
|
+
throw new CLIError(
|
|
79
|
+
"Missing required flag: --auto-approve (required in CI mode)",
|
|
80
|
+
"CI_AUTO_APPROVE_REQUIRED",
|
|
81
|
+
["Re-run with: runa ci prod-apply --auto-approve", "Keep CI non-interactive and deterministic"],
|
|
82
|
+
void 0,
|
|
83
|
+
10
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
function resolveProdApplyInputs() {
|
|
87
|
+
const productionDatabaseUrlAdmin = requireEnv("GH_DATABASE_URL_ADMIN");
|
|
88
|
+
const productionDatabaseUrl = requireEnv("GH_DATABASE_URL");
|
|
89
|
+
addGithubMask(productionDatabaseUrlAdmin);
|
|
90
|
+
addGithubMask(productionDatabaseUrl);
|
|
91
|
+
return {
|
|
92
|
+
productionDatabaseUrlAdmin,
|
|
93
|
+
productionDatabaseUrl,
|
|
94
|
+
githubSha: process.env.GITHUB_SHA ?? "unknown",
|
|
95
|
+
githubActor: process.env.GITHUB_ACTOR ?? "unknown",
|
|
96
|
+
githubRepository: process.env.GITHUB_REPOSITORY ?? "unknown"
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function setSummaryErrorFromUnknown(summary, error) {
|
|
100
|
+
if (error instanceof CLIError) {
|
|
101
|
+
summary.errors.push({
|
|
102
|
+
code: error.code ?? "CI_ERROR",
|
|
103
|
+
message: error.message,
|
|
104
|
+
step: "ci prod-apply",
|
|
105
|
+
details: error.cause instanceof Error ? error.cause.message : void 0
|
|
106
|
+
});
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (error instanceof Error) {
|
|
110
|
+
summary.errors.push({ code: "CI_ERROR", message: error.message, step: "ci prod-apply" });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
summary.errors.push({ code: "CI_ERROR", message: String(error), step: "ci prod-apply" });
|
|
114
|
+
}
|
|
115
|
+
function buildCiProdApplyStepSummaryMarkdown(params) {
|
|
116
|
+
const { summary } = params;
|
|
117
|
+
const lines = [];
|
|
118
|
+
const statusEmoji = summary.status === "success" ? "\u2705" : "\u274C";
|
|
119
|
+
const duration = formatDuration(summary.durationMs);
|
|
120
|
+
lines.push(
|
|
121
|
+
`## ${statusEmoji} Production Deploy ${summary.status === "success" ? "Completed" : "Failed"}`
|
|
122
|
+
);
|
|
123
|
+
lines.push("");
|
|
124
|
+
lines.push(`**Duration**: ${duration}`);
|
|
125
|
+
lines.push("");
|
|
126
|
+
if (summary.dbOutcome) {
|
|
127
|
+
lines.push(`**Exit mode**: \`${summary.dbOutcome.exitMode}\``);
|
|
128
|
+
const failedPhase = summary.dbOutcome.phases.find(
|
|
129
|
+
(phase) => phase.status === "failed" || phase.status === "timeout"
|
|
130
|
+
);
|
|
131
|
+
if (failedPhase) {
|
|
132
|
+
lines.push(`**Failed phase**: \`${failedPhase.id}\``);
|
|
133
|
+
}
|
|
134
|
+
if (summary.dbOutcome.summary.warnings > 0) {
|
|
135
|
+
lines.push(`**Warnings**: ${summary.dbOutcome.summary.warnings}`);
|
|
136
|
+
}
|
|
137
|
+
lines.push("");
|
|
138
|
+
}
|
|
139
|
+
if (summary.errors.length > 0) {
|
|
140
|
+
lines.push("### \u274C Errors");
|
|
141
|
+
lines.push("");
|
|
142
|
+
for (const e of summary.errors) {
|
|
143
|
+
lines.push(`- **${e.code}**: ${e.message}`);
|
|
144
|
+
}
|
|
145
|
+
lines.push("");
|
|
146
|
+
}
|
|
147
|
+
lines.push("<details>");
|
|
148
|
+
lines.push("<summary>\u{1F4CB} Technical Details</summary>");
|
|
149
|
+
lines.push("");
|
|
150
|
+
lines.push(`- Command: \`${summary.command}\``);
|
|
151
|
+
lines.push(`- Mode: \`${summary.mode}\``);
|
|
152
|
+
lines.push(`- Summary: \`${params.summaryPath}\``);
|
|
153
|
+
if (summary.dbOutcome) {
|
|
154
|
+
lines.push(`- DB exit mode: \`${summary.dbOutcome.exitMode}\``);
|
|
155
|
+
}
|
|
156
|
+
lines.push("");
|
|
157
|
+
lines.push("</details>");
|
|
158
|
+
lines.push("");
|
|
159
|
+
return lines.join("\n");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/commands/ci/commands/ci-resolvers.ts
|
|
163
|
+
init_esm_shims();
|
|
164
|
+
|
|
165
|
+
// src/commands/ci/utils/config-readers.ts
|
|
166
|
+
init_esm_shims();
|
|
167
|
+
function readNested(value, keyPath, guard) {
|
|
168
|
+
let current = value;
|
|
169
|
+
for (const key of keyPath) {
|
|
170
|
+
if (typeof current !== "object" || current === null) return null;
|
|
171
|
+
if (!(key in current)) return null;
|
|
172
|
+
current = current[key];
|
|
173
|
+
}
|
|
174
|
+
return guard(current) ? current : null;
|
|
175
|
+
}
|
|
176
|
+
var isBoolean = (v) => typeof v === "boolean";
|
|
177
|
+
var isString = (v) => typeof v === "string";
|
|
178
|
+
function readNestedBoolean(params) {
|
|
179
|
+
return readNested(params.value, params.keyPath, isBoolean);
|
|
180
|
+
}
|
|
181
|
+
function readNestedString(params) {
|
|
182
|
+
return readNested(params.value, params.keyPath, isString);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/commands/ci/commands/ci-resolvers.ts
|
|
186
|
+
function resolveMode(modeRaw) {
|
|
187
|
+
if (modeRaw === "github-actions" || modeRaw === "local") return modeRaw;
|
|
188
|
+
return process.env.GITHUB_ACTIONS === "true" ? "github-actions" : "local";
|
|
189
|
+
}
|
|
190
|
+
function resolveCiPrProfile(params) {
|
|
191
|
+
const raw = readNestedString({ value: params.config, keyPath: ["ci", "pr", "profile"] });
|
|
192
|
+
if (raw === "runa-strict" || raw === "pj-stable") return raw;
|
|
193
|
+
if (params.repoKind === "pj-repo") return "pj-stable";
|
|
194
|
+
return "runa-strict";
|
|
195
|
+
}
|
|
196
|
+
function debugLogPolicy(label, policy) {
|
|
197
|
+
if (process.env.RUNA_DEBUG === "true") {
|
|
198
|
+
console.error(`[DEBUG:resolveCiPrPolicy] \u2192 ${label}:`, policy);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
var DEFAULT_POLICIES = {
|
|
202
|
+
monorepo: {
|
|
203
|
+
allowLocalFallback: false,
|
|
204
|
+
source: "default"
|
|
205
|
+
},
|
|
206
|
+
"pj-repo": {
|
|
207
|
+
allowLocalFallback: true,
|
|
208
|
+
source: "default"
|
|
209
|
+
},
|
|
210
|
+
"pj-stable-profile": {
|
|
211
|
+
allowLocalFallback: true,
|
|
212
|
+
source: "config"
|
|
213
|
+
},
|
|
214
|
+
unknown: {
|
|
215
|
+
allowLocalFallback: false,
|
|
216
|
+
source: "default"
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
function resolveCiPrPolicy(params) {
|
|
220
|
+
const allowLocalFallback = readNestedBoolean({
|
|
221
|
+
value: params.config,
|
|
222
|
+
keyPath: ["ci", "pr", "allowLocalFallback"]
|
|
223
|
+
});
|
|
224
|
+
if (process.env.RUNA_DEBUG === "true") {
|
|
225
|
+
console.error("[DEBUG:resolveCiPrPolicy] Inputs:", {
|
|
226
|
+
repoKind: params.repoKind,
|
|
227
|
+
hasConfig: params.config !== null && params.config !== void 0,
|
|
228
|
+
configValues: { allowLocalFallback }
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (typeof allowLocalFallback === "boolean") {
|
|
232
|
+
const policy2 = {
|
|
233
|
+
allowLocalFallback,
|
|
234
|
+
source: "config"
|
|
235
|
+
};
|
|
236
|
+
debugLogPolicy("config override", policy2);
|
|
237
|
+
return policy2;
|
|
238
|
+
}
|
|
239
|
+
const profile = resolveCiPrProfile({ repoKind: params.repoKind, config: params.config });
|
|
240
|
+
if (profile === "pj-stable") {
|
|
241
|
+
const policy2 = DEFAULT_POLICIES["pj-stable-profile"];
|
|
242
|
+
debugLogPolicy("pj-stable profile", policy2);
|
|
243
|
+
return policy2;
|
|
244
|
+
}
|
|
245
|
+
const policy = DEFAULT_POLICIES[params.repoKind] ?? DEFAULT_POLICIES.unknown;
|
|
246
|
+
debugLogPolicy(`${params.repoKind} default`, policy);
|
|
247
|
+
return policy;
|
|
248
|
+
}
|
|
249
|
+
function parseIntOr(value, fallback) {
|
|
250
|
+
const n = Number.parseInt(String(value ?? ""), 10);
|
|
251
|
+
return Number.isNaN(n) ? fallback : n;
|
|
252
|
+
}
|
|
253
|
+
|
|
12
254
|
// src/commands/ci/machine/actors/setup/pr-common.ts
|
|
13
255
|
init_esm_shims();
|
|
14
256
|
function resolvePrContext(input) {
|
|
@@ -22,12 +264,8 @@ function resolvePrContext(input) {
|
|
|
22
264
|
headBranch: input.githubHeadRef ?? null
|
|
23
265
|
};
|
|
24
266
|
}
|
|
25
|
-
function resolvePolicy(
|
|
26
|
-
return {
|
|
27
|
-
allowLocalFallback: true,
|
|
28
|
-
allowPartialPhases: false,
|
|
29
|
-
source: "default"
|
|
30
|
-
};
|
|
267
|
+
function resolvePolicy(config, repoKind) {
|
|
268
|
+
return resolveCiPrPolicy({ config, repoKind });
|
|
31
269
|
}
|
|
32
270
|
function detectRepoKind(repoRoot) {
|
|
33
271
|
const hasApps = existsSync(path.join(repoRoot, "apps"));
|
|
@@ -176,7 +414,6 @@ function createErrorOutput(repoRoot, error, extraFields) {
|
|
|
176
414
|
prContext: { prNumber: null, action: null, sha: null, baseBranch: null, headBranch: null },
|
|
177
415
|
policy: {
|
|
178
416
|
allowLocalFallback: true,
|
|
179
|
-
allowPartialPhases: false,
|
|
180
417
|
source: "default"
|
|
181
418
|
},
|
|
182
419
|
app: null,
|
|
@@ -201,7 +438,7 @@ async function executePrSetupBase(input, ensureTmpDir) {
|
|
|
201
438
|
githubBaseRef: input.githubBaseRef,
|
|
202
439
|
githubHeadRef: input.githubHeadRef
|
|
203
440
|
});
|
|
204
|
-
const policy = resolvePolicy(input.config);
|
|
441
|
+
const policy = resolvePolicy(input.config, repoKind);
|
|
205
442
|
const configResult = loadRunaConfig(repoRoot);
|
|
206
443
|
const appConfigOverride = configResult?.config?.app?.directory ? {
|
|
207
444
|
directory: configResult.config.app.directory
|
|
@@ -458,4 +695,4 @@ async function waitForAppReady(params) {
|
|
|
458
695
|
});
|
|
459
696
|
}
|
|
460
697
|
|
|
461
|
-
export { createErrorOutput, detectApp, executePrSetupBase, startAppBackground, waitForAppReady, writeEnvLocal };
|
|
698
|
+
export { addGithubMask, appendGithubStepSummary, buildCiProdApplyStepSummaryMarkdown, createErrorOutput, createInitialSummary, detectApp, executePrSetupBase, parseIntOr, requireCiAutoApprove, resolveMode, resolveProdApplyInputs, setSummaryErrorFromUnknown, startAppBackground, waitForAppReady, writeEnvLocal };
|
|
@@ -11,6 +11,16 @@ createRequire(import.meta.url);
|
|
|
11
11
|
// src/commands/db/utils/db-target.ts
|
|
12
12
|
init_esm_shims();
|
|
13
13
|
init_local_supabase();
|
|
14
|
+
function buildProductionDbSuggestions() {
|
|
15
|
+
return [
|
|
16
|
+
"Use --env local for local Supabase operations",
|
|
17
|
+
"Production operations require a remote database URL",
|
|
18
|
+
"Try `runa db apply production --check` after loading production env files",
|
|
19
|
+
"If you are working in the runa repo, `pnpm runa db apply production --check` also works",
|
|
20
|
+
"Or run `dotenvx run -f .env.keys -f .env.production -- runa db apply production --check`",
|
|
21
|
+
"Set DATABASE_URL_ADMIN or GH_DATABASE_URL_ADMIN to the production database URL"
|
|
22
|
+
];
|
|
23
|
+
}
|
|
14
24
|
function getLocalDatabaseUrl() {
|
|
15
25
|
return buildLocalDatabaseUrl(process.cwd());
|
|
16
26
|
}
|
|
@@ -73,11 +83,7 @@ function resolveDatabaseTarget(environment) {
|
|
|
73
83
|
throw new CLIError(
|
|
74
84
|
"Production database URL points to localhost",
|
|
75
85
|
"PRODUCTION_DB_URL_IS_LOCAL",
|
|
76
|
-
|
|
77
|
-
"Use --env local for local Supabase operations",
|
|
78
|
-
"Production operations require a remote database URL",
|
|
79
|
-
"Set DATABASE_URL_ADMIN or GH_DATABASE_URL_ADMIN to the production database URL"
|
|
80
|
-
]
|
|
86
|
+
buildProductionDbSuggestions()
|
|
81
87
|
);
|
|
82
88
|
}
|
|
83
89
|
return { url: ghAdminUrl, source: "GH_DATABASE_URL_ADMIN" };
|
|
@@ -88,18 +94,17 @@ function resolveDatabaseTarget(environment) {
|
|
|
88
94
|
throw new CLIError(
|
|
89
95
|
"Production database URL points to localhost",
|
|
90
96
|
"PRODUCTION_DB_URL_IS_LOCAL",
|
|
91
|
-
|
|
92
|
-
"Use --env local for local Supabase operations",
|
|
93
|
-
"Production operations require a remote database URL",
|
|
94
|
-
"Set DATABASE_URL_ADMIN or GH_DATABASE_URL_ADMIN to the production database URL"
|
|
95
|
-
]
|
|
97
|
+
buildProductionDbSuggestions()
|
|
96
98
|
);
|
|
97
99
|
}
|
|
98
100
|
return { url: explicitAdminUrl, source: "DATABASE_URL_ADMIN" };
|
|
99
101
|
}
|
|
100
102
|
throw new CLIError("Production database URL not found", "PRODUCTION_DB_URL_NOT_FOUND", [
|
|
101
103
|
"Set GH_DATABASE_URL_ADMIN (CI) or DATABASE_URL_ADMIN (local) to a direct postgres URL",
|
|
102
|
-
"Do not use GH_DATABASE_URL or DATABASE_URL for DDL operations"
|
|
104
|
+
"Do not use GH_DATABASE_URL or DATABASE_URL for DDL operations",
|
|
105
|
+
"Try `runa db apply production --check` after loading production env files",
|
|
106
|
+
"If you are working in the runa repo, `pnpm runa db apply production --check` also works",
|
|
107
|
+
"Or run `dotenvx run -f .env.keys -f .env.production -- runa db apply production --check`"
|
|
103
108
|
]);
|
|
104
109
|
}
|
|
105
110
|
}
|