RDMR-1452 - Bump dependencies and fix vulnerabilities - #37
Merged
Merged
Conversation
usernuno
force-pushed
the
fix/RDMR-1452/dependencies-up-to-date
branch
3 times, most recently
from
September 15, 2026 18:39
f94d947 to
d270dc5
Compare
usernuno
marked this pull request as ready for review
September 15, 2026 18:43
usernuno
requested review from
Chuckytuh,
EiyuuZack,
OS-kepatotorica,
OS-ruialves,
benmccarty91 and
trevor-lambert
September 15, 2026 18:43
usernuno
force-pushed
the
fix/RDMR-1452/dependencies-up-to-date
branch
from
September 15, 2026 18:52
d270dc5 to
e99b20b
Compare
benmccarty91
left a comment
Contributor
There was a problem hiding this comment.
Should we also bump the root package.json version so this change can be published? I don't think there's automation in place to do this for us after it's merged.
usernuno
force-pushed
the
fix/RDMR-1452/dependencies-up-to-date
branch
from
September 18, 2026 10:10
e99b20b to
afdfa13
Compare
Brings every runtime and build dependency up to date, with the
exceptions noted below. Several of these releases are ESM-only or
parse more strictly than before, so a number of source changes were
needed to keep trampoline working as a CommonJS library.
Dependency updates
------------------
Runtime:
@ionic/cli-framework-output 2.2.2 -> 2.2.8
@ionic/utils-fs 3.1.5 -> 3.1.7
@ionic/utils-subprocess 2.1.8 -> 3.0.1
@ionic/utils-terminal 2.3.1 -> 2.3.5
@prettier/plugin-xml 2.2.0 -> 3.4.2
@xmldom/xmldom 0.7.5 -> 0.8.15
commander 8.2.0 -> 15.0.0
cross-spawn 7.0.3 -> 7.0.6
diff 7.0.0 -> 9.0.0
ini 5.0.0 -> 7.0.0
lodash 4.17.21 -> 4.18.1
plist 3.0.4 -> 5.0.0
prettier 2.7.1 -> 3.9.6
sharp 0.33.5 -> 0.35.4
tempy 1.0.1 -> 3.2.0
xpath 0.0.32 -> 0.0.34
yaml 1.10.2 -> 2.9.1
yargs 17.2.1 -> 18.1.0
Build:
typescript 4.4.4 -> 6.0.3
@types/cross-spawn 6.0.2 -> 6.0.6
@types/lodash 4.14.175 -> 4.17.25
@types/prompts 2.0.14 -> 2.4.9
Removed:
replace not imported anywhere in src
jest no test files, no test script, never run in CI
@types/sharp stub package, sharp ships its own types
@types/diff stub package, diff ships its own types
@types/plist plist types are now declared locally
@types/fs-extra provided by @ionic/utils-fs, which depends on it
Added:
@types/node was only reaching the build transitively
@types/yargs was only reaching the build by way of jest
Not updated to latest
---------------------
@xmldom/xmldom is held at 0.8.15 rather than 0.9.12. 0.9 parses
strictly: an undeclared namespace prefix, a fragment with more than one
root, a parseFromString without a mimeType, and input the old parser
quietly repaired are all fatal there. Each is reachable from an
existing configuration — a stock manifest declares only xmlns:android,
so a tools: fragment against it would stop being written — and matching
the old behaviour on 0.9 meant a namespace-resolution layer around
every fragment. 0.8.15 parses as 0.7 did, so none of that is needed and
XmlFile is left alone.
It carries no advisory of its own: the critical one covers <=0.8.14 and
was fixed there the same day 0.9.12 shipped, so the line is maintained
in parallel rather than left behind. The cost is a duplicate — plist
wants ^0.9.10 and xcode reaches another copy through simple-plist, so
the lockfile carries 0.9.12 twice alongside the hoisted 0.8.15. Neither
is a vulnerability and the two never meet, since plist hands back plain
objects rather than nodes.
@types/node is held at ^22 so it tracks the oldest supported Node
line declared in engines rather than the newest release.
typescript is held at ^6 to stay off the native port. TypeScript 7.0
is the Go rewrite of the compiler; 6.x is the last line running the
established JavaScript implementation, which is the safer toolchain
for a published package. This is a deliberate hold rather than a
blocker: 7.0 type-checks this project without errors, so the move can
be made on its own once the port has settled.
Vulnerabilities
---------------
Lowering these was a main goal of the bump. npm audit goes from 25
findings to 2:
before: 1 critical, 10 high, 3 moderate, 11 low
after: 2 moderate
Cleared: form-data (critical); @xmldom/xmldom, brace-expansion,
browserslist, js-yaml, lodash, minimatch, picomatch, replace, sharp
and ws (high); yaml (moderate); and the jest, babel and jsdom chains
(low), which went away with jest itself.
Still open, both stemming from the same root:
moderate uuid <11.1.1 reached through xcode@3.0.1 (uuid@7.0.3)
moderate xcode >=0.9.2 reported as the parent of the above
xcode has published no release that moves off uuid 7. These could be
silenced with an overrides entry pinning xcode's uuid, but that was
left out deliberately: pinning a third-party package's transitive is
outside the scope of a dependency bump, and npm only honours overrides
in the root project, so it would clear this repo's audit without
protecting any consumer. The advisory is also unreachable here — it
covers uuid v3/v5/v6 called with a buf argument, and xcode's only call
is uuid.v4() with none.
Node requirements
-----------------
engines.node is now "^22.22.2 || ^24.15.0 || >=26.0.0". That mirrors
ini@7, the strictest constraint in the tree, and every other
dependency is satisfied by it. Note the gaps: Node 23.x, 24.0-24.14
and 25.x are excluded.
CI now builds on Node 22.x and 24.x rather than 20.x, installs with
npm ci against the lockfile, and uses setup-node's built-in npm cache
instead of a hand-rolled actions/cache step.
tsconfig lib moves from es2019 to es2022 because utils-subprocess 3
reports the underlying failure on Error.cause, which es2019 does not
declare. target moves with it, so optional chaining and nullish
coalescing stop being downlevelled into _a === null || _a === void 0
chains; the Node floor already rules out every runtime that needed
that. rootDir is pinned to ./src — without it the inferred root moves
and the whole build lands under dist/src, which would change every
published path.
Source changes required by the bumps
------------------------------------
ESM-only dependencies. commander, plist, tempy and prettier's XML
plugin no longer publish a CommonJS entry point, so they are loaded
through dynamic import() and tsconfig moves to module/moduleResolution
"node16". Under "commonjs" TypeScript downlevels import() into
require(), which fails on tempy's top-level await. plist and tempy have
no way back — plist's export map declares only browser and import
conditions — so their helpers are async whatever the types say.
The operation loader is the one import() that does not survive the
switch. A native import() of a CommonJS module exposes module.exports
as its default, so `f.default` became the whole `{ OPS, default }`
object and every one of the 31 handlers registered as a non-callable —
`isOpRegistered` still said yes, and the first operation died on
`handler is not a function`. It loads handlers by a path built at
runtime, from this package's own CommonJS output, so it uses require()
as it effectively did before.
prettier 3 made format() async and dropped pluginSearchDirs, and
@prettier/plugin-xml 3 is an ES module, so it is loaded through
dynamic import().
It also moves off prettier/standalone onto the full build, which
matters for bracketSameLine below: standalone resolves only the options
its plugins register, so under prettier 3 a core option is dropped
without a word. Nothing here runs in a browser, so standalone bought
nothing to offset that.
xmlWhitespaceSensitivity moves from 'ignore' to 'preserve'. Under
plugin-xml 3 'ignore' re-tokenises a text node on its entity references
and rejoins the pieces with spaces, so `AT&T` became `AT & T` and
`Use <b>bold</b>` became `Use < b > bold < /b >` — the styling markup
stops being markup. It reaches every file trampoline opens, not only
the ones an operation targets, because the VFS commits all of them. Its
only reason to be on was element layout, which 'preserve' formats the
same way.
Three byte-level differences from before remain, none of which changes
a parsed value:
- a self-closing tag whose attributes wrap past printWidth now
closes with "/>" on the last attribute's line rather than its own.
plugin-xml 2 drove only ">" from bracketSameLine; 3 drives both
from the one flag.
- `>` in character data is written as `>`, from xmldom 0.8.
- text is no longer reflowed or trimmed: `<a> padded </a>` keeps
its padding, and mixed content stays on one line. 'ignore' rewrote
both, which is how it altered values in the first place.
@ionic/utils-subprocess 3 removed SubprocessError.error and moved the
underlying OS error to .cause. Reading .message instead would have
yielded a fixed literal such as "Command error.", so spawn failures
like EACCES now come from .cause.
xpath 0.0.34 narrows what select() returns, so the one call that reads
the result as a node list casts it, as the neighbouring calls already
did.
Bugs found while verifying the above
------------------------------------
These predate the bump but sit on changed lines.
injectFragment and replaceFragment parsed the fragment once and then
re-parented those same nodes into every match of the target. Only the
first match ever received them: appendChild and insertBefore move a
node rather than copy it, so by the second match the parse had been
emptied. replaceFragment additionally removed each later match before
discovering it had nothing to insert, so a target matching two nodes
deleted the second and then failed the run with "Cannot read
properties of null (reading 'nodeType')". Both now parse once per
match.
The single match path is not quite byte-identical either. Iterating a
live NodeList while appendChild re-parented out of it skipped every
other node, so `<!-- c --><a/>` injected only the comment and dropped
the element. Snapshotting first fixes that; text after the first
element is skipped, because a document has one root and anything
textual past it is the raw source a second root degraded to, which the
old code wrote back escaped.
The project.xml and ios.xml handlers passed entry.merge to setAttrs
where android.xml passes entry.attrs, so an attrs entry threw "Cannot
convert undefined or null to object" in the first and was swallowed as
a "Skipping ..." warning in the second. It affects trampoline's own
configure pipeline only; a consumer driving XmlFile directly never
reached these handlers.
Two more predate the bump and are fixed here because they destroy
work. writeProperties serialized its data and then wrote the object it
started from, so every android.properties operation died on
ERR_INVALID_ARG_TYPE. That rejection reached commitAll, which was a
Promise.all, and the process exited before the writes still in flight
could finish — a run that touched a gradle.properties left its
manifest, strings.xml and res/xml files at zero bytes. commitAll now
settles every commit before re-throwing, so a failing run reports the
same error with every other file intact.
replaceProperties also called insertIntoGradleFile without awaiting it,
on the path where a target is missing but its parent is not. The insert
assigns its result after an await, so it only landed because the source
was already cached; it is returned now rather than left to win a race.
Other changes
-------------
Removed src/configure/util/node.ts. Neither of its exports was called
anywhere, and it held the only runtime import of typescript, which was
declared only as a devDependency.
Dropped an unused chalk import and moved an fs-extra import over to
@ionic/utils-fs. Neither package was declared in package.json; both
were resolving purely by hoisting. An empty ambient declaration for
@prettier/plugin-xml was masking a require(esm) call and erasing the
plugin's own typings, and two more ambient declarations named packages
that are neither imported nor depended on.
plist's PlistValue is declared locally alongside PlistObject. Importing
it required a resolution-mode attribute that was emitted verbatim into
the published .d.ts, and that syntax cannot be parsed by TypeScript
older than 5.3.
Set importHelpers to false. The build emitted require("tslib") across
20 files while tslib was not a declared dependency, leaving consumers
dependent on it being hoisted into their tree.
Deduplicated the plist build options and flattened the subprocess
error chain.
Adds PlistObject/PlistValue to the public surface. Nothing exported
from src/index.ts is removed; the only removals anywhere are the two
node.ts exports, reachable by deep import alone.
Verified against capacitor-mobile-nativeshell, the one known consumer:
both shell-handler and capacitor-outsystems-builder type-check and
their suites pass unchanged, and the manifest, styles.xml and network
security config flows they drive produce byte-identical output.
XmlFile behaviour was checked against the previous build directly
rather than inferred from those suites, which reach the library and not
the operation pipeline: a multi-root fragment, an undeclared prefix, a
file the parser rejects, and a single-element inject all produce
identical output. The multi-match and comment-bearing injects above are
the intended differences; the three formatting notes are the rest.
BREAKING CHANGE: runProgram() and parsePlistString() are now async, and
PlistObject/PlistValue are declared by trampoline rather than
re-exported from plist. These break JavaScript callers at runtime and
TypeScript callers at compile time: parsePlistString now hands back a
promise, and a plist <data>
value parses to Uint8Array rather than Buffer, so .toString('base64')
on one returns its bytes as digits. plist is ESM-only with no require
condition, so the async signatures cannot be walked back.
References https://outsystemsrd.atlassian.net/browse/RDMR-1452
usernuno
force-pushed
the
fix/RDMR-1452/dependencies-up-to-date
branch
from
September 18, 2026 10:21
afdfa13 to
0074de9
Compare
Contributor
Author
Looking at the commit history, this is typically done in a following commit or PR. |
EiyuuZack
approved these changes
Sep 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Brings every runtime and build dependency up to date, with the exceptions noted below. Several of these releases are ESM-only or parse more strictly than before, so a number of source changes were needed to keep trampoline working as a CommonJS library.
Dependency updates
Runtime:
Build:
Removed:
Added:
Not updated to latest
@xmldom/xmldomis held at 0.8.15 rather than 0.9.12. 0.9 parses strictly: an undeclared namespace prefix, a fragment with more than one root, aparseFromStringwithout a mimeType, and input the old parser quietly repaired are all fatal there. Each is reachable from an existing configuration — a stock manifest declares onlyxmlns:android, so atools:fragment against it would stop being written — and matching the old behaviour on 0.9 meant a namespace-resolution layer around every fragment. 0.8.15 parses as 0.7 did, so none of that is needed andXmlFileis left alone.It carries no advisory of its own: the critical one covers
<=0.8.14and was fixed there the same day 0.9.12 shipped, so the line is maintained in parallel rather than left behind. The cost is a duplicate —plistwants^0.9.10andxcodereaches another copy throughsimple-plist, so the lockfile carries 0.9.12 twice alongside the hoisted 0.8.15. Neither is a vulnerability and the two never meet, sinceplisthands back plain objects rather than nodes.@types/nodeis held at^22so it tracks the oldest supported Node line declared inenginesrather than the newest release.typescriptis held at^6to stay off the native port. TypeScript 7.0 is the Go rewrite of the compiler; 6.x is the last line running the established JavaScript implementation, which is the safer toolchain for a published package. This is a deliberate hold rather than a blocker: 7.0 type-checks this project without errors, so the move can be made on its own once the port has settled.Vulnerabilities
Lowering these was a main goal of the bump.
npm auditgoes from 25 findings to 2:Cleared:
form-data(critical);@xmldom/xmldom,brace-expansion,browserslist,js-yaml,lodash,minimatch,picomatch,replace,sharpandws(high);yaml(moderate); and thejest,babelandjsdomchains (low), which went away withjestitself.Still open, both stemming from the same root:
xcodehas published no release that moves offuuid7. These could be silenced with anoverridesentry pinningxcode'suuid, but that was left out deliberately: pinning a third-party package's transitive is outside the scope of a dependency bump, and npm only honoursoverridesin the root project, so it would clear this repo's audit without protecting any consumer. The advisory is also unreachable here — it coversuuidv3/v5/v6 called with abufargument, andxcode's only call isuuid.v4()with none.Node requirements
engines.nodeis now^22.22.2 || ^24.15.0 || >=26.0.0. That mirrorsini@7, the strictest constraint in the tree, and every other dependency is satisfied by it. Note the gaps: Node 23.x, 24.0–24.14 and 25.x are excluded.CI now builds on Node 22.x and 24.x rather than 20.x, installs with
npm ciagainst the lockfile, and usessetup-node's built-in npm cache instead of a hand-rolledactions/cachestep.tsconfiglibmoves fromes2019toes2022because@ionic/utils-subprocess3 reports the underlying failure onError.cause, whiches2019does not declare.targetmoves with it, so optional chaining and nullish coalescing stop being downlevelled into_a === null || _a === void 0chains; the Node floor already rules out every runtime that needed that.rootDiris pinned to./src— without it the inferred root moves and the whole build lands underdist/src, which would change every published path.Source changes required by the bumps
ESM-only dependencies.
commander,plist,tempyand prettier's XML plugin no longer publish a CommonJS entry point, so they are loaded through dynamicimport()andtsconfigmoves tomodule/moduleResolutionnode16. UndercommonjsTypeScript downlevelsimport()intorequire(), which fails ontempy's top-level await.plistandtempyhave no way back —plist's export map declares onlybrowserandimportconditions — so their helpers are async whatever the types say.The operation loader is the one
import()that does not survive the switch. A nativeimport()of a CommonJS module exposesmodule.exportsas its default, sof.defaultbecame the whole{ OPS, default }object and every one of the 31 handlers registered as a non-callable —isOpRegisteredstill said yes, and the first operation died onhandler is not a function. It loads handlers by a path built at runtime, from this package's own CommonJS output, so it usesrequire()as it effectively did before.prettier3 madeformat()async and droppedpluginSearchDirs, and@prettier/plugin-xml3 is an ES module, so it is loaded through dynamicimport().It also moves off
prettier/standaloneonto the full build, which matters forbracketSameLinebelow: standalone resolves only the options its plugins register, so underprettier3 a core option is dropped without a word. Nothing here runs in a browser, so standalone bought nothing to offset that.xmlWhitespaceSensitivitymoves from'ignore'to'preserve'. Under plugin-xml 3,'ignore're-tokenises a text node on its entity references and rejoins the pieces with spaces, soAT&TbecameAT & TandUse <b>bold</b>becameUse < b > bold < /b >— the styling markup stops being markup. It reaches every file trampoline opens, not only the ones an operation targets, because the VFS commits all of them. Its only reason to be on was element layout, which'preserve'formats the same way.Three byte-level differences from before remain, none of which changes a parsed value:
printWidthnow closes with/>on the last attribute's line rather than its own. plugin-xml 2 drove only>frombracketSameLine; 3 drives both from the one flag.>in character data is written as>, from xmldom 0.8.<a> padded </a>keeps its padding, and mixed content stays on one line.'ignore'rewrote both, which is how it altered values in the first place.@ionic/utils-subprocess3 removedSubprocessError.errorand moved the underlying OS error to.cause. Reading.messageinstead would have yielded a fixed literal such asCommand error., so spawn failures likeEACCESnow come from.cause.xpath0.0.34 narrows whatselect()returns, so the one call that reads the result as a node list casts it, as the neighbouring calls already did.Bugs found while verifying the above
These predate the bump but sit on changed lines.
injectFragmentandreplaceFragmentparsed the fragment once and then re-parented those same nodes into every match of the target. Only the first match ever received them:appendChildandinsertBeforemove a node rather than copy it, so by the second match the parse had been emptied.replaceFragmentadditionally removed each later match before discovering it had nothing to insert, so a target matching two nodes deleted the second and then failed the run withCannot read properties of null (reading 'nodeType'). Both now parse once per match.The single match path is not quite byte-identical either. Iterating a live NodeList while
appendChildre-parented out of it skipped every other node, so<!-- c --><a/>injected only the comment and dropped the element. Snapshotting first fixes that; text after the first element is skipped, because a document has one root and anything textual past it is the raw source a second root degraded to, which the old code wrote back escaped.The
project.xmlandios.xmlhandlers passedentry.mergetosetAttrswhereandroid.xmlpassesentry.attrs, so anattrsentry threwCannot convert undefined or null to objectin the first and was swallowed as aSkipping ...warning in the second. It affects trampoline's own configure pipeline only; a consumer drivingXmlFiledirectly never reached these handlers.Two more predate the bump and are fixed here because they destroy work.
writePropertiesserialized its data and then wrote the object it started from, so everyandroid.propertiesoperation died onERR_INVALID_ARG_TYPE. That rejection reachedcommitAll, which was aPromise.all, and the process exited before the writes still in flight could finish — a run that touched agradle.propertiesleft its manifest,strings.xmlandres/xmlfiles at zero bytes.commitAllnow settles every commit before re-throwing, so a failing run reports the same error with every other file intact.replacePropertiesalso calledinsertIntoGradleFilewithout awaiting it, on the path where a target is missing but its parent is not. The insert assigns its result after an await, so it only landed because the source was already cached; it is returned now rather than left to win a race.Other changes
Removed
src/configure/util/node.ts. Neither of its exports was called anywhere, and it held the only runtime import oftypescript, which was declared only as a devDependency.Dropped an unused
chalkimport and moved anfs-extraimport over to@ionic/utils-fs. Neither package was declared inpackage.json; both were resolving purely by hoisting. An empty ambient declaration for@prettier/plugin-xmlwas masking arequire(esm)call and erasing the plugin's own typings, and two more ambient declarations named packages that are neither imported nor depended on.plist'sPlistValueis declared locally alongsidePlistObject. Importing it required a resolution-mode attribute that was emitted verbatim into the published.d.ts, and that syntax cannot be parsed by TypeScript older than 5.3.Set
importHelperstofalse. The build emittedrequire("tslib")across 20 files whiletslibwas not a declared dependency, leaving consumers dependent on it being hoisted into their tree.Deduplicated the plist build options and flattened the subprocess error chain.
Adds
PlistObject/PlistValueto the public surface. Nothing exported fromsrc/index.tsis removed; the only removals anywhere are the twonode.tsexports, reachable by deep import alone.Verified against
capacitor-mobile-nativeshell, the one known consumer: bothshell-handlerandcapacitor-outsystems-buildertype-check and their suites pass unchanged, and the manifest,styles.xmland network security config flows they drive produce byte-identical output.XmlFilebehaviour was checked against the previous build directly rather than inferred from those suites, which reach the library and not the operation pipeline: a multi-root fragment, an undeclared prefix, a file the parser rejects, and a single-element inject all produce identical output. The multi-match and comment-bearing injects above are the intended differences; the three formatting notes are the rest.BREAKING CHANGE:
runProgram()andparsePlistString()are now async, andPlistObject/PlistValueare declared by trampoline rather than re-exported fromplist. These break JavaScript callers at runtime and TypeScript callers at compile time:parsePlistStringnow hands back a promise, and a plist<data>value parses toUint8Arrayrather thanBuffer, so.toString('base64')on one returns its bytes as digits.plistis ESM-only with norequirecondition, so the async signatures cannot be walked back.References https://outsystemsrd.atlassian.net/browse/RDMR-1452
Change Type