@basemaps/cli 6.27.0 → 6.29.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/CHANGELOG.md +40 -0
- package/README.md +46 -13
- package/build/cli/base.cli.d.ts +0 -18
- package/build/cli/base.cli.d.ts.map +1 -1
- package/build/cli/base.cli.js +1 -60
- package/build/cli/bin.d.ts +2 -0
- package/build/cli/bin.d.ts.map +1 -0
- package/build/cli/bin.js +3 -0
- package/build/cli/cogify/{__test__ → __tests__}/batch.job.test.d.ts +0 -0
- package/build/cli/cogify/__tests__/batch.job.test.d.ts.map +1 -0
- package/build/cli/cogify/{__test__ → __tests__}/batch.job.test.js +48 -22
- package/build/cli/cogify/{__test__ → __tests__}/semver.test.d.ts +0 -0
- package/build/cli/cogify/__tests__/semver.test.d.ts.map +1 -0
- package/build/cli/cogify/{__test__ → __tests__}/semver.test.js +0 -0
- package/build/cli/cogify/action.cog.d.ts +2 -1
- package/build/cli/cogify/action.cog.d.ts.map +1 -1
- package/build/cli/cogify/action.cog.js +50 -43
- package/build/cli/cogify/action.job.d.ts +1 -1
- package/build/cli/cogify/action.job.d.ts.map +1 -1
- package/build/cli/cogify/action.job.js +2 -2
- package/build/cli/cogify/batch.job.d.ts +6 -4
- package/build/cli/cogify/batch.job.d.ts.map +1 -1
- package/build/cli/cogify/batch.job.js +63 -49
- package/build/cli/cogify/imagery.config.d.ts.map +1 -1
- package/build/cli/cogify/imagery.config.js +0 -2
- package/build/cli/config/action.bundle.d.ts +11 -0
- package/build/cli/config/action.bundle.d.ts.map +1 -0
- package/build/cli/config/action.bundle.js +36 -0
- package/build/cli/config/action.import.d.ts +14 -0
- package/build/cli/config/action.import.d.ts.map +1 -0
- package/build/cli/config/action.import.js +80 -0
- package/build/cli/config/config.diff.d.ts +10 -0
- package/build/cli/config/config.diff.d.ts.map +1 -0
- package/build/cli/config/config.diff.js +47 -0
- package/build/cli/config/config.update.d.ts +23 -0
- package/build/cli/config/config.update.d.ts.map +1 -0
- package/build/cli/config/config.update.js +71 -0
- package/build/cli/index.d.ts +7 -0
- package/build/cli/index.d.ts.map +1 -0
- package/build/cli/index.js +21 -0
- package/build/cli/screenshot/action.screenshot.d.ts +61 -0
- package/build/cli/screenshot/action.screenshot.d.ts.map +1 -0
- package/build/cli/screenshot/action.screenshot.js +121 -0
- package/build/cli/util.js +1 -1
- package/build/cog/{__test__ → __tests__}/builder.test.d.ts +0 -0
- package/build/cog/__tests__/builder.test.d.ts.map +1 -0
- package/build/cog/{__test__ → __tests__}/builder.test.js +0 -0
- package/build/cog/{__test__ → __tests__}/cog.stac.job.test.d.ts +0 -0
- package/build/cog/__tests__/cog.stac.job.test.d.ts.map +1 -0
- package/build/cog/{__test__ → __tests__}/cog.stac.job.test.js +1 -1
- package/build/cog/{__test__ → __tests__}/cog.test.d.ts +0 -0
- package/build/cog/__tests__/cog.test.d.ts.map +1 -0
- package/build/cog/{__test__ → __tests__}/cog.test.js +0 -0
- package/build/cog/{__test__ → __tests__}/cog.vrt.test.d.ts +0 -0
- package/build/cog/__tests__/cog.vrt.test.d.ts.map +1 -0
- package/build/cog/{__test__ → __tests__}/cog.vrt.test.js +1 -1
- package/build/cog/{__test__ → __tests__}/cutline.test.d.ts +0 -0
- package/build/cog/__tests__/cutline.test.d.ts.map +1 -0
- package/build/cog/{__test__ → __tests__}/cutline.test.js +2 -1
- package/build/cog/{__test__ → __tests__}/projection.loader.test.d.ts +0 -0
- package/build/cog/__tests__/projection.loader.test.d.ts.map +1 -0
- package/build/cog/{__test__ → __tests__}/projection.loader.test.js +0 -0
- package/build/cog/{__test__ → __tests__}/source.tiff.testhelper.d.ts +0 -0
- package/build/cog/__tests__/source.tiff.testhelper.d.ts.map +1 -0
- package/build/cog/{__test__ → __tests__}/source.tiff.testhelper.js +0 -0
- package/build/cog/cog.stac.job.js +1 -1
- package/build/cog/job.factory.d.ts.map +1 -1
- package/build/cog/job.factory.js +12 -4
- package/build/gdal/{__test__ → __tests__}/gdal.progress.test.d.ts +0 -0
- package/build/gdal/__tests__/gdal.progress.test.d.ts.map +1 -0
- package/build/gdal/{__test__ → __tests__}/gdal.progress.test.js +0 -0
- package/build/gdal/{__test__ → __tests__}/gdal.test.d.ts +0 -0
- package/build/gdal/__tests__/gdal.test.d.ts.map +1 -0
- package/build/gdal/{__test__ → __tests__}/gdal.test.js +0 -0
- package/package.json +17 -11
- package/build/cli/cogify/__test__/batch.job.test.d.ts.map +0 -1
- package/build/cli/cogify/__test__/semver.test.d.ts.map +0 -1
- package/build/cli/cogify/index.d.ts +0 -7
- package/build/cli/cogify/index.d.ts.map +0 -1
- package/build/cli/cogify/index.js +0 -16
- package/build/cog/__test__/builder.test.d.ts.map +0 -1
- package/build/cog/__test__/cog.stac.job.test.d.ts.map +0 -1
- package/build/cog/__test__/cog.test.d.ts.map +0 -1
- package/build/cog/__test__/cog.vrt.test.d.ts.map +0 -1
- package/build/cog/__test__/cutline.test.d.ts.map +0 -1
- package/build/cog/__test__/projection.loader.test.d.ts.map +0 -1
- package/build/cog/__test__/source.tiff.testhelper.d.ts.map +0 -1
- package/build/gdal/__test__/gdal.progress.test.d.ts.map +0 -1
- package/build/gdal/__test__/gdal.test.d.ts.map +0 -1
- package/cogify.js +0 -3
|
@@ -2,6 +2,7 @@ import { TileMatrixSet } from '@basemaps/geo';
|
|
|
2
2
|
import { Env, fsa, LogConfig, Projection } from '@basemaps/shared';
|
|
3
3
|
import Batch from 'aws-sdk/clients/batch.js';
|
|
4
4
|
import { createHash } from 'crypto';
|
|
5
|
+
import { basename } from 'path';
|
|
5
6
|
const JobQueue = 'CogBatchJobQueue';
|
|
6
7
|
const JobDefinition = 'CogBatchJob';
|
|
7
8
|
const ChunkJobMax = 1000;
|
|
@@ -24,6 +25,14 @@ export function extractResolutionFromName(name) {
|
|
|
24
25
|
return parseFloat(matches[1].replace('-', '.')) * 1000;
|
|
25
26
|
}
|
|
26
27
|
export class BatchJob {
|
|
28
|
+
static get batch() {
|
|
29
|
+
var _a, _b;
|
|
30
|
+
if (this._batch)
|
|
31
|
+
return this._batch;
|
|
32
|
+
const region = (_b = (_a = Env.get('AWS_REGION')) !== null && _a !== void 0 ? _a : Env.get('AWS_DEFAULT_REGION')) !== null && _b !== void 0 ? _b : 'ap-southeast-2';
|
|
33
|
+
this._batch = new Batch({ region });
|
|
34
|
+
return this._batch;
|
|
35
|
+
}
|
|
27
36
|
/**
|
|
28
37
|
* Create a id for a job
|
|
29
38
|
*
|
|
@@ -35,10 +44,12 @@ export class BatchJob {
|
|
|
35
44
|
static id(job, fileNames) {
|
|
36
45
|
// Job names are uncontrolled so hash the name and grab a small slice to use as a identifier
|
|
37
46
|
const jobName = createHash('sha256').update(job.name).digest('hex').slice(0, 16);
|
|
47
|
+
if (fileNames == null)
|
|
48
|
+
return `${job.id}-${jobName}-`;
|
|
38
49
|
fileNames.sort((a, b) => a.localeCompare(b));
|
|
39
|
-
return `${job.id}-${jobName}-${fileNames.join('_')}`.slice(0, 128);
|
|
50
|
+
return `${job.id}-${jobName}-${fileNames.length}x-${fileNames.join('_')}`.slice(0, 128);
|
|
40
51
|
}
|
|
41
|
-
static async batchOne(jobPath, job,
|
|
52
|
+
static async batchOne(jobPath, job, names, isCommit) {
|
|
42
53
|
const jobName = BatchJob.id(job, names);
|
|
43
54
|
let memory = 3900;
|
|
44
55
|
if (names.length === 1) {
|
|
@@ -55,7 +66,7 @@ export class BatchJob {
|
|
|
55
66
|
let commandStr = ['-V', 'cog', '--job', jobPath, '--commit'];
|
|
56
67
|
for (const name of names)
|
|
57
68
|
commandStr = commandStr.concat(['--name', name]);
|
|
58
|
-
const batchJob = await batch
|
|
69
|
+
const batchJob = await this.batch
|
|
59
70
|
.submitJob({
|
|
60
71
|
jobName,
|
|
61
72
|
jobQueue: JobQueue,
|
|
@@ -73,77 +84,75 @@ export class BatchJob {
|
|
|
73
84
|
* List all the current jobs in batch and their statuses
|
|
74
85
|
* @returns a map of JobName to if their status is "ok" (not failed)
|
|
75
86
|
*/
|
|
76
|
-
static async getCurrentJobList(
|
|
87
|
+
static async getCurrentJobList(job, logger) {
|
|
88
|
+
var _a;
|
|
89
|
+
const jobPrefix = BatchJob.id(job);
|
|
77
90
|
// For some reason AWS only lets us query one status at a time.
|
|
78
|
-
const allStatuses = ['SUBMITTED', 'PENDING', 'RUNNABLE', 'STARTING', 'RUNNING'
|
|
79
|
-
|
|
80
|
-
const
|
|
91
|
+
const allStatuses = ['SUBMITTED', 'PENDING', 'RUNNABLE', 'STARTING', 'RUNNING' /* 'SUCCEEDED' */];
|
|
92
|
+
// Succeeded is not needed as we check to see if the output file exists, if it succeeds and the output file doesn't exist then something has gone wrong
|
|
93
|
+
const allJobs = await Promise.all(allStatuses.map((jobStatus) => this.batch.listJobs({ jobQueue: JobQueue, jobStatus }).promise()));
|
|
94
|
+
const jobIds = new Set();
|
|
95
|
+
// Find all the relevant jobs that start with our job prefix
|
|
81
96
|
for (const status of allJobs) {
|
|
82
97
|
for (const job of status.jobSummaryList) {
|
|
83
|
-
if (job.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
98
|
+
if (!job.jobName.startsWith(jobPrefix))
|
|
99
|
+
continue;
|
|
100
|
+
jobIds.add(job.jobId);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Inspect all the jobs for what files are being "processed"
|
|
104
|
+
const tiffs = new Set();
|
|
105
|
+
let allJobIds = [...jobIds];
|
|
106
|
+
while (allJobIds.length > 0) {
|
|
107
|
+
logger.info({ jobCount: allJobIds.length }, 'JobFetch');
|
|
108
|
+
const jobList = allJobIds.slice(0, 100);
|
|
109
|
+
allJobIds = allJobIds.slice(100);
|
|
110
|
+
const describedJobs = await this.batch.describeJobs({ jobs: jobList }).promise();
|
|
111
|
+
if (describedJobs.jobs == null)
|
|
112
|
+
continue;
|
|
113
|
+
for (const job of describedJobs.jobs) {
|
|
114
|
+
const jobCommand = (_a = job.container) === null || _a === void 0 ? void 0 : _a.command;
|
|
115
|
+
if (jobCommand == null)
|
|
116
|
+
continue;
|
|
117
|
+
// Extract the tiff names from the job command
|
|
118
|
+
for (let i = 0; i < jobCommand.length; i++) {
|
|
119
|
+
if (jobCommand[i] === '--name')
|
|
120
|
+
tiffs.add(jobCommand[i + 1]);
|
|
88
121
|
}
|
|
89
122
|
}
|
|
90
123
|
}
|
|
91
|
-
return
|
|
124
|
+
return tiffs;
|
|
92
125
|
}
|
|
93
126
|
static async batchJob(job, commit = false, logger) {
|
|
94
|
-
var _a;
|
|
95
127
|
const jobPath = job.getJobPath('job.json');
|
|
96
128
|
if (!jobPath.startsWith('s3://')) {
|
|
97
129
|
throw new Error(`AWS Batch collection.json have to be in S3, jobPath:${jobPath}`);
|
|
98
130
|
}
|
|
99
131
|
LogConfig.set(logger.child({ correlationId: job.id, imageryName: job.name }));
|
|
100
|
-
const region = (_a = Env.get('AWS_DEFAULT_REGION')) !== null && _a !== void 0 ? _a : 'ap-southeast-2';
|
|
101
|
-
const batch = new Batch({ region });
|
|
102
132
|
fsa.configure(job.output.location);
|
|
103
|
-
const runningJobs = await BatchJob.getCurrentJobList(batch);
|
|
104
|
-
// Prepare chunk job and individual jobs based on imagery size.
|
|
105
|
-
const jobs = await this.getJobs(job);
|
|
106
133
|
// Get all the existing output tiffs
|
|
107
|
-
const
|
|
108
|
-
const existTiffs = [];
|
|
134
|
+
const existTiffs = new Set();
|
|
109
135
|
for await (const fileName of fsa.list(job.getJobPath())) {
|
|
110
136
|
if (fileName.endsWith('.tiff'))
|
|
111
|
-
existTiffs.
|
|
137
|
+
existTiffs.add(basename(fileName));
|
|
112
138
|
}
|
|
113
|
-
const
|
|
114
|
-
for (const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
logger.info({ jobName }, 'JobRunning');
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
// Check existence of all the output tiffs.
|
|
123
|
-
let allExists = true;
|
|
124
|
-
for (const name of names) {
|
|
125
|
-
if (!existTiffs.includes(job.getJobPath(`${name}.tiff`)))
|
|
126
|
-
allExists = false;
|
|
127
|
-
}
|
|
128
|
-
if (allExists) {
|
|
129
|
-
logger.info({ targetPath, names }, 'FileExists');
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
// Ready to submit
|
|
133
|
-
toSubmit.push(names);
|
|
134
|
-
}
|
|
135
|
-
if (toSubmit.length === 0) {
|
|
139
|
+
const runningJobs = await this.getCurrentJobList(job, logger);
|
|
140
|
+
for (const tiffName of runningJobs)
|
|
141
|
+
existTiffs.add(`${tiffName}.tiff`);
|
|
142
|
+
// Prepare chunk job and individual jobs based on imagery size.
|
|
143
|
+
const jobs = await this.getJobs(job, existTiffs, logger);
|
|
144
|
+
if (jobs.length === 0) {
|
|
136
145
|
logger.info('NoJobs');
|
|
137
146
|
return;
|
|
138
147
|
}
|
|
139
148
|
logger.info({
|
|
140
149
|
jobTotal: job.output.files.length,
|
|
141
|
-
jobLeft:
|
|
150
|
+
jobLeft: jobs.length,
|
|
142
151
|
jobQueue: JobQueue,
|
|
143
152
|
jobDefinition: JobDefinition,
|
|
144
153
|
}, 'JobSubmit');
|
|
145
|
-
for (const names of
|
|
146
|
-
const jobStatus = await BatchJob.batchOne(jobPath, job,
|
|
154
|
+
for (const names of jobs) {
|
|
155
|
+
const jobStatus = await BatchJob.batchOne(jobPath, job, names, commit);
|
|
147
156
|
logger.info(jobStatus, 'JobSubmitted');
|
|
148
157
|
}
|
|
149
158
|
if (!commit) {
|
|
@@ -155,11 +164,16 @@ export class BatchJob {
|
|
|
155
164
|
* Prepare the jobs from job files, and chunk the small images into single
|
|
156
165
|
* @returns List of jobs including single job and chunk jobs.
|
|
157
166
|
*/
|
|
158
|
-
static
|
|
167
|
+
static getJobs(job, existing, log) {
|
|
159
168
|
const jobs = [];
|
|
160
169
|
let chunkJob = [];
|
|
161
170
|
let chunkUnit = 0; // Calculate the chunkUnit based on the size
|
|
162
171
|
for (const file of job.output.files) {
|
|
172
|
+
const outputFile = `${file.name}.tiff`;
|
|
173
|
+
if (existing.has(outputFile)) {
|
|
174
|
+
log.debug({ fileName: outputFile }, 'Skip:Exists');
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
163
177
|
const imageSize = file.width / job.output.gsd;
|
|
164
178
|
if (imageSize > 16385) {
|
|
165
179
|
jobs.push([file.name]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"imagery.config.d.ts","sourceRoot":"","sources":["../../../src/cli/cogify/imagery.config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"imagery.config.d.ts","sourceRoot":"","sources":["../../../src/cli/cogify/imagery.config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAezF;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAezF"}
|
|
@@ -10,7 +10,6 @@ export async function insertConfigImagery(job, logger) {
|
|
|
10
10
|
const configImagery = {
|
|
11
11
|
id: imgId,
|
|
12
12
|
name: job.name,
|
|
13
|
-
createdAt: now,
|
|
14
13
|
updatedAt: now,
|
|
15
14
|
projection: job.tileMatrix.projection.code,
|
|
16
15
|
tileMatrix: job.tileMatrix.identifier,
|
|
@@ -37,7 +36,6 @@ export async function insertConfigTileSet(job, logger) {
|
|
|
37
36
|
name: job.name,
|
|
38
37
|
layers: [{ [job.tileMatrix.projection.code]: imId, name: job.name, minZoom: 0, maxZoom: 32 }],
|
|
39
38
|
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
|
40
|
-
createdAt: now,
|
|
41
39
|
updatedAt: now,
|
|
42
40
|
};
|
|
43
41
|
if (Config.TileSet.isWriteable())
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CommandLineAction } from '@rushstack/ts-command-line';
|
|
2
|
+
export declare const DefaultConfig = "config/";
|
|
3
|
+
export declare const DefaultOutput = "config/config.json";
|
|
4
|
+
export declare class CommandBundle extends CommandLineAction {
|
|
5
|
+
private config;
|
|
6
|
+
private output;
|
|
7
|
+
constructor();
|
|
8
|
+
protected onDefineParameters(): void;
|
|
9
|
+
onExecute(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=action.bundle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.bundle.d.ts","sourceRoot":"","sources":["../../../src/cli/config/action.bundle.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAA8B,MAAM,4BAA4B,CAAC;AAE3F,eAAO,MAAM,aAAa,YAAY,CAAC;AACvC,eAAO,MAAM,aAAa,uBAAuB,CAAC;AAElD,qBAAa,aAAc,SAAQ,iBAAiB;IAClD,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,MAAM,CAA6B;;IAU3C,SAAS,CAAC,kBAAkB,IAAI,IAAI;IAa9B,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CASjC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { fsa, LogConfig } from '@basemaps/shared';
|
|
2
|
+
import { ConfigJson } from '@basemaps/config';
|
|
3
|
+
import { CommandLineAction } from '@rushstack/ts-command-line';
|
|
4
|
+
export const DefaultConfig = 'config/';
|
|
5
|
+
export const DefaultOutput = 'config/config.json';
|
|
6
|
+
export class CommandBundle extends CommandLineAction {
|
|
7
|
+
constructor() {
|
|
8
|
+
super({
|
|
9
|
+
actionName: 'bundle',
|
|
10
|
+
summary: 'bundle a config json from config files',
|
|
11
|
+
documentation: 'Given a path of config files and bundle them into one config json',
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
onDefineParameters() {
|
|
15
|
+
this.config = this.defineStringParameter({
|
|
16
|
+
argumentName: 'CONFIG',
|
|
17
|
+
parameterLongName: '--config',
|
|
18
|
+
description: 'Path of config files',
|
|
19
|
+
});
|
|
20
|
+
this.output = this.defineStringParameter({
|
|
21
|
+
argumentName: 'OUTPUT',
|
|
22
|
+
parameterLongName: '--output',
|
|
23
|
+
description: 'Output of the bundle file',
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async onExecute() {
|
|
27
|
+
var _a, _b;
|
|
28
|
+
const logger = LogConfig.get();
|
|
29
|
+
const config = (_a = this.config.value) !== null && _a !== void 0 ? _a : DefaultConfig;
|
|
30
|
+
const bundle = (_b = this.output.value) !== null && _b !== void 0 ? _b : DefaultOutput;
|
|
31
|
+
const mem = await ConfigJson.fromPath(config, logger);
|
|
32
|
+
await fsa.writeJson(bundle, mem.toJson());
|
|
33
|
+
logger.info({ path: bundle }, 'ConfigBundled');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseConfig } from '@basemaps/config';
|
|
2
|
+
import { CommandLineAction } from '@rushstack/ts-command-line';
|
|
3
|
+
export declare class CommandImport extends CommandLineAction {
|
|
4
|
+
private config;
|
|
5
|
+
private commit;
|
|
6
|
+
promises: Promise<boolean>[];
|
|
7
|
+
/** List of paths to invalidate at the end of the request */
|
|
8
|
+
invalidations: string[];
|
|
9
|
+
constructor();
|
|
10
|
+
protected onDefineParameters(): void;
|
|
11
|
+
onExecute(): Promise<void>;
|
|
12
|
+
update(config: BaseConfig, commit: boolean): void;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=action.import.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.import.d.ts","sourceRoot":"","sources":["../../../src/cli/config/action.import.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAuC,MAAM,kBAAkB,CAAC;AACnF,OAAO,EAAE,iBAAiB,EAAwD,MAAM,4BAA4B,CAAC;AAIrH,qBAAa,aAAc,SAAQ,iBAAiB;IAClD,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,MAAM,CAA2B;IAEzC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,CAAM;IAClC,4DAA4D;IAC5D,aAAa,EAAE,MAAM,EAAE,CAAM;;IAU7B,SAAS,CAAC,kBAAkB,IAAI,IAAI;IAc9B,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IA0ChC,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;CASlD"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Env, fsa, LogConfig } from '@basemaps/shared';
|
|
2
|
+
import { ConfigProviderMemory } from '@basemaps/config';
|
|
3
|
+
import { CommandLineAction } from '@rushstack/ts-command-line';
|
|
4
|
+
import { Q, Updater } from './config.update.js';
|
|
5
|
+
import { invalidateCache } from '../util.js';
|
|
6
|
+
export class CommandImport extends CommandLineAction {
|
|
7
|
+
constructor() {
|
|
8
|
+
super({
|
|
9
|
+
actionName: 'import',
|
|
10
|
+
summary: 'import a config json into dynamodb',
|
|
11
|
+
documentation: 'Given a valid bundle config json and import them into dynamodb',
|
|
12
|
+
});
|
|
13
|
+
this.promises = [];
|
|
14
|
+
/** List of paths to invalidate at the end of the request */
|
|
15
|
+
this.invalidations = [];
|
|
16
|
+
}
|
|
17
|
+
onDefineParameters() {
|
|
18
|
+
this.config = this.defineStringParameter({
|
|
19
|
+
argumentName: 'CONFIG',
|
|
20
|
+
parameterLongName: '--config',
|
|
21
|
+
description: 'Path of config json',
|
|
22
|
+
required: true,
|
|
23
|
+
});
|
|
24
|
+
this.commit = this.defineFlagParameter({
|
|
25
|
+
parameterLongName: '--commit',
|
|
26
|
+
description: 'Actually start the import',
|
|
27
|
+
required: false,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async onExecute() {
|
|
31
|
+
var _a;
|
|
32
|
+
const logger = LogConfig.get();
|
|
33
|
+
logger.level = 'trace';
|
|
34
|
+
const commit = (_a = this.commit.value) !== null && _a !== void 0 ? _a : false;
|
|
35
|
+
const config = this.config.value;
|
|
36
|
+
if (config == null)
|
|
37
|
+
throw new Error('Please provide a config json');
|
|
38
|
+
const HostPrefix = Env.isProduction() ? '' : 'dev.';
|
|
39
|
+
const healthEndpoint = `https://${HostPrefix}basemaps.linz.govt.nz/v1/health`;
|
|
40
|
+
logger.info({ url: healthEndpoint }, 'Import:ValidateHealth');
|
|
41
|
+
if (commit) {
|
|
42
|
+
const res = await fetch(healthEndpoint);
|
|
43
|
+
if (!res.ok)
|
|
44
|
+
throw new Error('Cannot update basemaps is unhealthy');
|
|
45
|
+
}
|
|
46
|
+
logger.info({ config }, 'Import:Load');
|
|
47
|
+
const configJson = await fsa.readJson(config);
|
|
48
|
+
const mem = ConfigProviderMemory.fromJson(configJson);
|
|
49
|
+
mem.createVirtualTileSets();
|
|
50
|
+
logger.info({ config }, 'Import:Start');
|
|
51
|
+
for (const config of mem.objects.values())
|
|
52
|
+
this.update(config, commit);
|
|
53
|
+
await Promise.all(this.promises);
|
|
54
|
+
if (commit && this.invalidations.length > 0) {
|
|
55
|
+
// Lots of invalidations just invalidate everything
|
|
56
|
+
if (this.invalidations.length > 10) {
|
|
57
|
+
await invalidateCache('/*', commit);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
await invalidateCache(this.invalidations, commit);
|
|
61
|
+
}
|
|
62
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
63
|
+
const res = await fetch(healthEndpoint);
|
|
64
|
+
if (!res.ok)
|
|
65
|
+
throw new Error('Basemaps is unhealthy');
|
|
66
|
+
}
|
|
67
|
+
if (commit !== true)
|
|
68
|
+
logger.info('DryRun:Done');
|
|
69
|
+
}
|
|
70
|
+
update(config, commit) {
|
|
71
|
+
const promise = Q(async () => {
|
|
72
|
+
const updater = new Updater(config, commit);
|
|
73
|
+
const hasChanges = await updater.reconcile();
|
|
74
|
+
if (hasChanges)
|
|
75
|
+
this.invalidations.push(updater.invalidatePath());
|
|
76
|
+
return true;
|
|
77
|
+
});
|
|
78
|
+
this.promises.push(promise);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { LogType } from '@basemaps/shared';
|
|
2
|
+
import diff from 'deep-diff';
|
|
3
|
+
export declare const IgnoredProperties: string[];
|
|
4
|
+
export declare class ConfigDiff {
|
|
5
|
+
static printDiff<T>(changes: diff.Diff<T, T>[]): string;
|
|
6
|
+
static showDiff<T extends {
|
|
7
|
+
id: string;
|
|
8
|
+
}>(type: string, oldData: T, newData: T, logger: LogType): boolean;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=config.diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.diff.d.ts","sourceRoot":"","sources":["../../../src/cli/config/config.diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,eAAO,MAAM,iBAAiB,UAAyD,CAAC;AAExF,qBAAa,UAAU;IACrB,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM;IA0BvD,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO;CAS1G"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import c from 'ansi-colors';
|
|
2
|
+
import diff from 'deep-diff';
|
|
3
|
+
export const IgnoredProperties = ['id', 'createdAt', 'updatedAt', 'year', 'resolution'];
|
|
4
|
+
export class ConfigDiff {
|
|
5
|
+
static printDiff(changes) {
|
|
6
|
+
let output = '';
|
|
7
|
+
let isArray = false;
|
|
8
|
+
for (const change of changes) {
|
|
9
|
+
if (change.kind === 'A') {
|
|
10
|
+
if (change.path)
|
|
11
|
+
output += change.path.join();
|
|
12
|
+
output += this.printDiff([change.item]);
|
|
13
|
+
isArray = true; // Stop displaying the array changes for each line.
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
if (isArray)
|
|
17
|
+
continue;
|
|
18
|
+
if (change.kind === 'E') {
|
|
19
|
+
if (change.path)
|
|
20
|
+
output += change.path.join();
|
|
21
|
+
output += c.green('\t+' + JSON.stringify(change.rhs));
|
|
22
|
+
output += c.red('\t-' + JSON.stringify(change.lhs)) + '\n';
|
|
23
|
+
}
|
|
24
|
+
else if (change.kind === 'N') {
|
|
25
|
+
if (change.path)
|
|
26
|
+
output += change.path.join();
|
|
27
|
+
output += c.green('\t+' + JSON.stringify(change.rhs)) + '\n';
|
|
28
|
+
}
|
|
29
|
+
else if (change.kind === 'D') {
|
|
30
|
+
if (change.path)
|
|
31
|
+
output += change.path.join();
|
|
32
|
+
output += c.red('\t-' + JSON.stringify(change.lhs)) + '\n';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return output;
|
|
37
|
+
}
|
|
38
|
+
static showDiff(type, oldData, newData, logger) {
|
|
39
|
+
const changes = diff.diff(oldData, newData, (_path, key) => IgnoredProperties.indexOf(key) >= 0);
|
|
40
|
+
if (changes) {
|
|
41
|
+
logger.info({ type, record: newData.id }, 'Changes');
|
|
42
|
+
ConfigDiff.printDiff(changes);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BaseConfig, ConfigTileSet, ConfigImagery, ConfigProvider, ConfigVectorStyle } from '@basemaps/config';
|
|
2
|
+
import { BasemapsConfigObject } from '@basemaps/config/build/base.config.js';
|
|
3
|
+
import { LogType } from '@basemaps/shared';
|
|
4
|
+
export declare const Q: import("p-limit").LimitFunction;
|
|
5
|
+
export declare class Updater<S extends BaseConfig = BaseConfig> {
|
|
6
|
+
config: S;
|
|
7
|
+
prefix: string;
|
|
8
|
+
isCommit: boolean;
|
|
9
|
+
logger: LogType;
|
|
10
|
+
/**
|
|
11
|
+
* Class to apply an TileSetConfig source to the tile metadata db
|
|
12
|
+
* @param config a string or TileSetConfig to use
|
|
13
|
+
*/
|
|
14
|
+
constructor(config: S, isCommit: boolean);
|
|
15
|
+
getDB(): BasemapsConfigObject<ConfigTileSet | ConfigImagery | ConfigProvider | ConfigVectorStyle>;
|
|
16
|
+
getConfig(): ConfigTileSet | ConfigImagery | ConfigProvider | ConfigVectorStyle;
|
|
17
|
+
invalidatePath(): string;
|
|
18
|
+
/**
|
|
19
|
+
* Reconcile the differences between the config and the tile metadata DB and update if changed.
|
|
20
|
+
*/
|
|
21
|
+
reconcile(): Promise<boolean>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=config.update.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.update.d.ts","sourceRoot":"","sources":["../../../src/cli/config/config.update.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAGV,aAAa,EACb,aAAa,EACb,cAAc,EACd,iBAAiB,EAElB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAC7E,OAAO,EAAa,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAItD,eAAO,MAAM,CAAC,iCAAa,CAAC;AAE5B,qBAAa,OAAO,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU;IACpD,MAAM,EAAE,CAAC,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAEhB;;;OAGG;gBACS,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,OAAO;IASxC,KAAK,IAAI,oBAAoB,CAAC,aAAa,GAAG,aAAa,GAAG,cAAc,GAAG,iBAAiB,CAAC;IAQjG,SAAS,IAAI,aAAa,GAAG,aAAa,GAAG,cAAc,GAAG,iBAAiB;IAQ/E,cAAc,IAAI,MAAM;IAMxB;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAkBpC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ConfigDynamoBase, Config, ConfigPrefix, } from '@basemaps/config';
|
|
2
|
+
import { LogConfig } from '@basemaps/shared';
|
|
3
|
+
import { ConfigDiff } from './config.diff.js';
|
|
4
|
+
import PLimit from 'p-limit';
|
|
5
|
+
export const Q = PLimit(10);
|
|
6
|
+
export class Updater {
|
|
7
|
+
/**
|
|
8
|
+
* Class to apply an TileSetConfig source to the tile metadata db
|
|
9
|
+
* @param config a string or TileSetConfig to use
|
|
10
|
+
*/
|
|
11
|
+
constructor(config, isCommit) {
|
|
12
|
+
this.config = config;
|
|
13
|
+
const prefix = Config.getPrefix(config.id);
|
|
14
|
+
if (prefix == null)
|
|
15
|
+
throw new Error(`Incorrect Config Id ${config.id}`);
|
|
16
|
+
this.prefix = prefix;
|
|
17
|
+
this.isCommit = isCommit ? isCommit : false;
|
|
18
|
+
this.logger = LogConfig.get();
|
|
19
|
+
}
|
|
20
|
+
getDB() {
|
|
21
|
+
if (this.prefix === ConfigPrefix.Imagery)
|
|
22
|
+
return Config.Imagery;
|
|
23
|
+
if (this.prefix === ConfigPrefix.TileSet)
|
|
24
|
+
return Config.TileSet;
|
|
25
|
+
if (this.prefix === ConfigPrefix.Provider)
|
|
26
|
+
return Config.Provider;
|
|
27
|
+
if (this.prefix === ConfigPrefix.Style)
|
|
28
|
+
return Config.Style;
|
|
29
|
+
throw Error(`Unable to find the database table for prefix ${this.prefix}`);
|
|
30
|
+
}
|
|
31
|
+
getConfig() {
|
|
32
|
+
if (this.prefix === ConfigPrefix.Imagery)
|
|
33
|
+
return this.config;
|
|
34
|
+
if (this.prefix === ConfigPrefix.TileSet)
|
|
35
|
+
return this.config;
|
|
36
|
+
if (this.prefix === ConfigPrefix.Provider)
|
|
37
|
+
return this.config;
|
|
38
|
+
if (this.prefix === ConfigPrefix.Style)
|
|
39
|
+
return this.config;
|
|
40
|
+
throw Error(`Failed to cast the config ${this.config}`);
|
|
41
|
+
}
|
|
42
|
+
invalidatePath() {
|
|
43
|
+
if (this.prefix === ConfigPrefix.Provider)
|
|
44
|
+
return '/v1/*/WMTSCapabilities.xml';
|
|
45
|
+
else if (this.prefix === ConfigPrefix.Style)
|
|
46
|
+
return `/v1/tiles/togographic/style/${this.config.id.slice(3)}.json`;
|
|
47
|
+
else
|
|
48
|
+
return `/v1/tiles/${this.config.id.slice(3)}/*`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Reconcile the differences between the config and the tile metadata DB and update if changed.
|
|
52
|
+
*/
|
|
53
|
+
async reconcile() {
|
|
54
|
+
const db = this.getDB();
|
|
55
|
+
const oldData = await db.get(this.config.id);
|
|
56
|
+
const newData = this.getConfig();
|
|
57
|
+
if (oldData == null || ConfigDiff.showDiff(db.prefix, oldData, newData, this.logger)) {
|
|
58
|
+
const operation = oldData == null ? 'Insert' : 'Update';
|
|
59
|
+
this.logger.info({ type: db.prefix, record: newData.id, commit: this.isCommit }, `Change:${operation}`);
|
|
60
|
+
if (this.isCommit) {
|
|
61
|
+
if (db instanceof ConfigDynamoBase)
|
|
62
|
+
await db.put(newData);
|
|
63
|
+
else
|
|
64
|
+
throw new Error('Unable to commit changes to: ' + db.prefix);
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
this.logger.trace({ type: db.prefix, record: newData.id }, 'NoChanges');
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { BaseCommandLine } from '@basemaps/shared/build/cli/base.js';
|
|
3
|
+
import 'source-map-support/register.js';
|
|
4
|
+
export declare class BasemapsConfigCommandLine extends BaseCommandLine {
|
|
5
|
+
constructor();
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,gCAAgC,CAAC;AAOxC,qBAAa,yBAA0B,SAAQ,eAAe;;CAe7D"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { BaseCommandLine } from '@basemaps/shared/build/cli/base.js';
|
|
3
|
+
import 'source-map-support/register.js';
|
|
4
|
+
import { CommandCogCreate } from './cogify/action.cog.js';
|
|
5
|
+
import { CommandJobCreate } from './cogify/action.job.js';
|
|
6
|
+
import { CommandBundle } from './config/action.bundle.js';
|
|
7
|
+
import { CommandImport } from './config/action.import.js';
|
|
8
|
+
import { CommandScreenShot } from './screenshot/action.screenshot.js';
|
|
9
|
+
export class BasemapsConfigCommandLine extends BaseCommandLine {
|
|
10
|
+
constructor() {
|
|
11
|
+
super({
|
|
12
|
+
toolFilename: 'bmc',
|
|
13
|
+
toolDescription: 'Basemaps config command tools',
|
|
14
|
+
});
|
|
15
|
+
this.addAction(new CommandCogCreate());
|
|
16
|
+
this.addAction(new CommandJobCreate());
|
|
17
|
+
this.addAction(new CommandBundle());
|
|
18
|
+
this.addAction(new CommandImport());
|
|
19
|
+
this.addAction(new CommandScreenShot());
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { LogType } from '@basemaps/shared';
|
|
2
|
+
import { Browser } from 'playwright';
|
|
3
|
+
import { CommandLineAction } from '@rushstack/ts-command-line';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
export declare const DefaultTestTiles = "./test-tiles/default.test.tiles.json";
|
|
6
|
+
export declare const DefaultHost = "basemaps.linz.govt.nz";
|
|
7
|
+
declare enum TileMatrixIdentifier {
|
|
8
|
+
Nztm2000Quad = "NZTM2000Quad",
|
|
9
|
+
Google = "WebMercatorQuad"
|
|
10
|
+
}
|
|
11
|
+
declare const zTileTest: z.ZodObject<{
|
|
12
|
+
name: z.ZodString;
|
|
13
|
+
tileMatrix: z.ZodNativeEnum<typeof TileMatrixIdentifier>;
|
|
14
|
+
location: z.ZodObject<{
|
|
15
|
+
lat: z.ZodNumber;
|
|
16
|
+
lng: z.ZodNumber;
|
|
17
|
+
z: z.ZodNumber;
|
|
18
|
+
}, "strip", z.ZodTypeAny, {
|
|
19
|
+
z: number;
|
|
20
|
+
lat: number;
|
|
21
|
+
lng: number;
|
|
22
|
+
}, {
|
|
23
|
+
z: number;
|
|
24
|
+
lat: number;
|
|
25
|
+
lng: number;
|
|
26
|
+
}>;
|
|
27
|
+
tileSet: z.ZodString;
|
|
28
|
+
style: z.ZodOptional<z.ZodString>;
|
|
29
|
+
}, "strip", z.ZodTypeAny, {
|
|
30
|
+
style?: string | undefined;
|
|
31
|
+
name: string;
|
|
32
|
+
location: {
|
|
33
|
+
z: number;
|
|
34
|
+
lat: number;
|
|
35
|
+
lng: number;
|
|
36
|
+
};
|
|
37
|
+
tileMatrix: TileMatrixIdentifier;
|
|
38
|
+
tileSet: string;
|
|
39
|
+
}, {
|
|
40
|
+
style?: string | undefined;
|
|
41
|
+
name: string;
|
|
42
|
+
location: {
|
|
43
|
+
z: number;
|
|
44
|
+
lat: number;
|
|
45
|
+
lng: number;
|
|
46
|
+
};
|
|
47
|
+
tileMatrix: TileMatrixIdentifier;
|
|
48
|
+
tileSet: string;
|
|
49
|
+
}>;
|
|
50
|
+
export declare type TileTestSchema = z.infer<typeof zTileTest>;
|
|
51
|
+
export declare class CommandScreenShot extends CommandLineAction {
|
|
52
|
+
private config;
|
|
53
|
+
private host;
|
|
54
|
+
private tiles;
|
|
55
|
+
constructor();
|
|
56
|
+
protected onDefineParameters(): void;
|
|
57
|
+
onExecute(): Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
export declare function takeScreenshots(host: string, tiles: string, chrome: Browser, logger: LogType): Promise<void>;
|
|
60
|
+
export {};
|
|
61
|
+
//# sourceMappingURL=action.screenshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.screenshot.d.ts","sourceRoot":"","sources":["../../../src/cli/screenshot/action.screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAExE,OAAO,EAAE,OAAO,EAAY,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAA8B,MAAM,4BAA4B,CAAC;AAC3F,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,gBAAgB,yCAAyC,CAAC;AACvE,eAAO,MAAM,WAAW,0BAA0B,CAAC;AAEnD,aAAK,oBAAoB;IACvB,YAAY,iBAAiB;IAC7B,MAAM,oBAAoB;CAC3B;AAQD,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAMb,CAAC;AAEH,oBAAY,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAEvD,qBAAa,iBAAkB,SAAQ,iBAAiB;IACtD,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,IAAI,CAA6B;IACzC,OAAO,CAAC,KAAK,CAA6B;;IAU1C,SAAS,CAAC,kBAAkB,IAAI,IAAI;IAsB9B,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CAyBjC;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAkClH"}
|