@uipath/maestro-builder-sdk 6.3.0 → 6.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-index.md +6 -6
- package/dist/cli/commands/registry.js +68 -12
- package/dist/registry/archive/entry-policy.d.ts +36 -0
- package/dist/registry/archive/entry-policy.js +60 -0
- package/dist/registry/archive/extract.d.ts +31 -0
- package/dist/registry/archive/extract.js +92 -0
- package/dist/registry/archive/zip-reader.d.ts +38 -0
- package/dist/registry/archive/zip-reader.js +256 -0
- package/dist/registry/codecs/pyjson.d.ts +62 -0
- package/dist/registry/codecs/pyjson.js +208 -0
- package/dist/registry/compat/compare.d.ts +18 -0
- package/dist/registry/compat/compare.js +32 -0
- package/dist/registry/compat/paths.d.ts +4 -0
- package/dist/registry/compat/paths.js +17 -0
- package/dist/registry/compat/pystr.d.ts +50 -0
- package/dist/registry/compat/pystr.js +221 -0
- package/dist/registry/concurrency.d.ts +10 -0
- package/dist/registry/concurrency.js +34 -0
- package/dist/registry/curated-overlay.d.ts +36 -4
- package/dist/registry/curated-overlay.js +87 -14
- package/dist/registry/errors.d.ts +26 -0
- package/dist/registry/errors.js +38 -0
- package/dist/registry/extractor.d.ts +9 -10
- package/dist/registry/extractor.js +5 -15
- package/dist/registry/files.d.ts +26 -0
- package/dist/registry/files.js +160 -0
- package/dist/registry/generators/descriptors.d.ts +81 -0
- package/dist/registry/generators/descriptors.js +405 -0
- package/dist/registry/generators/fields.d.ts +46 -0
- package/dist/registry/generators/fields.js +111 -0
- package/dist/registry/generators/markdown/common.d.ts +21 -0
- package/dist/registry/generators/markdown/common.js +55 -0
- package/dist/registry/generators/markdown/index.d.ts +33 -0
- package/dist/registry/generators/markdown/index.js +37 -0
- package/dist/registry/generators/markdown/page.d.ts +8 -0
- package/dist/registry/generators/markdown/page.js +52 -0
- package/dist/registry/generators/markdown/pyvalue.d.ts +34 -0
- package/dist/registry/generators/markdown/pyvalue.js +106 -0
- package/dist/registry/generators/markdown/sections/choosing-object.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/choosing-object.js +52 -0
- package/dist/registry/generators/markdown/sections/example.d.ts +22 -0
- package/dist/registry/generators/markdown/sections/example.js +295 -0
- package/dist/registry/generators/markdown/sections/filtering.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/filtering.js +76 -0
- package/dist/registry/generators/markdown/sections/header.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/header.js +37 -0
- package/dist/registry/generators/markdown/sections/inputs.d.ts +30 -0
- package/dist/registry/generators/markdown/sections/inputs.js +151 -0
- package/dist/registry/generators/markdown/sections/lookups.d.ts +12 -0
- package/dist/registry/generators/markdown/sections/lookups.js +150 -0
- package/dist/registry/generators/markdown/sections/outputs.d.ts +3 -0
- package/dist/registry/generators/markdown/sections/outputs.js +9 -0
- package/dist/registry/generators/markdown/sections/resolving-fields.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/resolving-fields.js +139 -0
- package/dist/registry/generators/markdown/sections/schema-dynamic.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/schema-dynamic.js +86 -0
- package/dist/registry/generators/materialize.d.ts +30 -0
- package/dist/registry/generators/materialize.js +132 -0
- package/dist/registry/generators/md-symbols.d.ts +22 -0
- package/dist/registry/generators/md-symbols.js +58 -0
- package/dist/registry/generators/triggers.d.ts +15 -0
- package/dist/registry/generators/triggers.js +85 -0
- package/dist/registry/generators/type-registry.d.ts +50 -0
- package/dist/registry/generators/type-registry.js +154 -0
- package/dist/registry/generators/type-tree.d.ts +65 -0
- package/dist/registry/generators/type-tree.js +179 -0
- package/dist/registry/library-build/build.d.ts +57 -0
- package/dist/registry/library-build/build.js +286 -0
- package/dist/registry/library-build/canonical.d.ts +119 -0
- package/dist/registry/library-build/canonical.js +512 -0
- package/dist/registry/library-build/cli.d.ts +45 -0
- package/dist/registry/library-build/cli.js +182 -0
- package/dist/registry/library-build/context.d.ts +107 -0
- package/dist/registry/library-build/context.js +267 -0
- package/dist/registry/library-build/enrich.d.ts +36 -0
- package/dist/registry/library-build/enrich.js +158 -0
- package/dist/registry/library-build/events.d.ts +108 -0
- package/dist/registry/library-build/events.js +476 -0
- package/dist/registry/library-build/generic-expand.d.ts +77 -0
- package/dist/registry/library-build/generic-expand.js +363 -0
- package/dist/registry/library-build/plan.d.ts +71 -0
- package/dist/registry/library-build/plan.js +270 -0
- package/dist/registry/library-build/py.d.ts +45 -0
- package/dist/registry/library-build/py.js +167 -0
- package/dist/registry/library-build/schema-action.d.ts +38 -0
- package/dist/registry/library-build/schema-action.js +107 -0
- package/dist/registry/library-build/write.d.ts +6 -0
- package/dist/registry/library-build/write.js +26 -0
- package/dist/registry/library-reader.d.ts +20 -0
- package/dist/registry/library-reader.js +50 -0
- package/dist/registry/lookup-specs.d.ts +55 -0
- package/dist/registry/lookup-specs.js +140 -0
- package/dist/registry/materialize-cli.js +28 -14
- package/dist/registry/materializer.d.ts +25 -3
- package/dist/registry/materializer.js +45 -29
- package/dist/registry/model/library.d.ts +254 -0
- package/dist/registry/model/library.js +23 -0
- package/dist/registry/naming.d.ts +54 -0
- package/dist/registry/naming.js +142 -0
- package/dist/registry/orphans.d.ts +4 -0
- package/dist/registry/orphans.js +86 -0
- package/dist/registry/prepare/actions.d.ts +26 -0
- package/dist/registry/prepare/actions.js +94 -0
- package/dist/registry/prepare/connections.d.ts +181 -0
- package/dist/registry/prepare/connections.js +399 -0
- package/dist/registry/prepare/event.d.ts +96 -0
- package/dist/registry/prepare/event.js +268 -0
- package/dist/registry/prepare/handoff.d.ts +21 -0
- package/dist/registry/prepare/handoff.js +91 -0
- package/dist/registry/prepare/ledger.d.ts +104 -0
- package/dist/registry/prepare/ledger.js +216 -0
- package/dist/registry/prepare/messages.d.ts +190 -0
- package/dist/registry/prepare/messages.js +472 -0
- package/dist/registry/prepare/objects.d.ts +130 -0
- package/dist/registry/prepare/objects.js +318 -0
- package/dist/registry/prepare/overlay.d.ts +149 -0
- package/dist/registry/prepare/overlay.js +487 -0
- package/dist/registry/prepare/parents.d.ts +41 -0
- package/dist/registry/prepare/parents.js +122 -0
- package/dist/registry/prepare/pipeline.d.ts +91 -0
- package/dist/registry/prepare/pipeline.js +376 -0
- package/dist/registry/prepare/regenerate.d.ts +9 -0
- package/dist/registry/prepare/regenerate.js +50 -0
- package/dist/registry/prepare/resolve.d.ts +199 -0
- package/dist/registry/prepare/resolve.js +470 -0
- package/dist/registry/prepare/retry.d.ts +83 -0
- package/dist/registry/prepare/retry.js +109 -0
- package/dist/registry/prepare/source-import.d.ts +54 -0
- package/dist/registry/prepare/source-import.js +192 -0
- package/dist/registry/prepare/support.d.ts +41 -0
- package/dist/registry/prepare/support.js +178 -0
- package/dist/registry/prepare-cli.d.ts +16 -5
- package/dist/registry/prepare-cli.js +60 -53
- package/dist/registry/pull-cli.js +8 -7
- package/dist/registry/reporter.d.ts +26 -0
- package/dist/registry/reporter.js +41 -0
- package/dist/registry/uip/client.d.ts +99 -0
- package/dist/registry/uip/client.js +226 -0
- package/dist/registry/uip/commands.d.ts +40 -0
- package/dist/registry/uip/commands.js +80 -0
- package/dist/registry/uip/envelope.d.ts +37 -0
- package/dist/registry/uip/envelope.js +84 -0
- package/dist/registry/uip/response-cache.d.ts +28 -0
- package/dist/registry/uip/response-cache.js +57 -0
- package/lib/README.md +10 -8
- package/package.json +3 -3
- package/dist/generators/_connections.py +0 -331
- package/dist/generators/_events.py +0 -355
- package/dist/generators/_lookups.py +0 -114
- package/dist/generators/_naming.py +0 -185
- package/dist/generators/_objects.py +0 -299
- package/dist/generators/_resolve.py +0 -319
- package/dist/generators/convert_library_to_md.py +0 -1441
- package/dist/generators/extract_library.py +0 -125
- package/dist/generators/generate_connectors_ts.py +0 -936
- package/dist/generators/generate_library_json.py +0 -2527
- package/dist/generators/generate_triggers_ts.py +0 -110
- package/dist/generators/materialize_registry.py +0 -155
- package/dist/generators/prepare_connector.py +0 -1860
- package/dist/registry/python.d.ts +0 -26
- package/dist/registry/python.js +0 -46
package/dist/api-index.md
CHANGED
|
@@ -19,7 +19,7 @@ compiler checks your source against, and it carries the `@remarks` and
|
|
|
19
19
|
`@example` bodies the reference pages leave out. The span below is the whole
|
|
20
20
|
declaration including its doc comment, so one read is the complete answer.
|
|
21
21
|
|
|
22
|
-
Paths are relative to the
|
|
22
|
+
Paths are relative to the package root — the directory above this file:
|
|
23
23
|
|
|
24
24
|
```
|
|
25
25
|
node_modules/@uipath/maestro-builder-sdk/dist/core/actions.d.ts lines 539-610
|
|
@@ -29,11 +29,11 @@ Open the file at that line. No search, no `grep`, nothing shell-specific —
|
|
|
29
29
|
which also means this works on Windows, where the command the reference pages
|
|
30
30
|
used to suggest does not.
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
versions — a line moves whenever
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
This file is generated by the build that emits the `.d.ts` files beside it, so
|
|
33
|
+
its spans are always true of THIS installed version. They are not stable across
|
|
34
|
+
versions — a line moves whenever a declaration above it changes — which is why
|
|
35
|
+
the index ships here and nowhere else: anywhere with its own release cadence
|
|
36
|
+
could hand you spans for a version you do not have.
|
|
37
37
|
|
|
38
38
|
## `@uipath/maestro-builder-sdk`
|
|
39
39
|
|
|
@@ -27,10 +27,10 @@ export function registerRegistryCommands(program) {
|
|
|
27
27
|
.option('--force', 're-download and regenerate even if the archive is cached')
|
|
28
28
|
.option('--import <specifier>', 'module specifier for generated descriptors')
|
|
29
29
|
.option('--triggers <path>', 'registry-triggers.json for trigger descriptors')
|
|
30
|
-
.option('--python <executable>', 'Python interpreter to run the generators with')
|
|
31
30
|
.option('--root <prefix>', 'only extract archive entries under this prefix')
|
|
32
|
-
.option('--
|
|
33
|
-
.option('--
|
|
31
|
+
.option('--python <executable>', '(deprecated, ignored) the generators run in-process')
|
|
32
|
+
.option('--extractor <path>', '(deprecated, ignored) the extractor is part of the package')
|
|
33
|
+
.option('--generator <path>', '(deprecated, ignored) the generators are part of the package')
|
|
34
34
|
.action(async (options) => {
|
|
35
35
|
const argv = [];
|
|
36
36
|
addValue(argv, '--library', options.library);
|
|
@@ -54,12 +54,16 @@ export function registerRegistryCommands(program) {
|
|
|
54
54
|
family.command('materialize')
|
|
55
55
|
.description('Generate library-md and connectors/ from an extracted library-json.')
|
|
56
56
|
.requiredOption('--library-json <dir>', 'extracted library-json directory')
|
|
57
|
-
.
|
|
58
|
-
.
|
|
57
|
+
.option('--library-md <dir>', 'markdown output directory (omit to skip the Markdown)')
|
|
58
|
+
.option('--connectors <dir>', 'TypeScript descriptor output directory (omit to skip descriptors)')
|
|
59
59
|
.option('--import <specifier>', 'module specifier for generated descriptors')
|
|
60
60
|
.option('--triggers <path>', 'registry-triggers.json for trigger descriptors')
|
|
61
|
-
.option('--
|
|
62
|
-
.option('--
|
|
61
|
+
.option('--connector-subset <keys>', 'comma-separated connector keys to emit instead of all')
|
|
62
|
+
.option('--include-generic-expanded', 'also emit descriptors for GenericExpanded entries')
|
|
63
|
+
.option('--check-orphans', 'fail when an entry file has no index.json row (the image build asks for this)')
|
|
64
|
+
.option('--check-symbols', 'fail when a Markdown page cites a symbol the descriptors do not export')
|
|
65
|
+
.option('--python <executable>', '(deprecated, ignored) the generators run in-process')
|
|
66
|
+
.option('--generator <path>', '(deprecated, ignored) the generators are part of the package')
|
|
63
67
|
.action(async (options) => {
|
|
64
68
|
const argv = [];
|
|
65
69
|
addValue(argv, '--library-json', options.libraryJson);
|
|
@@ -67,10 +71,56 @@ export function registerRegistryCommands(program) {
|
|
|
67
71
|
addValue(argv, '--connectors', options.connectors);
|
|
68
72
|
addValue(argv, '--import', options.import);
|
|
69
73
|
addValue(argv, '--triggers', options.triggers);
|
|
74
|
+
addValue(argv, '--connector-subset', options.connectorSubset);
|
|
75
|
+
if (options.includeGenericExpanded)
|
|
76
|
+
argv.push('--include-generic-expanded');
|
|
77
|
+
if (options.checkSymbols)
|
|
78
|
+
argv.push('--check-symbols');
|
|
79
|
+
if (options.checkOrphans)
|
|
80
|
+
argv.push('--check-orphans');
|
|
70
81
|
addValue(argv, '--python', options.python);
|
|
71
82
|
addValue(argv, '--generator', options.generator);
|
|
72
83
|
await invoke('registry materialize', () => loadTool(import.meta.url, '../../registry/materialize-cli.js'), argv);
|
|
73
84
|
});
|
|
85
|
+
// The pipeline's generator, `generate_library_json.py`, flag for flag: the
|
|
86
|
+
// Azure YAML that calls it should only have to swap the executable.
|
|
87
|
+
family.command('build-library')
|
|
88
|
+
.description('Generate the canonical connector library from the UiPath Flow registry.')
|
|
89
|
+
.option('--output-dir <dir>', 'output directory (default: ./library)')
|
|
90
|
+
.option('--cache-dir <dir>', 'cache for `flow registry get` JSONs (default: ./.registry-cache)')
|
|
91
|
+
.option('--is-cache-dir <dir>', 'cache for `is resources describe` JSONs (default: ./.is-cache)')
|
|
92
|
+
.option('--connectors-cache-dir <dir>', 'cache for `is connectors metadata` JSONs (default: ./.is-connectors-cache)')
|
|
93
|
+
.option('--skip-enrichment', 'skip the `is resources describe` enrichment pass')
|
|
94
|
+
.option('--skip-connector-enrichment', 'skip the `is connectors metadata` connector-level pass')
|
|
95
|
+
.option('--skip-generic-expansion', 'keep Generic CRUD entries as templates instead of one entry per object/operation')
|
|
96
|
+
.option('--triggers-output <path>', 'trigger side-file path (default: <parent of --output-dir>/registry-triggers.json)')
|
|
97
|
+
.option('--keep-temp', 'keep the cache directories after the run')
|
|
98
|
+
.option('--offline', 'never run `uip`; treat an uncached call as a failed one (for replaying a build from kept caches)')
|
|
99
|
+
.option('--connectors <keys>', 'comma-separated connector keys to build as a curated subset (default: all)')
|
|
100
|
+
.option('--catalog-scope <scope>', "connector catalog scope: 'all', 'tier1' or 'tier2' (default: all)")
|
|
101
|
+
.option('--concurrency <n>', 'parallel remote CLI calls (default: 10)')
|
|
102
|
+
.action(async (options) => {
|
|
103
|
+
const argv = [];
|
|
104
|
+
addValue(argv, '--output-dir', options.outputDir);
|
|
105
|
+
addValue(argv, '--cache-dir', options.cacheDir);
|
|
106
|
+
addValue(argv, '--is-cache-dir', options.isCacheDir);
|
|
107
|
+
addValue(argv, '--connectors-cache-dir', options.connectorsCacheDir);
|
|
108
|
+
if (options.skipEnrichment)
|
|
109
|
+
argv.push('--skip-enrichment');
|
|
110
|
+
if (options.skipConnectorEnrichment)
|
|
111
|
+
argv.push('--skip-connector-enrichment');
|
|
112
|
+
if (options.skipGenericExpansion)
|
|
113
|
+
argv.push('--skip-generic-expansion');
|
|
114
|
+
addValue(argv, '--triggers-output', options.triggersOutput);
|
|
115
|
+
if (options.keepTemp)
|
|
116
|
+
argv.push('--keep-temp');
|
|
117
|
+
if (options.offline)
|
|
118
|
+
argv.push('--offline');
|
|
119
|
+
addValue(argv, '--connectors', options.connectors);
|
|
120
|
+
addValue(argv, '--catalog-scope', options.catalogScope);
|
|
121
|
+
addValue(argv, '--concurrency', options.concurrency);
|
|
122
|
+
await invoke('registry build-library', () => loadTool(import.meta.url, '../../registry/library-build/cli.js'), argv);
|
|
123
|
+
});
|
|
74
124
|
family.command('prepare')
|
|
75
125
|
.description("Resolve one operation's schema against a live connection into ./connectors-local.")
|
|
76
126
|
.argument('<connector-key>', 'connector key, e.g. uipath-salesforce')
|
|
@@ -82,7 +132,11 @@ export function registerRegistryCommands(program) {
|
|
|
82
132
|
.option('--bindings <file>', 'bindings file to update (default: ./bindings.json)')
|
|
83
133
|
.option('--bind-connection <name>', 'symbolic connection name (default: the connector suffix)')
|
|
84
134
|
.option('--bind-folder <name>', 'symbolic folder name (default: shared)')
|
|
85
|
-
|
|
135
|
+
// A SEPARATE key, not commander's negation of `--bindings`: sharing one key
|
|
136
|
+
// made the result order-dependent, so `--no-bindings --bindings b.json` and
|
|
137
|
+
// the reverse disagreed. argparse had two independent options.
|
|
138
|
+
.option('--skip-bindings', 'discover the connection but leave bindings.json alone')
|
|
139
|
+
.option('--no-bindings', 'alias of --skip-bindings')
|
|
86
140
|
.option('--object <name>', 'one connection-discovered object')
|
|
87
141
|
.option('--all-objects', 'every connection-discovered object')
|
|
88
142
|
.option('-f, --field <name=value>', 'parent field that selects the schema (repeatable)', collect, [])
|
|
@@ -92,8 +146,8 @@ export function registerRegistryCommands(program) {
|
|
|
92
146
|
.option('--connector-version <version>', 'connector version to resolve against')
|
|
93
147
|
.option('--import <specifier>', 'module specifier for generated descriptors')
|
|
94
148
|
.option('--json-only', 'write the JSON overlay without generating TypeScript')
|
|
95
|
-
.option('--python <executable>', '
|
|
96
|
-
.option('--generator <path>', '
|
|
149
|
+
.option('--python <executable>', '(deprecated, ignored once prepare runs in-process)')
|
|
150
|
+
.option('--generator <path>', '(deprecated, ignored: prepare runs in-process and no script is packaged)')
|
|
97
151
|
// Commander declares the surface; the inner CLI only ever sees what is
|
|
98
152
|
// forwarded below. Adding `--resolve` to prepare-cli.ts without adding it
|
|
99
153
|
// HERE made the flag unreachable through the shipped command, while the
|
|
@@ -111,8 +165,10 @@ export function registerRegistryCommands(program) {
|
|
|
111
165
|
addValue(argv, '--bindings', options.bindings);
|
|
112
166
|
addValue(argv, '--bind-connection', options.bindConnection);
|
|
113
167
|
addValue(argv, '--bind-folder', options.bindFolder);
|
|
114
|
-
// commander sets `bindings` false for `--no-bindings
|
|
115
|
-
|
|
168
|
+
// commander sets `bindings` false for `--no-bindings`; `--skip-bindings`
|
|
169
|
+
// arrives under its own key, so neither spelling can be lost to the
|
|
170
|
+
// order the two flags appear in.
|
|
171
|
+
if (options.bindings === false || options.skipBindings)
|
|
116
172
|
argv.push('--no-bindings');
|
|
117
173
|
addValue(argv, '--object', options.object);
|
|
118
174
|
if (options.allObjects)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which archive entries the extractor is willing to write, and where.
|
|
3
|
+
*
|
|
4
|
+
* A zip carries whatever paths its author put in it, so entry names are
|
|
5
|
+
* validated rather than joined. This is `safe_relative_path` / `is_symlink`
|
|
6
|
+
* from `integrations/scripts/extract_library.py`, kept rule for rule so both
|
|
7
|
+
* extractors agree on what a hostile entry looks like:
|
|
8
|
+
*
|
|
9
|
+
* * only entries under the root are considered, and the prefix is stripped,
|
|
10
|
+
* so the tree flattens to the destination;
|
|
11
|
+
* * a segment that is empty, `.` or `..` aborts the whole extraction. Not
|
|
12
|
+
* skips it — aborts. An archive containing `../../etc/foo` is not an
|
|
13
|
+
* archive with one bad file, it is an archive we should not be unpacking;
|
|
14
|
+
* * absolute names and Windows drive letters are refused for the same reason;
|
|
15
|
+
* * symlink entries are refused. Nothing in the library needs one, and a
|
|
16
|
+
* symlink written during extraction turns a later write into a write
|
|
17
|
+
* somewhere else.
|
|
18
|
+
*/
|
|
19
|
+
/** The archive asked for something outside the destination. */
|
|
20
|
+
export declare class UnsafeArchive extends Error {
|
|
21
|
+
constructor(message: string);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The destination-relative path for `entryName`, or `undefined` to skip it.
|
|
25
|
+
*
|
|
26
|
+
* Throws {@link UnsafeArchive} when the name is one we refuse outright.
|
|
27
|
+
*/
|
|
28
|
+
export declare function safeRelativePath(entryName: string, root: string): string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Whether a central-directory `externalAttr` marks a symlink.
|
|
31
|
+
*
|
|
32
|
+
* The unix mode lives in the top 16 bits of the external attributes. Zips
|
|
33
|
+
* written on Windows leave it zero, which reads as "not a symlink" — correct,
|
|
34
|
+
* since those cannot carry one.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isSymlinkEntry(externalAttr: number): boolean;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which archive entries the extractor is willing to write, and where.
|
|
3
|
+
*
|
|
4
|
+
* A zip carries whatever paths its author put in it, so entry names are
|
|
5
|
+
* validated rather than joined. This is `safe_relative_path` / `is_symlink`
|
|
6
|
+
* from `integrations/scripts/extract_library.py`, kept rule for rule so both
|
|
7
|
+
* extractors agree on what a hostile entry looks like:
|
|
8
|
+
*
|
|
9
|
+
* * only entries under the root are considered, and the prefix is stripped,
|
|
10
|
+
* so the tree flattens to the destination;
|
|
11
|
+
* * a segment that is empty, `.` or `..` aborts the whole extraction. Not
|
|
12
|
+
* skips it — aborts. An archive containing `../../etc/foo` is not an
|
|
13
|
+
* archive with one bad file, it is an archive we should not be unpacking;
|
|
14
|
+
* * absolute names and Windows drive letters are refused for the same reason;
|
|
15
|
+
* * symlink entries are refused. Nothing in the library needs one, and a
|
|
16
|
+
* symlink written during extraction turns a later write into a write
|
|
17
|
+
* somewhere else.
|
|
18
|
+
*/
|
|
19
|
+
/** The archive asked for something outside the destination. */
|
|
20
|
+
export class UnsafeArchive extends Error {
|
|
21
|
+
constructor(message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = "UnsafeArchive";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The destination-relative path for `entryName`, or `undefined` to skip it.
|
|
28
|
+
*
|
|
29
|
+
* Throws {@link UnsafeArchive} when the name is one we refuse outright.
|
|
30
|
+
*/
|
|
31
|
+
export function safeRelativePath(entryName, root) {
|
|
32
|
+
const normalized = entryName.replace(/\\/g, "/");
|
|
33
|
+
if (!normalized.startsWith(root))
|
|
34
|
+
return undefined;
|
|
35
|
+
const relative = normalized.slice(root.length);
|
|
36
|
+
if (relative === "" || relative.endsWith("/"))
|
|
37
|
+
return undefined; // the directory entry itself
|
|
38
|
+
const parts = relative.split("/");
|
|
39
|
+
if (relative.startsWith("/") || parts[0].includes(":")) {
|
|
40
|
+
throw new UnsafeArchive(`absolute or drive-qualified entry: ${entryName}`);
|
|
41
|
+
}
|
|
42
|
+
if (parts.some((part) => part === "" || part === "." || part === "..")) {
|
|
43
|
+
throw new UnsafeArchive(`unsafe entry: ${entryName}`);
|
|
44
|
+
}
|
|
45
|
+
return relative;
|
|
46
|
+
}
|
|
47
|
+
/** `S_IFMT` and `S_IFLNK` from `<sys/stat.h>`; `stat.S_ISLNK(mode)` is `(mode & S_IFMT) === S_IFLNK`. */
|
|
48
|
+
const S_IFMT = 0o170000;
|
|
49
|
+
const S_IFLNK = 0o120000;
|
|
50
|
+
/**
|
|
51
|
+
* Whether a central-directory `externalAttr` marks a symlink.
|
|
52
|
+
*
|
|
53
|
+
* The unix mode lives in the top 16 bits of the external attributes. Zips
|
|
54
|
+
* written on Windows leave it zero, which reads as "not a symlink" — correct,
|
|
55
|
+
* since those cannot carry one.
|
|
56
|
+
*/
|
|
57
|
+
export function isSymlinkEntry(externalAttr) {
|
|
58
|
+
const mode = (externalAttr >>> 16) & 0xffff;
|
|
59
|
+
return (mode & S_IFMT) === S_IFLNK;
|
|
60
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export { UnsafeArchive } from "./entry-policy.js";
|
|
2
|
+
export { NotAZip, UnsupportedArchive } from "./zip-reader.js";
|
|
3
|
+
/** Only entries under this prefix are the library; the prefix is stripped on the way out. */
|
|
4
|
+
export declare const DEFAULT_ROOT = "integrations/library-json/";
|
|
5
|
+
export interface ExtractArchiveOptions {
|
|
6
|
+
/** The downloaded `.zip`. */
|
|
7
|
+
readonly archivePath: string;
|
|
8
|
+
/** Where the flattened `library-json/` tree lands; created if missing. */
|
|
9
|
+
readonly destination: string;
|
|
10
|
+
/** Only extract entries under this prefix (default {@link DEFAULT_ROOT}). */
|
|
11
|
+
readonly root?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ExtractArchiveResult {
|
|
14
|
+
/** Files written — directory entries and entries outside the root do not count. */
|
|
15
|
+
readonly written: number;
|
|
16
|
+
}
|
|
17
|
+
export type ExtractLibraryErrorCode = "NO_SUCH_ARCHIVE" | "UNSAFE_ARCHIVE" | "NOT_A_ZIP" | "UNSUPPORTED_ARCHIVE" | "NO_INDEX";
|
|
18
|
+
/**
|
|
19
|
+
* Extraction stopped. `message` is exactly what the Python printed to stderr
|
|
20
|
+
* (`extract-library: …`), `code` says which way it failed, and `cause` carries
|
|
21
|
+
* the underlying {@link UnsafeArchive} / {@link NotAZip} /
|
|
22
|
+
* {@link UnsupportedArchive} when there is one.
|
|
23
|
+
*/
|
|
24
|
+
export declare class ExtractLibraryError extends Error {
|
|
25
|
+
readonly code: ExtractLibraryErrorCode;
|
|
26
|
+
readonly exitCode: 1;
|
|
27
|
+
constructor(message: string, code: ExtractLibraryErrorCode, options?: {
|
|
28
|
+
cause?: unknown;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export declare function extractArchive(options: ExtractArchiveOptions): ExtractArchiveResult;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract the connector library out of a downloaded zip, safely.
|
|
3
|
+
*
|
|
4
|
+
* This is `integrations/scripts/extract_library.py` in TypeScript, so the SDK
|
|
5
|
+
* no longer needs Python for the one step where `zipfile` was the reason. The
|
|
6
|
+
* caller has already fetched the archive and checked its sha256; this only
|
|
7
|
+
* unpacks it, trusting nothing in it:
|
|
8
|
+
*
|
|
9
|
+
* * entry names go through {@link safeRelativePath}, which flattens the tree
|
|
10
|
+
* to the destination and aborts the WHOLE extraction on traversal — the
|
|
11
|
+
* Python raised mid-loop and left what it had written, and so does this;
|
|
12
|
+
* * symlink entries are refused;
|
|
13
|
+
* * `index.json` has to land, or the archive was not the library and the
|
|
14
|
+
* caller should hear about it rather than get an empty directory.
|
|
15
|
+
*
|
|
16
|
+
* Every failure is an {@link ExtractLibraryError} whose message is the line the
|
|
17
|
+
* Python printed to stderr; the caller prints it and exits 1.
|
|
18
|
+
*/
|
|
19
|
+
import { readFileSync } from "node:fs";
|
|
20
|
+
import { join, resolve, sep } from "node:path";
|
|
21
|
+
import { isFile, writeBytes, ensureDir } from "../files.js";
|
|
22
|
+
import { UnsafeArchive, isSymlinkEntry, safeRelativePath } from "./entry-policy.js";
|
|
23
|
+
import { NotAZip, UnsupportedArchive, entryData, readZipEntries } from "./zip-reader.js";
|
|
24
|
+
export { UnsafeArchive } from "./entry-policy.js";
|
|
25
|
+
export { NotAZip, UnsupportedArchive } from "./zip-reader.js";
|
|
26
|
+
/** Only entries under this prefix are the library; the prefix is stripped on the way out. */
|
|
27
|
+
export const DEFAULT_ROOT = "integrations/library-json/";
|
|
28
|
+
/**
|
|
29
|
+
* Extraction stopped. `message` is exactly what the Python printed to stderr
|
|
30
|
+
* (`extract-library: …`), `code` says which way it failed, and `cause` carries
|
|
31
|
+
* the underlying {@link UnsafeArchive} / {@link NotAZip} /
|
|
32
|
+
* {@link UnsupportedArchive} when there is one.
|
|
33
|
+
*/
|
|
34
|
+
export class ExtractLibraryError extends Error {
|
|
35
|
+
code;
|
|
36
|
+
exitCode = 1;
|
|
37
|
+
constructor(message, code, options) {
|
|
38
|
+
super(message, options);
|
|
39
|
+
this.code = code;
|
|
40
|
+
this.name = "ExtractLibraryError";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** `child` is `parent` or lives beneath it — both already `path.resolve`d. */
|
|
44
|
+
function isWithin(parent, child) {
|
|
45
|
+
return child === parent || child.startsWith(parent.endsWith(sep) ? parent : parent + sep);
|
|
46
|
+
}
|
|
47
|
+
export function extractArchive(options) {
|
|
48
|
+
const { archivePath, destination } = options;
|
|
49
|
+
const root = options.root ?? DEFAULT_ROOT;
|
|
50
|
+
if (!isFile(archivePath)) {
|
|
51
|
+
throw new ExtractLibraryError(`extract-library: no such archive: ${archivePath}`, "NO_SUCH_ARCHIVE");
|
|
52
|
+
}
|
|
53
|
+
ensureDir(destination);
|
|
54
|
+
const destinationRoot = resolve(destination);
|
|
55
|
+
let written = 0;
|
|
56
|
+
try {
|
|
57
|
+
const buffer = readFileSync(archivePath);
|
|
58
|
+
for (const entry of readZipEntries(buffer)) {
|
|
59
|
+
const relative = safeRelativePath(entry.name, root);
|
|
60
|
+
if (relative === undefined)
|
|
61
|
+
continue;
|
|
62
|
+
if (isSymlinkEntry(entry.externalAttr)) {
|
|
63
|
+
throw new UnsafeArchive(`symlink entry: ${entry.name}`);
|
|
64
|
+
}
|
|
65
|
+
const target = resolve(destinationRoot, ...relative.split("/"));
|
|
66
|
+
// Defence in depth: the policy above already guarantees this, so it can
|
|
67
|
+
// never fire — but a second, independent check on the resolved path
|
|
68
|
+
// costs nothing and does not depend on the policy being right.
|
|
69
|
+
if (!isWithin(destinationRoot, target)) {
|
|
70
|
+
throw new UnsafeArchive(`entry escapes the destination: ${entry.name}`);
|
|
71
|
+
}
|
|
72
|
+
writeBytes(target, entryData(buffer, entry));
|
|
73
|
+
written += 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error instanceof UnsafeArchive) {
|
|
78
|
+
throw new ExtractLibraryError(`extract-library: ${error.message}`, "UNSAFE_ARCHIVE", { cause: error });
|
|
79
|
+
}
|
|
80
|
+
if (error instanceof NotAZip) {
|
|
81
|
+
throw new ExtractLibraryError(`extract-library: ${archivePath} is not a zip: ${error.message}`, "NOT_A_ZIP", { cause: error });
|
|
82
|
+
}
|
|
83
|
+
if (error instanceof UnsupportedArchive) {
|
|
84
|
+
throw new ExtractLibraryError(`extract-library: ${archivePath} uses a zip feature this reader refuses: ${error.message}`, "UNSUPPORTED_ARCHIVE", { cause: error });
|
|
85
|
+
}
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
if (written === 0 || !isFile(join(destination, "index.json"))) {
|
|
89
|
+
throw new ExtractLibraryError(`extract-library: ${archivePath} carries no ${root}index.json`, "NO_INDEX");
|
|
90
|
+
}
|
|
91
|
+
return { written };
|
|
92
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** The bytes are not a zip we can read: no end record, bad signatures, corrupt data. */
|
|
2
|
+
export declare class NotAZip extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
/** A well-formed zip that uses a feature this reader refuses (zip64, encryption, …). */
|
|
6
|
+
export declare class UnsupportedArchive extends Error {
|
|
7
|
+
constructor(message: string);
|
|
8
|
+
}
|
|
9
|
+
export interface ZipEntry {
|
|
10
|
+
/** The entry name as stored, `/`-separated by convention (not enforced here). */
|
|
11
|
+
readonly name: string;
|
|
12
|
+
/** 0 = stored, 8 = deflate. Anything else has already been refused. */
|
|
13
|
+
readonly method: number;
|
|
14
|
+
/** General purpose bit flags from the central directory. */
|
|
15
|
+
readonly flags: number;
|
|
16
|
+
readonly crc32: number;
|
|
17
|
+
readonly compressedSize: number;
|
|
18
|
+
readonly uncompressedSize: number;
|
|
19
|
+
readonly localHeaderOffset: number;
|
|
20
|
+
/** The high 16 bits carry the unix mode when the archive was made on unix. */
|
|
21
|
+
readonly externalAttr: number;
|
|
22
|
+
/** A directory entry: its name ends with `/`. */
|
|
23
|
+
readonly isDirectory: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Every entry in the archive, in central-directory order, with the features we
|
|
27
|
+
* refuse already rejected. Throws {@link NotAZip} when the bytes are not a zip
|
|
28
|
+
* and {@link UnsupportedArchive} for one we decline to read.
|
|
29
|
+
*/
|
|
30
|
+
export declare function readZipEntries(buffer: Uint8Array): ZipEntry[];
|
|
31
|
+
/**
|
|
32
|
+
* The uncompressed bytes of one entry, CRC-checked.
|
|
33
|
+
*
|
|
34
|
+
* The local header's name and extra lengths are read from the local header
|
|
35
|
+
* itself: a writer may store a different extra field there than in the central
|
|
36
|
+
* directory, so the directory's lengths cannot be used to find the data.
|
|
37
|
+
*/
|
|
38
|
+
export declare function entryData(buffer: Uint8Array, entry: ZipEntry): Uint8Array;
|