├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .prettierignore ├── LICENSE ├── README.md ├── chompfile.toml ├── docs.md ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── common │ ├── alphabetize.ts │ ├── b64.ts │ ├── env.ts │ ├── err.ts │ ├── fetch-common.ts │ ├── fetch-deno.ts │ ├── fetch-native.ts │ ├── fetch-node.ts │ ├── fetch-vscode.ts │ ├── fetch.ts │ ├── integrity.ts │ ├── json.ts │ ├── log.ts │ ├── pool.ts │ ├── source-style.ts │ ├── str.ts │ ├── url.ts │ └── wrapper.ts ├── generator-deno.ts ├── generator.test.ts ├── generator.ts ├── html │ ├── analyze.ts │ └── lexer.ts ├── install │ ├── installer.ts │ ├── lock.test.ts │ ├── lock.ts │ ├── package.ts │ └── pjson.ts ├── providers │ ├── deno.ts │ ├── esmsh.ts │ ├── index.test.ts │ ├── index.ts │ ├── jsdelivr.ts │ ├── jspm.ts │ ├── node.ts │ ├── nodemodules.ts │ ├── skypack.ts │ └── unpkg.ts └── trace │ ├── analysis.ts │ ├── cjs.ts │ ├── resolver.ts │ ├── tracemap.ts │ └── ts.ts ├── test ├── api │ ├── alias.test.js │ ├── api.test.js │ ├── assets.test.js │ ├── core.test.js │ ├── deduping.test.js │ ├── devdependencies.test.js │ ├── extract.test.js │ ├── flat-dedupes.test.js │ ├── freeze.test.js │ ├── ignore.test.js │ ├── input-map-self.test.js │ ├── inputmap.test.js │ ├── install-dedupe.test.js │ ├── install.test.js │ ├── integrity.test.js │ ├── jsonassert.skipbrowser.test.js │ ├── link.test.js │ ├── local.test.js │ ├── local │ │ ├── assets │ │ │ ├── file.css │ │ │ ├── file.js │ │ │ ├── file.json │ │ │ ├── file.wasm │ │ │ └── package.json │ │ ├── dep │ │ │ ├── main.js │ │ │ └── package.json │ │ ├── freeze │ │ │ └── package.json │ │ ├── latest │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── pkg │ │ │ ├── a.js │ │ │ ├── b.js │ │ │ ├── c.js │ │ │ ├── d.js │ │ │ ├── data.json │ │ │ ├── e.cjs │ │ │ ├── f.cjs │ │ │ ├── index.js │ │ │ ├── jquery.js │ │ │ ├── json.ts │ │ │ └── package.json │ │ ├── react1 │ │ │ ├── package.json │ │ │ └── react1.js │ │ └── react2 │ │ │ ├── package.json │ │ │ └── react2.js │ ├── localcjs.test.js │ ├── localdeps.test.js │ ├── localdepsdirect.test.js │ ├── localpin.test.js │ ├── node.test.js │ ├── notFound.test.js │ ├── npmdep.test.js │ ├── parallel.test.js │ ├── preloads.test.js │ ├── providerswitch.test.js │ ├── reenv.test.js │ ├── remotedep.test.js │ ├── resolutions.test.js │ ├── rooturl.test.js │ ├── self-uninstall.test.js │ ├── self.test.js │ ├── subpath.test.js │ ├── traceinstall.test.js │ ├── uninstall.test.js │ ├── update.test.js │ ├── urls.test.js │ ├── version.test.js │ └── versionbumps │ │ ├── package.json │ │ └── x.js ├── deno.js ├── deno │ ├── babel.skip.js │ ├── esmsh.js │ ├── node.test.js │ └── self.skip.js ├── errors │ ├── no-subpath-recovery.test.js │ └── recovery.test.js ├── example.mjs ├── html │ ├── analyze.test.js │ ├── breaks.test.js │ ├── emptymap.test.js │ ├── esmsh.test.js │ ├── indent.test.js │ ├── inject.test.js │ ├── injectionpoint.test.js │ ├── inline.test.js │ ├── lexer.test.js │ ├── lit.test.js │ ├── local │ │ ├── package.json │ │ └── react.js │ ├── maps.test.js │ ├── multi.test.js │ ├── offset.test.js │ ├── preload-local.test.js │ ├── preload.test.js │ └── ws.test.js ├── npm-compatibility │ ├── README.md │ ├── install-pkg.test.js │ ├── install.test.js │ ├── npm-behaviour.sh │ ├── primary-in-range │ │ ├── importmap.json │ │ ├── package-lock.json │ │ └── package.json │ ├── primary-not-latest-secondary-in-range │ │ ├── importmap.json │ │ ├── package-lock.json │ │ └── package.json │ ├── primary-not-latest-secondary-out-range │ │ ├── importmap.json │ │ ├── package-lock.json │ │ └── package.json │ ├── primary-out-range │ │ ├── importmap.json │ │ ├── package-lock.json │ │ └── package.json │ ├── secondary-in-range │ │ ├── importmap.json │ │ ├── package-lock.json │ │ └── package.json │ ├── secondary-out-range │ │ ├── importmap.json │ │ ├── package-lock.json │ │ └── package.json │ ├── update-pkg.test.js │ └── update.test.js ├── perf │ ├── large-install-set.json │ └── perf.test.js ├── providers │ ├── config.perf.test.js │ ├── coremods │ │ ├── deno.js │ │ ├── deno.notfound.js │ │ └── package.json │ ├── custom.test.js │ ├── customregistry.test.js │ ├── deno.skipbrowser.test.js │ ├── esmsh-root.test.js │ ├── esmsh.test.js │ ├── jsdelivr.test.js │ ├── jspm-err.test.js │ ├── jspm.system.test.js │ ├── localdeps.test.js │ ├── localdeps │ │ ├── dep │ │ │ ├── index.js │ │ │ └── package.json │ │ ├── pkg │ │ │ ├── .gitignore │ │ │ ├── index.js │ │ │ ├── node_modules │ │ │ │ ├── .package-lock.json │ │ │ │ ├── dep │ │ │ │ │ ├── index.js │ │ │ │ │ └── package.json │ │ │ │ └── tar │ │ │ │ │ ├── index.js │ │ │ │ │ └── package.json │ │ │ ├── package-lock.json │ │ │ └── package.json │ │ └── tar.tgz │ ├── nodemodules.test.js │ ├── scopeprovider.test.js │ ├── skypack.test.js │ └── unpkg.test.js ├── resolve │ ├── builtin-shim.test.js │ ├── chalk.test.js │ ├── cjspkg │ │ ├── browser-dep-exclude.js │ │ ├── browser-dep-include.js │ │ ├── browser.js │ │ ├── mod-shim.js │ │ ├── mod.js │ │ ├── node_modules │ │ │ └── process │ │ │ │ └── index.js │ │ └── package.json │ ├── dayjs.test.js │ ├── legacy-main.test.js │ ├── legacypkg │ │ ├── m │ │ │ └── index.js │ │ └── package.json │ ├── node-maps.test.js │ ├── nodemodules.test.js │ ├── object-inspect.test.js │ ├── ts.test.js │ ├── tspkg │ │ ├── dep.ts │ │ ├── main.ts │ │ └── types.ts │ ├── unused-cjs.test.js │ ├── unusedcjspkg │ │ ├── cjs.js │ │ ├── index.js │ │ └── package.json │ ├── wildcard.test.js │ └── wildcard │ │ ├── a-module.js │ │ └── package.json ├── runMochaTests.js ├── server.mjs ├── test.html └── utils │ ├── fetch.test.js │ ├── lookup.test.js │ ├── pcfg.test.js │ └── resolve.test.js ├── tsconfig.json └── typedoc.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: / 5 | schedule: 6 | interval: daily 7 | allow: 8 | - dependency-name: '@jspm/import-map' 9 | - dependency-name: 'es-module-lexer' -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: main 6 | pull_request: 7 | branches: main 8 | 9 | jobs: 10 | test-build: 11 | name: Build & Type Test 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | submodules: true 17 | - name: Setup Node.js ${{ matrix.node }} 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: ${{ matrix.node }} 21 | - name: Setup Chomp 22 | uses: guybedford/chomp-action@v1 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | - run: npm install 26 | - run: chomp build 27 | 28 | test-browser: 29 | name: Firefox Browser Tests 30 | runs-on: windows-latest 31 | strategy: 32 | matrix: 33 | firefox: ['107.0.1'] 34 | steps: 35 | - uses: actions/checkout@v2 36 | with: 37 | submodules: true 38 | - name: Setup Node.js ${{ matrix.node }} 39 | uses: actions/setup-node@v2 40 | with: 41 | node-version: ${{ matrix.node }} 42 | - name: Setup Chomp 43 | uses: guybedford/chomp-action@v1 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | - name: Setup Firefox ${{ matrix.firefox }} 47 | uses: browser-actions/setup-firefox@latest 48 | with: 49 | firefox-version: ${{ matrix.firefox }} 50 | - run: npm install 51 | - run: chomp test:browser 52 | env: 53 | CI_BROWSER: C:\Program Files\Firefox_${{ matrix.firefox }}\firefox.exe 54 | CI_BROWSER_FLAGS: -headless 55 | CI_BROWSER_FLUSH: taskkill /F /IM firefox.exe 56 | SKIP_PERF: 1 57 | 58 | test-servers: 59 | name: Node.js & Deno Tests 60 | runs-on: ubuntu-latest 61 | strategy: 62 | matrix: 63 | node: [18.x, 20.x, 21.x] 64 | deno: ['1'] 65 | steps: 66 | - uses: actions/checkout@v2 67 | with: 68 | submodules: true 69 | - name: Use Node.js ${{ matrix.node }} 70 | uses: actions/setup-node@v2 71 | with: 72 | node-version: ${{ matrix.node }} 73 | - uses: denoland/setup-deno@v1 74 | with: 75 | deno-version: ${{ matrix.deno }} 76 | - name: Setup Chomp 77 | uses: guybedford/chomp-action@v1 78 | env: 79 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 80 | - run: npm install 81 | - run: chomp test 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | lib 4 | yarn.lock 5 | .vscode 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jspm/generator/0127edc78701d34d05565a2603bc52b134332ee8/.gitmodules -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | test/test.html 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚠️ ARCHIVED 2 | 3 | **The JSPM Generator repository has moved into the JSPM Monorepo.** 4 | 5 | **Please visit the new location: [https://github.com/jspm/jspm/tree/main/generator](https://github.com/jspm/jspm/tree/main/generator)** 6 | 7 | --- 8 | 9 | This is the core import map generation project for the [JSPM CLI](https://github.com/jspm/jspm). 10 | 11 | * **Local Linking**: map packages to your local `node_modules` folder 12 | * **Common CDNs**: Resolve against common CDNs like [jspm.io](https://jspm.io/), [jsDelivr](https://jsdelivr.com), [UNPKG](https://unpkg.com/) and [more](#customProviders) 13 | * **Universal Semantics**: Implements [universal CDN resolution](https://jspm.org/docs/cdn-resolution.md) semantics, based on an extension of the Node.js resolution 14 | * **Conditional Resolution**: Map different versions of a module based on environment 15 | * **Dependency Versioning**: Respects the version constraints in local and remote `package.json` files 16 | * **Package Entrypoints**: Handles node-style package exports, imports and own-name resolution 17 | 18 | See the [documentation](https://jspm.org/docs/generator) and [getting started](https://jspm.org/docs/getting-started) guide on jspm.org. 19 | 20 | ## Contributing 21 | 22 | Contributions welcome. 23 | 24 | Build and test workflows use [Chomp](https://chompbuild.com). 25 | 26 | ## License 27 | 28 | Apache-2.0 29 | -------------------------------------------------------------------------------- /chompfile.toml: -------------------------------------------------------------------------------- 1 | version = 0.1 2 | 3 | extensions = ['chomp@0.1:swc', 'chomp@0.1:rollup', 'chomp@0.1:prettier'] 4 | 5 | default-task = 'build' 6 | 7 | [[task]] 8 | target = 'lib' 9 | deps = ['lib/**/*.js'] 10 | 11 | [[task]] 12 | name = 'build' 13 | target = 'dist' 14 | deps = ['lib', 'npm:install', 'build:dec'] 15 | template = 'rollup' 16 | [task.template-options] 17 | input = [ 18 | 'lib/generator-deno.js', 19 | 'lib/generator.js', 20 | 'lib/common/fetch-vscode.js', 21 | 'lib/common/fetch-deno.js', 22 | 'lib/common/fetch-node.js', 23 | 'lib/common/fetch-native.js' 24 | ] 25 | onwarn = false 26 | clear-dir = true 27 | [task.template-options.output] 28 | dir = 'dist' 29 | 30 | [[task]] 31 | name = 'build:ts' 32 | target = 'lib/##.js' 33 | deps = ['src/##.ts'] 34 | template = 'swc' 35 | [task.template-options.config] 36 | inlineSourcesContent = false 37 | 'jsc.target' = 'es2019' 38 | 39 | # TODO: We should emit declaration files with swc once they support it. 40 | # See: https://github.com/swc-project/swc/issues/657 41 | [[task]] 42 | name = 'build:dec' 43 | deps = ['src/**/*.ts'] 44 | run = 'tsc --emitDeclarationOnly' 45 | 46 | [[task]] 47 | target = 'lib/version.js' 48 | dep = 'package.json' 49 | engine = 'node' 50 | run = ''' 51 | import { readFileSync, writeFileSync } from 'fs'; 52 | const { version } = JSON.parse(readFileSync('package.json', 'utf8')); 53 | writeFileSync('lib/version.js', `export default ${JSON.stringify(version)}`); 54 | ''' 55 | 56 | [[task]] 57 | name = 'build:ts' 58 | target = 'lib/##.js' 59 | deps = ['src/##.ts'] 60 | template = 'swc' 61 | [task.template-options.config] 62 | inlineSourcesContent = false 63 | 'jsc.target' = 'es2019' 64 | 65 | [[task]] 66 | name = 'test' 67 | serial = true 68 | deps = [ 69 | 'test:unit', 70 | 'test:integration', 71 | 'test:browser', 72 | ] 73 | 74 | [[task]] 75 | name = 'test:unit' 76 | dep = 'unit:' 77 | 78 | [[task]] 79 | name = 'unit:#' 80 | deps = ['lib/##.test.js', 'lib'] 81 | run = 'node -C source --enable-source-maps $DEP' 82 | 83 | [[task]] 84 | name = 'test:integration' 85 | dep = 'integration:' 86 | 87 | [[task]] 88 | name = 'integration:#' 89 | deps = ['test/##.test.js', 'lib'] 90 | # env = { JSPM_GENERATOR_LOG = '1' } 91 | run = 'node -C source --enable-source-maps $DEP' 92 | 93 | [[task]] 94 | name = 'test:browser' 95 | deps = ['build:ts', 'dist/*', 'test/test.html'] 96 | run = 'node test/server.mjs' 97 | 98 | [[task]] 99 | name = 'test:browser:watch' 100 | env = { WATCH_MODE = '1' } 101 | run = 'node test/server.mjs' 102 | 103 | [[task]] 104 | name = 'cache-clear' 105 | engine = 'node' 106 | run = ''' 107 | import { clearCache } from '@jspm/generator'; 108 | clearCache(); 109 | ''' 110 | 111 | [[task]] 112 | target = 'test/test.html' 113 | deps = ['src/**/*.ts', 'dist/generator.js'] 114 | engine = 'node' 115 | run = ''' 116 | import { Generator } from '@jspm/generator'; 117 | import { readFile, writeFile } from 'fs/promises'; 118 | 119 | const generator = new Generator({ 120 | mapUrl: new URL('./test/test.html', import.meta.url.replace('//[', '/[')), 121 | env: ['browser', 'module', 'production'] 122 | }); 123 | 124 | await generator.link('@jspm/generator'); 125 | await generator.install('node:assert'); 126 | 127 | const html = await generator.htmlInject(await readFile(process.env.TARGET, 'utf8'), { 128 | htmlUrl: new URL(process.env.TARGET, import.meta.url.replace('//[', '/[')) 129 | }); 130 | await writeFile(process.env.TARGET, html); 131 | ''' 132 | 133 | [[task]] 134 | name = 'prettier' 135 | template = 'prettier' 136 | deps = ['src/**/*.ts', 'test/**/*.js'] 137 | [task.template-options] 138 | ignore-path = '.prettierignore' 139 | files = 'src/**/*.ts test/**/*.js' 140 | loglevel = 'warn' 141 | 142 | [[task]] 143 | name = 'typecheck' 144 | deps = ['src/**/*.ts', 'src/*.ts'] 145 | run = ''' 146 | tsc --noEmit 147 | ''' 148 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jspm/generator", 3 | "description": "Package Import Map Generation Tool", 4 | "license": "Apache-2.0", 5 | "version": "2.5.1", 6 | "types": "lib/generator.d.ts", 7 | "type": "module", 8 | "scripts": { 9 | "prepublishOnly": "chomp build" 10 | }, 11 | "imports": { 12 | "#fetch": { 13 | "source": { 14 | "vscode": "./lib/common/fetch-vscode.js", 15 | "deno": "./lib/common/fetch-deno.js", 16 | "node": "./lib/common/fetch-node.js", 17 | "default": "./lib/common/fetch-native.js" 18 | }, 19 | "default": { 20 | "vscode": "./dist/fetch-vscode.js", 21 | "deno": "./dist/fetch-deno.js", 22 | "node": "./dist/fetch-node.js", 23 | "default": "./dist/fetch-native.js" 24 | } 25 | }, 26 | "#test/*": { 27 | "source": "./test/*.js" 28 | } 29 | }, 30 | "exports": { 31 | ".": { 32 | "types": "./lib/generator.d.ts", 33 | "source": { 34 | "deno": "./lib/generator-deno.js", 35 | "default": "./lib/generator.js" 36 | }, 37 | "deno": "./dist/generator-deno.js", 38 | "default": "./dist/generator.js" 39 | }, 40 | "./*.js": { 41 | "source": "./lib/*.js" 42 | } 43 | }, 44 | "dependencies": { 45 | "@babel/core": "^7.24.7", 46 | "@babel/plugin-syntax-import-attributes": "^7.24.7", 47 | "@babel/preset-typescript": "^7.24.7", 48 | "@jspm/import-map": "^1.1.0", 49 | "es-module-lexer": "^1.5.4", 50 | "make-fetch-happen": "^8.0.14", 51 | "sver": "^1.8.4" 52 | }, 53 | "devDependencies": { 54 | "@jspm/core": "^2.0.1", 55 | "@swc/cli": "^0.1.65", 56 | "@swc/core": "^1.7.35", 57 | "@types/vscode": "^1.75.1", 58 | "@vscode/test-electron": "^2.2.3", 59 | "chalk": "^4.1.2", 60 | "cross-env": "^7.0.3", 61 | "kleur": "^4.1.5", 62 | "lit-element": "^2.5.1", 63 | "mocha": "^9.2.2", 64 | "open": "^8.4.1", 65 | "prettier": "^2.8.4", 66 | "rollup": "^2.79.1", 67 | "typescript": "^5.5.4" 68 | }, 69 | "files": [ 70 | "dist", 71 | "lib" 72 | ], 73 | "repository": { 74 | "type": "git", 75 | "url": "git+https://github.com/jspm/generator.git" 76 | }, 77 | "keywords": [ 78 | "jspm", 79 | "import maps", 80 | "es modules", 81 | "cdn", 82 | "package manager" 83 | ], 84 | "author": "Guy Bedford", 85 | "bugs": { 86 | "url": "https://github.com/jspm/generator/issues" 87 | }, 88 | "homepage": "https://github.com/jspm/generator#readme" 89 | } 90 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { pathToFileURL, fileURLToPath } from 'url'; 2 | 3 | export default { 4 | input: { 5 | 'generator': 'lib/generator.js', 6 | // TODO: internal module builds should be automatically iterated and built 7 | 'fetch-vscode': 'lib/common/fetch-vscode.js', 8 | 'fetch-deno': 'lib/common/fetch-deno.js', 9 | 'fetch-node': 'lib/common/fetch-node.js', 10 | 'fetch-native': 'lib/common/fetch-native.js' 11 | }, 12 | output: { 13 | dir: 'dist', 14 | format: 'esm' 15 | }, 16 | plugins: [{ 17 | resolveId (specifier, parent) { 18 | if (parent && !specifier.startsWith('./') && !specifier.startsWith('../') && !specifier.startsWith('/')) 19 | return { id: specifier, external: true }; 20 | return fileURLToPath(new URL(specifier, parent ? pathToFileURL(parent) : import.meta.url)); 21 | } 22 | }], 23 | // disable external module warnings 24 | // (JSPM / the import map handles these for us instead) 25 | onwarn (warning, warn) { 26 | if (warning.code === 'UNRESOLVED_IMPORT') 27 | return; 28 | warn(warning); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/common/alphabetize.ts: -------------------------------------------------------------------------------- 1 | export function alphabetize(obj: Record): Record { 2 | const out: Record = {}; 3 | for (const key of Object.keys(obj).sort()) out[key] = obj[key]; 4 | return out; 5 | } 6 | -------------------------------------------------------------------------------- /src/common/b64.ts: -------------------------------------------------------------------------------- 1 | export function encodeBase64(data: string): string { 2 | if (typeof window !== "undefined") { 3 | return window.btoa(data); 4 | } 5 | 6 | return Buffer.from(data).toString("base64"); 7 | } 8 | 9 | export function decodeBase64(data: string): string { 10 | if (typeof window !== "undefined") { 11 | return window.atob(data); 12 | } 13 | 14 | return Buffer.from(data, "base64").toString("utf8"); 15 | } 16 | -------------------------------------------------------------------------------- /src/common/env.ts: -------------------------------------------------------------------------------- 1 | export const isWindows = globalThis.process?.platform === "win32"; 2 | 3 | export const PATH = isWindows 4 | ? Object.keys(globalThis.process?.env).find((e) => 5 | Boolean(e.match(/^PATH$/i)) 6 | ) || "Path" 7 | : "PATH"; 8 | 9 | export const PATHS_SEP = globalThis.process?.platform === "win32" ? ";" : ":"; 10 | -------------------------------------------------------------------------------- /src/common/err.ts: -------------------------------------------------------------------------------- 1 | export class JspmError extends Error { 2 | jspmError = true; 3 | code: string | undefined; 4 | constructor(msg: string, code?: string) { 5 | super(msg); 6 | this.code = code; 7 | } 8 | } 9 | 10 | export function throwInternalError(...args): never { 11 | throw new Error( 12 | "Internal Error" + (args.length ? " " + args.join(", ") : "") 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/common/fetch-common.ts: -------------------------------------------------------------------------------- 1 | export interface WrappedResponse { 2 | status: number; 3 | statusText?: string; 4 | text?(): Promise; 5 | json?(): Promise; 6 | arrayBuffer?(): ArrayBuffer; 7 | } 8 | 9 | export type FetchFn = ( 10 | url: URL | string, 11 | ...args: any[] 12 | ) => Promise; 13 | 14 | export type WrappedFetch = (( 15 | url: URL | string, 16 | ...args: any[] 17 | ) => Promise) & { 18 | arrayBuffer: ( 19 | url: URL | string, 20 | ...args: any[] 21 | ) => Promise; 22 | text: (url: URL | string, ...args: any[]) => Promise; 23 | }; 24 | 25 | let retryCount = 5, 26 | poolSize = 100; 27 | 28 | export function setRetryCount(count: number) { 29 | retryCount = count; 30 | } 31 | 32 | export function setFetchPoolSize(size: number) { 33 | poolSize = size; 34 | } 35 | 36 | /** 37 | * Wraps a fetch request with pooling, and retry logic on exceptions (emfile / network errors). 38 | */ 39 | export function wrappedFetch(fetch: FetchFn): WrappedFetch { 40 | const wrappedFetch = async function (url: URL | string, ...args: any[]) { 41 | url = url.toString(); 42 | let retries = 0; 43 | try { 44 | await pushFetchPool(); 45 | while (true) { 46 | try { 47 | return await fetch(url, ...args); 48 | } catch (e) { 49 | if (retries++ >= retryCount) throw e; 50 | } 51 | } 52 | } finally { 53 | popFetchPool(); 54 | } 55 | }; 56 | wrappedFetch.arrayBuffer = async function (url, ...args) { 57 | url = url.toString(); 58 | let retries = 0; 59 | try { 60 | await pushFetchPool(); 61 | while (true) { 62 | try { 63 | var res = await fetch(url, ...args); 64 | } catch (e) { 65 | if (retries++ >= retryCount) throw e; 66 | continue; 67 | } 68 | switch (res.status) { 69 | case 200: 70 | case 304: 71 | break; 72 | // not found = null 73 | case 404: 74 | return null; 75 | default: 76 | throw new Error(`Invalid status code ${res.status}`); 77 | } 78 | try { 79 | return await res.arrayBuffer(); 80 | } catch (e) { 81 | if ( 82 | (retries++ >= retryCount && e.code === "ERR_SOCKET_TIMEOUT") || 83 | e.code === "ETIMEOUT" || 84 | e.code === "ECONNRESET" || 85 | e.code === "FETCH_ERROR" 86 | ) { 87 | } 88 | } 89 | } 90 | } finally { 91 | popFetchPool(); 92 | } 93 | }; 94 | wrappedFetch.text = async function (url, ...args) { 95 | const arrayBuffer = await this.arrayBuffer(url, ...args); 96 | if (!arrayBuffer) return null; 97 | return new TextDecoder().decode(arrayBuffer); 98 | }; 99 | return wrappedFetch; 100 | } 101 | 102 | // restrict in-flight fetches to a pool of 100 103 | let p = []; 104 | let c = 0; 105 | function pushFetchPool() { 106 | if (++c > poolSize) return new Promise((r) => p.push(r)); 107 | } 108 | function popFetchPool() { 109 | c--; 110 | if (p.length) p.shift()(); 111 | } 112 | -------------------------------------------------------------------------------- /src/common/fetch-deno.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from "url"; 2 | import { wrappedFetch, WrappedFetch } from "./fetch-common.js"; 3 | 4 | export function clearCache() {} 5 | 6 | export const fetch: WrappedFetch = wrappedFetch(async function ( 7 | url: URL, 8 | ...args: any[] 9 | ) { 10 | const urlString = url.toString(); 11 | if ( 12 | urlString.startsWith("file:") || 13 | urlString.startsWith("data:") || 14 | urlString.startsWith("node:") 15 | ) { 16 | try { 17 | let source: string; 18 | if (urlString.startsWith("file:")) { 19 | // @ts-ignore - can only resolve Deno when running in Deno 20 | source = (await Deno.readTextFile(fileURLToPath(urlString))) as string; 21 | } else if (urlString.startsWith("node:")) { 22 | source = ""; 23 | } else { 24 | source = decodeURIComponent( 25 | urlString.slice(urlString.indexOf(",") + 1) 26 | ); 27 | } 28 | return { 29 | status: 200, 30 | async text() { 31 | return source.toString(); 32 | }, 33 | async json() { 34 | return JSON.parse(source.toString()); 35 | }, 36 | arrayBuffer() { 37 | return new TextEncoder().encode(source.toString()).buffer; 38 | }, 39 | }; 40 | } catch (e) { 41 | if (e.code === "EISDIR") 42 | return { 43 | status: 200, 44 | async text() { 45 | return ""; 46 | }, 47 | async json() { 48 | throw new Error("Not JSON"); 49 | }, 50 | arrayBuffer() { 51 | return new ArrayBuffer(0); 52 | }, 53 | }; 54 | if (e.name === "NotFound") 55 | return { status: 404, statusText: e.toString() }; 56 | return { status: 500, statusText: e.toString() }; 57 | } 58 | } else { 59 | return globalThis.fetch(urlString, ...args); 60 | } 61 | }); 62 | -------------------------------------------------------------------------------- /src/common/fetch-native.ts: -------------------------------------------------------------------------------- 1 | import { WrappedFetch, wrappedFetch } from "./fetch-common.js"; 2 | 3 | export const fetch: WrappedFetch = wrappedFetch(globalThis.fetch); 4 | 5 | export const clearCache = () => {}; 6 | -------------------------------------------------------------------------------- /src/common/fetch-node.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import version from "../version.js"; 3 | import { wrappedFetch, WrappedFetch } from "./fetch-common.js"; 4 | import path from "path"; 5 | import { homedir } from "os"; 6 | import process from "process"; 7 | import makeFetchHappen from "make-fetch-happen"; 8 | import { readFileSync, rmdirSync } from "fs"; 9 | import { Buffer } from "buffer"; 10 | 11 | let cacheDir: string; 12 | if (process.platform === "darwin") 13 | cacheDir = path.join(homedir(), "Library", "Caches", "jspm"); 14 | else if (process.platform === "win32") 15 | cacheDir = path.join( 16 | process.env.LOCALAPPDATA || path.join(homedir(), "AppData", "Local"), 17 | "jspm-cache" 18 | ); 19 | else 20 | cacheDir = path.join( 21 | process.env.XDG_CACHE_HOME || path.join(homedir(), ".cache"), 22 | "jspm" 23 | ); 24 | 25 | export function clearCache() { 26 | rmdirSync(path.join(cacheDir, "fetch-cache"), { recursive: true }); 27 | } 28 | 29 | const _fetch = makeFetchHappen.defaults({ 30 | cacheManager: path.join(cacheDir, "fetch-cache"), 31 | headers: { "User-Agent": `jspm/generator@${version}` }, 32 | }); 33 | 34 | function sourceResponse(buffer: string | Buffer) { 35 | return { 36 | status: 200, 37 | async text() { 38 | return buffer.toString(); 39 | }, 40 | async json() { 41 | return JSON.parse(buffer.toString()); 42 | }, 43 | arrayBuffer() { 44 | if (typeof buffer === "string") 45 | return new TextEncoder().encode(buffer.toString()).buffer; 46 | return new Uint8Array( 47 | buffer.buffer, 48 | buffer.byteOffset, 49 | buffer.byteLength 50 | ); 51 | }, 52 | }; 53 | } 54 | 55 | const dirResponse = { 56 | status: 200, 57 | async text() { 58 | return ""; 59 | }, 60 | async json() { 61 | throw new Error("Not JSON"); 62 | }, 63 | arrayBuffer() { 64 | return new ArrayBuffer(0); 65 | }, 66 | }; 67 | 68 | export const fetch: WrappedFetch = wrappedFetch(async function ( 69 | url: URL, 70 | opts?: Record 71 | ) { 72 | const urlString = url.toString(); 73 | const protocol = urlString.slice(0, urlString.indexOf(":") + 1); 74 | let source: string | Buffer; 75 | switch (protocol) { 76 | case "file:": 77 | if (urlString.endsWith("/")) { 78 | try { 79 | readFileSync(new URL(urlString)); 80 | return { status: 404, statusText: "Directory does not exist" }; 81 | } catch (e) { 82 | if (e.code === "EISDIR") return dirResponse; 83 | throw e; 84 | } 85 | } 86 | try { 87 | return sourceResponse(readFileSync(new URL(urlString))); 88 | } catch (e) { 89 | if (e.code === "EISDIR") return dirResponse; 90 | if (e.code === "ENOENT" || e.code === "ENOTDIR") 91 | return { status: 404, statusText: e.toString() }; 92 | return { status: 500, statusText: e.toString() }; 93 | } 94 | case "data:": 95 | return sourceResponse( 96 | decodeURIComponent(urlString.slice(urlString.indexOf(",") + 1)) 97 | ); 98 | case "node:": 99 | return sourceResponse(""); 100 | case "http:": 101 | case "https:": 102 | // @ts-ignore 103 | return _fetch(url, opts); 104 | } 105 | }); 106 | -------------------------------------------------------------------------------- /src/common/fetch-vscode.ts: -------------------------------------------------------------------------------- 1 | import { WrappedFetch, wrappedFetch } from "./fetch-common.js"; 2 | import { fetch as _fetch } from "./fetch-native.js"; 3 | export { clearCache } from "./fetch-native.js"; 4 | 5 | function sourceResponse(buffer) { 6 | return { 7 | status: 200, 8 | async text() { 9 | return buffer.toString(); 10 | }, 11 | async json() { 12 | return JSON.parse(buffer.toString()); 13 | }, 14 | arrayBuffer() { 15 | return buffer.buffer || buffer; 16 | }, 17 | }; 18 | } 19 | 20 | const dirResponse = { 21 | status: 200, 22 | async text() { 23 | return ""; 24 | }, 25 | async json() { 26 | throw new Error("Not JSON"); 27 | }, 28 | arrayBuffer() { 29 | return new ArrayBuffer(0); 30 | }, 31 | }; 32 | 33 | // @ts-ignore 34 | const vscode = require("vscode"); 35 | 36 | export const fetch: WrappedFetch = wrappedFetch(async function ( 37 | url: URL, 38 | opts?: Record 39 | ) { 40 | const urlString = url.toString(); 41 | const protocol = urlString.slice(0, urlString.indexOf(":") + 1); 42 | switch (protocol) { 43 | case "file:": 44 | if (urlString.endsWith("/")) { 45 | try { 46 | await vscode.workspace.fs.readFile(vscode.Uri.parse(urlString)); 47 | return { status: 404, statusText: "Directory does not exist" }; 48 | } catch (e) { 49 | if (e.code === "FileIsADirectory") return dirResponse; 50 | throw e; 51 | } 52 | } 53 | try { 54 | return sourceResponse( 55 | new TextDecoder().decode( 56 | await vscode.workspace.fs.readFile(vscode.Uri.parse(urlString)) 57 | ) 58 | ); 59 | } catch (e) { 60 | if (e.code === "FileIsADirectory") return dirResponse; 61 | if ( 62 | e.code === "Unavailable" || 63 | e.code === "EntryNotFound" || 64 | e.code === "FileNotFound" 65 | ) 66 | return { status: 404, statusText: e.toString() }; 67 | return { status: 500, statusText: e.toString() }; 68 | } 69 | case "data:": 70 | case "http:": 71 | case "https:": 72 | // @ts-ignore 73 | return _fetch(url, opts); 74 | } 75 | }); 76 | -------------------------------------------------------------------------------- /src/common/fetch.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { fetch as fetchImpl, clearCache } from "#fetch"; 3 | 4 | export interface WrappedResponse { 5 | url: string; 6 | headers: Headers; 7 | ok: boolean; 8 | status: number; 9 | statusText?: string; 10 | text?(): Promise; 11 | json?(): Promise; 12 | arrayBuffer?(): ArrayBuffer; 13 | } 14 | 15 | export type FetchFn = ( 16 | url: URL | string, 17 | ...args: any[] 18 | ) => Promise; 19 | 20 | export type WrappedFetch = (( 21 | url: URL | string, 22 | ...args: any[] 23 | ) => Promise) & { 24 | arrayBuffer: ( 25 | url: URL | string, 26 | ...args: any[] 27 | ) => Promise; 28 | text: (url: URL | string, ...args: any[]) => Promise; 29 | }; 30 | 31 | let retryCount = 5, 32 | poolSize = 100; 33 | 34 | export function setRetryCount(count: number) { 35 | retryCount = count; 36 | } 37 | 38 | export function setFetchPoolSize(size: number) { 39 | poolSize = size; 40 | } 41 | 42 | let _fetch: WrappedFetch = wrappedFetch(fetchImpl); 43 | 44 | /** 45 | * Allows customizing the fetch implementation used by the generator. 46 | */ 47 | export function setFetch(fetch: typeof globalThis.fetch | WrappedFetch) { 48 | _fetch = wrappedFetch(fetch as WrappedFetch); 49 | } 50 | 51 | export { clearCache, _fetch as fetch }; 52 | 53 | /** 54 | * Wraps a fetch request with pooling, and retry logic on exceptions (emfile / network errors). 55 | */ 56 | function wrappedFetch(fetch: FetchFn): WrappedFetch { 57 | const wrappedFetch = async function (url: URL | string, ...args: any[]) { 58 | url = url.toString(); 59 | let retries = 0; 60 | try { 61 | await pushFetchPool(); 62 | while (true) { 63 | try { 64 | return await fetch(url, ...args); 65 | } catch (e) { 66 | if (retries++ >= retryCount) throw e; 67 | } 68 | } 69 | } finally { 70 | popFetchPool(); 71 | } 72 | }; 73 | wrappedFetch.arrayBuffer = async function (url, ...args) { 74 | url = url.toString(); 75 | let retries = 0; 76 | try { 77 | await pushFetchPool(); 78 | while (true) { 79 | try { 80 | var res = await fetch(url, ...args); 81 | } catch (e) { 82 | if (retries++ >= retryCount) throw e; 83 | continue; 84 | } 85 | switch (res.status) { 86 | case 200: 87 | case 304: 88 | break; 89 | // not found = null 90 | case 404: 91 | return null; 92 | default: 93 | throw new Error(`Invalid status code ${res.status}`); 94 | } 95 | try { 96 | return await res.arrayBuffer(); 97 | } catch (e) { 98 | if ( 99 | (retries++ >= retryCount && e.code === "ERR_SOCKET_TIMEOUT") || 100 | e.code === "ETIMEOUT" || 101 | e.code === "ECONNRESET" || 102 | e.code === "FETCH_ERROR" 103 | ) { 104 | } 105 | } 106 | } 107 | } finally { 108 | popFetchPool(); 109 | } 110 | }; 111 | wrappedFetch.text = async function (url, ...args) { 112 | const arrayBuffer = await this.arrayBuffer(url, ...args); 113 | if (!arrayBuffer) return null; 114 | return new TextDecoder().decode(arrayBuffer); 115 | }; 116 | return wrappedFetch; 117 | } 118 | 119 | // restrict in-flight fetches to a pool of 100 120 | let p = []; 121 | let c = 0; 122 | function pushFetchPool() { 123 | if (++c > poolSize) return new Promise((r) => p.push(r)); 124 | } 125 | function popFetchPool() { 126 | c--; 127 | if (p.length) p.shift()(); 128 | } 129 | -------------------------------------------------------------------------------- /src/common/integrity.ts: -------------------------------------------------------------------------------- 1 | let _nodeCrypto; 2 | 3 | export async function getIntegrityNodeLegacy( 4 | buf: Uint8Array | string 5 | ): Promise<`sha384-${string}`> { 6 | const hash = ( 7 | _nodeCrypto || (_nodeCrypto = await (0, eval)('import("node:crypto")')) 8 | ).createHash("sha384"); 9 | hash.update(buf); 10 | return `sha384-${hash.digest("base64")}`; 11 | } 12 | 13 | export let getIntegrity = async function getIntegrity( 14 | buf: Uint8Array | string 15 | ): Promise<`sha384-${string}`> { 16 | const data = typeof buf === "string" ? new TextEncoder().encode(buf) : buf; 17 | const hashBuffer = await crypto.subtle.digest("SHA-384", data); 18 | const hashArray = Array.from(new Uint8Array(hashBuffer)); 19 | const hashBase64 = btoa(String.fromCharCode(...hashArray)); 20 | return `sha384-${hashBase64}`; 21 | }; 22 | 23 | if (typeof crypto === "undefined") getIntegrity = getIntegrityNodeLegacy; 24 | -------------------------------------------------------------------------------- /src/common/json.ts: -------------------------------------------------------------------------------- 1 | import { JspmError } from "./err.js"; 2 | import { SourceStyle, detectStyle } from "./source-style.js"; 3 | 4 | export function parseStyled( 5 | source: string, 6 | fileName?: string 7 | ): { json: any; style: SourceStyle } { 8 | // remove any byte order mark 9 | if (source.startsWith("\uFEFF")) source = source.substr(1); 10 | 11 | let style = detectStyle(source); 12 | try { 13 | return { json: JSON.parse(source), style }; 14 | } catch (e) { 15 | throw new JspmError( 16 | `Error parsing JSON file${fileName ? " " + fileName : ""}` 17 | ); 18 | } 19 | } 20 | 21 | export function stringifyStyled(json: any, style: SourceStyle) { 22 | let jsonString = JSON.stringify(json, null, style.tab); 23 | 24 | return ( 25 | style.indent + 26 | jsonString 27 | .replace(/([^\\])""/g, "$1" + style.quote + style.quote) // empty strings 28 | .replace(/([^\\])"/g, "$1" + style.quote) 29 | .replace(/\n/g, style.newline + style.indent) + 30 | (style.trailingNewline || "") 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/common/log.ts: -------------------------------------------------------------------------------- 1 | export function createLogger() { 2 | let resolveQueue: () => void; 3 | let queuePromise = new Promise((resolve) => (resolveQueue = resolve)); 4 | let queue: { type: string; message: string }[] = []; 5 | 6 | const logStream = async function* () { 7 | while (true) { 8 | while (queue.length) yield queue.shift()!; 9 | await queuePromise; 10 | } 11 | }; 12 | 13 | function log(type: string, message: string) { 14 | if (queue.length) { 15 | queue.push({ type, message }); 16 | } else { 17 | queue = [{ type, message }]; 18 | const _resolveQueue = resolveQueue; 19 | queuePromise = new Promise((resolve) => (resolveQueue = resolve)); 20 | _resolveQueue(); 21 | } 22 | } 23 | 24 | return { log, logStream }; 25 | } 26 | 27 | export type Log = (type: string, message: string) => void; 28 | export type LogStream = () => AsyncGenerator< 29 | { type: string; message: string }, 30 | never, 31 | unknown 32 | >; 33 | -------------------------------------------------------------------------------- /src/common/pool.ts: -------------------------------------------------------------------------------- 1 | export class Pool { 2 | #POOL_SIZE = 10; 3 | #opCnt = 0; 4 | #cbs: (() => void)[] = []; 5 | constructor(POOL_SIZE: number) { 6 | this.#POOL_SIZE = POOL_SIZE; 7 | } 8 | async queue() { 9 | if (++this.#opCnt > this.#POOL_SIZE) 10 | await new Promise((resolve) => this.#cbs.push(resolve as () => {})); 11 | } 12 | pop() { 13 | this.#opCnt--; 14 | const cb = this.#cbs.pop(); 15 | if (cb) cb(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/common/source-style.ts: -------------------------------------------------------------------------------- 1 | import { isWindows } from "./env.js"; 2 | 3 | export interface SourceStyle { 4 | tab: string; 5 | newline: string; 6 | trailingNewline: string; 7 | indent: string; 8 | quote: string; 9 | } 10 | 11 | export const defaultStyle = { 12 | tab: " ", 13 | newline: isWindows ? "\r\n" : "\n", 14 | trailingNewline: isWindows ? "\r\n" : "\n", 15 | indent: "", 16 | quote: '"', 17 | }; 18 | 19 | export function detectNewline(source: string) { 20 | let newLineMatch = source.match(/\r?\n|\r(?!\n)/); 21 | if (newLineMatch) return newLineMatch[0]; 22 | return isWindows ? "\r\n" : "\n"; 23 | } 24 | 25 | export function detectIndent(source: string, newline: string) { 26 | let indent: string | undefined = undefined; 27 | // best-effort tab detection 28 | // yes this is overkill, but it avoids possibly annoying edge cases 29 | let lines = source.split(newline); 30 | for (const line of lines) { 31 | const curIndent = line.match(/^\s*[^\s]/); 32 | if (curIndent && (indent === undefined || curIndent.length < indent.length)) 33 | indent = curIndent[0].slice(0, -1); 34 | } 35 | indent = indent || ""; 36 | lines = lines.map((line) => line.slice(indent!.length)); 37 | let tabSpaces = lines.map((line) => line.match(/^[ \t]*/)?.[0] || "") || []; 38 | let tabDifferenceFreqs = new Map(); 39 | let lastLength = 0; 40 | tabSpaces.forEach((tabSpace) => { 41 | let diff = Math.abs(tabSpace.length - lastLength); 42 | if (diff !== 0) 43 | tabDifferenceFreqs.set(diff, (tabDifferenceFreqs.get(diff) || 0) + 1); 44 | lastLength = tabSpace.length; 45 | }); 46 | let bestTabLength = 0; 47 | for (const tabLength of tabDifferenceFreqs.keys()) { 48 | if ( 49 | !bestTabLength || 50 | tabDifferenceFreqs.get(tabLength)! >= 51 | tabDifferenceFreqs.get(bestTabLength)! 52 | ) 53 | bestTabLength = tabLength; 54 | } 55 | // having determined the most common spacing difference length, 56 | // generate samples of this tab length from the end of each line space 57 | // the most common sample is then the tab string 58 | let tabSamples = new Map(); 59 | tabSpaces.forEach((tabSpace) => { 60 | let sample = tabSpace.substr(tabSpace.length - bestTabLength); 61 | tabSamples.set(sample, (tabSamples.get(sample) || 0) + 1); 62 | }); 63 | let bestTabSample = ""; 64 | for (const [sample, freq] of tabSamples) { 65 | if (!bestTabSample || freq > tabSamples.get(bestTabSample)!) 66 | bestTabSample = sample; 67 | } 68 | if ( 69 | lines.length < 5 && 70 | lines.reduce((cnt, line) => cnt + line.length, 0) < 100 71 | ) 72 | bestTabSample = " "; 73 | return { indent: indent || "", tab: bestTabSample }; 74 | } 75 | 76 | export function detectStyle(source: string): SourceStyle { 77 | let style = Object.assign({}, defaultStyle); 78 | 79 | style.newline = detectNewline(source); 80 | 81 | let { indent, tab } = detectIndent(source, style.newline); 82 | style.indent = indent; 83 | style.tab = tab; 84 | 85 | let quoteMatch = source.match(/"|'/); 86 | if (quoteMatch) style.quote = quoteMatch[0]; 87 | 88 | style.trailingNewline = 89 | source && source.match(new RegExp(style.newline + "$")) 90 | ? style.newline 91 | : ""; 92 | 93 | return style; 94 | } 95 | -------------------------------------------------------------------------------- /src/common/str.ts: -------------------------------------------------------------------------------- 1 | const wsRegEx = /^\s+/; 2 | 3 | export class Replacer { 4 | source: string; 5 | offsetTable: [number, number][] = []; 6 | constructor(source: string) { 7 | this.source = source; 8 | } 9 | 10 | replace(start: number, end: number, replacement: string) { 11 | const startOffset = findOffset(this.offsetTable, start); 12 | const endOffset = findOffset(this.offsetTable, end); 13 | 14 | this.source = 15 | this.source.slice(0, start + startOffset) + 16 | replacement + 17 | this.source.slice(end + endOffset); 18 | addOffset( 19 | this.offsetTable, 20 | end, 21 | replacement.length - (end + endOffset - start - startOffset) 22 | ); 23 | } 24 | 25 | remove(start: number, end: number, trimWs: boolean | RegExp = false) { 26 | this.replace(start, end, ""); 27 | if (trimWs) { 28 | if (typeof trimWs === "boolean") trimWs = wsRegEx; 29 | const endIndex = this.idx(end); 30 | const [wsMatch] = this.source.slice(endIndex).match(trimWs) ?? []; 31 | this.source = 32 | this.source.slice(0, endIndex) + 33 | this.source.slice(endIndex + (wsMatch?.length ?? 0)); 34 | addOffset(this.offsetTable, end, -(wsMatch?.length ?? 0)); 35 | } 36 | } 37 | 38 | idx(idx: number) { 39 | return idx + findOffset(this.offsetTable, idx); 40 | } 41 | } 42 | 43 | function addOffset( 44 | offsetTable: [number, number][], 45 | idx: number, 46 | offset: number 47 | ) { 48 | let i = offsetTable.length, 49 | eq = false; 50 | while (i-- > 0) { 51 | const [offsetIdx] = offsetTable[i]; 52 | if (offsetIdx < idx || (offsetIdx === idx && (eq = true))) break; 53 | } 54 | if (eq) offsetTable.splice(i, 1, [idx, offset + offsetTable[i][1]]); 55 | else offsetTable.splice(i + 1, 0, [idx, offset]); 56 | } 57 | 58 | function findOffset(offsetTable: [number, number][], idx: number) { 59 | let curOffset = 0; 60 | for (const [offsetIdx, offset] of offsetTable) { 61 | if (offsetIdx > idx) break; 62 | curOffset += offset; 63 | } 64 | return curOffset; 65 | } 66 | -------------------------------------------------------------------------------- /src/common/url.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | // @ts-ignore 3 | var document: any; 4 | // @ts-ignore 5 | var location: any; 6 | } 7 | 8 | export function isFetchProtocol(protocol) { 9 | return ( 10 | protocol === "file:" || 11 | protocol === "https:" || 12 | protocol === "http:" || 13 | protocol === "data:" 14 | ); 15 | } 16 | 17 | export let baseUrl: URL; 18 | // @ts-ignore 19 | if (typeof Deno !== "undefined") { 20 | // @ts-ignore 21 | const denoCwd = Deno.cwd(); 22 | baseUrl = new URL( 23 | "file://" + (denoCwd[0] === "/" ? "" : "/") + denoCwd + "/" 24 | ); 25 | } else if (typeof process !== "undefined" && process.versions.node) { 26 | baseUrl = new URL("file://" + process.cwd() + "/"); 27 | } else if ((typeof document as any) !== "undefined") { 28 | baseUrl = new URL(document.baseURI); 29 | } 30 | if (!baseUrl && typeof location !== "undefined") { 31 | baseUrl = new URL(location.href); 32 | } 33 | baseUrl.search = baseUrl.hash = ""; 34 | 35 | export function resolveUrl( 36 | url: string, 37 | mapUrl: URL, 38 | rootUrl: URL | null 39 | ): string { 40 | if (url.startsWith("/")) 41 | return rootUrl 42 | ? new URL("." + url.slice(url[1] === "/" ? 1 : 0), rootUrl).href 43 | : url; 44 | return new URL(url, mapUrl).href; 45 | } 46 | 47 | export function importedFrom(parentUrl?: string | URL) { 48 | if (!parentUrl) return ""; 49 | return ` imported from ${parentUrl}`; 50 | } 51 | 52 | function matchesRoot(url: URL, baseUrl: URL) { 53 | return ( 54 | url.protocol === baseUrl.protocol && 55 | url.host === baseUrl.host && 56 | url.port === baseUrl.port && 57 | url.username === baseUrl.username && 58 | url.password === baseUrl.password 59 | ); 60 | } 61 | 62 | export function relativeUrl(url: URL, baseUrl: URL, absolute = false) { 63 | const href = url.href; 64 | let baseUrlHref = baseUrl.href; 65 | if (!baseUrlHref.endsWith("/")) baseUrlHref += "/"; 66 | if (href.startsWith(baseUrlHref)) 67 | return (absolute ? "/" : "./") + href.slice(baseUrlHref.length); 68 | if (!matchesRoot(url, baseUrl)) return url.href; 69 | if (absolute) return url.href; 70 | const baseUrlPath = baseUrl.pathname; 71 | const urlPath = url.pathname; 72 | const minLen = Math.min(baseUrlPath.length, urlPath.length); 73 | let sharedBaseIndex = -1; 74 | for (let i = 0; i < minLen; i++) { 75 | if (baseUrlPath[i] !== urlPath[i]) break; 76 | if (urlPath[i] === "/") sharedBaseIndex = i; 77 | } 78 | return ( 79 | "../".repeat(baseUrlPath.slice(sharedBaseIndex + 1).split("/").length - 1) + 80 | urlPath.slice(sharedBaseIndex + 1) + 81 | url.search + 82 | url.hash 83 | ); 84 | } 85 | 86 | export function isURL(specifier: string) { 87 | try { 88 | if (specifier[0] === "#") return false; 89 | new URL(specifier); 90 | } catch { 91 | return false; 92 | } 93 | return true; 94 | } 95 | 96 | export function isPlain(specifier: string) { 97 | return !isRelative(specifier) && !isURL(specifier); 98 | } 99 | 100 | export function isRelative(specifier: string) { 101 | return ( 102 | specifier.startsWith("./") || 103 | specifier.startsWith("../") || 104 | specifier.startsWith("/") 105 | ); 106 | } 107 | 108 | export function urlToNiceStr(url: string) { 109 | if (url.startsWith(baseUrl.href)) 110 | return "./" + url.slice(baseUrl.href.length); 111 | } 112 | -------------------------------------------------------------------------------- /src/common/wrapper.ts: -------------------------------------------------------------------------------- 1 | import { fetch } from "../common/fetch.js"; 2 | import { parse, init } from "es-module-lexer"; 3 | 4 | export async function getMaybeWrapperUrl(moduleUrl, fetchOpts) { 5 | await init; 6 | const source = await (await fetch(moduleUrl, fetchOpts)).text(); 7 | const [imports, , facade] = parse(source); 8 | if (facade && imports.length) { 9 | try { 10 | return new URL(imports[0].n, moduleUrl).href; 11 | } catch {} 12 | } 13 | return moduleUrl; 14 | } 15 | -------------------------------------------------------------------------------- /src/generator-deno.ts: -------------------------------------------------------------------------------- 1 | import babel from "@babel/core"; 2 | import babelPresetTs from "@babel/preset-typescript"; 3 | import babelPluginSyntaxImportAttributes from "@babel/plugin-syntax-import-attributes"; 4 | import { createHash } from "crypto"; 5 | import { realpath } from "fs"; 6 | import { pathToFileURL } from "url"; 7 | 8 | import { setBabel as setBabelCjs } from "./trace/cjs.js"; 9 | import { setBabel as setBabelTs } from "./trace/ts.js"; 10 | import { setPathFns } from "./trace/resolver.js"; 11 | 12 | setBabelCjs(babel); 13 | setBabelTs(babel, babelPresetTs, babelPluginSyntaxImportAttributes); 14 | setPathFns(realpath, pathToFileURL); 15 | 16 | export * from "./generator.js"; 17 | -------------------------------------------------------------------------------- /src/generator.test.ts: -------------------------------------------------------------------------------- 1 | import { getDefaultProviders, parseUrlPkg } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | { 5 | const pkg = await parseUrlPkg("https://ga.jspm.io/npm:react@17.0.1/index.js"); 6 | assert.strictEqual(pkg.source.provider, "jspm.io"); 7 | assert.strictEqual(pkg.source.layer, "default"); 8 | assert.strictEqual(pkg.pkg.name, "react"); 9 | assert.strictEqual(pkg.pkg.version, "17.0.1"); 10 | assert.strictEqual(pkg.pkg.registry, "npm"); 11 | } 12 | 13 | { 14 | const ps = getDefaultProviders(); 15 | assert(ps.includes("jspm.io#system")); 16 | assert(ps.includes("skypack")); 17 | } 18 | -------------------------------------------------------------------------------- /src/install/lock.test.ts: -------------------------------------------------------------------------------- 1 | import { ExactModule } from "@jspm/generator/install/package.js"; 2 | import { translateProvider } from "@jspm/generator/install/lock.js"; 3 | import { Generator } from "@jspm/generator"; 4 | import { encodeBase64 } from "@jspm/generator/common/b64.js"; 5 | import { strictEqual } from "assert"; 6 | 7 | const rootUrl = new URL("../../", import.meta.url); 8 | const g = new Generator({ 9 | mapUrl: rootUrl.href, 10 | }); 11 | const r = g.traceMap.resolver; 12 | 13 | { 14 | /* changeProvider tests */ 15 | async function testForRegistry( 16 | registry: string, 17 | n: string, 18 | v: string, 19 | isNull: boolean = false 20 | ) { 21 | const mdl: ExactModule = { 22 | pkg: { 23 | name: n, 24 | version: v, 25 | registry, 26 | }, 27 | subpath: null, 28 | source: { provider: "test", layer: "default" }, 29 | }; 30 | 31 | // Should have switched to "npm" registry, as that's what jspm.io tracks: 32 | const provider = { provider: "jspm.io", layer: "default" }; 33 | const res = await translateProvider(mdl, provider, r, rootUrl); 34 | if (isNull) { 35 | strictEqual(res, null); 36 | } else { 37 | strictEqual(res.pkg.name, "chalk"); 38 | strictEqual(res.pkg.registry, "npm"); 39 | strictEqual(res.pkg.version, "4.1.2"); 40 | } 41 | } 42 | 43 | // Must match the version of "chalk" installed locally! 44 | await testForRegistry("npm", "chalk", "4.1.2"); 45 | await testForRegistry( 46 | "node_modules", 47 | "chalk", 48 | encodeBase64(new URL("./node_modules/chalk/", rootUrl).href) 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/install/pjson.ts: -------------------------------------------------------------------------------- 1 | import * as json from "../common/json.js"; 2 | // @ts-ignore 3 | import { readFileSync, writeFileSync } from "fs"; 4 | import { Resolver } from "../trace/resolver.js"; 5 | import { PackageConfig } from "../install/package.js"; 6 | 7 | export type DependenciesField = 8 | | "dependencies" 9 | | "devDependencies" 10 | | "peerDependencies" 11 | | "optionalDependencies"; 12 | 13 | type ExportsTarget = 14 | | string 15 | | null 16 | | { [condition: string]: ExportsTarget } 17 | | ExportsTarget[]; 18 | 19 | export interface PackageJson { 20 | registry?: string; 21 | name?: string; 22 | version?: string; 23 | main?: string; 24 | files?: string[]; 25 | browser?: string | Record; 26 | exports?: ExportsTarget | Record; 27 | type?: string; 28 | dependencies?: Record; 29 | peerDependencies?: Record; 30 | optionalDependencies?: Record; 31 | devDependencies?: Record; 32 | } 33 | 34 | export async function updatePjson( 35 | resolver: Resolver, 36 | pjsonBase: string, 37 | updateFn: ( 38 | pjson: PackageJson 39 | ) => void | PackageJson | Promise 40 | ): Promise { 41 | const pjsonUrl = new URL("package.json", pjsonBase); 42 | let input; 43 | try { 44 | input = readFileSync(pjsonUrl).toString(); 45 | } catch (e) { 46 | input = "{}\n"; 47 | } 48 | let { json: pjson, style } = json.parseStyled(input); 49 | pjson = (await updateFn(pjson)) || pjson; 50 | const output = json.stringifyStyled(pjson, style); 51 | if (output === input) return false; 52 | writeFileSync(pjsonUrl, json.stringifyStyled(pjson, style)); 53 | resolver.pcfgs[pjsonBase] = pjson as PackageConfig; 54 | return true; 55 | } 56 | -------------------------------------------------------------------------------- /src/providers/esmsh.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ExactPackage, 3 | LatestPackageTarget, 4 | PackageConfig, 5 | } from "../install/package.js"; 6 | import { Resolver } from "../trace/resolver.js"; 7 | // @ts-ignore 8 | import { fetch } from "../common/fetch.js"; 9 | import { JspmError } from "../common/err.js"; 10 | import { fetchVersions } from "./jspm.js"; 11 | // @ts-ignore 12 | import { SemverRange } from "sver"; 13 | import { importedFrom } from "../common/url.js"; 14 | 15 | const cdnUrl = "https://esm.sh/"; 16 | 17 | export async function pkgToUrl(pkg: ExactPackage): Promise<`${string}/`> { 18 | // The wildcard '*' at the end tells the esm.sh CDN to externalise all 19 | // dependencies instead of bundling them into the returned module file. 20 | // see https://esm.sh/#docs 21 | return `${cdnUrl}*${pkg.name}@${pkg.version}/`; 22 | } 23 | 24 | const exactPkgRegEx = 25 | /^(?:v\d+\/)?\*?((?:@[^/\\%@]+\/)?[^./\\%@][^/\\%@]*)@([^\/]+)(\/.*)?$/; 26 | 27 | export function parseUrlPkg(url: string) { 28 | if (!url.startsWith(cdnUrl)) return; 29 | const [, name, version] = url.slice(cdnUrl.length).match(exactPkgRegEx) || []; 30 | if (!name || !version) return; 31 | return { registry: "npm", name, version }; 32 | } 33 | 34 | export async function getPackageConfig( 35 | this: Resolver, 36 | pkgUrl: string 37 | ): Promise { 38 | const res = await fetch(`${pkgUrl}package.json`, this.fetchOpts); 39 | switch (res.status) { 40 | case 200: 41 | case 304: 42 | break; 43 | case 400: 44 | case 401: 45 | case 403: 46 | case 404: 47 | case 406: 48 | case 500: 49 | this.pcfgs[pkgUrl] = null; 50 | return; 51 | default: 52 | throw new JspmError( 53 | `Invalid status code ${res.status} reading package config for ${pkgUrl}. ${res.statusText}` 54 | ); 55 | } 56 | 57 | return await res.json(); 58 | } 59 | 60 | export async function resolveLatestTarget( 61 | this: Resolver, 62 | target: LatestPackageTarget, 63 | layer: string, 64 | parentUrl: string 65 | ): Promise { 66 | const { registry, name, range, unstable } = target; 67 | const versions = await fetchVersions.call(this, name); 68 | const semverRange = new SemverRange(String(range) || "*", unstable); 69 | const version = semverRange.bestMatch(versions, unstable); 70 | if (version) { 71 | return { registry, name, version: version.toString() }; 72 | } 73 | throw new JspmError( 74 | `Unable to resolve ${registry}:${name}@${range} to a valid version${importedFrom( 75 | parentUrl 76 | )}` 77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /src/providers/index.test.ts: -------------------------------------------------------------------------------- 1 | import { getDefaultProviderStrings } from "@jspm/generator/providers/index.js"; 2 | import assert from "assert"; 3 | 4 | let ps: string[] = getDefaultProviderStrings(); 5 | assert.ok(ps.includes("jspm.io")); 6 | assert.ok(ps.includes("jspm.io#system")); 7 | assert.ok(ps.includes("esm.sh")); 8 | -------------------------------------------------------------------------------- /src/providers/index.ts: -------------------------------------------------------------------------------- 1 | import * as deno from "./deno.js"; 2 | import * as jspm from "./jspm.js"; 3 | import * as skypack from "./skypack.js"; 4 | import * as jsdelivr from "./jsdelivr.js"; 5 | import * as unpkg from "./unpkg.js"; 6 | import * as node from "./node.js"; 7 | import * as esmsh from "./esmsh.js"; 8 | import { 9 | PackageConfig, 10 | ExactPackage, 11 | LatestPackageTarget, 12 | } from "../install/package.js"; 13 | import { Resolver } from "../trace/resolver.js"; 14 | import { Install } from "../generator.js"; 15 | import { JspmError } from "../common/err.js"; 16 | 17 | export interface Provider { 18 | parseUrlPkg( 19 | this: Resolver, 20 | url: string 21 | ): 22 | | ExactPackage 23 | | { pkg: ExactPackage; subpath: `./${string}` | null; layer: string } 24 | | null; 25 | 26 | pkgToUrl( 27 | this: Resolver, 28 | pkg: ExactPackage, 29 | layer: string 30 | ): Promise<`${string}/`>; 31 | 32 | resolveLatestTarget( 33 | this: Resolver, 34 | target: LatestPackageTarget, 35 | layer: string, 36 | parentUrl: string 37 | ): Promise; 38 | 39 | ownsUrl?(this: Resolver, url: string): boolean; 40 | 41 | resolveBuiltin?( 42 | this: Resolver, 43 | specifier: string, 44 | env: string[] 45 | ): string | Install | null; 46 | 47 | getPackageConfig?( 48 | this: Resolver, 49 | pkgUrl: string 50 | ): Promise; 51 | 52 | supportedLayers?: string[]; 53 | 54 | configure?(config: any): void; 55 | } 56 | 57 | export const defaultProviders: Record = { 58 | deno, 59 | jsdelivr, 60 | node, 61 | skypack, 62 | unpkg, 63 | "esm.sh": esmsh, 64 | "jspm.io": jspm, 65 | }; 66 | 67 | export function getProvider(name: string, providers: Record) { 68 | const provider = providers[name]; 69 | if (provider) return provider; 70 | throw new JspmError(`No provider named "${name}" has been defined.`); 71 | } 72 | 73 | // Apply provider configurations 74 | export function configureProviders( 75 | providerConfig: Record, 76 | providers: Record 77 | ) { 78 | for (const [providerName, provider] of Object.entries(providers)) { 79 | if (provider.configure) { 80 | provider.configure(providerConfig[providerName] || {}); 81 | } 82 | } 83 | } 84 | 85 | export function getDefaultProviderStrings() { 86 | let res = []; 87 | for (const [name, provider] of Object.entries(defaultProviders)) { 88 | for (const layer of provider.supportedLayers ?? ["default"]) 89 | res.push(`${name}${layer === "default" ? "" : `#${layer}`}`); 90 | } 91 | 92 | return res; 93 | } 94 | 95 | export const registryProviders: Record = { 96 | "denoland:": "deno", 97 | "deno:": "deno", 98 | }; 99 | 100 | export const mappableSchemes = new Set(["npm", "deno", "node"]); 101 | 102 | export const builtinSchemes = new Set(["node", "deno"]); 103 | -------------------------------------------------------------------------------- /src/providers/jsdelivr.ts: -------------------------------------------------------------------------------- 1 | import { JspmError } from "../common/err.js"; 2 | import { importedFrom } from "../common/url.js"; 3 | import { ExactPackage, LatestPackageTarget } from "../install/package.js"; 4 | import { Resolver } from "../trace/resolver.js"; 5 | import { fetchVersions } from "./jspm.js"; 6 | // @ts-ignore 7 | import { SemverRange } from "sver"; 8 | 9 | const cdnUrl = "https://cdn.jsdelivr.net/"; 10 | 11 | export async function pkgToUrl(pkg: ExactPackage): Promise<`${string}/`> { 12 | return `${cdnUrl}${pkg.registry}/${pkg.name}@${pkg.version}/`; 13 | } 14 | 15 | const exactPkgRegEx = 16 | /^([^\/]+)\/((?:@[^/\\%@]+\/)?[^./\\%@][^/\\%@]*)@([^\/]+)(\/.*)?$/; 17 | 18 | export function parseUrlPkg(url: string) { 19 | if (!url.startsWith(cdnUrl)) return; 20 | const [, registry, name, version] = 21 | url.slice(cdnUrl.length).match(exactPkgRegEx) || []; 22 | return { registry, name, version }; 23 | } 24 | 25 | export async function resolveLatestTarget( 26 | this: Resolver, 27 | target: LatestPackageTarget, 28 | layer: string, 29 | parentUrl: string 30 | ): Promise { 31 | const { registry, name, range, unstable } = target; 32 | const versions = await fetchVersions.call(this, name); 33 | const semverRange = new SemverRange(String(range) || "*", unstable); 34 | const version = semverRange.bestMatch(versions, unstable); 35 | if (version) { 36 | return { registry, name, version: version.toString() }; 37 | } 38 | throw new JspmError( 39 | `Unable to resolve ${registry}:${name}@${range} to a valid version${importedFrom( 40 | parentUrl 41 | )}` 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /src/providers/node.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ExactPackage, 3 | LatestPackageTarget, 4 | PackageConfig, 5 | } from "../install/package.js"; 6 | import { SemverRange } from "sver"; 7 | import { 8 | resolveLatestTarget as resolveLatestTargetJspm, 9 | pkgToUrl as pkgToUrlJspm, 10 | } from "./jspm.js"; 11 | import { Install } from "../generator.js"; 12 | 13 | export const nodeBuiltinSet = new Set([ 14 | "_http_agent", 15 | "_http_client", 16 | "_http_common", 17 | "_http_incoming", 18 | "_http_outgoing", 19 | "_http_server", 20 | "_stream_duplex", 21 | "_stream_passthrough", 22 | "_stream_readable", 23 | "_stream_transform", 24 | "_stream_wrap", 25 | "_stream_writable", 26 | "_tls_common", 27 | "_tls_wrap", 28 | "assert", 29 | "assert/strict", 30 | "async_hooks", 31 | "buffer", 32 | "child_process", 33 | "cluster", 34 | "console", 35 | "constants", 36 | "crypto", 37 | "dgram", 38 | "diagnostics_channel", 39 | "dns", 40 | "dns/promises", 41 | "domain", 42 | "events", 43 | "fs", 44 | "fs/promises", 45 | "http", 46 | "http2", 47 | "https", 48 | "inspector", 49 | "module", 50 | "net", 51 | "os", 52 | "path", 53 | "path/posix", 54 | "path/win32", 55 | "perf_hooks", 56 | "process", 57 | "punycode", 58 | "querystring", 59 | "readline", 60 | "repl", 61 | "stream", 62 | "stream/promises", 63 | "string_decoder", 64 | "sys", 65 | "timers", 66 | "timers/promises", 67 | "tls", 68 | "trace_events", 69 | "tty", 70 | "url", 71 | "util", 72 | "util/types", 73 | "v8", 74 | "vm", 75 | "wasi", 76 | "worker_threads", 77 | "zlib", 78 | ]); 79 | 80 | export async function pkgToUrl( 81 | pkg: ExactPackage, 82 | layer: string 83 | ): Promise<`${string}/`> { 84 | if (pkg.registry !== "node") return pkgToUrlJspm(pkg, layer); 85 | return `node:${pkg.name}/`; 86 | } 87 | 88 | export function resolveBuiltin( 89 | specifier: string, 90 | env: string[] 91 | ): string | Install | undefined { 92 | let builtin = specifier.startsWith("node:") 93 | ? specifier.slice(5) 94 | : nodeBuiltinSet.has(specifier) 95 | ? specifier 96 | : null; 97 | if (!builtin) return; 98 | 99 | // Deno supports all node builtins via bare "node:XXX" specifiers. As of 100 | // std@0.178.0, the standard library no longer ships node polyfills, so we 101 | // should always install builtins as base specifiers. This does mean that we 102 | // no longer support old versions of deno unless they use --compat. 103 | if (env.includes("deno") || env.includes("node")) { 104 | return `node:${builtin}`; 105 | } 106 | 107 | // Strip the subpath for subpathed builtins 108 | if (builtin.includes("/")) builtin = builtin.split("/")[0]; 109 | 110 | return { 111 | target: { 112 | pkgTarget: { 113 | registry: "npm", 114 | name: "@jspm/core", 115 | ranges: [new SemverRange("*")], 116 | unstable: true, 117 | }, 118 | installSubpath: `./nodelibs/${builtin}`, 119 | }, 120 | alias: builtin, 121 | }; 122 | } 123 | 124 | // Special "." export means a file package (not a folder package) 125 | export async function getPackageConfig(): Promise { 126 | return { 127 | exports: { 128 | ".": ".", 129 | }, 130 | }; 131 | } 132 | 133 | export async function resolveLatestTarget( 134 | target: LatestPackageTarget, 135 | layer: string, 136 | parentUrl: string 137 | ): Promise { 138 | if (target.registry !== "npm" || target.name !== "@jspm/core") return null; 139 | return resolveLatestTargetJspm.call( 140 | this, 141 | { 142 | registry: "npm", 143 | name: "@jspm/core", 144 | range: new SemverRange("*"), 145 | unstable: true, 146 | }, 147 | layer, 148 | parentUrl 149 | ); 150 | } 151 | 152 | export function parseUrlPkg(url: string) { 153 | if (!url.startsWith("node:")) return; 154 | let name = url.slice(5); 155 | if (name.endsWith("/")) name = name.slice(0, -1); 156 | return { registry: "node", name, version: "" }; 157 | } 158 | -------------------------------------------------------------------------------- /src/providers/skypack.ts: -------------------------------------------------------------------------------- 1 | import { JspmError } from "../common/err.js"; 2 | import { importedFrom } from "../common/url.js"; 3 | import { ExactPackage, LatestPackageTarget } from "../install/package.js"; 4 | import { Resolver } from "../trace/resolver.js"; 5 | import { fetchVersions } from "./jspm.js"; 6 | // @ts-ignore 7 | import { SemverRange } from "sver"; 8 | 9 | const cdnUrl = "https://cdn.skypack.dev/"; 10 | 11 | export async function pkgToUrl(pkg: ExactPackage): Promise<`${string}/`> { 12 | return `${cdnUrl}${pkg.name}@${pkg.version}/`; 13 | } 14 | 15 | const exactPkgRegEx = /^((?:@[^/\\%@]+\/)?[^./\\%@][^/\\%@]*)@([^\/]+)(\/.*)?$/; 16 | 17 | export function parseUrlPkg(url: string) { 18 | if (!url.startsWith(cdnUrl)) return; 19 | const [, name, version] = url.slice(cdnUrl.length).match(exactPkgRegEx) || []; 20 | if (!name || !version) return; 21 | return { registry: "npm", name, version }; 22 | } 23 | 24 | export async function resolveLatestTarget( 25 | this: Resolver, 26 | target: LatestPackageTarget, 27 | layer: string, 28 | parentUrl: string 29 | ): Promise { 30 | const { registry, name, range, unstable } = target; 31 | const versions = await fetchVersions.call(this, name); 32 | const semverRange = new SemverRange(String(range) || "*", unstable); 33 | const version = semverRange.bestMatch(versions, unstable); 34 | if (version) { 35 | return { registry, name, version: version.toString() }; 36 | } 37 | throw new JspmError( 38 | `Unable to resolve ${registry}:${name}@${range} to a valid version${importedFrom( 39 | parentUrl 40 | )}` 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/providers/unpkg.ts: -------------------------------------------------------------------------------- 1 | import { JspmError } from "../common/err.js"; 2 | import { importedFrom } from "../common/url.js"; 3 | import { ExactPackage, LatestPackageTarget } from "../install/package.js"; 4 | import { Resolver } from "../trace/resolver.js"; 5 | import { fetchVersions } from "./jspm.js"; 6 | // @ts-ignore 7 | import { SemverRange } from "sver"; 8 | 9 | const cdnUrl = "https://unpkg.com/"; 10 | 11 | export async function pkgToUrl(pkg: ExactPackage): Promise<`${string}/`> { 12 | return `${cdnUrl}${pkg.name}@${pkg.version}/`; 13 | } 14 | 15 | const exactPkgRegEx = /^((?:@[^/\\%@]+\/)?[^./\\%@][^/\\%@]*)@([^\/]+)(\/.*)?$/; 16 | 17 | export function parseUrlPkg(url: string) { 18 | if (!url.startsWith(cdnUrl)) return; 19 | const [, name, version] = url.slice(cdnUrl.length).match(exactPkgRegEx) || []; 20 | if (name && version) { 21 | return { registry: "npm", name, version }; 22 | } 23 | } 24 | 25 | export async function resolveLatestTarget( 26 | this: Resolver, 27 | target: LatestPackageTarget, 28 | layer: string, 29 | parentUrl: string 30 | ): Promise { 31 | const { registry, name, range, unstable } = target; 32 | const versions = await fetchVersions.call(this, name); 33 | const semverRange = new SemverRange(String(range) || "*", unstable); 34 | const version = semverRange.bestMatch(versions, unstable); 35 | if (version) { 36 | return { registry, name, version: version.toString() }; 37 | } 38 | throw new JspmError( 39 | `Unable to resolve ${registry}:${name}@${range} to a valid version${importedFrom( 40 | parentUrl 41 | )}` 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /src/trace/analysis.ts: -------------------------------------------------------------------------------- 1 | import { JspmError } from "../common/err.js"; 2 | import { getIntegrity } from "../common/integrity.js"; 3 | 4 | export type Analysis = 5 | | AnalysisData 6 | | { 7 | parseError: JspmError | Error; 8 | }; 9 | 10 | export interface AnalysisData { 11 | deps: string[]; 12 | dynamicDeps: string[]; 13 | cjsLazyDeps: string[] | null; 14 | format: 15 | | "esm" 16 | | "commonjs" 17 | | "system" 18 | | "json" 19 | | "typescript" 20 | | "wasm" 21 | | "css"; 22 | size: number; 23 | 24 | // for commonjs format, true iff the module uses a CJS-only global 25 | usesCjs?: boolean; 26 | integrity: `sha384-${string}`; 27 | } 28 | 29 | export { createTsAnalysis } from "./ts.js"; 30 | export { createCjsAnalysis } from "./cjs.js"; 31 | 32 | export async function createEsmAnalysis( 33 | imports: any[], 34 | source: string, 35 | url: string 36 | ): Promise { 37 | // Change the return type to Promise 38 | if (!imports.length && systemMatch(source)) 39 | return createSystemAnalysis(source, imports, url); 40 | const deps: string[] = []; 41 | const dynamicDeps: string[] = []; 42 | for (const impt of imports) { 43 | if (impt.d === -1) { 44 | if (!deps.includes(impt.n)) deps.push(impt.n); 45 | continue; 46 | } 47 | // dynamic import -> deoptimize trace all dependencies (and all their exports) 48 | if (impt.d >= 0) { 49 | if (impt.n) { 50 | try { 51 | dynamicDeps.push(impt.n); 52 | } catch (e) { 53 | console.warn( 54 | `TODO: Dynamic import custom expression tracing in ${url} for:\n\n${source.slice( 55 | impt.ss, 56 | impt.se 57 | )}\n` 58 | ); 59 | } 60 | } 61 | } 62 | } 63 | const size = source.length; 64 | return { 65 | deps, 66 | dynamicDeps, 67 | cjsLazyDeps: null, 68 | size, 69 | format: "esm", 70 | integrity: await getIntegrity(source), 71 | }; 72 | } 73 | const leadingCommentRegex = /^\s*(\/\*[\s\S]*?\*\/|\s*\/\/[^\n]*)*/; 74 | const registerRegex = 75 | /^\s*System\s*\.\s*register\s*\(\s*(\[[^\]]*\])\s*,\s*\(?function\s*\(\s*([^\),\s]+\s*(,\s*([^\),\s]+)\s*)?\s*)?\)/; 76 | 77 | function systemMatch(code) { 78 | const commentMatch = code.match(leadingCommentRegex); 79 | const offset = commentMatch ? commentMatch[0].length : 0; 80 | return code.slice(offset).match(registerRegex); 81 | } 82 | 83 | export async function createSystemAnalysis( 84 | source: string, 85 | imports: string[], 86 | url: string 87 | ): Promise { 88 | const [, rawDeps, contextId] = systemMatch(source) || []; 89 | if (!rawDeps) return createEsmAnalysis(imports, source, url); 90 | const deps = JSON.parse(rawDeps.replace(/'/g, '"')); 91 | const dynamicDeps: string[] = []; 92 | if (contextId) { 93 | const dynamicImport = `${contextId}.import(`; 94 | let i = -1; 95 | while ((i = source.indexOf(dynamicImport, i + 1)) !== -1) { 96 | const importStart = i + dynamicImport.length + 1; 97 | const quote = source[i + dynamicImport.length]; 98 | if (quote === '"' || quote === "'") { 99 | const importEnd = source.indexOf(quote, i + dynamicImport.length + 1); 100 | if (importEnd !== -1) { 101 | try { 102 | dynamicDeps.push( 103 | JSON.parse('"' + source.slice(importStart, importEnd) + '"') 104 | ); 105 | continue; 106 | } catch (e) {} 107 | } 108 | } 109 | console.warn("TODO: Dynamic import custom expression tracing."); 110 | } 111 | } 112 | const size = source.length; 113 | return { 114 | deps, 115 | dynamicDeps, 116 | cjsLazyDeps: null, 117 | size, 118 | format: "system", 119 | integrity: await getIntegrity(source), 120 | }; 121 | } 122 | -------------------------------------------------------------------------------- /test/api/alias.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "jspm.io", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.install({ target: "react@16", alias: "custom" }); 11 | const json = generator.getMap(); 12 | assert.strictEqual( 13 | json.imports.custom, 14 | "https://ga.jspm.io/npm:react@16.14.0/index.js" 15 | ); 16 | -------------------------------------------------------------------------------- /test/api/api.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "jspm.io", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.install("react@16"); 11 | const json = generator.getMap(); 12 | assert.strictEqual( 13 | json.imports.react, 14 | "https://ga.jspm.io/npm:react@16.14.0/index.js" 15 | ); 16 | 17 | assert.strictEqual( 18 | generator.importMap.resolve("react"), 19 | "https://ga.jspm.io/npm:react@16.14.0/index.js" 20 | ); 21 | 22 | const meta = generator.getAnalysis( 23 | "https://ga.jspm.io/npm:react@16.14.0/index.js" 24 | ); 25 | assert.deepStrictEqual(meta, { 26 | format: "esm", 27 | staticDeps: ["./cjs/react.production.min.js", "object-assign"], 28 | dynamicDeps: [], 29 | cjsLazyDeps: [], 30 | }); 31 | -------------------------------------------------------------------------------- /test/api/assets.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import { deepStrictEqual, strictEqual } from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "jspm.io", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.install("@shopify/polaris@13.4.0/build/esm/styles.css"); 11 | await generator.install({ target: "./local/assets", subpath: "./css" }); 12 | await generator.install({ target: "./local/assets", subpath: "./json" }); 13 | await generator.install({ target: "./local/assets", subpath: "./wasm" }); 14 | 15 | const json = generator.getMap(); 16 | 17 | deepStrictEqual(Object.keys(json.imports), [ 18 | "@shopify/polaris/build/esm/styles.css", 19 | "assets/css", 20 | "assets/json", 21 | "assets/wasm", 22 | ]); 23 | strictEqual(json.scopes["./local/assets/"]["#asdf"], "./local/assets/file.js"); 24 | -------------------------------------------------------------------------------- /test/api/core.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | env: ["production", "browser"], 7 | }); 8 | 9 | await generator.install("@babel/core"); 10 | const json = generator.getMap(); 11 | assert.ok(json.imports["@babel/core"]); 12 | assert.ok(Object.keys(json.scopes["https://ga.jspm.io/"]).length > 20); 13 | -------------------------------------------------------------------------------- /test/api/deduping.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | env: ["production", "browser"], 7 | 8 | // Test deduping in absence of install modifiers: 9 | latest: false, 10 | freeze: false, 11 | }); 12 | 13 | await generator.install({ target: "./local/react1" }); 14 | const json = JSON.stringify(generator.getMap(), null, 2); 15 | 16 | assert(json.indexOf("react@16") !== -1); 17 | assert(json.indexOf("react@17") === -1); 18 | -------------------------------------------------------------------------------- /test/api/devdependencies.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: new URL("./local/pkg/asdf", import.meta.url), 6 | defaultProvider: "jspm.io", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.link("localpkg/jquery"); 11 | const json = generator.getMap(); 12 | 13 | assert.ok(json.imports["localpkg/jquery"]); 14 | assert.ok(json.scopes["./"].jquery.includes("@2")); 15 | -------------------------------------------------------------------------------- /test/api/extract.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | inputMap: { 6 | imports: { 7 | react: "https://ga.jspm.io/npm:react@17.0.1/dev.index.js", 8 | "react-dom": "https://ga.jspm.io/npm:react-dom@17.0.1/dev.index.js", 9 | }, 10 | scopes: { 11 | "https://ga.jspm.io/": { 12 | "object-assign": "https://ga.jspm.io/npm:object-assign@4.1.0/index.js", 13 | scheduler: "https://ga.jspm.io/npm:scheduler@0.20.1/dev.index.js", 14 | "scheduler/tracing": 15 | "https://ga.jspm.io/npm:scheduler@0.20.1/dev.tracing.js", 16 | }, 17 | }, 18 | }, 19 | mapUrl: import.meta.url, 20 | defaultProvider: "jspm.io", 21 | env: ["production", "browser"], 22 | freeze: true, 23 | }); 24 | 25 | const { map } = await generator.extractMap("react"); 26 | 27 | assert.strictEqual( 28 | map.imports.react, 29 | "https://ga.jspm.io/npm:react@17.0.1/index.js" 30 | ); 31 | assert.strictEqual( 32 | map.scopes["https://ga.jspm.io/"]["object-assign"], 33 | "https://ga.jspm.io/npm:object-assign@4.1.0/index.js" 34 | ); 35 | assert.strictEqual(Object.keys(map.imports).length, 1); 36 | assert.strictEqual(Object.keys(map.scopes["https://ga.jspm.io/"]).length, 1); 37 | -------------------------------------------------------------------------------- /test/api/flat-dedupes.test.js: -------------------------------------------------------------------------------- 1 | // TODO: re-enable this test once the CDN builder can reliably build these 2 | // packages. At the moment this fails constantly from build errors. 3 | 4 | // import { Generator } from "@jspm/generator"; 5 | // import { strictEqual } from "assert"; 6 | 7 | // const BASE_CONFIG = { 8 | // mapUrl: "about:blank", 9 | // ignore: [ 10 | // "react", 11 | // "react/jsx-runtime", 12 | // "react-dom", 13 | // "react-dom/server", 14 | // "framer", 15 | // "framer-motion", 16 | // "framer-motion/three", 17 | // ], 18 | // env: ["production", "browser", "module"], 19 | // resolutions: { 20 | // three: "0.142.0", 21 | // zustand: "3.7.1", 22 | // ethers: "5.7.2", // workaround for incorrect version pin in @wagmi/core 23 | // }, 24 | // }; 25 | // 26 | // const generatorOne = new Generator({ 27 | // ...BASE_CONFIG, 28 | // }); 29 | // await generatorOne.install("@react-three/fiber"); 30 | // const mapOne = generatorOne.getMap(); 31 | // 32 | // strictEqual( 33 | // mapOne.scopes["https://ga.jspm.io/"].zustand, 34 | // "https://ga.jspm.io/npm:zustand@3.7.1/esm/index.js" 35 | // ); 36 | // 37 | // const generatorTwo = new Generator({ 38 | // ...BASE_CONFIG, 39 | // inputMap: mapOne, 40 | // }); 41 | // await generatorTwo.install("wagmi"); 42 | // const mapTwo = generatorTwo.getMap(); 43 | // 44 | // strictEqual( 45 | // mapTwo.scopes["https://ga.jspm.io/"].zustand, 46 | // "https://ga.jspm.io/npm:zustand@3.7.1/esm/index.js" 47 | // ); 48 | // 49 | // const generatorThree = new Generator({ 50 | // ...BASE_CONFIG, 51 | // inputMap: mapTwo, 52 | // }); 53 | // await generatorThree.install("connectkit"); 54 | // const mapThree = generatorThree.getMap(); 55 | // 56 | // strictEqual( 57 | // mapThree.scopes["https://ga.jspm.io/"].zustand, 58 | // "https://ga.jspm.io/npm:zustand@3.7.1/esm/index.js" 59 | // ); 60 | // 61 | // const generatorFour = new Generator({ 62 | // ...BASE_CONFIG, 63 | // inputMap: mapThree, 64 | // }); 65 | // await generatorFour.reinstall(); 66 | // const mapFour = generatorFour.getMap(); 67 | // 68 | // strictEqual( 69 | // mapFour.scopes["https://ga.jspm.io/"].zustand, 70 | // "https://ga.jspm.io/npm:zustand@3.7.1/esm/index.js" 71 | // ); 72 | // strictEqual( 73 | // mapFour.scopes["https://ga.jspm.io/"].three, 74 | // "https://ga.jspm.io/npm:three@0.142.0/build/three.module.js" 75 | // ); 76 | -------------------------------------------------------------------------------- /test/api/ignore.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | inputMap: { 7 | imports: { 8 | react: "./location/that/cant/be/traced.js", 9 | }, 10 | }, 11 | ignore: ["react"], 12 | env: ["production", "browser", "module"], 13 | }); 14 | 15 | await generator.install("@react-three/fiber@7.0.15"); 16 | 17 | const json = generator.getMap(); 18 | 19 | assert.deepEqual(json.imports, { 20 | react: "./location/that/cant/be/traced.js", 21 | "@react-three/fiber": 22 | "https://ga.jspm.io/npm:@react-three/fiber@7.0.15/dist/react-three-fiber.esm.js", 23 | }); 24 | assert(json.scopes["https://ga.jspm.io/"].hasOwnProperty("react") === false); 25 | -------------------------------------------------------------------------------- /test/api/input-map-self.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: "about:blank", 6 | inputMap: { 7 | imports: { 8 | "@babel/core": 9 | "https://ga.jspm.io/npm:@jspm/core@2.0.0-beta.8/nodelibs/@empty.js", 10 | "@babel/preset-typescript": 11 | "https://ga.jspm.io/npm:@jspm/core@2.0.0-beta.8/nodelibs/@empty.js", 12 | }, 13 | }, 14 | ignore: ["@babel/core", "@babel/preset-typescript"], 15 | env: ["source"], 16 | }); 17 | 18 | await generator.install(new URL("../../", import.meta.url).href); 19 | 20 | const json = generator.getMap(); 21 | 22 | assert.ok(JSON.stringify(json).length < 2000); 23 | -------------------------------------------------------------------------------- /test/api/inputmap.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | inputMap: { 7 | imports: { 8 | react: "https://cdn.skypack.dev/react", 9 | }, 10 | }, 11 | env: ["production", "browser"], 12 | }); 13 | 14 | await generator.install("react-dom@17"); 15 | 16 | const json = generator.getMap(); 17 | 18 | assert.deepEqual(json, { 19 | imports: { 20 | react: "https://cdn.skypack.dev/react", 21 | "react-dom": "https://ga.jspm.io/npm:react-dom@17.0.2/index.js", 22 | }, 23 | scopes: { 24 | "https://ga.jspm.io/": { 25 | "object-assign": "https://ga.jspm.io/npm:object-assign@4.1.1/index.js", 26 | scheduler: "https://ga.jspm.io/npm:scheduler@0.20.2/index.js", 27 | }, 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /test/api/install-dedupe.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | inputMap: { 6 | imports: { 7 | lit: "https://ga.jspm.io/npm:lit@2.2.4/index.js", 8 | "lit/directive.js": "https://ga.jspm.io/npm:lit@2.2.4/directive.js", 9 | }, 10 | scopes: { 11 | "https://ga.jspm.io/": { 12 | "@lit/reactive-element": 13 | "https://ga.jspm.io/npm:@lit/reactive-element@1.3.4/reactive-element.js", 14 | "lit-element/lit-element.js": 15 | "https://ga.jspm.io/npm:lit-element@3.2.2/lit-element.js", 16 | "lit-html": "https://ga.jspm.io/npm:lit-html@2.2.7/lit-html.js", 17 | "lit-html/directive.js": 18 | "https://ga.jspm.io/npm:lit-html@2.2.7/directive.js", 19 | }, 20 | }, 21 | }, 22 | mapUrl: import.meta.url, 23 | defaultProvider: "jspm.io", 24 | env: ["production", "browser"], 25 | }); 26 | 27 | await generator.install("lit@2.3/html.js"); 28 | const json = generator.getMap(); 29 | 30 | assert.strictEqual( 31 | json.imports.lit, 32 | "https://ga.jspm.io/npm:lit@2.3.1/index.js" 33 | ); 34 | -------------------------------------------------------------------------------- /test/api/install.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | inputMap: { 6 | imports: { 7 | react: "https://ga.jspm.io/npm:react@17.0.1/dev.index.js", 8 | }, 9 | scopes: { 10 | "https://ga.jspm.io/": { 11 | "lit-html": "https://ga.jspm.io/npm:lit-html@2.6.0/lit-html.js", 12 | }, 13 | }, 14 | }, 15 | mapUrl: import.meta.url, 16 | env: ["production", "browser"], 17 | resolutions: { 18 | lit: "2.6.1", 19 | }, 20 | }); 21 | 22 | // Install with no arguments should install all top-level pins. 23 | await generator.install(); 24 | let json = generator.getMap(); 25 | 26 | assert.strictEqual( 27 | json.imports.react, 28 | "https://ga.jspm.io/npm:react@17.0.1/index.js" 29 | ); 30 | 31 | // Installing a new dependency with freeze should not throw: 32 | // await generator.link(["lit"]); 33 | // json = generator.getMap(); 34 | 35 | // assert.strictEqual( 36 | // json.imports.lit, 37 | // "https://ga.jspm.io/npm:lit@2.6.1/index.js" 38 | // ); 39 | 40 | // // Even though latest for lit-html is 2.6.1, it should remain locked due to 41 | // // the freeze option being set: 42 | // assert.strictEqual( 43 | // json.scopes["https://ga.jspm.io/"]["lit-html"], 44 | // "https://ga.jspm.io/npm:lit-html@2.6.0/lit-html.js" 45 | // ); 46 | -------------------------------------------------------------------------------- /test/api/integrity.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ integrity: true }); 5 | 6 | await generator.install("react@16"); 7 | 8 | const json = generator.getMap(); 9 | 10 | assert.strictEqual( 11 | json.imports.react, 12 | "https://ga.jspm.io/npm:react@16.14.0/dev.index.js" 13 | ); 14 | assert.strictEqual( 15 | json.integrity["https://ga.jspm.io/npm:react@16.14.0/dev.index.js"], 16 | "sha384-9fzQTOt5Qymc9ZJlv20DEPObkT3aHvkvCTxEDkOdtJbDEKIBx4XbUA1EWkWVACyd" 17 | ); 18 | -------------------------------------------------------------------------------- /test/api/jsonassert.skipbrowser.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | rootUrl: new URL("./", import.meta.url), 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.install({ target: "./local/pkg", subpath: "./json" }); 11 | const json = generator.getMap(); 12 | 13 | assert.strictEqual(json.imports["localpkg/json"], "/local/pkg/json.ts"); 14 | -------------------------------------------------------------------------------- /test/api/link.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: new URL("./versionbumps/importmap.json", import.meta.url), 6 | baseUrl: new URL("./versionbumps/", import.meta.url), 7 | 8 | inputMap: { 9 | imports: { 10 | "es-module-lexer": 11 | "https://ga.jspm.io/npm:es-module-lexer@0.10.5/dist/lexer.js", 12 | }, 13 | }, 14 | }); 15 | 16 | await generator.link("x"); 17 | 18 | const json = generator.getMap(); 19 | assert.strictEqual( 20 | json.imports["es-module-lexer"], 21 | "https://ga.jspm.io/npm:es-module-lexer@0.10.5/dist/lexer.js" 22 | ); 23 | -------------------------------------------------------------------------------- /test/api/local.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "jspm.io", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.install({ target: "./local/pkg", subpath: "./custom" }); 11 | const json = generator.getMap(); 12 | assert.strictEqual(json.imports["localpkg/custom"], "./local/pkg/a.js"); 13 | -------------------------------------------------------------------------------- /test/api/local/assets/file.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: red; 3 | } 4 | -------------------------------------------------------------------------------- /test/api/local/assets/file.js: -------------------------------------------------------------------------------- 1 | export var f = () => 5; 2 | -------------------------------------------------------------------------------- /test/api/local/assets/file.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": "file" 3 | } -------------------------------------------------------------------------------- /test/api/local/assets/file.wasm: -------------------------------------------------------------------------------- 1 | asm` #asdffp 2 |  3 | A memtabglobfunc 4 |  -------------------------------------------------------------------------------- /test/api/local/assets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "exports": { 4 | "./json": "./file.json", 5 | "./css": "./file.css", 6 | "./wasm": "./file.wasm" 7 | }, 8 | "imports": { 9 | "#asdf": "./file.js" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/api/local/dep/main.js: -------------------------------------------------------------------------------- 1 | console.log("dep"); 2 | -------------------------------------------------------------------------------- /test/api/local/dep/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "localdep", 3 | "type": "module", 4 | "exports": "./main.js" 5 | } 6 | -------------------------------------------------------------------------------- /test/api/local/freeze/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "description": "Constraints for 'freeze.test.js'.", 4 | "dependencies": { 5 | "chalk": "4.1.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/api/local/latest/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | console.log(React); 3 | -------------------------------------------------------------------------------- /test/api/local/latest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "latest", 3 | "type": "module", 4 | "exports": "./index.js", 5 | "dependencies": { 6 | "react": "~16.13.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/api/local/pkg/a.js: -------------------------------------------------------------------------------- 1 | console.log("a"); 2 | -------------------------------------------------------------------------------- /test/api/local/pkg/b.js: -------------------------------------------------------------------------------- 1 | import "dep"; 2 | -------------------------------------------------------------------------------- /test/api/local/pkg/c.js: -------------------------------------------------------------------------------- 1 | import "dep2"; 2 | -------------------------------------------------------------------------------- /test/api/local/pkg/d.js: -------------------------------------------------------------------------------- 1 | import "react"; 2 | -------------------------------------------------------------------------------- /test/api/local/pkg/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "b" 3 | } 4 | -------------------------------------------------------------------------------- /test/api/local/pkg/e.cjs: -------------------------------------------------------------------------------- 1 | require("#cjsdep"); 2 | import("#cjsdep"); 3 | -------------------------------------------------------------------------------- /test/api/local/pkg/f.cjs: -------------------------------------------------------------------------------- 1 | module.exports = "f"; 2 | (function () { 3 | require("./a.js"); 4 | })(); 5 | -------------------------------------------------------------------------------- /test/api/local/pkg/index.js: -------------------------------------------------------------------------------- 1 | import * as _ from "./a.js"; 2 | -------------------------------------------------------------------------------- /test/api/local/pkg/jquery.js: -------------------------------------------------------------------------------- 1 | import "jquery"; 2 | -------------------------------------------------------------------------------- /test/api/local/pkg/json.ts: -------------------------------------------------------------------------------- 1 | import json from "./data.json" assert { type: "json" }; 2 | console.log(json); 3 | -------------------------------------------------------------------------------- /test/api/local/pkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "localpkg", 3 | "type": "module", 4 | "exports": { 5 | ".": "./index.js", 6 | "./custom": "./a.js", 7 | "./withdep": "./b.js", 8 | "./withdep2": "./c.js", 9 | "./remotedep": "./d.js", 10 | "./cjs": "./e.cjs", 11 | "./jquery": "./jquery.js", 12 | "./json": "./json.ts", 13 | "./conditional": { 14 | "development": "./a.js", 15 | "production": "./b.js" 16 | } 17 | }, 18 | "imports": { 19 | "#cjsdep": "./f.cjs" 20 | }, 21 | "dependencies": { 22 | "dep": "file:../dep", 23 | "dep2": "../dep", 24 | "react": "https://ga.jspm.io/npm:react@16.14.0" 25 | }, 26 | "devDependencies": { 27 | "jquery": "2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/api/local/react1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "exports": "./react1.js", 3 | "type": "module", 4 | "dependencies": { 5 | "react": "16 || 17", 6 | "localdep": "../react2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/api/local/react1/react1.js: -------------------------------------------------------------------------------- 1 | import "react"; 2 | import "localdep"; 3 | -------------------------------------------------------------------------------- /test/api/local/react2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "exports": "./react2.js", 3 | "type": "module", 4 | "dependencies": { 5 | "react": "16" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/api/local/react2/react2.js: -------------------------------------------------------------------------------- 1 | import "react"; 2 | -------------------------------------------------------------------------------- /test/api/localcjs.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | // Not supported in browsers 5 | if (typeof document === "undefined") { 6 | const generator = new Generator({ 7 | mapUrl: import.meta.url, 8 | defaultProvider: "jspm.io", 9 | env: ["production", "browser"], 10 | commonJS: true, 11 | }); 12 | 13 | await generator.install({ target: "./local/pkg", subpath: "./cjs" }); 14 | const json = generator.getMap(); 15 | 16 | assert.strictEqual(json.imports["localpkg/cjs"], "./local/pkg/e.cjs"); 17 | assert.strictEqual( 18 | json.scopes["./local/pkg/"]["#cjsdep"], 19 | "./local/pkg/f.cjs" 20 | ); 21 | 22 | const meta = generator.getAnalysis( 23 | new URL("./local/pkg/f.cjs", import.meta.url) 24 | ); 25 | assert.deepStrictEqual(meta.cjsLazyDeps, ["./a.js"]); 26 | } 27 | -------------------------------------------------------------------------------- /test/api/localdeps.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "jspm.io", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.install({ target: "./local/pkg", subpath: "./withdep" }); 11 | const json = generator.getMap(); 12 | 13 | assert.strictEqual(json.imports["localpkg/withdep"], "./local/pkg/b.js"); 14 | assert.strictEqual(json.scopes["./local/pkg/"].dep, "./local/dep/main.js"); 15 | -------------------------------------------------------------------------------- /test/api/localdepsdirect.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "jspm.io", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.install({ target: "./local/pkg", subpath: "./withdep2" }); 11 | const json = generator.getMap(); 12 | 13 | assert.strictEqual(json.imports["localpkg/withdep2"], "./local/pkg/c.js"); 14 | assert.strictEqual(json.scopes["./local/pkg/"].dep2, "./local/dep/main.js"); 15 | -------------------------------------------------------------------------------- /test/api/localpin.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "jspm.io", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.link("./local/pkg/jquery.js"); 11 | const json = generator.getMap(); 12 | 13 | assert.ok(json.imports["jquery"]); 14 | -------------------------------------------------------------------------------- /test/api/node.test.js: -------------------------------------------------------------------------------- 1 | import { Generator, lookup } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | { 5 | const generator = new Generator(); 6 | 7 | await generator.link("fs/promises"); 8 | 9 | const json = generator.getMap(); 10 | 11 | assert.strictEqual( 12 | json.imports["fs/promises"].split("/").slice(-4).join("/"), 13 | `nodelibs/browser/fs/promises.js` 14 | ); 15 | } 16 | 17 | { 18 | const generator = new Generator({ 19 | env: ["production", "browser"], 20 | }); 21 | 22 | await generator.install("node:process"); 23 | 24 | const json = generator.getMap(); 25 | 26 | assert.strictEqual( 27 | json.imports["process"], 28 | `https://ga.jspm.io/npm:@jspm/core@${ 29 | (await lookup("@jspm/core")).resolved.version 30 | }/nodelibs/browser/process-production.js` 31 | ); 32 | } 33 | 34 | { 35 | const generator = new Generator({ 36 | env: ["production", "browser"], 37 | inputMap: { 38 | imports: { 39 | fs: "https://ga.jspm.io/npm:@jspm/core@2.0.0-beta.20/nodelibs/node/fs.js", 40 | }, 41 | }, 42 | }); 43 | 44 | await generator.install("node:process"); 45 | 46 | const json = generator.getMap(); 47 | 48 | assert.strictEqual( 49 | json.imports["fs"], 50 | `https://ga.jspm.io/npm:@jspm/core@${ 51 | (await lookup("@jspm/core")).resolved.version 52 | }/nodelibs/browser/fs.js` 53 | ); 54 | assert.strictEqual( 55 | json.imports["process"], 56 | `https://ga.jspm.io/npm:@jspm/core@${ 57 | (await lookup("@jspm/core")).resolved.version 58 | }/nodelibs/browser/process-production.js` 59 | ); 60 | } 61 | 62 | { 63 | const generator = new Generator({ 64 | env: ["production", "browser"], 65 | inputMap: { 66 | imports: { 67 | fs: "https://ga.jspm.io/npm:@jspm/core@2.0.0-beta.20/nodelibs/node/fs.js", 68 | }, 69 | }, 70 | }); 71 | 72 | await generator.link("node:process"); 73 | 74 | const json = generator.getMap(); 75 | 76 | assert.strictEqual( 77 | json.imports["node:process"], 78 | `https://ga.jspm.io/npm:@jspm/core@2.0.0-beta.20/nodelibs/browser/process-production.js` 79 | ); 80 | } 81 | 82 | { 83 | const generator = new Generator({ 84 | env: ["production", "browser"], 85 | inputMap: { 86 | imports: { 87 | fs: `https://ga.jspm.io/npm:@jspm/core@2.0.0-beta.20/nodelibs/node/fs.js`, 88 | }, 89 | }, 90 | }); 91 | 92 | await generator.install("node:process"); 93 | 94 | const json = generator.getMap(); 95 | 96 | assert.strictEqual( 97 | json.imports["process"], 98 | `https://ga.jspm.io/npm:@jspm/core@${ 99 | (await lookup("@jspm/core")).resolved.version 100 | }/nodelibs/browser/process-production.js` 101 | ); 102 | } 103 | -------------------------------------------------------------------------------- /test/api/notFound.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const BASE_CONFIG = { 5 | mapUrl: "about:blank", 6 | ignore: [ 7 | "react", 8 | "react/jsx-runtime", 9 | "react-dom", 10 | "react-dom/server", 11 | "framer", 12 | "framer-motion", 13 | "framer-motion/three", 14 | ], 15 | env: ["production", "browser", "module"], 16 | }; 17 | 18 | const generator = new Generator({ 19 | ...BASE_CONFIG, 20 | }); 21 | 22 | try { 23 | await generator.install("react@24"); 24 | assert.fail("react@24 is released"); 25 | } catch (e) { 26 | assert.strictEqual( 27 | e.message, 28 | "Unable to resolve npm:react@24 to a valid version imported from about:blank/" 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /test/api/npmdep.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "jspm.io", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.install( 11 | "@lit-async/ssr-client@1.0.0-rc.1/directives/server-until.js" 12 | ); 13 | const json = generator.getMap(); 14 | assert.strictEqual( 15 | json.imports["@lit-async/ssr-client/directives/server-until.js"], 16 | "https://ga.jspm.io/npm:@lit-async/ssr-client@1.0.0-rc.1/directives/server-until.js" 17 | ); 18 | -------------------------------------------------------------------------------- /test/api/parallel.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "jspm.io", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await Promise.all([ 11 | generator.install("react@16"), 12 | generator.install("lit-element@2.5.1"), 13 | ]); 14 | const json = generator.getMap(); 15 | 16 | assert.strictEqual( 17 | json.imports.react, 18 | "https://ga.jspm.io/npm:react@16.14.0/index.js" 19 | ); 20 | assert.strictEqual( 21 | json.imports["lit-element"], 22 | "https://ga.jspm.io/npm:lit-element@2.5.1/lit-element.js" 23 | ); 24 | -------------------------------------------------------------------------------- /test/api/preloads.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator(); 5 | 6 | const { staticDeps } = await generator.install("react@16"); 7 | assert.strictEqual(staticDeps.length, 5); 8 | 9 | { 10 | const { staticDeps } = await generator.install({ 11 | alias: "react17", 12 | target: "react@17", 13 | }); 14 | assert.strictEqual(staticDeps.length, 7); 15 | } 16 | 17 | const json = generator.getMap(); 18 | assert.strictEqual(Object.keys(json.imports).length, 2); 19 | -------------------------------------------------------------------------------- /test/api/providerswitch.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "jspm.io", 7 | inputMap: { 8 | imports: { 9 | react: "https://cdn.skypack.dev/react@18.2.0/index.js", 10 | }, 11 | }, 12 | }); 13 | 14 | // The generator should swap the provider from skypack to jspm.io. 15 | // TODO: once we land defaultProvider changes this test will break 16 | await generator.reinstall(); 17 | 18 | const json = generator.getMap(); 19 | assert(json.imports.react.startsWith("https://ga.jspm.io/npm:")); 20 | -------------------------------------------------------------------------------- /test/api/reenv.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | { 5 | const generator = new Generator({ 6 | inputMap: { 7 | imports: { 8 | react: "https://ga.jspm.io/npm:react@17.0.1/dev.index.js", 9 | }, 10 | scopes: { 11 | "https://ga.jspm.io/": { 12 | "object-assign": 13 | "https://ga.jspm.io/npm:object-assign@4.1.0/index.js", 14 | }, 15 | }, 16 | }, 17 | mapUrl: import.meta.url, 18 | defaultProvider: "jspm.io", 19 | env: ["production", "browser"], 20 | }); 21 | 22 | await generator.reinstall(); 23 | const json = generator.getMap(); 24 | 25 | assert.strictEqual( 26 | json.imports.react, 27 | "https://ga.jspm.io/npm:react@17.0.1/index.js" 28 | ); 29 | assert.strictEqual( 30 | json.scopes["https://ga.jspm.io/"]["object-assign"], 31 | "https://ga.jspm.io/npm:object-assign@4.1.0/index.js" 32 | ); 33 | } 34 | 35 | { 36 | const generator = new Generator({ 37 | env: ["production", "module", "browser"], 38 | ignore: ["custom"], 39 | inputMap: { 40 | imports: { 41 | custom: "/mapping.js", 42 | react: "https://ga.jspm.io/npm:react@17.0.1/dev.index.js", 43 | }, 44 | scopes: { 45 | "https://ga.jspm.io/": { 46 | "object-assign": 47 | "https://ga.jspm.io/npm:object-assign@4.1.0/index.js", 48 | }, 49 | }, 50 | }, 51 | }); 52 | 53 | await generator.reinstall(); 54 | 55 | const json = generator.getMap(); 56 | 57 | assert.strictEqual(json.imports.custom, "/mapping.js"); 58 | assert.strictEqual( 59 | json.imports.react, 60 | "https://ga.jspm.io/npm:react@17.0.1/index.js" 61 | ); 62 | assert.strictEqual( 63 | json.scopes["https://ga.jspm.io/"]["object-assign"], 64 | "https://ga.jspm.io/npm:object-assign@4.1.0/index.js" 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /test/api/remotedep.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "jspm.io", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.install({ target: "./local/pkg", subpath: "./remotedep" }); 11 | const json = generator.getMap(); 12 | 13 | assert.strictEqual(json.imports["localpkg/remotedep"], "./local/pkg/d.js"); 14 | assert.strictEqual( 15 | json.scopes["./local/pkg/"].react, 16 | "https://ga.jspm.io/npm:react@16.14.0/index.js" 17 | ); 18 | -------------------------------------------------------------------------------- /test/api/resolutions.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | // Test primary resolutions: 5 | let generator = new Generator({ 6 | mapUrl: import.meta.url, 7 | env: ["production", "browser"], 8 | resolutions: { 9 | semver: "6.2.0", 10 | }, 11 | }); 12 | 13 | await generator.install("semver@latest"); 14 | let json = generator.getMap(); 15 | assert.ok(json.imports["semver"]); 16 | assert.ok(json.imports["semver"].includes("6.2.0")); 17 | 18 | // Test primary resolutions with alias: 19 | generator = new Generator({ 20 | mapUrl: import.meta.url, 21 | env: ["production", "browser"], 22 | resolutions: { 23 | alias: "semver@6.2.0", 24 | }, 25 | }); 26 | 27 | await generator.install("alias"); 28 | json = generator.getMap(); 29 | assert.ok(json.imports["alias"]); 30 | assert.ok(json.imports["alias"].includes("6.2.0")); 31 | 32 | // Test secondary resolutions: 33 | generator = new Generator({ 34 | mapUrl: import.meta.url, 35 | env: ["production", "browser"], 36 | resolutions: { 37 | semver: "6.2.0", 38 | }, 39 | }); 40 | 41 | await generator.install("@babel/core@7.16.0"); 42 | json = generator.getMap(); 43 | assert.ok(json.imports["@babel/core"]); 44 | assert.ok(Object.keys(json.scopes["https://ga.jspm.io/"]).length > 20); 45 | assert.ok(json.scopes["https://ga.jspm.io/"]["semver"].includes("6.2.0")); 46 | 47 | // Test local resolutions: 48 | generator = new Generator({ 49 | baseUrl: new URL("../", import.meta.url), 50 | mapUrl: import.meta.url, 51 | env: ["production", "browser"], 52 | resolutions: { 53 | dep: "./api/local/react1", 54 | }, 55 | }); 56 | 57 | await generator.install({ target: "./api/local/pkg", subpath: "./withdep" }); 58 | json = generator.getMap(); 59 | assert.strictEqual(json.scopes["./local/"].dep, "./local/react1/react1.js"); 60 | -------------------------------------------------------------------------------- /test/api/rooturl.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | rootUrl: new URL("./", import.meta.url), 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.install({ target: "./local/pkg", subpath: "./custom" }); 11 | 12 | const json = generator.getMap(); 13 | 14 | assert.strictEqual(json.imports["localpkg/custom"], "/local/pkg/a.js"); 15 | -------------------------------------------------------------------------------- /test/api/self-uninstall.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | // Mimic calling the generator from the ./local/pkg package: 5 | const mapUrl = new URL("./local/pkg/importmap.json", import.meta.url).href; 6 | const pkgNames = ["localpkg", "localpkg/custom", "localpkg/conditional"]; 7 | 8 | for (const pkgName of pkgNames) { 9 | let generator = new Generator({ 10 | mapUrl, 11 | env: ["production"], 12 | }); 13 | 14 | // Installing the package from within itself should resolve locally, since the 15 | // package.json has a local export for ".": 16 | await generator.link(pkgName); 17 | let json = generator.getMap(); 18 | assert.ok(json.imports[pkgName]); 19 | 20 | // Uninstalling using the same generator instance should remove the install 21 | // entirely and return an empty map: 22 | await generator.uninstall(pkgName); 23 | assert.ok(!generator.getMap().imports); 24 | 25 | // Uninstalling using a new generator instance should _also_ remove the install 26 | // from the import map, i.e. the generator should be able to reconstruct the 27 | // context given just an input map: 28 | generator = new Generator({ 29 | mapUrl, 30 | inputMap: json, 31 | env: ["production"], 32 | }); 33 | 34 | // Uninstalling the package should get rid of it: 35 | await generator.uninstall(pkgName); 36 | json = generator.getMap(); 37 | assert.ok(!json.imports); 38 | } 39 | -------------------------------------------------------------------------------- /test/api/self.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | // TODO(bubblyworld): This test should be failing for two reasons: 5 | // 1. Generator installs take package specifiers, so self-reference installs 6 | // shouldn't be possible (they're a _module_ resolution thing). 7 | // 2. The current version of @jspm/generator is >1.0.0, so this shouldn't 8 | // be doing a local install anyway. 9 | 10 | const generator = new Generator({ 11 | mapUrl: import.meta.url, 12 | env: ["source"], 13 | }); 14 | 15 | const { staticDeps, dynamicDeps } = await generator.install( 16 | "@jspm/generator@1.0.0-beta.13" 17 | ); 18 | 19 | assert.ok(staticDeps.length < 50); 20 | assert.ok(dynamicDeps.length > 100); 21 | 22 | const json = generator.getMap(); 23 | 24 | assert.strictEqual(json.imports["@jspm/generator"], "../../lib/generator.js"); 25 | assert.strictEqual( 26 | json.scopes["../../"]["#fetch"], 27 | "../../lib/common/fetch-native.js" 28 | ); 29 | assert.strictEqual( 30 | json.scopes["https://ga.jspm.io/"]["semver"], 31 | "https://ga.jspm.io/npm:semver@6.3.1/semver.js" 32 | ); 33 | -------------------------------------------------------------------------------- /test/api/subpath.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "jspm.io", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.install({ target: "lit@2.0.0-rc.1", subpath: "./html.js" }); 11 | const json = generator.getMap(); 12 | assert.strictEqual( 13 | json.imports["lit/html.js"], 14 | "https://ga.jspm.io/npm:lit@2.0.0-rc.1/html.js" 15 | ); 16 | -------------------------------------------------------------------------------- /test/api/traceinstall.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "jspm.io", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.link("./local/pkg/b.js"); 11 | 12 | const json = generator.getMap(); 13 | 14 | assert.strictEqual(json.imports.dep, "./local/dep/main.js"); 15 | -------------------------------------------------------------------------------- /test/api/uninstall.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | { 5 | const generator = new Generator({ 6 | inputMap: { 7 | imports: { 8 | react: "https://ga.jspm.io/npm:react@17.0.1/dev.index.js", 9 | "react-dom": "https://ga.jspm.io/npm:react-dom@17.0.1/dev.index.js", 10 | }, 11 | scopes: { 12 | "https://ga.jspm.io/": { 13 | "object-assign": 14 | "https://ga.jspm.io/npm:object-assign@4.1.0/index.js", 15 | scheduler: "https://ga.jspm.io/npm:scheduler@0.20.1/dev.index.js", 16 | "scheduler/tracing": 17 | "https://ga.jspm.io/npm:scheduler@0.20.1/dev.tracing.js", 18 | }, 19 | }, 20 | }, 21 | mapUrl: import.meta.url, 22 | defaultProvider: "jspm.io", 23 | env: ["production", "browser"], 24 | }); 25 | 26 | await generator.uninstall("react-dom"); 27 | const json = generator.getMap(); 28 | 29 | assert.strictEqual( 30 | json.imports.react, 31 | "https://ga.jspm.io/npm:react@17.0.1/index.js" 32 | ); 33 | assert.strictEqual( 34 | json.scopes["https://ga.jspm.io/"]["object-assign"], 35 | "https://ga.jspm.io/npm:object-assign@4.1.0/index.js" 36 | ); 37 | assert.strictEqual(Object.keys(json.imports).length, 1); 38 | assert.strictEqual(Object.keys(json.scopes["https://ga.jspm.io/"]).length, 1); 39 | } 40 | 41 | { 42 | const generator = new Generator({ 43 | inputMap: { 44 | imports: { 45 | lit: "https://ga.jspm.io/npm:lit@2.2.4/index.js", 46 | "lit/directive.js": "https://ga.jspm.io/npm:lit@2.2.4/directive.js", 47 | }, 48 | scopes: { 49 | "https://ga.jspm.io/": { 50 | "@lit/reactive-element": 51 | "https://ga.jspm.io/npm:@lit/reactive-element@1.3.4/reactive-element.js", 52 | "lit-element/lit-element.js": 53 | "https://ga.jspm.io/npm:lit-element@3.2.2/lit-element.js", 54 | "lit-html": "https://ga.jspm.io/npm:lit-html@2.2.7/lit-html.js", 55 | "lit-html/directive.js": 56 | "https://ga.jspm.io/npm:lit-html@2.2.7/directive.js", 57 | }, 58 | }, 59 | }, 60 | mapUrl: import.meta.url, 61 | defaultProvider: "jspm.io", 62 | env: ["production", "browser"], 63 | }); 64 | 65 | await generator.uninstall(["lit", "lit/"]); 66 | const json = generator.getMap(); 67 | assert.strictEqual(Object.keys(json).length, 0); 68 | } 69 | -------------------------------------------------------------------------------- /test/api/update.test.js: -------------------------------------------------------------------------------- 1 | import { Generator, lookup } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | { 5 | const generator = new Generator({ 6 | inputMap: { 7 | imports: { 8 | react: "https://ga.jspm.io/npm:react@17.0.1/dev.index.js", 9 | }, 10 | scopes: { 11 | "https://ga.jspm.io/": { 12 | "object-assign": 13 | "https://ga.jspm.io/npm:object-assign@4.1.0/index.js", 14 | }, 15 | }, 16 | }, 17 | mapUrl: import.meta.url, 18 | defaultProvider: "jspm.io", 19 | env: ["production", "browser"], 20 | }); 21 | 22 | await generator.update("react"); 23 | const json = generator.getMap(); 24 | 25 | assert.strictEqual( 26 | json.imports.react, 27 | "https://ga.jspm.io/npm:react@17.0.2/index.js" 28 | ); 29 | assert.strictEqual( 30 | json.scopes["https://ga.jspm.io/"]["object-assign"], 31 | "https://ga.jspm.io/npm:object-assign@4.1.1/index.js" 32 | ); 33 | assert.strictEqual(Object.keys(json.imports).length, 1); 34 | assert.strictEqual(Object.keys(json.scopes["https://ga.jspm.io/"]).length, 1); 35 | } 36 | 37 | { 38 | const generator = new Generator({ 39 | inputMap: { 40 | imports: { 41 | lit: "https://ga.jspm.io/npm:lit@2.2.4/index.js", 42 | "lit/directive.js": "https://ga.jspm.io/npm:lit@2.2.4/directive.js", 43 | }, 44 | scopes: { 45 | "https://ga.jspm.io/": { 46 | "@lit/reactive-element": 47 | "https://ga.jspm.io/npm:@lit/reactive-element@1.3.4/reactive-element.js", 48 | "lit-element/lit-element.js": 49 | "https://ga.jspm.io/npm:lit-element@3.2.2/lit-element.js", 50 | "lit-html": "https://ga.jspm.io/npm:lit-html@2.2.7/lit-html.js", 51 | "lit-html/directive.js": 52 | "https://ga.jspm.io/npm:lit-html@2.2.7/directive.js", 53 | }, 54 | }, 55 | }, 56 | mapUrl: import.meta.url, 57 | defaultProvider: "jspm.io", 58 | env: ["production", "browser"], 59 | }); 60 | 61 | await generator.update("lit"); 62 | const json = generator.getMap(); 63 | const expectedVersion = (await lookup("lit@2")).resolved.version; 64 | 65 | assert.strictEqual( 66 | json.imports.lit, 67 | `https://ga.jspm.io/npm:lit@${expectedVersion}/index.js` 68 | ); 69 | assert.strictEqual( 70 | json.imports["lit/directive.js"], 71 | `https://ga.jspm.io/npm:lit@${expectedVersion}/directive.js` 72 | ); 73 | } 74 | 75 | { 76 | const generator = new Generator({ defaultProvider: 'jsdelivr' }); 77 | await generator.addMappings({ 78 | "imports": { 79 | "lodash": "https://cdn.jsdelivr.net/npm/lodash@4.17.20/lodash.js", 80 | "lodash/filter.js": "https://cdn.jsdelivr.net/npm/lodash@4.17.20/filter.js" 81 | } 82 | }); 83 | 84 | await generator.update(); 85 | const json = generator.getMap(); 86 | const expectedVersion = (await lookup("lodash@4")).resolved.version; 87 | 88 | assert.strictEqual( 89 | json.imports.lodash, 90 | `https://cdn.jsdelivr.net/npm/lodash@${expectedVersion}/lodash.js` 91 | ); 92 | assert.strictEqual( 93 | json.imports['lodash/filter.js'], 94 | `https://cdn.jsdelivr.net/npm/lodash@${expectedVersion}/filter.js` 95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /test/api/urls.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | // TODO: enable these one we support arbitrary URL installation 5 | 6 | // Should be able to install a package scope URL directly, and it should 7 | // resolve to the default export in the scope's package.json: 8 | await (async (enabled = true) => { 9 | if (!enabled) return; 10 | 11 | const gen = new Generator(); 12 | await gen.install("https://unpkg.com/lit@2.0.0/"); 13 | const map = gen.getMap(); 14 | 15 | assert.ok(map); 16 | assert.strictEqual(map?.imports?.lit, "https://unpkg.com/lit@2.0.0/index.js"); 17 | })(false); 18 | 19 | // Should be able to install a particular exports subpath from a package scope 20 | // URL directly using the pipe ("|") separator 21 | await (async (enabled = true) => { 22 | if (!enabled) return; 23 | 24 | const gen = new Generator(); 25 | await gen.install("https://unpkg.com/react@18.0.0|jsx-runtime"); 26 | const map = gen.getMap(); 27 | 28 | assert.ok(map); 29 | assert.strictEqual( 30 | map?.imports["react/jsx-runtime"], 31 | "https://unpkg.com/react@18.0.0/jsx-runtime.js" 32 | ); 33 | })(false); 34 | 35 | // Should be able to install a module URL directly, if that module URL is 36 | // present as an export in the scope's package.json: 37 | await (async (enabled = true) => { 38 | if (!enabled) return; 39 | 40 | const gen = new Generator(); 41 | await gen.install("https://unpkg.com/lit@2.0.0/index.js"); 42 | const map = gen.getMap(); 43 | 44 | assert.ok(map); 45 | assert.strictEqual(map?.imports?.lit, "https://unpkg.com/lit@2.0.0/index.js"); 46 | })(false); 47 | 48 | // TODO 49 | // Should be able to install a module URL directly, even if that module URL is 50 | // _not_ present as an export in the scope's package.json. This is a case of 51 | // "let people who know what they're doing actually do it": 52 | -------------------------------------------------------------------------------- /test/api/version.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "jspm.io", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.install({ target: "@pyscript/core@0.4.21" }); 11 | const json = generator.getMap(); 12 | assert.ok(json); 13 | -------------------------------------------------------------------------------- /test/api/versionbumps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "x", 3 | "exports": "./x.js", 4 | "type": "module", 5 | "dependencies": { 6 | "es-module-lexer": "^1" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/api/versionbumps/x.js: -------------------------------------------------------------------------------- 1 | import "es-module-lexer"; 2 | -------------------------------------------------------------------------------- /test/deno.js: -------------------------------------------------------------------------------- 1 | import { Buffer } from "buffer"; 2 | import { execSync } from "child_process"; 3 | import { tmpdir } from "os"; 4 | import { resolve } from "path"; 5 | import { writeFileSync, unlinkSync } from "fs"; 6 | import process from "process"; 7 | 8 | // function dataUrl (contentType, source) { 9 | // return `data:${contentType};base64,${Buffer.from(source).toString('base64')}`; 10 | // } 11 | 12 | export function denoExec(map, source) { 13 | const tmpDir = tmpdir(); 14 | const tmpMap = resolve(tmpDir, "map.json"); 15 | const tmpSrc = resolve(tmpDir, "app.js"); 16 | writeFileSync(tmpMap, JSON.stringify(map)); 17 | writeFileSync(tmpSrc, source); 18 | execSync( 19 | `${ 20 | process.env.DENO_BIN || "deno" 21 | } run --quiet --reload --unstable --no-check --allow-all --import-map=${tmpMap} ${tmpSrc}`, 22 | { stdio: "inherit" } 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /test/deno/babel.skip.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import { denoExec } from "#test/deno"; 3 | 4 | const generator = new Generator({ 5 | env: ["node", "deno"], 6 | }); 7 | 8 | await generator.install("@babel/core"); 9 | await generator.install("assert"); 10 | 11 | const map = generator.getMap(); 12 | 13 | await denoExec( 14 | map, 15 | ` 16 | import babel from '@babel/core'; 17 | import assert from 'assert'; 18 | 19 | const { code } = babel.transform('var p = 5'); 20 | assert.strictEqual(code, 'var p = 5;'); 21 | ` 22 | ); 23 | -------------------------------------------------------------------------------- /test/deno/esmsh.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import { denoExec } from "#test/deno"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | env: ["production", "deno", "module"], 7 | defaultProvider: "esm.sh", 8 | }); 9 | 10 | // Install the NPM assert shim and use it to test itself! 11 | // The esm.sh deno build for assert@2.0.0 is broken, so we use an old version. 12 | await generator.install("npm:assert@1.5.0"); 13 | await denoExec( 14 | generator.getMap(), 15 | ` 16 | import { ok } from 'assert'; 17 | ok(1 === 1); 18 | ` 19 | ); 20 | -------------------------------------------------------------------------------- /test/deno/node.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import { denoExec } from "#test/deno"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: "about:blank", 6 | env: ["production", "node", "deno", "module"], 7 | }); 8 | 9 | await generator.install("chalk"); 10 | 11 | await denoExec(generator.getMap(), `import chalk from 'chalk';`); 12 | -------------------------------------------------------------------------------- /test/deno/self.skip.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import { denoExec } from "#test/deno"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: "about:blank", 6 | env: ["production", "node", "deno", "module", "source"], 7 | 8 | // Hack - deno bombs on the circular imports in @babel/core@7.21.0, despite 9 | // it working fine in the browser and node. So we patch it: 10 | resolutions: { 11 | "@babel/core": "~7.20.0", 12 | "@babel/generator": "7.20.0", 13 | "@babel/helper-member-expression-to-functions": "7.20.7", 14 | }, 15 | }); 16 | 17 | const targetUrl = new URL("../../", import.meta.url).href; 18 | await generator.install({ alias: "@jspm/generator", target: targetUrl }); 19 | const map = generator.getMap(); 20 | 21 | await denoExec( 22 | generator.getMap(), 23 | ` 24 | import { Generator } from '@jspm/generator'; 25 | import { assertEquals } from "https://deno.land/std@0.100.0/testing/asserts.ts"; 26 | 27 | const generator = new Generator({ 28 | mapUrl: 'about:blank', 29 | env: ['production', 'node', 'deno', 'module', 'source'] 30 | }); 31 | 32 | // inception! 33 | await generator.install({ alias: '@jspm/generator', target: ${JSON.stringify( 34 | targetUrl 35 | )} }); 36 | const map = generator.getMap(); 37 | 38 | assertEquals(map.imports, ${JSON.stringify(map.imports)}) 39 | ` 40 | ); 41 | -------------------------------------------------------------------------------- /test/errors/no-subpath-recovery.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator(); 5 | 6 | try { 7 | await generator.install({ 8 | target: "@material-ui/icons@4.11.2", 9 | subpath: "./AutorenewOutline", 10 | }); 11 | assert.fail("Should Error"); 12 | } catch (e) { 13 | assert.ok(e.message.includes("Module not found")); 14 | } 15 | 16 | const t = setTimeout(() => { 17 | assert.fail("Process stalled"); 18 | }, 5000); 19 | 20 | await generator.install("react@16"); 21 | 22 | const json = generator.getMap(); 23 | 24 | assert.strictEqual( 25 | json.imports.react, 26 | "https://ga.jspm.io/npm:react@16.14.0/dev.index.js" 27 | ); 28 | clearTimeout(t); 29 | -------------------------------------------------------------------------------- /test/errors/recovery.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "jspm.io", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | try { 11 | await generator.install("package-that-does-not-exist"); 12 | assert.fail("Should Error"); 13 | } catch (e) { 14 | assert.ok(e.message.includes("Unable to resolve")); 15 | } 16 | 17 | const t = setTimeout(() => { 18 | assert.fail("Process stalled"); 19 | }, 5000); 20 | 21 | await generator.install("react@16"); 22 | 23 | const json = generator.getMap(); 24 | assert.strictEqual( 25 | json.imports.react, 26 | "https://ga.jspm.io/npm:react@16.14.0/index.js" 27 | ); 28 | clearTimeout(t); 29 | -------------------------------------------------------------------------------- /test/example.mjs: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | 3 | const generator = new Generator({ 4 | mapUrl: import.meta.url, 5 | defaultProvider: "jspm.io", 6 | env: ["production", "browser"], 7 | }); 8 | 9 | // Install a new package into the import map 10 | await generator.install("react"); 11 | 12 | // Install a package version and subpath into the import map (installs lit/decorators.js) 13 | await generator.install("lit@2/decorators.js"); 14 | 15 | // Install a package version to a custom alias 16 | await generator.install({ alias: "react16", target: "react@16" }); 17 | 18 | // Install a specific subpath of a package 19 | await generator.install({ target: "lit", subpath: "./html.js" }); 20 | 21 | console.log(JSON.stringify(generator.getMap(), null, 2)); 22 | -------------------------------------------------------------------------------- /test/html/analyze.test.js: -------------------------------------------------------------------------------- 1 | import { analyzeHtml } from "@jspm/generator"; 2 | import { deepStrictEqual } from "assert"; 3 | 4 | const analysis = analyzeHtml(` 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | `); 30 | 31 | deepStrictEqual(analysis.map, { 32 | start: 2, 33 | end: 95, 34 | attrs: { 35 | type: { 36 | start: 10, 37 | end: 25, 38 | name: "type", 39 | quote: '"', 40 | value: "importmap", 41 | }, 42 | }, 43 | json: { 44 | imports: { 45 | react: "/react.js", 46 | }, 47 | }, 48 | newScript: false, 49 | style: { 50 | indent: " ", 51 | newline: "\n", 52 | quote: '"', 53 | tab: " ", 54 | trailingNewline: "\n", 55 | }, 56 | }); 57 | 58 | deepStrictEqual(analysis.preloads, [ 59 | { 60 | start: 310, 61 | end: 372, 62 | attrs: { 63 | rel: { 64 | start: 316, 65 | end: 333, 66 | quote: "", 67 | name: "rel", 68 | value: "modulepreload", 69 | }, 70 | href: { 71 | start: 334, 72 | end: 351, 73 | quote: '"', 74 | name: "href", 75 | value: "./subdep.js", 76 | }, 77 | integrity: { 78 | start: 353, 79 | end: 368, 80 | quote: '"', 81 | name: "integrity", 82 | value: "asdf", 83 | }, 84 | }, 85 | }, 86 | ]); 87 | 88 | deepStrictEqual( 89 | [...analysis.staticImports], 90 | ["react", "./local.js", "/absolute.js"] 91 | ); 92 | 93 | deepStrictEqual([...analysis.dynamicImports], ["/dynamic"]); 94 | 95 | deepStrictEqual(analysis.esModuleShims, { 96 | attrs: { 97 | async: { 98 | name: "async", 99 | quote: "", 100 | value: null, 101 | start: 105, 102 | end: 110, 103 | }, 104 | crossorigin: { 105 | name: "crossorigin", 106 | quote: '"', 107 | value: "anonymous", 108 | start: 275, 109 | end: 297, 110 | }, 111 | integrity: { 112 | name: "integrity", 113 | quote: '"', 114 | value: 115 | "sha384-Gba99Cy/cyqL1MpdnMzkUKYpebrPnBrQ5xnOoqJJNQstoxJQjAE4xgr80AiDYuTA", 116 | start: 191, 117 | end: 273, 118 | }, 119 | src: { 120 | name: "src", 121 | quote: '"', 122 | value: 123 | "https://ga.jspm.io/npm:es-module-shims@0.12.8/dist/es-module-shims.min.js", 124 | start: 111, 125 | end: 189, 126 | }, 127 | }, 128 | end: 308, 129 | start: 97, 130 | }); 131 | -------------------------------------------------------------------------------- /test/html/breaks.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | import { SemverRange } from "sver"; 4 | 5 | const generator = new Generator({ 6 | rootUrl: new URL("./local", import.meta.url), 7 | env: ["production", "browser"], 8 | resolutions: { 9 | react: "17", 10 | }, 11 | }); 12 | 13 | const esmsPkg = await generator.traceMap.resolver.resolveLatestTarget( 14 | { name: "es-module-shims", registry: "npm", ranges: [new SemverRange("*")] }, 15 | generator.traceMap.installer.defaultProvider 16 | ); 17 | const esmsUrl = 18 | (await generator.traceMap.resolver.pkgToUrl( 19 | esmsPkg, 20 | generator.traceMap.installer.defaultProvider 21 | )) + "dist/es-module-shims.js"; 22 | 23 | const html = ` 24 | 25 | 26 | 27 | 28 | 29 | 30 | SOMISANA 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 43 | 44 | 45 | 46 | 53 | 54 | 57 | 58 | 59 | 60 |
61 | 62 | 63 | `; 64 | 65 | const pins = await generator.addMappings(html); 66 | const res = await generator.htmlInject(html, { pins, preload: true }); 67 | 68 | assert.strictEqual( 69 | res, 70 | "\n" + 71 | '\n' + 72 | "\n" + 73 | "\n" + 74 | ' \n' + 75 | ' \n' + 76 | "\n" + 77 | " SOMISANA\n" + 78 | ' \n' + 79 | "\n" + 80 | " \n" + 81 | `\n` + 82 | '\n" + 94 | '\n' + 95 | '\n' + 96 | '\n' + 97 | '\n' + 98 | ' \n' + 99 | ' \n' + 100 | ' \n' + 101 | "\n" + 102 | " \n" + 107 | "\n" + 108 | " \n" + 109 | ' \n' + 110 | " \n" + 117 | "\n" + 118 | ' \n" + 121 | "\n" + 122 | "\n" + 123 | "\n" + 124 | '
\n' + 125 | "\n" + 126 | "\n" + 127 | "" 128 | ); 129 | -------------------------------------------------------------------------------- /test/html/emptymap.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | import { SemverRange } from "sver"; 4 | 5 | const generator = new Generator({ 6 | rootUrl: new URL("./local", import.meta.url), 7 | env: ["production", "browser"], 8 | }); 9 | 10 | const esmsPkg = await generator.traceMap.resolver.resolveLatestTarget( 11 | { name: "es-module-shims", registry: "npm", ranges: [new SemverRange("*")] }, 12 | generator.traceMap.installer.defaultProvider 13 | ); 14 | const esmsUrl = 15 | (await generator.traceMap.resolver.pkgToUrl( 16 | esmsPkg, 17 | generator.traceMap.installer.defaultProvider 18 | )) + "dist/es-module-shims.js"; 19 | 20 | const html = ` 21 | 22 | 23 | `; 24 | 25 | const pins = await generator.addMappings(html); 26 | const res = await generator.htmlInject(html, { pins, preload: true }); 27 | 28 | assert.strictEqual( 29 | res, 30 | "\n" + 31 | "\n" + 32 | "\n" + 33 | `\n` + 34 | '\n" 37 | ); 38 | -------------------------------------------------------------------------------- /test/html/esmsh.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | import { SemverRange } from "sver"; 4 | 5 | const generator = new Generator({ 6 | mapUrl: new URL("./local/page.html", import.meta.url), 7 | env: ["production", "browser"], 8 | defaultProvider: "esm.sh", 9 | }); 10 | 11 | const esmsPkg = await generator.traceMap.resolver.resolveLatestTarget( 12 | { name: "es-module-shims", registry: "npm", ranges: [new SemverRange("*")] }, 13 | generator.traceMap.installer.defaultProvider 14 | ); 15 | let pins, html; 16 | 17 | html = ` 18 | 19 | 22 | `; 23 | pins = await generator.addMappings(html); 24 | 25 | assert( 26 | (await generator.htmlInject(html, { pins })).includes("https://esm.sh/*") 27 | ); 28 | -------------------------------------------------------------------------------- /test/html/inject.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | import { SemverRange } from "sver"; 4 | 5 | const generator = new Generator({ 6 | mapUrl: new URL("./local/page.html", import.meta.url), 7 | env: ["production", "browser"], 8 | }); 9 | 10 | const esmsPkg = await generator.traceMap.resolver.resolveLatestTarget( 11 | { name: "es-module-shims", registry: "npm", ranges: [new SemverRange("*")] }, 12 | generator.traceMap.installer.defaultProvider 13 | ); 14 | const esmsUrl = 15 | (await generator.traceMap.resolver.pkgToUrl( 16 | esmsPkg, 17 | generator.traceMap.installer.defaultProvider 18 | )) + "dist/es-module-shims.js"; 19 | 20 | let pins, html; 21 | 22 | html = ` 23 | 24 | 27 | `; 28 | pins = await generator.addMappings(html); 29 | assert.strictEqual( 30 | await generator.htmlInject(html, { pins }), 31 | "\n" + 32 | "\n" + 33 | "\n" + 34 | `\n` + 35 | '\n" + 47 | '\n" 50 | ); 51 | 52 | // Idempotency: 53 | html = 54 | "\n" + 55 | "\n" + 56 | "\n" + 57 | `\n` + 58 | '\n" + 70 | '\n"; 73 | 74 | pins = await generator.addMappings(html); 75 | assert.strictEqual( 76 | await generator.htmlInject(html, { pins }), 77 | "\n" + 78 | "\n" + 79 | "\n" + 80 | `\n` + 81 | '\n" + 93 | '\n" 96 | ); 97 | -------------------------------------------------------------------------------- /test/html/injectionpoint.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | import { SemverRange } from "sver"; 4 | 5 | const generator = new Generator({ 6 | rootUrl: new URL("./local", import.meta.url), 7 | env: ["production", "browser"], 8 | resolutions: { 9 | react: "17", 10 | }, 11 | }); 12 | 13 | const esmsPkg = await generator.traceMap.resolver.resolveLatestTarget( 14 | { name: "es-module-shims", registry: "npm", ranges: [new SemverRange("*")] }, 15 | generator.traceMap.installer.defaultProvider 16 | ); 17 | const esmsUrl = 18 | (await generator.traceMap.resolver.pkgToUrl( 19 | esmsPkg, 20 | generator.traceMap.installer.defaultProvider 21 | )) + "dist/es-module-shims.js"; 22 | 23 | const html = ` 24 | 25 | 28 | 29 | 30 | 33 | `; 34 | 35 | const pins = await generator.addMappings(html); 36 | 37 | assert.strictEqual( 38 | await generator.htmlInject(html, { pins, preload: true }), 39 | "\n" + 40 | "\n" + 41 | "\n" + 42 | `\n` + 43 | '\n" + 55 | '\n' + 56 | '\n' + 57 | '\n' + 58 | '\n" + 61 | "\n" + 62 | "\n" + 63 | ' \n" + 66 | "" 67 | ); 68 | -------------------------------------------------------------------------------- /test/html/inline.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | import { SemverRange } from "sver"; 4 | 5 | const generator = new Generator({ 6 | mapUrl: new URL("app.html", import.meta.url), 7 | env: ["production", "browser"], 8 | resolutions: { 9 | react: "17", 10 | }, 11 | }); 12 | 13 | const esmsPkg = await generator.traceMap.resolver.resolveLatestTarget( 14 | { name: "es-module-shims", registry: "npm", ranges: [new SemverRange("*")] }, 15 | generator.traceMap.installer.defaultProvider 16 | ); 17 | const esmsUrl = 18 | (await generator.traceMap.resolver.pkgToUrl( 19 | esmsPkg, 20 | generator.traceMap.installer.defaultProvider 21 | )) + "dist/es-module-shims.js"; 22 | 23 | const html = ` 24 | 25 | 29 | `; 30 | 31 | const pins = await generator.addMappings(html); 32 | assert.strictEqual( 33 | await generator.htmlInject(html, { pins }), 34 | "\n" + 35 | "\n" + 36 | "\n" + 37 | `\n` + 38 | '\n" + 50 | '\n" 54 | ); 55 | -------------------------------------------------------------------------------- /test/html/lexer.test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { parseHtml } from "../../lib/html/lexer.js"; 3 | 4 | { 5 | const source = ` 6 | 7 | 8 | `; 9 | const scripts = parseHtml(source); 10 | assert.strictEqual(scripts.length, 2); 11 | assert.strictEqual(scripts[0].attributes.length, 1); 12 | const attr = scripts[0].attributes[0]; 13 | assert.strictEqual(source.slice(attr.nameStart, attr.nameEnd), "type"); 14 | assert.strictEqual(source.slice(attr.valueStart, attr.valueEnd), "module"); 15 | assert.strictEqual(scripts[0].innerStart, 27); 16 | assert.strictEqual(scripts[0].innerEnd, 31); 17 | assert.strictEqual(scripts[0].start, 5); 18 | assert.strictEqual(scripts[0].end, 40); 19 | assert.strictEqual(scripts[1].start, 45); 20 | assert.strictEqual(scripts[1].end, 84); 21 | assert.strictEqual(scripts[1].attributes.length, 2); 22 | } 23 | 24 | { 25 | const source = ` 26 | 37 | 38 | 41 | 42 | 47 | `; 48 | const scripts = parseHtml(source); 49 | assert.strictEqual(scripts.length, 3); 50 | assert.strictEqual(scripts[0].innerEnd - scripts[0].innerStart, 151); 51 | assert.strictEqual(scripts[1].attributes.length, 1); 52 | let attr = scripts[1].attributes[0]; 53 | assert.strictEqual(source.slice(attr.nameStart, attr.nameEnd), 'ta"'); 54 | assert.strictEqual(source.slice(attr.valueStart, attr.valueEnd), "==='s'\\"); 55 | assert.strictEqual(scripts[1].innerStart, 195); 56 | assert.strictEqual(scripts[1].innerEnd, 227); 57 | assert.strictEqual(scripts[1].start, 172); 58 | assert.strictEqual(scripts[1].end, 236); 59 | assert.strictEqual(scripts[2].attributes.length, 3); 60 | attr = scripts[2].attributes[0]; 61 | assert.strictEqual(source.slice(attr.nameStart, attr.nameEnd), "\n" + 43 | `\n` + 44 | '\n" + 52 | '\n' + 53 | '\n' + 54 | '\n' + 55 | '\n" 58 | ); 59 | 60 | { 61 | const generator = new Generator({ 62 | rootUrl: new URL("./local", import.meta.url), 63 | env: ["production", "browser"], 64 | }); 65 | 66 | // Idempotency 67 | const html = 68 | "\n" + 69 | "\n" + 70 | `\n` + 71 | '\n" + 83 | '\n' + 84 | '\n' + 85 | '\n"; 88 | 89 | const pins = await generator.addMappings(html); 90 | assert.strictEqual( 91 | await generator.htmlInject(html, { 92 | pins, 93 | preload: true, 94 | whitespace: false, 95 | }), 96 | "\n" + 97 | "\n" + 98 | "\n" + 99 | `\n` + 100 | '\n' + 101 | '\n' + 102 | '\n" 105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /test/html/offset.test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { Replacer } from "../../lib/common/str.js"; 3 | 4 | { 5 | const replacer = new Replacer("hello world"); 6 | replacer.replace(0, 5, "Hey"); 7 | replacer.replace(6, 11, "Earth"); 8 | 9 | assert.strictEqual(replacer.source, "Hey Earth"); 10 | 11 | replacer.replace(0, 5, "Does this work?"); 12 | assert.strictEqual(replacer.source, "Does this work? Earth"); 13 | 14 | replacer.replace(0, 5, "Hmm"); 15 | assert.strictEqual(replacer.source, "Hmm Earth"); 16 | 17 | replacer.replace(11, 11, " World "); 18 | assert.strictEqual(replacer.source, "Hmm Earth World "); 19 | 20 | assert.strictEqual( 21 | replacer.source.slice(replacer.idx(1), replacer.idx(10)), 22 | "mm Eart" 23 | ); 24 | 25 | replacer.remove(0, 5, true); 26 | assert.strictEqual(replacer.source, "Earth World "); 27 | 28 | replacer.remove(6, 11, true); 29 | assert.strictEqual(replacer.source, ""); 30 | 31 | replacer.replace(0, 11, "howdy"); 32 | assert.strictEqual(replacer.source, "howdy"); 33 | } 34 | -------------------------------------------------------------------------------- /test/html/preload-local.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | import { fileURLToPath } from "url"; 4 | 5 | const generator = new Generator({ 6 | mapUrl: import.meta.url, 7 | defaultProvider: "nodemodules", 8 | }); 9 | 10 | const htmlUrl = new URL("./preload-local/index.html", import.meta.url); 11 | const html = ` 12 | 13 | 14 | 15 | 16 | preload-local.test.js 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | `; 27 | 28 | const pins = await generator.addMappings(html); 29 | const result = await generator.htmlInject(html, { 30 | pins, 31 | htmlUrl, 32 | preload: true, 33 | esModuleShims: false, 34 | }); 35 | 36 | const root = new URL("../..", import.meta.url); 37 | const re = /"modulepreload" *href="(.*)"/g; 38 | const preloads = result.matchAll(re); 39 | for (const preload of preloads) { 40 | // Make sure that all of the preloads are rebased: 41 | assert.ok(!preload[1].toString().startsWith(root.href)); 42 | } 43 | -------------------------------------------------------------------------------- /test/html/preload.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | import { SemverRange } from "sver"; 4 | import { getIntegrity } from "../../lib/common/integrity.js"; 5 | 6 | const generator = new Generator({ 7 | mapUrl: new URL("./local/page.html", import.meta.url), 8 | env: ["production", "browser"], 9 | }); 10 | 11 | const esmsPkg = await generator.traceMap.resolver.resolveLatestTarget( 12 | { name: "es-module-shims", registry: "npm", ranges: [new SemverRange("*")] }, 13 | generator.traceMap.installer.defaultProvider 14 | ); 15 | const esmsUrl = 16 | (await generator.traceMap.resolver.pkgToUrl( 17 | esmsPkg, 18 | generator.traceMap.installer.defaultProvider 19 | )) + "dist/es-module-shims.js"; 20 | const esmsIntegrity = await getIntegrity(await (await fetch(esmsUrl)).text()); 21 | 22 | let html = ` 23 | 24 | 27 | `; 28 | let pins = await generator.addMappings(html); 29 | 30 | const reactProductionIntegrity = await getIntegrity( 31 | await ( 32 | await fetch( 33 | "https://ga.jspm.io/npm:react@16.14.0/cjs/react.production.min.js" 34 | ) 35 | ).text() 36 | ); 37 | const reactIndexIntegrity = await getIntegrity( 38 | await (await fetch("https://ga.jspm.io/npm:react@16.14.0/index.js")).text() 39 | ); 40 | 41 | assert.strictEqual( 42 | await generator.htmlInject(html, { pins, integrity: true }), 43 | "\n" + 44 | "\n" + 45 | "\n" + 46 | `\n` + 47 | '\n" + 64 | '\n" 67 | ); 68 | 69 | // Idempotency 70 | html = 71 | "\n" + 72 | "\n" + 73 | "\n" + 74 | `\n` + 75 | '\n" + 87 | '\n' + 88 | '\n' + 89 | '\n' + 90 | '\n"; 93 | 94 | pins = await generator.addMappings(html); 95 | 96 | assert.strictEqual( 97 | await generator.htmlInject(html, { 98 | pins, 99 | preload: true, 100 | integrity: true, 101 | whitespace: false, 102 | }), 103 | "\n" + 104 | "\n" + 105 | "\n" + 106 | `\n` + 107 | `\n` + 108 | `\n` + 109 | '\n" 112 | ); 113 | -------------------------------------------------------------------------------- /test/html/ws.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | import { SemverRange } from "sver"; 4 | 5 | let generator = new Generator({ 6 | rootUrl: new URL("./local", import.meta.url), 7 | env: ["production", "browser"], 8 | resolutions: { 9 | react: "17", 10 | }, 11 | }); 12 | 13 | const esmsPkg = await generator.traceMap.resolver.resolveLatestTarget( 14 | { name: "es-module-shims", registry: "npm", ranges: [new SemverRange("*")] }, 15 | generator.traceMap.installer.defaultProvider 16 | ); 17 | const esmsUrl = 18 | (await generator.traceMap.resolver.pkgToUrl( 19 | esmsPkg, 20 | generator.traceMap.installer.defaultProvider 21 | )) + "dist/es-module-shims.js"; 22 | 23 | let html = ` 24 | 25 | 26 | `; 27 | 28 | let pins = await generator.addMappings(html); 29 | 30 | assert.strictEqual( 31 | await generator.htmlInject(html, { pins, whitespace: true }), 32 | "\n" + 33 | " \n" + 34 | " \n" + 35 | ` \n` + 36 | ' \n" + 48 | " \n" 49 | ); 50 | 51 | html = ` 52 | 53 | 54 | `; 55 | 56 | pins = await generator.addMappings(html); 57 | 58 | assert.strictEqual( 59 | await generator.htmlInject(html, { pins, whitespace: false }), 60 | "\n" + 61 | " \n" + 62 | " \n" + 63 | ` \n` + 64 | ' \n' + 65 | " \n" 66 | ); 67 | 68 | generator = new Generator({ 69 | rootUrl: new URL("./local", import.meta.url), 70 | env: ["production", "browser"], 71 | resolutions: { 72 | react: "17", 73 | }, 74 | }); 75 | 76 | html = ` 77 | 78 | 79 | 80 | 81 | 82 | A Title 83 | 84 | 85 |
86 | 87 | `; 88 | 89 | pins = await generator.addMappings(html); 90 | 91 | assert.strictEqual( 92 | await generator.htmlInject(html, { pins }), 93 | ` 94 | 95 | 96 | 97 | 98 | 99 | 102 | 103 | 104 | A Title 105 | 106 | 107 |
108 | 109 | ` 110 | ); 111 | -------------------------------------------------------------------------------- /test/npm-compatibility/README.md: -------------------------------------------------------------------------------- 1 | # NPM Compatibility 2 | 3 | We want to match the behaviour of `npm` for installs and updates, for better 4 | ecosystem alignment. The main question is: when does a primary/secondary 5 | dependency's version get bumped? This obviously depends on the constraint in 6 | your `package.json`, and what version (if any) is already installed in your 7 | `package-lock.json`. Since `@jspm/generator` doesn't currently have a lockfile, 8 | we treat all top-level `"imports"` in your map as primary dependencies, and any 9 | transitive dependencies in your `"scopes"` as secondary dependencies. 10 | 11 | The package we're using for testing this stuff is `wayfarer@6.6.2`, since it 12 | has a single dependency on `xtend@^4.0.1`, and multiple versions in the `6.6.x` 13 | range. To see the behaviour for your current version of `npm`, you can run 14 | `./npm-behaviour.sh`. Each of the four cases below has a corresponding test 15 | against the generator to catch regressions. 16 | 17 | 18 | ## Test Cases 19 | 20 | ### `npm install ` 21 | 22 | Primary in range: *bumped to latest in range* 23 | Primary out of range: *bumped to latest in range* 24 | Secondary in range: *kept at current version* 25 | Secondary out of range: *bumped to latest in range* 26 | Primary not latest, secondary in range: *primary bumped, secondary kept* 27 | Primary not latest, secondary out range: *primary bumped, secondary bumped* 28 | 29 | ### `npm install` 30 | 31 | Primary in range: *kept at current version* 32 | Primary out of range: *bumped to latest in range* 33 | Secondary in range: *kept at current version* 34 | Secondary out of range: *bumped to latest in range* 35 | Primary not latest, secondary in range: *primary kept, secondary kept* 36 | Primary not latest, secondary out range: *primary kept, secondary bumped* 37 | 38 | ### `npm update ` 39 | 40 | Primary in range: *bumped to latest in range* 41 | Primary out of range: *bumped to latest in range* 42 | Secondary in range: *kept at current version* 43 | Secondary out of range: *bumped to latest in range* 44 | Primary not latest, secondary in range: *primary bumped, secondary kept* 45 | Primary not latest, secondary out range: *primary bumped, secondary bumped* 46 | 47 | ### `npm update` 48 | 49 | Primary in range: *bumped to latest in range* 50 | Primary out of range: *bumped to latest in range* 51 | Secondary in range: *bumped to latest in range* 52 | Secondary out of range: *bumped to latest in range* 53 | Primary not latest, secondary in range: *primary bumped, secondary bumped* 54 | Primary not latest, secondary out range: *primary bumped, secondary bumped* 55 | 56 | 57 | ## Common Rules 58 | 59 | An argumentless `update` bumps the versions of everything to latest compatible. 60 | 61 | An argumentless `install` bumps everything that is out-of-range to latest 62 | compatible. 63 | 64 | An intentful `install `, always bumps the primary to latest compatible, 65 | and bumps the secondaries only if they're out of range. An intentful 66 | `update ` has exactly the same behaviour. 67 | -------------------------------------------------------------------------------- /test/npm-compatibility/install-pkg.test.js: -------------------------------------------------------------------------------- 1 | import { fetch } from "../../lib/common/fetch.js"; 2 | import { Generator } from "@jspm/generator"; 3 | import assert from "assert"; 4 | 5 | const expectedResults = { 6 | "primary-in-range": { 7 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.4/index.js", 8 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 9 | }, 10 | "primary-out-range": { 11 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.4/index.js", 12 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 13 | }, 14 | "secondary-in-range": { 15 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.2/index.js", 16 | xtend: "https://ga.jspm.io/npm:xtend@4.0.1/immutable.js", 17 | }, 18 | "secondary-out-range": { 19 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.2/index.js", 20 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 21 | }, 22 | "primary-not-latest-secondary-in-range": { 23 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.4/index.js", 24 | xtend: "https://ga.jspm.io/npm:xtend@4.0.1/immutable.js", 25 | }, 26 | "primary-not-latest-secondary-out-range": { 27 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.4/index.js", 28 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 29 | }, 30 | }; 31 | 32 | for (const [name, expected] of Object.entries(expectedResults)) { 33 | const res = await fetch( 34 | new URL(`./${name}/importmap.json`, import.meta.url), 35 | { 36 | cache: "no-store", // don't want cached stuff in tests 37 | } 38 | ); 39 | assert( 40 | res.status === 200 || res.status === 304, 41 | `Failed to fetch import map for ${name}: ${res.statusText}` 42 | ); 43 | const inputMap = await res.json(); 44 | const gen = new Generator({ 45 | baseUrl: new URL(`./${name}/`, import.meta.url), 46 | inputMap, 47 | }); 48 | 49 | await gen.install("wayfarer"); 50 | const map = JSON.stringify(gen.getMap(), null, 2); 51 | for (const [pkg, resolution] of Object.entries(expected)) { 52 | assert( 53 | map.includes(resolution), 54 | `${name}: ${pkg} should have resolution ${resolution}:\n${map}` 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/npm-compatibility/install.test.js: -------------------------------------------------------------------------------- 1 | import { fetch } from "../../lib/common/fetch.js"; 2 | import { Generator } from "@jspm/generator"; 3 | import assert from "assert"; 4 | 5 | const expectedResults = { 6 | "primary-in-range": { 7 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.2/index.js", 8 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 9 | }, 10 | "primary-out-range": { 11 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.4/index.js", 12 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 13 | }, 14 | "secondary-in-range": { 15 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.2/index.js", 16 | xtend: "https://ga.jspm.io/npm:xtend@4.0.1/immutable.js", 17 | }, 18 | "secondary-out-range": { 19 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.2/index.js", 20 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 21 | }, 22 | "primary-not-latest-secondary-in-range": { 23 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.2/index.js", 24 | xtend: "https://ga.jspm.io/npm:xtend@4.0.1/immutable.js", 25 | }, 26 | "primary-not-latest-secondary-out-range": { 27 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.2/index.js", 28 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 29 | }, 30 | }; 31 | 32 | for (const [name, expected] of Object.entries(expectedResults)) { 33 | const res = await fetch( 34 | new URL(`./${name}/importmap.json`, import.meta.url), 35 | { 36 | cache: "no-store", // don't want cached stuff in tests 37 | } 38 | ); 39 | assert( 40 | res.status === 200 || res.status === 304, 41 | `Failed to fetch import map for ${name}: ${res.statusText}` 42 | ); 43 | const inputMap = await res.json(); 44 | const gen = new Generator({ 45 | baseUrl: new URL(`./${name}/`, import.meta.url), 46 | inputMap, 47 | }); 48 | 49 | await gen.install(); 50 | const map = JSON.stringify(gen.getMap(), null, 2); 51 | for (const [pkg, resolution] of Object.entries(expected)) { 52 | assert( 53 | map.includes(resolution), 54 | `${name}: ${pkg} should have resolution ${resolution}:\n${map}` 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/npm-compatibility/npm-behaviour.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Spits out the behaviour of npm in various version resolution scenarios. 3 | set -e 4 | 5 | function get_version() { 6 | package_name=$1 7 | echo $(jq -r ".packages.\"node_modules/$package_name\".version" package-lock.json) 8 | } 9 | 10 | function get_range() { 11 | package_name=$1 12 | echo $(jq -r ".dependencies.\"${package_name}\"" package.json) 13 | } 14 | 15 | function test_command() { 16 | cmd=$1 17 | tests=( 18 | "primary-in-range" 19 | "primary-out-range" 20 | "secondary-in-range" 21 | "secondary-out-range" 22 | "primary-not-latest-secondary-in-range" 23 | "primary-not-latest-secondary-out-range" 24 | ) 25 | 26 | for test in "${tests[@]}" 27 | do 28 | cp "${test}/package.json" package.json.bkp 29 | cp "${test}/package-lock.json" package-lock.json.bkp 30 | cp "${test}/importmap.json" importmap.json.bkp 31 | 32 | cd "${test}" 33 | primary="wayfarer" 34 | primary_range=$(get_range "${primary}") 35 | primary_preversion=$(get_version "${primary}") 36 | secondary="xtend" 37 | secondary_range="^4.0.1" 38 | secondary_preversion=$(get_version "${secondary}") 39 | 40 | echo "Running \"npm ${@}\" in ${test}:" 41 | npm "${@}" 2>&1 1>/dev/null 42 | 43 | primary_postversion=$(get_version "${primary}") 44 | secondary_postversion=$(get_version "${secondary}") 45 | echo " range (pr): ${primary_range}" 46 | echo " before (pr): ${primary}@${primary_preversion}" 47 | echo " after (pr): ${primary}@${primary_postversion}" 48 | echo " range (sn): ${secondary_range}" 49 | echo " before (sn): ${secondary}@${secondary_preversion}" 50 | echo " after (sn): ${secondary}@${secondary_postversion}" 51 | cd - 2>&1 1>/dev/null 52 | 53 | rm -r "${test}/" 54 | mkdir "${test}" 55 | mv package.json.bkp "${test}/package.json" 56 | mv package-lock.json.bkp "${test}/package-lock.json" 57 | mv importmap.json.bkp "${test}/importmap.json" 58 | done 59 | echo 60 | } 61 | 62 | cd $(dirname "$0") 63 | test_command install wayfarer 64 | test_command install 65 | test_command update wayfarer 66 | test_command update 67 | -------------------------------------------------------------------------------- /test/npm-compatibility/primary-in-range/importmap.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": [ 3 | "browser", 4 | "development", 5 | "module" 6 | ], 7 | "imports": { 8 | "wayfarer": "https://ga.jspm.io/npm:wayfarer@6.6.2/index.js" 9 | }, 10 | "scopes": { 11 | "https://ga.jspm.io/": { 12 | "assert": "https://ga.jspm.io/npm:@jspm/core@2.0.1/nodelibs/browser/assert.js", 13 | "xtend": "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 14 | "xtend/mutable": "https://ga.jspm.io/npm:xtend@4.0.2/mutable.js" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/npm-compatibility/primary-in-range/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "test", 8 | "dependencies": { 9 | "wayfarer": "^6.6.2" 10 | } 11 | }, 12 | "node_modules/wayfarer": { 13 | "version": "6.6.2", 14 | "resolved": "https://registry.npmjs.org/wayfarer/-/wayfarer-6.6.2.tgz", 15 | "integrity": "sha512-jwIufUN6EYfMeCJYBA8r0YytqHaSGACLtOddMeBtdq5gTo2F2XgK+t7eEXSPUBF9vm+hdI/iPOtSz1EJOd01dQ==", 16 | "dependencies": { 17 | "xtend": "^4.0.1" 18 | } 19 | }, 20 | "node_modules/xtend": { 21 | "version": "4.0.2", 22 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 23 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 24 | "engines": { 25 | "node": ">=0.4" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/npm-compatibility/primary-in-range/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "dependencies": { 4 | "wayfarer": "^6.6.2" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/npm-compatibility/primary-not-latest-secondary-in-range/importmap.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": [ 3 | "browser", 4 | "development", 5 | "module" 6 | ], 7 | "imports": { 8 | "wayfarer": "https://ga.jspm.io/npm:wayfarer@6.6.2/index.js" 9 | }, 10 | "scopes": { 11 | "https://ga.jspm.io/": { 12 | "assert": "https://ga.jspm.io/npm:@jspm/core@2.0.1/nodelibs/browser/assert.js", 13 | "xtend": "https://ga.jspm.io/npm:xtend@4.0.1/immutable.js", 14 | "xtend/mutable": "https://ga.jspm.io/npm:xtend@4.0.1/mutable.js" 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /test/npm-compatibility/primary-not-latest-secondary-in-range/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "test", 8 | "dependencies": { 9 | "wayfarer": "^6.6.2" 10 | } 11 | }, 12 | "node_modules/wayfarer": { 13 | "version": "6.6.2", 14 | "resolved": "https://registry.npmjs.org/wayfarer/-/wayfarer-6.6.2.tgz", 15 | "integrity": "sha512-jwIufUN6EYfMeCJYBA8r0YytqHaSGACLtOddMeBtdq5gTo2F2XgK+t7eEXSPUBF9vm+hdI/iPOtSz1EJOd01dQ==", 16 | "dependencies": { 17 | "xtend": "^4.0.1" 18 | } 19 | }, 20 | "node_modules/xtend": { 21 | "version": "4.0.1", 22 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 23 | "integrity": "sha512-iTwvhNBRetXWe81+VcIw5YeadVSWyze7uA7nVnpP13ulrpnJ3UfQm5ApGnrkmxDJFdrblRdZs0EvaTCIfei5oQ==", 24 | "engines": { 25 | "node": ">=0.4" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/npm-compatibility/primary-not-latest-secondary-in-range/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "dependencies": { 4 | "wayfarer": "^6.6.2" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/npm-compatibility/primary-not-latest-secondary-out-range/importmap.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": [ 3 | "browser", 4 | "development", 5 | "module" 6 | ], 7 | "imports": { 8 | "wayfarer": "https://ga.jspm.io/npm:wayfarer@6.6.2/index.js" 9 | }, 10 | "scopes": { 11 | "https://ga.jspm.io/": { 12 | "assert": "https://ga.jspm.io/npm:@jspm/core@2.0.1/nodelibs/browser/assert.js", 13 | "xtend": "https://ga.jspm.io/npm:xtend@3.0.0/index.js", 14 | "xtend/mutable": "https://ga.jspm.io/npm:xtend@3.0.0/mutable.js" 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /test/npm-compatibility/primary-not-latest-secondary-out-range/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "test", 8 | "dependencies": { 9 | "wayfarer": "^6.6.2" 10 | } 11 | }, 12 | "node_modules/wayfarer": { 13 | "version": "6.6.2", 14 | "resolved": "https://registry.npmjs.org/wayfarer/-/wayfarer-6.6.2.tgz", 15 | "integrity": "sha512-jwIufUN6EYfMeCJYBA8r0YytqHaSGACLtOddMeBtdq5gTo2F2XgK+t7eEXSPUBF9vm+hdI/iPOtSz1EJOd01dQ==", 16 | "dependencies": { 17 | "xtend": "^4.0.1" 18 | } 19 | }, 20 | "node_modules/xtend": { 21 | "version": "3.0.0", 22 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", 23 | "integrity": "sha512-sp/sT9OALMjRW1fKDlPeuSZlDQpkqReA0pyJukniWbTGoEKefHxhGJynE3PNhUMlcM8qWIjPwecwCw4LArS5Eg==", 24 | "engines": { 25 | "node": ">=0.4" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/npm-compatibility/primary-not-latest-secondary-out-range/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "dependencies": { 4 | "wayfarer": "^6.6.2" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/npm-compatibility/primary-out-range/importmap.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": [ 3 | "browser", 4 | "development", 5 | "module" 6 | ], 7 | "imports": { 8 | "wayfarer": "https://ga.jspm.io/npm:wayfarer@6.6.2/index.js" 9 | }, 10 | "scopes": { 11 | "https://ga.jspm.io/": { 12 | "assert": "https://ga.jspm.io/npm:@jspm/core@2.0.1/nodelibs/browser/assert.js", 13 | "xtend": "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 14 | "xtend/mutable": "https://ga.jspm.io/npm:xtend@4.0.2/mutable.js" 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /test/npm-compatibility/primary-out-range/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "test", 8 | "dependencies": { 9 | "wayfarer": "^6.6.2" 10 | } 11 | }, 12 | "node_modules/wayfarer": { 13 | "version": "6.6.2", 14 | "resolved": "https://registry.npmjs.org/wayfarer/-/wayfarer-6.6.2.tgz", 15 | "integrity": "sha512-jwIufUN6EYfMeCJYBA8r0YytqHaSGACLtOddMeBtdq5gTo2F2XgK+t7eEXSPUBF9vm+hdI/iPOtSz1EJOd01dQ==", 16 | "dependencies": { 17 | "xtend": "^4.0.1" 18 | } 19 | }, 20 | "node_modules/xtend": { 21 | "version": "4.0.2", 22 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 23 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 24 | "engines": { 25 | "node": ">=0.4" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/npm-compatibility/primary-out-range/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "dependencies": { 4 | "wayfarer": "^6.6.3" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/npm-compatibility/secondary-in-range/importmap.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": [ 3 | "browser", 4 | "development", 5 | "module" 6 | ], 7 | "imports": { 8 | "wayfarer": "https://ga.jspm.io/npm:wayfarer@6.6.2/index.js" 9 | }, 10 | "scopes": { 11 | "https://ga.jspm.io/": { 12 | "assert": "https://ga.jspm.io/npm:@jspm/core@2.0.1/nodelibs/browser/assert.js", 13 | "xtend": "https://ga.jspm.io/npm:xtend@4.0.1/immutable.js", 14 | "xtend/mutable": "https://ga.jspm.io/npm:xtend@4.0.1/mutable.js" 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /test/npm-compatibility/secondary-in-range/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "test", 8 | "dependencies": { 9 | "wayfarer": "^6.6.2" 10 | } 11 | }, 12 | "node_modules/wayfarer": { 13 | "version": "6.6.2", 14 | "resolved": "https://registry.npmjs.org/wayfarer/-/wayfarer-6.6.2.tgz", 15 | "integrity": "sha512-jwIufUN6EYfMeCJYBA8r0YytqHaSGACLtOddMeBtdq5gTo2F2XgK+t7eEXSPUBF9vm+hdI/iPOtSz1EJOd01dQ==", 16 | "dependencies": { 17 | "xtend": "^4.0.1" 18 | } 19 | }, 20 | "node_modules/xtend": { 21 | "version": "4.0.1", 22 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 23 | "integrity": "sha512-iTwvhNBRetXWe81+VcIw5YeadVSWyze7uA7nVnpP13ulrpnJ3UfQm5ApGnrkmxDJFdrblRdZs0EvaTCIfei5oQ==", 24 | "engines": { 25 | "node": ">=0.4" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/npm-compatibility/secondary-in-range/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "dependencies": { 4 | "wayfarer": "6.6.2" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/npm-compatibility/secondary-out-range/importmap.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": [ 3 | "browser", 4 | "development", 5 | "module" 6 | ], 7 | "imports": { 8 | "wayfarer": "https://ga.jspm.io/npm:wayfarer@6.6.2/index.js" 9 | }, 10 | "scopes": { 11 | "https://ga.jspm.io/": { 12 | "assert": "https://ga.jspm.io/npm:@jspm/core@2.0.1/nodelibs/browser/assert.js", 13 | "xtend": "https://ga.jspm.io/npm:xtend@3.0.0/index.js", 14 | "xtend/mutable": "https://ga.jspm.io/npm:xtend@3.0.0/mutable.js" 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /test/npm-compatibility/secondary-out-range/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "test", 8 | "dependencies": { 9 | "wayfarer": "^6.6.2" 10 | } 11 | }, 12 | "node_modules/wayfarer": { 13 | "version": "6.6.2", 14 | "resolved": "https://registry.npmjs.org/wayfarer/-/wayfarer-6.6.2.tgz", 15 | "integrity": "sha512-jwIufUN6EYfMeCJYBA8r0YytqHaSGACLtOddMeBtdq5gTo2F2XgK+t7eEXSPUBF9vm+hdI/iPOtSz1EJOd01dQ==", 16 | "dependencies": { 17 | "xtend": "^4.0.1" 18 | } 19 | }, 20 | "node_modules/xtend": { 21 | "version": "3.0.0", 22 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", 23 | "integrity": "sha512-sp/sT9OALMjRW1fKDlPeuSZlDQpkqReA0pyJukniWbTGoEKefHxhGJynE3PNhUMlcM8qWIjPwecwCw4LArS5Eg==", 24 | "engines": { 25 | "node": ">=0.4" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/npm-compatibility/secondary-out-range/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "dependencies": { 4 | "wayfarer": "6.6.2" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/npm-compatibility/update-pkg.test.js: -------------------------------------------------------------------------------- 1 | import { fetch } from "../../lib/common/fetch.js"; 2 | import { Generator } from "@jspm/generator"; 3 | import assert from "assert"; 4 | 5 | const expectedResults = { 6 | "primary-in-range": { 7 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.4/index.js", 8 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 9 | }, 10 | "primary-out-range": { 11 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.4/index.js", 12 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 13 | }, 14 | "secondary-in-range": { 15 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.2/index.js", 16 | xtend: "https://ga.jspm.io/npm:xtend@4.0.1/immutable.js", 17 | }, 18 | "secondary-out-range": { 19 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.2/index.js", 20 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 21 | }, 22 | "primary-not-latest-secondary-in-range": { 23 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.4/index.js", 24 | xtend: "https://ga.jspm.io/npm:xtend@4.0.1/immutable.js", 25 | }, 26 | "primary-not-latest-secondary-out-range": { 27 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.4/index.js", 28 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 29 | }, 30 | }; 31 | 32 | for (const [name, expected] of Object.entries(expectedResults)) { 33 | const res = await fetch( 34 | new URL(`./${name}/importmap.json`, import.meta.url), 35 | { 36 | cache: "no-store", // don't want cached stuff in tests 37 | } 38 | ); 39 | assert( 40 | res.status === 200 || res.status === 304, 41 | `Failed to fetch import map for ${name}: ${res.statusText}` 42 | ); 43 | const inputMap = await res.json(); 44 | const gen = new Generator({ 45 | baseUrl: new URL(`./${name}/`, import.meta.url), 46 | inputMap, 47 | }); 48 | 49 | await gen.update("wayfarer"); 50 | const map = JSON.stringify(gen.getMap(), null, 2); 51 | for (const [pkg, resolution] of Object.entries(expected)) { 52 | assert( 53 | map.includes(resolution), 54 | `${name}: ${pkg} should have resolution ${resolution}:\n${map}` 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/npm-compatibility/update.test.js: -------------------------------------------------------------------------------- 1 | import { fetch } from "../../lib/common/fetch.js"; 2 | import { Generator } from "@jspm/generator"; 3 | import assert from "assert"; 4 | 5 | const expectedResults = { 6 | "primary-in-range": { 7 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.4/index.js", 8 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 9 | }, 10 | "primary-out-range": { 11 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.4/index.js", 12 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 13 | }, 14 | "secondary-in-range": { 15 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.2/index.js", 16 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 17 | }, 18 | "secondary-out-range": { 19 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.2/index.js", 20 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 21 | }, 22 | "primary-not-latest-secondary-in-range": { 23 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.4/index.js", 24 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 25 | }, 26 | "primary-not-latest-secondary-out-range": { 27 | wayfarer: "https://ga.jspm.io/npm:wayfarer@6.6.4/index.js", 28 | xtend: "https://ga.jspm.io/npm:xtend@4.0.2/immutable.js", 29 | }, 30 | }; 31 | 32 | for (const [name, expected] of Object.entries(expectedResults)) { 33 | const res = await fetch( 34 | new URL(`./${name}/importmap.json`, import.meta.url), 35 | { 36 | cache: "no-store", // don't want cached stuff in tests 37 | } 38 | ); 39 | assert( 40 | res.status === 200 || res.status === 304, 41 | `Failed to fetch import map for ${name}: ${res.statusText}` 42 | ); 43 | const inputMap = await res.json(); 44 | const gen = new Generator({ 45 | baseUrl: new URL(`./${name}/`, import.meta.url), 46 | inputMap, 47 | }); 48 | 49 | await gen.update(); 50 | const map = JSON.stringify(gen.getMap(), null, 2); 51 | for (const [pkg, resolution] of Object.entries(expected)) { 52 | assert( 53 | map.includes(resolution), 54 | `${name}: ${pkg} should have resolution ${resolution}:\n${map}` 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/perf/perf.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | import { fetch } from "../../lib/common/fetch.js"; 4 | 5 | const largeInstallSet = await ( 6 | await fetch(new URL("./large-install-set.json", import.meta.url), {}) 7 | ).json(); 8 | 9 | // First, prime the fetch cache so we are not testing the network as much as possible 10 | { 11 | const generator = new Generator({ 12 | defaultProvider: "jspm.io", 13 | resolutions: { 14 | react: "16.14.0", 15 | }, 16 | }); 17 | const installs = Object.entries(largeInstallSet).map( 18 | ([name, versionRange]) => ({ target: name + "@" + versionRange }) 19 | ); 20 | await generator.install(installs); 21 | } 22 | 23 | // Then we do the actual perf test run 24 | { 25 | const generator = new Generator({ 26 | defaultProvider: "jspm.io", 27 | resolutions: { 28 | react: "16.14.0", 29 | }, 30 | }); 31 | 32 | const installs = Object.entries(largeInstallSet).map( 33 | ([name, versionRange]) => ({ target: name + "@" + versionRange }) 34 | ); 35 | 36 | const start = performance.now(); 37 | await generator.install(installs); 38 | 39 | console.log(`PERF TEST TIME: ${performance.now() - start}ms`); 40 | } 41 | -------------------------------------------------------------------------------- /test/providers/config.perf.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | // this private origin shouldn't really be shared publicly 5 | const name = [111, 97, 107, 116, 105, 113] 6 | .map((x) => String.fromCharCode(x)) 7 | .reverse() 8 | .join(""); 9 | 10 | // Test with custom CDN URL 11 | { 12 | const generator = new Generator({ 13 | mapUrl: import.meta.url, 14 | defaultProvider: "jspm.io", 15 | providerConfig: { 16 | "jspm.io": { 17 | cdnUrl: `https://${name}.com/`, 18 | }, 19 | }, 20 | }); 21 | 22 | await generator.install("react@17.0.1"); 23 | const json = generator.getMap(); 24 | 25 | assert.strictEqual( 26 | json.imports.react, 27 | `https://${name}.com/npm:react@17.0.1/dev.index.js` 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /test/providers/coremods/deno.js: -------------------------------------------------------------------------------- 1 | import "deno:path"; 2 | import "deno:testing/asserts"; 3 | import "deno:version"; 4 | import "npm:sver@1"; 5 | -------------------------------------------------------------------------------- /test/providers/coremods/deno.notfound.js: -------------------------------------------------------------------------------- 1 | import "deno:notfound"; 2 | -------------------------------------------------------------------------------- /test/providers/coremods/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "exports": { 3 | "./deno": "./deno.js" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/providers/custom.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const unpkgUrl = "https://unpkg.com/"; 5 | const exactPkgRegEx = /^((?:@[^/\\%@]+\/)?[^./\\%@][^/\\%@]*)@([^\/]+)(\/.*)?$/; 6 | 7 | const generator = new Generator({ 8 | defaultProvider: "custom", 9 | customProviders: { 10 | custom: { 11 | pkgToUrl({ registry, name, version }) { 12 | return `${unpkgUrl}${name}@${version}/`; 13 | }, 14 | parseUrlPkg(url) { 15 | if (url.startsWith(unpkgUrl)) { 16 | const [, name, version] = 17 | url.slice(unpkgUrl.length).match(exactPkgRegEx) || []; 18 | return { registry: "npm", name, version }; 19 | } 20 | }, 21 | resolveLatestTarget( 22 | { registry, name, range, unstable }, 23 | layer, 24 | parentUrl 25 | ) { 26 | return { registry, name, version: "3.6.0" }; 27 | }, 28 | }, 29 | }, 30 | }); 31 | 32 | await generator.install("custom:jquery"); 33 | 34 | const json = generator.getMap(); 35 | 36 | assert.strictEqual( 37 | json.imports.jquery, 38 | "https://unpkg.com/jquery@3.6.0/dist/jquery.js" 39 | ); 40 | -------------------------------------------------------------------------------- /test/providers/customregistry.test.js: -------------------------------------------------------------------------------- 1 | import { Generator, lookup, fetch } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const myorgUrl = "https://unpkg.com/"; 5 | const exactPkgRegEx = /^((?:@[^/\\%@]+\/)?[^./\\%@][^/\\%@]*)@([^\/]+)(\/.*)?$/; 6 | 7 | const generator = new Generator({ 8 | defaultProvider: "myorg", 9 | customProviders: { 10 | myorg: { 11 | ownsUrl(url) { 12 | return url.startsWith(myorgUrl); 13 | }, 14 | pkgToUrl({ registry, name, version }) { 15 | return `${myorgUrl}${name}@${version}/`; 16 | }, 17 | async getPackageConfig(pkgUrl) { 18 | // hook package.json lookup to insert explicit registry identifiers in package.json dependencies 19 | // the alternative to this approach is to set a global "defaultRegistry" generator option 20 | // but that option is limited in that the defaultRegistry is global instead of being per-provider / 21 | // per-service. 22 | // This is thus the recommended way to support multi-registry workflows, keeping npm as the default 23 | // (per ecosystem semantics), and instead overriding package.json dependency schemas to point to 24 | // any new registries. 25 | const pcfg = await (await fetch(`${pkgUrl}package.json`)).json(); 26 | // strictly, this should also be for optionalDependencies + peerDependencies 27 | if (pcfg.dependencies) { 28 | let dependencies = {}; 29 | for (let [name, target] of Object.entries(pcfg.dependencies)) { 30 | if (target.indexOf(":") === -1) 31 | target = "myorg:" + name + "@" + target; 32 | dependencies[name] = target; 33 | } 34 | pcfg.dependencies = dependencies; 35 | } 36 | return pcfg; 37 | }, 38 | parseUrlPkg(url) { 39 | if (url.startsWith(myorgUrl)) { 40 | const [, name, version] = 41 | url.slice(myorgUrl.length).match(exactPkgRegEx) || []; 42 | return { registry: "myorg", name, version }; 43 | } 44 | }, 45 | async resolveLatestTarget( 46 | { registry, name, range, unstable }, 47 | layer, 48 | parentUrl 49 | ) { 50 | assert.ok(registry === "myorg"); 51 | const { 52 | resolved: { name: resolvedName, version: resolvedVersion }, 53 | } = await lookup(`${name}@${range.toString()}`); 54 | return { 55 | registry: "myorg", 56 | name: resolvedName, 57 | version: resolvedVersion, 58 | }; 59 | }, 60 | }, 61 | }, 62 | }); 63 | 64 | await generator.install("myorg:lit"); 65 | 66 | const json = generator.getMap(); 67 | 68 | assert.ok(json.imports.lit.startsWith("https://unpkg.com/lit@")); 69 | -------------------------------------------------------------------------------- /test/providers/esmsh-root.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const inputMap = { 5 | "imports": { 6 | "react-intl": "https://esm.sh/*react-intl@6.4.4/lib/index.js" 7 | }, 8 | "scopes": { 9 | "https://esm.sh/": { 10 | "@formatjs/ecma402-abstract": "https://esm.sh/*@formatjs/ecma402-abstract@1.17.0/lib/index.js", 11 | "@formatjs/fast-memoize": "https://esm.sh/*@formatjs/fast-memoize@2.2.0/lib/index.js", 12 | "@formatjs/icu-messageformat-parser": "https://esm.sh/*@formatjs/icu-messageformat-parser@2.6.0/lib/index.js", 13 | "@formatjs/icu-skeleton-parser": "https://esm.sh/*@formatjs/icu-skeleton-parser@1.6.0/lib/index.js", 14 | "@formatjs/intl": "https://esm.sh/*@formatjs/intl@2.9.0/lib/index.js", 15 | "@formatjs/intl-localematcher": "https://esm.sh/*@formatjs/intl-localematcher@0.4.0/lib/index.js", 16 | "hoist-non-react-statics": "https://esm.sh/*hoist-non-react-statics@3.3.2/dist/hoist-non-react-statics.cjs.js", 17 | "intl-messageformat": "https://esm.sh/*intl-messageformat@10.5.0/lib/index.js", 18 | "react": "https://esm.sh/*react@18.3.1/index.js", 19 | "react-is": "https://esm.sh/*react-is@16.13.1/index.js", 20 | "tslib": "https://esm.sh/*tslib@2.8.1/tslib.es6.mjs" 21 | } 22 | } 23 | }; 24 | 25 | const generator = new Generator({ 26 | mapUrl: import.meta.url, 27 | env: ["production", "browser"], 28 | inputMap, 29 | }); 30 | 31 | await generator.install(); 32 | -------------------------------------------------------------------------------- /test/providers/esmsh.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "esm.sh", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.install("react@18"); 11 | const json = generator.getMap(); 12 | 13 | assert.strictEqual(json.imports.react, "https://esm.sh/*react@18.3.1/index.js"); 14 | 15 | // TODO: Reenable lit test when fixed upstream 16 | // await generator.install("lit@2.0.0-rc.1"); 17 | // const json = generator.getMap(); 18 | 19 | // assert.strictEqual(json.imports.lit, "https://esm.sh/*lit@2.0.0-rc.1/index.js"); 20 | 21 | // const scope = json.scopes["https://esm.sh/"]; 22 | // assert.ok(scope["@lit/reactive-element"]); 23 | // assert.ok(scope["lit-element/lit-element.js"]); 24 | // assert.ok(scope["lit-html"]); 25 | 26 | await generator.install("twind"); 27 | 28 | { 29 | const json = generator.getMap(); 30 | assert.ok(json.imports.twind); 31 | } 32 | -------------------------------------------------------------------------------- /test/providers/jsdelivr.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "jsdelivr", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.install("lit@2.0.0-rc.1"); 11 | const json = generator.getMap(); 12 | 13 | assert.strictEqual( 14 | json.imports.lit, 15 | "https://cdn.jsdelivr.net/npm/lit@2.0.0-rc.1/index.js" 16 | ); 17 | const scope = json.scopes["https://cdn.jsdelivr.net/"]; 18 | assert.ok(scope["@lit/reactive-element"]); 19 | assert.ok(scope["lit-element/lit-element.js"]); 20 | assert.ok(scope["lit-html"]); 21 | -------------------------------------------------------------------------------- /test/providers/jspm-err.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | env: ["production", "browser"], 7 | }); 8 | 9 | try { 10 | await generator.install("@elliemae/ds-icons@1.53.3-rc.10"); 11 | throw new Error("Install should have errorred"); 12 | } catch (err) { 13 | // TODO: Find a package with a known build error! 14 | // This one started working.... 15 | assert.ok(true || err.message.includes("with error")); 16 | } 17 | -------------------------------------------------------------------------------- /test/providers/jspm.system.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "jspm.io#system", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.install("lit@2.0.0-rc.1"); 11 | const json = generator.getMap(); 12 | 13 | assert.strictEqual( 14 | json.imports.lit, 15 | "https://ga.system.jspm.io/npm:lit@2.0.0-rc.1/index.js" 16 | ); 17 | const scope = json.scopes["https://ga.system.jspm.io/"]; 18 | assert.ok(scope["@lit/reactive-element"]); 19 | assert.ok(scope["lit-element/lit-element.js"]); 20 | assert.ok(scope["lit-html"]); 21 | -------------------------------------------------------------------------------- /test/providers/localdeps.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | // Test that local "file:..."-type depencies are correctly linked into the 5 | // node_modules folder: 6 | 7 | let generator = new Generator({ 8 | mapUrl: new URL("./localdeps/pkg/importmap.json", import.meta.url), 9 | defaultProvider: "nodemodules", 10 | }); 11 | 12 | await generator.link("./index.js"); 13 | const map = generator.getMap(); 14 | assert.strictEqual(map.imports?.["tar"], "./node_modules/tar/index.js"); 15 | assert.strictEqual(map.imports?.["dep"], "./node_modules/dep/index.js"); 16 | -------------------------------------------------------------------------------- /test/providers/localdeps/dep/index.js: -------------------------------------------------------------------------------- 1 | const a = 1; 2 | -------------------------------------------------------------------------------- /test/providers/localdeps/dep/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dep", 3 | "type": "module", 4 | "exports": "./index.js" 5 | } 6 | -------------------------------------------------------------------------------- /test/providers/localdeps/pkg/.gitignore: -------------------------------------------------------------------------------- 1 | !node_modules 2 | -------------------------------------------------------------------------------- /test/providers/localdeps/pkg/index.js: -------------------------------------------------------------------------------- 1 | import "tar"; 2 | import "dep"; 3 | -------------------------------------------------------------------------------- /test/providers/localdeps/pkg/node_modules/.package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pkg", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "node_modules/dep": { 7 | "resolved": "file:../dep" 8 | }, 9 | "node_modules/tar": { 10 | "resolved": "file:../tar.tgz", 11 | "integrity": "sha512-Gd42EZQ184rEXzHr2J19hITSpI/tj2aUVyoVV4qIocQa6RwomEMU8udJ0HHPIKMreevaAsKxnkM+vkuFEiLPgA==" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/providers/localdeps/pkg/node_modules/dep/index.js: -------------------------------------------------------------------------------- 1 | const a = 1; 2 | -------------------------------------------------------------------------------- /test/providers/localdeps/pkg/node_modules/dep/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dep", 3 | "type": "module", 4 | "exports": "./index.js" 5 | } 6 | -------------------------------------------------------------------------------- /test/providers/localdeps/pkg/node_modules/tar/index.js: -------------------------------------------------------------------------------- 1 | const b = 1; 2 | -------------------------------------------------------------------------------- /test/providers/localdeps/pkg/node_modules/tar/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"tar", 3 | "type": "module", 4 | "exports": "./index.js" 5 | } 6 | -------------------------------------------------------------------------------- /test/providers/localdeps/pkg/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pkg", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "pkg", 8 | "dependencies": { 9 | "dep": "file:../dep", 10 | "tar": "file:../tar.tgz" 11 | } 12 | }, 13 | "node_modules/dep": { 14 | "resolved": "file:../dep" 15 | }, 16 | "node_modules/tar": { 17 | "resolved": "file:../tar.tgz", 18 | "integrity": "sha512-Gd42EZQ184rEXzHr2J19hITSpI/tj2aUVyoVV4qIocQa6RwomEMU8udJ0HHPIKMreevaAsKxnkM+vkuFEiLPgA==" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/providers/localdeps/pkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pkg", 3 | "type": "module", 4 | "exports": "./index.js", 5 | "dependencies": { 6 | "dep": "file:../dep", 7 | "tar": "file:../tar.tgz" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/providers/localdeps/tar.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jspm/generator/0127edc78701d34d05565a2603bc52b134332ee8/test/providers/localdeps/tar.tgz -------------------------------------------------------------------------------- /test/providers/nodemodules.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | let generator = new Generator({ 5 | mapUrl: new URL("../../", import.meta.url), 6 | defaultProvider: "nodemodules", 7 | commonJS: true, 8 | }); 9 | 10 | await generator.install("chalk"); 11 | 12 | let json = generator.getMap(); 13 | assert.strictEqual( 14 | json.imports["chalk"], 15 | "./node_modules/chalk/source/index.js" 16 | ); 17 | 18 | // Check that we can install using the jspm.io provider, and then go back to 19 | // the nodemodules provider, without affecting the resolutions: 20 | 21 | generator = new Generator({ 22 | mapUrl: new URL("../../", import.meta.url), 23 | defaultProvider: "jspm.io", 24 | inputMap: json, 25 | commonJS: true, 26 | }); 27 | await generator.install(); 28 | json = generator.getMap(); 29 | 30 | generator = new Generator({ 31 | mapUrl: new URL("../../", import.meta.url), 32 | defaultProvider: "nodemodules", 33 | inputMap: json, 34 | commonJS: true, 35 | }); 36 | await generator.install(); 37 | json = generator.getMap(); 38 | assert.strictEqual( 39 | json.imports["chalk"], 40 | "./node_modules/chalk/source/index.js" 41 | ); 42 | -------------------------------------------------------------------------------- /test/providers/scopeprovider.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: new URL("../../", import.meta.url), 6 | defaultProvider: "nodemodules", 7 | providers: { 8 | "lit-html": "jspm.io", 9 | }, 10 | }); 11 | 12 | await generator.install("lit-element"); 13 | await generator.install("lit-html"); 14 | 15 | const json = generator.getMap(); 16 | 17 | assert.strictEqual( 18 | json.imports["lit-element"], 19 | "./node_modules/lit-element/lit-element.js" 20 | ); 21 | 22 | assert.ok( 23 | json.scopes["./node_modules/lit-element/"]["lit-html/"].startsWith( 24 | "https://ga.jspm.io" 25 | ) 26 | ); 27 | -------------------------------------------------------------------------------- /test/providers/skypack.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "skypack", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.install("react@16"); 11 | const json = generator.getMap(); 12 | assert.strictEqual( 13 | json.imports.react, 14 | "https://cdn.skypack.dev/react@16.14.0/index.js" 15 | ); 16 | -------------------------------------------------------------------------------- /test/providers/unpkg.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "unpkg", 7 | env: ["production", "browser"], 8 | }); 9 | 10 | await generator.install("lit@2.0.0-rc.1"); 11 | const json = generator.getMap(); 12 | 13 | assert.strictEqual( 14 | json.imports.lit, 15 | "https://unpkg.com/lit@2.0.0-rc.1/index.js" 16 | ); 17 | 18 | const scope = json.scopes["https://unpkg.com/"]; 19 | assert.ok(scope["@lit/reactive-element"]); 20 | assert.ok(scope["lit-element/lit-element.js"]); 21 | assert.ok(scope["lit-html"]); 22 | -------------------------------------------------------------------------------- /test/resolve/builtin-shim.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | if (typeof document === "undefined") { 5 | const generator = new Generator({ 6 | mapUrl: import.meta.url, 7 | defaultProvider: "nodemodules", 8 | commonJS: true, 9 | }); 10 | 11 | // await generator.link('./cjspkg/mod.js'); 12 | await generator.link("./cjspkg/mod-shim.js"); 13 | 14 | const json = generator.getMap(); 15 | 16 | assert.deepStrictEqual(json, { 17 | imports: { 18 | "process/": "./cjspkg/node_modules/process/index.js", 19 | }, 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /test/resolve/chalk.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | if (typeof document === "undefined") { 5 | const generator = new Generator({ 6 | mapUrl: import.meta.url, 7 | defaultProvider: "nodemodules", 8 | commonJS: true, 9 | }); 10 | 11 | await generator.install("chalk"); 12 | const json = generator.getMap(); 13 | assert.equal(Object.keys(json.imports).length, 5); 14 | 15 | // The exact scope name changes depending on whether chalk's dependencies are 16 | // installed as primaries or secondaries on the server where this test runs: 17 | const scopeKeys = Object.keys(json.scopes); 18 | assert.equal(scopeKeys.length, 1); 19 | assert.equal(Object.keys(json.scopes[scopeKeys[0]]).length, 4); 20 | } 21 | -------------------------------------------------------------------------------- /test/resolve/cjspkg/browser-dep-exclude.js: -------------------------------------------------------------------------------- 1 | import "excluded"; 2 | -------------------------------------------------------------------------------- /test/resolve/cjspkg/browser-dep-include.js: -------------------------------------------------------------------------------- 1 | import "jquery"; 2 | -------------------------------------------------------------------------------- /test/resolve/cjspkg/browser.js: -------------------------------------------------------------------------------- 1 | import "./browser-dep-exclude.js"; 2 | import "./browser-dep-include.js"; 3 | -------------------------------------------------------------------------------- /test/resolve/cjspkg/mod-shim.js: -------------------------------------------------------------------------------- 1 | const depProcess = require("process/"); 2 | 3 | console.log(depProcess === "process shim"); 4 | -------------------------------------------------------------------------------- /test/resolve/cjspkg/mod.js: -------------------------------------------------------------------------------- 1 | const builtinProcess = require("process"); 2 | 3 | console.log(builtinProcess === process); 4 | -------------------------------------------------------------------------------- /test/resolve/cjspkg/node_modules/process/index.js: -------------------------------------------------------------------------------- 1 | module.exports = 'process shim'; 2 | -------------------------------------------------------------------------------- /test/resolve/cjspkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "process": "^0.11.10" 4 | }, 5 | "browser": { 6 | "./browser-dep-exclude.js": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/resolve/dayjs.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | defaultProvider: "jsdelivr", 6 | }); 7 | 8 | await generator.install("@cubejs-client/core@0.35.23"); 9 | const json = generator.getMap(); 10 | assert(Object.keys(json.imports).length === 1); 11 | -------------------------------------------------------------------------------- /test/resolve/legacy-main.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "nodemodules", 7 | commonJS: true, 8 | }); 9 | 10 | await generator.install("./legacypkg"); 11 | 12 | const json = generator.getMap(); 13 | 14 | assert.strictEqual(json.imports["legacypkg"], "./legacypkg/m/index.js"); 15 | -------------------------------------------------------------------------------- /test/resolve/legacypkg/m/index.js: -------------------------------------------------------------------------------- 1 | module.exports = "index"; 2 | -------------------------------------------------------------------------------- /test/resolve/legacypkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "m" 3 | } 4 | -------------------------------------------------------------------------------- /test/resolve/node-maps.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | env: ["node", "development"], 6 | }); 7 | 8 | await generator.install("react-dom"); 9 | 10 | const json = generator.getMap(); 11 | 12 | assert.strictEqual( 13 | json.scopes["https://ga.jspm.io/"]["node:process"], 14 | undefined 15 | ); 16 | -------------------------------------------------------------------------------- /test/resolve/nodemodules.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: new URL("../../", import.meta.url), 6 | defaultProvider: "nodemodules", 7 | }); 8 | 9 | await generator.install("lit-element"); 10 | await generator.install("lit-html"); 11 | 12 | const json = generator.getMap(); 13 | 14 | assert.strictEqual( 15 | json.imports["lit-element"], 16 | "./node_modules/lit-element/lit-element.js" 17 | ); 18 | assert.strictEqual( 19 | json.scopes["./node_modules/lit-element/"]["lit-html/"], 20 | "./node_modules/lit-html/" 21 | ); 22 | -------------------------------------------------------------------------------- /test/resolve/object-inspect.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | }); 7 | 8 | await generator.install("object-inspect@1.12.0"); 9 | 10 | const json = generator.getMap(); 11 | 12 | assert.equal(Object.keys(json.imports).length, 1); 13 | assert.equal(Object.keys(json.scopes).length, 1); 14 | -------------------------------------------------------------------------------- /test/resolve/ts.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import { strictEqual } from "assert"; 3 | 4 | if (typeof document === "undefined") { 5 | const generator = new Generator({ 6 | mapUrl: import.meta.url, 7 | defaultProvider: "nodemodules", 8 | typeScript: true, 9 | }); 10 | 11 | await generator.link("./tspkg/main.ts"); 12 | 13 | const map = generator.getMap(); 14 | strictEqual(typeof map.imports["node:fs"], "string"); 15 | 16 | strictEqual( 17 | generator.getAnalysis(new URL("./tspkg/dep.ts", import.meta.url)).format, 18 | "typescript" 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /test/resolve/tspkg/dep.ts: -------------------------------------------------------------------------------- 1 | import 'node:fs'; 2 | export var dep: string = "dep"; 3 | -------------------------------------------------------------------------------- /test/resolve/tspkg/main.ts: -------------------------------------------------------------------------------- 1 | import type { t } from './types.ts'; 2 | import "./dep.ts"; 3 | export var p: t = 5; 4 | -------------------------------------------------------------------------------- /test/resolve/tspkg/types.ts: -------------------------------------------------------------------------------- 1 | // should not be traced! 2 | import 'jquery'; 3 | 4 | export type t = number; 5 | -------------------------------------------------------------------------------- /test/resolve/unused-cjs.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | commonJS: true, 7 | }); 8 | 9 | // Should not throw, index file doesn't use CJS: 10 | await generator.install("./unusedcjspkg"); 11 | 12 | // Should throw, uses module global: 13 | await (async () => { 14 | try { 15 | await generator.install("./unusedcjspkg/cjs.js"); 16 | assert(false); 17 | } catch {} 18 | })(); 19 | 20 | await generator.install({ target: "./cjspkg", subpath: "./browser.js" }); 21 | assert.deepStrictEqual(generator.getMap(), { 22 | imports: { 23 | "./cjspkg/browser-dep-exclude.js": 24 | "https://ga.jspm.io/npm:@jspm/core@2.1.0/nodelibs/@empty.js", 25 | "cjspkg/browser.js": "./cjspkg/browser.js", 26 | unusedcjspkg: "./unusedcjspkg/index.js", 27 | }, 28 | scopes: { 29 | "./cjspkg/": { 30 | jquery: "https://ga.jspm.io/npm:jquery@3.7.1/dist/jquery.js", 31 | }, 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /test/resolve/unusedcjspkg/cjs.js: -------------------------------------------------------------------------------- 1 | // This file actually uses CJS globals: 2 | function test() { 3 | module.exports = { a: "b" }; 4 | } 5 | 6 | test(); 7 | require("asdf"); 8 | -------------------------------------------------------------------------------- /test/resolve/unusedcjspkg/index.js: -------------------------------------------------------------------------------- 1 | // This file is in a CommonJS resolution context (there's no `"type": "module"` 2 | // field in the package.json), but doesn't actually use any CommonJS globals, 3 | // so we should be able to link this without enabling CJS explicitly: 4 | some = 0; 5 | unbound = 1; 6 | globals = 2; 7 | var require = NOT_REQUIRE; 8 | -------------------------------------------------------------------------------- /test/resolve/unusedcjspkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "index.js" 3 | } 4 | -------------------------------------------------------------------------------- /test/resolve/wildcard.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | defaultProvider: "nodemodules", 7 | }); 8 | 9 | await generator.install({ 10 | target: new URL("./wildcard", import.meta.url).href, 11 | subpath: "./some/module", 12 | }); 13 | 14 | const json = generator.getMap(); 15 | 16 | assert.deepStrictEqual(json, { 17 | imports: { 18 | "wildcard/some/module": "./wildcard/a-module.js", 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /test/resolve/wildcard/a-module.js: -------------------------------------------------------------------------------- 1 | export default "mod"; 2 | -------------------------------------------------------------------------------- /test/resolve/wildcard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "exports": { 3 | "./some/*": "./a-*.js" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/runMochaTests.js: -------------------------------------------------------------------------------- 1 | import "@jspm/generator"; 2 | 3 | // keepalive 4 | setInterval(() => { 5 | fetch("/tests/ping"); 6 | }, 3000); 7 | 8 | (async () => { 9 | let tests = await (await fetch("/tests/list")).json(); 10 | 11 | mocha.setup("tdd"); 12 | mocha.set; 13 | mocha.allowUncaught(); 14 | self.assert = function (val) { 15 | equal(!!val, true); 16 | }; 17 | assert.equal = equal; 18 | assert.ok = assert; 19 | function equal(a, b) { 20 | if (a !== b) throw new Error('Expected "' + a + '" to be "' + b + '"'); 21 | } 22 | self.fail = function (msg) { 23 | throw new Error(msg); 24 | }; 25 | 26 | // Keep track of reasons for failure in a global: 27 | self.__TEST_FAILURES__ = []; 28 | 29 | suite("Browser Tests", async function () { 30 | this.timeout(120_000); 31 | for (const name of tests) { 32 | if (name.startsWith("deno") || name.startsWith("node")) continue; 33 | test(name, async function () { 34 | try { 35 | await import("./" + name + ".js"); 36 | } catch (err) { 37 | __TEST_FAILURES__.push([name, err.stack]); 38 | throw err; 39 | } 40 | }); 41 | } 42 | }); 43 | 44 | mocha.run(function (failures) { 45 | if (failures) { 46 | fetch("/error?" + failures, { 47 | method: "POST", 48 | body: JSON.stringify(__TEST_FAILURES__), 49 | }); 50 | } else { 51 | fetch("/done"); 52 | } 53 | }); 54 | })(); 55 | -------------------------------------------------------------------------------- /test/utils/fetch.test.js: -------------------------------------------------------------------------------- 1 | import { fetch } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const pcfg = await ( 5 | await fetch("https://ga.jspm.io/npm:jquery@3.6.0/package.json") 6 | ).json(); 7 | assert.strictEqual(pcfg.name, "jquery"); 8 | -------------------------------------------------------------------------------- /test/utils/lookup.test.js: -------------------------------------------------------------------------------- 1 | import { lookup } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const { install, resolved } = await lookup("jquery@3.5"); 5 | 6 | assert.strictEqual(install.target.registry, "npm"); 7 | assert.strictEqual(install.target.name, "jquery"); 8 | assert.strictEqual(install.target.range, "3.5"); 9 | assert.strictEqual(install.subpath, "."); 10 | assert.strictEqual(install.alias, "jquery"); 11 | assert.strictEqual(resolved.registry, "npm"); 12 | assert.strictEqual(resolved.name, "jquery"); 13 | assert.strictEqual(resolved.version, "3.5.1"); 14 | -------------------------------------------------------------------------------- /test/utils/pcfg.test.js: -------------------------------------------------------------------------------- 1 | import { getPackageConfig } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const pcfg = await getPackageConfig("https://ga.jspm.io/npm:jquery@3.6.0/"); 5 | assert.strictEqual(pcfg.name, "jquery"); 6 | -------------------------------------------------------------------------------- /test/utils/resolve.test.js: -------------------------------------------------------------------------------- 1 | import { Generator } from "@jspm/generator"; 2 | import assert from "assert"; 3 | 4 | const generator = new Generator({ 5 | mapUrl: import.meta.url, 6 | env: ["source"], 7 | }); 8 | 9 | await generator.install("@jspm/generator"); 10 | 11 | const generatorUrl = new URL("../../lib/generator.js", import.meta.url).href; 12 | 13 | assert.strictEqual(generator.resolve("@jspm/generator"), generatorUrl); 14 | assert.strictEqual( 15 | generator.resolve("#fetch", generatorUrl), 16 | new URL("./common/fetch-native.js", generatorUrl).href 17 | ); 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "typeRoots": ["node_modules/@types"], 4 | "allowSyntheticDefaultImports": true, 5 | "moduleResolution": "node", 6 | "module": "esnext", 7 | "target": "es2022", 8 | "sourceMap": true, 9 | "outDir": "lib", 10 | "declaration": true, 11 | "declarationDir": "lib", 12 | "removeComments": false, 13 | "strictBindCallApply": true 14 | }, 15 | "include": [ 16 | "src/**/*.ts", 17 | "src/script-lexer.js", 18 | "src/api.js" 19 | ], 20 | "exclude": [ 21 | "src/cli.ts", 22 | "src/**/*.test.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["src/generator.ts"], 3 | "readme": "./docs.md", 4 | "externalDocumentation": { 5 | "@jspm/import-map": { 6 | "dtsPath": "node_modules/@jspm/import-map/src/map.ts", 7 | "externalBaseURL": "/docs/import-map/stable" 8 | } 9 | } 10 | } 11 | --------------------------------------------------------------------------------