@metaobjectsdev/cli 1.0.2 → 1.0.4-rc.1
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/README.md +13 -5
- package/dist/src/commands/deps.d.ts +3 -0
- package/dist/src/commands/deps.d.ts.map +1 -0
- package/dist/src/commands/deps.js +200 -0
- package/dist/src/commands/deps.js.map +1 -0
- package/dist/src/commands/docs.d.ts.map +1 -1
- package/dist/src/commands/docs.js +19 -3
- package/dist/src/commands/docs.js.map +1 -1
- package/dist/src/commands/eject.d.ts +2 -1
- package/dist/src/commands/eject.d.ts.map +1 -1
- package/dist/src/commands/eject.js +235 -75
- package/dist/src/commands/eject.js.map +1 -1
- package/dist/src/commands/gen.d.ts.map +1 -1
- package/dist/src/commands/gen.js +173 -30
- package/dist/src/commands/gen.js.map +1 -1
- package/dist/src/commands/init.d.ts +5 -9
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +92 -402
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/migrate.d.ts.map +1 -1
- package/dist/src/commands/migrate.js +24 -19
- package/dist/src/commands/migrate.js.map +1 -1
- package/dist/src/commands/prompt-snapshot.d.ts.map +1 -1
- package/dist/src/commands/prompt-snapshot.js +2 -1
- package/dist/src/commands/prompt-snapshot.js.map +1 -1
- package/dist/src/commands/verify.d.ts.map +1 -1
- package/dist/src/commands/verify.js +186 -29
- package/dist/src/commands/verify.js.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +97 -15
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib/advisory.d.ts +6 -0
- package/dist/src/lib/advisory.d.ts.map +1 -1
- package/dist/src/lib/advisory.js +10 -0
- package/dist/src/lib/advisory.js.map +1 -1
- package/dist/src/lib/agent-context-staleness.d.ts.map +1 -1
- package/dist/src/lib/agent-context-staleness.js +7 -1
- package/dist/src/lib/agent-context-staleness.js.map +1 -1
- package/dist/src/lib/args.d.ts +75 -3
- package/dist/src/lib/args.d.ts.map +1 -1
- package/dist/src/lib/args.js +54 -5
- package/dist/src/lib/args.js.map +1 -1
- package/dist/src/lib/catalog-listing.d.ts +92 -0
- package/dist/src/lib/catalog-listing.d.ts.map +1 -0
- package/dist/src/lib/catalog-listing.js +268 -0
- package/dist/src/lib/catalog-listing.js.map +1 -0
- package/dist/src/lib/catalog.d.ts +41 -0
- package/dist/src/lib/catalog.d.ts.map +1 -0
- package/dist/src/lib/catalog.js +89 -0
- package/dist/src/lib/catalog.js.map +1 -0
- package/dist/src/lib/collection-load-options.d.ts +13 -0
- package/dist/src/lib/collection-load-options.d.ts.map +1 -0
- package/dist/src/lib/collection-load-options.js +25 -0
- package/dist/src/lib/collection-load-options.js.map +1 -0
- package/dist/src/lib/dependency-sync.d.ts +170 -0
- package/dist/src/lib/dependency-sync.d.ts.map +1 -0
- package/dist/src/lib/dependency-sync.js +457 -0
- package/dist/src/lib/dependency-sync.js.map +1 -0
- package/dist/src/lib/install-set.d.ts +31 -0
- package/dist/src/lib/install-set.d.ts.map +1 -0
- package/dist/src/lib/install-set.js +103 -0
- package/dist/src/lib/install-set.js.map +1 -0
- package/dist/src/lib/library-eject.d.ts +79 -0
- package/dist/src/lib/library-eject.d.ts.map +1 -0
- package/dist/src/lib/library-eject.js +241 -0
- package/dist/src/lib/library-eject.js.map +1 -0
- package/dist/src/lib/library-listing.d.ts +63 -0
- package/dist/src/lib/library-listing.d.ts.map +1 -0
- package/dist/src/lib/library-listing.js +178 -0
- package/dist/src/lib/library-listing.js.map +1 -0
- package/dist/src/lib/library-prefix-advisory.d.ts +17 -0
- package/dist/src/lib/library-prefix-advisory.d.ts.map +1 -0
- package/dist/src/lib/library-prefix-advisory.js +67 -0
- package/dist/src/lib/library-prefix-advisory.js.map +1 -0
- package/dist/src/lib/load-metaobjects-config.d.ts +1 -2
- package/dist/src/lib/load-metaobjects-config.d.ts.map +1 -1
- package/dist/src/lib/load-metaobjects-config.js +11 -17
- package/dist/src/lib/load-metaobjects-config.js.map +1 -1
- package/dist/src/lib/migrate-scope.d.ts +32 -1
- package/dist/src/lib/migrate-scope.d.ts.map +1 -1
- package/dist/src/lib/migrate-scope.js +51 -3
- package/dist/src/lib/migrate-scope.js.map +1 -1
- package/dist/src/lib/overlay-lint.d.ts +37 -0
- package/dist/src/lib/overlay-lint.d.ts.map +1 -0
- package/dist/src/lib/overlay-lint.js +102 -0
- package/dist/src/lib/overlay-lint.js.map +1 -0
- package/dist/src/lib/requirement-check.d.ts +26 -3
- package/dist/src/lib/requirement-check.d.ts.map +1 -1
- package/dist/src/lib/requirement-check.js +71 -13
- package/dist/src/lib/requirement-check.js.map +1 -1
- package/package.json +10 -10
- package/src/commands/deps.ts +231 -0
- package/src/commands/docs.ts +19 -3
- package/src/commands/eject.ts +304 -90
- package/src/commands/gen.ts +189 -29
- package/src/commands/init.ts +95 -407
- package/src/commands/migrate.ts +26 -16
- package/src/commands/prompt-snapshot.ts +2 -1
- package/src/commands/verify.ts +213 -30
- package/src/index.ts +97 -15
- package/src/lib/advisory.ts +13 -0
- package/src/lib/agent-context-staleness.ts +7 -1
- package/src/lib/args.ts +118 -8
- package/src/lib/catalog-listing.ts +369 -0
- package/src/lib/catalog.ts +107 -0
- package/src/lib/collection-load-options.ts +42 -0
- package/src/lib/dependency-sync.ts +656 -0
- package/src/lib/install-set.ts +116 -0
- package/src/lib/library-eject.ts +298 -0
- package/src/lib/library-listing.ts +258 -0
- package/src/lib/library-prefix-advisory.ts +78 -0
- package/src/lib/load-metaobjects-config.ts +15 -21
- package/src/lib/migrate-scope.ts +59 -4
- package/src/lib/overlay-lint.ts +129 -0
- package/src/lib/requirement-check.ts +100 -14
package/src/commands/gen.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { relative } from "node:path";
|
|
|
2
2
|
import { parseGenArgs } from "../lib/args.js";
|
|
3
3
|
import { resolveGenConfig } from "../lib/config.js";
|
|
4
4
|
import { loadMemoryOptionsFrom, loadMetaobjectsConfig, resolveGenCollection, resolveGenConfigDir } from "../lib/load-metaobjects-config.js";
|
|
5
|
+
import { collectionLoadOptions } from "../lib/collection-load-options.js";
|
|
5
6
|
import { formatGenResult, formatGenResultToon, type GenFileEntry, type GenFileStatus } from "../lib/output.js";
|
|
6
7
|
import { formatGenResultJson } from "../lib/output-json.js";
|
|
7
8
|
import type { OutputFormat } from "../lib/format.js";
|
|
@@ -14,9 +15,18 @@ import {
|
|
|
14
15
|
type AdvisoryFindingRow, type AdvisorySection,
|
|
15
16
|
} from "../lib/advisory.js";
|
|
16
17
|
import { loadMemory, resolveCollection } from "@metaobjectsdev/sdk";
|
|
17
|
-
import { runGen
|
|
18
|
+
import { runGen } from "@metaobjectsdev/codegen-ts";
|
|
18
19
|
import type { WriteStatus } from "@metaobjectsdev/codegen-ts";
|
|
20
|
+
import { packageOfResolutionKey } from "@metaobjectsdev/metadata";
|
|
21
|
+
import type { MetaRoot } from "@metaobjectsdev/metadata";
|
|
22
|
+
import type { Collection } from "@metaobjectsdev/sdk";
|
|
19
23
|
import { reportLoadError } from "../lib/load-error.js";
|
|
24
|
+
import {
|
|
25
|
+
buildCatalogListing, renderCatalogText, wiredGeneratorNames, ownedGeneratorNames,
|
|
26
|
+
declaredDepsOf,
|
|
27
|
+
} from "../lib/catalog-listing.js";
|
|
28
|
+
import { emitStructured } from "../lib/format.js";
|
|
29
|
+
import { composeCatalog } from "../lib/catalog.js";
|
|
20
30
|
|
|
21
31
|
/**
|
|
22
32
|
* Print a load failure with everything the loader's ADR-0009 envelope carried — the stable
|
|
@@ -52,10 +62,16 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
|
|
|
52
62
|
try { flags = parseGenArgs(args); }
|
|
53
63
|
catch (err) { log.error((err as Error).message); return 2; }
|
|
54
64
|
|
|
55
|
-
// ADR-0021 D3 — `meta gen --list`: print the
|
|
56
|
-
//
|
|
65
|
+
// ADR-0021 D3 — `meta gen --list`: print the generator CATALOG and exit 0 WITHOUT
|
|
66
|
+
// running codegen. No project is required (like `meta types`): "what can this engine
|
|
67
|
+
// do" is a question about the installed engine. `--probe` adds "...and what would
|
|
68
|
+
// each emit for MY model", which does need one.
|
|
57
69
|
if (flags.list) {
|
|
58
|
-
return
|
|
70
|
+
return listCatalogCommand(cwd, fmt, flags.probe);
|
|
71
|
+
}
|
|
72
|
+
if (flags.probe) {
|
|
73
|
+
log.error("--probe is only meaningful with --list. Run: meta gen --list --probe");
|
|
74
|
+
return 2;
|
|
59
75
|
}
|
|
60
76
|
|
|
61
77
|
const cliConfig = resolveGenConfig(flags);
|
|
@@ -122,7 +138,7 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
|
|
|
122
138
|
let metadata;
|
|
123
139
|
try {
|
|
124
140
|
metadata = await loadMemory(genCollection.configDir, {
|
|
125
|
-
|
|
141
|
+
...collectionLoadOptions(genCollection),
|
|
126
142
|
...loadMemoryOptionsFrom(forgeConfig),
|
|
127
143
|
});
|
|
128
144
|
} catch (err) {
|
|
@@ -130,12 +146,28 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
|
|
|
130
146
|
return 2;
|
|
131
147
|
}
|
|
132
148
|
|
|
149
|
+
// FR-023 §11.1 item 2 — refuse a positional that names ONLY objects imported
|
|
150
|
+
// from a dependency and excluded from output by the default exclusion rule.
|
|
151
|
+
// Distinct from the runner's existing "no entities matched" WARNING
|
|
152
|
+
// (runner.ts): that path covers a name matching nothing loaded at all; this
|
|
153
|
+
// one covers a name that matches something real, just not something this
|
|
154
|
+
// project generates. A name matching nothing falls through untouched — the
|
|
155
|
+
// runner's own warning still covers it.
|
|
156
|
+
const importedRefusal = refuseImportedPositional(cliConfig.entities, metadata, genCollection);
|
|
157
|
+
if (importedRefusal !== undefined) {
|
|
158
|
+
log.error(importedRefusal);
|
|
159
|
+
return 2;
|
|
160
|
+
}
|
|
161
|
+
|
|
133
162
|
let result;
|
|
134
163
|
try {
|
|
135
164
|
result = await runGen({
|
|
136
165
|
config: forgeConfig,
|
|
137
166
|
metadata,
|
|
138
167
|
projectRoot,
|
|
168
|
+
// The COMPOSED catalog, so the requires / api-framework gates can see the react
|
|
169
|
+
// and tanstack entries too. codegen-ts only knows its own slice.
|
|
170
|
+
catalog: composeCatalog(),
|
|
139
171
|
baseline: flags.baseline,
|
|
140
172
|
// --dry-run must actually preview. This was previously passed only to the
|
|
141
173
|
// display object below, so a "preview" run wrote every file.
|
|
@@ -145,6 +177,14 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
|
|
|
145
177
|
// passed: an unconfigured project's predicate admits everything, so this
|
|
146
178
|
// is a no-op for the common case, not a behavior change.
|
|
147
179
|
scope: genCollection.inScope,
|
|
180
|
+
// FR-023 §4.3 — the collection's own source files (never a dependency's
|
|
181
|
+
// snapshot artifact), so sharedModelFile() defaults its `files` selection
|
|
182
|
+
// to exactly what this project itself declares.
|
|
183
|
+
sourceFiles: genCollection.ownFiles,
|
|
184
|
+
// FR-043 §6 — the shipped-library selection. Always passed, including as `[]`:
|
|
185
|
+
// that is what tells the post-selection audit "this project opted into none",
|
|
186
|
+
// which is a different statement from a programmatic caller that never said.
|
|
187
|
+
libraries: genCollection.libraries,
|
|
148
188
|
...(cliConfig.entities.length > 0 ? { entityFilter: cliConfig.entities } : {}),
|
|
149
189
|
});
|
|
150
190
|
} catch (err) {
|
|
@@ -154,6 +194,21 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
|
|
|
154
194
|
|
|
155
195
|
for (const w of result.warnings) { log.warn(w); }
|
|
156
196
|
|
|
197
|
+
// A first-run POINTER, not a warning: an empty selection is the designed state of a
|
|
198
|
+
// fresh `meta init`, so calling it a problem would make every new project start with
|
|
199
|
+
// one. It says what to do next and disappears the moment anything is wired.
|
|
200
|
+
if ((forgeConfig.generators?.length ?? 0) === 0) {
|
|
201
|
+
log.info(
|
|
202
|
+
"\nNothing is generated until you choose it — `generators: []` is what `meta init` " +
|
|
203
|
+
"scaffolds, by design.\n" +
|
|
204
|
+
" meta gen --list --probe the catalog, with how many files each generator " +
|
|
205
|
+
"would emit for YOUR model\n" +
|
|
206
|
+
" meta eject <name>... take the ones you want; it prints the import, the " +
|
|
207
|
+
"entry to wire, and what to install\n" +
|
|
208
|
+
"(`meta docs` needs none of this — documentation is on by default.)",
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
157
212
|
// result.files[].path is the absolute full path from decideAndWrite. With
|
|
158
213
|
// per-target output, show each path relative to the project root so files in
|
|
159
214
|
// different targets are distinguishable.
|
|
@@ -220,6 +275,45 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
|
|
|
220
275
|
return hasFailure ? 1 : 0;
|
|
221
276
|
}
|
|
222
277
|
|
|
278
|
+
/**
|
|
279
|
+
* FR-023 §11.1 item 2 — the error message for `meta gen <Name>` when EVERY
|
|
280
|
+
* loaded object named `<Name>` is imported from a dependency and excluded from
|
|
281
|
+
* output (imported metadata is load-only by default; only the consumer's own
|
|
282
|
+
* `scope.include` naming the package literally opts it in). Returns undefined
|
|
283
|
+
* for every other case, INCLUDING a name matching nothing at all — that stays
|
|
284
|
+
* the runner's existing "no entities matched" warning, unchanged.
|
|
285
|
+
*
|
|
286
|
+
* A bare positional is a NAME, not a fully-qualified one (`cliConfig.entities`
|
|
287
|
+
* is matched against `entity.name` elsewhere in this file the same way), so two
|
|
288
|
+
* packages declaring the same short name are both candidates; the refusal
|
|
289
|
+
* fires only when NONE of them would generate.
|
|
290
|
+
*/
|
|
291
|
+
function refuseImportedPositional(
|
|
292
|
+
entityNames: readonly string[],
|
|
293
|
+
metadata: MetaRoot,
|
|
294
|
+
collection: Collection,
|
|
295
|
+
): string | undefined {
|
|
296
|
+
if (entityNames.length === 0) return undefined;
|
|
297
|
+
const allObjects = metadata.objects();
|
|
298
|
+
for (const name of entityNames) {
|
|
299
|
+
const matches = allObjects.filter((o) => o.name === name);
|
|
300
|
+
if (matches.length === 0) continue; // nothing loaded by this name — the runner's own warning covers it
|
|
301
|
+
const allExcludedImports = matches.every((o) => {
|
|
302
|
+
const fqn = o.resolutionKey();
|
|
303
|
+
return collection.imported(fqn) && !collection.inScope(fqn);
|
|
304
|
+
});
|
|
305
|
+
if (!allExcludedImports) continue;
|
|
306
|
+
const fqn = matches[0]!.resolutionKey();
|
|
307
|
+
const pkg = packageOfResolutionKey(fqn);
|
|
308
|
+
const dependencyName = collection.dependencies.find((d) => d.packages.includes(pkg))?.name ?? pkg;
|
|
309
|
+
return (
|
|
310
|
+
`meta gen: '${name}' (${fqn}) is imported from dependency '${dependencyName}' and is not ` +
|
|
311
|
+
`generated here — add its package to scope.include in .metaobjects/config.json to generate it`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
|
|
223
317
|
/**
|
|
224
318
|
* Run the advisory verify-as-teacher scan (same pass `meta verify` runs): surface
|
|
225
319
|
* authored source that hand-rolls what the metadata could model. `gen` is the
|
|
@@ -251,34 +345,100 @@ function runAntiPatternScan(
|
|
|
251
345
|
}
|
|
252
346
|
|
|
253
347
|
/**
|
|
254
|
-
* `meta gen --list` —
|
|
348
|
+
* `meta gen --list` — the generator catalog (ADR-0021 D3; opt-in-codegen design §D3).
|
|
255
349
|
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
350
|
+
* Codegen is opt-in, so this is the door: the tool describes what it can do and the
|
|
351
|
+
* builder decides what the app needs. Rows are grouped by `layer`, the axis you select
|
|
352
|
+
* by; `--format json|toon` emits the same catalog as ONE document (nothing else on
|
|
353
|
+
* stdout, per `meta types`' purity rule).
|
|
354
|
+
*
|
|
355
|
+
* With `--probe`, every catalog generator is constructed and dry-run against this
|
|
356
|
+
* project's real model, so the `capability` layer stops being a list of labels and
|
|
357
|
+
* becomes `output-parser: 3, callable: 0, requirement-tests: 7` — information that
|
|
358
|
+
* cannot go stale, because it does not describe the generators, it runs them.
|
|
260
359
|
*/
|
|
261
|
-
function
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
360
|
+
async function listCatalogCommand(
|
|
361
|
+
cwd: string,
|
|
362
|
+
fmt: OutputFormat,
|
|
363
|
+
probe: boolean,
|
|
364
|
+
): Promise<number> {
|
|
365
|
+
let opts: Parameters<typeof buildCatalogListing>[0] = {};
|
|
266
366
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
367
|
+
if (probe) {
|
|
368
|
+
// A probe reports what YOUR model would produce, so a project is mandatory here
|
|
369
|
+
// even though it is optional for a bare --list. Failing loudly beats a listing of
|
|
370
|
+
// zeros that reads like "none of these apply to you".
|
|
371
|
+
let collection;
|
|
372
|
+
try {
|
|
373
|
+
collection = await resolveCollection(cwd);
|
|
374
|
+
} catch (err) {
|
|
375
|
+
log.error(
|
|
376
|
+
`meta gen --list --probe needs a project to probe: ${(err as Error).message}\n` +
|
|
377
|
+
"Run `meta gen --list` (no --probe) for the catalog on its own.",
|
|
378
|
+
);
|
|
379
|
+
return 2;
|
|
380
|
+
}
|
|
381
|
+
const projectRoot = resolveGenConfigDir(cwd, collection.configDir);
|
|
382
|
+
const genCollection = await resolveGenCollection(collection, projectRoot);
|
|
383
|
+
|
|
384
|
+
let forgeConfig;
|
|
385
|
+
try {
|
|
386
|
+
forgeConfig = await loadMetaobjectsConfig(projectRoot);
|
|
387
|
+
} catch (err) {
|
|
388
|
+
log.error((err as Error).message);
|
|
389
|
+
return 2;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
let metadata;
|
|
393
|
+
try {
|
|
394
|
+
metadata = await loadMemory(genCollection.configDir, {
|
|
395
|
+
...collectionLoadOptions(genCollection),
|
|
396
|
+
...loadMemoryOptionsFrom(forgeConfig),
|
|
397
|
+
});
|
|
398
|
+
} catch (err) {
|
|
399
|
+
reportLoadError(log, "failed to load metadata", err);
|
|
400
|
+
return 2;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
opts = {
|
|
404
|
+
project: {
|
|
405
|
+
projectRoot,
|
|
406
|
+
config: forgeConfig,
|
|
407
|
+
wiredNames: wiredGeneratorNames(forgeConfig),
|
|
408
|
+
ownedNames: ownedGeneratorNames(projectRoot),
|
|
409
|
+
declaredDeps: declaredDepsOf(projectRoot),
|
|
410
|
+
// FR-043 — the library rows read the selection from the COLLECTION, which is
|
|
411
|
+
// where `libraries` lives now (`.metaobjects/config.json`), not from the
|
|
412
|
+
// codegen config it was moved out of.
|
|
413
|
+
libraries: genCollection.libraries,
|
|
414
|
+
},
|
|
415
|
+
probe: { metadata, scope: genCollection.inScope },
|
|
416
|
+
};
|
|
417
|
+
} else {
|
|
418
|
+
// No probe: still report `wired` / `owned` when a project happens to be here, since
|
|
419
|
+
// both are free. A directory with no config is not an error — the catalog is a fact
|
|
420
|
+
// about the installed engine.
|
|
421
|
+
try {
|
|
422
|
+
const collection = await resolveCollection(cwd);
|
|
423
|
+
const projectRoot = resolveGenConfigDir(cwd, collection.configDir);
|
|
424
|
+
const forgeConfig = await loadMetaobjectsConfig(projectRoot);
|
|
425
|
+
opts = {
|
|
426
|
+
project: {
|
|
427
|
+
projectRoot,
|
|
428
|
+
config: forgeConfig,
|
|
429
|
+
wiredNames: wiredGeneratorNames(forgeConfig),
|
|
430
|
+
ownedNames: ownedGeneratorNames(projectRoot),
|
|
431
|
+
declaredDeps: declaredDepsOf(projectRoot),
|
|
432
|
+
libraries: collection.libraries,
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
} catch {
|
|
436
|
+
// No project here — the catalog stands on its own.
|
|
437
|
+
}
|
|
280
438
|
}
|
|
281
439
|
|
|
282
|
-
|
|
440
|
+
const rows = await buildCatalogListing(opts);
|
|
441
|
+
if (fmt === "text") log.info(renderCatalogText(rows, probe));
|
|
442
|
+
else emitStructured(rows, fmt);
|
|
283
443
|
return 0;
|
|
284
444
|
}
|