From f8870cb10db558300147099c339128ba91a7f9c0 Mon Sep 17 00:00:00 2001 From: Tom Riglar Date: Mon, 22 Jun 2026 15:01:17 +0100 Subject: [PATCH 1/2] chore: migrate package to ESM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert the CLI from CommonJS to ESM ("type": "module", tsconfig module/moduleResolution nodenext), which unblocks the three ESM-only deps held back in #28: chalk 5, plist 5, citty 0.2. - Add explicit .js extensions to all relative import/export/dynamic-import specifiers (required under NodeNext), preserving file-over-directory resolution (./types -> ./types.js, the file, not the barrel). - Rewrite CJS interop sites: chalk/StreamZip default imports; read package.json version via fs + import.meta.url instead of require(). - node-stream-zip is a single-function CJS export, so its three import sites must use a default import — a namespace import yields no .async. - Drop @types/plist (plist 5 bundles its own types). - Rename eslint.config.js -> eslint.config.cjs (flat config stays CJS). Validated: build, typecheck (src+test), lint (0 errors), 147 tests, and CLI/MCP smoke runs all green. Co-Authored-By: Claude Opus 4.8 (1M context) --- eslint.config.js => eslint.config.cjs | 0 package.json | 8 +-- pnpm-lock.yaml | 57 +++++++------------ src/commands/artifacts.ts | 10 ++-- src/commands/cloud.ts | 40 ++++++------- src/commands/list.ts | 12 ++-- src/commands/live.ts | 18 +++--- src/commands/login.ts | 10 ++-- src/commands/logout.ts | 10 ++-- src/commands/status.ts | 16 +++--- src/commands/switch-org.ts | 10 ++-- src/commands/upgrade.ts | 6 +- src/commands/upload.ts | 18 +++--- src/commands/whoami.ts | 6 +- src/config/flags/device.flags.ts | 2 +- src/constants.ts | 14 ++--- src/gateways/api-gateway.ts | 8 +-- src/gateways/cli-auth-gateway.ts | 2 +- src/gateways/realtime-gateway.ts | 2 +- src/gateways/supabase-gateway.ts | 2 +- src/index.ts | 26 ++++----- src/mcp/context.ts | 6 +- src/mcp/helpers.ts | 2 +- src/mcp/index.ts | 6 +- src/mcp/server.ts | 14 ++--- src/mcp/tools/download-artifacts.ts | 6 +- src/mcp/tools/get-status.ts | 6 +- src/mcp/tools/list-devices.ts | 6 +- src/mcp/tools/list-runs.ts | 6 +- src/mcp/tools/run-cloud-test.ts | 24 ++++---- src/methods.ts | 18 +++--- src/services/device-validation.service.ts | 4 +- src/services/execution-plan.service.ts | 2 +- src/services/flow-paths.ts | 2 +- src/services/metadata-extractor.service.ts | 4 +- src/services/moropo.service.ts | 4 +- src/services/report-download.service.ts | 4 +- src/services/results-polling.service.ts | 16 +++--- src/services/telemetry.service.ts | 4 +- src/services/test-submission.service.ts | 6 +- src/services/version.service.ts | 2 +- src/types/domain/auth.types.ts | 2 +- src/types/index.ts | 4 +- src/utils/auth.ts | 12 ++-- src/utils/cli.ts | 14 +++-- src/utils/compatibility.ts | 2 +- src/utils/config-store.ts | 2 +- src/utils/orgs.ts | 2 +- src/utils/styling.ts | 4 +- .../integration/artifacts.integration.test.ts | 2 +- test/integration/cloud.integration.test.ts | 2 +- test/integration/list.integration.test.ts | 2 +- test/integration/mcp.integration.test.ts | 2 +- test/integration/status.integration.test.ts | 2 +- test/integration/upload.integration.test.ts | 2 +- test/unit/auth.test.ts | 4 +- test/unit/ci.test.ts | 2 +- test/unit/flow-paths.test.ts | 2 +- test/unit/live-command.test.ts | 2 +- test/unit/metadata-extractor.service.test.ts | 2 +- test/unit/report-download.service.test.ts | 4 +- tsconfig.json | 4 +- 62 files changed, 240 insertions(+), 253 deletions(-) rename eslint.config.js => eslint.config.cjs (100%) diff --git a/eslint.config.js b/eslint.config.cjs similarity index 100% rename from eslint.config.js rename to eslint.config.cjs diff --git a/package.json b/package.json index 55fad97..0337999 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,12 @@ "@modelcontextprotocol/sdk": "^1.29.0", "@supabase/supabase-js": "^2.108.2", "bplist-parser": "^0.3.2", - "chalk": "4.1.2", - "citty": "^0.1.6", + "chalk": "^5.6.2", + "citty": "^0.2.2", "js-yaml": "^5.0.0", "node-apk": "^1.2.1", "node-stream-zip": "^1.15.0", - "plist": "^3.1.1", + "plist": "^5.0.0", "tar": "^7.5.16", "tus-js-client": "^4.3.1", "yazl": "^3.3.1", @@ -27,7 +27,6 @@ "@types/js-yaml": "^4.0.9", "@types/mocha": "^10.0.10", "@types/node": "^26.0.0", - "@types/plist": "^3.0.5", "@types/yazl": "^3.3.1", "chai": "^6.2.2", "eslint": "^10.5.0", @@ -53,6 +52,7 @@ "main": "dist/index.js", "name": "@devicecloud.dev/dcd", "private": false, + "type": "module", "publishConfig": { "access": "public" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b2e70d..5db7755 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,11 +48,11 @@ importers: specifier: ^0.3.2 version: 0.3.2 chalk: - specifier: 4.1.2 - version: 4.1.2 + specifier: ^5.6.2 + version: 5.6.2 citty: - specifier: ^0.1.6 - version: 0.1.6 + specifier: ^0.2.2 + version: 0.2.2 js-yaml: specifier: ^5.0.0 version: 5.0.0 @@ -63,8 +63,8 @@ importers: specifier: ^1.15.0 version: 1.15.0 plist: - specifier: ^3.1.1 - version: 3.1.1 + specifier: ^5.0.0 + version: 5.0.0 tar: specifier: ^7.5.16 version: 7.5.16 @@ -93,9 +93,6 @@ importers: '@types/node': specifier: ^26.0.0 version: 26.0.0 - '@types/plist': - specifier: ^3.0.5 - version: 3.0.5 '@types/yazl': specifier: ^3.3.1 version: 3.3.1 @@ -462,9 +459,6 @@ packages: '@types/node@26.0.0': resolution: {integrity: sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==} - '@types/plist@3.0.5': - resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} - '@types/yazl@3.3.1': resolution: {integrity: sha512-DIWfCKpsTp6hE5BDBHV3+fIL/bLUF9Bv13iDrWnMlmhQpH67buNvI291ZauQ1xcccxK3FqQ9honnXpq4R8NMuQ==} @@ -621,9 +615,6 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.10.38: resolution: {integrity: sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==} engines: {node: '>=6.0.0'} @@ -702,6 +693,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + change-case@5.4.4: resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} @@ -717,8 +712,8 @@ packages: resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} engines: {node: '>=8'} - citty@0.1.6: - resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + citty@0.2.2: + resolution: {integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==} cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} @@ -737,10 +732,6 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - content-disposition@1.1.0: resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} engines: {node: '>=18'} @@ -1683,9 +1674,9 @@ packages: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} - plist@3.1.1: - resolution: {integrity: sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==} - engines: {node: '>=10.4.0'} + plist@5.0.0: + resolution: {integrity: sha512-20N+g1DvMm/DFRbsvER7tT4wDryq0WunK7VMkDaiJcKNapAnUMkTsAnacFYf8n420F4Hf6/hefgmJRkMb1M0fg==} + engines: {node: '>=18'} pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} @@ -2400,11 +2391,6 @@ snapshots: dependencies: undici-types: 8.3.0 - '@types/plist@3.0.5': - dependencies: - '@types/node': 26.0.0 - xmlbuilder: 15.1.1 - '@types/yazl@3.3.1': dependencies: '@types/node': 26.0.0 @@ -2605,8 +2591,6 @@ snapshots: balanced-match@4.0.4: {} - base64-js@1.5.1: {} - baseline-browser-mapping@2.10.38: {} big-integer@1.6.52: {} @@ -2688,6 +2672,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.6.2: {} + change-case@5.4.4: {} chokidar@4.0.3: @@ -2698,9 +2684,7 @@ snapshots: ci-info@4.4.0: {} - citty@0.1.6: - dependencies: - consola: 3.4.2 + citty@0.2.2: {} cliui@8.0.1: dependencies: @@ -2721,8 +2705,6 @@ snapshots: concat-map@0.0.1: {} - consola@3.4.2: {} - content-disposition@1.1.0: {} content-type@1.0.5: {} @@ -3784,10 +3766,9 @@ snapshots: pkce-challenge@5.0.1: {} - plist@3.1.1: + plist@5.0.0: dependencies: '@xmldom/xmldom': 0.9.10 - base64-js: 1.5.1 xmlbuilder: 15.1.1 pluralize@8.0.0: {} diff --git a/src/commands/artifacts.ts b/src/commands/artifacts.ts index 202b1f9..aa5227a 100644 --- a/src/commands/artifacts.ts +++ b/src/commands/artifacts.ts @@ -1,10 +1,10 @@ import { defineCommand } from 'citty'; -import { apiFlags } from '../config/flags/api.flags'; -import { ReportDownloadService } from '../services/report-download.service'; -import { resolveAuth } from '../utils/auth'; -import { CliError, logger, validateEnum } from '../utils/cli'; -import { resolveApiUrl } from '../utils/config-store'; +import { apiFlags } from '../config/flags/api.flags.js'; +import { ReportDownloadService } from '../services/report-download.service.js'; +import { resolveAuth } from '../utils/auth.js'; +import { CliError, logger, validateEnum } from '../utils/cli.js'; +import { resolveApiUrl } from '../utils/config-store.js'; const DOWNLOAD_OPTIONS = ['ALL', 'FAILED'] as const; const REPORT_OPTIONS = ['allure', 'html', 'html-detailed', 'junit'] as const; diff --git a/src/commands/cloud.ts b/src/commands/cloud.ts index c3f452c..4370968 100644 --- a/src/commands/cloud.ts +++ b/src/commands/cloud.ts @@ -2,32 +2,32 @@ import { defineCommand } from 'citty'; import * as path from 'node:path'; -import { flags as allFlags } from '../constants'; -import { ApiGateway } from '../gateways/api-gateway'; -import { uploadBinary, verifyAppZip, writeJSONFile } from '../methods'; -import { DeviceValidationService } from '../services/device-validation.service'; -import { plan } from '../services/execution-plan.service'; +import { flags as allFlags } from '../constants.js'; +import { ApiGateway } from '../gateways/api-gateway.js'; +import { uploadBinary, verifyAppZip, writeJSONFile } from '../methods.js'; +import { DeviceValidationService } from '../services/device-validation.service.js'; +import { plan } from '../services/execution-plan.service.js'; import { buildTestMetadataMap, computeCommonRoot, -} from '../services/flow-paths'; -import { MoropoService } from '../services/moropo.service'; -import { ReportDownloadService } from '../services/report-download.service'; +} from '../services/flow-paths.js'; +import { MoropoService } from '../services/moropo.service.js'; +import { ReportDownloadService } from '../services/report-download.service.js'; import { ResultsPollingService, RunFailedError, -} from '../services/results-polling.service'; -import { telemetry } from '../services/telemetry.service'; -import { TestSubmissionService } from '../services/test-submission.service'; -import { VersionService } from '../services/version.service'; +} from '../services/results-polling.service.js'; +import { telemetry } from '../services/telemetry.service.js'; +import { TestSubmissionService } from '../services/test-submission.service.js'; +import { VersionService } from '../services/version.service.js'; import { EAndroidApiLevels, EAndroidDevices, EiOSDevices, EiOSVersions, -} from '../types/domain/device.types'; -import { resolveAuth } from '../utils/auth'; -import { isCI } from '../utils/ci'; +} from '../types/domain/device.types.js'; +import { resolveAuth } from '../utils/auth.js'; +import { isCI } from '../utils/ci.js'; import { CliError, coerceArray, @@ -36,13 +36,13 @@ import { logger, parseIntFlag, validateEnum, -} from '../utils/cli'; +} from '../utils/cli.js'; import { CompatibilityData, fetchCompatibilityData, -} from '../utils/compatibility'; -import { resolveApiUrl } from '../utils/config-store'; -import { downloadExpoUrl, extractTarGz, findAppBundle, isUrl } from '../utils/expo'; +} from '../utils/compatibility.js'; +import { resolveApiUrl } from '../utils/config-store.js'; +import { downloadExpoUrl, extractTarGz, findAppBundle, isUrl } from '../utils/expo.js'; import { box, colors, @@ -53,7 +53,7 @@ import { listItem, sectionHeader, symbols, -} from '../utils/styling'; +} from '../utils/styling.js'; // Suppress punycode deprecation warning (caused by whatwg, supabase dependency). // Every other warning must still reach the user — removeAllListeners drops diff --git a/src/commands/list.ts b/src/commands/list.ts index 688bcb5..cc072a8 100644 --- a/src/commands/list.ts +++ b/src/commands/list.ts @@ -1,11 +1,11 @@ import { defineCommand } from 'citty'; -import { apiFlags } from '../config/flags/api.flags'; -import { ApiGateway } from '../gateways/api-gateway'; -import { resolveAuth } from '../utils/auth'; -import { CliError, logger, parseIntFlag } from '../utils/cli'; -import { resolveApiUrl } from '../utils/config-store'; -import { colors, formatId, formatUrl, sectionHeader, symbols } from '../utils/styling'; +import { apiFlags } from '../config/flags/api.flags.js'; +import { ApiGateway } from '../gateways/api-gateway.js'; +import { resolveAuth } from '../utils/auth.js'; +import { CliError, logger, parseIntFlag } from '../utils/cli.js'; +import { resolveApiUrl } from '../utils/config-store.js'; +import { colors, formatId, formatUrl, sectionHeader, symbols } from '../utils/styling.js'; type UploadListItem = { consoleUrl: string; diff --git a/src/commands/live.ts b/src/commands/live.ts index 4e3c382..6aaa996 100644 --- a/src/commands/live.ts +++ b/src/commands/live.ts @@ -1,15 +1,15 @@ import { defineCommand } from 'citty'; import { readFileSync, writeFileSync } from 'node:fs'; -import { resolveFrontendUrl } from '../config/environments'; -import { apiFlags } from '../config/flags/api.flags'; -import { ApiGateway } from '../gateways/api-gateway'; -import type { AuthContext } from '../types/domain/auth.types'; -import type { LiveSession } from '../types/domain/live.types'; -import { resolveAuth } from '../utils/auth'; -import { CliError, logger, validateEnum } from '../utils/cli'; -import { resolveApiUrl } from '../utils/config-store'; -import { colors, sectionHeader, symbols } from '../utils/styling'; +import { resolveFrontendUrl } from '../config/environments.js'; +import { apiFlags } from '../config/flags/api.flags.js'; +import { ApiGateway } from '../gateways/api-gateway.js'; +import type { AuthContext } from '../types/domain/auth.types.js'; +import type { LiveSession } from '../types/domain/live.types.js'; +import { resolveAuth } from '../utils/auth.js'; +import { CliError, logger, validateEnum } from '../utils/cli.js'; +import { resolveApiUrl } from '../utils/config-store.js'; +import { colors, sectionHeader, symbols } from '../utils/styling.js'; const PLATFORM_OPTIONS = ['android', 'ios'] as const; type Platform = (typeof PLATFORM_OPTIONS)[number]; diff --git a/src/commands/login.ts b/src/commands/login.ts index cd341a7..81e45e3 100644 --- a/src/commands/login.ts +++ b/src/commands/login.ts @@ -30,11 +30,11 @@ import { defineCommand } from 'citty'; import { spawn } from 'node:child_process'; import { createHash, randomBytes } from 'node:crypto'; -import { ENVIRONMENTS, inferEnvFromApiUrl, resolveFrontendUrl } from '../config/environments'; -import { CliError, logger } from '../utils/cli'; -import { readConfig, writeConfig } from '../utils/config-store'; -import { fetchOrgs, pickOrg } from '../utils/orgs'; -import { colors, sectionHeader, symbols } from '../utils/styling'; +import { ENVIRONMENTS, inferEnvFromApiUrl, resolveFrontendUrl } from '../config/environments.js'; +import { CliError, logger } from '../utils/cli.js'; +import { readConfig, writeConfig } from '../utils/config-store.js'; +import { fetchOrgs, pickOrg } from '../utils/orgs.js'; +import { colors, sectionHeader, symbols } from '../utils/styling.js'; interface ClaimedSession { access_token: string; diff --git a/src/commands/logout.ts b/src/commands/logout.ts index c1dfc47..30f6e06 100644 --- a/src/commands/logout.ts +++ b/src/commands/logout.ts @@ -4,11 +4,11 @@ */ import { defineCommand } from 'citty'; -import { ENVIRONMENTS } from '../config/environments'; -import { CliAuthGateway } from '../gateways/cli-auth-gateway'; -import { logger } from '../utils/cli'; -import { clearConfig, getConfigPath, readConfig } from '../utils/config-store'; -import { colors, symbols } from '../utils/styling'; +import { ENVIRONMENTS } from '../config/environments.js'; +import { CliAuthGateway } from '../gateways/cli-auth-gateway.js'; +import { logger } from '../utils/cli.js'; +import { clearConfig, getConfigPath, readConfig } from '../utils/config-store.js'; +import { colors, symbols } from '../utils/styling.js'; export const logoutCommand = defineCommand({ meta: { diff --git a/src/commands/status.ts b/src/commands/status.ts index 6dc2b1f..cb3e1ea 100644 --- a/src/commands/status.ts +++ b/src/commands/status.ts @@ -1,16 +1,16 @@ import { defineCommand } from 'citty'; -import { apiFlags } from '../config/flags/api.flags'; -import { ApiGateway } from '../gateways/api-gateway'; -import { formatDurationSeconds } from '../methods'; -import { resolveAuth } from '../utils/auth'; -import { CliError, logger } from '../utils/cli'; -import { resolveApiUrl } from '../utils/config-store'; +import { apiFlags } from '../config/flags/api.flags.js'; +import { ApiGateway } from '../gateways/api-gateway.js'; +import { formatDurationSeconds } from '../methods.js'; +import { resolveAuth } from '../utils/auth.js'; +import { CliError, logger } from '../utils/cli.js'; +import { resolveApiUrl } from '../utils/config-store.js'; import { ConnectivityCheckResult, checkInternetConnectivity, -} from '../utils/connectivity'; -import { colors, formatId, formatStatus, formatUrl, sectionHeader } from '../utils/styling'; +} from '../utils/connectivity.js'; +import { colors, formatId, formatStatus, formatUrl, sectionHeader } from '../utils/styling.js'; type StatusKind = 'CANCELLED' | 'FAILED' | 'PASSED' | 'PENDING' | 'QUEUED' | 'RUNNING'; diff --git a/src/commands/switch-org.ts b/src/commands/switch-org.ts index ba51987..cffc1d6 100644 --- a/src/commands/switch-org.ts +++ b/src/commands/switch-org.ts @@ -7,11 +7,11 @@ */ import { defineCommand } from 'citty'; -import { resolveAuth } from '../utils/auth'; -import { CliError, logger } from '../utils/cli'; -import { readConfig, resolveApiUrl, writeConfig } from '../utils/config-store'; -import { fetchOrgs, pickOrg, OrgListItem } from '../utils/orgs'; -import { colors, symbols } from '../utils/styling'; +import { resolveAuth } from '../utils/auth.js'; +import { CliError, logger } from '../utils/cli.js'; +import { readConfig, resolveApiUrl, writeConfig } from '../utils/config-store.js'; +import { fetchOrgs, pickOrg, OrgListItem } from '../utils/orgs.js'; +import { colors, symbols } from '../utils/styling.js'; export const switchOrgCommand = defineCommand({ meta: { diff --git a/src/commands/upgrade.ts b/src/commands/upgrade.ts index a499710..3086bf1 100644 --- a/src/commands/upgrade.ts +++ b/src/commands/upgrade.ts @@ -20,14 +20,14 @@ import { pipeline } from 'node:stream/promises'; import { defineCommand } from 'citty'; -import { VersionService } from '../services/version.service'; +import { VersionService } from '../services/version.service.js'; import { CliError, getCliVersion, getInstallMethod, logger, -} from '../utils/cli'; -import { colors, symbols } from '../utils/styling'; +} from '../utils/cli.js'; +import { colors, symbols } from '../utils/styling.js'; const DEFAULT_DOWNLOAD_BASE = 'https://get.devicecloud.dev'; diff --git a/src/commands/upload.ts b/src/commands/upload.ts index 5b66f65..764abad 100644 --- a/src/commands/upload.ts +++ b/src/commands/upload.ts @@ -1,15 +1,15 @@ import { defineCommand } from 'citty'; import { rm } from 'node:fs/promises'; -import { apiFlags } from '../config/flags/api.flags'; -import { binaryFlags } from '../config/flags/binary.flags'; -import { outputFlags } from '../config/flags/output.flags'; -import { uploadBinary, verifyAppZip } from '../methods'; -import { resolveAuth } from '../utils/auth'; -import { CliError, logger } from '../utils/cli'; -import { resolveApiUrl } from '../utils/config-store'; -import { downloadExpoUrl, extractTarGz, findAppBundle, isUrl } from '../utils/expo'; -import { colors, formatId, sectionHeader, symbols } from '../utils/styling'; +import { apiFlags } from '../config/flags/api.flags.js'; +import { binaryFlags } from '../config/flags/binary.flags.js'; +import { outputFlags } from '../config/flags/output.flags.js'; +import { uploadBinary, verifyAppZip } from '../methods.js'; +import { resolveAuth } from '../utils/auth.js'; +import { CliError, logger } from '../utils/cli.js'; +import { resolveApiUrl } from '../utils/config-store.js'; +import { downloadExpoUrl, extractTarGz, findAppBundle, isUrl } from '../utils/expo.js'; +import { colors, formatId, sectionHeader, symbols } from '../utils/styling.js'; export const uploadCommand = defineCommand({ meta: { diff --git a/src/commands/whoami.ts b/src/commands/whoami.ts index 306fc18..4c9c90b 100644 --- a/src/commands/whoami.ts +++ b/src/commands/whoami.ts @@ -3,9 +3,9 @@ */ import { defineCommand } from 'citty'; -import { logger } from '../utils/cli'; -import { readConfig } from '../utils/config-store'; -import { colors, sectionHeader, symbols } from '../utils/styling'; +import { logger } from '../utils/cli.js'; +import { readConfig } from '../utils/config-store.js'; +import { colors, sectionHeader, symbols } from '../utils/styling.js'; export const whoamiCommand = defineCommand({ meta: { diff --git a/src/config/flags/device.flags.ts b/src/config/flags/device.flags.ts index 50a635e..37135d4 100644 --- a/src/config/flags/device.flags.ts +++ b/src/config/flags/device.flags.ts @@ -5,7 +5,7 @@ import { EAndroidDevices, EiOSDevices, EiOSVersions, -} from '../../types/domain/device.types'; +} from '../../types/domain/device.types.js'; const androidApiLevels = Object.values(EAndroidApiLevels).join(', '); const androidDevices = Object.values(EAndroidDevices).join(', '); diff --git a/src/constants.ts b/src/constants.ts index 5b507fa..7cdf1e1 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -5,13 +5,13 @@ import type { ArgsDef } from 'citty'; -import { apiFlags } from './config/flags/api.flags'; -import { binaryFlags } from './config/flags/binary.flags'; -import { deviceFlags } from './config/flags/device.flags'; -import { environmentFlags } from './config/flags/environment.flags'; -import { executionFlags } from './config/flags/execution.flags'; -import { githubFlags } from './config/flags/github.flags'; -import { outputFlags } from './config/flags/output.flags'; +import { apiFlags } from './config/flags/api.flags.js'; +import { binaryFlags } from './config/flags/binary.flags.js'; +import { deviceFlags } from './config/flags/device.flags.js'; +import { environmentFlags } from './config/flags/environment.flags.js'; +import { executionFlags } from './config/flags/execution.flags.js'; +import { githubFlags } from './config/flags/github.flags.js'; +import { outputFlags } from './config/flags/output.flags.js'; /** * All flag definitions consolidated from domain-specific flag modules. diff --git a/src/gateways/api-gateway.ts b/src/gateways/api-gateway.ts index 854ec81..7d5ecbb 100644 --- a/src/gateways/api-gateway.ts +++ b/src/gateways/api-gateway.ts @@ -4,15 +4,15 @@ import * as path from 'node:path'; import { Readable } from 'node:stream'; import { pipeline } from 'node:stream/promises'; -import { TAppMetadata } from '../types'; -import type { AuthContext } from '../types/domain/auth.types'; +import { TAppMetadata } from '../types.js'; +import type { AuthContext } from '../types/domain/auth.types.js'; import type { LiveCommandStatus, LiveExecResult, LiveSession, LiveSessionSummary, -} from '../types/domain/live.types'; -import { paths } from '../types/generated/schema.types'; +} from '../types/domain/live.types.js'; +import { paths } from '../types/generated/schema.types.js'; /** * Error thrown for non-OK API responses, carrying the HTTP status so callers diff --git a/src/gateways/cli-auth-gateway.ts b/src/gateways/cli-auth-gateway.ts index 7c13020..247609a 100644 --- a/src/gateways/cli-auth-gateway.ts +++ b/src/gateways/cli-auth-gateway.ts @@ -7,7 +7,7 @@ */ import { createClient } from '@supabase/supabase-js'; -import type { StoredSession } from '../utils/config-store'; +import type { StoredSession } from '../utils/config-store.js'; export interface RefreshedSession { access_token: string; diff --git a/src/gateways/realtime-gateway.ts b/src/gateways/realtime-gateway.ts index ddc82df..2ffab6e 100644 --- a/src/gateways/realtime-gateway.ts +++ b/src/gateways/realtime-gateway.ts @@ -19,7 +19,7 @@ import { type SupabaseClient, } from '@supabase/supabase-js'; -import { ENVIRONMENTS, type DcdEnvName } from '../config/environments'; +import { ENVIRONMENTS, type DcdEnvName } from '../config/environments.js'; export interface RealtimeResultsSubscription { /** Tear down the channel and close the socket. Best-effort, never throws. */ diff --git a/src/gateways/supabase-gateway.ts b/src/gateways/supabase-gateway.ts index 1a5e3d4..67d0478 100644 --- a/src/gateways/supabase-gateway.ts +++ b/src/gateways/supabase-gateway.ts @@ -3,7 +3,7 @@ import { createReadStream } from 'node:fs'; import { createClient } from '@supabase/supabase-js'; import * as tus from 'tus-js-client'; -import { ENVIRONMENTS, type DcdEnvName } from '../config/environments'; +import { ENVIRONMENTS, type DcdEnvName } from '../config/environments.js'; /** Disk-backed upload descriptor — see UploadSource in src/methods.ts. */ export interface ResumableUploadSource { diff --git a/src/index.ts b/src/index.ts index 4f05d42..b1a6df6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,19 +2,19 @@ import type { CommandDef, SubCommandsDef } from 'citty'; import { defineCommand, runCommand, showUsage } from 'citty'; -import { artifactsCommand } from './commands/artifacts'; -import { cloudCommand } from './commands/cloud'; -import { listCommand } from './commands/list'; -import { liveCommand } from './commands/live'; -import { loginCommand } from './commands/login'; -import { logoutCommand } from './commands/logout'; -import { statusCommand } from './commands/status'; -import { switchOrgCommand } from './commands/switch-org'; -import { upgradeCommand } from './commands/upgrade'; -import { uploadCommand } from './commands/upload'; -import { whoamiCommand } from './commands/whoami'; -import { telemetry } from './services/telemetry.service'; -import { CliError, getCliVersion, logger } from './utils/cli'; +import { artifactsCommand } from './commands/artifacts.js'; +import { cloudCommand } from './commands/cloud.js'; +import { listCommand } from './commands/list.js'; +import { liveCommand } from './commands/live.js'; +import { loginCommand } from './commands/login.js'; +import { logoutCommand } from './commands/logout.js'; +import { statusCommand } from './commands/status.js'; +import { switchOrgCommand } from './commands/switch-org.js'; +import { upgradeCommand } from './commands/upgrade.js'; +import { uploadCommand } from './commands/upload.js'; +import { whoamiCommand } from './commands/whoami.js'; +import { telemetry } from './services/telemetry.service.js'; +import { CliError, getCliVersion, logger } from './utils/cli.js'; const main = defineCommand({ meta: { diff --git a/src/mcp/context.ts b/src/mcp/context.ts index 49c0068..735a2cf 100644 --- a/src/mcp/context.ts +++ b/src/mcp/context.ts @@ -7,9 +7,9 @@ * with `logStderr` (never the `utils/cli` `logger`, which writes to stdout and * can `process.exit`). */ -import type { AuthContext } from '../types/domain/auth.types'; -import { resolveAuth } from '../utils/auth'; -import { resolveApiUrl } from '../utils/config-store'; +import type { AuthContext } from '../types/domain/auth.types.js'; +import { resolveAuth } from '../utils/auth.js'; +import { resolveApiUrl } from '../utils/config-store.js'; /** Write a line to stderr. Safe under stdio transport; stdout is reserved. */ export function logStderr(message: string): void { diff --git a/src/mcp/helpers.ts b/src/mcp/helpers.ts index 0a5176f..f4874bd 100644 --- a/src/mcp/helpers.ts +++ b/src/mcp/helpers.ts @@ -5,7 +5,7 @@ */ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; -import { telemetry } from '../services/telemetry.service'; +import { telemetry } from '../services/telemetry.service.js'; /** A tool result whose text payload is pretty-printed JSON. */ export function jsonResult(data: unknown): CallToolResult { diff --git a/src/mcp/index.ts b/src/mcp/index.ts index 0d59575..eb40881 100644 --- a/src/mcp/index.ts +++ b/src/mcp/index.ts @@ -9,10 +9,10 @@ */ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { telemetry } from '../services/telemetry.service'; +import { telemetry } from '../services/telemetry.service.js'; -import { isReadOnly, logStderr } from './context'; -import { createServer } from './server'; +import { isReadOnly, logStderr } from './context.js'; +import { createServer } from './server.js'; async function main(): Promise { telemetry.setCommand('mcp'); diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 21a42bb..662bac7 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -1,13 +1,13 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { getCliVersion } from '../utils/cli'; +import { getCliVersion } from '../utils/cli.js'; -import { isReadOnly } from './context'; -import { registerDownloadArtifacts } from './tools/download-artifacts'; -import { registerGetStatus } from './tools/get-status'; -import { registerListDevices } from './tools/list-devices'; -import { registerListRuns } from './tools/list-runs'; -import { registerRunCloudTest } from './tools/run-cloud-test'; +import { isReadOnly } from './context.js'; +import { registerDownloadArtifacts } from './tools/download-artifacts.js'; +import { registerGetStatus } from './tools/get-status.js'; +import { registerListDevices } from './tools/list-devices.js'; +import { registerListRuns } from './tools/list-runs.js'; +import { registerRunCloudTest } from './tools/run-cloud-test.js'; /** * Build the devicecloud.dev MCP server with its tool set registered. Read-only diff --git a/src/mcp/tools/download-artifacts.ts b/src/mcp/tools/download-artifacts.ts index 476fef7..9e28175 100644 --- a/src/mcp/tools/download-artifacts.ts +++ b/src/mcp/tools/download-artifacts.ts @@ -3,9 +3,9 @@ import * as path from 'node:path'; import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; -import { ReportDownloadService } from '../../services/report-download.service'; -import { getContext, logStderr } from '../context'; -import { jsonResult, runTool } from '../helpers'; +import { ReportDownloadService } from '../../services/report-download.service.js'; +import { getContext, logStderr } from '../context.js'; +import { jsonResult, runTool } from '../helpers.js'; /** * Download a completed run's artifacts (zip) and/or a formatted report to local diff --git a/src/mcp/tools/get-status.ts b/src/mcp/tools/get-status.ts index 8df61d9..f0e84cb 100644 --- a/src/mcp/tools/get-status.ts +++ b/src/mcp/tools/get-status.ts @@ -1,9 +1,9 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; -import { ApiGateway } from '../../gateways/api-gateway'; -import { getContext } from '../context'; -import { jsonResult, runTool } from '../helpers'; +import { ApiGateway } from '../../gateways/api-gateway.js'; +import { getContext } from '../context.js'; +import { jsonResult, runTool } from '../helpers.js'; /** * Status of a single upload by id or name. This is the polling primitive: after diff --git a/src/mcp/tools/list-devices.ts b/src/mcp/tools/list-devices.ts index f0b1236..dc746d1 100644 --- a/src/mcp/tools/list-devices.ts +++ b/src/mcp/tools/list-devices.ts @@ -1,8 +1,8 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { fetchCompatibilityData } from '../../utils/compatibility'; -import { getContext } from '../context'; -import { jsonResult, runTool } from '../helpers'; +import { fetchCompatibilityData } from '../../utils/compatibility.js'; +import { getContext } from '../context.js'; +import { jsonResult, runTool } from '../helpers.js'; /** * Discovery tool: the matrix of devices, OS versions, and Maestro versions the diff --git a/src/mcp/tools/list-runs.ts b/src/mcp/tools/list-runs.ts index 7c4f692..8b24413 100644 --- a/src/mcp/tools/list-runs.ts +++ b/src/mcp/tools/list-runs.ts @@ -1,9 +1,9 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; -import { ApiGateway } from '../../gateways/api-gateway'; -import { getContext } from '../context'; -import { jsonResult, runTool } from '../helpers'; +import { ApiGateway } from '../../gateways/api-gateway.js'; +import { getContext } from '../context.js'; +import { jsonResult, runTool } from '../helpers.js'; /** List recent flow uploads for the org, with optional filters + pagination. */ export function registerListRuns(server: McpServer): void { diff --git a/src/mcp/tools/run-cloud-test.ts b/src/mcp/tools/run-cloud-test.ts index 021b269..d05628a 100644 --- a/src/mcp/tools/run-cloud-test.ts +++ b/src/mcp/tools/run-cloud-test.ts @@ -3,18 +3,18 @@ import * as path from 'node:path'; import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; -import { ApiGateway } from '../../gateways/api-gateway'; -import { plan } from '../../services/execution-plan.service'; -import { computeCommonRoot, buildTestMetadataMap } from '../../services/flow-paths'; -import { DeviceValidationService } from '../../services/device-validation.service'; -import { TestSubmissionService } from '../../services/test-submission.service'; -import { VersionService } from '../../services/version.service'; -import { uploadBinary, verifyAppZip } from '../../methods'; -import { getCliVersion } from '../../utils/cli'; -import { fetchCompatibilityData } from '../../utils/compatibility'; -import { getConsoleUrl } from '../../utils/styling'; -import { getContext, logStderr } from '../context'; -import { jsonResult, runTool } from '../helpers'; +import { ApiGateway } from '../../gateways/api-gateway.js'; +import { plan } from '../../services/execution-plan.service.js'; +import { computeCommonRoot, buildTestMetadataMap } from '../../services/flow-paths.js'; +import { DeviceValidationService } from '../../services/device-validation.service.js'; +import { TestSubmissionService } from '../../services/test-submission.service.js'; +import { VersionService } from '../../services/version.service.js'; +import { uploadBinary, verifyAppZip } from '../../methods.js'; +import { getCliVersion } from '../../utils/cli.js'; +import { fetchCompatibilityData } from '../../utils/compatibility.js'; +import { getConsoleUrl } from '../../utils/styling.js'; +import { getContext, logStderr } from '../context.js'; +import { jsonResult, runTool } from '../helpers.js'; const SUPPORTED_APP_EXTENSIONS = ['.apk', '.app', '.zip']; const POLL_INTERVAL_MS = 10_000; diff --git a/src/methods.ts b/src/methods.ts index ef385fa..15c125c 100644 --- a/src/methods.ts +++ b/src/methods.ts @@ -1,4 +1,4 @@ -import { ux } from './utils/progress'; +import { ux } from './utils/progress.js'; import { createHash } from 'node:crypto'; import { createReadStream, @@ -11,16 +11,16 @@ import { access, mkdtemp, readFile, rm, stat } from 'node:fs/promises'; import * as os from 'node:os'; import * as path from 'node:path'; import { pipeline } from 'node:stream/promises'; -import * as StreamZip from 'node-stream-zip'; +import StreamZip from 'node-stream-zip'; import * as yazl from 'yazl'; -import { inferEnvFromApiUrl } from './config/environments'; -import { ApiError, ApiGateway } from './gateways/api-gateway'; -import { SupabaseGateway } from './gateways/supabase-gateway'; -import { MetadataExtractorService } from './services/metadata-extractor.service'; -import { TAppMetadata } from './types'; -import type { AuthContext } from './types/domain/auth.types'; -import { colors, formatId } from './utils/styling'; +import { inferEnvFromApiUrl } from './config/environments.js'; +import { ApiError, ApiGateway } from './gateways/api-gateway.js'; +import { SupabaseGateway } from './gateways/supabase-gateway.js'; +import { MetadataExtractorService } from './services/metadata-extractor.service.js'; +import { TAppMetadata } from './types.js'; +import type { AuthContext } from './types/domain/auth.types.js'; +import { colors, formatId } from './utils/styling.js'; const mimeTypeLookupByExtension: Record = { apk: 'application/vnd.android.package-archive', diff --git a/src/services/device-validation.service.ts b/src/services/device-validation.service.ts index 4613e5e..0716d80 100644 --- a/src/services/device-validation.service.ts +++ b/src/services/device-validation.service.ts @@ -1,5 +1,5 @@ -import { EAndroidDevices, EiOSDevices } from '../types/domain/device.types'; -import { CompatibilityData } from '../utils/compatibility'; +import { EAndroidDevices, EiOSDevices } from '../types/domain/device.types.js'; +import { CompatibilityData } from '../utils/compatibility.js'; export interface DeviceValidationOptions { debug?: boolean; diff --git a/src/services/execution-plan.service.ts b/src/services/execution-plan.service.ts index 903a670..777f72b 100644 --- a/src/services/execution-plan.service.ts +++ b/src/services/execution-plan.service.ts @@ -8,7 +8,7 @@ import { readDirectory, readTestYamlFileAsJson, readYamlFileAsJson, -} from './execution-plan.utils'; +} from './execution-plan.utils.js'; /** Email notification configuration */ interface INotificationsConfig { diff --git a/src/services/flow-paths.ts b/src/services/flow-paths.ts index 0e14f5d..195bdd3 100644 --- a/src/services/flow-paths.ts +++ b/src/services/flow-paths.ts @@ -7,7 +7,7 @@ */ import * as path from 'node:path'; -import { toPortableRelativePath } from '../utils/paths'; +import { toPortableRelativePath } from '../utils/paths.js'; /** * Longest whole-segment directory prefix shared by every flow + referenced diff --git a/src/services/metadata-extractor.service.ts b/src/services/metadata-extractor.service.ts index 32638e4..79a7d84 100644 --- a/src/services/metadata-extractor.service.ts +++ b/src/services/metadata-extractor.service.ts @@ -2,7 +2,7 @@ import { parseBuffer } from 'bplist-parser'; import { Apk } from 'node-apk'; import { readFile, rm } from 'node:fs/promises'; import * as path from 'node:path'; -import StreamZip = require('node-stream-zip'); +import StreamZip from 'node-stream-zip'; import { parse } from 'plist'; export interface TAppMetadata { @@ -136,7 +136,7 @@ export class ExpoTarGzMetadataExtractor implements IMetadataExtractor { } async extract(filePath: string): Promise { - const { extractTarGz, findAppBundle } = await import('../utils/expo'); + const { extractTarGz, findAppBundle } = await import('../utils/expo.js'); const extractDir = await extractTarGz(filePath, false); try { const appPath = await findAppBundle(extractDir); diff --git a/src/services/moropo.service.ts b/src/services/moropo.service.ts index 0495dee..57873d3 100644 --- a/src/services/moropo.service.ts +++ b/src/services/moropo.service.ts @@ -1,11 +1,11 @@ -import { ux } from '../utils/progress'; +import { ux } from '../utils/progress.js'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import { Readable } from 'node:stream'; import { pipeline } from 'node:stream/promises'; -import StreamZip = require('node-stream-zip'); +import StreamZip from 'node-stream-zip'; export interface MoropoDownloadOptions { apiKey: string; diff --git a/src/services/report-download.service.ts b/src/services/report-download.service.ts index 198722f..80437e5 100644 --- a/src/services/report-download.service.ts +++ b/src/services/report-download.service.ts @@ -1,7 +1,7 @@ import * as path from 'node:path'; -import { ApiGateway } from '../gateways/api-gateway'; -import type { AuthContext } from '../types/domain/auth.types'; +import { ApiGateway } from '../gateways/api-gateway.js'; +import type { AuthContext } from '../types/domain/auth.types.js'; export interface DownloadOptions { auth: AuthContext; diff --git a/src/services/results-polling.service.ts b/src/services/results-polling.service.ts index e54d3d1..c19bb17 100644 --- a/src/services/results-polling.service.ts +++ b/src/services/results-polling.service.ts @@ -1,16 +1,16 @@ import * as path from 'node:path'; -import { ApiGateway } from '../gateways/api-gateway'; +import { ApiGateway } from '../gateways/api-gateway.js'; import { RealtimeResultsGateway, type RealtimeResultsSubscription, -} from '../gateways/realtime-gateway'; -import { formatDurationSeconds } from '../methods'; -import type { AuthContext } from '../types/domain/auth.types'; -import { paths } from '../types/generated/schema.types'; -import { checkInternetConnectivity } from '../utils/connectivity'; -import { ux } from '../utils/progress'; -import { colors, formatTestSummary, table } from '../utils/styling'; +} from '../gateways/realtime-gateway.js'; +import { formatDurationSeconds } from '../methods.js'; +import type { AuthContext } from '../types/domain/auth.types.js'; +import { paths } from '../types/generated/schema.types.js'; +import { checkInternetConnectivity } from '../utils/connectivity.js'; +import { ux } from '../utils/progress.js'; +import { colors, formatTestSummary, table } from '../utils/styling.js'; type TestResult = NonNullable< paths['/results/{uploadId}']['get']['responses']['200']['content']['application/json']['results'] diff --git a/src/services/telemetry.service.ts b/src/services/telemetry.service.ts index 593cd3a..a65356b 100644 --- a/src/services/telemetry.service.ts +++ b/src/services/telemetry.service.ts @@ -18,8 +18,8 @@ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import type { AuthContext } from '../types/domain/auth.types'; -import { getCliVersion, getInstallMethod } from '../utils/cli'; +import type { AuthContext } from '../types/domain/auth.types.js'; +import { getCliVersion, getInstallMethod } from '../utils/cli.js'; export type TelemetryLevel = 'log' | 'info' | 'warn' | 'error'; diff --git a/src/services/test-submission.service.ts b/src/services/test-submission.service.ts index da6c6ba..0f1c6c9 100644 --- a/src/services/test-submission.service.ts +++ b/src/services/test-submission.service.ts @@ -1,9 +1,9 @@ import { createHash } from 'node:crypto'; import * as path from 'node:path'; -import { compressFilesFromRelativePath } from '../methods'; -import { toPortableRelativePath } from '../utils/paths'; -import { IExecutionPlan } from './execution-plan.service'; +import { compressFilesFromRelativePath } from '../methods.js'; +import { toPortableRelativePath } from '../utils/paths.js'; +import { IExecutionPlan } from './execution-plan.service.js'; export interface TestSubmissionConfig { androidApiLevel?: string; diff --git a/src/services/version.service.ts b/src/services/version.service.ts index 7ea255d..ceda057 100644 --- a/src/services/version.service.ts +++ b/src/services/version.service.ts @@ -1,4 +1,4 @@ -import { CompatibilityData } from '../utils/compatibility'; +import { CompatibilityData } from '../utils/compatibility.js'; const DEFAULT_MANIFEST_URL = 'https://get.devicecloud.dev/latest.json'; const MANIFEST_TIMEOUT_MS = 3000; diff --git a/src/types/domain/auth.types.ts b/src/types/domain/auth.types.ts index 8a5e6ea..922d72c 100644 --- a/src/types/domain/auth.types.ts +++ b/src/types/domain/auth.types.ts @@ -1,4 +1,4 @@ -import type { DcdEnvName } from '../../config/environments'; +import type { DcdEnvName } from '../../config/environments.js'; /** * Auth context threaded through gateways and services. Callers build this once diff --git a/src/types/index.ts b/src/types/index.ts index 3704751..b2ccf0c 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -4,7 +4,7 @@ */ // Domain-specific types -export * from './domain/device.types'; +export * from './domain/device.types.js'; // Generated types from OpenAPI schema -export * from './generated/schema.types'; +export * from './generated/schema.types.js'; diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 1e2184d..62a376f 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -9,19 +9,19 @@ */ import { closeSync, openSync, rmSync, statSync } from 'node:fs'; -import { ENVIRONMENTS } from '../config/environments'; -import { CliAuthGateway } from '../gateways/cli-auth-gateway'; -import { telemetry } from '../services/telemetry.service'; -import type { AuthContext } from '../types/domain/auth.types'; +import { ENVIRONMENTS } from '../config/environments.js'; +import { CliAuthGateway } from '../gateways/cli-auth-gateway.js'; +import { telemetry } from '../services/telemetry.service.js'; +import type { AuthContext } from '../types/domain/auth.types.js'; -import { CliError } from './cli'; +import { CliError } from './cli.js'; import { StoredConfig, StoredSession, getConfigPath, readConfig, writeConfig, -} from './config-store'; +} from './config-store.js'; const REFRESH_SKEW_SECONDS = 60; // Refresh lock tuning: a refresh is a single HTTP round-trip, so anything diff --git a/src/utils/cli.ts b/src/utils/cli.ts index 903bd3a..1a320f8 100644 --- a/src/utils/cli.ts +++ b/src/utils/cli.ts @@ -6,15 +6,19 @@ * - A minimal Logger mirroring the oclif Command log/warn/error shape so call * sites ported from oclif keep working. */ -import { telemetry } from '../services/telemetry.service'; +import { readFileSync } from 'node:fs'; -import { symbols } from './styling'; +import { telemetry } from '../services/telemetry.service.js'; -// Resolve version at runtime — avoids pulling package.json into the tsbuildinfo rootDir. +import { symbols } from './styling.js'; + +// Resolve version at runtime — read the file rather than importing it, so +// package.json never gets pulled into the tsc program / dist rootDir. export function getCliVersion(): string { try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const pkg = require('../../package.json') as { version: string }; + const pkg = JSON.parse( + readFileSync(new URL('../../package.json', import.meta.url), 'utf8'), + ) as { version: string }; return pkg.version; } catch { return '0.0.0'; diff --git a/src/utils/compatibility.ts b/src/utils/compatibility.ts index 90086b5..a48324a 100644 --- a/src/utils/compatibility.ts +++ b/src/utils/compatibility.ts @@ -1,4 +1,4 @@ -import type { AuthContext } from '../types/domain/auth.types'; +import type { AuthContext } from '../types/domain/auth.types.js'; export interface CompatibilityData { android: Record; diff --git a/src/utils/config-store.ts b/src/utils/config-store.ts index 2be0171..52133e4 100644 --- a/src/utils/config-store.ts +++ b/src/utils/config-store.ts @@ -21,7 +21,7 @@ import { import { homedir } from 'node:os'; import * as path from 'node:path'; -import { ENVIRONMENTS } from '../config/environments'; +import { ENVIRONMENTS } from '../config/environments.js'; export const CONFIG_SCHEMA_VERSION = 1; diff --git a/src/utils/orgs.ts b/src/utils/orgs.ts index 0c6a5ef..47e44aa 100644 --- a/src/utils/orgs.ts +++ b/src/utils/orgs.ts @@ -4,7 +4,7 @@ */ import * as p from '@clack/prompts'; -import { CliError } from './cli'; +import { CliError } from './cli.js'; export interface OrgListItem { id: string; diff --git a/src/utils/styling.ts b/src/utils/styling.ts index 65fb869..0c1d4f0 100644 --- a/src/utils/styling.ts +++ b/src/utils/styling.ts @@ -1,6 +1,6 @@ -import chalk = require('chalk'); +import chalk from 'chalk'; -import { findEnvByApiUrl } from '../config/environments'; +import { findEnvByApiUrl } from '../config/environments.js'; /** * Centralized styling utilities for CLI output diff --git a/test/integration/artifacts.integration.test.ts b/test/integration/artifacts.integration.test.ts index 30babf4..9a79b04 100644 --- a/test/integration/artifacts.integration.test.ts +++ b/test/integration/artifacts.integration.test.ts @@ -10,7 +10,7 @@ import { MOCK_API_URL, exec, runExpectingFailure, -} from './helpers'; +} from './helpers.js'; describe('Artifacts Command Integration Tests', () => { const mockApiUrl = MOCK_API_URL; diff --git a/test/integration/cloud.integration.test.ts b/test/integration/cloud.integration.test.ts index a03543d..e87f06f 100644 --- a/test/integration/cloud.integration.test.ts +++ b/test/integration/cloud.integration.test.ts @@ -9,7 +9,7 @@ import { MOCK_API_URL, exec, runExpectingFailure, -} from './helpers'; +} from './helpers.js'; describe('DCD Cloud Command Integration Tests', () => { const mockApiUrl = MOCK_API_URL; diff --git a/test/integration/list.integration.test.ts b/test/integration/list.integration.test.ts index b1ce934..10805d7 100644 --- a/test/integration/list.integration.test.ts +++ b/test/integration/list.integration.test.ts @@ -7,7 +7,7 @@ import { MOCK_API_URL, exec, runExpectingFailure, -} from './helpers'; +} from './helpers.js'; describe('List Command Integration Tests', () => { const mockApiUrl = MOCK_API_URL; diff --git a/test/integration/mcp.integration.test.ts b/test/integration/mcp.integration.test.ts index dc2d667..9ef4b96 100644 --- a/test/integration/mcp.integration.test.ts +++ b/test/integration/mcp.integration.test.ts @@ -12,7 +12,7 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js' import { expect } from 'chai'; import * as path from 'node:path'; -import { MOCK_API_KEY, MOCK_API_URL } from './helpers'; +import { MOCK_API_KEY, MOCK_API_URL } from './helpers.js'; const MCP_BIN = path.resolve('dist/mcp/index.js'); diff --git a/test/integration/status.integration.test.ts b/test/integration/status.integration.test.ts index 99db8c2..f5a0574 100644 --- a/test/integration/status.integration.test.ts +++ b/test/integration/status.integration.test.ts @@ -7,7 +7,7 @@ import { MOCK_API_URL, exec, runExpectingFailure, -} from './helpers'; +} from './helpers.js'; describe('Status Command Integration Tests', () => { const mockApiUrl = MOCK_API_URL; diff --git a/test/integration/upload.integration.test.ts b/test/integration/upload.integration.test.ts index 8e970ca..40c8058 100644 --- a/test/integration/upload.integration.test.ts +++ b/test/integration/upload.integration.test.ts @@ -10,7 +10,7 @@ import { MOCK_API_URL, exec, runExpectingFailure, -} from './helpers'; +} from './helpers.js'; describe('Upload Command Integration Tests', () => { const mockApiUrl = MOCK_API_URL; diff --git a/test/unit/auth.test.ts b/test/unit/auth.test.ts index 31bd22e..6140715 100644 --- a/test/unit/auth.test.ts +++ b/test/unit/auth.test.ts @@ -3,7 +3,7 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import { resolveAuth } from '../../src/utils/auth'; +import { resolveAuth } from '../../src/utils/auth.js'; import { clearConfig, configFileMode, @@ -11,7 +11,7 @@ import { readConfig, resolveApiUrl, writeConfig, -} from '../../src/utils/config-store'; +} from '../../src/utils/config-store.js'; const ORIGINAL_ENV = { ...process.env }; diff --git a/test/unit/ci.test.ts b/test/unit/ci.test.ts index 049a76c..6fccaf9 100644 --- a/test/unit/ci.test.ts +++ b/test/unit/ci.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { isCI } from '../../src/utils/ci'; +import { isCI } from '../../src/utils/ci.js'; // Every env var isCI() inspects — cleared before each case so the environment // the suite happens to run in (often a real CI) can't leak into assertions. diff --git a/test/unit/flow-paths.test.ts b/test/unit/flow-paths.test.ts index d9af84f..4658b6c 100644 --- a/test/unit/flow-paths.test.ts +++ b/test/unit/flow-paths.test.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; import { buildTestMetadataMap, computeCommonRoot, -} from '../../src/services/flow-paths'; +} from '../../src/services/flow-paths.js'; describe('flow-paths', () => { describe('computeCommonRoot', () => { diff --git a/test/unit/live-command.test.ts b/test/unit/live-command.test.ts index 4901275..5dd343c 100644 --- a/test/unit/live-command.test.ts +++ b/test/unit/live-command.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { liveCommand } from '../../src/commands/live'; +import { liveCommand } from '../../src/commands/live.js'; /** * Regression coverage for #15: citty invokes a parent command's `run` *after* diff --git a/test/unit/metadata-extractor.service.test.ts b/test/unit/metadata-extractor.service.test.ts index bf4d3fa..0d20454 100644 --- a/test/unit/metadata-extractor.service.test.ts +++ b/test/unit/metadata-extractor.service.test.ts @@ -6,7 +6,7 @@ import * as path from 'node:path'; import { AndroidMetadataExtractor, MetadataExtractorService, -} from '../../src/services/metadata-extractor.service'; +} from '../../src/services/metadata-extractor.service.js'; const WIKIPEDIA_APK = path.resolve('test/fixtures/wikipedia.apk'); diff --git a/test/unit/report-download.service.test.ts b/test/unit/report-download.service.test.ts index 4e1c6ba..2ed8566 100644 --- a/test/unit/report-download.service.test.ts +++ b/test/unit/report-download.service.test.ts @@ -3,8 +3,8 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import { ReportDownloadService } from '../../src/services/report-download.service'; -import type { AuthContext } from '../../src/types/domain/auth.types'; +import { ReportDownloadService } from '../../src/services/report-download.service.js'; +import type { AuthContext } from '../../src/types/domain/auth.types.js'; const TEST_AUTH: AuthContext = { mode: 'apiKey', diff --git a/tsconfig.json b/tsconfig.json index 7fa0664..96a35b1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,9 @@ { "compilerOptions": { "declaration": true, - "module": "commonjs", + "module": "nodenext", + "moduleResolution": "nodenext", + "esModuleInterop": true, "outDir": "dist", "rootDir": "src", "strict": true, From 38e001892d981619a76cfa6a2d2b32177e23a012 Mon Sep 17 00:00:00 2001 From: Tom Riglar Date: Mon, 22 Jun 2026 15:31:31 +0100 Subject: [PATCH 2/2] fix: harden node-apk/bplist-parser CJS named imports for ESM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI (Node 22) failed with "node-apk does not provide an export named 'Apk'". Both node-apk and bplist-parser are CJS with no exports map, so named imports rely on cjs-module-lexer's named-export detection — which is Node-version-dependent (Node 25 detects them, Node 22 does not). Switch to default-import + destructure, which relies only on the guaranteed `default = module.exports` interop and works on every Node version. plist stays a named import (it's genuine ESM). Verified under Node 22: unit suite (incl. APK metadata extraction) loads and passes. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/services/metadata-extractor.service.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/services/metadata-extractor.service.ts b/src/services/metadata-extractor.service.ts index 79a7d84..3c26478 100644 --- a/src/services/metadata-extractor.service.ts +++ b/src/services/metadata-extractor.service.ts @@ -1,10 +1,16 @@ -import { parseBuffer } from 'bplist-parser'; -import { Apk } from 'node-apk'; +import bplistParser from 'bplist-parser'; +import nodeApk from 'node-apk'; import { readFile, rm } from 'node:fs/promises'; import * as path from 'node:path'; import StreamZip from 'node-stream-zip'; import { parse } from 'plist'; +// node-apk and bplist-parser are CJS with no `exports` map; Node's named-export +// detection for CJS (cjs-module-lexer) is version-dependent, so destructure off +// the default import instead — that interop is guaranteed on every Node version. +const { Apk } = nodeApk; +const { parseBuffer } = bplistParser; + export interface TAppMetadata { appId: string; platform: 'android' | 'ios';