├── .gitignore ├── README.md └── typescript ├── hello-world ├── .eslintignore ├── .eslintrc ├── .exercism │ ├── config.json │ └── metadata.json ├── HELP.md ├── README.md ├── babel.config.js ├── hello-world.test.ts ├── hello-world.ts ├── jest.config.js ├── package.json ├── tsconfig.json └── yarn.lock ├── resistor-color-duo ├── .exercism │ ├── config.json │ └── metadata.json ├── .pnp.cjs ├── .pnp.loader.mjs ├── .vscode │ ├── extensions.json │ └── settings.json ├── .yarn │ └── install-state.gz ├── .yarnrc.yml ├── HELP.md ├── README.md ├── babel.config.cjs ├── eslint.config.mjs ├── jest.config.cjs ├── package.json ├── resistor-color-duo.test.ts ├── resistor-color-duo.ts ├── tsconfig.json └── yarn.lock ├── resistor-color-trio ├── .exercism │ ├── config.json │ └── metadata.json ├── .pnp.cjs ├── .pnp.loader.mjs ├── .vscode │ ├── extensions.json │ └── settings.json ├── .yarn │ └── install-state.gz ├── .yarnrc.yml ├── HELP.md ├── README.md ├── babel.config.cjs ├── eslint.config.mjs ├── jest.config.cjs ├── package.json ├── resistor-color-trio.test.ts ├── resistor-color-trio.ts ├── test-runner.mjs ├── tsconfig.json └── yarn.lock ├── resistor-color ├── .exercism │ ├── config.json │ └── metadata.json ├── .pnp.cjs ├── .pnp.loader.mjs ├── .vscode │ ├── extensions.json │ └── settings.json ├── .yarn │ └── install-state.gz ├── .yarnrc.yml ├── HELP.md ├── README.md ├── babel.config.cjs ├── eslint.config.mjs ├── jest.config.cjs ├── package.json ├── resistor-color.test.ts ├── resistor-color.ts ├── test-runner.mjs ├── tsconfig.json └── yarn.lock └── two-fer ├── .exercism ├── config.json └── metadata.json ├── .pnp.cjs ├── .pnp.loader.mjs ├── .vscode ├── extensions.json └── settings.json ├── .yarn └── install-state.gz ├── .yarnrc.yml ├── HELP.md ├── README.md ├── babel.config.cjs ├── eslint.config.mjs ├── jest.config.cjs ├── package.json ├── tsconfig.json ├── two-fer.test.ts ├── two-fer.ts └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | **/**/node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Practicing Functional TypeScript 2 | 3 | ## Purpose 4 | 5 | Simplify software complexity by practicing functional programming in TypeScript. Functional programming with pure functions helps reduce complexity, making code easier to understand, test, and maintain. 6 | 7 | ## Approach 8 | 9 | - **Functional TypeScript:** Solve problems using core JavaScript and TypeScript features with a focus on functional programming. 10 | - **Built-in Methods:** Prioritize using JavaScript's built-in functions and TypeScript's type system to implement solutions without relying on external libraries. 11 | 12 | ## Exercises 13 | 14 | This repository contains several exercises focused on functional TypeScript: 15 | 16 | 1. Hello World 17 | 2. Two Fer 18 | 3. Resistor Color 19 | 4. Resistor Color Duo 20 | 5. Resistor Color Trio 21 | 6. Gigasecond 22 | 7. RNA Transcription 23 | 8. Space Age 24 | 25 | ## Why Functional Programming? 26 | 27 | Functional programming is about simplifying complexity by breaking down problems into pure, composable functions. This repository is designed to help you practice functional programming concepts in TypeScript by solving real-world problems. 28 | 29 | ## How to Solve Exercises 30 | 31 | - **Use Built-in Methods:** Leverage JavaScript's and TypeScript's built-in methods for functional programming. 32 | - **Emphasize Purity:** Focus on creating pure functions that are easy to test and reason about. 33 | - **Ensure Immutability:** Write code that avoids mutating data, preferring immutable data structures and operations. 34 | -------------------------------------------------------------------------------- /typescript/hello-world/.eslintignore: -------------------------------------------------------------------------------- 1 | !.meta 2 | 3 | # Protected or generated 4 | .git 5 | .vscode 6 | 7 | # When using npm 8 | node_modules/* 9 | 10 | # Configuration files 11 | babel.config.js 12 | jest.config.js -------------------------------------------------------------------------------- /typescript/hello-world/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "project": "./tsconfig.json", 6 | "ecmaFeatures": { 7 | "jsx": true 8 | }, 9 | "ecmaVersion": 2020, 10 | "sourceType": "module" 11 | }, 12 | "extends": "@exercism/eslint-config-typescript", 13 | "env": { 14 | "jest": true 15 | }, 16 | "overrides": [ 17 | { 18 | "files": [".meta/proof.ci.ts", ".meta/exemplar.ts", "*.test.ts"], 19 | "excludedFiles": ["custom.test.ts"], 20 | "extends": "@exercism/eslint-config-typescript/maintainers" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /typescript/hello-world/.exercism/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "blurb": "The classical introductory exercise. Just say \"Hello, World!\"", 3 | "authors": ["masters3d"], 4 | "contributors": [ 5 | "DFXLuna", 6 | "iHiD", 7 | "kytrinyx", 8 | "lukaszklis", 9 | "porkostomus", 10 | "SleeplessByte" 11 | ], 12 | "files": { 13 | "solution": ["hello-world.ts"], 14 | "test": ["hello-world.test.ts"], 15 | "example": [".meta/proof.ci.ts"] 16 | }, 17 | "source": "This is an exercise to introduce users to using Exercism", 18 | "source_url": "http://en.wikipedia.org/wiki/%22Hello,_world!%22_program" 19 | } 20 | -------------------------------------------------------------------------------- /typescript/hello-world/.exercism/metadata.json: -------------------------------------------------------------------------------- 1 | {"track":"typescript","exercise":"hello-world","id":"649bca7d08a444d499a6bd4ae0827d7b","url":"https://exercism.org/tracks/typescript/exercises/hello-world","handle":"yosevu","is_requester":true,"auto_approve":false} -------------------------------------------------------------------------------- /typescript/hello-world/HELP.md: -------------------------------------------------------------------------------- 1 | # Help 2 | 3 | ## Running the tests 4 | 5 | Before trying to execute the tests, ensure the assignment folder is set-up correctly by following the installation steps, namely `corepack yarn install` and the Editor SDK setup. 6 | 7 | Execute the tests with: 8 | 9 | ```bash 10 | $ corepack yarn test 11 | ``` 12 | 13 | ## Skipped tests 14 | 15 | In the test suites all tests but the first have been skipped. 16 | 17 | Once you get a test passing, you can enable the next one by changing `xit` to `it`. 18 | Additionally tests may be grouped using `xdescribe`. 19 | Enable the group by changing that to `describe`. 20 | Finally, some exercises may have optional tests `it.skip`. 21 | Remove `.skip` to execute the optional test. 22 | 23 | ## Submitting your solution 24 | 25 | You can submit your solution using the `exercism submit hello-world.ts` command. 26 | This command will upload your solution to the Exercism website and print the solution page's URL. 27 | 28 | It's possible to submit an incomplete solution which allows you to: 29 | 30 | - See how others have completed the exercise 31 | - Request help from a mentor 32 | 33 | ## Need to get help? 34 | 35 | If you'd like help solving the exercise, check the following pages: 36 | 37 | - The [TypeScript track's documentation](https://exercism.org/docs/tracks/typescript) 38 | - The [TypeScript track's programming category on the forum](https://forum.exercism.org/c/programming/typescript) 39 | - [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) 40 | - The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) 41 | 42 | Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. 43 | 44 | To get help if you're having trouble, you can use one of the following resources: 45 | 46 | - [TypeScript QuickStart](https://www.typescriptlang.org/docs/handbook/release-notes/overview.html) 47 | - [ECMAScript 2015 Language Specification](https://www.ecma-international.org/wp-content/uploads/ECMA-262_6th_edition_june_2015.pdf) (pdf) 48 | - [Mozilla JavaScript Reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference) 49 | - [/r/typescript](https://www.reddit.com/r/typescript) is the TypeScript subreddit. 50 | - [StackOverflow](https://stackoverflow.com/questions/tagged/typescript) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions. -------------------------------------------------------------------------------- /typescript/hello-world/README.md: -------------------------------------------------------------------------------- 1 | # Hello World 2 | 3 | Welcome to Hello World on Exercism's TypeScript Track. 4 | If you need help running the tests or submitting your code, check out `HELP.md`. 5 | 6 | ## Instructions 7 | 8 | The classical introductory exercise. Just say "Hello, World!". 9 | 10 | ["Hello, World!"](http://en.wikipedia.org/wiki/%22Hello,_world!%22_program) is 11 | the traditional first program for beginning programming in a new language 12 | or environment. 13 | 14 | The objectives are simple: 15 | 16 | - Write a function that returns the string "Hello, World!". 17 | - Run the test suite and make sure that it succeeds. 18 | - Submit your solution and check it at the website. 19 | 20 | If everything goes well, you will be ready to fetch your first real exercise. 21 | 22 | ## Setup 23 | 24 | Go through the setup instructions for TypeScript to 25 | install the necessary dependencies: 26 | 27 | http://exercism.io/languages/typescript 28 | 29 | ## Requirements 30 | 31 | Install assignment dependencies: 32 | 33 | ```bash 34 | $ yarn install 35 | ``` 36 | 37 | ## Making the test suite pass 38 | 39 | Execute the tests with: 40 | 41 | ```bash 42 | $ yarn test 43 | ``` 44 | 45 | In many test suites all but the first test have been skipped. 46 | 47 | Once you get a test passing, you can unskip the next one by 48 | changing `xit` to `it`. 49 | 50 | ## Tutorial 51 | 52 | This section is a step-by-step guide to solving this exercise. 53 | 54 | This exercise has two files: 55 | 56 | - hello-world.ts 57 | - hello-world.test.ts 58 | 59 | The first file is where you will write your code. 60 | The second is where the tests are defined. 61 | 62 | The tests will check whether your code is doing the right thing. 63 | You don't need to be able to write a test suite from scratch, 64 | but it helps to understand what a test looks like, and what 65 | it is doing. 66 | 67 | Open up the test file, hello-world.test.ts. 68 | There is a single test inside: 69 | 70 | ```typescript 71 | it('says hello world', () => { 72 | expect(hello()).toEqual('Hello, World!') 73 | }) 74 | ``` 75 | 76 | Run the test now, with the following command on the command-line: 77 | 78 | ```bash 79 | $ yarn test 80 | ``` 81 | 82 | The test fails, which makes sense since you've not written any code yet. 83 | 84 | The failure looks like this: 85 | 86 | ``` 87 | × says hello world (5ms) 88 | 89 | ● Hello World › says hello world 90 | 91 | expect(received).toEqual(expected) // deep equality 92 | 93 | Expected: "Hello, World!" 94 | Received: "What's up doc 👋🏽?" 95 | 96 | 4 | 97 | 5 | it('says hello world', () => { 98 | > 6 | expect(hello()).toEqual('Hello, World!') 99 | | ^ 100 | 7 | }) 101 | 8 | 102 | 9 | }) 103 | 104 | at Object.it (hello-world.test.ts:6:32) 105 | ``` 106 | 107 | And these are those code lines with probable defects in the `hello-world.test.ts` file: 108 | 109 | the 6th line: 110 | 111 | ```typescript 112 | expect(hello()).toEqual('Hello, World!') 113 | ^ 114 | ``` 115 | 116 | Hence the problem is with the `hello()` function call. 117 | We can see that the test is expecting `'Hello, World!'` as output, but instead is getting `"What's up doc 👋🏽?"`. 118 | 119 | So let's check now this function in the `hello-worlds.ts` file: 120 | 121 | ```typescript 122 | export function hello(): string { 123 | return "What's up doc 👋🏽?" 124 | } 125 | ``` 126 | 127 | Now we see that the function returns the incorrect string, which is the reason for our failure. Let's fix this by changing the returned value: 128 | 129 | ```typescript 130 | export function hello(): string { 131 | return 'Hello, World!' 132 | } 133 | ``` 134 | 135 | Run the test again: 136 | 137 | ```bash 138 | PASS ./hello-world.test.ts 139 | Hello World 140 | √ says hello world (4ms) 141 | ``` 142 | 143 | And it passes! 144 | 145 | ## Source 146 | 147 | ### Created by 148 | 149 | - @masters3d 150 | 151 | ### Contributed to by 152 | 153 | - @DFXLuna 154 | - @iHiD 155 | - @kytrinyx 156 | - @lukaszklis 157 | - @porkostomus 158 | - @SleeplessByte 159 | 160 | ### Based on 161 | 162 | This is an exercise to introduce users to using Exercism - http://en.wikipedia.org/wiki/%22Hello,_world!%22_program -------------------------------------------------------------------------------- /typescript/hello-world/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | useBuiltIns: 'entry', 10 | corejs: '3.15', 11 | }, 12 | ], 13 | '@babel/preset-typescript', 14 | ], 15 | plugins: [ 16 | '@babel/proposal-class-properties', 17 | '@babel/proposal-object-rest-spread', 18 | '@babel/plugin-syntax-bigint', 19 | ], 20 | } 21 | -------------------------------------------------------------------------------- /typescript/hello-world/hello-world.test.ts: -------------------------------------------------------------------------------- 1 | import { hello } from './hello-world' 2 | 3 | describe('Hello World', () => { 4 | it('says hello world', () => { 5 | expect(hello()).toEqual('Hello, World!') 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /typescript/hello-world/hello-world.ts: -------------------------------------------------------------------------------- 1 | export const hello = () => 'Hello, World!' 2 | -------------------------------------------------------------------------------- /typescript/hello-world/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | projects: [''], 4 | testMatch: [ 5 | '**/__tests__/**/*.[jt]s?(x)', 6 | '**/test/**/*.[jt]s?(x)', 7 | '**/?(*.)+(spec|test).[jt]s?(x)', 8 | ], 9 | testPathIgnorePatterns: [ 10 | '/(?:production_)?node_modules/', 11 | '.d.ts$', 12 | '/test/fixtures', 13 | '/test/helpers', 14 | '__mocks__', 15 | ], 16 | transform: { 17 | '^.+\\.[jt]sx?$': 'babel-jest', 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /typescript/hello-world/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@exercism/typescript-hello-world", 3 | "version": "1.0.0", 4 | "description": "Exercism exercises in Typescript.", 5 | "private": true, 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/exercism/typescript" 9 | }, 10 | "devDependencies": { 11 | "@babel/core": "^7.14.6", 12 | "@babel/plugin-proposal-class-properties": "^7.14.5", 13 | "@babel/plugin-proposal-object-rest-spread": "^7.14.5", 14 | "@babel/plugin-syntax-bigint": "^7.8.3", 15 | "@babel/preset-env": "^7.15.0", 16 | "@babel/preset-typescript": "^7.15.0", 17 | "@types/jest": "^26.0.24", 18 | "@types/node": "^16.4.13", 19 | "@exercism/eslint-config-typescript": "^0.2.3", 20 | "babel-jest": "^27.0.6", 21 | "core-js": "^3.15.2", 22 | "eslint": "^7.32.0", 23 | "eslint-plugin-import": "^2.23.4", 24 | "jest": "^27.0.6", 25 | "typescript": "^4.3.5" 26 | }, 27 | "scripts": { 28 | "test": "yarn lint:types && jest --no-cache", 29 | "lint": "yarn lint:types && yarn lint:ci", 30 | "lint:types": "yarn tsc --noEmit -p .", 31 | "lint:ci": "eslint . --ext .tsx,.ts" 32 | }, 33 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" 34 | } 35 | -------------------------------------------------------------------------------- /typescript/hello-world/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, 5 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 6 | "lib": [ 7 | "esnext", 8 | "es2016", 9 | "es2017" 10 | ] /* Specify library files to be included in the compilation. */, 11 | // "allowJs": true, /* Allow javascript files to be compiled. */ 12 | // "checkJs": true, /* Report errors in .js files. */ 13 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 14 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 15 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 16 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 17 | // "outFile": "./", /* Concatenate and emit output to single file. */ 18 | // "outDir": "./build", /* Redirect output structure to the directory. */ 19 | // "rootDirs": ["./"], /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 20 | // "composite": true, /* Enable project compilation */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | "noEmit": true /* Do not emit outputs. */, 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | "isolatedModules": true /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */, 26 | // "noEmitOnError": true, /* Do not emit outputs when compilation fails. */ 27 | 28 | /* Strict Type-Checking Options */ 29 | "strict": true /* Enable all strict type-checking options. */, 30 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 31 | // "strictNullChecks": true, /* Enable strict null checks. */ 32 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 33 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 34 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 35 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 36 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 37 | 38 | /* Additional Checks */ 39 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 40 | "noUnusedParameters": true /* Report errors on unused parameters. */, 41 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 42 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 43 | 44 | /* Module Resolution Options */ 45 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 46 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 47 | // "paths": { "~src/*": ["./src/*"] }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 49 | // "typeRoots": [], /* List of folders to include type definitions from. */ 50 | // "types": [], /* Type declaration files to be included in compilation. */ 51 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, 52 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 54 | 55 | /* Source Map Options */ 56 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 60 | 61 | /* Experimental Options */ 62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 64 | }, 65 | "compileOnSave": true, 66 | "include": ["*", ".meta/*"], 67 | "exclude": ["node_modules"] 68 | } 69 | -------------------------------------------------------------------------------- /typescript/resistor-color-duo/.exercism/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "msomji" 4 | ], 5 | "contributors": [ 6 | "EduardoBautista", 7 | "SleeplessByte" 8 | ], 9 | "files": { 10 | "solution": [ 11 | "resistor-color-duo.ts" 12 | ], 13 | "test": [ 14 | "resistor-color-duo.test.ts" 15 | ], 16 | "example": [ 17 | ".meta/proof.ci.ts" 18 | ] 19 | }, 20 | "blurb": "Convert color codes, as used on resistors, to a numeric value.", 21 | "custom": { 22 | "version.tests.compatibility": "jest-29", 23 | "flag.tests.task-per-describe": false, 24 | "flag.tests.may-run-long": false, 25 | "flag.tests.includes-optional": false, 26 | "flag.tests.jest": true, 27 | "flag.tests.tstyche": false 28 | }, 29 | "source": "Maud de Vries, Erik Schierboom", 30 | "source_url": "https://github.com/exercism/problem-specifications/issues/1464" 31 | } 32 | -------------------------------------------------------------------------------- /typescript/resistor-color-duo/.exercism/metadata.json: -------------------------------------------------------------------------------- 1 | {"track":"typescript","exercise":"resistor-color-duo","id":"b21f58ca9ec5409a9773f858d67c59a7","url":"https://exercism.org/tracks/typescript/exercises/resistor-color-duo","handle":"yosevu","is_requester":true,"auto_approve":false} -------------------------------------------------------------------------------- /typescript/resistor-color-duo/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "arcanis.vscode-zipfs", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /typescript/resistor-color-duo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["exercism"], 3 | "search.exclude": { 4 | "**/.yarn": true, 5 | "**/.pnp.*": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /typescript/resistor-color-duo/.yarn/install-state.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yosevu/practicing-functional-typescript/f8eee201a26b5488e69962a5e0735a29a8440a7a/typescript/resistor-color-duo/.yarn/install-state.gz -------------------------------------------------------------------------------- /typescript/resistor-color-duo/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: true 4 | -------------------------------------------------------------------------------- /typescript/resistor-color-duo/HELP.md: -------------------------------------------------------------------------------- 1 | # Help 2 | 3 | ## Running the tests 4 | 5 | Before trying to execute the tests, ensure the assignment folder is set-up correctly by following the installation steps, namely `corepack yarn install` and the Editor SDK setup. 6 | 7 | Execute the tests with: 8 | 9 | ```bash 10 | $ corepack yarn test 11 | ``` 12 | 13 | ## Skipped tests 14 | 15 | In the test suites all tests but the first have been skipped. 16 | 17 | Once you get a test passing, you can enable the next one by changing `xit` to `it`. 18 | Additionally tests may be grouped using `xdescribe`. 19 | Enable the group by changing that to `describe`. 20 | Finally, some exercises may have optional tests `it.skip`. 21 | Remove `.skip` to execute the optional test. 22 | 23 | ## Submitting your solution 24 | 25 | You can submit your solution using the `exercism submit resistor-color-duo.ts` command. 26 | This command will upload your solution to the Exercism website and print the solution page's URL. 27 | 28 | It's possible to submit an incomplete solution which allows you to: 29 | 30 | - See how others have completed the exercise 31 | - Request help from a mentor 32 | 33 | ## Need to get help? 34 | 35 | If you'd like help solving the exercise, check the following pages: 36 | 37 | - The [TypeScript track's documentation](https://exercism.org/docs/tracks/typescript) 38 | - The [TypeScript track's programming category on the forum](https://forum.exercism.org/c/programming/typescript) 39 | - [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) 40 | - The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) 41 | 42 | Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. 43 | 44 | To get help if you're having trouble, you can use one of the following resources: 45 | 46 | - [TypeScript QuickStart](https://www.typescriptlang.org/docs/handbook/release-notes/overview.html) 47 | - [ECMAScript 2015 Language Specification](https://www.ecma-international.org/wp-content/uploads/ECMA-262_6th_edition_june_2015.pdf) (pdf) 48 | - [Mozilla JavaScript Reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference) 49 | - [/r/typescript](https://www.reddit.com/r/typescript) is the TypeScript subreddit. 50 | - [StackOverflow](https://stackoverflow.com/questions/tagged/typescript) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions. -------------------------------------------------------------------------------- /typescript/resistor-color-duo/README.md: -------------------------------------------------------------------------------- 1 | # Resistor Color Duo 2 | 3 | Welcome to Resistor Color Duo on Exercism's TypeScript Track. 4 | If you need help running the tests or submitting your code, check out `HELP.md`. 5 | 6 | ## Instructions 7 | 8 | If you want to build something using a Raspberry Pi, you'll probably use _resistors_. 9 | For this exercise, you need to know two things about them: 10 | 11 | - Each resistor has a resistance value. 12 | - Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. 13 | 14 | To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values. 15 | Each band has a position and a numeric value. 16 | 17 | The first 2 bands of a resistor have a simple encoding scheme: each color maps to a single number. 18 | For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15. 19 | 20 | In this exercise you are going to create a helpful program so that you don't have to remember the values of the bands. 21 | The program will take color names as input and output a two digit number, even if the input is more than two colors! 22 | 23 | The band colors are encoded as follows: 24 | 25 | - black: 0 26 | - brown: 1 27 | - red: 2 28 | - orange: 3 29 | - yellow: 4 30 | - green: 5 31 | - blue: 6 32 | - violet: 7 33 | - grey: 8 34 | - white: 9 35 | 36 | From the example above: 37 | brown-green should return 15, and 38 | brown-green-violet should return 15 too, ignoring the third color. 39 | 40 | ## Source 41 | 42 | ### Created by 43 | 44 | - @msomji 45 | 46 | ### Contributed to by 47 | 48 | - @EduardoBautista 49 | - @SleeplessByte 50 | 51 | ### Based on 52 | 53 | Maud de Vries, Erik Schierboom - https://github.com/exercism/problem-specifications/issues/1464 -------------------------------------------------------------------------------- /typescript/resistor-color-duo/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.37' }]], 3 | plugins: [], 4 | } 5 | -------------------------------------------------------------------------------- /typescript/resistor-color-duo/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import tsEslint from 'typescript-eslint' 4 | import config from '@exercism/eslint-config-typescript' 5 | import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' 6 | 7 | export default [ 8 | ...tsEslint.config(...config, { 9 | files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], 10 | extends: maintainersConfig, 11 | }), 12 | { 13 | ignores: [ 14 | // # Protected or generated 15 | '.git/**/*', 16 | '.vscode/**/*', 17 | 18 | //# When using npm 19 | 'node_modules/**/*', 20 | 21 | // # Configuration files 22 | 'babel.config.cjs', 23 | 'jest.config.cjs', 24 | ], 25 | }, 26 | ] 27 | -------------------------------------------------------------------------------- /typescript/resistor-color-duo/jest.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | projects: [''], 4 | testMatch: [ 5 | '**/__tests__/**/*.[jt]s?(x)', 6 | '**/test/**/*.[jt]s?(x)', 7 | '**/?(*.)+(spec|test).[jt]s?(x)', 8 | ], 9 | testPathIgnorePatterns: [ 10 | '/(?:production_)?node_modules/', 11 | '.d.ts$', 12 | '/test/fixtures', 13 | '/test/helpers', 14 | '__mocks__', 15 | ], 16 | transform: { 17 | '^.+\\.[jt]sx?$': 'babel-jest', 18 | }, 19 | moduleNameMapper: { 20 | '^(\\.\\/.+)\\.js$': '$1', 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /typescript/resistor-color-duo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@exercism/typescript-resistor-color-duo", 3 | "version": "1.0.0", 4 | "description": "Exercism exercises in Typescript.", 5 | "private": true, 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/exercism/typescript" 9 | }, 10 | "type": "module", 11 | "engines": { 12 | "node": "^18.16.0 || >=20.0.0" 13 | }, 14 | "devDependencies": { 15 | "@exercism/babel-preset-typescript": "^0.5.0", 16 | "@exercism/eslint-config-typescript": "^0.7.1", 17 | "@jest/globals": "^29.7.0", 18 | "@types/node": "~22.0.2", 19 | "babel-jest": "^29.7.0", 20 | "core-js": "~3.37.1", 21 | "eslint": "^9.8.0", 22 | "expect": "^29.7.0", 23 | "jest": "^29.7.0", 24 | "prettier": "^3.3.3", 25 | "tstyche": "^2.1.1", 26 | "typescript": "~5.5.4", 27 | "typescript-eslint": "^7.18.0" 28 | }, 29 | "scripts": { 30 | "test": "jest", 31 | "test:types": "tstyche", 32 | "test:implementation": "jest --no-cache --passWithNoTests", 33 | "lint": "yarn lint:types && yarn lint:ci", 34 | "lint:types": "tsc --noEmit -p .", 35 | "lint:ci": "eslint . --ext .tsx,.ts" 36 | }, 37 | "packageManager": "yarn@4.3.1" 38 | } 39 | -------------------------------------------------------------------------------- /typescript/resistor-color-duo/resistor-color-duo.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from '@jest/globals' 2 | import { decodedValue } from './resistor-color-duo.ts' 3 | 4 | describe('Resistor Colors', () => { 5 | it('Brown and black', () => { 6 | expect(decodedValue(['brown', 'black'])).toEqual(10) 7 | }) 8 | 9 | it('Blue and grey', () => { 10 | expect(decodedValue(['blue', 'grey'])).toEqual(68) 11 | }) 12 | 13 | it('White and red', () => { 14 | expect(decodedValue(['white', 'red'])).toEqual(92) 15 | }) 16 | 17 | it('Yellow and violet', () => { 18 | expect(decodedValue(['yellow', 'violet'])).toEqual(47) 19 | }) 20 | 21 | it('Orange and orange', () => { 22 | expect(decodedValue(['orange', 'orange'])).toEqual(33) 23 | }) 24 | 25 | it('Ignore additional colors', () => { 26 | expect(decodedValue(['green', 'brown', 'orange'])).toEqual(51) 27 | }) 28 | 29 | it('Black and brown, one-digit', () => { 30 | expect(decodedValue(['black', 'brown'])).toEqual(1) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /typescript/resistor-color-duo/resistor-color-duo.ts: -------------------------------------------------------------------------------- 1 | const COLORS = [ 2 | 'black', 3 | 'brown', 4 | 'red', 5 | 'orange', 6 | 'yellow', 7 | 'green', 8 | 'blue', 9 | 'violet', 10 | 'grey', 11 | 'white', 12 | ] as const; 13 | 14 | type Color = (typeof COLORS)[number]; 15 | 16 | const isColor = (color: string): color is Color => COLORS.includes(color as Color); 17 | 18 | const validateColors = (colors: readonly string[]): readonly Color[] => { 19 | if (colors.length < 2) { 20 | throw new Error('At least two colors need to be present'); 21 | } 22 | return colors.slice(0, 2).filter(isColor) as readonly Color[]; 23 | }; 24 | 25 | const colorCode = (color: Color) => COLORS.indexOf(color); 26 | 27 | export const decodedValue = (colors: readonly string[]): number => { 28 | const [tens, ones] = validateColors(colors); 29 | return 10 * colorCode(tens) + colorCode(ones); 30 | }; 31 | -------------------------------------------------------------------------------- /typescript/resistor-color-duo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "display": "Configuration for Exercism TypeScript Exercises", 3 | "compilerOptions": { 4 | // Allows you to use the newest syntax, and have access to console.log 5 | // https://www.typescriptlang.org/tsconfig#lib 6 | "lib": ["ES2020", "dom"], 7 | // Make sure typescript is configured to output ESM 8 | // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm 9 | "module": "Node16", 10 | // Since this project is using babel, TypeScript may target something very 11 | // high, and babel will make sure it runs on your local Node version. 12 | // https://babeljs.io/docs/en/ 13 | "target": "ES2020", // ESLint doesn't support this yet: "es2022", 14 | 15 | "strict": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "forceConsistentCasingInFileNames": true, 19 | 20 | // Because jest-resolve isn't like node resolve, the absolute path must be .ts 21 | "allowImportingTsExtensions": true, 22 | "noEmit": true, 23 | 24 | // Because we'll be using babel: ensure that Babel can safely transpile 25 | // files in the TypeScript project. 26 | // 27 | // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats 28 | "isolatedModules": true 29 | }, 30 | "include": [ 31 | "*.ts", 32 | "*.tsx", 33 | ".meta/*.ts", 34 | ".meta/*.tsx", 35 | "__typetests__/*.tst.ts" 36 | ], 37 | "exclude": ["node_modules"] 38 | } 39 | -------------------------------------------------------------------------------- /typescript/resistor-color-trio/.exercism/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "rodmagaldi" 4 | ], 5 | "contributors": [ 6 | "SleeplessByte" 7 | ], 8 | "files": { 9 | "solution": [ 10 | "resistor-color-trio.ts" 11 | ], 12 | "test": [ 13 | "resistor-color-trio.test.ts" 14 | ], 15 | "example": [ 16 | ".meta/proof.ci.ts" 17 | ] 18 | }, 19 | "blurb": "Convert color codes, as used on resistors, to a human-readable label.", 20 | "custom": { 21 | "version.tests.compatibility": "jest-29", 22 | "flag.tests.task-per-describe": false, 23 | "flag.tests.may-run-long": false, 24 | "flag.tests.includes-optional": false, 25 | "flag.tests.jest": true, 26 | "flag.tests.tstyche": false 27 | }, 28 | "source": "Maud de Vries, Erik Schierboom", 29 | "source_url": "https://github.com/exercism/problem-specifications/issues/1549" 30 | } 31 | -------------------------------------------------------------------------------- /typescript/resistor-color-trio/.exercism/metadata.json: -------------------------------------------------------------------------------- 1 | {"track":"typescript","exercise":"resistor-color-trio","id":"3006f984ceb4462598583ad8f3e7f301","url":"https://exercism.org/tracks/typescript/exercises/resistor-color-trio","handle":"yosevu","is_requester":true,"auto_approve":false} -------------------------------------------------------------------------------- /typescript/resistor-color-trio/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "arcanis.vscode-zipfs", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /typescript/resistor-color-trio/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["exercism"], 3 | "search.exclude": { 4 | "**/.yarn": true, 5 | "**/.pnp.*": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /typescript/resistor-color-trio/.yarn/install-state.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yosevu/practicing-functional-typescript/f8eee201a26b5488e69962a5e0735a29a8440a7a/typescript/resistor-color-trio/.yarn/install-state.gz -------------------------------------------------------------------------------- /typescript/resistor-color-trio/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: true 4 | -------------------------------------------------------------------------------- /typescript/resistor-color-trio/HELP.md: -------------------------------------------------------------------------------- 1 | # Help 2 | 3 | ## Running the tests 4 | 5 | Before trying to execute the tests, ensure the assignment folder is set-up correctly by following the installation steps, namely `corepack yarn install` and the Editor SDK setup. 6 | 7 | Execute the tests with: 8 | 9 | ```bash 10 | $ corepack yarn test 11 | ``` 12 | 13 | ## Skipped tests 14 | 15 | In the test suites all tests but the first have been skipped. 16 | 17 | Once you get a test passing, you can enable the next one by changing `xit` to `it`. 18 | Additionally tests may be grouped using `xdescribe`. 19 | Enable the group by changing that to `describe`. 20 | Finally, some exercises may have optional tests `it.skip`. 21 | Remove `.skip` to execute the optional test. 22 | 23 | ## Submitting your solution 24 | 25 | You can submit your solution using the `exercism submit resistor-color-trio.ts` command. 26 | This command will upload your solution to the Exercism website and print the solution page's URL. 27 | 28 | It's possible to submit an incomplete solution which allows you to: 29 | 30 | - See how others have completed the exercise 31 | - Request help from a mentor 32 | 33 | ## Need to get help? 34 | 35 | If you'd like help solving the exercise, check the following pages: 36 | 37 | - The [TypeScript track's documentation](https://exercism.org/docs/tracks/typescript) 38 | - The [TypeScript track's programming category on the forum](https://forum.exercism.org/c/programming/typescript) 39 | - [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) 40 | - The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) 41 | 42 | Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. 43 | 44 | To get help if you're having trouble, you can use one of the following resources: 45 | 46 | - [TypeScript QuickStart](https://www.typescriptlang.org/docs/handbook/release-notes/overview.html) 47 | - [ECMAScript 2015 Language Specification](https://www.ecma-international.org/wp-content/uploads/ECMA-262_6th_edition_june_2015.pdf) (pdf) 48 | - [Mozilla JavaScript Reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference) 49 | - [/r/typescript](https://www.reddit.com/r/typescript) is the TypeScript subreddit. 50 | - [StackOverflow](https://stackoverflow.com/questions/tagged/typescript) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions. -------------------------------------------------------------------------------- /typescript/resistor-color-trio/README.md: -------------------------------------------------------------------------------- 1 | # Resistor Color Trio 2 | 3 | Welcome to Resistor Color Trio on Exercism's TypeScript Track. 4 | If you need help running the tests or submitting your code, check out `HELP.md`. 5 | 6 | ## Instructions 7 | 8 | If you want to build something using a Raspberry Pi, you'll probably use _resistors_. 9 | For this exercise, you need to know only three things about them: 10 | 11 | - Each resistor has a resistance value. 12 | - Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. 13 | To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values. 14 | - Each band acts as a digit of a number. 15 | For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15. 16 | In this exercise, you are going to create a helpful program so that you don't have to remember the values of the bands. 17 | The program will take 3 colors as input, and outputs the correct value, in ohms. 18 | The color bands are encoded as follows: 19 | 20 | - black: 0 21 | - brown: 1 22 | - red: 2 23 | - orange: 3 24 | - yellow: 4 25 | - green: 5 26 | - blue: 6 27 | - violet: 7 28 | - grey: 8 29 | - white: 9 30 | 31 | In Resistor Color Duo you decoded the first two colors. 32 | For instance: orange-orange got the main value `33`. 33 | The third color stands for how many zeros need to be added to the main value. 34 | The main value plus the zeros gives us a value in ohms. 35 | For the exercise it doesn't matter what ohms really are. 36 | For example: 37 | 38 | - orange-orange-black would be 33 and no zeros, which becomes 33 ohms. 39 | - orange-orange-red would be 33 and 2 zeros, which becomes 3300 ohms. 40 | - orange-orange-orange would be 33 and 3 zeros, which becomes 33000 ohms. 41 | 42 | (If Math is your thing, you may want to think of the zeros as exponents of 10. 43 | If Math is not your thing, go with the zeros. 44 | It really is the same thing, just in plain English instead of Math lingo.) 45 | 46 | This exercise is about translating the colors into a label: 47 | 48 | > "... ohms" 49 | 50 | So an input of `"orange", "orange", "black"` should return: 51 | 52 | > "33 ohms" 53 | 54 | When we get to larger resistors, a [metric prefix][metric-prefix] is used to indicate a larger magnitude of ohms, such as "kiloohms". 55 | That is similar to saying "2 kilometers" instead of "2000 meters", or "2 kilograms" for "2000 grams". 56 | 57 | For example, an input of `"orange", "orange", "orange"` should return: 58 | 59 | > "33 kiloohms" 60 | 61 | [metric-prefix]: https://en.wikipedia.org/wiki/Metric_prefix 62 | 63 | ## Source 64 | 65 | ### Created by 66 | 67 | - @rodmagaldi 68 | 69 | ### Contributed to by 70 | 71 | - @SleeplessByte 72 | 73 | ### Based on 74 | 75 | Maud de Vries, Erik Schierboom - https://github.com/exercism/problem-specifications/issues/1549 -------------------------------------------------------------------------------- /typescript/resistor-color-trio/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.37' }]], 3 | plugins: [], 4 | } 5 | -------------------------------------------------------------------------------- /typescript/resistor-color-trio/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import tsEslint from 'typescript-eslint' 4 | import config from '@exercism/eslint-config-typescript' 5 | import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' 6 | 7 | export default [ 8 | ...tsEslint.config(...config, { 9 | files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], 10 | extends: maintainersConfig, 11 | }), 12 | { 13 | ignores: [ 14 | // # Protected or generated 15 | '.git/**/*', 16 | '.vscode/**/*', 17 | 18 | //# When using npm 19 | 'node_modules/**/*', 20 | 21 | // # Configuration files 22 | 'babel.config.cjs', 23 | 'jest.config.cjs', 24 | ], 25 | }, 26 | ] 27 | -------------------------------------------------------------------------------- /typescript/resistor-color-trio/jest.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | projects: [''], 4 | testMatch: [ 5 | '**/__tests__/**/*.[jt]s?(x)', 6 | '**/test/**/*.[jt]s?(x)', 7 | '**/?(*.)+(spec|test).[jt]s?(x)', 8 | ], 9 | testPathIgnorePatterns: [ 10 | '/(?:production_)?node_modules/', 11 | '.d.ts$', 12 | '/test/fixtures', 13 | '/test/helpers', 14 | '__mocks__', 15 | ], 16 | transform: { 17 | '^.+\\.[jt]sx?$': 'babel-jest', 18 | }, 19 | moduleNameMapper: { 20 | '^(\\.\\/.+)\\.js$': '$1', 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /typescript/resistor-color-trio/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@exercism/typescript-resistor-color-trio", 3 | "version": "1.0.0", 4 | "description": "Exercism practice exercise on resistor-color-trio", 5 | "author": "Katrina Owen", 6 | "contributors": [ 7 | "Derk-Jan Karrenbeld (https://derk-jan.com)" 8 | ], 9 | "private": true, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/exercism/typescript" 13 | }, 14 | "type": "module", 15 | "engines": { 16 | "node": "^18.16.0 || >=20.0.0" 17 | }, 18 | "devDependencies": { 19 | "@exercism/babel-preset-typescript": "^0.5.0", 20 | "@exercism/eslint-config-typescript": "^0.7.1", 21 | "@jest/globals": "^29.7.0", 22 | "@types/node": "~22.0.2", 23 | "babel-jest": "^29.7.0", 24 | "core-js": "~3.37.1", 25 | "eslint": "^9.8.0", 26 | "expect": "^29.7.0", 27 | "jest": "^29.7.0", 28 | "prettier": "^3.3.3", 29 | "tstyche": "^2.1.1", 30 | "typescript": "~5.5.4", 31 | "typescript-eslint": "^7.18.0" 32 | }, 33 | "scripts": { 34 | "test": "corepack yarn node test-runner.mjs", 35 | "test:types": "corepack yarn tstyche", 36 | "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", 37 | "lint": "corepack yarn lint:types && corepack yarn lint:ci", 38 | "lint:types": "corepack yarn tsc --noEmit -p .", 39 | "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" 40 | }, 41 | "packageManager": "yarn@4.3.1" 42 | } 43 | -------------------------------------------------------------------------------- /typescript/resistor-color-trio/resistor-color-trio.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from '@jest/globals' 2 | import { decodedResistorValue } from './resistor-color-trio.ts' 3 | 4 | describe('Resistor Colors', () => { 5 | it('Orange and orange and black', () => { 6 | expect(decodedResistorValue(['orange', 'orange', 'black'])).toEqual( 7 | '33 ohms' 8 | ) 9 | }) 10 | 11 | it('Blue and grey and brown', () => { 12 | expect(decodedResistorValue(['blue', 'grey', 'brown'])).toEqual('680 ohms') 13 | }) 14 | 15 | it('Red and black and red', () => { 16 | expect(decodedResistorValue(['red', 'black', 'red'])).toEqual('2 kiloohms') 17 | }) 18 | 19 | it('Green and brown and orange', () => { 20 | expect(decodedResistorValue(['green', 'brown', 'orange'])).toEqual( 21 | '51 kiloohms' 22 | ) 23 | }) 24 | 25 | it('Yellow and violet and yellow', () => { 26 | expect(decodedResistorValue(['yellow', 'violet', 'yellow'])).toEqual( 27 | '470 kiloohms' 28 | ) 29 | }) 30 | 31 | it('Blue and violet and blue', () => { 32 | expect(decodedResistorValue(['blue', 'violet', 'blue'])).toEqual( 33 | '67 megaohms' 34 | ) 35 | }) 36 | 37 | it('Minimum possible value', () => { 38 | expect(decodedResistorValue(['black', 'black', 'black'])).toEqual('0 ohms') 39 | }) 40 | 41 | it('Maximum possible value', () => { 42 | expect(decodedResistorValue(['white', 'white', 'white'])).toEqual( 43 | '99 gigaohms' 44 | ) 45 | }) 46 | 47 | it('First two colors make an invalid octal number', () => { 48 | expect(decodedResistorValue(['black', 'grey', 'black'])).toEqual('8 ohms') 49 | }) 50 | 51 | it('Ignore extra colors', () => { 52 | expect(decodedResistorValue(['blue', 'green', 'yellow', 'orange'])).toEqual( 53 | '650 kiloohms' 54 | ) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /typescript/resistor-color-trio/resistor-color-trio.ts: -------------------------------------------------------------------------------- 1 | const COLORS = [ 2 | 'black', 3 | 'brown', 4 | 'red', 5 | 'orange', 6 | 'yellow', 7 | 'green', 8 | 'blue', 9 | 'violet', 10 | 'grey', 11 | 'white', 12 | ] as const; 13 | 14 | const METRIC_PREFIXES = ['ohms', 'kiloohms', 'megaohms', 'gigaohms'] as const; 15 | 16 | type Color = (typeof COLORS)[number]; 17 | 18 | const colorCode = (color: Color): number => COLORS.indexOf(color); 19 | 20 | export function decodedResistorValue(colors: readonly string[]): string { 21 | const validColors = colors.slice(0, 3).filter((color): color is Color => COLORS.includes(color as Color)); 22 | const [tens, ones, zeros] = validColors.map(colorCode); 23 | 24 | const baseValue = tens * 10 + ones; 25 | const magnitude = baseValue * Math.pow(10, zeros); 26 | 27 | if (magnitude === 0) { 28 | return '0 ohms'; 29 | } 30 | 31 | const exponent = Math.floor(Math.log10(magnitude) / 3); 32 | const adjustedValue = magnitude / Math.pow(10, exponent * 3); 33 | 34 | return `${adjustedValue} ${METRIC_PREFIXES[exponent]}`; 35 | } 36 | -------------------------------------------------------------------------------- /typescript/resistor-color-trio/test-runner.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * 👋🏽 Hello there reader, 5 | * 6 | * It looks like you are working on this solution using the Exercism CLI and 7 | * not the online editor. That's great! The file you are looking at executes 8 | * the various steps the online test-runner also takes. 9 | * 10 | * @see https://github.com/exercism/typescript-test-runner 11 | * 12 | * TypeScript track exercises generally consist of at least two out of three 13 | * types of tests to run. 14 | * 15 | * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid 16 | * 2. tstyche, static analysis tests to see if the types used are expected 17 | * 3. jest, runtime implementation tests to see if the solution is correct 18 | * 19 | * If one of these three fails, this script terminates with -1, -2, or -3 20 | * respectively. If it succeeds, it terminates with exit code 0. 21 | * 22 | * @note you need corepack (bundled with node LTS) enabled in order for this 23 | * test runner to work as expected. Follow the installation and test 24 | * instructions if you see errors about corepack or pnp. 25 | */ 26 | 27 | import { execSync } from 'node:child_process' 28 | import { existsSync, readFileSync } from 'node:fs' 29 | import { exit } from 'node:process' 30 | import { URL } from 'node:url' 31 | 32 | /** 33 | * Before executing any tests, the test runner attempts to find the 34 | * exercise config.json file which has metadata about which types of tests 35 | * to run for this solution. 36 | */ 37 | const metaDirectory = new URL('./.meta/', import.meta.url) 38 | const exercismDirectory = new URL('./.exercism/', import.meta.url) 39 | const configDirectory = existsSync(metaDirectory) 40 | ? metaDirectory 41 | : existsSync(exercismDirectory) 42 | ? exercismDirectory 43 | : null 44 | 45 | if (configDirectory === null) { 46 | throw new Error( 47 | 'Expected .meta or .exercism directory to exist, but I cannot find it.' 48 | ) 49 | } 50 | 51 | const configFile = new URL('./config.json', configDirectory) 52 | if (!existsSync(configFile)) { 53 | throw new Error('Expected config.json to exist at ' + configFile.toString()) 54 | } 55 | 56 | // Experimental: import config from './config.json' with { type: 'json' } 57 | /** @type {import('./config.json') } */ 58 | const config = JSON.parse(readFileSync(configFile)) 59 | 60 | const jest = !config.custom || config.custom['flag.tests.jest'] 61 | const tstyche = config.custom?.['flag.tests.tstyche'] 62 | console.log( 63 | `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` 64 | ) 65 | 66 | /** 67 | * 1. tsc: the typescript compiler 68 | */ 69 | try { 70 | console.log('[tests] tsc (compile)') 71 | execSync('corepack yarn lint:types', { 72 | stdio: 'inherit', 73 | cwd: process.cwd(), 74 | }) 75 | } catch { 76 | exit(-1) 77 | } 78 | 79 | /** 80 | * 2. tstyche: type tests 81 | */ 82 | if (tstyche) { 83 | try { 84 | console.log('[tests] tstyche (type tests)') 85 | execSync('corepack yarn test:types', { 86 | stdio: 'inherit', 87 | cwd: process.cwd(), 88 | }) 89 | } catch { 90 | exit(-2) 91 | } 92 | } 93 | 94 | /** 95 | * 3. jest: implementation tests 96 | */ 97 | if (jest) { 98 | try { 99 | console.log('[tests] tstyche (implementation tests)') 100 | execSync('corepack yarn test:implementation', { 101 | stdio: 'inherit', 102 | cwd: process.cwd(), 103 | }) 104 | } catch { 105 | exit(-3) 106 | } 107 | } 108 | 109 | /** 110 | * Done! 🥳 111 | */ 112 | -------------------------------------------------------------------------------- /typescript/resistor-color-trio/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "display": "Configuration for Exercism TypeScript Exercises", 3 | "compilerOptions": { 4 | // Allows you to use the newest syntax, and have access to console.log 5 | // https://www.typescriptlang.org/tsconfig#lib 6 | "lib": ["ES2020", "dom"], 7 | // Make sure typescript is configured to output ESM 8 | // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm 9 | "module": "Node16", 10 | // Since this project is using babel, TypeScript may target something very 11 | // high, and babel will make sure it runs on your local Node version. 12 | // https://babeljs.io/docs/en/ 13 | "target": "ES2020", // ESLint doesn't support this yet: "es2022", 14 | 15 | "strict": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "forceConsistentCasingInFileNames": true, 19 | 20 | // Because jest-resolve isn't like node resolve, the absolute path must be .ts 21 | "allowImportingTsExtensions": true, 22 | "noEmit": true, 23 | 24 | // Because we'll be using babel: ensure that Babel can safely transpile 25 | // files in the TypeScript project. 26 | // 27 | // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats 28 | "isolatedModules": true 29 | }, 30 | "include": [ 31 | "*.ts", 32 | "*.tsx", 33 | ".meta/*.ts", 34 | ".meta/*.tsx", 35 | "__typetests__/*.tst.ts" 36 | ], 37 | "exclude": ["node_modules"] 38 | } 39 | -------------------------------------------------------------------------------- /typescript/resistor-color/.exercism/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Roshanjossey" 4 | ], 5 | "contributors": [ 6 | "SleeplessByte" 7 | ], 8 | "files": { 9 | "solution": [ 10 | "resistor-color.ts" 11 | ], 12 | "test": [ 13 | "resistor-color.test.ts" 14 | ], 15 | "example": [ 16 | ".meta/proof.ci.ts" 17 | ] 18 | }, 19 | "blurb": "Convert a resistor band's color to its numeric representation.", 20 | "custom": { 21 | "version.tests.compatibility": "jest-29", 22 | "flag.tests.task-per-describe": false, 23 | "flag.tests.may-run-long": false, 24 | "flag.tests.includes-optional": false, 25 | "flag.tests.jest": true, 26 | "flag.tests.tstyche": false 27 | }, 28 | "source": "Maud de Vries, Erik Schierboom", 29 | "source_url": "https://github.com/exercism/problem-specifications/issues/1458" 30 | } 31 | -------------------------------------------------------------------------------- /typescript/resistor-color/.exercism/metadata.json: -------------------------------------------------------------------------------- 1 | {"track":"typescript","exercise":"resistor-color","id":"8df814c69d7b43278bcae5552902d0b0","url":"https://exercism.org/tracks/typescript/exercises/resistor-color","handle":"yosevu","is_requester":true,"auto_approve":false} -------------------------------------------------------------------------------- /typescript/resistor-color/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "arcanis.vscode-zipfs", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /typescript/resistor-color/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["exercism"], 3 | "search.exclude": { 4 | "**/.yarn": true, 5 | "**/.pnp.*": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /typescript/resistor-color/.yarn/install-state.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yosevu/practicing-functional-typescript/f8eee201a26b5488e69962a5e0735a29a8440a7a/typescript/resistor-color/.yarn/install-state.gz -------------------------------------------------------------------------------- /typescript/resistor-color/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: true 4 | -------------------------------------------------------------------------------- /typescript/resistor-color/HELP.md: -------------------------------------------------------------------------------- 1 | # Help 2 | 3 | ## Running the tests 4 | 5 | Before trying to execute the tests, ensure the assignment folder is set-up correctly by following the installation steps, namely `corepack yarn install` and the Editor SDK setup. 6 | 7 | Execute the tests with: 8 | 9 | ```bash 10 | $ corepack yarn test 11 | ``` 12 | 13 | ## Skipped tests 14 | 15 | In the test suites all tests but the first have been skipped. 16 | 17 | Once you get a test passing, you can enable the next one by changing `xit` to `it`. 18 | Additionally tests may be grouped using `xdescribe`. 19 | Enable the group by changing that to `describe`. 20 | Finally, some exercises may have optional tests `it.skip`. 21 | Remove `.skip` to execute the optional test. 22 | 23 | ## Submitting your solution 24 | 25 | You can submit your solution using the `exercism submit resistor-color.ts` command. 26 | This command will upload your solution to the Exercism website and print the solution page's URL. 27 | 28 | It's possible to submit an incomplete solution which allows you to: 29 | 30 | - See how others have completed the exercise 31 | - Request help from a mentor 32 | 33 | ## Need to get help? 34 | 35 | If you'd like help solving the exercise, check the following pages: 36 | 37 | - The [TypeScript track's documentation](https://exercism.org/docs/tracks/typescript) 38 | - The [TypeScript track's programming category on the forum](https://forum.exercism.org/c/programming/typescript) 39 | - [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) 40 | - The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) 41 | 42 | Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. 43 | 44 | To get help if you're having trouble, you can use one of the following resources: 45 | 46 | - [TypeScript QuickStart](https://www.typescriptlang.org/docs/handbook/release-notes/overview.html) 47 | - [ECMAScript 2015 Language Specification](https://www.ecma-international.org/wp-content/uploads/ECMA-262_6th_edition_june_2015.pdf) (pdf) 48 | - [Mozilla JavaScript Reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference) 49 | - [/r/typescript](https://www.reddit.com/r/typescript) is the TypeScript subreddit. 50 | - [StackOverflow](https://stackoverflow.com/questions/tagged/typescript) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions. -------------------------------------------------------------------------------- /typescript/resistor-color/README.md: -------------------------------------------------------------------------------- 1 | # Resistor Color 2 | 3 | Welcome to Resistor Color on Exercism's TypeScript Track. 4 | If you need help running the tests or submitting your code, check out `HELP.md`. 5 | 6 | ## Instructions 7 | 8 | If you want to build something using a Raspberry Pi, you'll probably use _resistors_. 9 | For this exercise, you need to know two things about them: 10 | 11 | - Each resistor has a resistance value. 12 | - Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. 13 | 14 | To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values. 15 | Each band has a position and a numeric value. 16 | 17 | The first 2 bands of a resistor have a simple encoding scheme: each color maps to a single number. 18 | 19 | In this exercise you are going to create a helpful program so that you don't have to remember the values of the bands. 20 | 21 | These colors are encoded as follows: 22 | 23 | - black: 0 24 | - brown: 1 25 | - red: 2 26 | - orange: 3 27 | - yellow: 4 28 | - green: 5 29 | - blue: 6 30 | - violet: 7 31 | - grey: 8 32 | - white: 9 33 | 34 | The goal of this exercise is to create a way: 35 | 36 | - to look up the numerical value associated with a particular color band 37 | - to list the different band colors 38 | 39 | Mnemonics map the colors to the numbers, that, when stored as an array, happen to map to their index in the array: 40 | Better Be Right Or Your Great Big Values Go Wrong. 41 | 42 | More information on the color encoding of resistors can be found in the [Electronic color code Wikipedia article][e-color-code]. 43 | 44 | [e-color-code]: https://en.wikipedia.org/wiki/Electronic_color_code 45 | 46 | ## Source 47 | 48 | ### Created by 49 | 50 | - @Roshanjossey 51 | 52 | ### Contributed to by 53 | 54 | - @SleeplessByte 55 | 56 | ### Based on 57 | 58 | Maud de Vries, Erik Schierboom - https://github.com/exercism/problem-specifications/issues/1458 -------------------------------------------------------------------------------- /typescript/resistor-color/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.37' }]], 3 | plugins: [], 4 | } 5 | -------------------------------------------------------------------------------- /typescript/resistor-color/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import tsEslint from 'typescript-eslint' 4 | import config from '@exercism/eslint-config-typescript' 5 | import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' 6 | 7 | export default [ 8 | ...tsEslint.config(...config, { 9 | files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], 10 | extends: maintainersConfig, 11 | }), 12 | { 13 | ignores: [ 14 | // # Protected or generated 15 | '.git/**/*', 16 | '.vscode/**/*', 17 | 18 | //# When using npm 19 | 'node_modules/**/*', 20 | 21 | // # Configuration files 22 | 'babel.config.cjs', 23 | 'jest.config.cjs', 24 | ], 25 | }, 26 | ] 27 | -------------------------------------------------------------------------------- /typescript/resistor-color/jest.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | projects: [''], 4 | testMatch: [ 5 | '**/__tests__/**/*.[jt]s?(x)', 6 | '**/test/**/*.[jt]s?(x)', 7 | '**/?(*.)+(spec|test).[jt]s?(x)', 8 | ], 9 | testPathIgnorePatterns: [ 10 | '/(?:production_)?node_modules/', 11 | '.d.ts$', 12 | '/test/fixtures', 13 | '/test/helpers', 14 | '__mocks__', 15 | ], 16 | transform: { 17 | '^.+\\.[jt]sx?$': 'babel-jest', 18 | }, 19 | moduleNameMapper: { 20 | '^(\\.\\/.+)\\.js$': '$1', 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /typescript/resistor-color/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@exercism/typescript-resistor-color", 3 | "version": "1.0.0", 4 | "description": "Exercism exercises in Typescript.", 5 | "private": true, 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/exercism/typescript" 9 | }, 10 | "type": "module", 11 | "engines": { 12 | "node": "^18.16.0 || >=20.0.0" 13 | }, 14 | "devDependencies": { 15 | "@exercism/babel-preset-typescript": "^0.5.0", 16 | "@exercism/eslint-config-typescript": "^0.7.1", 17 | "@jest/globals": "^29.7.0", 18 | "@types/node": "~22.0.2", 19 | "babel-jest": "^29.7.0", 20 | "core-js": "~3.37.1", 21 | "eslint": "^9.8.0", 22 | "expect": "^29.7.0", 23 | "jest": "^29.7.0", 24 | "prettier": "^3.3.3", 25 | "tstyche": "^2.1.1", 26 | "typescript": "~5.5.4", 27 | "typescript-eslint": "^7.18.0" 28 | }, 29 | "scripts": { 30 | "test": "corepack yarn node test-runner.mjs", 31 | "test:types": "corepack yarn tstyche", 32 | "test:implementation": "corepack yarn jest --no-cache --passWithNoTests", 33 | "lint": "corepack yarn lint:types && corepack yarn lint:ci", 34 | "lint:types": "corepack yarn tsc --noEmit -p .", 35 | "lint:ci": "corepack yarn eslint . --ext .tsx,.ts" 36 | }, 37 | "packageManager": "yarn@4.3.1" 38 | } 39 | -------------------------------------------------------------------------------- /typescript/resistor-color/resistor-color.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from '@jest/globals' 2 | import { colorCode, COLORS } from './resistor-color.ts' 3 | 4 | describe('color code', () => { 5 | it('Black', () => { 6 | expect(colorCode('black')).toEqual(0) 7 | }) 8 | 9 | it('White', () => { 10 | expect(colorCode('white')).toEqual(9) 11 | }) 12 | 13 | it('Orange', () => { 14 | expect(colorCode('orange')).toEqual(3) 15 | }) 16 | }) 17 | 18 | describe('Colors', () => { 19 | it('returns all colors', () => { 20 | expect(COLORS).toEqual([ 21 | 'black', 22 | 'brown', 23 | 'red', 24 | 'orange', 25 | 'yellow', 26 | 'green', 27 | 'blue', 28 | 'violet', 29 | 'grey', 30 | 'white', 31 | ]) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /typescript/resistor-color/resistor-color.ts: -------------------------------------------------------------------------------- 1 | export const COLOR_NAMES = [ 2 | "black", 3 | "brown", 4 | "red", 5 | "orange", 6 | "yellow", 7 | "green", 8 | "blue", 9 | "violet", 10 | "grey", 11 | "white", 12 | ] as const; 13 | 14 | export const getColorCode = (colorName: typeof COLOR_NAMES[number]) => 15 | COLOR_NAMES.findIndex(name => name === colorName); 16 | -------------------------------------------------------------------------------- /typescript/resistor-color/test-runner.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * 👋🏽 Hello there reader, 5 | * 6 | * It looks like you are working on this solution using the Exercism CLI and 7 | * not the online editor. That's great! The file you are looking at executes 8 | * the various steps the online test-runner also takes. 9 | * 10 | * @see https://github.com/exercism/typescript-test-runner 11 | * 12 | * TypeScript track exercises generally consist of at least two out of three 13 | * types of tests to run. 14 | * 15 | * 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid 16 | * 2. tstyche, static analysis tests to see if the types used are expected 17 | * 3. jest, runtime implementation tests to see if the solution is correct 18 | * 19 | * If one of these three fails, this script terminates with -1, -2, or -3 20 | * respectively. If it succeeds, it terminates with exit code 0. 21 | * 22 | * @note you need corepack (bundled with node LTS) enabled in order for this 23 | * test runner to work as expected. Follow the installation and test 24 | * instructions if you see errors about corepack or pnp. 25 | */ 26 | 27 | import { execSync } from 'node:child_process' 28 | import { existsSync, readFileSync } from 'node:fs' 29 | import { exit } from 'node:process' 30 | import { URL } from 'node:url' 31 | 32 | /** 33 | * Before executing any tests, the test runner attempts to find the 34 | * exercise config.json file which has metadata about which types of tests 35 | * to run for this solution. 36 | */ 37 | const metaDirectory = new URL('./.meta/', import.meta.url) 38 | const exercismDirectory = new URL('./.exercism/', import.meta.url) 39 | const configDirectory = existsSync(metaDirectory) 40 | ? metaDirectory 41 | : existsSync(exercismDirectory) 42 | ? exercismDirectory 43 | : null 44 | 45 | if (configDirectory === null) { 46 | throw new Error( 47 | 'Expected .meta or .exercism directory to exist, but I cannot find it.' 48 | ) 49 | } 50 | 51 | const configFile = new URL('./config.json', configDirectory) 52 | if (!existsSync(configFile)) { 53 | throw new Error('Expected config.json to exist at ' + configFile.toString()) 54 | } 55 | 56 | // Experimental: import config from './config.json' with { type: 'json' } 57 | /** @type {import('./config.json') } */ 58 | const config = JSON.parse(readFileSync(configFile)) 59 | 60 | const jest = !config.custom || config.custom['flag.tests.jest'] 61 | const tstyche = config.custom?.['flag.tests.tstyche'] 62 | console.log( 63 | `[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, ` 64 | ) 65 | 66 | /** 67 | * 1. tsc: the typescript compiler 68 | */ 69 | try { 70 | console.log('[tests] tsc (compile)') 71 | execSync('corepack yarn lint:types', { 72 | stdio: 'inherit', 73 | cwd: process.cwd(), 74 | }) 75 | } catch { 76 | exit(-1) 77 | } 78 | 79 | /** 80 | * 2. tstyche: type tests 81 | */ 82 | if (tstyche) { 83 | try { 84 | console.log('[tests] tstyche (type tests)') 85 | execSync('corepack yarn test:types', { 86 | stdio: 'inherit', 87 | cwd: process.cwd(), 88 | }) 89 | } catch { 90 | exit(-2) 91 | } 92 | } 93 | 94 | /** 95 | * 3. jest: implementation tests 96 | */ 97 | if (jest) { 98 | try { 99 | console.log('[tests] tstyche (implementation tests)') 100 | execSync('corepack yarn test:implementation', { 101 | stdio: 'inherit', 102 | cwd: process.cwd(), 103 | }) 104 | } catch { 105 | exit(-3) 106 | } 107 | } 108 | 109 | /** 110 | * Done! 🥳 111 | */ 112 | -------------------------------------------------------------------------------- /typescript/resistor-color/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "display": "Configuration for Exercism TypeScript Exercises", 3 | "compilerOptions": { 4 | // Allows you to use the newest syntax, and have access to console.log 5 | // https://www.typescriptlang.org/tsconfig#lib 6 | "lib": ["ES2020", "dom"], 7 | // Make sure typescript is configured to output ESM 8 | // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm 9 | "module": "Node16", 10 | // Since this project is using babel, TypeScript may target something very 11 | // high, and babel will make sure it runs on your local Node version. 12 | // https://babeljs.io/docs/en/ 13 | "target": "ES2020", // ESLint doesn't support this yet: "es2022", 14 | 15 | "strict": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "forceConsistentCasingInFileNames": true, 19 | 20 | // Because jest-resolve isn't like node resolve, the absolute path must be .ts 21 | "allowImportingTsExtensions": true, 22 | "noEmit": true, 23 | 24 | // Because we'll be using babel: ensure that Babel can safely transpile 25 | // files in the TypeScript project. 26 | // 27 | // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats 28 | "isolatedModules": true 29 | }, 30 | "include": [ 31 | "*.ts", 32 | "*.tsx", 33 | ".meta/*.ts", 34 | ".meta/*.tsx", 35 | "__typetests__/*.tst.ts" 36 | ], 37 | "exclude": ["node_modules"] 38 | } 39 | -------------------------------------------------------------------------------- /typescript/two-fer/.exercism/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "CRivasGomez" 4 | ], 5 | "contributors": [ 6 | "masters3d", 7 | "SleeplessByte" 8 | ], 9 | "files": { 10 | "solution": [ 11 | "two-fer.ts" 12 | ], 13 | "test": [ 14 | "two-fer.test.ts" 15 | ], 16 | "example": [ 17 | ".meta/proof.ci.ts" 18 | ] 19 | }, 20 | "blurb": "Create a sentence of the form \"One for X, one for me.\".", 21 | "custom": { 22 | "version.tests.compatibility": "jest-29", 23 | "flag.tests.task-per-describe": false, 24 | "flag.tests.may-run-long": false, 25 | "flag.tests.includes-optional": false, 26 | "flag.tests.jest": true, 27 | "flag.tests.tstyche": false 28 | }, 29 | "source_url": "https://github.com/exercism/problem-specifications/issues/757" 30 | } 31 | -------------------------------------------------------------------------------- /typescript/two-fer/.exercism/metadata.json: -------------------------------------------------------------------------------- 1 | {"track":"typescript","exercise":"two-fer","id":"ed19185d2aae4d1b92b0c79a9223289c","url":"https://exercism.org/tracks/typescript/exercises/two-fer","handle":"yosevu","is_requester":true,"auto_approve":false} -------------------------------------------------------------------------------- /typescript/two-fer/.pnp.loader.mjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // @ts-nocheck 3 | 4 | import fs from 'fs'; 5 | import { URL as URL$1, fileURLToPath, pathToFileURL } from 'url'; 6 | import path from 'path'; 7 | import { createHash } from 'crypto'; 8 | import { EOL } from 'os'; 9 | import esmModule, { createRequire, isBuiltin } from 'module'; 10 | import assert from 'assert'; 11 | 12 | const SAFE_TIME = 456789e3; 13 | 14 | const PortablePath = { 15 | root: `/`, 16 | dot: `.`, 17 | parent: `..` 18 | }; 19 | const npath = Object.create(path); 20 | const ppath = Object.create(path.posix); 21 | npath.cwd = () => process.cwd(); 22 | ppath.cwd = process.platform === `win32` ? () => toPortablePath(process.cwd()) : process.cwd; 23 | if (process.platform === `win32`) { 24 | ppath.resolve = (...segments) => { 25 | if (segments.length > 0 && ppath.isAbsolute(segments[0])) { 26 | return path.posix.resolve(...segments); 27 | } else { 28 | return path.posix.resolve(ppath.cwd(), ...segments); 29 | } 30 | }; 31 | } 32 | const contains = function(pathUtils, from, to) { 33 | from = pathUtils.normalize(from); 34 | to = pathUtils.normalize(to); 35 | if (from === to) 36 | return `.`; 37 | if (!from.endsWith(pathUtils.sep)) 38 | from = from + pathUtils.sep; 39 | if (to.startsWith(from)) { 40 | return to.slice(from.length); 41 | } else { 42 | return null; 43 | } 44 | }; 45 | npath.contains = (from, to) => contains(npath, from, to); 46 | ppath.contains = (from, to) => contains(ppath, from, to); 47 | const WINDOWS_PATH_REGEXP = /^([a-zA-Z]:.*)$/; 48 | const UNC_WINDOWS_PATH_REGEXP = /^\/\/(\.\/)?(.*)$/; 49 | const PORTABLE_PATH_REGEXP = /^\/([a-zA-Z]:.*)$/; 50 | const UNC_PORTABLE_PATH_REGEXP = /^\/unc\/(\.dot\/)?(.*)$/; 51 | function fromPortablePathWin32(p) { 52 | let portablePathMatch, uncPortablePathMatch; 53 | if (portablePathMatch = p.match(PORTABLE_PATH_REGEXP)) 54 | p = portablePathMatch[1]; 55 | else if (uncPortablePathMatch = p.match(UNC_PORTABLE_PATH_REGEXP)) 56 | p = `\\\\${uncPortablePathMatch[1] ? `.\\` : ``}${uncPortablePathMatch[2]}`; 57 | else 58 | return p; 59 | return p.replace(/\//g, `\\`); 60 | } 61 | function toPortablePathWin32(p) { 62 | p = p.replace(/\\/g, `/`); 63 | let windowsPathMatch, uncWindowsPathMatch; 64 | if (windowsPathMatch = p.match(WINDOWS_PATH_REGEXP)) 65 | p = `/${windowsPathMatch[1]}`; 66 | else if (uncWindowsPathMatch = p.match(UNC_WINDOWS_PATH_REGEXP)) 67 | p = `/unc/${uncWindowsPathMatch[1] ? `.dot/` : ``}${uncWindowsPathMatch[2]}`; 68 | return p; 69 | } 70 | const toPortablePath = process.platform === `win32` ? toPortablePathWin32 : (p) => p; 71 | const fromPortablePath = process.platform === `win32` ? fromPortablePathWin32 : (p) => p; 72 | npath.fromPortablePath = fromPortablePath; 73 | npath.toPortablePath = toPortablePath; 74 | function convertPath(targetPathUtils, sourcePath) { 75 | return targetPathUtils === npath ? fromPortablePath(sourcePath) : toPortablePath(sourcePath); 76 | } 77 | 78 | const defaultTime = new Date(SAFE_TIME * 1e3); 79 | const defaultTimeMs = defaultTime.getTime(); 80 | async function copyPromise(destinationFs, destination, sourceFs, source, opts) { 81 | const normalizedDestination = destinationFs.pathUtils.normalize(destination); 82 | const normalizedSource = sourceFs.pathUtils.normalize(source); 83 | const prelayout = []; 84 | const postlayout = []; 85 | const { atime, mtime } = opts.stableTime ? { atime: defaultTime, mtime: defaultTime } : await sourceFs.lstatPromise(normalizedSource); 86 | await destinationFs.mkdirpPromise(destinationFs.pathUtils.dirname(destination), { utimes: [atime, mtime] }); 87 | await copyImpl(prelayout, postlayout, destinationFs, normalizedDestination, sourceFs, normalizedSource, { ...opts, didParentExist: true }); 88 | for (const operation of prelayout) 89 | await operation(); 90 | await Promise.all(postlayout.map((operation) => { 91 | return operation(); 92 | })); 93 | } 94 | async function copyImpl(prelayout, postlayout, destinationFs, destination, sourceFs, source, opts) { 95 | const destinationStat = opts.didParentExist ? await maybeLStat(destinationFs, destination) : null; 96 | const sourceStat = await sourceFs.lstatPromise(source); 97 | const { atime, mtime } = opts.stableTime ? { atime: defaultTime, mtime: defaultTime } : sourceStat; 98 | let updated; 99 | switch (true) { 100 | case sourceStat.isDirectory(): 101 | { 102 | updated = await copyFolder(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts); 103 | } 104 | break; 105 | case sourceStat.isFile(): 106 | { 107 | updated = await copyFile(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts); 108 | } 109 | break; 110 | case sourceStat.isSymbolicLink(): 111 | { 112 | updated = await copySymlink(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts); 113 | } 114 | break; 115 | default: { 116 | throw new Error(`Unsupported file type (${sourceStat.mode})`); 117 | } 118 | } 119 | if (opts.linkStrategy?.type !== `HardlinkFromIndex` || !sourceStat.isFile()) { 120 | if (updated || destinationStat?.mtime?.getTime() !== mtime.getTime() || destinationStat?.atime?.getTime() !== atime.getTime()) { 121 | postlayout.push(() => destinationFs.lutimesPromise(destination, atime, mtime)); 122 | updated = true; 123 | } 124 | if (destinationStat === null || (destinationStat.mode & 511) !== (sourceStat.mode & 511)) { 125 | postlayout.push(() => destinationFs.chmodPromise(destination, sourceStat.mode & 511)); 126 | updated = true; 127 | } 128 | } 129 | return updated; 130 | } 131 | async function maybeLStat(baseFs, p) { 132 | try { 133 | return await baseFs.lstatPromise(p); 134 | } catch (e) { 135 | return null; 136 | } 137 | } 138 | async function copyFolder(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts) { 139 | if (destinationStat !== null && !destinationStat.isDirectory()) { 140 | if (opts.overwrite) { 141 | prelayout.push(async () => destinationFs.removePromise(destination)); 142 | destinationStat = null; 143 | } else { 144 | return false; 145 | } 146 | } 147 | let updated = false; 148 | if (destinationStat === null) { 149 | prelayout.push(async () => { 150 | try { 151 | await destinationFs.mkdirPromise(destination, { mode: sourceStat.mode }); 152 | } catch (err) { 153 | if (err.code !== `EEXIST`) { 154 | throw err; 155 | } 156 | } 157 | }); 158 | updated = true; 159 | } 160 | const entries = await sourceFs.readdirPromise(source); 161 | const nextOpts = opts.didParentExist && !destinationStat ? { ...opts, didParentExist: false } : opts; 162 | if (opts.stableSort) { 163 | for (const entry of entries.sort()) { 164 | if (await copyImpl(prelayout, postlayout, destinationFs, destinationFs.pathUtils.join(destination, entry), sourceFs, sourceFs.pathUtils.join(source, entry), nextOpts)) { 165 | updated = true; 166 | } 167 | } 168 | } else { 169 | const entriesUpdateStatus = await Promise.all(entries.map(async (entry) => { 170 | await copyImpl(prelayout, postlayout, destinationFs, destinationFs.pathUtils.join(destination, entry), sourceFs, sourceFs.pathUtils.join(source, entry), nextOpts); 171 | })); 172 | if (entriesUpdateStatus.some((status) => status)) { 173 | updated = true; 174 | } 175 | } 176 | return updated; 177 | } 178 | async function copyFileViaIndex(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts, linkStrategy) { 179 | const sourceHash = await sourceFs.checksumFilePromise(source, { algorithm: `sha1` }); 180 | const defaultMode = 420; 181 | const sourceMode = sourceStat.mode & 511; 182 | const indexFileName = `${sourceHash}${sourceMode !== defaultMode ? sourceMode.toString(8) : ``}`; 183 | const indexPath = destinationFs.pathUtils.join(linkStrategy.indexPath, sourceHash.slice(0, 2), `${indexFileName}.dat`); 184 | let AtomicBehavior; 185 | ((AtomicBehavior2) => { 186 | AtomicBehavior2[AtomicBehavior2["Lock"] = 0] = "Lock"; 187 | AtomicBehavior2[AtomicBehavior2["Rename"] = 1] = "Rename"; 188 | })(AtomicBehavior || (AtomicBehavior = {})); 189 | let atomicBehavior = 1 /* Rename */; 190 | let indexStat = await maybeLStat(destinationFs, indexPath); 191 | if (destinationStat) { 192 | const isDestinationHardlinkedFromIndex = indexStat && destinationStat.dev === indexStat.dev && destinationStat.ino === indexStat.ino; 193 | const isIndexModified = indexStat?.mtimeMs !== defaultTimeMs; 194 | if (isDestinationHardlinkedFromIndex) { 195 | if (isIndexModified && linkStrategy.autoRepair) { 196 | atomicBehavior = 0 /* Lock */; 197 | indexStat = null; 198 | } 199 | } 200 | if (!isDestinationHardlinkedFromIndex) { 201 | if (opts.overwrite) { 202 | prelayout.push(async () => destinationFs.removePromise(destination)); 203 | destinationStat = null; 204 | } else { 205 | return false; 206 | } 207 | } 208 | } 209 | const tempPath = !indexStat && atomicBehavior === 1 /* Rename */ ? `${indexPath}.${Math.floor(Math.random() * 4294967296).toString(16).padStart(8, `0`)}` : null; 210 | let tempPathCleaned = false; 211 | prelayout.push(async () => { 212 | if (!indexStat) { 213 | if (atomicBehavior === 0 /* Lock */) { 214 | await destinationFs.lockPromise(indexPath, async () => { 215 | const content = await sourceFs.readFilePromise(source); 216 | await destinationFs.writeFilePromise(indexPath, content); 217 | }); 218 | } 219 | if (atomicBehavior === 1 /* Rename */ && tempPath) { 220 | const content = await sourceFs.readFilePromise(source); 221 | await destinationFs.writeFilePromise(tempPath, content); 222 | try { 223 | await destinationFs.linkPromise(tempPath, indexPath); 224 | } catch (err) { 225 | if (err.code === `EEXIST`) { 226 | tempPathCleaned = true; 227 | await destinationFs.unlinkPromise(tempPath); 228 | } else { 229 | throw err; 230 | } 231 | } 232 | } 233 | } 234 | if (!destinationStat) { 235 | await destinationFs.linkPromise(indexPath, destination); 236 | } 237 | }); 238 | postlayout.push(async () => { 239 | if (!indexStat) { 240 | await destinationFs.lutimesPromise(indexPath, defaultTime, defaultTime); 241 | if (sourceMode !== defaultMode) { 242 | await destinationFs.chmodPromise(indexPath, sourceMode); 243 | } 244 | } 245 | if (tempPath && !tempPathCleaned) { 246 | await destinationFs.unlinkPromise(tempPath); 247 | } 248 | }); 249 | return false; 250 | } 251 | async function copyFileDirect(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts) { 252 | if (destinationStat !== null) { 253 | if (opts.overwrite) { 254 | prelayout.push(async () => destinationFs.removePromise(destination)); 255 | destinationStat = null; 256 | } else { 257 | return false; 258 | } 259 | } 260 | prelayout.push(async () => { 261 | const content = await sourceFs.readFilePromise(source); 262 | await destinationFs.writeFilePromise(destination, content); 263 | }); 264 | return true; 265 | } 266 | async function copyFile(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts) { 267 | if (opts.linkStrategy?.type === `HardlinkFromIndex`) { 268 | return copyFileViaIndex(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts, opts.linkStrategy); 269 | } else { 270 | return copyFileDirect(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts); 271 | } 272 | } 273 | async function copySymlink(prelayout, postlayout, destinationFs, destination, destinationStat, sourceFs, source, sourceStat, opts) { 274 | if (destinationStat !== null) { 275 | if (opts.overwrite) { 276 | prelayout.push(async () => destinationFs.removePromise(destination)); 277 | destinationStat = null; 278 | } else { 279 | return false; 280 | } 281 | } 282 | prelayout.push(async () => { 283 | await destinationFs.symlinkPromise(convertPath(destinationFs.pathUtils, await sourceFs.readlinkPromise(source)), destination); 284 | }); 285 | return true; 286 | } 287 | 288 | class FakeFS { 289 | constructor(pathUtils) { 290 | this.pathUtils = pathUtils; 291 | } 292 | async *genTraversePromise(init, { stableSort = false } = {}) { 293 | const stack = [init]; 294 | while (stack.length > 0) { 295 | const p = stack.shift(); 296 | const entry = await this.lstatPromise(p); 297 | if (entry.isDirectory()) { 298 | const entries = await this.readdirPromise(p); 299 | if (stableSort) { 300 | for (const entry2 of entries.sort()) { 301 | stack.push(this.pathUtils.join(p, entry2)); 302 | } 303 | } else { 304 | throw new Error(`Not supported`); 305 | } 306 | } else { 307 | yield p; 308 | } 309 | } 310 | } 311 | async checksumFilePromise(path, { algorithm = `sha512` } = {}) { 312 | const fd = await this.openPromise(path, `r`); 313 | try { 314 | const CHUNK_SIZE = 65536; 315 | const chunk = Buffer.allocUnsafeSlow(CHUNK_SIZE); 316 | const hash = createHash(algorithm); 317 | let bytesRead = 0; 318 | while ((bytesRead = await this.readPromise(fd, chunk, 0, CHUNK_SIZE)) !== 0) 319 | hash.update(bytesRead === CHUNK_SIZE ? chunk : chunk.slice(0, bytesRead)); 320 | return hash.digest(`hex`); 321 | } finally { 322 | await this.closePromise(fd); 323 | } 324 | } 325 | async removePromise(p, { recursive = true, maxRetries = 5 } = {}) { 326 | let stat; 327 | try { 328 | stat = await this.lstatPromise(p); 329 | } catch (error) { 330 | if (error.code === `ENOENT`) { 331 | return; 332 | } else { 333 | throw error; 334 | } 335 | } 336 | if (stat.isDirectory()) { 337 | if (recursive) { 338 | const entries = await this.readdirPromise(p); 339 | await Promise.all(entries.map((entry) => { 340 | return this.removePromise(this.pathUtils.resolve(p, entry)); 341 | })); 342 | } 343 | for (let t = 0; t <= maxRetries; t++) { 344 | try { 345 | await this.rmdirPromise(p); 346 | break; 347 | } catch (error) { 348 | if (error.code !== `EBUSY` && error.code !== `ENOTEMPTY`) { 349 | throw error; 350 | } else if (t < maxRetries) { 351 | await new Promise((resolve) => setTimeout(resolve, t * 100)); 352 | } 353 | } 354 | } 355 | } else { 356 | await this.unlinkPromise(p); 357 | } 358 | } 359 | removeSync(p, { recursive = true } = {}) { 360 | let stat; 361 | try { 362 | stat = this.lstatSync(p); 363 | } catch (error) { 364 | if (error.code === `ENOENT`) { 365 | return; 366 | } else { 367 | throw error; 368 | } 369 | } 370 | if (stat.isDirectory()) { 371 | if (recursive) 372 | for (const entry of this.readdirSync(p)) 373 | this.removeSync(this.pathUtils.resolve(p, entry)); 374 | this.rmdirSync(p); 375 | } else { 376 | this.unlinkSync(p); 377 | } 378 | } 379 | async mkdirpPromise(p, { chmod, utimes } = {}) { 380 | p = this.resolve(p); 381 | if (p === this.pathUtils.dirname(p)) 382 | return void 0; 383 | const parts = p.split(this.pathUtils.sep); 384 | let createdDirectory; 385 | for (let u = 2; u <= parts.length; ++u) { 386 | const subPath = parts.slice(0, u).join(this.pathUtils.sep); 387 | if (!this.existsSync(subPath)) { 388 | try { 389 | await this.mkdirPromise(subPath); 390 | } catch (error) { 391 | if (error.code === `EEXIST`) { 392 | continue; 393 | } else { 394 | throw error; 395 | } 396 | } 397 | createdDirectory ??= subPath; 398 | if (chmod != null) 399 | await this.chmodPromise(subPath, chmod); 400 | if (utimes != null) { 401 | await this.utimesPromise(subPath, utimes[0], utimes[1]); 402 | } else { 403 | const parentStat = await this.statPromise(this.pathUtils.dirname(subPath)); 404 | await this.utimesPromise(subPath, parentStat.atime, parentStat.mtime); 405 | } 406 | } 407 | } 408 | return createdDirectory; 409 | } 410 | mkdirpSync(p, { chmod, utimes } = {}) { 411 | p = this.resolve(p); 412 | if (p === this.pathUtils.dirname(p)) 413 | return void 0; 414 | const parts = p.split(this.pathUtils.sep); 415 | let createdDirectory; 416 | for (let u = 2; u <= parts.length; ++u) { 417 | const subPath = parts.slice(0, u).join(this.pathUtils.sep); 418 | if (!this.existsSync(subPath)) { 419 | try { 420 | this.mkdirSync(subPath); 421 | } catch (error) { 422 | if (error.code === `EEXIST`) { 423 | continue; 424 | } else { 425 | throw error; 426 | } 427 | } 428 | createdDirectory ??= subPath; 429 | if (chmod != null) 430 | this.chmodSync(subPath, chmod); 431 | if (utimes != null) { 432 | this.utimesSync(subPath, utimes[0], utimes[1]); 433 | } else { 434 | const parentStat = this.statSync(this.pathUtils.dirname(subPath)); 435 | this.utimesSync(subPath, parentStat.atime, parentStat.mtime); 436 | } 437 | } 438 | } 439 | return createdDirectory; 440 | } 441 | async copyPromise(destination, source, { baseFs = this, overwrite = true, stableSort = false, stableTime = false, linkStrategy = null } = {}) { 442 | return await copyPromise(this, destination, baseFs, source, { overwrite, stableSort, stableTime, linkStrategy }); 443 | } 444 | copySync(destination, source, { baseFs = this, overwrite = true } = {}) { 445 | const stat = baseFs.lstatSync(source); 446 | const exists = this.existsSync(destination); 447 | if (stat.isDirectory()) { 448 | this.mkdirpSync(destination); 449 | const directoryListing = baseFs.readdirSync(source); 450 | for (const entry of directoryListing) { 451 | this.copySync(this.pathUtils.join(destination, entry), baseFs.pathUtils.join(source, entry), { baseFs, overwrite }); 452 | } 453 | } else if (stat.isFile()) { 454 | if (!exists || overwrite) { 455 | if (exists) 456 | this.removeSync(destination); 457 | const content = baseFs.readFileSync(source); 458 | this.writeFileSync(destination, content); 459 | } 460 | } else if (stat.isSymbolicLink()) { 461 | if (!exists || overwrite) { 462 | if (exists) 463 | this.removeSync(destination); 464 | const target = baseFs.readlinkSync(source); 465 | this.symlinkSync(convertPath(this.pathUtils, target), destination); 466 | } 467 | } else { 468 | throw new Error(`Unsupported file type (file: ${source}, mode: 0o${stat.mode.toString(8).padStart(6, `0`)})`); 469 | } 470 | const mode = stat.mode & 511; 471 | this.chmodSync(destination, mode); 472 | } 473 | async changeFilePromise(p, content, opts = {}) { 474 | if (Buffer.isBuffer(content)) { 475 | return this.changeFileBufferPromise(p, content, opts); 476 | } else { 477 | return this.changeFileTextPromise(p, content, opts); 478 | } 479 | } 480 | async changeFileBufferPromise(p, content, { mode } = {}) { 481 | let current = Buffer.alloc(0); 482 | try { 483 | current = await this.readFilePromise(p); 484 | } catch (error) { 485 | } 486 | if (Buffer.compare(current, content) === 0) 487 | return; 488 | await this.writeFilePromise(p, content, { mode }); 489 | } 490 | async changeFileTextPromise(p, content, { automaticNewlines, mode } = {}) { 491 | let current = ``; 492 | try { 493 | current = await this.readFilePromise(p, `utf8`); 494 | } catch (error) { 495 | } 496 | const normalizedContent = automaticNewlines ? normalizeLineEndings(current, content) : content; 497 | if (current === normalizedContent) 498 | return; 499 | await this.writeFilePromise(p, normalizedContent, { mode }); 500 | } 501 | changeFileSync(p, content, opts = {}) { 502 | if (Buffer.isBuffer(content)) { 503 | return this.changeFileBufferSync(p, content, opts); 504 | } else { 505 | return this.changeFileTextSync(p, content, opts); 506 | } 507 | } 508 | changeFileBufferSync(p, content, { mode } = {}) { 509 | let current = Buffer.alloc(0); 510 | try { 511 | current = this.readFileSync(p); 512 | } catch (error) { 513 | } 514 | if (Buffer.compare(current, content) === 0) 515 | return; 516 | this.writeFileSync(p, content, { mode }); 517 | } 518 | changeFileTextSync(p, content, { automaticNewlines = false, mode } = {}) { 519 | let current = ``; 520 | try { 521 | current = this.readFileSync(p, `utf8`); 522 | } catch (error) { 523 | } 524 | const normalizedContent = automaticNewlines ? normalizeLineEndings(current, content) : content; 525 | if (current === normalizedContent) 526 | return; 527 | this.writeFileSync(p, normalizedContent, { mode }); 528 | } 529 | async movePromise(fromP, toP) { 530 | try { 531 | await this.renamePromise(fromP, toP); 532 | } catch (error) { 533 | if (error.code === `EXDEV`) { 534 | await this.copyPromise(toP, fromP); 535 | await this.removePromise(fromP); 536 | } else { 537 | throw error; 538 | } 539 | } 540 | } 541 | moveSync(fromP, toP) { 542 | try { 543 | this.renameSync(fromP, toP); 544 | } catch (error) { 545 | if (error.code === `EXDEV`) { 546 | this.copySync(toP, fromP); 547 | this.removeSync(fromP); 548 | } else { 549 | throw error; 550 | } 551 | } 552 | } 553 | async lockPromise(affectedPath, callback) { 554 | const lockPath = `${affectedPath}.flock`; 555 | const interval = 1e3 / 60; 556 | const startTime = Date.now(); 557 | let fd = null; 558 | const isAlive = async () => { 559 | let pid; 560 | try { 561 | [pid] = await this.readJsonPromise(lockPath); 562 | } catch (error) { 563 | return Date.now() - startTime < 500; 564 | } 565 | try { 566 | process.kill(pid, 0); 567 | return true; 568 | } catch (error) { 569 | return false; 570 | } 571 | }; 572 | while (fd === null) { 573 | try { 574 | fd = await this.openPromise(lockPath, `wx`); 575 | } catch (error) { 576 | if (error.code === `EEXIST`) { 577 | if (!await isAlive()) { 578 | try { 579 | await this.unlinkPromise(lockPath); 580 | continue; 581 | } catch (error2) { 582 | } 583 | } 584 | if (Date.now() - startTime < 60 * 1e3) { 585 | await new Promise((resolve) => setTimeout(resolve, interval)); 586 | } else { 587 | throw new Error(`Couldn't acquire a lock in a reasonable time (via ${lockPath})`); 588 | } 589 | } else { 590 | throw error; 591 | } 592 | } 593 | } 594 | await this.writePromise(fd, JSON.stringify([process.pid])); 595 | try { 596 | return await callback(); 597 | } finally { 598 | try { 599 | await this.closePromise(fd); 600 | await this.unlinkPromise(lockPath); 601 | } catch (error) { 602 | } 603 | } 604 | } 605 | async readJsonPromise(p) { 606 | const content = await this.readFilePromise(p, `utf8`); 607 | try { 608 | return JSON.parse(content); 609 | } catch (error) { 610 | error.message += ` (in ${p})`; 611 | throw error; 612 | } 613 | } 614 | readJsonSync(p) { 615 | const content = this.readFileSync(p, `utf8`); 616 | try { 617 | return JSON.parse(content); 618 | } catch (error) { 619 | error.message += ` (in ${p})`; 620 | throw error; 621 | } 622 | } 623 | async writeJsonPromise(p, data, { compact = false } = {}) { 624 | const space = compact ? 0 : 2; 625 | return await this.writeFilePromise(p, `${JSON.stringify(data, null, space)} 626 | `); 627 | } 628 | writeJsonSync(p, data, { compact = false } = {}) { 629 | const space = compact ? 0 : 2; 630 | return this.writeFileSync(p, `${JSON.stringify(data, null, space)} 631 | `); 632 | } 633 | async preserveTimePromise(p, cb) { 634 | const stat = await this.lstatPromise(p); 635 | const result = await cb(); 636 | if (typeof result !== `undefined`) 637 | p = result; 638 | await this.lutimesPromise(p, stat.atime, stat.mtime); 639 | } 640 | async preserveTimeSync(p, cb) { 641 | const stat = this.lstatSync(p); 642 | const result = cb(); 643 | if (typeof result !== `undefined`) 644 | p = result; 645 | this.lutimesSync(p, stat.atime, stat.mtime); 646 | } 647 | } 648 | class BasePortableFakeFS extends FakeFS { 649 | constructor() { 650 | super(ppath); 651 | } 652 | } 653 | function getEndOfLine(content) { 654 | const matches = content.match(/\r?\n/g); 655 | if (matches === null) 656 | return EOL; 657 | const crlf = matches.filter((nl) => nl === `\r 658 | `).length; 659 | const lf = matches.length - crlf; 660 | return crlf > lf ? `\r 661 | ` : ` 662 | `; 663 | } 664 | function normalizeLineEndings(originalContent, newContent) { 665 | return newContent.replace(/\r?\n/g, getEndOfLine(originalContent)); 666 | } 667 | 668 | class ProxiedFS extends FakeFS { 669 | getExtractHint(hints) { 670 | return this.baseFs.getExtractHint(hints); 671 | } 672 | resolve(path) { 673 | return this.mapFromBase(this.baseFs.resolve(this.mapToBase(path))); 674 | } 675 | getRealPath() { 676 | return this.mapFromBase(this.baseFs.getRealPath()); 677 | } 678 | async openPromise(p, flags, mode) { 679 | return this.baseFs.openPromise(this.mapToBase(p), flags, mode); 680 | } 681 | openSync(p, flags, mode) { 682 | return this.baseFs.openSync(this.mapToBase(p), flags, mode); 683 | } 684 | async opendirPromise(p, opts) { 685 | return Object.assign(await this.baseFs.opendirPromise(this.mapToBase(p), opts), { path: p }); 686 | } 687 | opendirSync(p, opts) { 688 | return Object.assign(this.baseFs.opendirSync(this.mapToBase(p), opts), { path: p }); 689 | } 690 | async readPromise(fd, buffer, offset, length, position) { 691 | return await this.baseFs.readPromise(fd, buffer, offset, length, position); 692 | } 693 | readSync(fd, buffer, offset, length, position) { 694 | return this.baseFs.readSync(fd, buffer, offset, length, position); 695 | } 696 | async writePromise(fd, buffer, offset, length, position) { 697 | if (typeof buffer === `string`) { 698 | return await this.baseFs.writePromise(fd, buffer, offset); 699 | } else { 700 | return await this.baseFs.writePromise(fd, buffer, offset, length, position); 701 | } 702 | } 703 | writeSync(fd, buffer, offset, length, position) { 704 | if (typeof buffer === `string`) { 705 | return this.baseFs.writeSync(fd, buffer, offset); 706 | } else { 707 | return this.baseFs.writeSync(fd, buffer, offset, length, position); 708 | } 709 | } 710 | async closePromise(fd) { 711 | return this.baseFs.closePromise(fd); 712 | } 713 | closeSync(fd) { 714 | this.baseFs.closeSync(fd); 715 | } 716 | createReadStream(p, opts) { 717 | return this.baseFs.createReadStream(p !== null ? this.mapToBase(p) : p, opts); 718 | } 719 | createWriteStream(p, opts) { 720 | return this.baseFs.createWriteStream(p !== null ? this.mapToBase(p) : p, opts); 721 | } 722 | async realpathPromise(p) { 723 | return this.mapFromBase(await this.baseFs.realpathPromise(this.mapToBase(p))); 724 | } 725 | realpathSync(p) { 726 | return this.mapFromBase(this.baseFs.realpathSync(this.mapToBase(p))); 727 | } 728 | async existsPromise(p) { 729 | return this.baseFs.existsPromise(this.mapToBase(p)); 730 | } 731 | existsSync(p) { 732 | return this.baseFs.existsSync(this.mapToBase(p)); 733 | } 734 | accessSync(p, mode) { 735 | return this.baseFs.accessSync(this.mapToBase(p), mode); 736 | } 737 | async accessPromise(p, mode) { 738 | return this.baseFs.accessPromise(this.mapToBase(p), mode); 739 | } 740 | async statPromise(p, opts) { 741 | return this.baseFs.statPromise(this.mapToBase(p), opts); 742 | } 743 | statSync(p, opts) { 744 | return this.baseFs.statSync(this.mapToBase(p), opts); 745 | } 746 | async fstatPromise(fd, opts) { 747 | return this.baseFs.fstatPromise(fd, opts); 748 | } 749 | fstatSync(fd, opts) { 750 | return this.baseFs.fstatSync(fd, opts); 751 | } 752 | lstatPromise(p, opts) { 753 | return this.baseFs.lstatPromise(this.mapToBase(p), opts); 754 | } 755 | lstatSync(p, opts) { 756 | return this.baseFs.lstatSync(this.mapToBase(p), opts); 757 | } 758 | async fchmodPromise(fd, mask) { 759 | return this.baseFs.fchmodPromise(fd, mask); 760 | } 761 | fchmodSync(fd, mask) { 762 | return this.baseFs.fchmodSync(fd, mask); 763 | } 764 | async chmodPromise(p, mask) { 765 | return this.baseFs.chmodPromise(this.mapToBase(p), mask); 766 | } 767 | chmodSync(p, mask) { 768 | return this.baseFs.chmodSync(this.mapToBase(p), mask); 769 | } 770 | async fchownPromise(fd, uid, gid) { 771 | return this.baseFs.fchownPromise(fd, uid, gid); 772 | } 773 | fchownSync(fd, uid, gid) { 774 | return this.baseFs.fchownSync(fd, uid, gid); 775 | } 776 | async chownPromise(p, uid, gid) { 777 | return this.baseFs.chownPromise(this.mapToBase(p), uid, gid); 778 | } 779 | chownSync(p, uid, gid) { 780 | return this.baseFs.chownSync(this.mapToBase(p), uid, gid); 781 | } 782 | async renamePromise(oldP, newP) { 783 | return this.baseFs.renamePromise(this.mapToBase(oldP), this.mapToBase(newP)); 784 | } 785 | renameSync(oldP, newP) { 786 | return this.baseFs.renameSync(this.mapToBase(oldP), this.mapToBase(newP)); 787 | } 788 | async copyFilePromise(sourceP, destP, flags = 0) { 789 | return this.baseFs.copyFilePromise(this.mapToBase(sourceP), this.mapToBase(destP), flags); 790 | } 791 | copyFileSync(sourceP, destP, flags = 0) { 792 | return this.baseFs.copyFileSync(this.mapToBase(sourceP), this.mapToBase(destP), flags); 793 | } 794 | async appendFilePromise(p, content, opts) { 795 | return this.baseFs.appendFilePromise(this.fsMapToBase(p), content, opts); 796 | } 797 | appendFileSync(p, content, opts) { 798 | return this.baseFs.appendFileSync(this.fsMapToBase(p), content, opts); 799 | } 800 | async writeFilePromise(p, content, opts) { 801 | return this.baseFs.writeFilePromise(this.fsMapToBase(p), content, opts); 802 | } 803 | writeFileSync(p, content, opts) { 804 | return this.baseFs.writeFileSync(this.fsMapToBase(p), content, opts); 805 | } 806 | async unlinkPromise(p) { 807 | return this.baseFs.unlinkPromise(this.mapToBase(p)); 808 | } 809 | unlinkSync(p) { 810 | return this.baseFs.unlinkSync(this.mapToBase(p)); 811 | } 812 | async utimesPromise(p, atime, mtime) { 813 | return this.baseFs.utimesPromise(this.mapToBase(p), atime, mtime); 814 | } 815 | utimesSync(p, atime, mtime) { 816 | return this.baseFs.utimesSync(this.mapToBase(p), atime, mtime); 817 | } 818 | async lutimesPromise(p, atime, mtime) { 819 | return this.baseFs.lutimesPromise(this.mapToBase(p), atime, mtime); 820 | } 821 | lutimesSync(p, atime, mtime) { 822 | return this.baseFs.lutimesSync(this.mapToBase(p), atime, mtime); 823 | } 824 | async mkdirPromise(p, opts) { 825 | return this.baseFs.mkdirPromise(this.mapToBase(p), opts); 826 | } 827 | mkdirSync(p, opts) { 828 | return this.baseFs.mkdirSync(this.mapToBase(p), opts); 829 | } 830 | async rmdirPromise(p, opts) { 831 | return this.baseFs.rmdirPromise(this.mapToBase(p), opts); 832 | } 833 | rmdirSync(p, opts) { 834 | return this.baseFs.rmdirSync(this.mapToBase(p), opts); 835 | } 836 | async rmPromise(p, opts) { 837 | return this.baseFs.rmPromise(this.mapToBase(p), opts); 838 | } 839 | rmSync(p, opts) { 840 | return this.baseFs.rmSync(this.mapToBase(p), opts); 841 | } 842 | async linkPromise(existingP, newP) { 843 | return this.baseFs.linkPromise(this.mapToBase(existingP), this.mapToBase(newP)); 844 | } 845 | linkSync(existingP, newP) { 846 | return this.baseFs.linkSync(this.mapToBase(existingP), this.mapToBase(newP)); 847 | } 848 | async symlinkPromise(target, p, type) { 849 | const mappedP = this.mapToBase(p); 850 | if (this.pathUtils.isAbsolute(target)) 851 | return this.baseFs.symlinkPromise(this.mapToBase(target), mappedP, type); 852 | const mappedAbsoluteTarget = this.mapToBase(this.pathUtils.join(this.pathUtils.dirname(p), target)); 853 | const mappedTarget = this.baseFs.pathUtils.relative(this.baseFs.pathUtils.dirname(mappedP), mappedAbsoluteTarget); 854 | return this.baseFs.symlinkPromise(mappedTarget, mappedP, type); 855 | } 856 | symlinkSync(target, p, type) { 857 | const mappedP = this.mapToBase(p); 858 | if (this.pathUtils.isAbsolute(target)) 859 | return this.baseFs.symlinkSync(this.mapToBase(target), mappedP, type); 860 | const mappedAbsoluteTarget = this.mapToBase(this.pathUtils.join(this.pathUtils.dirname(p), target)); 861 | const mappedTarget = this.baseFs.pathUtils.relative(this.baseFs.pathUtils.dirname(mappedP), mappedAbsoluteTarget); 862 | return this.baseFs.symlinkSync(mappedTarget, mappedP, type); 863 | } 864 | async readFilePromise(p, encoding) { 865 | return this.baseFs.readFilePromise(this.fsMapToBase(p), encoding); 866 | } 867 | readFileSync(p, encoding) { 868 | return this.baseFs.readFileSync(this.fsMapToBase(p), encoding); 869 | } 870 | readdirPromise(p, opts) { 871 | return this.baseFs.readdirPromise(this.mapToBase(p), opts); 872 | } 873 | readdirSync(p, opts) { 874 | return this.baseFs.readdirSync(this.mapToBase(p), opts); 875 | } 876 | async readlinkPromise(p) { 877 | return this.mapFromBase(await this.baseFs.readlinkPromise(this.mapToBase(p))); 878 | } 879 | readlinkSync(p) { 880 | return this.mapFromBase(this.baseFs.readlinkSync(this.mapToBase(p))); 881 | } 882 | async truncatePromise(p, len) { 883 | return this.baseFs.truncatePromise(this.mapToBase(p), len); 884 | } 885 | truncateSync(p, len) { 886 | return this.baseFs.truncateSync(this.mapToBase(p), len); 887 | } 888 | async ftruncatePromise(fd, len) { 889 | return this.baseFs.ftruncatePromise(fd, len); 890 | } 891 | ftruncateSync(fd, len) { 892 | return this.baseFs.ftruncateSync(fd, len); 893 | } 894 | watch(p, a, b) { 895 | return this.baseFs.watch( 896 | this.mapToBase(p), 897 | a, 898 | b 899 | ); 900 | } 901 | watchFile(p, a, b) { 902 | return this.baseFs.watchFile( 903 | this.mapToBase(p), 904 | a, 905 | b 906 | ); 907 | } 908 | unwatchFile(p, cb) { 909 | return this.baseFs.unwatchFile(this.mapToBase(p), cb); 910 | } 911 | fsMapToBase(p) { 912 | if (typeof p === `number`) { 913 | return p; 914 | } else { 915 | return this.mapToBase(p); 916 | } 917 | } 918 | } 919 | 920 | function direntToPortable(dirent) { 921 | const portableDirent = dirent; 922 | if (typeof dirent.path === `string`) 923 | portableDirent.path = npath.toPortablePath(dirent.path); 924 | return portableDirent; 925 | } 926 | class NodeFS extends BasePortableFakeFS { 927 | constructor(realFs = fs) { 928 | super(); 929 | this.realFs = realFs; 930 | } 931 | getExtractHint() { 932 | return false; 933 | } 934 | getRealPath() { 935 | return PortablePath.root; 936 | } 937 | resolve(p) { 938 | return ppath.resolve(p); 939 | } 940 | async openPromise(p, flags, mode) { 941 | return await new Promise((resolve, reject) => { 942 | this.realFs.open(npath.fromPortablePath(p), flags, mode, this.makeCallback(resolve, reject)); 943 | }); 944 | } 945 | openSync(p, flags, mode) { 946 | return this.realFs.openSync(npath.fromPortablePath(p), flags, mode); 947 | } 948 | async opendirPromise(p, opts) { 949 | return await new Promise((resolve, reject) => { 950 | if (typeof opts !== `undefined`) { 951 | this.realFs.opendir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject)); 952 | } else { 953 | this.realFs.opendir(npath.fromPortablePath(p), this.makeCallback(resolve, reject)); 954 | } 955 | }).then((dir) => { 956 | const dirWithFixedPath = dir; 957 | Object.defineProperty(dirWithFixedPath, `path`, { 958 | value: p, 959 | configurable: true, 960 | writable: true 961 | }); 962 | return dirWithFixedPath; 963 | }); 964 | } 965 | opendirSync(p, opts) { 966 | const dir = typeof opts !== `undefined` ? this.realFs.opendirSync(npath.fromPortablePath(p), opts) : this.realFs.opendirSync(npath.fromPortablePath(p)); 967 | const dirWithFixedPath = dir; 968 | Object.defineProperty(dirWithFixedPath, `path`, { 969 | value: p, 970 | configurable: true, 971 | writable: true 972 | }); 973 | return dirWithFixedPath; 974 | } 975 | async readPromise(fd, buffer, offset = 0, length = 0, position = -1) { 976 | return await new Promise((resolve, reject) => { 977 | this.realFs.read(fd, buffer, offset, length, position, (error, bytesRead) => { 978 | if (error) { 979 | reject(error); 980 | } else { 981 | resolve(bytesRead); 982 | } 983 | }); 984 | }); 985 | } 986 | readSync(fd, buffer, offset, length, position) { 987 | return this.realFs.readSync(fd, buffer, offset, length, position); 988 | } 989 | async writePromise(fd, buffer, offset, length, position) { 990 | return await new Promise((resolve, reject) => { 991 | if (typeof buffer === `string`) { 992 | return this.realFs.write(fd, buffer, offset, this.makeCallback(resolve, reject)); 993 | } else { 994 | return this.realFs.write(fd, buffer, offset, length, position, this.makeCallback(resolve, reject)); 995 | } 996 | }); 997 | } 998 | writeSync(fd, buffer, offset, length, position) { 999 | if (typeof buffer === `string`) { 1000 | return this.realFs.writeSync(fd, buffer, offset); 1001 | } else { 1002 | return this.realFs.writeSync(fd, buffer, offset, length, position); 1003 | } 1004 | } 1005 | async closePromise(fd) { 1006 | await new Promise((resolve, reject) => { 1007 | this.realFs.close(fd, this.makeCallback(resolve, reject)); 1008 | }); 1009 | } 1010 | closeSync(fd) { 1011 | this.realFs.closeSync(fd); 1012 | } 1013 | createReadStream(p, opts) { 1014 | const realPath = p !== null ? npath.fromPortablePath(p) : p; 1015 | return this.realFs.createReadStream(realPath, opts); 1016 | } 1017 | createWriteStream(p, opts) { 1018 | const realPath = p !== null ? npath.fromPortablePath(p) : p; 1019 | return this.realFs.createWriteStream(realPath, opts); 1020 | } 1021 | async realpathPromise(p) { 1022 | return await new Promise((resolve, reject) => { 1023 | this.realFs.realpath(npath.fromPortablePath(p), {}, this.makeCallback(resolve, reject)); 1024 | }).then((path) => { 1025 | return npath.toPortablePath(path); 1026 | }); 1027 | } 1028 | realpathSync(p) { 1029 | return npath.toPortablePath(this.realFs.realpathSync(npath.fromPortablePath(p), {})); 1030 | } 1031 | async existsPromise(p) { 1032 | return await new Promise((resolve) => { 1033 | this.realFs.exists(npath.fromPortablePath(p), resolve); 1034 | }); 1035 | } 1036 | accessSync(p, mode) { 1037 | return this.realFs.accessSync(npath.fromPortablePath(p), mode); 1038 | } 1039 | async accessPromise(p, mode) { 1040 | return await new Promise((resolve, reject) => { 1041 | this.realFs.access(npath.fromPortablePath(p), mode, this.makeCallback(resolve, reject)); 1042 | }); 1043 | } 1044 | existsSync(p) { 1045 | return this.realFs.existsSync(npath.fromPortablePath(p)); 1046 | } 1047 | async statPromise(p, opts) { 1048 | return await new Promise((resolve, reject) => { 1049 | if (opts) { 1050 | this.realFs.stat(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject)); 1051 | } else { 1052 | this.realFs.stat(npath.fromPortablePath(p), this.makeCallback(resolve, reject)); 1053 | } 1054 | }); 1055 | } 1056 | statSync(p, opts) { 1057 | if (opts) { 1058 | return this.realFs.statSync(npath.fromPortablePath(p), opts); 1059 | } else { 1060 | return this.realFs.statSync(npath.fromPortablePath(p)); 1061 | } 1062 | } 1063 | async fstatPromise(fd, opts) { 1064 | return await new Promise((resolve, reject) => { 1065 | if (opts) { 1066 | this.realFs.fstat(fd, opts, this.makeCallback(resolve, reject)); 1067 | } else { 1068 | this.realFs.fstat(fd, this.makeCallback(resolve, reject)); 1069 | } 1070 | }); 1071 | } 1072 | fstatSync(fd, opts) { 1073 | if (opts) { 1074 | return this.realFs.fstatSync(fd, opts); 1075 | } else { 1076 | return this.realFs.fstatSync(fd); 1077 | } 1078 | } 1079 | async lstatPromise(p, opts) { 1080 | return await new Promise((resolve, reject) => { 1081 | if (opts) { 1082 | this.realFs.lstat(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject)); 1083 | } else { 1084 | this.realFs.lstat(npath.fromPortablePath(p), this.makeCallback(resolve, reject)); 1085 | } 1086 | }); 1087 | } 1088 | lstatSync(p, opts) { 1089 | if (opts) { 1090 | return this.realFs.lstatSync(npath.fromPortablePath(p), opts); 1091 | } else { 1092 | return this.realFs.lstatSync(npath.fromPortablePath(p)); 1093 | } 1094 | } 1095 | async fchmodPromise(fd, mask) { 1096 | return await new Promise((resolve, reject) => { 1097 | this.realFs.fchmod(fd, mask, this.makeCallback(resolve, reject)); 1098 | }); 1099 | } 1100 | fchmodSync(fd, mask) { 1101 | return this.realFs.fchmodSync(fd, mask); 1102 | } 1103 | async chmodPromise(p, mask) { 1104 | return await new Promise((resolve, reject) => { 1105 | this.realFs.chmod(npath.fromPortablePath(p), mask, this.makeCallback(resolve, reject)); 1106 | }); 1107 | } 1108 | chmodSync(p, mask) { 1109 | return this.realFs.chmodSync(npath.fromPortablePath(p), mask); 1110 | } 1111 | async fchownPromise(fd, uid, gid) { 1112 | return await new Promise((resolve, reject) => { 1113 | this.realFs.fchown(fd, uid, gid, this.makeCallback(resolve, reject)); 1114 | }); 1115 | } 1116 | fchownSync(fd, uid, gid) { 1117 | return this.realFs.fchownSync(fd, uid, gid); 1118 | } 1119 | async chownPromise(p, uid, gid) { 1120 | return await new Promise((resolve, reject) => { 1121 | this.realFs.chown(npath.fromPortablePath(p), uid, gid, this.makeCallback(resolve, reject)); 1122 | }); 1123 | } 1124 | chownSync(p, uid, gid) { 1125 | return this.realFs.chownSync(npath.fromPortablePath(p), uid, gid); 1126 | } 1127 | async renamePromise(oldP, newP) { 1128 | return await new Promise((resolve, reject) => { 1129 | this.realFs.rename(npath.fromPortablePath(oldP), npath.fromPortablePath(newP), this.makeCallback(resolve, reject)); 1130 | }); 1131 | } 1132 | renameSync(oldP, newP) { 1133 | return this.realFs.renameSync(npath.fromPortablePath(oldP), npath.fromPortablePath(newP)); 1134 | } 1135 | async copyFilePromise(sourceP, destP, flags = 0) { 1136 | return await new Promise((resolve, reject) => { 1137 | this.realFs.copyFile(npath.fromPortablePath(sourceP), npath.fromPortablePath(destP), flags, this.makeCallback(resolve, reject)); 1138 | }); 1139 | } 1140 | copyFileSync(sourceP, destP, flags = 0) { 1141 | return this.realFs.copyFileSync(npath.fromPortablePath(sourceP), npath.fromPortablePath(destP), flags); 1142 | } 1143 | async appendFilePromise(p, content, opts) { 1144 | return await new Promise((resolve, reject) => { 1145 | const fsNativePath = typeof p === `string` ? npath.fromPortablePath(p) : p; 1146 | if (opts) { 1147 | this.realFs.appendFile(fsNativePath, content, opts, this.makeCallback(resolve, reject)); 1148 | } else { 1149 | this.realFs.appendFile(fsNativePath, content, this.makeCallback(resolve, reject)); 1150 | } 1151 | }); 1152 | } 1153 | appendFileSync(p, content, opts) { 1154 | const fsNativePath = typeof p === `string` ? npath.fromPortablePath(p) : p; 1155 | if (opts) { 1156 | this.realFs.appendFileSync(fsNativePath, content, opts); 1157 | } else { 1158 | this.realFs.appendFileSync(fsNativePath, content); 1159 | } 1160 | } 1161 | async writeFilePromise(p, content, opts) { 1162 | return await new Promise((resolve, reject) => { 1163 | const fsNativePath = typeof p === `string` ? npath.fromPortablePath(p) : p; 1164 | if (opts) { 1165 | this.realFs.writeFile(fsNativePath, content, opts, this.makeCallback(resolve, reject)); 1166 | } else { 1167 | this.realFs.writeFile(fsNativePath, content, this.makeCallback(resolve, reject)); 1168 | } 1169 | }); 1170 | } 1171 | writeFileSync(p, content, opts) { 1172 | const fsNativePath = typeof p === `string` ? npath.fromPortablePath(p) : p; 1173 | if (opts) { 1174 | this.realFs.writeFileSync(fsNativePath, content, opts); 1175 | } else { 1176 | this.realFs.writeFileSync(fsNativePath, content); 1177 | } 1178 | } 1179 | async unlinkPromise(p) { 1180 | return await new Promise((resolve, reject) => { 1181 | this.realFs.unlink(npath.fromPortablePath(p), this.makeCallback(resolve, reject)); 1182 | }); 1183 | } 1184 | unlinkSync(p) { 1185 | return this.realFs.unlinkSync(npath.fromPortablePath(p)); 1186 | } 1187 | async utimesPromise(p, atime, mtime) { 1188 | return await new Promise((resolve, reject) => { 1189 | this.realFs.utimes(npath.fromPortablePath(p), atime, mtime, this.makeCallback(resolve, reject)); 1190 | }); 1191 | } 1192 | utimesSync(p, atime, mtime) { 1193 | this.realFs.utimesSync(npath.fromPortablePath(p), atime, mtime); 1194 | } 1195 | async lutimesPromise(p, atime, mtime) { 1196 | return await new Promise((resolve, reject) => { 1197 | this.realFs.lutimes(npath.fromPortablePath(p), atime, mtime, this.makeCallback(resolve, reject)); 1198 | }); 1199 | } 1200 | lutimesSync(p, atime, mtime) { 1201 | this.realFs.lutimesSync(npath.fromPortablePath(p), atime, mtime); 1202 | } 1203 | async mkdirPromise(p, opts) { 1204 | return await new Promise((resolve, reject) => { 1205 | this.realFs.mkdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject)); 1206 | }); 1207 | } 1208 | mkdirSync(p, opts) { 1209 | return this.realFs.mkdirSync(npath.fromPortablePath(p), opts); 1210 | } 1211 | async rmdirPromise(p, opts) { 1212 | return await new Promise((resolve, reject) => { 1213 | if (opts) { 1214 | this.realFs.rmdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject)); 1215 | } else { 1216 | this.realFs.rmdir(npath.fromPortablePath(p), this.makeCallback(resolve, reject)); 1217 | } 1218 | }); 1219 | } 1220 | rmdirSync(p, opts) { 1221 | return this.realFs.rmdirSync(npath.fromPortablePath(p), opts); 1222 | } 1223 | async rmPromise(p, opts) { 1224 | return await new Promise((resolve, reject) => { 1225 | if (opts) { 1226 | this.realFs.rm(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject)); 1227 | } else { 1228 | this.realFs.rm(npath.fromPortablePath(p), this.makeCallback(resolve, reject)); 1229 | } 1230 | }); 1231 | } 1232 | rmSync(p, opts) { 1233 | return this.realFs.rmSync(npath.fromPortablePath(p), opts); 1234 | } 1235 | async linkPromise(existingP, newP) { 1236 | return await new Promise((resolve, reject) => { 1237 | this.realFs.link(npath.fromPortablePath(existingP), npath.fromPortablePath(newP), this.makeCallback(resolve, reject)); 1238 | }); 1239 | } 1240 | linkSync(existingP, newP) { 1241 | return this.realFs.linkSync(npath.fromPortablePath(existingP), npath.fromPortablePath(newP)); 1242 | } 1243 | async symlinkPromise(target, p, type) { 1244 | return await new Promise((resolve, reject) => { 1245 | this.realFs.symlink(npath.fromPortablePath(target.replace(/\/+$/, ``)), npath.fromPortablePath(p), type, this.makeCallback(resolve, reject)); 1246 | }); 1247 | } 1248 | symlinkSync(target, p, type) { 1249 | return this.realFs.symlinkSync(npath.fromPortablePath(target.replace(/\/+$/, ``)), npath.fromPortablePath(p), type); 1250 | } 1251 | async readFilePromise(p, encoding) { 1252 | return await new Promise((resolve, reject) => { 1253 | const fsNativePath = typeof p === `string` ? npath.fromPortablePath(p) : p; 1254 | this.realFs.readFile(fsNativePath, encoding, this.makeCallback(resolve, reject)); 1255 | }); 1256 | } 1257 | readFileSync(p, encoding) { 1258 | const fsNativePath = typeof p === `string` ? npath.fromPortablePath(p) : p; 1259 | return this.realFs.readFileSync(fsNativePath, encoding); 1260 | } 1261 | async readdirPromise(p, opts) { 1262 | return await new Promise((resolve, reject) => { 1263 | if (opts) { 1264 | if (opts.recursive && process.platform === `win32`) { 1265 | if (opts.withFileTypes) { 1266 | this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback((results) => resolve(results.map(direntToPortable)), reject)); 1267 | } else { 1268 | this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback((results) => resolve(results.map(npath.toPortablePath)), reject)); 1269 | } 1270 | } else { 1271 | this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject)); 1272 | } 1273 | } else { 1274 | this.realFs.readdir(npath.fromPortablePath(p), this.makeCallback(resolve, reject)); 1275 | } 1276 | }); 1277 | } 1278 | readdirSync(p, opts) { 1279 | if (opts) { 1280 | if (opts.recursive && process.platform === `win32`) { 1281 | if (opts.withFileTypes) { 1282 | return this.realFs.readdirSync(npath.fromPortablePath(p), opts).map(direntToPortable); 1283 | } else { 1284 | return this.realFs.readdirSync(npath.fromPortablePath(p), opts).map(npath.toPortablePath); 1285 | } 1286 | } else { 1287 | return this.realFs.readdirSync(npath.fromPortablePath(p), opts); 1288 | } 1289 | } else { 1290 | return this.realFs.readdirSync(npath.fromPortablePath(p)); 1291 | } 1292 | } 1293 | async readlinkPromise(p) { 1294 | return await new Promise((resolve, reject) => { 1295 | this.realFs.readlink(npath.fromPortablePath(p), this.makeCallback(resolve, reject)); 1296 | }).then((path) => { 1297 | return npath.toPortablePath(path); 1298 | }); 1299 | } 1300 | readlinkSync(p) { 1301 | return npath.toPortablePath(this.realFs.readlinkSync(npath.fromPortablePath(p))); 1302 | } 1303 | async truncatePromise(p, len) { 1304 | return await new Promise((resolve, reject) => { 1305 | this.realFs.truncate(npath.fromPortablePath(p), len, this.makeCallback(resolve, reject)); 1306 | }); 1307 | } 1308 | truncateSync(p, len) { 1309 | return this.realFs.truncateSync(npath.fromPortablePath(p), len); 1310 | } 1311 | async ftruncatePromise(fd, len) { 1312 | return await new Promise((resolve, reject) => { 1313 | this.realFs.ftruncate(fd, len, this.makeCallback(resolve, reject)); 1314 | }); 1315 | } 1316 | ftruncateSync(fd, len) { 1317 | return this.realFs.ftruncateSync(fd, len); 1318 | } 1319 | watch(p, a, b) { 1320 | return this.realFs.watch( 1321 | npath.fromPortablePath(p), 1322 | a, 1323 | b 1324 | ); 1325 | } 1326 | watchFile(p, a, b) { 1327 | return this.realFs.watchFile( 1328 | npath.fromPortablePath(p), 1329 | a, 1330 | b 1331 | ); 1332 | } 1333 | unwatchFile(p, cb) { 1334 | return this.realFs.unwatchFile(npath.fromPortablePath(p), cb); 1335 | } 1336 | makeCallback(resolve, reject) { 1337 | return (err, result) => { 1338 | if (err) { 1339 | reject(err); 1340 | } else { 1341 | resolve(result); 1342 | } 1343 | }; 1344 | } 1345 | } 1346 | 1347 | const NUMBER_REGEXP = /^[0-9]+$/; 1348 | const VIRTUAL_REGEXP = /^(\/(?:[^/]+\/)*?(?:\$\$virtual|__virtual__))((?:\/((?:[^/]+-)?[a-f0-9]+)(?:\/([^/]+))?)?((?:\/.*)?))$/; 1349 | const VALID_COMPONENT = /^([^/]+-)?[a-f0-9]+$/; 1350 | class VirtualFS extends ProxiedFS { 1351 | constructor({ baseFs = new NodeFS() } = {}) { 1352 | super(ppath); 1353 | this.baseFs = baseFs; 1354 | } 1355 | static makeVirtualPath(base, component, to) { 1356 | if (ppath.basename(base) !== `__virtual__`) 1357 | throw new Error(`Assertion failed: Virtual folders must be named "__virtual__"`); 1358 | if (!ppath.basename(component).match(VALID_COMPONENT)) 1359 | throw new Error(`Assertion failed: Virtual components must be ended by an hexadecimal hash`); 1360 | const target = ppath.relative(ppath.dirname(base), to); 1361 | const segments = target.split(`/`); 1362 | let depth = 0; 1363 | while (depth < segments.length && segments[depth] === `..`) 1364 | depth += 1; 1365 | const finalSegments = segments.slice(depth); 1366 | const fullVirtualPath = ppath.join(base, component, String(depth), ...finalSegments); 1367 | return fullVirtualPath; 1368 | } 1369 | static resolveVirtual(p) { 1370 | const match = p.match(VIRTUAL_REGEXP); 1371 | if (!match || !match[3] && match[5]) 1372 | return p; 1373 | const target = ppath.dirname(match[1]); 1374 | if (!match[3] || !match[4]) 1375 | return target; 1376 | const isnum = NUMBER_REGEXP.test(match[4]); 1377 | if (!isnum) 1378 | return p; 1379 | const depth = Number(match[4]); 1380 | const backstep = `../`.repeat(depth); 1381 | const subpath = match[5] || `.`; 1382 | return VirtualFS.resolveVirtual(ppath.join(target, backstep, subpath)); 1383 | } 1384 | getExtractHint(hints) { 1385 | return this.baseFs.getExtractHint(hints); 1386 | } 1387 | getRealPath() { 1388 | return this.baseFs.getRealPath(); 1389 | } 1390 | realpathSync(p) { 1391 | const match = p.match(VIRTUAL_REGEXP); 1392 | if (!match) 1393 | return this.baseFs.realpathSync(p); 1394 | if (!match[5]) 1395 | return p; 1396 | const realpath = this.baseFs.realpathSync(this.mapToBase(p)); 1397 | return VirtualFS.makeVirtualPath(match[1], match[3], realpath); 1398 | } 1399 | async realpathPromise(p) { 1400 | const match = p.match(VIRTUAL_REGEXP); 1401 | if (!match) 1402 | return await this.baseFs.realpathPromise(p); 1403 | if (!match[5]) 1404 | return p; 1405 | const realpath = await this.baseFs.realpathPromise(this.mapToBase(p)); 1406 | return VirtualFS.makeVirtualPath(match[1], match[3], realpath); 1407 | } 1408 | mapToBase(p) { 1409 | if (p === ``) 1410 | return p; 1411 | if (this.pathUtils.isAbsolute(p)) 1412 | return VirtualFS.resolveVirtual(p); 1413 | const resolvedRoot = VirtualFS.resolveVirtual(this.baseFs.resolve(PortablePath.dot)); 1414 | const resolvedP = VirtualFS.resolveVirtual(this.baseFs.resolve(p)); 1415 | return ppath.relative(resolvedRoot, resolvedP) || PortablePath.dot; 1416 | } 1417 | mapFromBase(p) { 1418 | return p; 1419 | } 1420 | } 1421 | 1422 | const URL = Number(process.versions.node.split('.', 1)[0]) < 20 ? URL$1 : globalThis.URL; 1423 | 1424 | const [major, minor] = process.versions.node.split(`.`).map((value) => parseInt(value, 10)); 1425 | const WATCH_MODE_MESSAGE_USES_ARRAYS = major > 19 || major === 19 && minor >= 2 || major === 18 && minor >= 13; 1426 | const HAS_LAZY_LOADED_TRANSLATORS = major === 20 && minor < 6 || major === 19 && minor >= 3; 1427 | const SUPPORTS_IMPORT_ATTRIBUTES = major >= 21 || major === 20 && minor >= 10 || major === 18 && minor >= 20; 1428 | const SUPPORTS_IMPORT_ATTRIBUTES_ONLY = major >= 22; 1429 | 1430 | function readPackageScope(checkPath) { 1431 | const rootSeparatorIndex = checkPath.indexOf(npath.sep); 1432 | let separatorIndex; 1433 | do { 1434 | separatorIndex = checkPath.lastIndexOf(npath.sep); 1435 | checkPath = checkPath.slice(0, separatorIndex); 1436 | if (checkPath.endsWith(`${npath.sep}node_modules`)) 1437 | return false; 1438 | const pjson = readPackage(checkPath + npath.sep); 1439 | if (pjson) { 1440 | return { 1441 | data: pjson, 1442 | path: checkPath 1443 | }; 1444 | } 1445 | } while (separatorIndex > rootSeparatorIndex); 1446 | return false; 1447 | } 1448 | function readPackage(requestPath) { 1449 | const jsonPath = npath.resolve(requestPath, `package.json`); 1450 | if (!fs.existsSync(jsonPath)) 1451 | return null; 1452 | return JSON.parse(fs.readFileSync(jsonPath, `utf8`)); 1453 | } 1454 | 1455 | async function tryReadFile$1(path2) { 1456 | try { 1457 | return await fs.promises.readFile(path2, `utf8`); 1458 | } catch (error) { 1459 | if (error.code === `ENOENT`) 1460 | return null; 1461 | throw error; 1462 | } 1463 | } 1464 | function tryParseURL(str, base) { 1465 | try { 1466 | return new URL(str, base); 1467 | } catch { 1468 | return null; 1469 | } 1470 | } 1471 | let entrypointPath = null; 1472 | function setEntrypointPath(file) { 1473 | entrypointPath = file; 1474 | } 1475 | function getFileFormat(filepath) { 1476 | const ext = path.extname(filepath); 1477 | switch (ext) { 1478 | case `.mjs`: { 1479 | return `module`; 1480 | } 1481 | case `.cjs`: { 1482 | return `commonjs`; 1483 | } 1484 | case `.wasm`: { 1485 | throw new Error( 1486 | `Unknown file extension ".wasm" for ${filepath}` 1487 | ); 1488 | } 1489 | case `.json`: { 1490 | return `json`; 1491 | } 1492 | case `.js`: { 1493 | const pkg = readPackageScope(filepath); 1494 | if (!pkg) 1495 | return `commonjs`; 1496 | return pkg.data.type ?? `commonjs`; 1497 | } 1498 | default: { 1499 | if (entrypointPath !== filepath) 1500 | return null; 1501 | const pkg = readPackageScope(filepath); 1502 | if (!pkg) 1503 | return `commonjs`; 1504 | if (pkg.data.type === `module`) 1505 | return null; 1506 | return pkg.data.type ?? `commonjs`; 1507 | } 1508 | } 1509 | } 1510 | 1511 | async function load$1(urlString, context, nextLoad) { 1512 | const url = tryParseURL(urlString); 1513 | if (url?.protocol !== `file:`) 1514 | return nextLoad(urlString, context, nextLoad); 1515 | const filePath = fileURLToPath(url); 1516 | const format = getFileFormat(filePath); 1517 | if (!format) 1518 | return nextLoad(urlString, context, nextLoad); 1519 | if (format === `json`) { 1520 | if (SUPPORTS_IMPORT_ATTRIBUTES_ONLY) { 1521 | if (context.importAttributes?.type !== `json`) { 1522 | const err = new TypeError(`[ERR_IMPORT_ATTRIBUTE_MISSING]: Module "${urlString}" needs an import attribute of "type: json"`); 1523 | err.code = `ERR_IMPORT_ATTRIBUTE_MISSING`; 1524 | throw err; 1525 | } 1526 | } else { 1527 | const type = `importAttributes` in context ? context.importAttributes?.type : context.importAssertions?.type; 1528 | if (type !== `json`) { 1529 | const err = new TypeError(`[ERR_IMPORT_ASSERTION_TYPE_MISSING]: Module "${urlString}" needs an import ${SUPPORTS_IMPORT_ATTRIBUTES ? `attribute` : `assertion`} of type "json"`); 1530 | err.code = `ERR_IMPORT_ASSERTION_TYPE_MISSING`; 1531 | throw err; 1532 | } 1533 | } 1534 | } 1535 | if (process.env.WATCH_REPORT_DEPENDENCIES && process.send) { 1536 | const pathToSend = pathToFileURL( 1537 | npath.fromPortablePath( 1538 | VirtualFS.resolveVirtual(npath.toPortablePath(filePath)) 1539 | ) 1540 | ).href; 1541 | process.send({ 1542 | "watch:import": WATCH_MODE_MESSAGE_USES_ARRAYS ? [pathToSend] : pathToSend 1543 | }); 1544 | } 1545 | return { 1546 | format, 1547 | source: format === `commonjs` ? void 0 : await fs.promises.readFile(filePath, `utf8`), 1548 | shortCircuit: true 1549 | }; 1550 | } 1551 | 1552 | const ArrayIsArray = Array.isArray; 1553 | const JSONStringify = JSON.stringify; 1554 | const ObjectGetOwnPropertyNames = Object.getOwnPropertyNames; 1555 | const ObjectPrototypeHasOwnProperty = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop); 1556 | const RegExpPrototypeExec = (obj, string) => RegExp.prototype.exec.call(obj, string); 1557 | const RegExpPrototypeSymbolReplace = (obj, ...rest) => RegExp.prototype[Symbol.replace].apply(obj, rest); 1558 | const StringPrototypeEndsWith = (str, ...rest) => String.prototype.endsWith.apply(str, rest); 1559 | const StringPrototypeIncludes = (str, ...rest) => String.prototype.includes.apply(str, rest); 1560 | const StringPrototypeLastIndexOf = (str, ...rest) => String.prototype.lastIndexOf.apply(str, rest); 1561 | const StringPrototypeIndexOf = (str, ...rest) => String.prototype.indexOf.apply(str, rest); 1562 | const StringPrototypeReplace = (str, ...rest) => String.prototype.replace.apply(str, rest); 1563 | const StringPrototypeSlice = (str, ...rest) => String.prototype.slice.apply(str, rest); 1564 | const StringPrototypeStartsWith = (str, ...rest) => String.prototype.startsWith.apply(str, rest); 1565 | const SafeMap = Map; 1566 | const JSONParse = JSON.parse; 1567 | 1568 | function createErrorType(code, messageCreator, errorType) { 1569 | return class extends errorType { 1570 | constructor(...args) { 1571 | super(messageCreator(...args)); 1572 | this.code = code; 1573 | this.name = `${errorType.name} [${code}]`; 1574 | } 1575 | }; 1576 | } 1577 | const ERR_PACKAGE_IMPORT_NOT_DEFINED = createErrorType( 1578 | `ERR_PACKAGE_IMPORT_NOT_DEFINED`, 1579 | (specifier, packagePath, base) => { 1580 | return `Package import specifier "${specifier}" is not defined${packagePath ? ` in package ${packagePath}package.json` : ``} imported from ${base}`; 1581 | }, 1582 | TypeError 1583 | ); 1584 | const ERR_INVALID_MODULE_SPECIFIER = createErrorType( 1585 | `ERR_INVALID_MODULE_SPECIFIER`, 1586 | (request, reason, base = void 0) => { 1587 | return `Invalid module "${request}" ${reason}${base ? ` imported from ${base}` : ``}`; 1588 | }, 1589 | TypeError 1590 | ); 1591 | const ERR_INVALID_PACKAGE_TARGET = createErrorType( 1592 | `ERR_INVALID_PACKAGE_TARGET`, 1593 | (pkgPath, key, target, isImport = false, base = void 0) => { 1594 | const relError = typeof target === `string` && !isImport && target.length && !StringPrototypeStartsWith(target, `./`); 1595 | if (key === `.`) { 1596 | assert(isImport === false); 1597 | return `Invalid "exports" main target ${JSONStringify(target)} defined in the package config ${pkgPath}package.json${base ? ` imported from ${base}` : ``}${relError ? `; targets must start with "./"` : ``}`; 1598 | } 1599 | return `Invalid "${isImport ? `imports` : `exports`}" target ${JSONStringify( 1600 | target 1601 | )} defined for '${key}' in the package config ${pkgPath}package.json${base ? ` imported from ${base}` : ``}${relError ? `; targets must start with "./"` : ``}`; 1602 | }, 1603 | Error 1604 | ); 1605 | const ERR_INVALID_PACKAGE_CONFIG = createErrorType( 1606 | `ERR_INVALID_PACKAGE_CONFIG`, 1607 | (path, base, message) => { 1608 | return `Invalid package config ${path}${base ? ` while importing ${base}` : ``}${message ? `. ${message}` : ``}`; 1609 | }, 1610 | Error 1611 | ); 1612 | 1613 | function filterOwnProperties(source, keys) { 1614 | const filtered = /* @__PURE__ */ Object.create(null); 1615 | for (let i = 0; i < keys.length; i++) { 1616 | const key = keys[i]; 1617 | if (ObjectPrototypeHasOwnProperty(source, key)) { 1618 | filtered[key] = source[key]; 1619 | } 1620 | } 1621 | return filtered; 1622 | } 1623 | 1624 | const packageJSONCache = new SafeMap(); 1625 | function getPackageConfig(path, specifier, base, readFileSyncFn) { 1626 | const existing = packageJSONCache.get(path); 1627 | if (existing !== void 0) { 1628 | return existing; 1629 | } 1630 | const source = readFileSyncFn(path); 1631 | if (source === void 0) { 1632 | const packageConfig2 = { 1633 | pjsonPath: path, 1634 | exists: false, 1635 | main: void 0, 1636 | name: void 0, 1637 | type: "none", 1638 | exports: void 0, 1639 | imports: void 0 1640 | }; 1641 | packageJSONCache.set(path, packageConfig2); 1642 | return packageConfig2; 1643 | } 1644 | let packageJSON; 1645 | try { 1646 | packageJSON = JSONParse(source); 1647 | } catch (error) { 1648 | throw new ERR_INVALID_PACKAGE_CONFIG( 1649 | path, 1650 | (base ? `"${specifier}" from ` : "") + fileURLToPath(base || specifier), 1651 | error.message 1652 | ); 1653 | } 1654 | let { imports, main, name, type } = filterOwnProperties(packageJSON, [ 1655 | "imports", 1656 | "main", 1657 | "name", 1658 | "type" 1659 | ]); 1660 | const exports = ObjectPrototypeHasOwnProperty(packageJSON, "exports") ? packageJSON.exports : void 0; 1661 | if (typeof imports !== "object" || imports === null) { 1662 | imports = void 0; 1663 | } 1664 | if (typeof main !== "string") { 1665 | main = void 0; 1666 | } 1667 | if (typeof name !== "string") { 1668 | name = void 0; 1669 | } 1670 | if (type !== "module" && type !== "commonjs") { 1671 | type = "none"; 1672 | } 1673 | const packageConfig = { 1674 | pjsonPath: path, 1675 | exists: true, 1676 | main, 1677 | name, 1678 | type, 1679 | exports, 1680 | imports 1681 | }; 1682 | packageJSONCache.set(path, packageConfig); 1683 | return packageConfig; 1684 | } 1685 | function getPackageScopeConfig(resolved, readFileSyncFn) { 1686 | let packageJSONUrl = new URL("./package.json", resolved); 1687 | while (true) { 1688 | const packageJSONPath2 = packageJSONUrl.pathname; 1689 | if (StringPrototypeEndsWith(packageJSONPath2, "node_modules/package.json")) { 1690 | break; 1691 | } 1692 | const packageConfig2 = getPackageConfig( 1693 | fileURLToPath(packageJSONUrl), 1694 | resolved, 1695 | void 0, 1696 | readFileSyncFn 1697 | ); 1698 | if (packageConfig2.exists) { 1699 | return packageConfig2; 1700 | } 1701 | const lastPackageJSONUrl = packageJSONUrl; 1702 | packageJSONUrl = new URL("../package.json", packageJSONUrl); 1703 | if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) { 1704 | break; 1705 | } 1706 | } 1707 | const packageJSONPath = fileURLToPath(packageJSONUrl); 1708 | const packageConfig = { 1709 | pjsonPath: packageJSONPath, 1710 | exists: false, 1711 | main: void 0, 1712 | name: void 0, 1713 | type: "none", 1714 | exports: void 0, 1715 | imports: void 0 1716 | }; 1717 | packageJSONCache.set(packageJSONPath, packageConfig); 1718 | return packageConfig; 1719 | } 1720 | 1721 | function throwImportNotDefined(specifier, packageJSONUrl, base) { 1722 | throw new ERR_PACKAGE_IMPORT_NOT_DEFINED( 1723 | specifier, 1724 | packageJSONUrl && fileURLToPath(new URL(".", packageJSONUrl)), 1725 | fileURLToPath(base) 1726 | ); 1727 | } 1728 | function throwInvalidSubpath(subpath, packageJSONUrl, internal, base) { 1729 | const reason = `request is not a valid subpath for the "${internal ? "imports" : "exports"}" resolution of ${fileURLToPath(packageJSONUrl)}`; 1730 | throw new ERR_INVALID_MODULE_SPECIFIER( 1731 | subpath, 1732 | reason, 1733 | base && fileURLToPath(base) 1734 | ); 1735 | } 1736 | function throwInvalidPackageTarget(subpath, target, packageJSONUrl, internal, base) { 1737 | if (typeof target === "object" && target !== null) { 1738 | target = JSONStringify(target, null, ""); 1739 | } else { 1740 | target = `${target}`; 1741 | } 1742 | throw new ERR_INVALID_PACKAGE_TARGET( 1743 | fileURLToPath(new URL(".", packageJSONUrl)), 1744 | subpath, 1745 | target, 1746 | internal, 1747 | base && fileURLToPath(base) 1748 | ); 1749 | } 1750 | const invalidSegmentRegEx = /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))(\\|\/|$)/i; 1751 | const patternRegEx = /\*/g; 1752 | function resolvePackageTargetString(target, subpath, match, packageJSONUrl, base, pattern, internal, conditions) { 1753 | if (subpath !== "" && !pattern && target[target.length - 1] !== "/") 1754 | throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); 1755 | if (!StringPrototypeStartsWith(target, "./")) { 1756 | if (internal && !StringPrototypeStartsWith(target, "../") && !StringPrototypeStartsWith(target, "/")) { 1757 | let isURL = false; 1758 | try { 1759 | new URL(target); 1760 | isURL = true; 1761 | } catch { 1762 | } 1763 | if (!isURL) { 1764 | const exportTarget = pattern ? RegExpPrototypeSymbolReplace(patternRegEx, target, () => subpath) : target + subpath; 1765 | return exportTarget; 1766 | } 1767 | } 1768 | throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); 1769 | } 1770 | if (RegExpPrototypeExec( 1771 | invalidSegmentRegEx, 1772 | StringPrototypeSlice(target, 2) 1773 | ) !== null) 1774 | throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); 1775 | const resolved = new URL(target, packageJSONUrl); 1776 | const resolvedPath = resolved.pathname; 1777 | const packagePath = new URL(".", packageJSONUrl).pathname; 1778 | if (!StringPrototypeStartsWith(resolvedPath, packagePath)) 1779 | throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); 1780 | if (subpath === "") 1781 | return resolved; 1782 | if (RegExpPrototypeExec(invalidSegmentRegEx, subpath) !== null) { 1783 | const request = pattern ? StringPrototypeReplace(match, "*", () => subpath) : match + subpath; 1784 | throwInvalidSubpath(request, packageJSONUrl, internal, base); 1785 | } 1786 | if (pattern) { 1787 | return new URL( 1788 | RegExpPrototypeSymbolReplace(patternRegEx, resolved.href, () => subpath) 1789 | ); 1790 | } 1791 | return new URL(subpath, resolved); 1792 | } 1793 | function isArrayIndex(key) { 1794 | const keyNum = +key; 1795 | if (`${keyNum}` !== key) 1796 | return false; 1797 | return keyNum >= 0 && keyNum < 4294967295; 1798 | } 1799 | function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath, base, pattern, internal, conditions) { 1800 | if (typeof target === "string") { 1801 | return resolvePackageTargetString( 1802 | target, 1803 | subpath, 1804 | packageSubpath, 1805 | packageJSONUrl, 1806 | base, 1807 | pattern, 1808 | internal); 1809 | } else if (ArrayIsArray(target)) { 1810 | if (target.length === 0) { 1811 | return null; 1812 | } 1813 | let lastException; 1814 | for (let i = 0; i < target.length; i++) { 1815 | const targetItem = target[i]; 1816 | let resolveResult; 1817 | try { 1818 | resolveResult = resolvePackageTarget( 1819 | packageJSONUrl, 1820 | targetItem, 1821 | subpath, 1822 | packageSubpath, 1823 | base, 1824 | pattern, 1825 | internal, 1826 | conditions 1827 | ); 1828 | } catch (e) { 1829 | lastException = e; 1830 | if (e.code === "ERR_INVALID_PACKAGE_TARGET") { 1831 | continue; 1832 | } 1833 | throw e; 1834 | } 1835 | if (resolveResult === void 0) { 1836 | continue; 1837 | } 1838 | if (resolveResult === null) { 1839 | lastException = null; 1840 | continue; 1841 | } 1842 | return resolveResult; 1843 | } 1844 | if (lastException === void 0 || lastException === null) 1845 | return lastException; 1846 | throw lastException; 1847 | } else if (typeof target === "object" && target !== null) { 1848 | const keys = ObjectGetOwnPropertyNames(target); 1849 | for (let i = 0; i < keys.length; i++) { 1850 | const key = keys[i]; 1851 | if (isArrayIndex(key)) { 1852 | throw new ERR_INVALID_PACKAGE_CONFIG( 1853 | fileURLToPath(packageJSONUrl), 1854 | base, 1855 | '"exports" cannot contain numeric property keys.' 1856 | ); 1857 | } 1858 | } 1859 | for (let i = 0; i < keys.length; i++) { 1860 | const key = keys[i]; 1861 | if (key === "default" || conditions.has(key)) { 1862 | const conditionalTarget = target[key]; 1863 | const resolveResult = resolvePackageTarget( 1864 | packageJSONUrl, 1865 | conditionalTarget, 1866 | subpath, 1867 | packageSubpath, 1868 | base, 1869 | pattern, 1870 | internal, 1871 | conditions 1872 | ); 1873 | if (resolveResult === void 0) 1874 | continue; 1875 | return resolveResult; 1876 | } 1877 | } 1878 | return void 0; 1879 | } else if (target === null) { 1880 | return null; 1881 | } 1882 | throwInvalidPackageTarget( 1883 | packageSubpath, 1884 | target, 1885 | packageJSONUrl, 1886 | internal, 1887 | base 1888 | ); 1889 | } 1890 | function patternKeyCompare(a, b) { 1891 | const aPatternIndex = StringPrototypeIndexOf(a, "*"); 1892 | const bPatternIndex = StringPrototypeIndexOf(b, "*"); 1893 | const baseLenA = aPatternIndex === -1 ? a.length : aPatternIndex + 1; 1894 | const baseLenB = bPatternIndex === -1 ? b.length : bPatternIndex + 1; 1895 | if (baseLenA > baseLenB) 1896 | return -1; 1897 | if (baseLenB > baseLenA) 1898 | return 1; 1899 | if (aPatternIndex === -1) 1900 | return 1; 1901 | if (bPatternIndex === -1) 1902 | return -1; 1903 | if (a.length > b.length) 1904 | return -1; 1905 | if (b.length > a.length) 1906 | return 1; 1907 | return 0; 1908 | } 1909 | function packageImportsResolve({ name, base, conditions, readFileSyncFn }) { 1910 | if (name === "#" || StringPrototypeStartsWith(name, "#/") || StringPrototypeEndsWith(name, "/")) { 1911 | const reason = "is not a valid internal imports specifier name"; 1912 | throw new ERR_INVALID_MODULE_SPECIFIER(name, reason, fileURLToPath(base)); 1913 | } 1914 | let packageJSONUrl; 1915 | const packageConfig = getPackageScopeConfig(base, readFileSyncFn); 1916 | if (packageConfig.exists) { 1917 | packageJSONUrl = pathToFileURL(packageConfig.pjsonPath); 1918 | const imports = packageConfig.imports; 1919 | if (imports) { 1920 | if (ObjectPrototypeHasOwnProperty(imports, name) && !StringPrototypeIncludes(name, "*")) { 1921 | const resolveResult = resolvePackageTarget( 1922 | packageJSONUrl, 1923 | imports[name], 1924 | "", 1925 | name, 1926 | base, 1927 | false, 1928 | true, 1929 | conditions 1930 | ); 1931 | if (resolveResult != null) { 1932 | return resolveResult; 1933 | } 1934 | } else { 1935 | let bestMatch = ""; 1936 | let bestMatchSubpath; 1937 | const keys = ObjectGetOwnPropertyNames(imports); 1938 | for (let i = 0; i < keys.length; i++) { 1939 | const key = keys[i]; 1940 | const patternIndex = StringPrototypeIndexOf(key, "*"); 1941 | if (patternIndex !== -1 && StringPrototypeStartsWith( 1942 | name, 1943 | StringPrototypeSlice(key, 0, patternIndex) 1944 | )) { 1945 | const patternTrailer = StringPrototypeSlice(key, patternIndex + 1); 1946 | if (name.length >= key.length && StringPrototypeEndsWith(name, patternTrailer) && patternKeyCompare(bestMatch, key) === 1 && StringPrototypeLastIndexOf(key, "*") === patternIndex) { 1947 | bestMatch = key; 1948 | bestMatchSubpath = StringPrototypeSlice( 1949 | name, 1950 | patternIndex, 1951 | name.length - patternTrailer.length 1952 | ); 1953 | } 1954 | } 1955 | } 1956 | if (bestMatch) { 1957 | const target = imports[bestMatch]; 1958 | const resolveResult = resolvePackageTarget( 1959 | packageJSONUrl, 1960 | target, 1961 | bestMatchSubpath, 1962 | bestMatch, 1963 | base, 1964 | true, 1965 | true, 1966 | conditions 1967 | ); 1968 | if (resolveResult != null) { 1969 | return resolveResult; 1970 | } 1971 | } 1972 | } 1973 | } 1974 | } 1975 | throwImportNotDefined(name, packageJSONUrl, base); 1976 | } 1977 | 1978 | let findPnpApi = esmModule.findPnpApi; 1979 | if (!findPnpApi) { 1980 | const require = createRequire(import.meta.url); 1981 | const pnpApi = require(`./.pnp.cjs`); 1982 | pnpApi.setup(); 1983 | findPnpApi = esmModule.findPnpApi; 1984 | } 1985 | const pathRegExp = /^(?![a-zA-Z]:[\\/]|\\\\|\.{0,2}(?:\/|$))((?:node:)?(?:@[^/]+\/)?[^/]+)\/*(.*|)$/; 1986 | const isRelativeRegexp = /^\.{0,2}\//; 1987 | function tryReadFile(filePath) { 1988 | try { 1989 | return fs.readFileSync(filePath, `utf8`); 1990 | } catch (err) { 1991 | if (err.code === `ENOENT`) 1992 | return void 0; 1993 | throw err; 1994 | } 1995 | } 1996 | async function resolvePrivateRequest(specifier, issuer, context, nextResolve) { 1997 | const resolved = packageImportsResolve({ 1998 | name: specifier, 1999 | base: pathToFileURL(issuer), 2000 | conditions: new Set(context.conditions), 2001 | readFileSyncFn: tryReadFile 2002 | }); 2003 | if (resolved instanceof URL) { 2004 | return { url: resolved.href, shortCircuit: true }; 2005 | } else { 2006 | if (resolved.startsWith(`#`)) 2007 | throw new Error(`Mapping from one private import to another isn't allowed`); 2008 | return resolve$1(resolved, context, nextResolve); 2009 | } 2010 | } 2011 | async function resolve$1(originalSpecifier, context, nextResolve) { 2012 | if (!findPnpApi || isBuiltin(originalSpecifier)) 2013 | return nextResolve(originalSpecifier, context, nextResolve); 2014 | let specifier = originalSpecifier; 2015 | const url = tryParseURL(specifier, isRelativeRegexp.test(specifier) ? context.parentURL : void 0); 2016 | if (url) { 2017 | if (url.protocol !== `file:`) 2018 | return nextResolve(originalSpecifier, context, nextResolve); 2019 | specifier = fileURLToPath(url); 2020 | } 2021 | const { parentURL, conditions = [] } = context; 2022 | const issuer = parentURL && tryParseURL(parentURL)?.protocol === `file:` ? fileURLToPath(parentURL) : process.cwd(); 2023 | const pnpapi = findPnpApi(issuer) ?? (url ? findPnpApi(specifier) : null); 2024 | if (!pnpapi) 2025 | return nextResolve(originalSpecifier, context, nextResolve); 2026 | if (specifier.startsWith(`#`)) 2027 | return resolvePrivateRequest(specifier, issuer, context, nextResolve); 2028 | const dependencyNameMatch = specifier.match(pathRegExp); 2029 | let allowLegacyResolve = false; 2030 | if (dependencyNameMatch) { 2031 | const [, dependencyName, subPath] = dependencyNameMatch; 2032 | if (subPath === `` && dependencyName !== `pnpapi`) { 2033 | const resolved = pnpapi.resolveToUnqualified(`${dependencyName}/package.json`, issuer); 2034 | if (resolved) { 2035 | const content = await tryReadFile$1(resolved); 2036 | if (content) { 2037 | const pkg = JSON.parse(content); 2038 | allowLegacyResolve = pkg.exports == null; 2039 | } 2040 | } 2041 | } 2042 | } 2043 | let result; 2044 | try { 2045 | result = pnpapi.resolveRequest(specifier, issuer, { 2046 | conditions: new Set(conditions), 2047 | extensions: allowLegacyResolve ? void 0 : [] 2048 | }); 2049 | } catch (err) { 2050 | if (err instanceof Error && `code` in err && err.code === `MODULE_NOT_FOUND`) 2051 | err.code = `ERR_MODULE_NOT_FOUND`; 2052 | throw err; 2053 | } 2054 | if (!result) 2055 | throw new Error(`Resolving '${specifier}' from '${issuer}' failed`); 2056 | const resultURL = pathToFileURL(result); 2057 | if (url) { 2058 | resultURL.search = url.search; 2059 | resultURL.hash = url.hash; 2060 | } 2061 | if (!parentURL) 2062 | setEntrypointPath(fileURLToPath(resultURL)); 2063 | return { 2064 | url: resultURL.href, 2065 | shortCircuit: true 2066 | }; 2067 | } 2068 | 2069 | if (!HAS_LAZY_LOADED_TRANSLATORS) { 2070 | const binding = process.binding(`fs`); 2071 | const originalReadFile = binding.readFileUtf8 || binding.readFileSync; 2072 | if (originalReadFile) { 2073 | binding[originalReadFile.name] = function(...args) { 2074 | try { 2075 | return fs.readFileSync(args[0], { 2076 | encoding: `utf8`, 2077 | flag: args[1] 2078 | }); 2079 | } catch { 2080 | } 2081 | return originalReadFile.apply(this, args); 2082 | }; 2083 | } else { 2084 | const binding2 = process.binding(`fs`); 2085 | const originalfstat = binding2.fstat; 2086 | const ZIP_MASK = 4278190080; 2087 | const ZIP_MAGIC = 704643072; 2088 | binding2.fstat = function(...args) { 2089 | const [fd, useBigint, req] = args; 2090 | if ((fd & ZIP_MASK) === ZIP_MAGIC && useBigint === false && req === void 0) { 2091 | try { 2092 | const stats = fs.fstatSync(fd); 2093 | return new Float64Array([ 2094 | stats.dev, 2095 | stats.mode, 2096 | stats.nlink, 2097 | stats.uid, 2098 | stats.gid, 2099 | stats.rdev, 2100 | stats.blksize, 2101 | stats.ino, 2102 | stats.size, 2103 | stats.blocks 2104 | ]); 2105 | } catch { 2106 | } 2107 | } 2108 | return originalfstat.apply(this, args); 2109 | }; 2110 | } 2111 | } 2112 | 2113 | const resolve = resolve$1; 2114 | const load = load$1; 2115 | 2116 | export { load, resolve }; 2117 | -------------------------------------------------------------------------------- /typescript/two-fer/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "arcanis.vscode-zipfs", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /typescript/two-fer/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["exercism"], 3 | "search.exclude": { 4 | "**/.yarn": true, 5 | "**/.pnp.*": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /typescript/two-fer/.yarn/install-state.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yosevu/practicing-functional-typescript/f8eee201a26b5488e69962a5e0735a29a8440a7a/typescript/two-fer/.yarn/install-state.gz -------------------------------------------------------------------------------- /typescript/two-fer/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: true 4 | -------------------------------------------------------------------------------- /typescript/two-fer/HELP.md: -------------------------------------------------------------------------------- 1 | # Help 2 | 3 | ## Running the tests 4 | 5 | Before trying to execute the tests, ensure the assignment folder is set-up correctly by following the installation steps, namely `corepack yarn install` and the Editor SDK setup. 6 | 7 | Execute the tests with: 8 | 9 | ```bash 10 | $ corepack yarn test 11 | ``` 12 | 13 | ## Skipped tests 14 | 15 | In the test suites all tests but the first have been skipped. 16 | 17 | Once you get a test passing, you can enable the next one by changing `xit` to `it`. 18 | Additionally tests may be grouped using `xdescribe`. 19 | Enable the group by changing that to `describe`. 20 | Finally, some exercises may have optional tests `it.skip`. 21 | Remove `.skip` to execute the optional test. 22 | 23 | ## Submitting your solution 24 | 25 | You can submit your solution using the `exercism submit two-fer.ts` command. 26 | This command will upload your solution to the Exercism website and print the solution page's URL. 27 | 28 | It's possible to submit an incomplete solution which allows you to: 29 | 30 | - See how others have completed the exercise 31 | - Request help from a mentor 32 | 33 | ## Need to get help? 34 | 35 | If you'd like help solving the exercise, check the following pages: 36 | 37 | - The [TypeScript track's documentation](https://exercism.org/docs/tracks/typescript) 38 | - The [TypeScript track's programming category on the forum](https://forum.exercism.org/c/programming/typescript) 39 | - [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) 40 | - The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) 41 | 42 | Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. 43 | 44 | To get help if you're having trouble, you can use one of the following resources: 45 | 46 | - [TypeScript QuickStart](https://www.typescriptlang.org/docs/handbook/release-notes/overview.html) 47 | - [ECMAScript 2015 Language Specification](https://www.ecma-international.org/wp-content/uploads/ECMA-262_6th_edition_june_2015.pdf) (pdf) 48 | - [Mozilla JavaScript Reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference) 49 | - [/r/typescript](https://www.reddit.com/r/typescript) is the TypeScript subreddit. 50 | - [StackOverflow](https://stackoverflow.com/questions/tagged/typescript) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions. -------------------------------------------------------------------------------- /typescript/two-fer/README.md: -------------------------------------------------------------------------------- 1 | # Two Fer 2 | 3 | Welcome to Two Fer on Exercism's TypeScript Track. 4 | If you need help running the tests or submitting your code, check out `HELP.md`. 5 | 6 | ## Introduction 7 | 8 | In some English accents, when you say "two for" quickly, it sounds like "two fer". 9 | Two-for-one is a way of saying that if you buy one, you also get one for free. 10 | So the phrase "two-fer" often implies a two-for-one offer. 11 | 12 | Imagine a bakery that has a holiday offer where you can buy two cookies for the price of one ("two-fer one!"). 13 | You take the offer and (very generously) decide to give the extra cookie to someone else in the queue. 14 | 15 | ## Instructions 16 | 17 | Your task is to determine what you will say as you give away the extra cookie. 18 | 19 | If you know the person's name (e.g. if they're named Do-yun), then you will say: 20 | 21 | ```text 22 | One for Do-yun, one for me. 23 | ``` 24 | 25 | If you don't know the person's name, you will say _you_ instead. 26 | 27 | ```text 28 | One for you, one for me. 29 | ``` 30 | 31 | Here are some examples: 32 | 33 | | Name | Dialogue | 34 | | :----- | :-------------------------- | 35 | | Alice | One for Alice, one for me. | 36 | | Bohdan | One for Bohdan, one for me. | 37 | | | One for you, one for me. | 38 | | Zaphod | One for Zaphod, one for me. | 39 | 40 | ## Source 41 | 42 | ### Created by 43 | 44 | - @CRivasGomez 45 | 46 | ### Contributed to by 47 | 48 | - @masters3d 49 | - @SleeplessByte 50 | 51 | ### Based on 52 | 53 | https://github.com/exercism/problem-specifications/issues/757 -------------------------------------------------------------------------------- /typescript/two-fer/babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.37' }]], 3 | plugins: [], 4 | } 5 | -------------------------------------------------------------------------------- /typescript/two-fer/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import tsEslint from 'typescript-eslint' 4 | import config from '@exercism/eslint-config-typescript' 5 | import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs' 6 | 7 | export default [ 8 | ...tsEslint.config(...config, { 9 | files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'], 10 | extends: maintainersConfig, 11 | }), 12 | { 13 | ignores: [ 14 | // # Protected or generated 15 | '.git/**/*', 16 | '.vscode/**/*', 17 | 18 | //# When using npm 19 | 'node_modules/**/*', 20 | 21 | // # Configuration files 22 | 'babel.config.cjs', 23 | 'jest.config.cjs', 24 | ], 25 | }, 26 | ] 27 | -------------------------------------------------------------------------------- /typescript/two-fer/jest.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | projects: [''], 4 | testMatch: [ 5 | '**/__tests__/**/*.[jt]s?(x)', 6 | '**/test/**/*.[jt]s?(x)', 7 | '**/?(*.)+(spec|test).[jt]s?(x)', 8 | ], 9 | testPathIgnorePatterns: [ 10 | '/(?:production_)?node_modules/', 11 | '.d.ts$', 12 | '/test/fixtures', 13 | '/test/helpers', 14 | '__mocks__', 15 | ], 16 | transform: { 17 | '^.+\\.[jt]sx?$': 'babel-jest', 18 | }, 19 | moduleNameMapper: { 20 | '^(\\.\\/.+)\\.js$': '$1', 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /typescript/two-fer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@exercism/typescript-two-fer", 3 | "version": "1.0.0", 4 | "description": "Exercism exercises in Typescript.", 5 | "private": true, 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/exercism/typescript" 9 | }, 10 | "type": "module", 11 | "engines": { 12 | "node": "^18.16.0 || >=20.0.0" 13 | }, 14 | "devDependencies": { 15 | "@exercism/babel-preset-typescript": "^0.5.0", 16 | "@exercism/eslint-config-typescript": "^0.7.1", 17 | "@jest/globals": "^29.7.0", 18 | "@types/node": "~22.0.2", 19 | "babel-jest": "^29.7.0", 20 | "core-js": "~3.37.1", 21 | "eslint": "^9.8.0", 22 | "expect": "^29.7.0", 23 | "jest": "^29.7.0", 24 | "prettier": "^3.3.3", 25 | "tstyche": "^2.1.1", 26 | "typescript": "~5.5.4", 27 | "typescript-eslint": "^7.18.0" 28 | }, 29 | "scripts": { 30 | "test": "jest", 31 | "test:types": "tstyche", 32 | "test:implementation": "jest --no-cache --passWithNoTests", 33 | "lint": "yarn lint:types && yarn lint:ci", 34 | "lint:types": "tsc --noEmit -p .", 35 | "lint:ci": "eslint . --ext .tsx,.ts" 36 | }, 37 | "packageManager": "yarn@4.3.1" 38 | } 39 | -------------------------------------------------------------------------------- /typescript/two-fer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "display": "Configuration for Exercism TypeScript Exercises", 3 | "compilerOptions": { 4 | // Allows you to use the newest syntax, and have access to console.log 5 | // https://www.typescriptlang.org/tsconfig#lib 6 | "lib": ["ES2020", "dom"], 7 | // Make sure typescript is configured to output ESM 8 | // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm 9 | "module": "Node16", 10 | // Since this project is using babel, TypeScript may target something very 11 | // high, and babel will make sure it runs on your local Node version. 12 | // https://babeljs.io/docs/en/ 13 | "target": "ES2020", // ESLint doesn't support this yet: "es2022", 14 | 15 | "strict": true, 16 | "esModuleInterop": true, 17 | "skipLibCheck": true, 18 | "forceConsistentCasingInFileNames": true, 19 | 20 | // Because jest-resolve isn't like node resolve, the absolute path must be .ts 21 | "allowImportingTsExtensions": true, 22 | "noEmit": true, 23 | 24 | // Because we'll be using babel: ensure that Babel can safely transpile 25 | // files in the TypeScript project. 26 | // 27 | // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats 28 | "isolatedModules": true 29 | }, 30 | "include": [ 31 | "*.ts", 32 | "*.tsx", 33 | ".meta/*.ts", 34 | ".meta/*.tsx", 35 | "__typetests__/*.tst.ts" 36 | ], 37 | "exclude": ["node_modules"] 38 | } 39 | -------------------------------------------------------------------------------- /typescript/two-fer/two-fer.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from '@jest/globals' 2 | import { twoFer } from './two-fer.ts' 3 | 4 | describe('TwoFer', () => { 5 | it('no name given', () => { 6 | const expected = 'One for you, one for me.' 7 | expect(twoFer()).toEqual(expected) 8 | }) 9 | 10 | it('a name given', () => { 11 | const expected = 'One for Alice, one for me.' 12 | expect(twoFer('Alice')).toEqual(expected) 13 | }) 14 | 15 | it('another name given', () => { 16 | const expected = 'One for Bob, one for me.' 17 | expect(twoFer('Bob')).toEqual(expected) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /typescript/two-fer/two-fer.ts: -------------------------------------------------------------------------------- 1 | export const twoFer = (name = 'you') => `One for ${name}, one for me.` 2 | --------------------------------------------------------------------------------