Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,651 changes: 1,503 additions & 2,148 deletions package-lock.json

Large diffs are not rendered by default.

21 changes: 3 additions & 18 deletions packages/configure/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"clean": "rimraf ./dist",
"build": "npm run clean && tsc",
"watch": "tsc -w",
"test": "jest"
"test": "vitest run"
},
"main": "dist/index.js",
"typings": "dist/index.d.ts",
Expand All @@ -28,7 +28,6 @@
"@prettier/plugin-xml": "^1.1.0",
"@trapezedev/project": "7.1.3",
"@types/fs-extra": "^9.0.13",
"@types/jest": "^27.0.2",
"@types/lodash": "^4.14.175",
"@types/plist": "^3.0.2",
"@types/prompts": "^2.0.14",
Expand All @@ -50,25 +49,11 @@
"type": "git",
"url": "git+https://github.com/ionic-team/capacitor-configure.git"
},
"jest": {
"preset": "ts-jest",
"testTimeout": 30000,
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"mjs"
],
"testMatch": [
"**/test/**/*.test.(ts|tsx|js|mjs)"
]
},
"devDependencies": {
"jest": "^27.2.5",
"rimraf": "^6.0.1",
"tempy": "^3.1.0",
"ts-jest": "^27.0.7",
"typescript": "^4.4.4"
"typescript": "^5.9.3",
"vitest": "^4.1.4"
},
"gitHead": "3b4f9f02d6a74247a5606fe1c0688c9a1ec3fd4b"
}
2 changes: 1 addition & 1 deletion packages/configure/test/ctx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ describe('context and capacitor project loading', () => {
const OLD_ENV = process.env;

beforeEach(() => {
jest.resetModules() // Most important - it clears the cache
vi.resetModules() // Most important - it clears the cache
process.env = { ...OLD_ENV }; // Make a copy
});

Expand Down
3 changes: 2 additions & 1 deletion packages/configure/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"noImplicitAny": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"target": "es2019"
"target": "es2019",
"types": ["node", "lodash", "prompts"]
},
"files": ["src/index.ts"],
"include": ["src/**/*.ts"]
Expand Down
9 changes: 9 additions & 0 deletions packages/configure/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
globals: true,
testTimeout: 30000,
include: ['test/**/*.test.{ts,tsx,js,mjs}'],
},
});
22 changes: 3 additions & 19 deletions packages/project/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"clean": "rimraf ./dist",
"build": "npm run clean && tsc",
"watch": "tsc -w",
"test": "jest"
"test": "vitest run"
},
"main": "dist/index.js",
"typings": "dist/index.d.ts",
Expand Down Expand Up @@ -47,32 +47,16 @@
"type": "git",
"url": "git+https://github.com/ionic-team/capacitor-configure.git"
},
"jest": {
"preset": "ts-jest",
"testTimeout": 30000,
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"mjs"
],
"testMatch": [
"**/test/**/*.test.(ts|tsx|js|mjs)"
]
},
"devDependencies": {
"@types/cross-spawn": "^6.0.2",
"@types/diff": "^5.0.2",
"@types/fs-extra": "^9.0.13",
"@types/ini": "^1.3.31",
"@types/jest": "^27.0.2",
"@types/lodash": "^4.14.175",
"@types/plist": "^3.0.2",
"@types/slice-ansi": "^5.0.0",
"jest": "^27.2.5",
"rimraf": "^6.0.1",
"ts-jest": "^27.0.7",
"typescript": "^4.4.4"
"typescript": "^5.9.3",
"vitest": "^4.1.4"
},
"gitHead": "3b4f9f02d6a74247a5606fe1c0688c9a1ec3fd4b"
}
4 changes: 2 additions & 2 deletions packages/project/src/android/gradle-file.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { dirname, join } from 'path';
import { mkdtempSync } from 'fs';
import os from 'os';
import { temporaryFile } from 'tempy';
import { pathExists, readFile, writeFile } from '@ionic/utils-fs';
import { spawnCommand } from '../util/subprocess';
import { indent } from '../util/text';
Expand Down Expand Up @@ -225,7 +225,7 @@ export class GradleFile extends VFSStorable {
if (!this.tempFile) {
// If the temp file doesn't exist yet, create it and write the current file source to it
const gradleContents = await this.getGradleSource();
this.tempFile = temporaryFile({ extension: 'gradle' });
this.tempFile = join(mkdtempSync(join(os.tmpdir(), 'trapeze-')), 'temp.gradle');
await writeFile(this.tempFile, gradleContents);
Comment on lines 225 to 229
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now creates a real temporary directory via mkdtempSync(...) but nothing cleans it up, so repeated runs can leave many trapeze-* dirs under the OS temp folder. Consider tracking the created temp directory and removing it when the GradleFile/VFS is finished (or register a process-exit cleanup), or use a temp helper that supports automatic cleanup.

Copilot uses AI. Check for mistakes.
} else if (vfsRef) {
// Otherwise if it already exists then write the current vfs data to it
Expand Down
2 changes: 1 addition & 1 deletion packages/project/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export type IosProjectName = string;

export type AndroidResDir = 'anim' | 'animator' | 'color' | 'drawable' | 'font' | 'interpolator' | 'layout' | 'menu' | 'mipmap' | 'navigation' | 'raw' | 'transition' | 'values' | 'xml' | string;

export const enum AndroidGradleInjectType {
export enum AndroidGradleInjectType {
Infer = 'infer',
Method = 'method',
Variable = 'variable'
Expand Down
12 changes: 12 additions & 0 deletions packages/project/src/util/xml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ export function parseXmlString(contents: string): Document {
return new xmldom.DOMParser().parseFromString(contents, 'text/xml') as unknown as Document;
}

/**
* Parses an XML fragment that may contain multiple root elements
* by wrapping it in a temporary root element.
* Accepts optional namespace attributes to support prefixed elements.
*/
export function parseXmlFragment(contents: string, namespaceAttrs?: string): NodeList {
const nsAttrs = namespaceAttrs ? ` ${namespaceAttrs}` : '';
const wrapped = `<__fragment__${nsAttrs}>${contents}</__fragment__>`;
const doc = new xmldom.DOMParser().parseFromString(wrapped, 'text/xml') as unknown as Document;
return doc.documentElement.childNodes;
}

export function serializeXml(doc: any) {
return new XMLSerializer().serializeToString(doc);
}
Expand Down
20 changes: 16 additions & 4 deletions packages/project/src/xml.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { formatXml, parseXml, parseXmlString, serializeXml, writeXml } from './util/xml';
import { formatXml, parseXml, parseXmlFragment, parseXmlString, serializeXml, writeXml } from './util/xml';
import xpath, { XPathSelect } from 'xpath';
import { xml2js, js2xml } from 'xml-js';
import { VFS, VFSFile, VFSStorable } from './vfs';
Expand Down Expand Up @@ -54,6 +54,19 @@ export class XmlFile extends VFSStorable {
return this.doc?.documentElement;
}

private getNamespaceAttrs(): string {
const rootNode = this.getDocumentElement();
if (!rootNode) return '';
const attrs: string[] = [];
for (const attr in rootNode.attributes) {
const attribute = rootNode.attributes[attr];
if (attribute.name?.startsWith('xmlns')) {
attrs.push(`${attribute.name}="${attribute.value}"`);
}
Comment on lines +61 to +65
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rootNode.attributes is a NamedNodeMap; iterating it with for...in can pick up non-attribute enumerable properties and is brittle across DOM implementations. Prefer iterating by index (for (let i = 0; i < rootNode.attributes.length; i++) and item(i)) when collecting xmlns* attributes.

Copilot uses AI. Check for mistakes.
}
return attrs.join(' ');
}

find(target: string): Element[] | null {
if (!this.doc) {
return null;
Expand Down Expand Up @@ -99,13 +112,12 @@ export class XmlFile extends VFSStorable {
}

const nodes = this.select?.(target, this.doc) as Element[];
const parsed = parseXmlString(fragment);
const docNodes = parsed.childNodes ?? [];
const docNodes = Array.from(parseXmlFragment(fragment, this.getNamespaceAttrs()));

Logger.v('xml', 'injectFragment', `at ${target}`);

nodes.forEach(n =>
Array.prototype.forEach.call(docNodes, d => n.appendChild(d)),
docNodes.forEach(d => n.appendChild(d)),
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

injectFragment appends the same Node instances into every matched target. Because appendChild moves a node, only the last matched node will end up containing the injected fragment (despite the docstring saying each node should get it). Clone/import the fragment nodes per target (e.g., deep-clone each node for each n) or re-parse the fragment for each target.

Suggested change
docNodes.forEach(d => n.appendChild(d)),
docNodes.forEach(d => n.appendChild(d.cloneNode(true))),

Copilot uses AI. Check for mistakes.
);

this.vfs.set(this.path, this);
Expand Down
4 changes: 2 additions & 2 deletions packages/project/test/project.android.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ try {
});

it('should copy resources file from url', async () => {
await project.android?.copyToResources('drawable', 'icon.png', 'https://via.placeholder.com/150C');
await project.android?.copyToResources('drawable', 'icon.png', 'https://picsum.photos/150');
const destContents = await project.android?.getResource('drawable', 'icon.png', null) as Buffer;

expect(destContents.length).toBeGreaterThan(0);
Expand All @@ -345,7 +345,7 @@ try {
});

it('should copy URL', async () => {
await project.android?.copyFile('https://via.placeholder.com/150C', 'placeholder.png');
await project.android?.copyFile('https://picsum.photos/150', 'placeholder.png');
const dest = join(dir, 'android', 'placeholder.png');
const destContents = await readFile(dest);
expect(destContents.length).toBeGreaterThan(0);
Expand Down
2 changes: 1 addition & 1 deletion packages/project/test/project.ios.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ describe('project - ios standard', () => {
});

it('should copy URL', async () => {
await project.ios?.copyFile('https://via.placeholder.com/150C', 'placeholder.png');
await project.ios?.copyFile('https://picsum.photos/150', 'placeholder.png');
const dest = join(dir, 'ios/App', 'placeholder.png');
const destContents = await readFile(dest);
expect(destContents.length).toBeGreaterThan(0);
Expand Down
2 changes: 1 addition & 1 deletion packages/project/test/xml-file.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('xml file', () => {
file.deleteNodes('//string');
const doc = file.getDocumentElement();

const serialized = serializeXml(doc).replaceAll(/\s+/g, '');
const serialized = serializeXml(doc).replace(/\s+/g, '');
expect(serialized).toBe(`
<resources></resources>
`.trim());
Expand Down
3 changes: 2 additions & 1 deletion packages/project/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"noImplicitAny": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"target": "es2019"
"target": "es2019",
"types": ["node", "cross-spawn", "diff", "fs-extra", "ini", "lodash", "plist"]
},
"files": ["src/index.ts"],
"include": ["/src/**/*.ts", "**/src/**/*.ts", "/src/**/*.d.ts"]
Expand Down
9 changes: 9 additions & 0 deletions packages/project/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
globals: true,
testTimeout: 30000,
include: ['test/**/*.test.{ts,tsx,js,mjs}'],
},
});
Loading