@backstage/plugin-techdocs-backend 1.10.14-next.1 → 1.11.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 +43 -0
- package/alpha/package.json +1 -1
- package/dist/DocsBuilder/BuildMetadataStorage.cjs.js +30 -0
- package/dist/DocsBuilder/BuildMetadataStorage.cjs.js.map +1 -0
- package/dist/DocsBuilder/builder.cjs.js +172 -0
- package/dist/DocsBuilder/builder.cjs.js.map +1 -0
- package/dist/alpha.cjs.js +3 -120
- package/dist/alpha.cjs.js.map +1 -1
- package/dist/alpha.d.ts +3 -6
- package/dist/cache/TechDocsCache.cjs.js +70 -0
- package/dist/cache/TechDocsCache.cjs.js.map +1 -0
- package/dist/cache/cacheMiddleware.cjs.js +52 -0
- package/dist/cache/cacheMiddleware.cjs.js.map +1 -0
- package/dist/index.cjs.js +10 -866
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +9 -2
- package/dist/plugin.cjs.js +130 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/search/DefaultTechDocsCollator.cjs.js +144 -0
- package/dist/search/DefaultTechDocsCollator.cjs.js.map +1 -0
- package/dist/search/index.cjs.js +15 -0
- package/dist/search/index.cjs.js.map +1 -0
- package/dist/service/CachedEntityLoader.cjs.js +41 -0
- package/dist/service/CachedEntityLoader.cjs.js.map +1 -0
- package/dist/service/DefaultDocsBuildStrategy.cjs.js +19 -0
- package/dist/service/DefaultDocsBuildStrategy.cjs.js.map +1 -0
- package/dist/service/DocsSynchronizer.cjs.js +197 -0
- package/dist/service/DocsSynchronizer.cjs.js.map +1 -0
- package/dist/service/router.cjs.js +219 -0
- package/dist/service/router.cjs.js.map +1 -0
- package/package.json +17 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# @backstage/plugin-techdocs-backend
|
|
2
2
|
|
|
3
|
+
## 1.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3109c24: The export for the new backend system at the `/alpha` export is now also available via the main entry point, which means that you can remove the `/alpha` suffix from the import.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 094eaa3: Remove references to in-repo backend-common
|
|
12
|
+
- fbdc631: Allow to pass StorageOptions to GCS Publisher
|
|
13
|
+
- Updated dependencies
|
|
14
|
+
- @backstage/plugin-search-backend-module-techdocs@0.3.0
|
|
15
|
+
- @backstage/plugin-catalog-node@1.13.1
|
|
16
|
+
- @backstage/plugin-techdocs-node@1.12.12
|
|
17
|
+
- @backstage/integration@1.15.1
|
|
18
|
+
- @backstage/catalog-client@1.7.1
|
|
19
|
+
- @backstage/backend-plugin-api@1.0.1
|
|
20
|
+
- @backstage/catalog-model@1.7.0
|
|
21
|
+
- @backstage/config@1.2.0
|
|
22
|
+
- @backstage/errors@1.2.4
|
|
23
|
+
- @backstage/plugin-catalog-common@1.1.0
|
|
24
|
+
- @backstage/plugin-permission-common@0.8.1
|
|
25
|
+
- @backstage/plugin-techdocs-common@0.1.0
|
|
26
|
+
|
|
27
|
+
## 1.10.14-next.2
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- fbdc631: Allow to pass StorageOptions to GCS Publisher
|
|
32
|
+
- Updated dependencies
|
|
33
|
+
- @backstage/plugin-catalog-node@1.13.1-next.1
|
|
34
|
+
- @backstage/plugin-techdocs-node@1.12.12-next.2
|
|
35
|
+
- @backstage/integration@1.15.1-next.1
|
|
36
|
+
- @backstage/catalog-client@1.7.1-next.0
|
|
37
|
+
- @backstage/backend-plugin-api@1.0.1-next.1
|
|
38
|
+
- @backstage/catalog-model@1.7.0
|
|
39
|
+
- @backstage/config@1.2.0
|
|
40
|
+
- @backstage/errors@1.2.4
|
|
41
|
+
- @backstage/plugin-catalog-common@1.1.0
|
|
42
|
+
- @backstage/plugin-permission-common@0.8.1
|
|
43
|
+
- @backstage/plugin-search-backend-module-techdocs@0.2.3-next.2
|
|
44
|
+
- @backstage/plugin-techdocs-common@0.1.0
|
|
45
|
+
|
|
3
46
|
## 1.10.14-next.1
|
|
4
47
|
|
|
5
48
|
### Patch Changes
|
package/alpha/package.json
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const lastUpdatedRecord = {};
|
|
4
|
+
class BuildMetadataStorage {
|
|
5
|
+
entityUid;
|
|
6
|
+
lastUpdatedRecord;
|
|
7
|
+
constructor(entityUid) {
|
|
8
|
+
this.entityUid = entityUid;
|
|
9
|
+
this.lastUpdatedRecord = lastUpdatedRecord;
|
|
10
|
+
}
|
|
11
|
+
setLastUpdated() {
|
|
12
|
+
this.lastUpdatedRecord[this.entityUid] = Date.now();
|
|
13
|
+
}
|
|
14
|
+
getLastUpdated() {
|
|
15
|
+
return this.lastUpdatedRecord[this.entityUid];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const shouldCheckForUpdate = (entityUid) => {
|
|
19
|
+
const lastUpdated = new BuildMetadataStorage(entityUid).getLastUpdated();
|
|
20
|
+
if (lastUpdated) {
|
|
21
|
+
if (Date.now() - lastUpdated < 60 * 1e3) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
exports.BuildMetadataStorage = BuildMetadataStorage;
|
|
29
|
+
exports.shouldCheckForUpdate = shouldCheckForUpdate;
|
|
30
|
+
//# sourceMappingURL=BuildMetadataStorage.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BuildMetadataStorage.cjs.js","sources":["../../src/DocsBuilder/BuildMetadataStorage.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Entity uid: unix timestamp\nconst lastUpdatedRecord = {} as Record<string, number>;\n\n/**\n * Store timestamps of the most recent TechDocs update of each Entity. This is\n * used to avoid checking for an update on each and every request to TechDocs.\n */\nexport class BuildMetadataStorage {\n private entityUid: string;\n private lastUpdatedRecord: Record<string, number>;\n\n constructor(entityUid: string) {\n this.entityUid = entityUid;\n this.lastUpdatedRecord = lastUpdatedRecord;\n }\n\n setLastUpdated(): void {\n this.lastUpdatedRecord[this.entityUid] = Date.now();\n }\n\n getLastUpdated(): number | undefined {\n return this.lastUpdatedRecord[this.entityUid];\n }\n}\n\n/**\n * Return false if a check for update has happened in last 60 seconds.\n */\nexport const shouldCheckForUpdate = (entityUid: string) => {\n const lastUpdated = new BuildMetadataStorage(entityUid).getLastUpdated();\n if (lastUpdated) {\n // The difference is in milliseconds\n if (Date.now() - lastUpdated < 60 * 1000) {\n return false;\n }\n }\n return true;\n};\n"],"names":[],"mappings":";;AAiBA,MAAM,oBAAoB,EAAC,CAAA;AAMpB,MAAM,oBAAqB,CAAA;AAAA,EACxB,SAAA,CAAA;AAAA,EACA,iBAAA,CAAA;AAAA,EAER,YAAY,SAAmB,EAAA;AAC7B,IAAA,IAAA,CAAK,SAAY,GAAA,SAAA,CAAA;AACjB,IAAA,IAAA,CAAK,iBAAoB,GAAA,iBAAA,CAAA;AAAA,GAC3B;AAAA,EAEA,cAAuB,GAAA;AACrB,IAAA,IAAA,CAAK,iBAAkB,CAAA,IAAA,CAAK,SAAS,CAAA,GAAI,KAAK,GAAI,EAAA,CAAA;AAAA,GACpD;AAAA,EAEA,cAAqC,GAAA;AACnC,IAAO,OAAA,IAAA,CAAK,iBAAkB,CAAA,IAAA,CAAK,SAAS,CAAA,CAAA;AAAA,GAC9C;AACF,CAAA;AAKa,MAAA,oBAAA,GAAuB,CAAC,SAAsB,KAAA;AACzD,EAAA,MAAM,WAAc,GAAA,IAAI,oBAAqB,CAAA,SAAS,EAAE,cAAe,EAAA,CAAA;AACvE,EAAA,IAAI,WAAa,EAAA;AAEf,IAAA,IAAI,IAAK,CAAA,GAAA,EAAQ,GAAA,WAAA,GAAc,KAAK,GAAM,EAAA;AACxC,MAAO,OAAA,KAAA,CAAA;AAAA,KACT;AAAA,GACF;AACA,EAAO,OAAA,IAAA,CAAA;AACT;;;;;"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var catalogModel = require('@backstage/catalog-model');
|
|
4
|
+
var errors = require('@backstage/errors');
|
|
5
|
+
var pluginTechdocsNode = require('@backstage/plugin-techdocs-node');
|
|
6
|
+
var fs = require('fs-extra');
|
|
7
|
+
var os = require('os');
|
|
8
|
+
var path = require('path');
|
|
9
|
+
var BuildMetadataStorage = require('./BuildMetadataStorage.cjs.js');
|
|
10
|
+
|
|
11
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
12
|
+
|
|
13
|
+
var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
|
|
14
|
+
var os__default = /*#__PURE__*/_interopDefaultCompat(os);
|
|
15
|
+
var path__default = /*#__PURE__*/_interopDefaultCompat(path);
|
|
16
|
+
|
|
17
|
+
class DocsBuilder {
|
|
18
|
+
preparer;
|
|
19
|
+
generator;
|
|
20
|
+
publisher;
|
|
21
|
+
entity;
|
|
22
|
+
logger;
|
|
23
|
+
config;
|
|
24
|
+
scmIntegrations;
|
|
25
|
+
logStream;
|
|
26
|
+
cache;
|
|
27
|
+
constructor({
|
|
28
|
+
preparers,
|
|
29
|
+
generators,
|
|
30
|
+
publisher,
|
|
31
|
+
entity,
|
|
32
|
+
logger,
|
|
33
|
+
config,
|
|
34
|
+
scmIntegrations,
|
|
35
|
+
logStream,
|
|
36
|
+
cache
|
|
37
|
+
}) {
|
|
38
|
+
this.preparer = preparers.get(entity);
|
|
39
|
+
this.generator = generators.get(entity);
|
|
40
|
+
this.publisher = publisher;
|
|
41
|
+
this.entity = entity;
|
|
42
|
+
this.logger = logger;
|
|
43
|
+
this.config = config;
|
|
44
|
+
this.scmIntegrations = scmIntegrations;
|
|
45
|
+
this.logStream = logStream;
|
|
46
|
+
this.cache = cache;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Build the docs and return whether they have been newly generated or have been cached
|
|
50
|
+
* @returns true, if the docs have been built. false, if the cached docs are still up-to-date.
|
|
51
|
+
*/
|
|
52
|
+
async build() {
|
|
53
|
+
if (!this.entity.metadata.uid) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
"Trying to build documentation for entity not in software catalog"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
this.logger.info(
|
|
59
|
+
`Step 1 of 3: Preparing docs for entity ${catalogModel.stringifyEntityRef(
|
|
60
|
+
this.entity
|
|
61
|
+
)}`
|
|
62
|
+
);
|
|
63
|
+
let storedEtag;
|
|
64
|
+
if (await this.publisher.hasDocsBeenGenerated(this.entity)) {
|
|
65
|
+
try {
|
|
66
|
+
storedEtag = (await this.publisher.fetchTechDocsMetadata({
|
|
67
|
+
namespace: this.entity.metadata.namespace ?? catalogModel.DEFAULT_NAMESPACE,
|
|
68
|
+
kind: this.entity.kind,
|
|
69
|
+
name: this.entity.metadata.name
|
|
70
|
+
})).etag;
|
|
71
|
+
} catch (err) {
|
|
72
|
+
this.logger.warn(
|
|
73
|
+
`Unable to read techdocs_metadata.json, proceeding with fresh build, error ${err}.`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
let preparedDir;
|
|
78
|
+
let newEtag;
|
|
79
|
+
try {
|
|
80
|
+
const preparerResponse = await this.preparer.prepare(this.entity, {
|
|
81
|
+
etag: storedEtag,
|
|
82
|
+
logger: this.logger
|
|
83
|
+
});
|
|
84
|
+
preparedDir = preparerResponse.preparedDir;
|
|
85
|
+
newEtag = preparerResponse.etag;
|
|
86
|
+
} catch (err) {
|
|
87
|
+
if (errors.isError(err) && err.name === "NotModifiedError") {
|
|
88
|
+
new BuildMetadataStorage.BuildMetadataStorage(this.entity.metadata.uid).setLastUpdated();
|
|
89
|
+
this.logger.debug(
|
|
90
|
+
`Docs for ${catalogModel.stringifyEntityRef(
|
|
91
|
+
this.entity
|
|
92
|
+
)} are unmodified. Using cache, skipping generate and prepare`
|
|
93
|
+
);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
this.logger.info(
|
|
99
|
+
`Prepare step completed for entity ${catalogModel.stringifyEntityRef(
|
|
100
|
+
this.entity
|
|
101
|
+
)}, stored at ${preparedDir}`
|
|
102
|
+
);
|
|
103
|
+
this.logger.info(
|
|
104
|
+
`Step 2 of 3: Generating docs for entity ${catalogModel.stringifyEntityRef(
|
|
105
|
+
this.entity
|
|
106
|
+
)}`
|
|
107
|
+
);
|
|
108
|
+
const workingDir = this.config.getOptionalString(
|
|
109
|
+
"backend.workingDirectory"
|
|
110
|
+
);
|
|
111
|
+
const tmpdirPath = workingDir || os__default.default.tmpdir();
|
|
112
|
+
const tmpdirResolvedPath = fs__default.default.realpathSync(tmpdirPath);
|
|
113
|
+
const outputDir = await fs__default.default.mkdtemp(
|
|
114
|
+
path__default.default.join(tmpdirResolvedPath, "techdocs-tmp-")
|
|
115
|
+
);
|
|
116
|
+
const parsedLocationAnnotation = pluginTechdocsNode.getLocationForEntity(
|
|
117
|
+
this.entity,
|
|
118
|
+
this.scmIntegrations
|
|
119
|
+
);
|
|
120
|
+
await this.generator.run({
|
|
121
|
+
inputDir: preparedDir,
|
|
122
|
+
outputDir,
|
|
123
|
+
parsedLocationAnnotation,
|
|
124
|
+
etag: newEtag,
|
|
125
|
+
logger: this.logger,
|
|
126
|
+
logStream: this.logStream,
|
|
127
|
+
siteOptions: {
|
|
128
|
+
name: this.entity.metadata.title ?? this.entity.metadata.name
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
if (this.preparer.shouldCleanPreparedDirectory()) {
|
|
132
|
+
this.logger.debug(
|
|
133
|
+
`Removing prepared directory ${preparedDir} since the site has been generated`
|
|
134
|
+
);
|
|
135
|
+
try {
|
|
136
|
+
fs__default.default.remove(preparedDir);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
errors.assertError(error);
|
|
139
|
+
this.logger.debug(`Error removing prepared directory ${error.message}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
this.logger.info(
|
|
143
|
+
`Step 3 of 3: Publishing docs for entity ${catalogModel.stringifyEntityRef(
|
|
144
|
+
this.entity
|
|
145
|
+
)}`
|
|
146
|
+
);
|
|
147
|
+
const published = await this.publisher.publish({
|
|
148
|
+
entity: this.entity,
|
|
149
|
+
directory: outputDir
|
|
150
|
+
});
|
|
151
|
+
if (this.cache && published && published?.objects?.length) {
|
|
152
|
+
this.logger.debug(
|
|
153
|
+
`Invalidating ${published.objects.length} cache objects`
|
|
154
|
+
);
|
|
155
|
+
await this.cache.invalidateMultiple(published.objects);
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
fs__default.default.remove(outputDir);
|
|
159
|
+
this.logger.debug(
|
|
160
|
+
`Removing generated directory ${outputDir} since the site has been published`
|
|
161
|
+
);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
errors.assertError(error);
|
|
164
|
+
this.logger.debug(`Error removing generated directory ${error.message}`);
|
|
165
|
+
}
|
|
166
|
+
new BuildMetadataStorage.BuildMetadataStorage(this.entity.metadata.uid).setLastUpdated();
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
exports.DocsBuilder = DocsBuilder;
|
|
172
|
+
//# sourceMappingURL=builder.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder.cjs.js","sources":["../../src/DocsBuilder/builder.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n DEFAULT_NAMESPACE,\n Entity,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { assertError, isError } from '@backstage/errors';\nimport { ScmIntegrationRegistry } from '@backstage/integration';\nimport {\n GeneratorBase,\n GeneratorBuilder,\n getLocationForEntity,\n PreparerBase,\n PreparerBuilder,\n PublisherBase,\n} from '@backstage/plugin-techdocs-node';\nimport fs from 'fs-extra';\nimport os from 'os';\nimport path from 'path';\nimport { Writable } from 'stream';\nimport { Logger } from 'winston';\nimport { BuildMetadataStorage } from './BuildMetadataStorage';\nimport { TechDocsCache } from '../cache';\n\ntype DocsBuilderArguments = {\n preparers: PreparerBuilder;\n generators: GeneratorBuilder;\n publisher: PublisherBase;\n entity: Entity;\n logger: Logger;\n config: Config;\n scmIntegrations: ScmIntegrationRegistry;\n logStream?: Writable;\n cache?: TechDocsCache;\n};\n\nexport class DocsBuilder {\n private preparer: PreparerBase;\n private generator: GeneratorBase;\n private publisher: PublisherBase;\n private entity: Entity;\n private logger: Logger;\n private config: Config;\n private scmIntegrations: ScmIntegrationRegistry;\n private logStream: Writable | undefined;\n private cache?: TechDocsCache;\n\n constructor({\n preparers,\n generators,\n publisher,\n entity,\n logger,\n config,\n scmIntegrations,\n logStream,\n cache,\n }: DocsBuilderArguments) {\n this.preparer = preparers.get(entity);\n this.generator = generators.get(entity);\n this.publisher = publisher;\n this.entity = entity;\n this.logger = logger;\n this.config = config;\n this.scmIntegrations = scmIntegrations;\n this.logStream = logStream;\n this.cache = cache;\n }\n\n /**\n * Build the docs and return whether they have been newly generated or have been cached\n * @returns true, if the docs have been built. false, if the cached docs are still up-to-date.\n */\n public async build(): Promise<boolean> {\n if (!this.entity.metadata.uid) {\n throw new Error(\n 'Trying to build documentation for entity not in software catalog',\n );\n }\n\n /**\n * Prepare (and cache check)\n */\n\n this.logger.info(\n `Step 1 of 3: Preparing docs for entity ${stringifyEntityRef(\n this.entity,\n )}`,\n );\n\n // If available, use the etag stored in techdocs_metadata.json to\n // check if docs are outdated and need to be regenerated.\n let storedEtag: string | undefined;\n if (await this.publisher.hasDocsBeenGenerated(this.entity)) {\n try {\n storedEtag = (\n await this.publisher.fetchTechDocsMetadata({\n namespace: this.entity.metadata.namespace ?? DEFAULT_NAMESPACE,\n kind: this.entity.kind,\n name: this.entity.metadata.name,\n })\n ).etag;\n } catch (err) {\n // Proceed with a fresh build\n this.logger.warn(\n `Unable to read techdocs_metadata.json, proceeding with fresh build, error ${err}.`,\n );\n }\n }\n\n let preparedDir: string;\n let newEtag: string;\n try {\n const preparerResponse = await this.preparer.prepare(this.entity, {\n etag: storedEtag,\n logger: this.logger,\n });\n\n preparedDir = preparerResponse.preparedDir;\n newEtag = preparerResponse.etag;\n } catch (err) {\n if (isError(err) && err.name === 'NotModifiedError') {\n // No need to prepare anymore since cache is valid.\n // Set last check happened to now\n new BuildMetadataStorage(this.entity.metadata.uid).setLastUpdated();\n this.logger.debug(\n `Docs for ${stringifyEntityRef(\n this.entity,\n )} are unmodified. Using cache, skipping generate and prepare`,\n );\n return false;\n }\n throw err;\n }\n\n this.logger.info(\n `Prepare step completed for entity ${stringifyEntityRef(\n this.entity,\n )}, stored at ${preparedDir}`,\n );\n\n /**\n * Generate\n */\n\n this.logger.info(\n `Step 2 of 3: Generating docs for entity ${stringifyEntityRef(\n this.entity,\n )}`,\n );\n\n const workingDir = this.config.getOptionalString(\n 'backend.workingDirectory',\n );\n const tmpdirPath = workingDir || os.tmpdir();\n // Fixes a problem with macOS returning a path that is a symlink\n const tmpdirResolvedPath = fs.realpathSync(tmpdirPath);\n const outputDir = await fs.mkdtemp(\n path.join(tmpdirResolvedPath, 'techdocs-tmp-'),\n );\n\n const parsedLocationAnnotation = getLocationForEntity(\n this.entity,\n this.scmIntegrations,\n );\n await this.generator.run({\n inputDir: preparedDir,\n outputDir,\n parsedLocationAnnotation,\n etag: newEtag,\n logger: this.logger,\n logStream: this.logStream,\n siteOptions: {\n name: this.entity.metadata.title ?? this.entity.metadata.name,\n },\n });\n\n // Remove Prepared directory since it is no longer needed.\n // Caveat: Can not remove prepared directory in case of git preparer since the\n // local git repository is used to get etag on subsequent requests.\n if (this.preparer.shouldCleanPreparedDirectory()) {\n this.logger.debug(\n `Removing prepared directory ${preparedDir} since the site has been generated`,\n );\n try {\n // Not a blocker hence no need to await this.\n fs.remove(preparedDir);\n } catch (error) {\n assertError(error);\n this.logger.debug(`Error removing prepared directory ${error.message}`);\n }\n }\n\n /**\n * Publish\n */\n\n this.logger.info(\n `Step 3 of 3: Publishing docs for entity ${stringifyEntityRef(\n this.entity,\n )}`,\n );\n\n const published = await this.publisher.publish({\n entity: this.entity,\n directory: outputDir,\n });\n\n // Invalidate the cache for any published objects.\n if (this.cache && published && published?.objects?.length) {\n this.logger.debug(\n `Invalidating ${published.objects.length} cache objects`,\n );\n await this.cache.invalidateMultiple(published.objects);\n }\n\n try {\n // Not a blocker hence no need to await this.\n fs.remove(outputDir);\n this.logger.debug(\n `Removing generated directory ${outputDir} since the site has been published`,\n );\n } catch (error) {\n assertError(error);\n this.logger.debug(`Error removing generated directory ${error.message}`);\n }\n\n // Update the last check time for the entity\n new BuildMetadataStorage(this.entity.metadata.uid).setLastUpdated();\n\n return true;\n }\n}\n"],"names":["stringifyEntityRef","DEFAULT_NAMESPACE","isError","BuildMetadataStorage","os","fs","path","getLocationForEntity","assertError"],"mappings":"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"}
|
package/dist/alpha.cjs.js
CHANGED
|
@@ -2,126 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
var
|
|
6
|
-
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
7
|
-
var pluginTechdocsNode = require('@backstage/plugin-techdocs-node');
|
|
8
|
-
var pluginTechdocsBackend = require('@backstage/plugin-techdocs-backend');
|
|
9
|
-
var alpha = require('@backstage/plugin-catalog-node/alpha');
|
|
5
|
+
var plugin = require('./plugin.cjs.js');
|
|
10
6
|
|
|
11
|
-
const
|
|
12
|
-
pluginId: "techdocs",
|
|
13
|
-
register(env) {
|
|
14
|
-
let docsBuildStrategy;
|
|
15
|
-
let buildLogTransport;
|
|
16
|
-
env.registerExtensionPoint(pluginTechdocsNode.techdocsBuildsExtensionPoint, {
|
|
17
|
-
setBuildStrategy(buildStrategy) {
|
|
18
|
-
if (docsBuildStrategy) {
|
|
19
|
-
throw new Error("DocsBuildStrategy may only be set once");
|
|
20
|
-
}
|
|
21
|
-
docsBuildStrategy = buildStrategy;
|
|
22
|
-
},
|
|
23
|
-
setBuildLogTransport(transport) {
|
|
24
|
-
if (buildLogTransport) {
|
|
25
|
-
throw new Error("BuildLogTransport may only be set once");
|
|
26
|
-
}
|
|
27
|
-
buildLogTransport = transport;
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
let customTechdocsGenerator;
|
|
31
|
-
env.registerExtensionPoint(pluginTechdocsNode.techdocsGeneratorExtensionPoint, {
|
|
32
|
-
setTechdocsGenerator(generator) {
|
|
33
|
-
if (customTechdocsGenerator) {
|
|
34
|
-
throw new Error("TechdocsGenerator may only be set once");
|
|
35
|
-
}
|
|
36
|
-
customTechdocsGenerator = generator;
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
const customPreparers = /* @__PURE__ */ new Map();
|
|
40
|
-
env.registerExtensionPoint(pluginTechdocsNode.techdocsPreparerExtensionPoint, {
|
|
41
|
-
registerPreparer(protocol, preparer) {
|
|
42
|
-
if (customPreparers.has(protocol)) {
|
|
43
|
-
throw new Error(
|
|
44
|
-
`Preparer for protocol ${protocol} is already registered`
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
customPreparers.set(protocol, preparer);
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
let customTechdocsPublisher;
|
|
51
|
-
env.registerExtensionPoint(pluginTechdocsNode.techdocsPublisherExtensionPoint, {
|
|
52
|
-
registerPublisher(type, publisher) {
|
|
53
|
-
if (customTechdocsPublisher) {
|
|
54
|
-
throw new Error(`Publisher for type ${type} is already registered`);
|
|
55
|
-
}
|
|
56
|
-
customTechdocsPublisher = publisher;
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
env.registerInit({
|
|
60
|
-
deps: {
|
|
61
|
-
config: backendPluginApi.coreServices.rootConfig,
|
|
62
|
-
logger: backendPluginApi.coreServices.logger,
|
|
63
|
-
urlReader: backendPluginApi.coreServices.urlReader,
|
|
64
|
-
http: backendPluginApi.coreServices.httpRouter,
|
|
65
|
-
discovery: backendPluginApi.coreServices.discovery,
|
|
66
|
-
cache: backendPluginApi.coreServices.cache,
|
|
67
|
-
httpAuth: backendPluginApi.coreServices.httpAuth,
|
|
68
|
-
auth: backendPluginApi.coreServices.auth,
|
|
69
|
-
catalog: alpha.catalogServiceRef
|
|
70
|
-
},
|
|
71
|
-
async init({
|
|
72
|
-
config,
|
|
73
|
-
logger,
|
|
74
|
-
urlReader,
|
|
75
|
-
http,
|
|
76
|
-
discovery,
|
|
77
|
-
cache,
|
|
78
|
-
httpAuth,
|
|
79
|
-
auth,
|
|
80
|
-
catalog
|
|
81
|
-
}) {
|
|
82
|
-
const winstonLogger = backendCommon.loggerToWinstonLogger(logger);
|
|
83
|
-
const preparers = await pluginTechdocsNode.Preparers.fromConfig(config, {
|
|
84
|
-
reader: urlReader,
|
|
85
|
-
logger: winstonLogger
|
|
86
|
-
});
|
|
87
|
-
for (const [protocol, preparer] of customPreparers.entries()) {
|
|
88
|
-
preparers.register(protocol, preparer);
|
|
89
|
-
}
|
|
90
|
-
const generators = await pluginTechdocsNode.Generators.fromConfig(config, {
|
|
91
|
-
logger: winstonLogger,
|
|
92
|
-
customGenerator: customTechdocsGenerator
|
|
93
|
-
});
|
|
94
|
-
const publisher = await pluginTechdocsNode.Publisher.fromConfig(config, {
|
|
95
|
-
logger: winstonLogger,
|
|
96
|
-
discovery,
|
|
97
|
-
customPublisher: customTechdocsPublisher
|
|
98
|
-
});
|
|
99
|
-
await publisher.getReadiness();
|
|
100
|
-
const cacheManager = backendCommon.cacheToPluginCacheManager(cache);
|
|
101
|
-
http.use(
|
|
102
|
-
await pluginTechdocsBackend.createRouter({
|
|
103
|
-
logger: winstonLogger,
|
|
104
|
-
cache: cacheManager,
|
|
105
|
-
docsBuildStrategy,
|
|
106
|
-
buildLogTransport,
|
|
107
|
-
preparers,
|
|
108
|
-
generators,
|
|
109
|
-
publisher,
|
|
110
|
-
config,
|
|
111
|
-
discovery,
|
|
112
|
-
httpAuth,
|
|
113
|
-
auth,
|
|
114
|
-
catalogClient: catalog
|
|
115
|
-
})
|
|
116
|
-
);
|
|
117
|
-
http.addAuthPolicy({
|
|
118
|
-
path: "/static",
|
|
119
|
-
allow: "user-cookie"
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
});
|
|
7
|
+
const _feature = plugin.techdocsPlugin;
|
|
125
8
|
|
|
126
|
-
exports.default =
|
|
9
|
+
exports.default = _feature;
|
|
127
10
|
//# sourceMappingURL=alpha.cjs.js.map
|
package/dist/alpha.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alpha.cjs.js","sources":["../src/
|
|
1
|
+
{"version":3,"file":"alpha.cjs.js","sources":["../src/alpha.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { techdocsPlugin } from './plugin';\n\n/** @alpha */\nconst _feature = techdocsPlugin;\nexport default _feature;\n"],"names":["techdocsPlugin"],"mappings":";;;;;;AAmBA,MAAM,QAAW,GAAAA;;;;"}
|
package/dist/alpha.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
* @alpha
|
|
6
|
-
*/
|
|
7
|
-
declare const techdocsPlugin: _backstage_backend_plugin_api.BackendFeature;
|
|
3
|
+
/** @alpha */
|
|
4
|
+
declare const _feature: _backstage_backend_plugin_api.BackendFeature;
|
|
8
5
|
|
|
9
|
-
export {
|
|
6
|
+
export { _feature as default };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@backstage/errors');
|
|
4
|
+
|
|
5
|
+
class CacheInvalidationError extends errors.CustomErrorBase {
|
|
6
|
+
}
|
|
7
|
+
class TechDocsCache {
|
|
8
|
+
cache;
|
|
9
|
+
logger;
|
|
10
|
+
readTimeout;
|
|
11
|
+
constructor({
|
|
12
|
+
cache,
|
|
13
|
+
logger,
|
|
14
|
+
readTimeout
|
|
15
|
+
}) {
|
|
16
|
+
this.cache = cache;
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
this.readTimeout = readTimeout;
|
|
19
|
+
}
|
|
20
|
+
static fromConfig(config, { cache, logger }) {
|
|
21
|
+
const timeout = config.getOptionalNumber("techdocs.cache.readTimeout");
|
|
22
|
+
const readTimeout = timeout === void 0 ? 1e3 : timeout;
|
|
23
|
+
return new TechDocsCache({ cache, logger, readTimeout });
|
|
24
|
+
}
|
|
25
|
+
async get(path) {
|
|
26
|
+
try {
|
|
27
|
+
const response = await Promise.race([
|
|
28
|
+
this.cache.get(path),
|
|
29
|
+
new Promise((cancelAfter) => setTimeout(cancelAfter, this.readTimeout))
|
|
30
|
+
]);
|
|
31
|
+
if (response !== void 0) {
|
|
32
|
+
this.logger.debug(`Cache hit: ${path}`);
|
|
33
|
+
return Buffer.from(response, "base64");
|
|
34
|
+
}
|
|
35
|
+
this.logger.debug(`Cache miss: ${path}`);
|
|
36
|
+
return response;
|
|
37
|
+
} catch (e) {
|
|
38
|
+
errors.assertError(e);
|
|
39
|
+
this.logger.warn(`Error getting cache entry ${path}: ${e.message}`);
|
|
40
|
+
this.logger.debug(e.message, e);
|
|
41
|
+
return void 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async set(path, data) {
|
|
45
|
+
this.logger.debug(`Writing cache entry for ${path}`);
|
|
46
|
+
this.cache.set(path, data.toString("base64")).catch((e) => this.logger.error("write error", e));
|
|
47
|
+
}
|
|
48
|
+
async invalidate(path) {
|
|
49
|
+
return this.cache.delete(path);
|
|
50
|
+
}
|
|
51
|
+
async invalidateMultiple(paths) {
|
|
52
|
+
const settled = await Promise.allSettled(
|
|
53
|
+
paths.map((path) => this.cache.delete(path))
|
|
54
|
+
);
|
|
55
|
+
const rejected = settled.filter(
|
|
56
|
+
(s) => s.status === "rejected"
|
|
57
|
+
);
|
|
58
|
+
if (rejected.length) {
|
|
59
|
+
throw new CacheInvalidationError(
|
|
60
|
+
"TechDocs cache invalidation error",
|
|
61
|
+
rejected
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return settled;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
exports.CacheInvalidationError = CacheInvalidationError;
|
|
69
|
+
exports.TechDocsCache = TechDocsCache;
|
|
70
|
+
//# sourceMappingURL=TechDocsCache.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TechDocsCache.cjs.js","sources":["../../src/cache/TechDocsCache.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { assertError, CustomErrorBase } from '@backstage/errors';\nimport { Config } from '@backstage/config';\nimport { CacheService, LoggerService } from '@backstage/backend-plugin-api';\n\nexport class CacheInvalidationError extends CustomErrorBase {}\n\nexport class TechDocsCache {\n protected readonly cache: CacheService;\n protected readonly logger: LoggerService;\n protected readonly readTimeout: number;\n\n private constructor({\n cache,\n logger,\n readTimeout,\n }: {\n cache: CacheService;\n logger: LoggerService;\n readTimeout: number;\n }) {\n this.cache = cache;\n this.logger = logger;\n this.readTimeout = readTimeout;\n }\n\n static fromConfig(\n config: Config,\n { cache, logger }: { cache: CacheService; logger: LoggerService },\n ) {\n const timeout = config.getOptionalNumber('techdocs.cache.readTimeout');\n const readTimeout = timeout === undefined ? 1000 : timeout;\n return new TechDocsCache({ cache, logger, readTimeout });\n }\n\n async get(path: string): Promise<Buffer | undefined> {\n try {\n // Promise.race ensures we don't hang the client for long if the cache is\n // temporarily unreachable.\n const response = (await Promise.race([\n this.cache.get(path),\n new Promise(cancelAfter => setTimeout(cancelAfter, this.readTimeout)),\n ])) as string | undefined;\n\n if (response !== undefined) {\n this.logger.debug(`Cache hit: ${path}`);\n return Buffer.from(response, 'base64');\n }\n\n this.logger.debug(`Cache miss: ${path}`);\n return response;\n } catch (e) {\n assertError(e);\n this.logger.warn(`Error getting cache entry ${path}: ${e.message}`);\n this.logger.debug(e.message, e);\n return undefined;\n }\n }\n\n async set(path: string, data: Buffer): Promise<void> {\n this.logger.debug(`Writing cache entry for ${path}`);\n this.cache\n .set(path, data.toString('base64'))\n .catch(e => this.logger.error('write error', e));\n }\n\n async invalidate(path: string): Promise<void> {\n return this.cache.delete(path);\n }\n\n async invalidateMultiple(\n paths: string[],\n ): Promise<PromiseSettledResult<void>[]> {\n const settled = await Promise.allSettled(\n paths.map(path => this.cache.delete(path)),\n );\n const rejected = settled.filter(\n s => s.status === 'rejected',\n ) as PromiseRejectedResult[];\n\n if (rejected.length) {\n throw new CacheInvalidationError(\n 'TechDocs cache invalidation error',\n rejected,\n );\n }\n\n return settled;\n }\n}\n"],"names":["CustomErrorBase","assertError"],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var router = require('express-promise-router');
|
|
4
|
+
|
|
5
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
6
|
+
|
|
7
|
+
var router__default = /*#__PURE__*/_interopDefaultCompat(router);
|
|
8
|
+
|
|
9
|
+
const createCacheMiddleware = ({
|
|
10
|
+
cache
|
|
11
|
+
}) => {
|
|
12
|
+
const cacheMiddleware = router__default.default();
|
|
13
|
+
cacheMiddleware.use(async (req, res, next) => {
|
|
14
|
+
const socket = res.socket;
|
|
15
|
+
const isCacheable = req.path.startsWith("/static/docs/");
|
|
16
|
+
const isGetRequest = req.method === "GET";
|
|
17
|
+
if (!isCacheable || !socket) {
|
|
18
|
+
next();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const reqPath = decodeURI(req.path.match(/\/static\/docs\/(.*)$/)[1]);
|
|
22
|
+
const realEnd = socket.end.bind(socket);
|
|
23
|
+
const realWrite = socket.write.bind(socket);
|
|
24
|
+
let writeToCache = true;
|
|
25
|
+
const chunks = [];
|
|
26
|
+
socket.write = (data, encoding, callback) => {
|
|
27
|
+
chunks.push(Buffer.from(data));
|
|
28
|
+
if (typeof encoding === "function") {
|
|
29
|
+
return realWrite(data, encoding);
|
|
30
|
+
}
|
|
31
|
+
return realWrite(data, encoding, callback);
|
|
32
|
+
};
|
|
33
|
+
socket.on("close", async (hadError) => {
|
|
34
|
+
const content = Buffer.concat(chunks);
|
|
35
|
+
const head = content.toString("utf8", 0, 12);
|
|
36
|
+
if (isGetRequest && writeToCache && !hadError && head.match(/HTTP\/\d\.\d 200/)) {
|
|
37
|
+
await cache.set(reqPath, content);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
const cached = await cache.get(reqPath);
|
|
41
|
+
if (cached) {
|
|
42
|
+
writeToCache = false;
|
|
43
|
+
realEnd(cached);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
next();
|
|
47
|
+
});
|
|
48
|
+
return cacheMiddleware;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
exports.createCacheMiddleware = createCacheMiddleware;
|
|
52
|
+
//# sourceMappingURL=cacheMiddleware.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cacheMiddleware.cjs.js","sources":["../../src/cache/cacheMiddleware.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Router } from 'express';\nimport router from 'express-promise-router';\nimport { TechDocsCache } from './TechDocsCache';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\ntype CacheMiddlewareOptions = {\n cache: TechDocsCache;\n logger: LoggerService;\n};\n\ntype ErrorCallback = (err?: Error) => void;\n\nexport const createCacheMiddleware = ({\n cache,\n}: CacheMiddlewareOptions): Router => {\n const cacheMiddleware = router();\n\n // Middleware that, through socket monkey patching, captures responses as\n // they're sent over /static/docs/* and caches them. Subsequent requests are\n // loaded from cache. Cache key is the object's path (after `/static/docs/`).\n cacheMiddleware.use(async (req, res, next) => {\n const socket = res.socket;\n const isCacheable = req.path.startsWith('/static/docs/');\n const isGetRequest = req.method === 'GET';\n\n // Continue early if this is non-cacheable, or there's no socket.\n if (!isCacheable || !socket) {\n next();\n return;\n }\n\n // Make concrete references to these things.\n const reqPath = decodeURI(req.path.match(/\\/static\\/docs\\/(.*)$/)![1]);\n const realEnd = socket.end.bind(socket);\n const realWrite = socket.write.bind(socket);\n let writeToCache = true;\n const chunks: Buffer[] = [];\n\n // Monkey-patch the response's socket to keep track of chunks as they are\n // written over the wire.\n socket.write = (\n data: string | Uint8Array,\n encoding?: BufferEncoding | ErrorCallback,\n callback?: ErrorCallback,\n ) => {\n chunks.push(Buffer.from(data));\n if (typeof encoding === 'function') {\n return realWrite(data, encoding);\n }\n return realWrite(data, encoding, callback);\n };\n\n // When a socket is closed, if there were no errors and the data written\n // over the socket should be cached, cache it!\n socket.on('close', async hadError => {\n const content = Buffer.concat(chunks);\n const head = content.toString('utf8', 0, 12);\n if (\n isGetRequest &&\n writeToCache &&\n !hadError &&\n head.match(/HTTP\\/\\d\\.\\d 200/)\n ) {\n await cache.set(reqPath, content);\n }\n });\n\n // Attempt to retrieve data from the cache.\n const cached = await cache.get(reqPath);\n\n // If there is a cache hit, write it out on the socket, ensure we don't re-\n // cache the data, and prevent going back to canonical storage by never\n // calling next().\n if (cached) {\n writeToCache = false;\n realEnd(cached);\n return;\n }\n\n // No data retrieved from cache: allow retrieval from canonical storage.\n next();\n });\n\n return cacheMiddleware;\n};\n"],"names":["router"],"mappings":";;;;;;;;AA2BO,MAAM,wBAAwB,CAAC;AAAA,EACpC,KAAA;AACF,CAAsC,KAAA;AACpC,EAAA,MAAM,kBAAkBA,uBAAO,EAAA,CAAA;AAK/B,EAAA,eAAA,CAAgB,GAAI,CAAA,OAAO,GAAK,EAAA,GAAA,EAAK,IAAS,KAAA;AAC5C,IAAA,MAAM,SAAS,GAAI,CAAA,MAAA,CAAA;AACnB,IAAA,MAAM,WAAc,GAAA,GAAA,CAAI,IAAK,CAAA,UAAA,CAAW,eAAe,CAAA,CAAA;AACvD,IAAM,MAAA,YAAA,GAAe,IAAI,MAAW,KAAA,KAAA,CAAA;AAGpC,IAAI,IAAA,CAAC,WAAe,IAAA,CAAC,MAAQ,EAAA;AAC3B,MAAK,IAAA,EAAA,CAAA;AACL,MAAA,OAAA;AAAA,KACF;AAGA,IAAM,MAAA,OAAA,GAAU,UAAU,GAAI,CAAA,IAAA,CAAK,MAAM,uBAAuB,CAAA,CAAG,CAAC,CAAC,CAAA,CAAA;AACrE,IAAA,MAAM,OAAU,GAAA,MAAA,CAAO,GAAI,CAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AACtC,IAAA,MAAM,SAAY,GAAA,MAAA,CAAO,KAAM,CAAA,IAAA,CAAK,MAAM,CAAA,CAAA;AAC1C,IAAA,IAAI,YAAe,GAAA,IAAA,CAAA;AACnB,IAAA,MAAM,SAAmB,EAAC,CAAA;AAI1B,IAAA,MAAA,CAAO,KAAQ,GAAA,CACb,IACA,EAAA,QAAA,EACA,QACG,KAAA;AACH,MAAA,MAAA,CAAO,IAAK,CAAA,MAAA,CAAO,IAAK,CAAA,IAAI,CAAC,CAAA,CAAA;AAC7B,MAAI,IAAA,OAAO,aAAa,UAAY,EAAA;AAClC,QAAO,OAAA,SAAA,CAAU,MAAM,QAAQ,CAAA,CAAA;AAAA,OACjC;AACA,MAAO,OAAA,SAAA,CAAU,IAAM,EAAA,QAAA,EAAU,QAAQ,CAAA,CAAA;AAAA,KAC3C,CAAA;AAIA,IAAO,MAAA,CAAA,EAAA,CAAG,OAAS,EAAA,OAAM,QAAY,KAAA;AACnC,MAAM,MAAA,OAAA,GAAU,MAAO,CAAA,MAAA,CAAO,MAAM,CAAA,CAAA;AACpC,MAAA,MAAM,IAAO,GAAA,OAAA,CAAQ,QAAS,CAAA,MAAA,EAAQ,GAAG,EAAE,CAAA,CAAA;AAC3C,MAAA,IACE,gBACA,YACA,IAAA,CAAC,YACD,IAAK,CAAA,KAAA,CAAM,kBAAkB,CAC7B,EAAA;AACA,QAAM,MAAA,KAAA,CAAM,GAAI,CAAA,OAAA,EAAS,OAAO,CAAA,CAAA;AAAA,OAClC;AAAA,KACD,CAAA,CAAA;AAGD,IAAA,MAAM,MAAS,GAAA,MAAM,KAAM,CAAA,GAAA,CAAI,OAAO,CAAA,CAAA;AAKtC,IAAA,IAAI,MAAQ,EAAA;AACV,MAAe,YAAA,GAAA,KAAA,CAAA;AACf,MAAA,OAAA,CAAQ,MAAM,CAAA,CAAA;AACd,MAAA,OAAA;AAAA,KACF;AAGA,IAAK,IAAA,EAAA,CAAA;AAAA,GACN,CAAA,CAAA;AAED,EAAO,OAAA,eAAA,CAAA;AACT;;;;"}
|