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 changes: 3 additions & 0 deletions .github/workflows/unit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ jobs:
run: |
npm run build:cli
node bin/code-push.js build-patch-tools --print-hash > /dev/null
# Loads a TypeScript config file that uses `export default` and a tsconfig `paths` alias,
# from its project directory the way a user runs the CLI.
(cd cli/__fixtures__/ts-config-project && node ../../../bin/code-push.js show-history -b 1.0.0 -p ios | grep -q '"packageHash": "fixture"')
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,18 @@ module.exports = Config;
- Modifying an existing release history with the `update-history` command.


**(2) For `code-push.config.ts` (TypeScript) to work properly, you may need to update your `tsconfig.json`.**
**(2) A `code-push.config.ts` (TypeScript) file needs a loader installed.**

Install `tsx`, which requires no further setup:

```bash
npm install --save-dev tsx
```

`ts-node` also keeps working, and is used when `tsx` is not installed. Note that `ts-node` is no
longer maintained, so support for it is **deprecated and will be removed in a future major
version** — please migrate to `tsx`. Until then, `ts-node` needs the following `tsconfig.json`
setup:

```diff
{
Expand All @@ -414,6 +425,8 @@ module.exports = Config;

```

A `code-push.config.js` (JavaScript) file needs neither.


### 6. Diff Updates (Optional)

Expand Down
2 changes: 1 addition & 1 deletion cli/README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

- **Node.js** >= 18
- React Native 프로젝트에서 **Hermes** 엔진 활성화
- **ts-node** (선택 사항, 설정 파일이 `.ts`인 경우 필요)
- **tsx** 또는 **ts-node** (선택 사항, 설정 파일이 `.ts`인 경우에만 필요하며 `tsx`를 권장합니다)

## 빠른 시작

Expand Down
2 changes: 1 addition & 1 deletion cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ CLI for [`@bravemobile/react-native-code-push`](../README.md). Bundles, releases

- **Node.js** >= 18
- **Hermes** engine enabled in your React Native project
- **ts-node** (optional — only needed if your config file is `.ts`)
- **tsx** or **ts-node** (optional — only needed if your config file is `.ts`; `tsx` recommended)

## Quick Start

Expand Down
14 changes: 14 additions & 0 deletions cli/__fixtures__/ts-config-project/code-push.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Loaded by the CI smoke test through the compiled CLI: a TypeScript config that uses
// `export default` and a tsconfig `paths` alias, the two things the loader must handle.
import type { CliConfigInterface, ReleaseHistoryInterface } from "@bravemobile/react-native-code-push";
import { downloadUrlFor } from "@helpers/host";

const config: CliConfigInterface = {
bundleUploader: async (source) => ({ downloadUrl: downloadUrlFor(source) }),
getReleaseHistory: async (targetBinaryVersion): Promise<ReleaseHistoryInterface> => ({
[targetBinaryVersion]: { enabled: true, mandatory: false, downloadUrl: downloadUrlFor("fixture.zip"), packageHash: "fixture" },
}),
setReleaseHistory: async () => {},
};

export default config;
5 changes: 5 additions & 0 deletions cli/__fixtures__/ts-config-project/helpers/host.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const CDN_HOST = "https://cdn.example.com";

export function downloadUrlFor(fileName: string): string {
return `${CDN_HOST}/${fileName}`;
}
4 changes: 4 additions & 0 deletions cli/__fixtures__/ts-config-project/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "ts-config-project",
"private": true
}
10 changes: 10 additions & 0 deletions cli/__fixtures__/ts-config-project/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"baseUrl": ".",
"paths": {
"@helpers/*": ["helpers/*"]
}
}
}
6 changes: 5 additions & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@
"yauzl": "^3.2.0"
},
"peerDependencies": {
"ts-node": ">=10"
"ts-node": ">=10",
"tsx": ">=4"
},
"peerDependenciesMeta": {
"ts-node": {
"optional": true
},
"tsx": {
"optional": true
}
},
"engines": {
Expand Down
3 changes: 2 additions & 1 deletion cli/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
},
"include": ["./**/*.ts"],
"exclude": [
"dist"
"dist",
"**/__fixtures__"
]
}
52 changes: 43 additions & 9 deletions cli/utils/fsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,62 @@ import path from "path";
import { createRequire } from "module";
import type { CliConfigInterface } from "../../typings/react-native-code-push.d.ts";

const nodeRequire = createRequire(import.meta.url);

/**
* allows to require a config file with .ts extension
*/
function requireConfig(filePath: string): CliConfigInterface {
const ext = path.extname(filePath);
// Resolve the loader from the project the config file lives in, not from the CLI install.
const projectRequire = createRequire(filePath);

if (ext === '.ts') {
try {
nodeRequire('ts-node/register');
} catch {
console.error('ts-node not found. Please install ts-node as a devDependency.');
process.exit(1);
}
return unwrapDefaultExport(requireTsConfig(projectRequire, filePath));
} else if (ext === '.js') {
// do nothing
} else {
throw new Error(`Unsupported file extension: ${ext}`);
}

return nodeRequire(filePath) as CliConfigInterface;
return unwrapDefaultExport(projectRequire(filePath));
}

/**
* tsx is preferred: it needs no tsconfig setup and resolves tsconfig `paths` aliases on its own.
* ts-node keeps working for projects that already have it configured.
*/
function requireTsConfig(projectRequire: NodeRequire, filePath: string): { default?: unknown } {
if (canResolve(projectRequire, 'tsx/cjs/api')) {
const { require: tsxRequire } = projectRequire('tsx/cjs/api') as {
require: (id: string, fromFile: string) => { default?: unknown };
};
return tsxRequire(filePath, filePath);
}

if (canResolve(projectRequire, 'ts-node/register')) {
console.warn(
'warn: Loading the config file with ts-node, which is no longer maintained. Support for it ' +
'will be removed in a future major version - please install tsx instead (`npm i -D tsx`).',
);
projectRequire('ts-node/register');
return projectRequire(filePath);
}

console.error('A TypeScript config file needs a loader. Please install tsx as a devDependency (`npm i -D tsx`).');
process.exit(1);
}

function canResolve(projectRequire: NodeRequire, id: string): boolean {
try {
projectRequire.resolve(id);
return true;
} catch {
return false;
}
}

// `export default` compiles to `exports.default`; `module.exports =` is returned as is.
function unwrapDefaultExport(loaded: { default?: unknown }): CliConfigInterface {
return (loaded.default ?? loaded) as CliConfigInterface;
}

export function findAndReadConfigFile(startDir: string, configFileName: string): CliConfigInterface {
Expand Down
Loading