@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.
Files changed (161) hide show
  1. package/dist/api-index.md +6 -6
  2. package/dist/cli/commands/registry.js +68 -12
  3. package/dist/registry/archive/entry-policy.d.ts +36 -0
  4. package/dist/registry/archive/entry-policy.js +60 -0
  5. package/dist/registry/archive/extract.d.ts +31 -0
  6. package/dist/registry/archive/extract.js +92 -0
  7. package/dist/registry/archive/zip-reader.d.ts +38 -0
  8. package/dist/registry/archive/zip-reader.js +256 -0
  9. package/dist/registry/codecs/pyjson.d.ts +62 -0
  10. package/dist/registry/codecs/pyjson.js +208 -0
  11. package/dist/registry/compat/compare.d.ts +18 -0
  12. package/dist/registry/compat/compare.js +32 -0
  13. package/dist/registry/compat/paths.d.ts +4 -0
  14. package/dist/registry/compat/paths.js +17 -0
  15. package/dist/registry/compat/pystr.d.ts +50 -0
  16. package/dist/registry/compat/pystr.js +221 -0
  17. package/dist/registry/concurrency.d.ts +10 -0
  18. package/dist/registry/concurrency.js +34 -0
  19. package/dist/registry/curated-overlay.d.ts +36 -4
  20. package/dist/registry/curated-overlay.js +87 -14
  21. package/dist/registry/errors.d.ts +26 -0
  22. package/dist/registry/errors.js +38 -0
  23. package/dist/registry/extractor.d.ts +9 -10
  24. package/dist/registry/extractor.js +5 -15
  25. package/dist/registry/files.d.ts +26 -0
  26. package/dist/registry/files.js +160 -0
  27. package/dist/registry/generators/descriptors.d.ts +81 -0
  28. package/dist/registry/generators/descriptors.js +405 -0
  29. package/dist/registry/generators/fields.d.ts +46 -0
  30. package/dist/registry/generators/fields.js +111 -0
  31. package/dist/registry/generators/markdown/common.d.ts +21 -0
  32. package/dist/registry/generators/markdown/common.js +55 -0
  33. package/dist/registry/generators/markdown/index.d.ts +33 -0
  34. package/dist/registry/generators/markdown/index.js +37 -0
  35. package/dist/registry/generators/markdown/page.d.ts +8 -0
  36. package/dist/registry/generators/markdown/page.js +52 -0
  37. package/dist/registry/generators/markdown/pyvalue.d.ts +34 -0
  38. package/dist/registry/generators/markdown/pyvalue.js +106 -0
  39. package/dist/registry/generators/markdown/sections/choosing-object.d.ts +2 -0
  40. package/dist/registry/generators/markdown/sections/choosing-object.js +52 -0
  41. package/dist/registry/generators/markdown/sections/example.d.ts +22 -0
  42. package/dist/registry/generators/markdown/sections/example.js +295 -0
  43. package/dist/registry/generators/markdown/sections/filtering.d.ts +2 -0
  44. package/dist/registry/generators/markdown/sections/filtering.js +76 -0
  45. package/dist/registry/generators/markdown/sections/header.d.ts +2 -0
  46. package/dist/registry/generators/markdown/sections/header.js +37 -0
  47. package/dist/registry/generators/markdown/sections/inputs.d.ts +30 -0
  48. package/dist/registry/generators/markdown/sections/inputs.js +151 -0
  49. package/dist/registry/generators/markdown/sections/lookups.d.ts +12 -0
  50. package/dist/registry/generators/markdown/sections/lookups.js +150 -0
  51. package/dist/registry/generators/markdown/sections/outputs.d.ts +3 -0
  52. package/dist/registry/generators/markdown/sections/outputs.js +9 -0
  53. package/dist/registry/generators/markdown/sections/resolving-fields.d.ts +2 -0
  54. package/dist/registry/generators/markdown/sections/resolving-fields.js +139 -0
  55. package/dist/registry/generators/markdown/sections/schema-dynamic.d.ts +2 -0
  56. package/dist/registry/generators/markdown/sections/schema-dynamic.js +86 -0
  57. package/dist/registry/generators/materialize.d.ts +30 -0
  58. package/dist/registry/generators/materialize.js +132 -0
  59. package/dist/registry/generators/md-symbols.d.ts +22 -0
  60. package/dist/registry/generators/md-symbols.js +58 -0
  61. package/dist/registry/generators/triggers.d.ts +15 -0
  62. package/dist/registry/generators/triggers.js +85 -0
  63. package/dist/registry/generators/type-registry.d.ts +50 -0
  64. package/dist/registry/generators/type-registry.js +154 -0
  65. package/dist/registry/generators/type-tree.d.ts +65 -0
  66. package/dist/registry/generators/type-tree.js +179 -0
  67. package/dist/registry/library-build/build.d.ts +57 -0
  68. package/dist/registry/library-build/build.js +286 -0
  69. package/dist/registry/library-build/canonical.d.ts +119 -0
  70. package/dist/registry/library-build/canonical.js +512 -0
  71. package/dist/registry/library-build/cli.d.ts +45 -0
  72. package/dist/registry/library-build/cli.js +182 -0
  73. package/dist/registry/library-build/context.d.ts +107 -0
  74. package/dist/registry/library-build/context.js +267 -0
  75. package/dist/registry/library-build/enrich.d.ts +36 -0
  76. package/dist/registry/library-build/enrich.js +158 -0
  77. package/dist/registry/library-build/events.d.ts +108 -0
  78. package/dist/registry/library-build/events.js +476 -0
  79. package/dist/registry/library-build/generic-expand.d.ts +77 -0
  80. package/dist/registry/library-build/generic-expand.js +363 -0
  81. package/dist/registry/library-build/plan.d.ts +71 -0
  82. package/dist/registry/library-build/plan.js +270 -0
  83. package/dist/registry/library-build/py.d.ts +45 -0
  84. package/dist/registry/library-build/py.js +167 -0
  85. package/dist/registry/library-build/schema-action.d.ts +38 -0
  86. package/dist/registry/library-build/schema-action.js +107 -0
  87. package/dist/registry/library-build/write.d.ts +6 -0
  88. package/dist/registry/library-build/write.js +26 -0
  89. package/dist/registry/library-reader.d.ts +20 -0
  90. package/dist/registry/library-reader.js +50 -0
  91. package/dist/registry/lookup-specs.d.ts +55 -0
  92. package/dist/registry/lookup-specs.js +140 -0
  93. package/dist/registry/materialize-cli.js +28 -14
  94. package/dist/registry/materializer.d.ts +25 -3
  95. package/dist/registry/materializer.js +45 -29
  96. package/dist/registry/model/library.d.ts +254 -0
  97. package/dist/registry/model/library.js +23 -0
  98. package/dist/registry/naming.d.ts +54 -0
  99. package/dist/registry/naming.js +142 -0
  100. package/dist/registry/orphans.d.ts +4 -0
  101. package/dist/registry/orphans.js +86 -0
  102. package/dist/registry/prepare/actions.d.ts +26 -0
  103. package/dist/registry/prepare/actions.js +94 -0
  104. package/dist/registry/prepare/connections.d.ts +181 -0
  105. package/dist/registry/prepare/connections.js +399 -0
  106. package/dist/registry/prepare/event.d.ts +96 -0
  107. package/dist/registry/prepare/event.js +268 -0
  108. package/dist/registry/prepare/handoff.d.ts +21 -0
  109. package/dist/registry/prepare/handoff.js +91 -0
  110. package/dist/registry/prepare/ledger.d.ts +104 -0
  111. package/dist/registry/prepare/ledger.js +216 -0
  112. package/dist/registry/prepare/messages.d.ts +190 -0
  113. package/dist/registry/prepare/messages.js +472 -0
  114. package/dist/registry/prepare/objects.d.ts +130 -0
  115. package/dist/registry/prepare/objects.js +318 -0
  116. package/dist/registry/prepare/overlay.d.ts +149 -0
  117. package/dist/registry/prepare/overlay.js +487 -0
  118. package/dist/registry/prepare/parents.d.ts +41 -0
  119. package/dist/registry/prepare/parents.js +122 -0
  120. package/dist/registry/prepare/pipeline.d.ts +91 -0
  121. package/dist/registry/prepare/pipeline.js +376 -0
  122. package/dist/registry/prepare/regenerate.d.ts +9 -0
  123. package/dist/registry/prepare/regenerate.js +50 -0
  124. package/dist/registry/prepare/resolve.d.ts +199 -0
  125. package/dist/registry/prepare/resolve.js +470 -0
  126. package/dist/registry/prepare/retry.d.ts +83 -0
  127. package/dist/registry/prepare/retry.js +109 -0
  128. package/dist/registry/prepare/source-import.d.ts +54 -0
  129. package/dist/registry/prepare/source-import.js +192 -0
  130. package/dist/registry/prepare/support.d.ts +41 -0
  131. package/dist/registry/prepare/support.js +178 -0
  132. package/dist/registry/prepare-cli.d.ts +16 -5
  133. package/dist/registry/prepare-cli.js +60 -53
  134. package/dist/registry/pull-cli.js +8 -7
  135. package/dist/registry/reporter.d.ts +26 -0
  136. package/dist/registry/reporter.js +41 -0
  137. package/dist/registry/uip/client.d.ts +99 -0
  138. package/dist/registry/uip/client.js +226 -0
  139. package/dist/registry/uip/commands.d.ts +40 -0
  140. package/dist/registry/uip/commands.js +80 -0
  141. package/dist/registry/uip/envelope.d.ts +37 -0
  142. package/dist/registry/uip/envelope.js +84 -0
  143. package/dist/registry/uip/response-cache.d.ts +28 -0
  144. package/dist/registry/uip/response-cache.js +57 -0
  145. package/lib/README.md +10 -8
  146. package/package.json +3 -3
  147. package/dist/generators/_connections.py +0 -331
  148. package/dist/generators/_events.py +0 -355
  149. package/dist/generators/_lookups.py +0 -114
  150. package/dist/generators/_naming.py +0 -185
  151. package/dist/generators/_objects.py +0 -299
  152. package/dist/generators/_resolve.py +0 -319
  153. package/dist/generators/convert_library_to_md.py +0 -1441
  154. package/dist/generators/extract_library.py +0 -125
  155. package/dist/generators/generate_connectors_ts.py +0 -936
  156. package/dist/generators/generate_library_json.py +0 -2527
  157. package/dist/generators/generate_triggers_ts.py +0 -110
  158. package/dist/generators/materialize_registry.py +0 -155
  159. package/dist/generators/prepare_connector.py +0 -1860
  160. package/dist/registry/python.d.ts +0 -26
  161. 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 installed package root:
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
- The spans are generated by the same build that emits the `dist/` they point
33
- into, so they match the package they ship beside. They are NOT stable across
34
- versions — a line moves whenever the declaration above it changes — so an index
35
- and a `dist/` from different releases will not line up. If a span looks wrong,
36
- that is the thing to check first.
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('--extractor <path>', 'extract_library.py to run instead of the packaged copy')
33
- .option('--generator <path>', 'materialize_registry.py to run instead of the packaged copy')
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
- .requiredOption('--library-md <dir>', 'markdown output directory')
58
- .requiredOption('--connectors <dir>', 'TypeScript descriptor output directory')
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('--python <executable>', 'Python interpreter to run the generators with')
62
- .option('--generator <path>', 'materialize_registry.py to run instead of the packaged copy')
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
- .option('--no-bindings', 'discover the connection but leave bindings.json alone')
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>', 'Python interpreter to run the generator with')
96
- .option('--generator <path>', 'prepare_connector.py to run instead of the packaged copy')
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
- if (options.bindings === false)
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;