diff --git a/.dependency-cruiser.mjs b/.dependency-cruiser.mjs new file mode 100644 index 000000000000..464f3307a877 --- /dev/null +++ b/.dependency-cruiser.mjs @@ -0,0 +1,450 @@ +/** @type {import('dependency-cruiser').IConfiguration} */ +export default { + forbidden: [ + /* rules from the 'recommended' preset: */ + { + name: 'no-circular', + severity: 'warn', + comment: + 'This dependency is part of a circular relationship. You might want to revise ' + + 'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ', + from: {}, + to: { + circular: true + } + }, + { + name: 'no-orphans', + comment: + "This is an orphan module - it's likely not used (anymore?). Either use it or " + + "remove it. If it's logical this module is an orphan (i.e. it's a config file), " + + "add an exception for it in your dependency-cruiser configuration. By default " + + "this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " + + "files (.d.ts), tsconfig.json and some of the babel and webpack configs.", + severity: 'warn', + from: { + orphan: true, + pathNot: [ + '(^|/)\\.[^/]+\\.(js|cjs|mjs|ts|json)$', // dot files + '\\.d\\.ts$', // TypeScript declaration files + '(^|/)tsconfig\\.json$', // TypeScript config + '(^|/)(babel|webpack)\\.config\\.(js|cjs|mjs|ts|json)$' // other configs + ] + }, + to: {}, + }, + { + name: 'no-deprecated-core', + comment: + 'A module depends on a node core module that has been deprecated. Find an alternative - these are ' + + "bound to exist - node doesn't deprecate lightly.", + severity: 'warn', + from: {}, + to: { + dependencyTypes: [ + 'core' + ], + path: [ + '^(v8\/tools\/codemap)$', + '^(v8\/tools\/consarray)$', + '^(v8\/tools\/csvparser)$', + '^(v8\/tools\/logreader)$', + '^(v8\/tools\/profile_view)$', + '^(v8\/tools\/profile)$', + '^(v8\/tools\/SourceMap)$', + '^(v8\/tools\/splaytree)$', + '^(v8\/tools\/tickprocessor-driver)$', + '^(v8\/tools\/tickprocessor)$', + '^(node-inspect\/lib\/_inspect)$', + '^(node-inspect\/lib\/internal\/inspect_client)$', + '^(node-inspect\/lib\/internal\/inspect_repl)$', + '^(async_hooks)$', + '^(punycode)$', + '^(domain)$', + '^(constants)$', + '^(sys)$', + '^(_linklist)$', + '^(_stream_wrap)$' + ], + } + }, + { + name: 'not-to-deprecated', + comment: + 'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' + + 'version of that module, or find an alternative. Deprecated modules are a security risk.', + severity: 'warn', + from: {}, + to: { + dependencyTypes: [ + 'deprecated' + ] + } + }, + { + name: 'no-non-package-json', + severity: 'error', + comment: + "This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " + + "That's problematic as the package either (1) won't be available on live (2 - worse) will be " + + "available on live with an non-guaranteed version. Fix it by adding the package to the dependencies " + + "in your package.json.", + from: {}, + to: { + dependencyTypes: [ + 'npm-no-pkg', + 'npm-unknown' + ] + } + }, + { + name: 'not-to-unresolvable', + comment: + "This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " + + 'module: add it to your package.json. In all other cases you likely already know what to do.', + severity: 'error', + from: {}, + to: { + couldNotResolve: true + } + }, + { + name: 'no-duplicate-dep-types', + comment: + "Likely this module depends on an external ('npm') package that occurs more than once " + + "in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " + + "maintenance problems later on.", + severity: 'warn', + from: {}, + to: { + moreThanOneDependencyType: true, + // as it's pretty common to have a type import be a type only import + // _and_ (e.g.) a devDependency - don't consider type-only dependency + // types for this rule + dependencyTypesNot: ["type-only"] + } + }, + + /* rules you might want to tweak for your specific situation: */ + { + name: 'not-to-spec', + comment: + 'This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. ' + + "If there's something in a spec that's of use to other modules, it doesn't have that single " + + 'responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.', + severity: 'error', + from: {}, + to: { + path: '\\.(spec|test)\\.(js|mjs|cjs|ts|ls|coffee|litcoffee|coffee\\.md)$' + } + }, + { + name: 'not-to-dev-dep', + severity: 'error', + comment: + "This module depends on an npm package from the 'devDependencies' section of your " + + 'package.json. It looks like something that ships to production, though. To prevent problems ' + + "with npm packages that aren't there on production declare it (only!) in the 'dependencies'" + + 'section of your package.json. If this module is development only - add it to the ' + + 'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration', + from: { + path: '^(src)', + pathNot: '\\.(spec|test)\\.(js|mjs|cjs|ts|ls|coffee|litcoffee|coffee\\.md)$' + }, + to: { + dependencyTypes: [ + 'npm-dev' + ] + } + }, + { + name: 'optional-deps-used', + severity: 'info', + comment: + "This module depends on an npm package that is declared as an optional dependency " + + "in your package.json. As this makes sense in limited situations only, it's flagged here. " + + "If you're using an optional dependency here by design - add an exception to your" + + "dependency-cruiser configuration.", + from: {}, + to: { + dependencyTypes: [ + 'npm-optional' + ] + } + }, + { + name: 'peer-deps-used', + comment: + "This module depends on an npm package that is declared as a peer dependency " + + "in your package.json. This makes sense if your package is e.g. a plugin, but in " + + "other cases - maybe not so much. If the use of a peer dependency is intentional " + + "add an exception to your dependency-cruiser configuration.", + severity: 'warn', + from: {}, + to: { + dependencyTypes: [ + 'npm-peer' + ] + } + } + ], + options: { + + /* conditions specifying which files not to follow further when encountered: + - path: a regular expression to match + - dependencyTypes: see https://github.com/sverweij/dependency-cruiser/blob/main/doc/rules-reference.md#dependencytypes-and-dependencytypesnot + for a complete list + */ + doNotFollow: { + path: 'node_modules' + }, + + /* conditions specifying which dependencies to exclude + - path: a regular expression to match + - dynamic: a boolean indicating whether to ignore dynamic (true) or static (false) dependencies. + leave out if you want to exclude neither (recommended!) + */ + // exclude : { + // path: '', + // dynamic: true + // }, + + /* pattern specifying which files to include (regular expression) + dependency-cruiser will skip everything not matching this pattern + */ + // includeOnly : '', + + /* dependency-cruiser will include modules matching against the focus + regular expression in its output, as well as their neighbours (direct + dependencies and dependents) + */ + // focus : '', + + /* list of module systems to cruise */ + // moduleSystems: ['amd', 'cjs', 'es6', 'tsd'], + + /* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/develop/' + to open it on your online repo or `vscode://file/${process.cwd()}/` to + open it in visual studio code), + */ + // prefix: '', + + /* false (the default): ignore dependencies that only exist before typescript-to-javascript compilation + true: also detect dependencies that only exist before typescript-to-javascript compilation + "specify": for each dependency identify whether it only exists before compilation or also after + */ + tsPreCompilationDeps: true, + + /* + list of extensions to scan that aren't javascript or compile-to-javascript. + Empty by default. Only put extensions in here that you want to take into + account that are _not_ parsable. + */ + // extraExtensionsToScan: [".json", ".jpg", ".png", ".svg", ".webp"], + + /* if true combines the package.jsons found from the module up to the base + folder the cruise is initiated from. Useful for how (some) mono-repos + manage dependencies & dependency definitions. + */ + // combinedDependencies: false, + + /* if true leave symlinks untouched, otherwise use the realpath */ + // preserveSymlinks: false, + + /* TypeScript project file ('tsconfig.json') to use for + (1) compilation and + (2) resolution (e.g. with the paths property) + + The (optional) fileName attribute specifies which file to take (relative to + dependency-cruiser's current working directory). When not provided + defaults to './tsconfig.json'. + */ + tsConfig: { + fileName: 'tsconfig.json' + }, + + /* Webpack configuration to use to get resolve options from. + + The (optional) fileName attribute specifies which file to take (relative + to dependency-cruiser's current working directory. When not provided defaults + to './webpack.conf.js'. + + The (optional) `env` and `arguments` attributes contain the parameters to be passed if + your webpack config is a function and takes them (see webpack documentation + for details) + */ + // webpackConfig: { + // fileName: './webpack.config.js', + // env: {}, + // arguments: {}, + // }, + + /* Babel config ('.babelrc', '.babelrc.json', '.babelrc.json5', ...) to use + for compilation (and whatever other naughty things babel plugins do to + source code). This feature is well tested and usable, but might change + behavior a bit over time (e.g. more precise results for used module + systems) without dependency-cruiser getting a major version bump. + */ + // babelConfig: { + // fileName: './.babelrc' + // }, + + /* List of strings you have in use in addition to cjs/ es6 requires + & imports to declare module dependencies. Use this e.g. if you've + re-declared require, use a require-wrapper or use window.require as + a hack. + */ + // exoticRequireStrings: [], + /* options to pass on to enhanced-resolve, the package dependency-cruiser + uses to resolve module references to disk. You can set most of these + options in a webpack.conf.js - this section is here for those + projects that don't have a separate webpack config file. + + Note: settings in webpack.conf.js override the ones specified here. + */ + enhancedResolveOptions: { + /* List of strings to consider as 'exports' fields in package.json. Use + ['exports'] when you use packages that use such a field and your environment + supports it (e.g. node ^12.19 || >=14.7 or recent versions of webpack). + + If you have an `exportsFields` attribute in your webpack config, that one + will have precedence over the one specified here. + */ + exportsFields: ["exports"], + /* List of conditions to check for in the exports field. e.g. use ['imports'] + if you're only interested in exposed es6 modules, ['require'] for commonjs, + or all conditions at once `(['import', 'require', 'node', 'default']`) + if anything goes for you. Only works when the 'exportsFields' array is + non-empty. + + If you have a 'conditionNames' attribute in your webpack config, that one will + have precedence over the one specified here. + */ + conditionNames: ["import", "require", "node", "default"], + /* + The extensions, by default are the same as the ones dependency-cruiser + can access (run `npx depcruise --info` to see which ones that are in + _your_ environment. If that list is larger than what you need (e.g. + it contains .js, .jsx, .ts, .tsx, .cts, .mts - but you don't use + TypeScript you can pass just the extensions you actually use (e.g. + [".js", ".jsx"]). This can speed up the most expensive step in + dependency cruising (module resolution) quite a bit. + */ + // extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"], + /* + If your TypeScript project makes use of types specified in 'types' + fields in package.jsons of external dependencies, specify "types" + in addition to "main" in here, so enhanced-resolve (the resolver + dependency-cruiser uses) knows to also look there. You can also do + this if you're not sure, but still use TypeScript. In a future version + of dependency-cruiser this will likely become the default. + */ + mainFields: ["main", "types"], + }, + reporterOptions: { + dot: { + showMetrics: true, + /* pattern of modules that can be consolidated in the detailed + graphical dependency graph. The default pattern in this configuration + collapses everything in node_modules to one folder deep so you see + the external modules, but not the innards your app depends upon. + */ + collapsePattern: 'node_modules/(@[^/]+/[^/]+|[^/]+)', + + /* Options to tweak the appearance of your graph.See + https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions + for details and some examples. If you don't specify a theme + don't worry - dependency-cruiser will fall back to the default one. + */ + // theme: { + // graph: { + // /* use splines: "ortho" for straight lines. Be aware though + // graphviz might take a long time calculating ortho(gonal) + // routings. + // */ + // splines: "true" + // }, + // modules: [ + // { + // criteria: { matchesFocus: true }, + // attributes: { + // fillcolor: "lime", + // penwidth: 2, + // }, + // }, + // { + // criteria: { matchesFocus: false }, + // attributes: { + // fillcolor: "lightgrey", + // }, + // }, + // { + // criteria: { matchesReaches: true }, + // attributes: { + // fillcolor: "lime", + // penwidth: 2, + // }, + // }, + // { + // criteria: { matchesReaches: false }, + // attributes: { + // fillcolor: "lightgrey", + // }, + // }, + // { + // criteria: { source: "^src/model" }, + // attributes: { fillcolor: "#ccccff" } + // }, + // { + // criteria: { source: "^src/view" }, + // attributes: { fillcolor: "#ccffcc" } + // }, + // ], + // dependencies: [ + // { + // criteria: { "rules[0].severity": "error" }, + // attributes: { fontcolor: "red", color: "red" } + // }, + // { + // criteria: { "rules[0].severity": "warn" }, + // attributes: { fontcolor: "orange", color: "orange" } + // }, + // { + // criteria: { "rules[0].severity": "info" }, + // attributes: { fontcolor: "blue", color: "blue" } + // }, + // { + // criteria: { resolved: "^src/model" }, + // attributes: { color: "#0000ff77" } + // }, + // { + // criteria: { resolved: "^src/view" }, + // attributes: { color: "#00770077" } + // } + // ] + // } + }, + archi: { + /* pattern of modules that can be consolidated in the high level + graphical dependency graph. If you use the high level graphical + dependency graph reporter (`archi`) you probably want to tweak + this collapsePattern to your situation. + */ + collapsePattern: '^(packages|src|lib|app|bin|test(s?)|spec(s?))/[^/]+|node_modules/(@[^/]+/[^/]+|[^/]+)', + + /* Options to tweak the appearance of your graph.See + https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions + for details and some examples. If you don't specify a theme + for 'archi' dependency-cruiser will use the one specified in the + dot section (see above), if any, and otherwise use the default one. + */ + // theme: { + // }, + }, + "text": { + "highlightFocused": true + }, + } + } +}; +// generated: dependency-cruiser@13.0.3 on 2023-06-08T22:17:09.126Z diff --git a/.editorconfig b/.editorconfig index 871c2f2fb484..d60b940abe37 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,7 +7,7 @@ root = true [*] end_of_line = lf insert_final_newline = true -charset = utf8 +charset = utf-8 [*.{js,jsx,ts,tsx,graphql,sql,md,html,mjml,json,jsonc,json5,yml,yaml,template,sh,Dockerfile}] indent_style = space diff --git a/.eslintrc.js b/.eslintrc.js index d79188d2c17d..af531e4f0404 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,11 +1,11 @@ const path = require('path') -const findUp = require('findup-sync') +const { findUp } = require('@redwoodjs/project-config') // Framework Babel config is monorepo root ./babel.config.js -// `yarn lint` runs for each workspace, which needs findup for path to root +// `yarn lint` runs for each workspace, which needs findUp for path to root const findBabelConfig = (cwd = process.cwd()) => { - const configPath = findUp('babel.config.js', { cwd }) + const configPath = findUp('babel.config.js', cwd) if (!configPath) { throw new Error(`Eslint-parser could not find a "babel.config.js" file`) } @@ -23,14 +23,17 @@ module.exports = { ignorePatterns: [ 'dist', 'fixtures', - 'packages/internal/src/build/babelPlugins/__tests__/__fixtures__/**/*', + 'packages/babel-config/src/plugins/__tests__/__fixtures__/**/*', + 'packages/babel-config/src/__tests__/__fixtures__/**/*', 'packages/core/**/__fixtures__/**/*', 'packages/codemods/**/__testfixtures__/**/*', 'packages/core/config/storybook/**/*', + 'packages/studio/dist-*/**/*', ], rules: { '@typescript-eslint/no-explicit-any': 'off', curly: 'error', + '@typescript-eslint/consistent-type-imports': 'error', }, env: { // We use the most modern environment available. Then we rely on Babel to @@ -170,5 +173,34 @@ module.exports = { ], }, }, + // Allow computed member access on process.env in NodeJS contexts and tests + { + files: [ + 'packages/core/config/webpack.common.js', + 'packages/testing/**', + 'packages/vite/src/index.ts', + ], + rules: { + '@redwoodjs/process-env-computed': 'off', + }, + }, + { + files: ['packages/project-config/**'], + excludedFiles: [ + '**/__tests__/**', + '**/*.test.ts?(x)', + '**/*.spec.ts?(x)', + ], + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: false, + optionalDependencies: false, + peerDependencies: true, + }, + ], + }, + }, ], } diff --git a/.github/actions/actionsLib.mjs b/.github/actions/actionsLib.mjs new file mode 100644 index 000000000000..8aaf223dcfff --- /dev/null +++ b/.github/actions/actionsLib.mjs @@ -0,0 +1,163 @@ +/* eslint-env node */ +// @ts-check + +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +import { getExecOutput } from '@actions/exec' +import { hashFiles } from '@actions/glob' + +/** + * @typedef {import('@actions/exec').ExecOptions} ExecOptions + */ + +export const REDWOOD_FRAMEWORK_PATH = fileURLToPath(new URL('../../', import.meta.url)) + +/** + * @param {string} command + * @param {ExecOptions} options + */ +function execWithEnv(command, { env = {}, ...rest } = {}) { + return getExecOutput( + command, + undefined, + { + env: { + ...process.env, + ...env + }, + ...rest + } + ) +} + +/** + * @param {string} cwd + */ +export function createExecWithEnvInCwd(cwd) { + /** + * @param {string} command + * @param {Omit} options + */ + return function (command, options = {}) { + return execWithEnv(command, { cwd, ...options }) + } +} + +export const execInFramework = createExecWithEnvInCwd(REDWOOD_FRAMEWORK_PATH) + +/** + * @param {string} redwoodProjectCwd + */ +export function projectDeps(redwoodProjectCwd) { + return execInFramework('yarn project:deps', { env: { RWJS_CWD: redwoodProjectCwd } }) +} + +/** + * @param {string} redwoodProjectCwd + */ +export function projectCopy(redwoodProjectCwd) { + return execInFramework('yarn project:copy', { env: { RWJS_CWD: redwoodProjectCwd } }) +} + +/** + * @param {{ baseKeyPrefix: string, distKeyPrefix: string, canary: boolean }} options + */ +export async function createCacheKeys({ baseKeyPrefix, distKeyPrefix, canary }) { + const baseKey = [ + baseKeyPrefix, + process.env.RUNNER_OS, + process.env.GITHUB_REF.replaceAll('/', '-'), + await hashFiles(path.join('__fixtures__', 'test-project')) + ].join('-') + + const dependenciesKey = [ + baseKey, + 'dependencies', + await hashFiles(['yarn.lock', '.yarnrc.yml'].join('\n')), + ].join('-') + (canary ? '-canary' : '') + + const distKey = [ + dependenciesKey, + distKeyPrefix, + 'dist', + await hashFiles([ + 'package.json', + 'babel.config.js', + 'tsconfig.json', + 'tsconfig.compilerOption.json', + 'nx.json', + 'lerna.json', + 'packages', + ].join('\n')) + ].join('-') + (canary ? '-canary' : '') + + return { + baseKey, + dependenciesKey, + distKey + } +} + +/** + * @callback ExecInProject + * @param {string} commandLine command to execute (can include additional args). Must be correctly escaped. + * @param {Omit=} options exec options. See ExecOptions + * @returns {Promise} exit code + */ + +/** + * @param {string} testProjectPath + * @param {string} fixtureName + * @param {Object} core + * @param {(key: string, value: string) => void} core.setOutput + * @param {ExecInProject} execInProject + * @returns {Promise} + */ +export async function setUpRscTestProject( + testProjectPath, + fixtureName, + core, + execInProject +) { + core.setOutput('test-project-path', testProjectPath) + + console.log('rwPath', REDWOOD_FRAMEWORK_PATH) + console.log('testProjectPath', testProjectPath) + + const fixturePath = path.join( + REDWOOD_FRAMEWORK_PATH, + '__fixtures__', + fixtureName + ) + const rwBinPath = path.join( + REDWOOD_FRAMEWORK_PATH, + 'packages/cli/dist/index.js' + ) + const rwfwBinPath = path.join( + REDWOOD_FRAMEWORK_PATH, + 'packages/cli/dist/rwfw.js' + ) + + console.log(`Creating project at ${testProjectPath}`) + console.log() + fs.cpSync(fixturePath, testProjectPath, { recursive: true }) + + console.log(`Adding framework dependencies to ${testProjectPath}`) + await projectDeps(testProjectPath) + console.log() + + console.log(`Installing node_modules in ${testProjectPath}`) + await execInProject('yarn install') + + console.log(`Copying over framework files to ${testProjectPath}`) + await execInProject(`node ${rwfwBinPath} project:copy`, { + env: { RWFW_PATH: REDWOOD_FRAMEWORK_PATH }, + }) + console.log() + + console.log(`Building project in ${testProjectPath}`) + await execInProject(`node ${rwBinPath} build -v`) + console.log() +} diff --git a/.github/actions/check_create_redwood_app/action.yml b/.github/actions/check_create_redwood_app/action.yml new file mode 100644 index 000000000000..c3d123004953 --- /dev/null +++ b/.github/actions/check_create_redwood_app/action.yml @@ -0,0 +1,8 @@ +name: Check create redwood app +description: Determines if the create redwood app JS template should be rebuilt +runs: + using: node20 + main: check_create_redwood_app.mjs +inputs: + labels: + required: true diff --git a/.github/actions/check_create_redwood_app/check_create_redwood_app.mjs b/.github/actions/check_create_redwood_app/check_create_redwood_app.mjs new file mode 100644 index 000000000000..130121c41a2a --- /dev/null +++ b/.github/actions/check_create_redwood_app/check_create_redwood_app.mjs @@ -0,0 +1,55 @@ +/* eslint-env es6, node */ +import { getInput } from '@actions/core' + +// If the PR has the "crwa-ok" label, just pass. +const { labels } = JSON.parse(getInput('labels')) +const hasCRWA_OkLabel = labels.some((label) => label.name === 'crwa-ok') + +if (hasCRWA_OkLabel) { + console.log('Skipping check because of the "crwa-ok" label') +} else { + // Check if the PR rebuilds the fixture. If it does, that's enough. + const { exec, getExecOutput } = await import('@actions/exec') + await exec('git fetch origin main') + const { stdout } = await getExecOutput('git diff origin/main --name-only') + const changedFiles = stdout.toString().trim().split('\n').filter(Boolean) + const didRebuildJS_Template = changedFiles.some((file) => + file.startsWith('packages/create-redwood-app/templates/js') + ) + + if (didRebuildJS_Template) { + console.log( + [ + // Empty space here (and in subsequent console logs) + // because git fetch origin main prints to stdout. + '', + "The create redwood app JS template's been rebuilt", + ].join('\n') + ) + } else { + // If it doesn't, does it need to be rebuilt? If not, no problem. Otherwise, throw. + const shouldRebuildJS_Template = changedFiles.some( + (file) => + file.startsWith('packages/create-redwood-app/templates/ts') + ) + + if (!shouldRebuildJS_Template) { + console.log(['', "The create redwood app JS template doesn't need to be rebuilt"].join('\n')) + } else { + console.log( + [ + '', + 'This PR changes the create-redwood-app TS template.', + 'That usually means the JS template needs to be rebuilt.', + `If you know that it doesn't, add the "crwa-ok" label. Otherwise, rebuild the JS template and commit the changes:`, + '', + ' cd packages/create-redwood-app', + ' yarn ts-to-js', + '', + ].join('\n') + ) + + process.exitCode = 1 + } + } +} diff --git a/.github/actions/check_create_redwood_app/package.json b/.github/actions/check_create_redwood_app/package.json new file mode 100644 index 000000000000..03bc407905ca --- /dev/null +++ b/.github/actions/check_create_redwood_app/package.json @@ -0,0 +1,9 @@ +{ + "name": "check_test_project_fixture", + "private": true, + "dependencies": { + "@actions/core": "1.10.1", + "@actions/exec": "1.1.1" + }, + "packageManager": "yarn@4.0.2" +} diff --git a/.github/actions/check_create_redwood_app/yarn.lock b/.github/actions/check_create_redwood_app/yarn.lock new file mode 100644 index 000000000000..67423743c42c --- /dev/null +++ b/.github/actions/check_create_redwood_app/yarn.lock @@ -0,0 +1,66 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@actions/core@npm:1.10.1": + version: 1.10.1 + resolution: "@actions/core@npm:1.10.1" + dependencies: + "@actions/http-client": "npm:^2.0.1" + uuid: "npm:^8.3.2" + checksum: 8c0/7a61446697a23dcad3545cf0634dedbdedf20ae9a0ee6ee977554589a15deb4a93593ee48a41258933d58ce0778f446b0d2c162b60750956fb75e0b9560fb832 + languageName: node + linkType: hard + +"@actions/exec@npm:1.1.1": + version: 1.1.1 + resolution: "@actions/exec@npm:1.1.1" + dependencies: + "@actions/io": "npm:^1.0.1" + checksum: 8c0/4a09f6bdbe50ce68b5cf8a7254d176230d6a74bccf6ecc3857feee209a8c950ba9adec87cc5ecceb04110182d1c17117234e45557d72fde6229b7fd3a395322a + languageName: node + linkType: hard + +"@actions/http-client@npm:^2.0.1": + version: 2.0.1 + resolution: "@actions/http-client@npm:2.0.1" + dependencies: + tunnel: "npm:^0.0.6" + checksum: 8c0/b58987ba2f53d7988f612ede7ff834573a3360c21f8fdea9fea92f26ada0fd0efafb22aa7d83f49c18965a5b765775d5253e2edb8d9476d924c4b304ef726b67 + languageName: node + linkType: hard + +"@actions/io@npm:^1.0.1": + version: 1.1.2 + resolution: "@actions/io@npm:1.1.2" + checksum: 8c0/61c871bbee1cf58f57917d9bb2cf6bb7ea4dc40de3f65c7fb4ec619ceff57fc98f56be9cca2d476b09e7a96e1cba0d88cd125c4f690d384b9483935186f256c1 + languageName: node + linkType: hard + +"check_test_project_fixture@workspace:.": + version: 0.0.0-use.local + resolution: "check_test_project_fixture@workspace:." + dependencies: + "@actions/core": "npm:1.10.1" + "@actions/exec": "npm:1.1.1" + languageName: unknown + linkType: soft + +"tunnel@npm:^0.0.6": + version: 0.0.6 + resolution: "tunnel@npm:0.0.6" + checksum: 8c0/e27e7e896f2426c1c747325b5f54efebc1a004647d853fad892b46d64e37591ccd0b97439470795e5262b5c0748d22beb4489a04a0a448029636670bfd801b75 + languageName: node + linkType: hard + +"uuid@npm:^8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 8c0/bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54 + languageName: node + linkType: hard diff --git a/.github/actions/check_test_project_fixture/action.yml b/.github/actions/check_test_project_fixture/action.yml index fcc930d7b9fc..43374e7ea0e7 100644 --- a/.github/actions/check_test_project_fixture/action.yml +++ b/.github/actions/check_test_project_fixture/action.yml @@ -1,7 +1,7 @@ name: Check test project fixture description: Determines if the test project fixture should be rebuilt runs: - using: node16 + using: node20 main: check_test_project_fixture.mjs inputs: labels: diff --git a/.github/actions/check_test_project_fixture/check_test_project_fixture.mjs b/.github/actions/check_test_project_fixture/check_test_project_fixture.mjs index ea8ebb2fadcb..970763d3f91f 100644 --- a/.github/actions/check_test_project_fixture/check_test_project_fixture.mjs +++ b/.github/actions/check_test_project_fixture/check_test_project_fixture.mjs @@ -47,7 +47,7 @@ if (hasFixtureOkLabel) { 'That usually means the test-project fixture needs to be rebuilt.', `If you know that it doesn't, add the "fixture-ok" label. Otherwise, rebuild the fixture and commit the changes:`, '', - ' yarn build:test-project --rebuild-fixture', + ' yarn rebuild-test-project-fixture', '', ].join('\n') ) diff --git a/.github/actions/check_test_project_fixture/package.json b/.github/actions/check_test_project_fixture/package.json index 43577bcc0575..03bc407905ca 100644 --- a/.github/actions/check_test_project_fixture/package.json +++ b/.github/actions/check_test_project_fixture/package.json @@ -2,8 +2,8 @@ "name": "check_test_project_fixture", "private": true, "dependencies": { - "@actions/core": "1.10.0", + "@actions/core": "1.10.1", "@actions/exec": "1.1.1" }, - "packageManager": "yarn@3.5.0" + "packageManager": "yarn@4.0.2" } diff --git a/.github/actions/check_test_project_fixture/yarn.lock b/.github/actions/check_test_project_fixture/yarn.lock index 56729d47cb3f..67423743c42c 100644 --- a/.github/actions/check_test_project_fixture/yarn.lock +++ b/.github/actions/check_test_project_fixture/yarn.lock @@ -2,16 +2,16 @@ # Manual changes might be lost - proceed with caution! __metadata: - version: 6 - cacheKey: 8c0 + version: 8 + cacheKey: 10c0 -"@actions/core@npm:1.10.0": - version: 1.10.0 - resolution: "@actions/core@npm:1.10.0" +"@actions/core@npm:1.10.1": + version: 1.10.1 + resolution: "@actions/core@npm:1.10.1" dependencies: - "@actions/http-client": ^2.0.1 - uuid: ^8.3.2 - checksum: 9214d1e0cf5cf2a5d48b8f3b12488c6be9f6722ea60f2397409226e8410b5a3e12e558d9b66c93469d180399865ec20180119408a1770f026bd9ecac6965fcda + "@actions/http-client": "npm:^2.0.1" + uuid: "npm:^8.3.2" + checksum: 8c0/7a61446697a23dcad3545cf0634dedbdedf20ae9a0ee6ee977554589a15deb4a93593ee48a41258933d58ce0778f446b0d2c162b60750956fb75e0b9560fb832 languageName: node linkType: hard @@ -19,8 +19,8 @@ __metadata: version: 1.1.1 resolution: "@actions/exec@npm:1.1.1" dependencies: - "@actions/io": ^1.0.1 - checksum: 4a09f6bdbe50ce68b5cf8a7254d176230d6a74bccf6ecc3857feee209a8c950ba9adec87cc5ecceb04110182d1c17117234e45557d72fde6229b7fd3a395322a + "@actions/io": "npm:^1.0.1" + checksum: 8c0/4a09f6bdbe50ce68b5cf8a7254d176230d6a74bccf6ecc3857feee209a8c950ba9adec87cc5ecceb04110182d1c17117234e45557d72fde6229b7fd3a395322a languageName: node linkType: hard @@ -28,15 +28,15 @@ __metadata: version: 2.0.1 resolution: "@actions/http-client@npm:2.0.1" dependencies: - tunnel: ^0.0.6 - checksum: b58987ba2f53d7988f612ede7ff834573a3360c21f8fdea9fea92f26ada0fd0efafb22aa7d83f49c18965a5b765775d5253e2edb8d9476d924c4b304ef726b67 + tunnel: "npm:^0.0.6" + checksum: 8c0/b58987ba2f53d7988f612ede7ff834573a3360c21f8fdea9fea92f26ada0fd0efafb22aa7d83f49c18965a5b765775d5253e2edb8d9476d924c4b304ef726b67 languageName: node linkType: hard "@actions/io@npm:^1.0.1": version: 1.1.2 resolution: "@actions/io@npm:1.1.2" - checksum: 61c871bbee1cf58f57917d9bb2cf6bb7ea4dc40de3f65c7fb4ec619ceff57fc98f56be9cca2d476b09e7a96e1cba0d88cd125c4f690d384b9483935186f256c1 + checksum: 8c0/61c871bbee1cf58f57917d9bb2cf6bb7ea4dc40de3f65c7fb4ec619ceff57fc98f56be9cca2d476b09e7a96e1cba0d88cd125c4f690d384b9483935186f256c1 languageName: node linkType: hard @@ -44,15 +44,15 @@ __metadata: version: 0.0.0-use.local resolution: "check_test_project_fixture@workspace:." dependencies: - "@actions/core": 1.10.0 - "@actions/exec": 1.1.1 + "@actions/core": "npm:1.10.1" + "@actions/exec": "npm:1.1.1" languageName: unknown linkType: soft "tunnel@npm:^0.0.6": version: 0.0.6 resolution: "tunnel@npm:0.0.6" - checksum: e27e7e896f2426c1c747325b5f54efebc1a004647d853fad892b46d64e37591ccd0b97439470795e5262b5c0748d22beb4489a04a0a448029636670bfd801b75 + checksum: 8c0/e27e7e896f2426c1c747325b5f54efebc1a004647d853fad892b46d64e37591ccd0b97439470795e5262b5c0748d22beb4489a04a0a448029636670bfd801b75 languageName: node linkType: hard @@ -61,6 +61,6 @@ __metadata: resolution: "uuid@npm:8.3.2" bin: uuid: dist/bin/uuid - checksum: bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54 + checksum: 8c0/bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54 languageName: node linkType: hard diff --git a/.github/actions/detect-changes/action.yml b/.github/actions/detect-changes/action.yml new file mode 100644 index 000000000000..dc581c9253c4 --- /dev/null +++ b/.github/actions/detect-changes/action.yml @@ -0,0 +1,14 @@ +name: Detect Changes +description: Determines what areas of the framework have been changed + +outputs: + docs: + description: If *only* docs have changed + rsc: + description: If RSC-related changes have been made + ssr: + description: If SSR-related changes have been made + +runs: + using: node20 + main: detectChanges.mjs diff --git a/.github/actions/detect-changes/cases/code_changes.mjs b/.github/actions/detect-changes/cases/code_changes.mjs new file mode 100644 index 000000000000..fd0306ebc1fb --- /dev/null +++ b/.github/actions/detect-changes/cases/code_changes.mjs @@ -0,0 +1,40 @@ +/** + * Detects if the given file path points to a code file (as apposed to a docs + * file) + */ +function isCodeFile(filePath) { + if (filePath.startsWith('docs')) { + return false + } + + if ( + [ + 'CHANGELOG.md', + 'CODE_OF_CONDUCT.md', + 'CONTRIBUTING.md', + 'CONTRIBUTORS.md', + 'LICENSE', + 'README.md', + 'SECURITY.md', + ].includes(filePath) + ) { + return false + } + + return true +} + +/** + * Checks if the given array of file paths contains any files with potential + * code changes + */ +export function hasCodeChanges(changedFiles) { + return changedFiles.some((file) => { + if (isCodeFile(file)) { + console.log(`Found code file: ${file}`) + return true + } + + return false + }) +} diff --git a/.github/actions/detect-changes/cases/rsc.mjs b/.github/actions/detect-changes/cases/rsc.mjs new file mode 100644 index 000000000000..e2e31da48e8a --- /dev/null +++ b/.github/actions/detect-changes/cases/rsc.mjs @@ -0,0 +1,34 @@ +/** + * Detects if there are RSC changes + * + * @param {string[]} changedFiles The list of files which git has listed as changed + * @returns {boolean} True if there are changes, false if not + */ +export function rscChanged(changedFiles){ + for (const changedFile of changedFiles) { + // As the RSC implementation changes, this list will need to be updated. + // Also, I could be much more specific here, but then I'd also have to + // update this list much more often. So this'll serve as a good enough + // starting point. + if ( + changedFile.startsWith('tasks/smoke-tests/rsc/') || + changedFile.startsWith('tasks/smoke-tests/rsa/') || + changedFile.startsWith('tasks/smoke-tests/basePlaywright.config.ts') || + changedFile.startsWith('.github/actions/set-up-rsa-project/') || + changedFile.startsWith('.github/actions/set-up-rsc-external-packages-project/') || + changedFile.startsWith('.github/actions/set-up-rsc-project/') || + changedFile.startsWith('packages/internal/') || + changedFile.startsWith('packages/project-config/') || + changedFile.startsWith('packages/web/') || + changedFile.startsWith('packages/vite/') || + changedFile.startsWith('__fixtures__/test-project-rsa') || + changedFile.startsWith('__fixtures__/test-project-rsc-external-packages') + ) { + console.log('RSC change detected:', changedFile) + return true + } + } + + console.log('No RSC changes') + return false +} diff --git a/.github/actions/detect-changes/cases/ssr.mjs b/.github/actions/detect-changes/cases/ssr.mjs new file mode 100644 index 000000000000..c3d05e7296cb --- /dev/null +++ b/.github/actions/detect-changes/cases/ssr.mjs @@ -0,0 +1,27 @@ +/** + * Detects if there are SSR changes + * + * @param {string[]} changedFiles The list of files which git has listed as changed + * @returns {boolean} True if there are changes, false if not + */ +export function ssrChanged(changedFiles){ + for (const changedFile of changedFiles) { + if ( + changedFile.startsWith('tasks/smoke-tests/streaming-ssr') || + changedFile === 'tasks/smoke-tests/basePlaywright.config.ts' || + changedFile === 'tasks/test-project/codemods/delayedPage.js' || + changedFile.startsWith('packages/internal/') || + changedFile.startsWith('packages/project-config/') || + changedFile.startsWith('packages/web/') || + changedFile.startsWith('packages/router/') || + changedFile.startsWith('packages/web-server/') || + changedFile.startsWith('packages/vite/') + ) { + console.log('SSR change detected:', changedFile) + return true + } + } + + console.log('No SSR changes') + return false +} diff --git a/.github/actions/detect-changes/detectChanges.mjs b/.github/actions/detect-changes/detectChanges.mjs new file mode 100644 index 000000000000..c3028c57a937 --- /dev/null +++ b/.github/actions/detect-changes/detectChanges.mjs @@ -0,0 +1,105 @@ +import fs from 'node:fs' + +import core from '@actions/core' +import { hasCodeChanges } from './cases/code_changes.mjs' +import { rscChanged } from './cases/rsc.mjs' +import { ssrChanged } from './cases/ssr.mjs' + +const getPrNumber = (githubRef) => { + // Example GITHUB_REF refs/pull/9544/merge + const result = /refs\/pull\/(\d+)\/merge/g.exec(process.env.GITHUB_REF) + + let prNumber = result?.[1] + + if (!prNumber) { + try { + // Example GITHUB_EVENT_PATH + // /home/runner/work/_temp/_github_workflow/event.json + const ev = JSON.parse( + fs.readFileSync(process.env.GITHUB_EVENT_PATH, 'utf8') + ) + prNumber = ev.pull_request?.number + } catch { + // fall through + } + } + + if (!prNumber) { + throw new Error('Could not find the PR number') + } + + return prNumber +} + +async function getChangedFiles(page = 1) { + const prNumber = getPrNumber() + + console.log(`Getting changed files for PR ${prNumber} (page ${page})`) + + let changedFiles = [] + + // Query the GitHub API to get the changed files in the PR + const githubToken = process.env.GITHUB_TOKEN + const url = `https://api.github.com/repos/redwoodjs/redwood/pulls/${prNumber}/files?per_page=100&page=${page}` + const resp = await fetch(url, { + headers: { + Authorization: githubToken ? `Bearer ${githubToken}` : undefined, + ['X-GitHub-Api-Version']: '2022-11-28', + Accept: 'application/vnd.github+json', + }, + }) + + const json = await resp.json() + const files = json?.map((file) => file.filename) || [] + + changedFiles = changedFiles.concat(files) + + // Look at the headers to see if the result is paginated + const linkHeader = resp.headers.get('link') + if (linkHeader && linkHeader.includes('rel="next"')) { + const files = await getChangedFiles(page + 1) + changedFiles = changedFiles.concat(files) + } + + return changedFiles +} + +async function main() { + const branch = process.env.GITHUB_BASE_REF + + // If there's no branch, we're not in a pull request. + if (!branch) { + core.setOutput('onlydocs', false) + core.setOutput('rsc', false) + core.setOutput('ssr', false) + return + } + + const changedFiles = await getChangedFiles() + console.log(`${changedFiles.length} changed files`) + + if (changedFiles.length === 0) { + console.log( + 'No changed files found. Something must have gone wrong. Fall back to ' + + 'running all tests.' + ) + core.setOutput('onlydocs', false) + core.setOutput('rsc', true) + core.setOutput('ssr', true) + return + } + + if (!hasCodeChanges(changedFiles)) { + console.log('No code changes detected, only docs') + core.setOutput('onlydocs', true) + core.setOutput('rsc', false) + core.setOutput('ssr', false) + return + } + + core.setOutput('onlydocs', false) + core.setOutput('rsc', rscChanged(changedFiles)) + core.setOutput('ssr', ssrChanged(changedFiles)) +} + +main() diff --git a/.github/actions/detect-changes/package.json b/.github/actions/detect-changes/package.json new file mode 100644 index 000000000000..e3eddcdb1965 --- /dev/null +++ b/.github/actions/detect-changes/package.json @@ -0,0 +1,9 @@ +{ + "name": "detect-changes", + "private": true, + "dependencies": { + "@actions/core": "1.10.1", + "@actions/exec": "1.1.1" + }, + "packageManager": "yarn@4.0.2" +} diff --git a/.github/actions/detect-changes/yarn.lock b/.github/actions/detect-changes/yarn.lock new file mode 100644 index 000000000000..d92f30d6019d --- /dev/null +++ b/.github/actions/detect-changes/yarn.lock @@ -0,0 +1,66 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@actions/core@npm:1.10.1": + version: 1.10.1 + resolution: "@actions/core@npm:1.10.1" + dependencies: + "@actions/http-client": "npm:^2.0.1" + uuid: "npm:^8.3.2" + checksum: 7a61446697a23dcad3545cf0634dedbdedf20ae9a0ee6ee977554589a15deb4a93593ee48a41258933d58ce0778f446b0d2c162b60750956fb75e0b9560fb832 + languageName: node + linkType: hard + +"@actions/exec@npm:1.1.1": + version: 1.1.1 + resolution: "@actions/exec@npm:1.1.1" + dependencies: + "@actions/io": "npm:^1.0.1" + checksum: 4a09f6bdbe50ce68b5cf8a7254d176230d6a74bccf6ecc3857feee209a8c950ba9adec87cc5ecceb04110182d1c17117234e45557d72fde6229b7fd3a395322a + languageName: node + linkType: hard + +"@actions/http-client@npm:^2.0.1": + version: 2.0.1 + resolution: "@actions/http-client@npm:2.0.1" + dependencies: + tunnel: "npm:^0.0.6" + checksum: b58987ba2f53d7988f612ede7ff834573a3360c21f8fdea9fea92f26ada0fd0efafb22aa7d83f49c18965a5b765775d5253e2edb8d9476d924c4b304ef726b67 + languageName: node + linkType: hard + +"@actions/io@npm:^1.0.1": + version: 1.1.2 + resolution: "@actions/io@npm:1.1.2" + checksum: 61c871bbee1cf58f57917d9bb2cf6bb7ea4dc40de3f65c7fb4ec619ceff57fc98f56be9cca2d476b09e7a96e1cba0d88cd125c4f690d384b9483935186f256c1 + languageName: node + linkType: hard + +"detect-changes@workspace:.": + version: 0.0.0-use.local + resolution: "detect-changes@workspace:." + dependencies: + "@actions/core": "npm:1.10.1" + "@actions/exec": "npm:1.1.1" + languageName: unknown + linkType: soft + +"tunnel@npm:^0.0.6": + version: 0.0.6 + resolution: "tunnel@npm:0.0.6" + checksum: e27e7e896f2426c1c747325b5f54efebc1a004647d853fad892b46d64e37591ccd0b97439470795e5262b5c0748d22beb4489a04a0a448029636670bfd801b75 + languageName: node + linkType: hard + +"uuid@npm:^8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54 + languageName: node + linkType: hard diff --git a/.github/actions/only_doc_changes/action.yml b/.github/actions/only_doc_changes/action.yml deleted file mode 100644 index 686db64bb73a..000000000000 --- a/.github/actions/only_doc_changes/action.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: Docs -description: Determines if the PR only changes docs -outputs: - only-doc-changes: - description: If the PR only changes docs -runs: - using: node16 - main: only_doc_changes.mjs diff --git a/.github/actions/only_doc_changes/only_doc_changes.mjs b/.github/actions/only_doc_changes/only_doc_changes.mjs deleted file mode 100644 index 2dbfef5ebffc..000000000000 --- a/.github/actions/only_doc_changes/only_doc_changes.mjs +++ /dev/null @@ -1,35 +0,0 @@ -import { exec, getExecOutput } from '@actions/exec' -import core from '@actions/core' - -const branch = process.env.GITHUB_BASE_REF - -await exec(`git fetch origin ${branch}`) - -const { stdout } = await getExecOutput(`git diff origin/${branch} --name-only`) - -const changedFiles = stdout.toString().trim().split('\n').filter(Boolean) - -for (const changedFile of changedFiles) { - if (changedFile.startsWith('docs')) { - continue - } - - for (const fileToIgnore of [ - 'CHANGELOG.md', - 'CODE_OF_CONDUCT.md', - 'CONTRIBUTING.md', - 'CONTRIBUTORS.md', - 'LICENSE', - 'README.md', - 'SECURITY.md', - ]) { - if (changedFile === fileToIgnore) { - continue - } - } - - core.setOutput('only-doc-changes', false) - process.exit(0) -} - -core.setOutput('only-doc-changes', true) diff --git a/.github/actions/only_doc_changes/package.json b/.github/actions/only_doc_changes/package.json deleted file mode 100644 index 78b008014e62..000000000000 --- a/.github/actions/only_doc_changes/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "only_doc_changes", - "private": true, - "dependencies": { - "@actions/core": "1.10.0", - "@actions/exec": "1.1.1" - }, - "packageManager": "yarn@3.5.0" -} diff --git a/.github/actions/only_doc_changes/yarn.lock b/.github/actions/only_doc_changes/yarn.lock deleted file mode 100644 index 527838bbdcb8..000000000000 --- a/.github/actions/only_doc_changes/yarn.lock +++ /dev/null @@ -1,66 +0,0 @@ -# This file is generated by running "yarn install" inside your project. -# Manual changes might be lost - proceed with caution! - -__metadata: - version: 6 - cacheKey: 8c0 - -"@actions/core@npm:1.10.0": - version: 1.10.0 - resolution: "@actions/core@npm:1.10.0" - dependencies: - "@actions/http-client": ^2.0.1 - uuid: ^8.3.2 - checksum: 9214d1e0cf5cf2a5d48b8f3b12488c6be9f6722ea60f2397409226e8410b5a3e12e558d9b66c93469d180399865ec20180119408a1770f026bd9ecac6965fcda - languageName: node - linkType: hard - -"@actions/exec@npm:1.1.1": - version: 1.1.1 - resolution: "@actions/exec@npm:1.1.1" - dependencies: - "@actions/io": ^1.0.1 - checksum: 4a09f6bdbe50ce68b5cf8a7254d176230d6a74bccf6ecc3857feee209a8c950ba9adec87cc5ecceb04110182d1c17117234e45557d72fde6229b7fd3a395322a - languageName: node - linkType: hard - -"@actions/http-client@npm:^2.0.1": - version: 2.0.1 - resolution: "@actions/http-client@npm:2.0.1" - dependencies: - tunnel: ^0.0.6 - checksum: b58987ba2f53d7988f612ede7ff834573a3360c21f8fdea9fea92f26ada0fd0efafb22aa7d83f49c18965a5b765775d5253e2edb8d9476d924c4b304ef726b67 - languageName: node - linkType: hard - -"@actions/io@npm:^1.0.1": - version: 1.1.2 - resolution: "@actions/io@npm:1.1.2" - checksum: 61c871bbee1cf58f57917d9bb2cf6bb7ea4dc40de3f65c7fb4ec619ceff57fc98f56be9cca2d476b09e7a96e1cba0d88cd125c4f690d384b9483935186f256c1 - languageName: node - linkType: hard - -"only_doc_changes@workspace:.": - version: 0.0.0-use.local - resolution: "only_doc_changes@workspace:." - dependencies: - "@actions/core": 1.10.0 - "@actions/exec": 1.1.1 - languageName: unknown - linkType: soft - -"tunnel@npm:^0.0.6": - version: 0.0.6 - resolution: "tunnel@npm:0.0.6" - checksum: e27e7e896f2426c1c747325b5f54efebc1a004647d853fad892b46d64e37591ccd0b97439470795e5262b5c0748d22beb4489a04a0a448029636670bfd801b75 - languageName: node - linkType: hard - -"uuid@npm:^8.3.2": - version: 8.3.2 - resolution: "uuid@npm:8.3.2" - bin: - uuid: dist/bin/uuid - checksum: bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54 - languageName: node - linkType: hard diff --git a/.github/actions/require-milestone/action.yml b/.github/actions/require-milestone/action.yml new file mode 100644 index 000000000000..3f89663494d9 --- /dev/null +++ b/.github/actions/require-milestone/action.yml @@ -0,0 +1,6 @@ +name: Require milestone +description: Ensures that a PR has a valid milestone + +runs: + using: node20 + main: requireMilestone.mjs diff --git a/.github/actions/require-milestone/requireMilestone.mjs b/.github/actions/require-milestone/requireMilestone.mjs new file mode 100644 index 000000000000..3c6b76b3dfe9 --- /dev/null +++ b/.github/actions/require-milestone/requireMilestone.mjs @@ -0,0 +1,35 @@ +// @ts-check + +import fs from 'node:fs' + +function main() { + // `GITHUB_EVENT_PATH` is set in the GitHub Actions runner. + // It's the path to the file on the runner that contains the full event webhook payload. + // See https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables. + const event = fs.readFileSync(process.env.GITHUB_EVENT_PATH, 'utf-8') + + const { + pull_request: { + milestone + } + } = JSON.parse(event) + + if (milestone) { + return + } + + process.exitCode = 1 + + console.error([ + "A pull request must have a milestone that indicates where it's supposed to be released:", + '', + "- next-release -- the PR should be released in the next minor (it's a feature)", + "- next-release-patch -- the PR should be released in the next patch (it's a bug fix or project-side chore)", + "- v7.0.0 -- the PR should be released in v7.0.0 (it's breaking or builds off a breaking PR)", + "- chore -- the PR is a framework-side chore (changes CI, tasks, etc.) and it isn't released, per se", + '', + `(If you're still not sure, go with "next-release".)` + ].join('\n')) +} + +main() diff --git a/.github/actions/set-up-job/action.yml b/.github/actions/set-up-job/action.yml deleted file mode 100644 index 20e5ddf68566..000000000000 --- a/.github/actions/set-up-job/action.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: 🧶 Set up job -description: | - Set up node and yarn cache, then install. This sequence of steps appeared often enough - in many of Redwood's jobs to make it worth abstracting. - -inputs: - node-version: - default: 18 - github-token: - default: ${{ github.token }} - -runs: - using: composite - steps: - - uses: actions/setup-node@v3 - with: - node-version: ${{ inputs.node-version }} - - # From https://github.com/actions/cache/blob/main/examples.md#node---yarn-2. - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT - shell: bash - - - uses: actions/cache@v3 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock', '.yarnrc.yml') }} - restore-keys: | - ${{ runner.os }}-yarn- - - - run: yarn install - shell: bash - env: - GITHUB_TOKEN: ${{ inputs.github-token }} diff --git a/.github/actions/set-up-rsa-project/README.md b/.github/actions/set-up-rsa-project/README.md new file mode 100644 index 000000000000..0bf518131d44 --- /dev/null +++ b/.github/actions/set-up-rsa-project/README.md @@ -0,0 +1,29 @@ +# GitHub action to copy a template RSA project to use for testing + +This action copies a RW project with Streaming SSR and RSC support already set +up. It's used for RSA smoke tests. + +It copies the `__fixtures__/test-project-rsa` project, runs `yarn install` and +`project:copy`. Finally it builds the rw app. + +## Testing/running locally + +Go into the github actions folder +`cd .github/actions` + +Then run the following command to execute the action +`node set-up-rsa-project/setUpRsaProjectLocally.mjs` + +## Design + +The main logic of the action is in the `../actionsLib.mjs` file. To be able to +run that code both on GitHub and locally it uses dependency injection. The +injection is done by `setupRsaProjectLocally.mjs` for when you want to run the +action on your own machine and by `setupRsaProjectGitHib.mjs` when it's +triggered by GitHub CI. + +When doing further changes to the code here it's very important to keep the +DI scripts as light on logic as possible. Ideally all logic is kept to +`../actionsLib.mjs` so that the same logic is used both locally and on GitHub. +Do note though that more actions share that code, so make sure not to break +the other actions when making changes there. diff --git a/.github/actions/set-up-rsa-project/action.yaml b/.github/actions/set-up-rsa-project/action.yaml new file mode 100644 index 000000000000..066eb600010a --- /dev/null +++ b/.github/actions/set-up-rsa-project/action.yaml @@ -0,0 +1,10 @@ +name: Set up RSA test project from fixture +description: Sets up an RSA project for smoke-tests + +runs: + using: node20 + main: 'setUpRsaProjectGitHub.mjs' + +outputs: + test-project-path: + description: Path to the test project diff --git a/.github/actions/set-up-rsa-project/jsconfig.json b/.github/actions/set-up-rsa-project/jsconfig.json new file mode 100644 index 000000000000..8effcfaa09ef --- /dev/null +++ b/.github/actions/set-up-rsa-project/jsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "noEmit": true, + "esModuleInterop": true, + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "skipLibCheck": false, + "jsx": "react-jsx" + }, +} diff --git a/.github/actions/set-up-rsa-project/package.json b/.github/actions/set-up-rsa-project/package.json new file mode 100644 index 000000000000..196dba219d44 --- /dev/null +++ b/.github/actions/set-up-rsa-project/package.json @@ -0,0 +1,6 @@ +{ + "name": "set-up-rsa-project", + "version": "0.0.0", + "private": true, + "type": "module" +} diff --git a/.github/actions/set-up-rsa-project/setUpRsaProjectGitHub.mjs b/.github/actions/set-up-rsa-project/setUpRsaProjectGitHub.mjs new file mode 100644 index 000000000000..497d82e4c9bf --- /dev/null +++ b/.github/actions/set-up-rsa-project/setUpRsaProjectGitHub.mjs @@ -0,0 +1,22 @@ +/* eslint-env node */ +// @ts-check + +import path from 'node:path' + +import core from '@actions/core' + +import { createExecWithEnvInCwd, setUpRscTestProject } from '../actionsLib.mjs' + +const testProjectAndFixtureName = 'test-project-rsa' +const testProjectPath = path.join( + path.dirname(process.cwd()), + testProjectAndFixtureName +) +const execInProject = createExecWithEnvInCwd(testProjectPath) + +setUpRscTestProject( + testProjectPath, + testProjectAndFixtureName, + core, + execInProject +) diff --git a/.github/actions/set-up-rsa-project/setUpRsaProjectLocally.mjs b/.github/actions/set-up-rsa-project/setUpRsaProjectLocally.mjs new file mode 100644 index 000000000000..8e9baa044594 --- /dev/null +++ b/.github/actions/set-up-rsa-project/setUpRsaProjectLocally.mjs @@ -0,0 +1,111 @@ +/* eslint-env node */ +// @ts-check + +import os from 'node:os' +import path from 'node:path' + +import execa from 'execa' + +import { setUpRscTestProject } from '../actionsLib.mjs' + +class ExecaError extends Error { + stdout + stderr + exitCode + + constructor({ stdout, stderr, exitCode }) { + super(`execa failed with exit code ${exitCode}`) + this.stdout = stdout + this.stderr = stderr + this.exitCode = exitCode + } +} + +/** + * @template [EncodingType=string] + * @typedef {import('execa').Options} ExecaOptions + */ + +/** + * @typedef {{ + * env?: Record + * }} ExecOptions + */ + +/** + * @param {string} commandLine command to execute (can include additional args). Must be correctly escaped. + * @param {string[]=} args arguments for tool. Escaping is handled by the lib. + * @param {ExecOptions=} options exec options. See ExecOptions + */ +async function exec(commandLine, args, options) { + return execa(commandLine, args, options) + .then(({ stdout, stderr, exitCode }) => { + if (exitCode !== 0) { + throw new ExecaError({ stdout, stderr, exitCode }) + } + }) + .catch((error) => { + if (error instanceof ExecaError) { + // Rethrow ExecaError + throw error + } else { + const { stdout, stderr, exitCode } = error + console.log('error', error) + throw new ExecaError({ stdout, stderr, exitCode }) + } + }) +} + +/** + * @param {string} cwd + * @param {Record=} env + * @returns {ExecaOptions} + */ +function getExecaOptions(cwd, env = {}) { + return { + shell: true, + stdio: 'inherit', + cleanup: true, + cwd, + env, + } +} + +const testProjectAndFixtureName = 'test-project-rsa' + +const testProjectPath = path.join( + os.tmpdir(), + 'redwood', + testProjectAndFixtureName, + // ":" is problematic with paths + new Date().toISOString().split(':').join('-') +) + +// Mock for @actions/core +const core = { + setOutput: () => {}, +} + +/** + * Exec a command. + * Output will be streamed to the live console. + * Returns promise with return code + * + * @param {string} commandLine command to execute (can include additional args). Must be correctly escaped. + * @param {ExecOptions=} options exec options. See ExecOptions + * @returns {Promise} exit code + */ +function execInProject(commandLine, options) { + return exec( + commandLine, + undefined, + getExecaOptions(testProjectPath, options?.env) + ) +} + +setUpRscTestProject( + testProjectPath, + testProjectAndFixtureName, + core, + execInProject +) diff --git a/.github/actions/set-up-rsc-external-packages-project/README.md b/.github/actions/set-up-rsc-external-packages-project/README.md new file mode 100644 index 000000000000..8c47dc603dd5 --- /dev/null +++ b/.github/actions/set-up-rsc-external-packages-project/README.md @@ -0,0 +1,29 @@ +# GitHub action to copy a template RSC project to use for testing + +This action copies a RW project with Streaming SSR and RSC support already set +up. It's used for RSC smoke tests. + +It copies the `__fixtures__/test-project-rsc-external-packages` project, runs +`yarn install` and `project:copy`. Finally it builds the rw app. + +## Testing/running locally + +Go into the github actions folder +`cd .github/actions` + +Then run the following command to execute the action +`node set-up-rsc-external-packages-project/setUpRscExternalPackagesProjectLocally.mjs` + +## Design + +The main logic of the action is in the `../actionsLib.mjs` file. To be able to +run that code both on GitHub and locally it uses dependency injection. The +injection is done by `setupRscExternalPackagesProjectLocally.mjs` for when you +want to run the action on your own machine and by +`setupRscExternalPackagesProjectGitHib.mjs` when it's triggered by GitHub CI. + +When doing further changes to the code here it's very important to keep the +DI scripts as light on logic as possible. Ideally all logic is kept to +`../actionsLib.mjs` so that the same logic is used both locally and on GitHub. +Do note though that more actions share that code, so make sure not to break +the other actions when making changes there. diff --git a/.github/actions/set-up-rsc-external-packages-project/action.yaml b/.github/actions/set-up-rsc-external-packages-project/action.yaml new file mode 100644 index 000000000000..cdac1d7c9fbc --- /dev/null +++ b/.github/actions/set-up-rsc-external-packages-project/action.yaml @@ -0,0 +1,10 @@ +name: Set up RSC test project with external packages from fixture +description: Sets up an RSC project that imports external packages for smoke-tests + +runs: + using: node20 + main: 'setUpRscExternalPackagesProjectGitHub.mjs' + +outputs: + test-project-path: + description: Path to the test project diff --git a/.github/actions/set-up-rsc-external-packages-project/jsconfig.json b/.github/actions/set-up-rsc-external-packages-project/jsconfig.json new file mode 100644 index 000000000000..8effcfaa09ef --- /dev/null +++ b/.github/actions/set-up-rsc-external-packages-project/jsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "noEmit": true, + "esModuleInterop": true, + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "skipLibCheck": false, + "jsx": "react-jsx" + }, +} diff --git a/.github/actions/set-up-rsc-external-packages-project/package.json b/.github/actions/set-up-rsc-external-packages-project/package.json new file mode 100644 index 000000000000..8dd1f5abbe06 --- /dev/null +++ b/.github/actions/set-up-rsc-external-packages-project/package.json @@ -0,0 +1,6 @@ +{ + "name": "set-up-rsc-external-packages-project", + "version": "0.0.0", + "private": true, + "type": "module" +} diff --git a/.github/actions/set-up-rsc-external-packages-project/setUpRscExternalPackagesProjectGitHub.mjs b/.github/actions/set-up-rsc-external-packages-project/setUpRscExternalPackagesProjectGitHub.mjs new file mode 100644 index 000000000000..e09f3f597d5f --- /dev/null +++ b/.github/actions/set-up-rsc-external-packages-project/setUpRscExternalPackagesProjectGitHub.mjs @@ -0,0 +1,22 @@ +/* eslint-env node */ +// @ts-check + +import path from 'node:path' + +import core from '@actions/core' + +import { createExecWithEnvInCwd, setUpRscTestProject } from '../actionsLib.mjs' + +const testProjectAndFixtureName = 'test-project-rsc-external-packages' +const testProjectPath = path.join( + path.dirname(process.cwd()), + testProjectAndFixtureName +) +const execInProject = createExecWithEnvInCwd(testProjectPath) + +setUpRscTestProject( + testProjectPath, + testProjectAndFixtureName, + core, + execInProject +) diff --git a/.github/actions/set-up-rsc-external-packages-project/setUpRscExternalPackagesProjectLocally.mjs b/.github/actions/set-up-rsc-external-packages-project/setUpRscExternalPackagesProjectLocally.mjs new file mode 100644 index 000000000000..2f2af2c87d85 --- /dev/null +++ b/.github/actions/set-up-rsc-external-packages-project/setUpRscExternalPackagesProjectLocally.mjs @@ -0,0 +1,111 @@ +/* eslint-env node */ +// @ts-check + +import os from 'node:os' +import path from 'node:path' + +import execa from 'execa' + +import { setUpRscTestProject } from '../actionsLib.mjs' + +class ExecaError extends Error { + stdout + stderr + exitCode + + constructor({ stdout, stderr, exitCode }) { + super(`execa failed with exit code ${exitCode}`) + this.stdout = stdout + this.stderr = stderr + this.exitCode = exitCode + } +} + +/** + * @template [EncodingType=string] + * @typedef {import('execa').Options} ExecaOptions + */ + +/** + * @typedef {{ + * env?: Record + * }} ExecOptions + */ + +/** + * @param {string} commandLine command to execute (can include additional args). Must be correctly escaped. + * @param {string[]=} args arguments for tool. Escaping is handled by the lib. + * @param {ExecOptions=} options exec options. See ExecOptions + */ +async function exec(commandLine, args, options) { + return execa(commandLine, args, options) + .then(({ stdout, stderr, exitCode }) => { + if (exitCode !== 0) { + throw new ExecaError({ stdout, stderr, exitCode }) + } + }) + .catch((error) => { + if (error instanceof ExecaError) { + // Rethrow ExecaError + throw error + } else { + const { stdout, stderr, exitCode } = error + console.log('error', error) + throw new ExecaError({ stdout, stderr, exitCode }) + } + }) +} + +/** + * @param {string} cwd + * @param {Record=} env + * @returns {ExecaOptions} + */ +function getExecaOptions(cwd, env = {}) { + return { + shell: true, + stdio: 'inherit', + cleanup: true, + cwd, + env, + } +} + +const testProjectAndFixtureName = 'test-project-rsc-external-packages' + +const testProjectPath = path.join( + os.tmpdir(), + 'redwood', + testProjectAndFixtureName, + // ":" is problematic with paths + new Date().toISOString().split(':').join('-') +) + +// Mock for @actions/core +const core = { + setOutput: () => {}, +} + +/** + * Exec a command. + * Output will be streamed to the live console. + * Returns promise with return code + * + * @param {string} commandLine command to execute (can include additional args). Must be correctly escaped. + * @param {ExecOptions=} options exec options. See ExecOptions + * @returns {Promise} exit code + */ +function execInProject(commandLine, options) { + return exec( + commandLine, + undefined, + getExecaOptions(testProjectPath, options?.env) + ) +} + +setUpRscTestProject( + testProjectPath, + testProjectAndFixtureName, + core, + execInProject +) diff --git a/.github/actions/set-up-rsc-project/README.md b/.github/actions/set-up-rsc-project/README.md new file mode 100644 index 000000000000..4b01027d9cee --- /dev/null +++ b/.github/actions/set-up-rsc-project/README.md @@ -0,0 +1,32 @@ +# GitHub action to create a RW project with RSCs set up + +This action creates a RW project with Streaming SSR and RSC support set up. +It's used for RSC smoke tests. + +It runs `npx -y create-redwood-app@canary ...` to set the project up with the +latest canary release of Redwood. It then runs +`experimental setup-streaming-ssr` and `experimental setup-rsc` followed by +a build of the rw app. Finally it runs `project:copy` to get the latest +changes to the framework (i.e. the changes introduced by the PR triggering this +action) into the project. + +## Testing/running locally + +Go into the github actions folder +`cd .github/actions` + +Then run the following command to execute the action +`node set-up-rsc-project/setUpRscProjectLocally.mjs` + +## Design + +The main logic of the action is in the `setUpRscProject.mjs` file. To be able +to run that code both on GitHub and locally it uses dependency injection. The +injection is done by `setupRscProjectLocally.mjs` for when you want to run +the action on your own machine and by `setupRscProjectGitHib.mjs` when it's +triggered by GitHub CI. + +When doing further changes to the code here it's very important to keep the +DI scripts as light on logic as possible. Ideally all logic is kept to +`setUpRscProject.mjs` so that the same logic is used both locally and on +GitHub. diff --git a/.github/actions/set-up-rsc-project/action.yaml b/.github/actions/set-up-rsc-project/action.yaml new file mode 100644 index 000000000000..858e5937be56 --- /dev/null +++ b/.github/actions/set-up-rsc-project/action.yaml @@ -0,0 +1,10 @@ +name: Set up RSC test project +description: Sets up an RSC project for smoke-tests + +runs: + using: node20 + main: 'setUpRscProjectGitHub.mjs' + +outputs: + test-project-path: + description: Path to the test project diff --git a/.github/actions/set-up-rsc-project/jsconfig.json b/.github/actions/set-up-rsc-project/jsconfig.json new file mode 100644 index 000000000000..8effcfaa09ef --- /dev/null +++ b/.github/actions/set-up-rsc-project/jsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "noEmit": true, + "esModuleInterop": true, + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "skipLibCheck": false, + "jsx": "react-jsx" + }, +} diff --git a/.github/actions/set-up-rsc-project/package.json b/.github/actions/set-up-rsc-project/package.json new file mode 100644 index 000000000000..90aa7b8a79bd --- /dev/null +++ b/.github/actions/set-up-rsc-project/package.json @@ -0,0 +1,6 @@ +{ + "name": "set-up-rsc-project", + "version": "0.0.0", + "private": true, + "type": "module" +} diff --git a/.github/actions/set-up-rsc-project/setUpRscProject.mjs b/.github/actions/set-up-rsc-project/setUpRscProject.mjs new file mode 100644 index 000000000000..928926f38355 --- /dev/null +++ b/.github/actions/set-up-rsc-project/setUpRscProject.mjs @@ -0,0 +1,109 @@ +/* eslint-env node */ +// @ts-check + +import path from 'node:path' + +import { REDWOOD_FRAMEWORK_PATH } from '../actionsLib.mjs' + +/** + * @typedef {import('@actions/exec').ExecOptions} ExecOptions + */ + +/** + * Exec a command. + * Output will be streamed to the live console. + * Returns promise with return code + * + * @callback Exec + * @param {string} commandLine command to execute (can include additional args). Must be correctly escaped. + * @param {string[]=} args arguments for tool. Escaping is handled by the lib. + * @param {ExecOptions=} options exec options. See ExecOptions + * @returns {Promise} exit code + */ + +/** + * @callback ExecInProject + * @param {string} commandLine command to execute (can include additional args). Must be correctly escaped. + * @param {Omit=} options exec options. See ExecOptions + * @returns {Promise} exit code + */ + +/** + * @param {string} rscProjectPath + * @param {Object} core + * @param {(key: string, value: string) => void} core.setOutput + * @param {Exec} exec + * @param {ExecInProject} execInProject + * @returns {Promise} + */ +export async function main( + rscProjectPath, + core, + exec, + execInProject +) { + core.setOutput('rsc-project-path', rscProjectPath) + + console.log('rwPath', REDWOOD_FRAMEWORK_PATH) + console.log('rscProjectPath', rscProjectPath) + + await setUpRscProject( + rscProjectPath, + exec, + execInProject, + ) +} + +/** + * @param {string} rscProjectPath + * @param {Exec} exec + * @param {ExecInProject} execInProject + * @returns {Promise} + */ +async function setUpRscProject( + rscProjectPath, + exec, + execInProject, +) { + const rwBinPath = path.join( + REDWOOD_FRAMEWORK_PATH, + 'packages/cli/dist/index.js' + ) + const rwfwBinPath = path.join( + REDWOOD_FRAMEWORK_PATH, + 'packages/cli/dist/rwfw.js' + ) + + console.log(`Creating project at ${rscProjectPath}`) + console.log() + await exec('npx', [ + '-y', + 'create-redwood-app@canary', + '-y', + '--no-git', + rscProjectPath, + ]) + + console.log(`Setting up Streaming/SSR in ${rscProjectPath}`) + const cmdSetupStreamingSSR = `node ${rwBinPath} experimental setup-streaming-ssr -f` + await execInProject(cmdSetupStreamingSSR) + console.log() + + console.log(`Setting up RSC in ${rscProjectPath}`) + await execInProject(`node ${rwBinPath} experimental setup-rsc`) + console.log() + + console.log(`Copying over framework files to ${rscProjectPath}`) + await execInProject(`node ${rwfwBinPath} project:copy`, { + env: { RWFW_PATH: REDWOOD_FRAMEWORK_PATH }, + }) + console.log() + + console.log('Installing dependencies') + await execInProject('yarn install') + console.log() + + console.log(`Building project in ${rscProjectPath}`) + await execInProject(`node ${rwBinPath} build -v`) + console.log() +} diff --git a/.github/actions/set-up-rsc-project/setUpRscProjectGitHub.mjs b/.github/actions/set-up-rsc-project/setUpRscProjectGitHub.mjs new file mode 100644 index 000000000000..1ba4dd3ac6fb --- /dev/null +++ b/.github/actions/set-up-rsc-project/setUpRscProjectGitHub.mjs @@ -0,0 +1,17 @@ +/* eslint-env node */ +// @ts-check + +import path from 'node:path' + +import core from '@actions/core' +import { exec } from '@actions/exec' + +import { createExecWithEnvInCwd } from '../actionsLib.mjs' + +import { main } from './setUpRscProject.mjs' + +const rscProjectPath = path.join(path.dirname(process.cwd()), 'rsc-project') + +const execInProject = createExecWithEnvInCwd(rscProjectPath) + +main(rscProjectPath, core, exec, execInProject) diff --git a/.github/actions/set-up-rsc-project/setUpRscProjectLocally.mjs b/.github/actions/set-up-rsc-project/setUpRscProjectLocally.mjs new file mode 100644 index 000000000000..c5c57ab2b31f --- /dev/null +++ b/.github/actions/set-up-rsc-project/setUpRscProjectLocally.mjs @@ -0,0 +1,117 @@ +/* eslint-env node */ +// @ts-check + +import os from 'node:os' +import path from 'node:path' + +import execa from 'execa' + +import { main } from './setUpRscProject.mjs' + +class ExecaError extends Error { + stdout + stderr + exitCode + + constructor({ stdout, stderr, exitCode }) { + super(`execa failed with exit code ${exitCode}`) + this.stdout = stdout + this.stderr = stderr + this.exitCode = exitCode + } +} + +/** + * @template [EncodingType=string] + * @typedef {import('execa').Options} ExecaOptions + */ + +/** + * @typedef {{ + * env?: Record + * }} ExecOptions + */ + +/** + * @param {string} commandLine command to execute (can include additional args). Must be correctly escaped. + * @param {string[]=} args arguments for tool. Escaping is handled by the lib. + * @param {ExecOptions=} options exec options. See ExecOptions + */ +async function exec(commandLine, args, options) { + return execa(commandLine, args, options) + .then(({ stdout, stderr, exitCode }) => { + if (exitCode !== 0) { + throw new ExecaError({ stdout, stderr, exitCode }) + } + }) + .catch((error) => { + if (error instanceof ExecaError) { + // Rethrow ExecaError + throw error + } else { + const { stdout, stderr, exitCode } = error + console.log('error', error) + throw new ExecaError({ stdout, stderr, exitCode }) + } + }) +} + +/** + * @param {string} cwd + * @param {Record=} env + * @returns {ExecaOptions} + */ +function getExecaOptions(cwd, env = {}) { + return { + shell: true, + stdio: 'inherit', + cleanup: true, + cwd, + env, + } +} + +const rscProjectPath = path.join( + os.tmpdir(), + 'redwood-rsc-project', + // ":" is problematic with paths + new Date().toISOString().split(':').join('-') +) + +// Mock for @actions/core +const core = { + setOutput: () => {}, +} + +/** + * Exec a command. + * Output will be streamed to the live console. + * Returns promise with return code + * + * @param {string} commandLine command to execute (can include additional args). Must be correctly escaped. + * @param {ExecOptions=} options exec options. See ExecOptions + * @returns {Promise} exit code + */ +function execInProject(commandLine, options) { + return exec( + commandLine, + undefined, + getExecaOptions(rscProjectPath, options?.env) + ) +} + +/** + * Exec a command. + * Output will be streamed to the live console. + * Returns promise with return code + * + * @param {string} commandLine command to execute (can include additional args). Must be correctly escaped. + * @param {string[]=} args arguments for tool. Escaping is handled by the lib. + * @param {ExecOptions=} options exec options. See ExecOptions + * @returns {Promise} exit code + */ +function execInRoot(commandLine, args, options) { + return exec(commandLine, args, getExecaOptions('/', options?.env)) +} + +main(rscProjectPath, core, execInRoot, execInProject) diff --git a/.github/actions/set-up-test-project/action.yaml b/.github/actions/set-up-test-project/action.yaml new file mode 100644 index 000000000000..0c0e623056a5 --- /dev/null +++ b/.github/actions/set-up-test-project/action.yaml @@ -0,0 +1,18 @@ +name: Set up test project +description: Sets up the test project fixture in CI for smoke tests and CLI checks + +runs: + using: node20 + main: 'setUpTestProject.mjs' + +inputs: + bundler: + description: The bundler to use (vite or webpack) + default: vite + canary: + description: Upgrade the project to canary? + default: "false" + +outputs: + test-project-path: + description: Path to the test project diff --git a/.github/actions/set-up-test-project/setUpTestProject.mjs b/.github/actions/set-up-test-project/setUpTestProject.mjs new file mode 100644 index 000000000000..e8eb2da6a80b --- /dev/null +++ b/.github/actions/set-up-test-project/setUpTestProject.mjs @@ -0,0 +1,148 @@ +/* eslint-env node */ +// @ts-check + +import path from 'node:path' + +import cache from '@actions/cache' +import core from '@actions/core' + +import fs from 'fs-extra' + +import { + createCacheKeys, + createExecWithEnvInCwd, + projectCopy, + projectDeps, + REDWOOD_FRAMEWORK_PATH, +} from '../actionsLib.mjs' + +const TEST_PROJECT_PATH = path.join( + path.dirname(process.cwd()), + 'test-project' +) + +core.setOutput('test-project-path', TEST_PROJECT_PATH) + +const bundler = core.getInput('bundler') + +const canary = core.getInput('canary') === 'true' + + +console.log({ + bundler, + canary +}) + +console.log() + +const { + dependenciesKey, + distKey +} = await createCacheKeys({ baseKeyPrefix: 'test-project', distKeyPrefix: bundler, canary }) + +/** + * @returns {Promise} + */ +async function main() { + const distCacheKey = await cache.restoreCache([TEST_PROJECT_PATH], distKey) + + if (distCacheKey) { + console.log(`Cache restored from key: ${distKey}`) + return + } + + const dependenciesCacheKey = await cache.restoreCache([TEST_PROJECT_PATH], dependenciesKey) + + if (dependenciesCacheKey) { + console.log(`Cache restored from key: ${dependenciesKey}`) + await sharedTasks() + } else { + console.log(`Cache not found for input keys: ${distKey}, ${dependenciesKey}`) + await setUpTestProject({ + canary: true + }) + } + + await cache.saveCache([TEST_PROJECT_PATH], distKey) + console.log(`Cache saved with key: ${distKey}`) +} + +/** + * *@param {{canary: boolean}} options + * @returns {Promise} + */ +async function setUpTestProject({ canary }) { + const TEST_PROJECT_FIXTURE_PATH = path.join( + REDWOOD_FRAMEWORK_PATH, + '__fixtures__', + 'test-project' + ) + + console.log(`Creating project at ${TEST_PROJECT_PATH}`) + console.log() + await fs.copy(TEST_PROJECT_FIXTURE_PATH, TEST_PROJECT_PATH) + + console.log(`Adding framework dependencies to ${TEST_PROJECT_PATH}`) + await projectDeps(TEST_PROJECT_PATH) + console.log() + + console.log(`Installing node_modules in ${TEST_PROJECT_PATH}`) + await execInProject('yarn install') + console.log() + + if (canary) { + console.log(`Upgrading project to canary`) + await execInProject('yarn rw upgrade -t canary') + console.log() + } + + await cache.saveCache([TEST_PROJECT_PATH], dependenciesKey) + console.log(`Cache saved with key: ${dependenciesKey}`) + + await sharedTasks() +} + +const execInProject = createExecWithEnvInCwd(TEST_PROJECT_PATH) + +/** + * @returns {Promise} + */ +async function sharedTasks() { + console.log('Copying framework packages to project') + await projectCopy(TEST_PROJECT_PATH) + console.log() + + console.log({ bundler }) + console.log() + + if (bundler === 'webpack') { + console.log(`Setting the bundler to ${bundler}`) + console.log() + + const redwoodTOMLPath = path.join(TEST_PROJECT_PATH, 'redwood.toml') + const redwoodTOML = fs.readFileSync(redwoodTOMLPath, 'utf-8') + const redwoodTOMLWithWebpack = redwoodTOML.replace('[web]\n', '[web]\n bundler = "webpack"\n') + fs.writeFileSync(redwoodTOMLPath, redwoodTOMLWithWebpack) + + // There's an empty line at the end of the redwood.toml file, so no need to console.log after. + console.log(fs.readFileSync(redwoodTOMLPath, 'utf-8')) + } + + console.log('Generating dbAuth secret') + const { stdout } = await execInProject( + 'yarn rw g secret --raw', + { silent: true } + ) + fs.appendFileSync( + path.join(TEST_PROJECT_PATH, '.env'), + `SESSION_SECRET='${stdout}'` + ) + console.log() + + console.log('Running prisma migrate reset') + await execInProject( + 'yarn rw prisma migrate reset --force', + ) +} + +main() diff --git a/.github/actions/set-up-yarn-cache/action.yml b/.github/actions/set-up-yarn-cache/action.yml new file mode 100644 index 000000000000..8bafd1508d4a --- /dev/null +++ b/.github/actions/set-up-yarn-cache/action.yml @@ -0,0 +1,37 @@ +name: Set up yarn cache +description: | + Sets up caching for yarn install steps. + Caches yarn's cache directory, install state, and node_modules. + +runs: + using: composite + + steps: + # We try to cache and restore yarn's cache directory and install state to speed up the yarn install step. + # Caching yarn's cache directory avoids its fetch step. + - name: 📁 Get yarn cache directory + id: get-yarn-cache-directory + run: echo "CACHE_DIRECTORY=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT + shell: bash + + # If the primary key doesn't match, the cache will probably be stale or incomplete, + # but still worth restoring for the yarn install step. + - name: ♻️ Restore yarn cache + uses: actions/cache@v3 + with: + path: ${{ steps.get-yarn-cache-directory.outputs.CACHE_DIRECTORY }} + key: yarn-cache-${{ runner.os }}-${{ hashFiles('yarn.lock', '.yarnrc.yml') }} + restore-keys: yarn-cache-${{ runner.os }} + + # We avoid restore-keys for these steps because it's important to just start from scratch for new PRs. + - name: ♻️ Restore yarn install state + uses: actions/cache@v3 + with: + path: .yarn/install-state.gz + key: yarn-install-state-${{ runner.os }}-${{ hashFiles('yarn.lock', '.yarnrc.yml') }} + + - name: ♻️ Restore node_modules + uses: actions/cache@v3 + with: + path: '**/node_modules' + key: yarn-node-modules-${{ runner.os }}-${{ hashFiles('yarn.lock', '.yarnrc.yml') }} diff --git a/.github/actions/setup_test_project/action.yaml b/.github/actions/setup_test_project/action.yaml deleted file mode 100644 index 93ae99a0d110..000000000000 --- a/.github/actions/setup_test_project/action.yaml +++ /dev/null @@ -1,8 +0,0 @@ -name: 'Setup test project' -description: 'Setup for CLI checks and telemetry benchmarks' -outputs: - test_project_path: - description: 'Path to the test project' -runs: - using: 'node16' - main: 'setup_test_project.mjs' diff --git a/.github/actions/setup_test_project/setup_test_project.mjs b/.github/actions/setup_test_project/setup_test_project.mjs deleted file mode 100644 index 97c6bda385c0..000000000000 --- a/.github/actions/setup_test_project/setup_test_project.mjs +++ /dev/null @@ -1,35 +0,0 @@ -import os from 'node:os' -import path from 'node:path' -import fs from 'fs-extra' - -import { exec } from '@actions/exec' -import * as core from '@actions/core' - -const test_project_path = path.join( - os.tmpdir(), - 'test-project', - // ":" is problematic with paths - new Date().toISOString().split(':').join('-') -) - -console.log({ - test_project_path -}) - -core.setOutput('test_project_path', test_project_path) - -await exec(`yarn build:test-project --ts --link ${test_project_path}`) - -try { - if ( - !fs.existsSync(path.join(test_project_path, 'web/tsconfig.json')) || - !fs.existsSync(path.join(test_project_path, 'api/tsconfig.json')) - ) { - throw ('Test-project is not TypeScript') - } -} catch(e) { - console.log('********************************') - console.error('\nError: Test-project is expected to be TypeScript\nExiting test-project setup.\n') - console.log('********************************') - process.exit(1) -} diff --git a/.github/actions/telemetry_check/check.mjs b/.github/actions/telemetry_check/check.mjs index 52837b76f7f5..830fd13532ac 100644 --- a/.github/actions/telemetry_check/check.mjs +++ b/.github/actions/telemetry_check/check.mjs @@ -1,6 +1,7 @@ /* eslint-env node */ import http from 'http' +import path from 'path' import { exec } from '@actions/exec' @@ -8,28 +9,6 @@ console.log( `Telemetry is being redirected to ${process.env.REDWOOD_REDIRECT_TELEMETRY}` ) -// All the fields we expect inside a telemetry packet -const expectedPacketFields = [ - 'type', - 'command', - 'duration', - 'uid', - 'ci', - 'redwoodCi', - 'NODE_ENV', - 'os', - 'osVersion', - // "shell", // Not expected on windows - 'nodeVersion', - 'yarnVersion', - 'npmVersion', - 'redwoodVersion', - 'system', - 'complexity', - 'sides', - 'webBundler', -] - // Setup fake telemetry server const server = http.createServer((req, res) => { let data = '' @@ -39,27 +18,8 @@ const server = http.createServer((req, res) => { req.on('end', () => { res.writeHead(200) res.end() - - const packet = JSON.parse(data) - - let hasAllFields = true - for (const field of expectedPacketFields) { - if (packet[field] === undefined) { - hasAllFields = false - console.error(`Telemetry packet is missing field "${field}"`) - } - } - - const isCI = packet.ci ?? false - - if (hasAllFields && isCI) { - console.log('Valid telemetry received') - process.exit(0) - } else { - console.error('Invalid telemetry received') - console.error(packet) - process.exit(1) - } + console.log('Telemetry packet received') + process.exit(0) }) }) @@ -77,13 +37,21 @@ try { switch (mode) { case 'crwa': exitCode = await exec( - `yarn node ./packages/create-redwood-app/dist/create-redwood-app.js ../project-for-telemetry --typescript false --git false --yarn-install true` + `yarn node ./packages/create-redwood-app/dist/create-redwood-app.js ../project-for-telemetry --typescript true --git false` ) if (exitCode) { process.exit(1) } break case 'cli': + exitCode = await exec( + `yarn install`, null, { + cwd: path.join(process.cwd(), '../project-for-telemetry') + } + ) + if (exitCode) { + process.exit(1) + } exitCode = await exec( `yarn --cwd ../project-for-telemetry node ../redwood/packages/cli/dist/index.js info` ) diff --git a/.github/actions/update_all_contributors/action.yml b/.github/actions/update_all_contributors/action.yml index 7a02d87d522f..46dd2eb16cc1 100644 --- a/.github/actions/update_all_contributors/action.yml +++ b/.github/actions/update_all_contributors/action.yml @@ -1,5 +1,5 @@ name: Update all contributors description: Updates all contributors runs: - using: node16 + using: node20 main: update_all_contributors.mjs diff --git a/.github/renovate.json b/.github/renovate.json index 80082b3d5660..3aa6fbfd133d 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,8 +1,17 @@ { - "extends": ["config:base"], - "postUpdateOptions": ["yarnDedupeHighest"], - "assignees": ["@jtoar"], - "labels": ["release:chore"], + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "configMigration": true, + + "extends": [ + "config:recommended" + ], + + "postUpdateOptions": [ + "yarnDedupeHighest" + ], + "prConcurrentLimit": 3, + "rebaseWhen": "conflicted", + "packageRules": [ { "matchUpdateTypes": [ @@ -10,45 +19,49 @@ "patch" ], "automerge": true + }, + + { + "groupName": "ESM and @redwoodjs packages", + "enabled": false, + + "matchPackageNames": [ + "boxen", + "chalk", + "camelcase", + "configstore", + "decamelize", + "execa", + "humanize-string", + "latest-version", + "pascalcase", + "pretty-bytes", + "pretty-ms", + "stdout-update", + "tempy", + "terminal-link", + "title-case", + "untildify" + ], + "matchPackagePatterns": [ + "^@redwoodjs/" + ] + }, + + { + "groupName": "chore", + + "matchPackageNames": [ + "all-contributors-cli", + "cypress", + "cypress-wait-until", + "dependency-cruiser", + "glob", + "mheap/github-action-required-labels action", + "nx", + "sort-package-json", + "zx" + ] } - ], - "ignoreDeps": [ - "boxen", - "configstore", - "decamelize", - "execa", - "humanize-string", - "latest-version", - "ora", - "tempy", - "terminal-link", - "chalk", - "pascalcase", - "@redwoodjs/api", - "@redwoodjs/api-server", - "@redwoodjs/auth", - "@redwoodjs/cli", - "@redwoodjs/codemods", - "@redwoodjs/core", - "@redwoodjs/create-redwood-app", - "@redwoodjs/eslint-config", - "@redwoodjs/forms", - "@redwoodjs/graphql-server", - "@redwoodjs/internal", - "@redwoodjs/prerender", - "@redwoodjs/project-config", - "@redwoodjs/record", - "@redwoodjs/router", - "@redwoodjs/structure", - "@redwoodjs/telemetry", - "@redwoodjs/testing", - "@redwoodjs/web", - "lru-cache", - "@types/lru-cache", - "pretty-bytes", - "is-port-reachable", - "pretty-ms", - "camelcase", - "sort-package-json" ] } diff --git a/.github/scripts/publish_canary.sh b/.github/scripts/publish_canary.sh new file mode 100755 index 000000000000..0d846c109075 --- /dev/null +++ b/.github/scripts/publish_canary.sh @@ -0,0 +1,69 @@ +#!/bin/bash +# +# Used in the publish-canary.yml GitHub Action workflow. + +echo "//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}" > .npmrc + +TAG='canary' && [[ "$GITHUB_REF_NAME" = 'next' ]] && TAG='next' +echo "Publishing $TAG" + +args=() + +if [[ "$GITHUB_REF_NAME" = 'main' ]]; then + args+=(premajor) +fi + +args+=( + --include-merged-tags + --canary + --exact + --preid "$TAG" + --dist-tag "$TAG" + --force-publish + --loglevel verbose + --no-git-reset +) + +# `echo 'n'` to answer "no" to the "Are you sure you want to publish these +# packages?" prompt. +# `|&` to pipe both stdout and stderr to grep. Mostly do this keep the github +# action output clean. +# At the end we use awk to increase the commit count by 1, because we'll commit +# updated package.jsons in the next step, which will increase increase the +# final number that lerna will use when publishing the canary packages. +echo 'n' \ + | yarn lerna publish "${args[@]}" \ + |& grep '\-canary\.' \ + | tail -n 1 \ + | sed 's/.*=> //' \ + | sed 's/\+.*//' \ + | awk -F. '{ $NF = $NF + 1 } 1' OFS=. \ + > canary_version + +sed "s/\"@redwoodjs\/\(.*\)\": \".*\"/\"@redwoodjs\/\1\": \"$(cat canary_version)\"/" \ + packages/create-redwood-app/templates/js/package.json > tmpfile \ + && mv tmpfile packages/create-redwood-app/templates/js/package.json +sed "s/\"@redwoodjs\/\(.*\)\": \".*\"/\"@redwoodjs\/\1\": \"$(cat canary_version)\"/" \ + packages/create-redwood-app/templates/js/api/package.json > tmpfile \ + && mv tmpfile packages/create-redwood-app/templates/js/api/package.json +sed "s/\"@redwoodjs\/\(.*\)\": \".*\"/\"@redwoodjs\/\1\": \"$(cat canary_version)\"/" \ + packages/create-redwood-app/templates/js/web/package.json > tmpfile \ + && mv tmpfile packages/create-redwood-app/templates/js/web/package.json + +sed "s/\"@redwoodjs\/\(.*\)\": \".*\"/\"@redwoodjs\/\1\": \"$(cat canary_version)\"/" \ + packages/create-redwood-app/templates/ts/package.json > tmpfile \ + && mv tmpfile packages/create-redwood-app/templates/ts/package.json +sed "s/\"@redwoodjs\/\(.*\)\": \".*\"/\"@redwoodjs\/\1\": \"$(cat canary_version)\"/" \ + packages/create-redwood-app/templates/ts/api/package.json > tmpfile \ + && mv tmpfile packages/create-redwood-app/templates/ts/api/package.json +sed "s/\"@redwoodjs\/\(.*\)\": \".*\"/\"@redwoodjs\/\1\": \"$(cat canary_version)\"/" \ + packages/create-redwood-app/templates/ts/web/package.json > tmpfile \ + && mv tmpfile packages/create-redwood-app/templates/ts/web/package.json + +git config user.name "GitHub Actions" +git config user.email "<>" + +git commit -am "Update create-redwood-app templates to use canary packages" + +args+=(--yes) +yarn lerna publish "${args[@]}" diff --git a/.github/workflows/check-create-redwood-app.yml b/.github/workflows/check-create-redwood-app.yml new file mode 100644 index 000000000000..1f22ece18aa8 --- /dev/null +++ b/.github/workflows/check-create-redwood-app.yml @@ -0,0 +1,33 @@ +name: Check create-redwood-app + +on: + pull_request: + types: [opened, synchronize, reopened, labeled, unlabeled] + +# Cancel in-progress runs of this workflow. +# See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-only-cancel-in-progress-jobs-or-runs-for-the-current-workflow. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check-create-redwood-app: + name: Check create redwood app + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Enable Corepack + run: corepack enable + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - run: yarn install + working-directory: ./.github/actions/check_create_redwood_app + + - name: Check create redwood app + uses: ./.github/actions/check_create_redwood_app + with: + labels: '{ "labels": ${{ toJSON(github.event.pull_request.labels) }} }' diff --git a/.github/workflows/check-test-project-fixture.yml b/.github/workflows/check-test-project-fixture.yml index d620052010a7..2a681dfb36a3 100644 --- a/.github/workflows/check-test-project-fixture.yml +++ b/.github/workflows/check-test-project-fixture.yml @@ -15,10 +15,15 @@ jobs: name: Check test project fixture runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + + - name: Enable Corepack + run: corepack enable + + - uses: actions/setup-node@v4 with: - node-version: 16 + node-version: 20 + - run: yarn install working-directory: ./.github/actions/check_test_project_fixture diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 313876592f16..1bd24a1b7dde 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,9 @@ name: ⚙️ CI on: - pull_request + pull_request: + push: + branches: ['next', 'release/**'] # Cancel in-progress runs of this workflow. # See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-only-cancel-in-progress-jobs-or-runs-for-the-current-workflow. @@ -13,69 +15,109 @@ env: NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} jobs: - only-doc-changes: + detect-changes: if: github.repository == 'redwoodjs/redwood' - name: 📖 Only doc changes? + name: 🔍 Detect changes runs-on: ubuntu-latest + outputs: - only-doc-changes: ${{ steps.only-doc-changes.outputs.only-doc-changes }} + onlydocs: ${{ steps.detect-changes.outputs.onlydocs }} + rsc: ${{ steps.detect-changes.outputs.rsc }} + ssr: ${{ steps.detect-changes.outputs.ssr }} + steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - name: Enable Corepack + run: corepack enable + + - name: ⬢ Set up Node.js + uses: actions/setup-node@v4 with: - node-version: 16 + node-version: 20 - - run: yarn install - working-directory: ./.github/actions/only_doc_changes + - name: 🐈 Yarn install + working-directory: ./.github/actions/detect-changes + run: yarn install --inline-builds + env: + GITHUB_TOKEN: ${{ github.token }} - - name: 📖 Only doc changes? - id: only-doc-changes - uses: ./.github/actions/only_doc_changes + - name: 🔍 Detect changes + id: detect-changes + uses: ./.github/actions/detect-changes check: - needs: only-doc-changes - if: needs.only-doc-changes.outputs.only-doc-changes == 'false' + needs: detect-changes + if: needs.detect-changes.outputs.onlydocs == 'false' + name: ✅ Check constraints, dependencies, and package.json's runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + + - name: Enable Corepack + run: corepack enable + + - name: ⬢ Set up Node.js + uses: actions/setup-node@v4 with: - node-version: 16 - - run: yarn install + node-version: 20 + + - name: 🐈 Yarn install working-directory: ./tasks/check + run: yarn install --inline-builds + env: + GITHUB_TOKEN: ${{ github.token }} - name: ✅ Check constraints, dependencies, and package.json's uses: ./tasks/check - check-docs: - needs: only-doc-changes - if: needs.only-doc-changes.outputs.only-doc-changes == 'true' + check-skip: + needs: detect-changes + if: needs.detect-changes.outputs.onlydocs == 'true' + name: ✅ Check constraints, dependencies, and package.json's runs-on: ubuntu-latest + steps: - - run: echo "Only doc changes" + - run: echo "Skipped" build-lint-test: needs: check + strategy: matrix: os: [ubuntu-latest, windows-latest] - node-version: [16, 18] - fail-fast: true - name: 🏗 Build, lint, test / ${{ matrix.os }} / node ${{ matrix.node-version }} latest + + name: 🏗 Build, lint, test / ${{ matrix.os }} / node 18 latest runs-on: ${{ matrix.os }} + steps: - - name: Remove the tsc problem matcher if not ubuntu-latest, node 16 - if: (matrix.os == 'ubuntu-latest' && matrix.node-version == '16') == false + - name: Remove the tsc problem matcher if not ubuntu-latest + if: matrix.os != 'ubuntu-latest' run: echo "echo "::remove-matcher owner=tsc::"" - - uses: actions/checkout@v3 - - name: 🧶 Set up job - uses: ./.github/actions/set-up-job + - uses: actions/checkout@v4 + + - name: Enable Corepack + run: corepack enable + + - name: ⬢ Set up Node.js + uses: actions/setup-node@v4 with: - node-version: ${{ matrix.node-version }} + node-version: 20 + + - name: Enable Corepack + run: corepack enable + + - name: 🐈 Set up yarn cache + uses: ./.github/actions/set-up-yarn-cache + + - name: 🐈 Yarn install + run: yarn install --inline-builds + env: + GITHUB_TOKEN: ${{ github.token }} - name: 🔨 Build run: yarn build @@ -83,41 +125,59 @@ jobs: - name: 🔎 Lint run: yarn lint + - name: 🌡 Test Types + run: yarn test:types + - name: Get number of CPU cores if: always() id: cpu-cores - uses: SimenB/github-actions-cpu-cores@v1 + uses: SimenB/github-actions-cpu-cores@v2 - name: 🧪 Test run: yarn test-ci ${{ steps.cpu-cores.outputs.count }} - build-lint-test-docs: - needs: only-doc-changes - if: needs.only-doc-changes.outputs.only-doc-changes == 'true' + build-lint-test-skip: + needs: detect-changes + if: needs.detect-changes.outputs.onlydocs == 'true' + strategy: matrix: os: [ubuntu-latest, windows-latest] - node-version: [16, 18] - name: 🏗 Build, lint, test / ${{ matrix.os }} / node ${{ matrix.node-version }} latest + + name: 🏗 Build, lint, test / ${{ matrix.os }} / node 18 latest runs-on: ${{ matrix.os }} + steps: - - run: echo "Only doc changes" + - run: echo "Skipped" tutorial-e2e: needs: check + strategy: matrix: - os: [ubuntu-latest] - node-version: [16, 18] - fail-fast: true - name: 🌲 Tutorial E2E / ${{ matrix.os }} / node ${{ matrix.node-version }} latest - runs-on: ${{ matrix.os }} + bundler: [vite, webpack] + + name: 🌲 Tutorial E2E / ${{ matrix.bundler }} / node 18 latest + runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v3 - - name: 🧶 Set up job - uses: ./.github/actions/set-up-job + - uses: actions/checkout@v4 + + - name: Enable Corepack + run: corepack enable + + - name: ⬢ Set up Node.js + uses: actions/setup-node@v4 with: - node-version: ${{ matrix.node-version }} + node-version: 20 + + - name: 🐈 Set up yarn cache + uses: ./.github/actions/set-up-yarn-cache + + - name: 🐈 Yarn install + run: yarn install --inline-builds + env: + GITHUB_TOKEN: ${{ github.token }} - name: 📁 Create a temporary directory id: createpath @@ -128,7 +188,10 @@ jobs: echo "::set-output name=framework_path::$framework_path" - name: 🌲 Create a Redwood App - run: ./tasks/run-e2e ${{ steps.createpath.outputs.project_path }} --no-start + run: | + ./tasks/run-e2e ${{ steps.createpath.outputs.project_path }} \ + --no-start \ + --bundler ${{ matrix.bundler }} env: YARN_ENABLE_IMMUTABLE_INSTALLS: false @@ -144,6 +207,9 @@ jobs: run: yarn rw dev --no-generate --fwd="--no-open" & working-directory: ${{ steps.createpath.outputs.project_path }} + - name: 🌲 Install Cypress + run: yarn run cypress install + - name: 🌲 Run cypress uses: cypress-io/github-action@v5 env: @@ -155,48 +221,71 @@ jobs: env: true browser: chrome record: false - wait-on: ${{ matrix.node-version == 18 && 'http://[::1]:8910' || 'http://localhost:8910' }} + wait-on: 'http://[::1]:8910' working-directory: ./tasks/e2e spec: | cypress/e2e/01-tutorial/*.cy.js cypress/e2e/04-logger/*.cy.js - tutorial-e2e-docs: - needs: only-doc-changes - if: needs.only-doc-changes.outputs.only-doc-changes == 'true' + tutorial-e2e-skip: + needs: detect-changes + if: needs.detect-changes.outputs.onlydocs == 'true' + strategy: matrix: - os: [ubuntu-latest] - node-version: [16, 18] - name: 🌲 Tutorial E2E / ${{ matrix.os }} / node ${{ matrix.node-version }} latest - runs-on: ${{ matrix.os }} + bundler: [vite, webpack] + + name: 🌲 Tutorial E2E / ${{ matrix.bundler }} / node 18 latest + runs-on: ubuntu-latest + steps: - - run: echo "Only doc changes" + - run: echo "Skipped" - smoke-test: + smoke-tests: needs: check + strategy: matrix: os: [ubuntu-latest, windows-latest] - node-version: [16, 18] - fail-fast: true - name: 👀 Smoke test / ${{ matrix.os }} / node ${{ matrix.node-version }} latest + bundler: [vite, webpack] + + name: 🔄 Smoke tests / ${{ matrix.os }} / ${{ matrix.bundler }} / node 18 latest runs-on: ${{ matrix.os }} + env: REDWOOD_CI: 1 REDWOOD_VERBOSE_TELEMETRY: 1 - # This makes sure that playwright dependencies are cached in node_modules. - PLAYWRIGHT_BROWSERS_PATH: 0 + steps: - - uses: actions/checkout@v3 - - name: 🧶 Set up job - uses: ./.github/actions/set-up-job + - uses: actions/checkout@v4 + + - name: Enable Corepack + run: corepack enable + + - name: ⬢ Set up Node.js + uses: actions/setup-node@v4 with: - node-version: ${{ matrix.node-version }} + node-version: 20 - - name: 🌲 Setup test project - id: setup_test_project - uses: ./.github/actions/setup_test_project + - name: Enable Corepack + run: corepack enable + + - name: 🐈 Set up yarn cache + uses: ./.github/actions/set-up-yarn-cache + + - name: 🐈 Yarn install + run: yarn install --inline-builds + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: 🔨 Build + run: yarn build + + - name: 🌲 Set up test project + id: set-up-test-project + uses: ./.github/actions/set-up-test-project + with: + bundler: ${{ matrix.bundler }} env: REDWOOD_DISABLE_TELEMETRY: 1 YARN_ENABLE_IMMUTABLE_INSTALLS: false @@ -204,164 +293,411 @@ jobs: - name: 🎭 Install playwright dependencies run: npx playwright install --with-deps chromium - - name: Run `rw build` without prerender + - name: 🧑‍💻 Run dev smoke tests + working-directory: ./tasks/smoke-tests/dev + run: npx playwright test + env: + REDWOOD_TEST_PROJECT_PATH: '${{ steps.set-up-test-project.outputs.test-project-path }}' + REDWOOD_DISABLE_TELEMETRY: 1 + + - name: 🔐 Run auth smoke tests + working-directory: ./tasks/smoke-tests/auth + run: npx playwright test + env: + REDWOOD_TEST_PROJECT_PATH: ${{ steps.set-up-test-project.outputs.test-project-path }} + REDWOOD_DISABLE_TELEMETRY: 1 + + - name: Run `rw build --no-prerender` run: | yarn rw build --no-prerender - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} - name: Run `rw prerender` run: | yarn rw prerender --verbose - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} + + - name: 🖥️ Run serve smoke tests + working-directory: tasks/smoke-tests/serve + run: npx playwright test + env: + REDWOOD_TEST_PROJECT_PATH: ${{ steps.set-up-test-project.outputs.test-project-path }} + REDWOOD_DISABLE_TELEMETRY: 1 + + - name: 📄 Run prerender smoke tests + working-directory: tasks/smoke-tests/prerender + run: npx playwright test + env: + REDWOOD_TEST_PROJECT_PATH: ${{ steps.set-up-test-project.outputs.test-project-path }} + REDWOOD_DISABLE_TELEMETRY: 1 - - name: Run smoke tests on 'rw dev', 'rw serve', 'rw storybook' - working-directory: ./tasks/smoke-test - run: npx playwright test --project ${{ matrix.os == 'ubuntu-latest' && 'replay-chromium' || 'chromium' }} --reporter @replayio/playwright/reporter,line + - name: 📕 Run Storybook smoke tests + working-directory: tasks/smoke-tests/storybook + run: npx playwright test env: - PROJECT_PATH: ${{ steps.setup_test_project.outputs.test_project_path }} + REDWOOD_TEST_PROJECT_PATH: ${{ steps.set-up-test-project.outputs.test-project-path }} REDWOOD_DISABLE_TELEMETRY: 1 - RECORD_REPLAY_METADATA_TEST_RUN_TITLE: Smoke test / ${{ matrix.os }} / node ${{ matrix.node-version }} latest - RECORD_REPLAY_TEST_METRICS: 1 - name: Run `rw info` run: | yarn rw info - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} - name: Run `rw lint` run: | yarn rw lint ./api/src --fix - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} - name: Run "rw test api" run: | yarn rw test api --no-watch - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} - name: Run "rw test web" run: | yarn rw test web --no-watch - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} - name: Run "rw check" run: | yarn rw check - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} - name: Run "rw storybook" run: | yarn rw sb --smoke-test - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} - name: Run "rw exec" run: | yarn rw g script testScript && yarn rw exec testScript - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} - name: Run "prisma generate" run: | yarn rw prisma generate - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} - name: Run "rw data-migrate" run: | yarn rw dataMigrate up - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} - name: Run "data-migrate install" run: | yarn rw data-migrate install - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} - name: Run "prisma migrate" run: | yarn rw prisma migrate dev --name ci-test - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} - name: Run `rw deploy --help` run: yarn rw setup deploy --help && yarn rw deploy --help - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} - name: Run `rw setup ui --help` run: yarn rw setup --help && yarn rw setup ui --help - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} - name: Run "g page" run: | yarn rw g page ciTest - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} - name: Run "g sdl" run: | yarn rw g sdl userExample - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} - name: Run "rw type-check" run: | yarn rw type-check - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} - name: Throw Error | Run `rw g sdl ` run: | yarn rw g sdl DoesNotExist - working-directory: ${{ steps.setup_test_project.outputs.test_project_path }} + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} continue-on-error: true - - name: Upload Replays - if: always() - uses: replayio/action-upload@v0.4.7 - with: - api-key: rwk_cZn4WLe8106j6tC5ygNQxDpxAwCLpFo5oLQftiRN7OP + # We've disabled Replay for now but may add it back. When we do, + # we need to add this to all the smoke tests steps' env: + # + # ``` + # env: + # RECORD_REPLAY_METADATA_TEST_RUN_TITLE: 🔄 Smoke tests / ${{ matrix.os }} / node 18 latest + # RECORD_REPLAY_TEST_METRICS: 1 + # ``` + # + # - name: Upload Replays + # if: always() + # uses: replayio/action-upload@v0.5.0 + # with: + # api-key: rwk_cZn4WLe8106j6tC5ygNQxDpxAwCLpFo5oLQftiRN7OP + + smoke-tests-skip: + needs: detect-changes + if: needs.detect-changes.outputs.onlydocs == 'true' - smoke-test-docs: - needs: only-doc-changes - if: needs.only-doc-changes.outputs.only-doc-changes == 'true' strategy: matrix: os: [ubuntu-latest, windows-latest] - node-version: [16, 18] - name: 👀 Smoke test / ${{ matrix.os }} / node ${{ matrix.node-version }} latest + bundler: [vite, webpack] + + name: 🔄 Smoke tests / ${{ matrix.os }} / ${{ matrix.bundler }} / node 18 latest runs-on: ${{ matrix.os }} + steps: - - run: echo "Only doc changes" + - run: echo "Skipped" telemetry-check: needs: check + strategy: matrix: os: [ubuntu-latest, windows-latest] - node-version: [16, 18] - fail-fast: true - name: 🔭 Telemetry check / ${{ matrix.os }} / node ${{ matrix.node-version }} latest + + name: 🔭 Telemetry check / ${{ matrix.os }} / node 18 latest runs-on: ${{ matrix.os }} + env: REDWOOD_REDIRECT_TELEMETRY: "http://127.0.0.1:48619" # Random port + steps: - - uses: actions/checkout@v3 - - name: 🧶 Set up job - uses: ./.github/actions/set-up-job + - uses: actions/checkout@v4 + + - name: Enable Corepack + run: corepack enable + + - name: ⬢ Set up Node.js + uses: actions/setup-node@v4 with: - node-version: ${{ matrix.node-version }} + node-version: 20 + + - name: Enable Corepack + run: corepack enable + + - name: 🐈 Set up yarn cache + uses: ./.github/actions/set-up-yarn-cache + + - name: 🐈 Yarn install + run: yarn install --inline-builds + env: + GITHUB_TOKEN: ${{ github.token }} - name: 🔨 Build run: yarn build - name: 📢 Listen for telemetry (CRWA) run: node ./.github/actions/telemetry_check/check.mjs --mode crwa - env: - YARN_ENABLE_IMMUTABLE_INSTALLS: false - name: 📢 Listen for telemetry (CLI) run: node ./.github/actions/telemetry_check/check.mjs --mode cli + env: + YARN_ENABLE_IMMUTABLE_INSTALLS: false + + telemetry-check-skip: + needs: detect-changes + if: needs.detect-changes.outputs.onlydocs == 'true' + + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + + name: 🔭 Telemetry check / ${{ matrix.os }} / node 18 latest + runs-on: ${{ matrix.os }} + + steps: + - run: echo "Skipped" + + rsc-smoke-tests: + needs: [check, detect-changes] + if: needs.detect-changes.outputs.rsc == 'true' + + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + + name: 🔄🐘 RSC Smoke tests / ${{ matrix.os }} + runs-on: ${{ matrix.os }} + + env: + REDWOOD_CI: 1 + REDWOOD_VERBOSE_TELEMETRY: 1 + + steps: + - uses: actions/checkout@v4 + + - name: Enable Corepack + run: corepack enable + + - name: ⬢ Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Enable Corepack + run: corepack enable + + - name: 🐈 Set up yarn cache + uses: ./.github/actions/set-up-yarn-cache + + - name: 🐈 Yarn install + run: yarn install --inline-builds + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: 🔨 Build + run: yarn build + + - name: 🌲 Set up RSC project + id: set-up-rsc-project + uses: ./.github/actions/set-up-rsc-project + env: + REDWOOD_DISABLE_TELEMETRY: 1 + YARN_ENABLE_IMMUTABLE_INSTALLS: false + + - name: 🎭 Install playwright dependencies + run: npx playwright install --with-deps chromium + + - name: 🐘 Run RSC smoke tests + working-directory: tasks/smoke-tests/rsc + run: npx playwright test + env: + REDWOOD_TEST_PROJECT_PATH: ${{ steps.set-up-rsc-project.outputs.rsc-project-path }} + REDWOOD_DISABLE_TELEMETRY: 1 + + - name: 🌲 Set up RSA smoke test + id: set-up-rsa-project + uses: ./.github/actions/set-up-rsa-project + env: + REDWOOD_DISABLE_TELEMETRY: 1 + YARN_ENABLE_IMMUTABLE_INSTALLS: false + + - name: 🐘 Run RSA smoke tests + working-directory: tasks/smoke-tests/rsa + run: npx playwright test + env: + REDWOOD_TEST_PROJECT_PATH: ${{ steps.set-up-rsa-project.outputs.test-project-path }} + REDWOOD_DISABLE_TELEMETRY: 1 + + - name: 🌲 Set up RSC external packages smoke test + id: set-up-rsc-external-packages-project + uses: ./.github/actions/set-up-rsc-external-packages-project + env: + REDWOOD_DISABLE_TELEMETRY: 1 + YARN_ENABLE_IMMUTABLE_INSTALLS: false + + - name: 🐘 Run RSC external packages smoke tests + working-directory: tasks/smoke-tests/rsc-external-packages + run: npx playwright test + env: + REDWOOD_TEST_PROJECT_PATH: ${{ steps.set-up-rsc-external-packages-project.outputs.test-project-path }} + REDWOOD_DISABLE_TELEMETRY: 1 + + rsc-smoke-tests-skip: + needs: detect-changes + if: needs.detect-changes.outputs.rsc == 'false' - telemetry-check-docs: - needs: only-doc-changes - if: needs.only-doc-changes.outputs.only-doc-changes == 'true' strategy: matrix: os: [ubuntu-latest, windows-latest] - node-version: [16, 18] - name: 🔭 Telemetry check / ${{ matrix.os }} / node ${{ matrix.node-version }} latest + + name: 🔄🐘 RSC Smoke tests / ${{ matrix.os }} runs-on: ${{ matrix.os }} + + steps: + - run: echo "Skipped" + + ssr-smoke-tests: + needs: [check, detect-changes] + if: needs.detect-changes.outputs.ssr == 'true' + + strategy: + matrix: + # TODO: add `windows-latest`. + os: [ubuntu-latest] + + name: 🔁 SSR Smoke tests / ${{ matrix.os }} + runs-on: ${{ matrix.os }} + + env: + REDWOOD_CI: 1 + REDWOOD_VERBOSE_TELEMETRY: 1 + + steps: + - uses: actions/checkout@v4 + + - name: Enable Corepack + run: corepack enable + + - name: ⬢ Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: 🐈 Set up yarn cache + uses: ./.github/actions/set-up-yarn-cache + + - name: 🐈 Yarn install + run: yarn install --inline-builds + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: 🔨 Build + run: yarn build + + - name: 🌲 Set up test project + id: set-up-test-project + uses: ./.github/actions/set-up-test-project + with: + bundler: vite + canary: true + env: + REDWOOD_DISABLE_TELEMETRY: 1 + YARN_ENABLE_IMMUTABLE_INSTALLS: false + + - name: Run SSR codemods on test project + run: ./tasks/test-project/convert-to-ssr-fixture ${{ steps.set-up-test-project.outputs.test-project-path }} + env: + REDWOOD_DISABLE_TELEMETRY: 1 + + - name: 🎭 Install playwright dependencies + run: npx playwright install --with-deps chromium + + - name: Run SSR [DEV] smoke tests + working-directory: ./tasks/smoke-tests/streaming-ssr-dev + run: npx playwright test + env: + REDWOOD_TEST_PROJECT_PATH: '${{ steps.set-up-test-project.outputs.test-project-path }}' + REDWOOD_DISABLE_TELEMETRY: 1 + + - name: Build for production + working-directory: ${{ steps.set-up-test-project.outputs.test-project-path }} + run: yarn rw build --no-prerender + env: + REDWOOD_DISABLE_TELEMETRY: 1 + + - name: Run SSR [PROD] smoke tests + working-directory: ./tasks/smoke-tests/streaming-ssr-prod + run: npx playwright test + env: + REDWOOD_TEST_PROJECT_PATH: '${{ steps.set-up-test-project.outputs.test-project-path }}' + REDWOOD_DISABLE_TELEMETRY: 1 + + ssr-smoke-tests-skip: + needs: detect-changes + if: needs.detect-changes.outputs.ssr == 'false' + + strategy: + matrix: + # TODO: add `windows-latest`. + os: [ubuntu-latest] + + name: 🔁 SSR Smoke tests / ${{ matrix.os }} + runs-on: ${{ matrix.os }} + steps: - - run: echo "Only doc changes" + - run: echo "Skipped" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 09e7c1b9b231..2af57bf1c982 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -41,11 +41,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} config-file: ./.github/codeql/codeql-config.yml @@ -57,7 +57,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -71,4 +71,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/monthly_issue_metrics_json.yml b/.github/workflows/monthly_issue_metrics_json.yml new file mode 100644 index 000000000000..cfd3be71eacd --- /dev/null +++ b/.github/workflows/monthly_issue_metrics_json.yml @@ -0,0 +1,39 @@ +name: Monthly issue metrics with JSON output +on: + workflow_dispatch: + schedule: + # 3:04 AM on the 1st day of every month + - cron: '4 3 1 * *' + +permissions: + issues: write + pull-requests: read + +jobs: + build: + name: monthly issue metrics (json) + runs-on: ubuntu-latest + + steps: + - name: Get dates for last month + shell: bash + run: | + # Calculate the first day of the previous month + first_day=$(date -d "last month" +%Y-%m-01) + + # Calculate the last day of the previous month + last_day=$(date -d "$first_day +1 month -1 day" +%Y-%m-%d) + + #Set an environment variable with the date range + echo "$first_day..$last_day" + echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV" + + - name: Run issue-metrics tool + id: issue-metrics + uses: github/issue-metrics@v2 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SEARCH_QUERY: 'repo:redwoodjs/redwood is:issue created:${{ env.last_month }} -reason:"not planned"' + + - name: Print output of issue metrics tool + run: echo "${{ steps.issue-metrics.outputs.metrics }}" diff --git a/.github/workflows/publish-canary.yml b/.github/workflows/publish-canary.yml index beb0b2ba735e..862707ef9d61 100644 --- a/.github/workflows/publish-canary.yml +++ b/.github/workflows/publish-canary.yml @@ -22,14 +22,27 @@ jobs: outputs: version: ${{ steps.get-version.outputs.value }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # `fetch-depth`—number of commits to fetch. `0` fetches all history for all branches and tags. # This is required because lerna uses tags to determine the version. with: fetch-depth: 0 - - name: 🧶 Set up job - uses: ./.github/actions/set-up-job + - name: Enable Corepack + run: corepack enable + + - name: ⬢ Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: 🐈 Set up yarn cache + uses: ./.github/actions/set-up-yarn-cache + + - name: 🐈 Yarn install + run: yarn install --inline-builds + env: + GITHUB_TOKEN: ${{ github.token }} - name: ✅ Check constraints, dependencies, and package.json's uses: ./tasks/check @@ -44,32 +57,7 @@ jobs: run: yarn test - name: 🚢 Publish - run: | - echo "//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}" > .npmrc - - TAG='canary' && [[ "$GITHUB_REF_NAME" = 'next' ]] && TAG='next' - echo "Publishing $TAG" - - args=() - - if [[ "$GITHUB_REF_NAME" = 'main' ]]; then - args+=(premajor) - fi - - args+=( - --include-merged-tags - --canary - --exact - --preid "$TAG" - --dist-tag "$TAG" - --force-publish - --loglevel verbose - --no-git-reset - --yes - ) - - yarn lerna publish "${args[@]}" - + run: ./.github/scripts/publish_canary.sh env: NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} @@ -86,7 +74,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: 💬 Message Slack uses: ./.github/actions/message_slack_publishing with: diff --git a/.github/workflows/publish-release-candidate.yml b/.github/workflows/publish-release-candidate.yml index c63f0c721a04..a6fb174f204f 100644 --- a/.github/workflows/publish-release-candidate.yml +++ b/.github/workflows/publish-release-candidate.yml @@ -20,15 +20,18 @@ jobs: if: github.repository == 'redwoodjs/redwood' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Required because lerna uses tags to determine the version. with: fetch-depth: 0 - - name: Setup node - uses: actions/setup-node@v3 + - name: Enable Corepack + run: corepack enable + + - name: ⬢ Set up Node.js + uses: actions/setup-node@v4 with: - node-version: 16 + node-version: 20 - name: 🏷 Check git tags run: | @@ -56,14 +59,27 @@ jobs: outputs: version: ${{ steps.get-version.outputs.value }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: # `fetch-depth`—number of commits to fetch. `0` fetches all history for all branches and tags. # This is required because lerna uses tags to determine the version. fetch-depth: 0 - - name: 🧶 Set up job - uses: ./.github/actions/set-up-job + - name: Enable Corepack + run: corepack enable + + - name: ⬢ Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: 🐈 Set up yarn cache + uses: ./.github/actions/set-up-yarn-cache + + - name: 🐈 Yarn install + run: yarn install --inline-builds + env: + GITHUB_TOKEN: ${{ github.token }} - name: ✅ Check constraints, dependencies, and package.json's uses: ./tasks/check @@ -106,7 +122,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: 💬 Message Slack uses: ./.github/actions/message_slack_publishing with: diff --git a/.github/workflows/require-milestone.yml b/.github/workflows/require-milestone.yml new file mode 100644 index 000000000000..052a0f029d48 --- /dev/null +++ b/.github/workflows/require-milestone.yml @@ -0,0 +1,27 @@ +name: 🚩 Require milestone + +on: + pull_request: + types: [opened, synchronize, reopened, milestoned, demilestoned] + +# Cancel in-progress runs of this workflow. +# See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-only-cancel-in-progress-jobs-or-runs-for-the-current-workflow. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + require-milestone: + name: 🚩 Require milestone + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: ⬢ Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: ✅ Check milestone + uses: ./.github/actions/require-milestone diff --git a/.github/workflows/require-release-label.yml b/.github/workflows/require-release-label.yml index 60e0bd86e38a..63a2cc0a99e2 100644 --- a/.github/workflows/require-release-label.yml +++ b/.github/workflows/require-release-label.yml @@ -15,8 +15,8 @@ jobs: name: 🏷 Require release label runs-on: ubuntu-latest steps: - - uses: mheap/github-action-required-labels@v3 + - uses: mheap/github-action-required-labels@v5 with: mode: exactly count: 1 - labels: "release:docs, release:chore, release:fix, release:feature, release:feature-breaking" + labels: "release:docs, release:chore, release:experiment, release:fix, release:feature, release:breaking" diff --git a/.github/workflows/update-all-contributors.yml b/.github/workflows/update-all-contributors.yml index cc0d4e990ffa..63317934a946 100644 --- a/.github/workflows/update-all-contributors.yml +++ b/.github/workflows/update-all-contributors.yml @@ -18,13 +18,16 @@ jobs: name: Update all contributors runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: token: ${{ secrets.JTOAR_TOKEN }} - - uses: actions/setup-node@v3 + - name: Enable Corepack + run: corepack enable + + - uses: actions/setup-node@v4 with: - node-version: 16 + node-version: 20 - run: yarn install diff --git a/.github/workflows/weekly_issue_metrics_json.yml b/.github/workflows/weekly_issue_metrics_json.yml new file mode 100644 index 000000000000..da0a8bd6fc27 --- /dev/null +++ b/.github/workflows/weekly_issue_metrics_json.yml @@ -0,0 +1,41 @@ +name: Weekly issue metrics with JSON output +on: + workflow_dispatch: + schedule: + # 2:33 AM every Monday + - cron: '33 2 * * 1' + +permissions: + issues: write + pull-requests: read + +jobs: + build: + name: weekly issue metrics json + runs-on: ubuntu-latest + steps: + - name: Get dates for last week + shell: bash + run: | + # Calculate the first day of the previous week (and as we all know + # weeks start on Mondays ;)) + first_day=$(date -d "last Sunday - 6 days" +%Y-%m-%d) + + # Calculate the last day of the previous week + last_day=$(date -d "last Sunday" +%Y-%m-%d) + + #Set an environment variable with the date range + echo "$first_day..$last_day" + echo "last_week=$first_day..$last_day" >> "$GITHUB_ENV" + + - name: Run issue-metrics tool + uses: github/issue-metrics@v2 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SEARCH_QUERY: 'repo:redwoodjs/redwood is:issue created:${{ env.last_week }} -reason:"not planned"' + + - name: Print output of issue metrics tool + run: | + cat ./issue_metrics.json + cat ./issue_metrics.json | jq .total_item_count + cat ./issue_metrics.json | jq .average_time_to_first_response diff --git a/.gitignore b/.gitignore index 64a1d279ec12..abfd3ba30b5d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.icloud +.cosine .idea .DS_Store node_modules @@ -8,7 +9,6 @@ yarn-error.log **/*.tsbuildinfo tasks/.verdaccio tasks/e2e/cypress/fixtures/example.json -tasks/release/*.md tmp/ blog-test-project/* .yarn/* @@ -21,3 +21,9 @@ blog-test-project/* .pnp.* *.code-workspace .nova + +# For esbuild. +**/meta.json +**/meta.*.json + +.nx/cache diff --git a/.gitpod.yml b/.gitpod.yml index 5452362c8349..7efea74f68a5 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -7,27 +7,27 @@ tasks: openMode: split-left before: | export RWFW_PATH="/workspace/redwood" - export RWJS_DEV_API_URL="http://localhost" init: | mkdir /workspace/rw-test-app command: | cd /workspace/rw-test-app - echo -e "\n\n\033[94m ======================================================" && echo -e "\n\033[33m ⌛ Please wait until the dev server is running on the right-side terminal. \n "rw-test-app" is being generated & linked with latest framework code. \n\nIf you make further changes to the framework..." && echo -e "1. \033[33mEnsure env vars are set \033[92m'export RWFW_PATH="/workspace/redwood" RWJS_DEV_API_URL="http://localhost"'\033[33m" && echo -e "2. \033[33mRun \033[92m'yarn rwfw project:sync'\033[33m to watch & sync changes into the test project" && echo -e "\n\033[94m ======================================================\n\n" + echo -e "\n\n\033[94m ======================================================" && echo -e "\n\033[33m ⌛ Please wait until the dev server is running on the right-side terminal. \n "rw-test-app" is being generated & linked with latest framework code. \n\nIf you make further changes to the framework..." && echo -e "1. \033[33mEnsure env vars are set \033[92m'export RWFW_PATH="/workspace/redwood"'\033[33m" && echo -e "2. \033[33mRun \033[92m'yarn rwfw project:sync'\033[33m to watch & sync changes into the test project" && echo -e "\n\033[94m ======================================================\n\n" - name: "Dev Servers" openMode: split-right before: | export RWFW_PATH="/workspace/redwood" - export RWJS_DEV_API_URL="http://localhost" export REDWOOD_DISABLE_TELEMETRY=1 init: | cd /workspace/redwood + corepack enable yarn install yarn run build:test-project ../rw-test-app --typescript --link --verbose + cd /workspace/rw-test-app && sed -i "s/\(open *= *\).*/\1false/" redwood.toml command: | cd /workspace/rw-test-app - yarn rw dev --fwd="--client-web-socket-url=ws$(gp url 8910 | cut -c 5-)/ws" + yarn rw dev ports: diff --git a/.prettierignore b/.prettierignore index b33452924a44..2b1ef87f61bf 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,5 @@ # Do not format Markdown files to allow easier documentation contribution *.md + +/.nx/cache diff --git a/.vscode/settings.json b/.vscode/settings.json index 9a5deb77495c..fe09e853a9bc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,7 @@ "files.trimTrailingWhitespace": true, "editor.formatOnSave": false, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "workbench.colorCustomizations": { "statusBar.background": "#b85833", @@ -16,6 +16,9 @@ "**/__fixtures__": true, ".yarn-packages-cache": true }, + "[markdown][html][mjml]": { + "files.trimTrailingWhitespace": false + }, "typescript.tsdk": "node_modules/typescript/lib", "peacock.color": "#b85833" } diff --git a/.yarn/plugins/@yarnpkg/plugin-constraints.cjs b/.yarn/plugins/@yarnpkg/plugin-constraints.cjs deleted file mode 100644 index 7a2139dcfaa4..000000000000 --- a/.yarn/plugins/@yarnpkg/plugin-constraints.cjs +++ /dev/null @@ -1,52 +0,0 @@ -/* eslint-disable */ -//prettier-ignore -module.exports = { -name: "@yarnpkg/plugin-constraints", -factory: function (require) { -var plugin=(()=>{var Li=Object.create,Je=Object.defineProperty;var Hi=Object.getOwnPropertyDescriptor;var Gi=Object.getOwnPropertyNames;var Yi=Object.getPrototypeOf,Ui=Object.prototype.hasOwnProperty;var Zi=r=>Je(r,"__esModule",{value:!0});var I=(r,u)=>()=>(u||r((u={exports:{}}).exports,u),u.exports),Qi=(r,u)=>{for(var p in u)Je(r,p,{get:u[p],enumerable:!0})},Ji=(r,u,p)=>{if(u&&typeof u=="object"||typeof u=="function")for(let c of Gi(u))!Ui.call(r,c)&&c!=="default"&&Je(r,c,{get:()=>u[c],enumerable:!(p=Hi(u,c))||p.enumerable});return r},G=r=>Ji(Zi(Je(r!=null?Li(Yi(r)):{},"default",r&&r.__esModule&&"default"in r?{get:()=>r.default,enumerable:!0}:{value:r,enumerable:!0})),r);var Xr=I((Nu,_r)=>{var Ki;(function(r){var u=function(){return{"append/2":[new r.type.Rule(new r.type.Term("append",[new r.type.Var("X"),new r.type.Var("L")]),new r.type.Term("foldl",[new r.type.Term("append",[]),new r.type.Var("X"),new r.type.Term("[]",[]),new r.type.Var("L")]))],"append/3":[new r.type.Rule(new r.type.Term("append",[new r.type.Term("[]",[]),new r.type.Var("X"),new r.type.Var("X")]),null),new r.type.Rule(new r.type.Term("append",[new r.type.Term(".",[new r.type.Var("H"),new r.type.Var("T")]),new r.type.Var("X"),new r.type.Term(".",[new r.type.Var("H"),new r.type.Var("S")])]),new r.type.Term("append",[new r.type.Var("T"),new r.type.Var("X"),new r.type.Var("S")]))],"member/2":[new r.type.Rule(new r.type.Term("member",[new r.type.Var("X"),new r.type.Term(".",[new r.type.Var("X"),new r.type.Var("_")])]),null),new r.type.Rule(new r.type.Term("member",[new r.type.Var("X"),new r.type.Term(".",[new r.type.Var("_"),new r.type.Var("Xs")])]),new r.type.Term("member",[new r.type.Var("X"),new r.type.Var("Xs")]))],"permutation/2":[new r.type.Rule(new r.type.Term("permutation",[new r.type.Term("[]",[]),new r.type.Term("[]",[])]),null),new r.type.Rule(new r.type.Term("permutation",[new r.type.Term(".",[new r.type.Var("H"),new r.type.Var("T")]),new r.type.Var("S")]),new r.type.Term(",",[new r.type.Term("permutation",[new r.type.Var("T"),new r.type.Var("P")]),new r.type.Term(",",[new r.type.Term("append",[new r.type.Var("X"),new r.type.Var("Y"),new r.type.Var("P")]),new r.type.Term("append",[new r.type.Var("X"),new r.type.Term(".",[new r.type.Var("H"),new r.type.Var("Y")]),new r.type.Var("S")])])]))],"maplist/2":[new r.type.Rule(new r.type.Term("maplist",[new r.type.Var("_"),new r.type.Term("[]",[])]),null),new r.type.Rule(new r.type.Term("maplist",[new r.type.Var("P"),new r.type.Term(".",[new r.type.Var("X"),new r.type.Var("Xs")])]),new r.type.Term(",",[new r.type.Term("call",[new r.type.Var("P"),new r.type.Var("X")]),new r.type.Term("maplist",[new r.type.Var("P"),new r.type.Var("Xs")])]))],"maplist/3":[new r.type.Rule(new r.type.Term("maplist",[new r.type.Var("_"),new r.type.Term("[]",[]),new r.type.Term("[]",[])]),null),new r.type.Rule(new r.type.Term("maplist",[new r.type.Var("P"),new r.type.Term(".",[new r.type.Var("A"),new r.type.Var("As")]),new r.type.Term(".",[new r.type.Var("B"),new r.type.Var("Bs")])]),new r.type.Term(",",[new r.type.Term("call",[new r.type.Var("P"),new r.type.Var("A"),new r.type.Var("B")]),new r.type.Term("maplist",[new r.type.Var("P"),new r.type.Var("As"),new r.type.Var("Bs")])]))],"maplist/4":[new r.type.Rule(new r.type.Term("maplist",[new r.type.Var("_"),new r.type.Term("[]",[]),new r.type.Term("[]",[]),new r.type.Term("[]",[])]),null),new r.type.Rule(new r.type.Term("maplist",[new r.type.Var("P"),new r.type.Term(".",[new r.type.Var("A"),new r.type.Var("As")]),new r.type.Term(".",[new r.type.Var("B"),new r.type.Var("Bs")]),new r.type.Term(".",[new r.type.Var("C"),new r.type.Var("Cs")])]),new r.type.Term(",",[new r.type.Term("call",[new r.type.Var("P"),new r.type.Var("A"),new r.type.Var("B"),new r.type.Var("C")]),new r.type.Term("maplist",[new r.type.Var("P"),new r.type.Var("As"),new r.type.Var("Bs"),new r.type.Var("Cs")])]))],"maplist/5":[new r.type.Rule(new r.type.Term("maplist",[new r.type.Var("_"),new r.type.Term("[]",[]),new r.type.Term("[]",[]),new r.type.Term("[]",[]),new r.type.Term("[]",[])]),null),new r.type.Rule(new r.type.Term("maplist",[new r.type.Var("P"),new r.type.Term(".",[new r.type.Var("A"),new r.type.Var("As")]),new r.type.Term(".",[new r.type.Var("B"),new r.type.Var("Bs")]),new r.type.Term(".",[new r.type.Var("C"),new r.type.Var("Cs")]),new r.type.Term(".",[new r.type.Var("D"),new r.type.Var("Ds")])]),new r.type.Term(",",[new r.type.Term("call",[new r.type.Var("P"),new r.type.Var("A"),new r.type.Var("B"),new r.type.Var("C"),new r.type.Var("D")]),new r.type.Term("maplist",[new r.type.Var("P"),new r.type.Var("As"),new r.type.Var("Bs"),new r.type.Var("Cs"),new r.type.Var("Ds")])]))],"maplist/6":[new r.type.Rule(new r.type.Term("maplist",[new r.type.Var("_"),new r.type.Term("[]",[]),new r.type.Term("[]",[]),new r.type.Term("[]",[]),new r.type.Term("[]",[]),new r.type.Term("[]",[])]),null),new r.type.Rule(new r.type.Term("maplist",[new r.type.Var("P"),new r.type.Term(".",[new r.type.Var("A"),new r.type.Var("As")]),new r.type.Term(".",[new r.type.Var("B"),new r.type.Var("Bs")]),new r.type.Term(".",[new r.type.Var("C"),new r.type.Var("Cs")]),new r.type.Term(".",[new r.type.Var("D"),new r.type.Var("Ds")]),new r.type.Term(".",[new r.type.Var("E"),new r.type.Var("Es")])]),new r.type.Term(",",[new r.type.Term("call",[new r.type.Var("P"),new r.type.Var("A"),new r.type.Var("B"),new r.type.Var("C"),new r.type.Var("D"),new r.type.Var("E")]),new r.type.Term("maplist",[new r.type.Var("P"),new r.type.Var("As"),new r.type.Var("Bs"),new r.type.Var("Cs"),new r.type.Var("Ds"),new r.type.Var("Es")])]))],"maplist/7":[new r.type.Rule(new r.type.Term("maplist",[new r.type.Var("_"),new r.type.Term("[]",[]),new r.type.Term("[]",[]),new r.type.Term("[]",[]),new r.type.Term("[]",[]),new r.type.Term("[]",[]),new r.type.Term("[]",[])]),null),new r.type.Rule(new r.type.Term("maplist",[new r.type.Var("P"),new r.type.Term(".",[new r.type.Var("A"),new r.type.Var("As")]),new r.type.Term(".",[new r.type.Var("B"),new r.type.Var("Bs")]),new r.type.Term(".",[new r.type.Var("C"),new r.type.Var("Cs")]),new r.type.Term(".",[new r.type.Var("D"),new r.type.Var("Ds")]),new r.type.Term(".",[new r.type.Var("E"),new r.type.Var("Es")]),new r.type.Term(".",[new r.type.Var("F"),new r.type.Var("Fs")])]),new r.type.Term(",",[new r.type.Term("call",[new r.type.Var("P"),new r.type.Var("A"),new r.type.Var("B"),new r.type.Var("C"),new r.type.Var("D"),new r.type.Var("E"),new r.type.Var("F")]),new r.type.Term("maplist",[new r.type.Var("P"),new r.type.Var("As"),new r.type.Var("Bs"),new r.type.Var("Cs"),new r.type.Var("Ds"),new r.type.Var("Es"),new r.type.Var("Fs")])]))],"maplist/8":[new r.type.Rule(new r.type.Term("maplist",[new r.type.Var("_"),new r.type.Term("[]",[]),new r.type.Term("[]",[]),new r.type.Term("[]",[]),new r.type.Term("[]",[]),new r.type.Term("[]",[]),new r.type.Term("[]",[]),new r.type.Term("[]",[])]),null),new r.type.Rule(new r.type.Term("maplist",[new r.type.Var("P"),new r.type.Term(".",[new r.type.Var("A"),new r.type.Var("As")]),new r.type.Term(".",[new r.type.Var("B"),new r.type.Var("Bs")]),new r.type.Term(".",[new r.type.Var("C"),new r.type.Var("Cs")]),new r.type.Term(".",[new r.type.Var("D"),new r.type.Var("Ds")]),new r.type.Term(".",[new r.type.Var("E"),new r.type.Var("Es")]),new r.type.Term(".",[new r.type.Var("F"),new r.type.Var("Fs")]),new r.type.Term(".",[new r.type.Var("G"),new r.type.Var("Gs")])]),new r.type.Term(",",[new r.type.Term("call",[new r.type.Var("P"),new r.type.Var("A"),new r.type.Var("B"),new r.type.Var("C"),new r.type.Var("D"),new r.type.Var("E"),new r.type.Var("F"),new r.type.Var("G")]),new r.type.Term("maplist",[new r.type.Var("P"),new r.type.Var("As"),new r.type.Var("Bs"),new r.type.Var("Cs"),new r.type.Var("Ds"),new r.type.Var("Es"),new r.type.Var("Fs"),new r.type.Var("Gs")])]))],"include/3":[new r.type.Rule(new r.type.Term("include",[new r.type.Var("_"),new r.type.Term("[]",[]),new r.type.Term("[]",[])]),null),new r.type.Rule(new r.type.Term("include",[new r.type.Var("P"),new r.type.Term(".",[new r.type.Var("H"),new r.type.Var("T")]),new r.type.Var("L")]),new r.type.Term(",",[new r.type.Term("=..",[new r.type.Var("P"),new r.type.Var("A")]),new r.type.Term(",",[new r.type.Term("append",[new r.type.Var("A"),new r.type.Term(".",[new r.type.Var("H"),new r.type.Term("[]",[])]),new r.type.Var("B")]),new r.type.Term(",",[new r.type.Term("=..",[new r.type.Var("F"),new r.type.Var("B")]),new r.type.Term(",",[new r.type.Term(";",[new r.type.Term(",",[new r.type.Term("call",[new r.type.Var("F")]),new r.type.Term(",",[new r.type.Term("=",[new r.type.Var("L"),new r.type.Term(".",[new r.type.Var("H"),new r.type.Var("S")])]),new r.type.Term("!",[])])]),new r.type.Term("=",[new r.type.Var("L"),new r.type.Var("S")])]),new r.type.Term("include",[new r.type.Var("P"),new r.type.Var("T"),new r.type.Var("S")])])])])]))],"exclude/3":[new r.type.Rule(new r.type.Term("exclude",[new r.type.Var("_"),new r.type.Term("[]",[]),new r.type.Term("[]",[])]),null),new r.type.Rule(new r.type.Term("exclude",[new r.type.Var("P"),new r.type.Term(".",[new r.type.Var("H"),new r.type.Var("T")]),new r.type.Var("S")]),new r.type.Term(",",[new r.type.Term("exclude",[new r.type.Var("P"),new r.type.Var("T"),new r.type.Var("E")]),new r.type.Term(",",[new r.type.Term("=..",[new r.type.Var("P"),new r.type.Var("L")]),new r.type.Term(",",[new r.type.Term("append",[new r.type.Var("L"),new r.type.Term(".",[new r.type.Var("H"),new r.type.Term("[]",[])]),new r.type.Var("Q")]),new r.type.Term(",",[new r.type.Term("=..",[new r.type.Var("R"),new r.type.Var("Q")]),new r.type.Term(";",[new r.type.Term(",",[new r.type.Term("call",[new r.type.Var("R")]),new r.type.Term(",",[new r.type.Term("!",[]),new r.type.Term("=",[new r.type.Var("S"),new r.type.Var("E")])])]),new r.type.Term("=",[new r.type.Var("S"),new r.type.Term(".",[new r.type.Var("H"),new r.type.Var("E")])])])])])])]))],"foldl/4":[new r.type.Rule(new r.type.Term("foldl",[new r.type.Var("_"),new r.type.Term("[]",[]),new r.type.Var("I"),new r.type.Var("I")]),null),new r.type.Rule(new r.type.Term("foldl",[new r.type.Var("P"),new r.type.Term(".",[new r.type.Var("H"),new r.type.Var("T")]),new r.type.Var("I"),new r.type.Var("R")]),new r.type.Term(",",[new r.type.Term("=..",[new r.type.Var("P"),new r.type.Var("L")]),new r.type.Term(",",[new r.type.Term("append",[new r.type.Var("L"),new r.type.Term(".",[new r.type.Var("I"),new r.type.Term(".",[new r.type.Var("H"),new r.type.Term(".",[new r.type.Var("X"),new r.type.Term("[]",[])])])]),new r.type.Var("L2")]),new r.type.Term(",",[new r.type.Term("=..",[new r.type.Var("P2"),new r.type.Var("L2")]),new r.type.Term(",",[new r.type.Term("call",[new r.type.Var("P2")]),new r.type.Term("foldl",[new r.type.Var("P"),new r.type.Var("T"),new r.type.Var("X"),new r.type.Var("R")])])])])]))],"select/3":[new r.type.Rule(new r.type.Term("select",[new r.type.Var("E"),new r.type.Term(".",[new r.type.Var("E"),new r.type.Var("Xs")]),new r.type.Var("Xs")]),null),new r.type.Rule(new r.type.Term("select",[new r.type.Var("E"),new r.type.Term(".",[new r.type.Var("X"),new r.type.Var("Xs")]),new r.type.Term(".",[new r.type.Var("X"),new r.type.Var("Ys")])]),new r.type.Term("select",[new r.type.Var("E"),new r.type.Var("Xs"),new r.type.Var("Ys")]))],"sum_list/2":[new r.type.Rule(new r.type.Term("sum_list",[new r.type.Term("[]",[]),new r.type.Num(0,!1)]),null),new r.type.Rule(new r.type.Term("sum_list",[new r.type.Term(".",[new r.type.Var("X"),new r.type.Var("Xs")]),new r.type.Var("S")]),new r.type.Term(",",[new r.type.Term("sum_list",[new r.type.Var("Xs"),new r.type.Var("Y")]),new r.type.Term("is",[new r.type.Var("S"),new r.type.Term("+",[new r.type.Var("X"),new r.type.Var("Y")])])]))],"max_list/2":[new r.type.Rule(new r.type.Term("max_list",[new r.type.Term(".",[new r.type.Var("X"),new r.type.Term("[]",[])]),new r.type.Var("X")]),null),new r.type.Rule(new r.type.Term("max_list",[new r.type.Term(".",[new r.type.Var("X"),new r.type.Var("Xs")]),new r.type.Var("S")]),new r.type.Term(",",[new r.type.Term("max_list",[new r.type.Var("Xs"),new r.type.Var("Y")]),new r.type.Term(";",[new r.type.Term(",",[new r.type.Term(">=",[new r.type.Var("X"),new r.type.Var("Y")]),new r.type.Term(",",[new r.type.Term("=",[new r.type.Var("S"),new r.type.Var("X")]),new r.type.Term("!",[])])]),new r.type.Term("=",[new r.type.Var("S"),new r.type.Var("Y")])])]))],"min_list/2":[new r.type.Rule(new r.type.Term("min_list",[new r.type.Term(".",[new r.type.Var("X"),new r.type.Term("[]",[])]),new r.type.Var("X")]),null),new r.type.Rule(new r.type.Term("min_list",[new r.type.Term(".",[new r.type.Var("X"),new r.type.Var("Xs")]),new r.type.Var("S")]),new r.type.Term(",",[new r.type.Term("min_list",[new r.type.Var("Xs"),new r.type.Var("Y")]),new r.type.Term(";",[new r.type.Term(",",[new r.type.Term("=<",[new r.type.Var("X"),new r.type.Var("Y")]),new r.type.Term(",",[new r.type.Term("=",[new r.type.Var("S"),new r.type.Var("X")]),new r.type.Term("!",[])])]),new r.type.Term("=",[new r.type.Var("S"),new r.type.Var("Y")])])]))],"prod_list/2":[new r.type.Rule(new r.type.Term("prod_list",[new r.type.Term("[]",[]),new r.type.Num(1,!1)]),null),new r.type.Rule(new r.type.Term("prod_list",[new r.type.Term(".",[new r.type.Var("X"),new r.type.Var("Xs")]),new r.type.Var("S")]),new r.type.Term(",",[new r.type.Term("prod_list",[new r.type.Var("Xs"),new r.type.Var("Y")]),new r.type.Term("is",[new r.type.Var("S"),new r.type.Term("*",[new r.type.Var("X"),new r.type.Var("Y")])])]))],"last/2":[new r.type.Rule(new r.type.Term("last",[new r.type.Term(".",[new r.type.Var("X"),new r.type.Term("[]",[])]),new r.type.Var("X")]),null),new r.type.Rule(new r.type.Term("last",[new r.type.Term(".",[new r.type.Var("_"),new r.type.Var("Xs")]),new r.type.Var("X")]),new r.type.Term("last",[new r.type.Var("Xs"),new r.type.Var("X")]))],"prefix/2":[new r.type.Rule(new r.type.Term("prefix",[new r.type.Var("Part"),new r.type.Var("Whole")]),new r.type.Term("append",[new r.type.Var("Part"),new r.type.Var("_"),new r.type.Var("Whole")]))],"nth0/3":[new r.type.Rule(new r.type.Term("nth0",[new r.type.Var("X"),new r.type.Var("Y"),new r.type.Var("Z")]),new r.type.Term(";",[new r.type.Term("->",[new r.type.Term("var",[new r.type.Var("X")]),new r.type.Term("nth",[new r.type.Num(0,!1),new r.type.Var("X"),new r.type.Var("Y"),new r.type.Var("Z"),new r.type.Var("_")])]),new r.type.Term(",",[new r.type.Term(">=",[new r.type.Var("X"),new r.type.Num(0,!1)]),new r.type.Term(",",[new r.type.Term("nth",[new r.type.Num(0,!1),new r.type.Var("X"),new r.type.Var("Y"),new r.type.Var("Z"),new r.type.Var("_")]),new r.type.Term("!",[])])])]))],"nth1/3":[new r.type.Rule(new r.type.Term("nth1",[new r.type.Var("X"),new r.type.Var("Y"),new r.type.Var("Z")]),new r.type.Term(";",[new r.type.Term("->",[new r.type.Term("var",[new r.type.Var("X")]),new r.type.Term("nth",[new r.type.Num(1,!1),new r.type.Var("X"),new r.type.Var("Y"),new r.type.Var("Z"),new r.type.Var("_")])]),new r.type.Term(",",[new r.type.Term(">",[new r.type.Var("X"),new r.type.Num(0,!1)]),new r.type.Term(",",[new r.type.Term("nth",[new r.type.Num(1,!1),new r.type.Var("X"),new r.type.Var("Y"),new r.type.Var("Z"),new r.type.Var("_")]),new r.type.Term("!",[])])])]))],"nth0/4":[new r.type.Rule(new r.type.Term("nth0",[new r.type.Var("X"),new r.type.Var("Y"),new r.type.Var("Z"),new r.type.Var("W")]),new r.type.Term(";",[new r.type.Term("->",[new r.type.Term("var",[new r.type.Var("X")]),new r.type.Term("nth",[new r.type.Num(0,!1),new r.type.Var("X"),new r.type.Var("Y"),new r.type.Var("Z"),new r.type.Var("W")])]),new r.type.Term(",",[new r.type.Term(">=",[new r.type.Var("X"),new r.type.Num(0,!1)]),new r.type.Term(",",[new r.type.Term("nth",[new r.type.Num(0,!1),new r.type.Var("X"),new r.type.Var("Y"),new r.type.Var("Z"),new r.type.Var("W")]),new r.type.Term("!",[])])])]))],"nth1/4":[new r.type.Rule(new r.type.Term("nth1",[new r.type.Var("X"),new r.type.Var("Y"),new r.type.Var("Z"),new r.type.Var("W")]),new r.type.Term(";",[new r.type.Term("->",[new r.type.Term("var",[new r.type.Var("X")]),new r.type.Term("nth",[new r.type.Num(1,!1),new r.type.Var("X"),new r.type.Var("Y"),new r.type.Var("Z"),new r.type.Var("W")])]),new r.type.Term(",",[new r.type.Term(">",[new r.type.Var("X"),new r.type.Num(0,!1)]),new r.type.Term(",",[new r.type.Term("nth",[new r.type.Num(1,!1),new r.type.Var("X"),new r.type.Var("Y"),new r.type.Var("Z"),new r.type.Var("W")]),new r.type.Term("!",[])])])]))],"nth/5":[new r.type.Rule(new r.type.Term("nth",[new r.type.Var("N"),new r.type.Var("N"),new r.type.Term(".",[new r.type.Var("X"),new r.type.Var("Xs")]),new r.type.Var("X"),new r.type.Var("Xs")]),null),new r.type.Rule(new r.type.Term("nth",[new r.type.Var("N"),new r.type.Var("O"),new r.type.Term(".",[new r.type.Var("X"),new r.type.Var("Xs")]),new r.type.Var("Y"),new r.type.Term(".",[new r.type.Var("X"),new r.type.Var("Ys")])]),new r.type.Term(",",[new r.type.Term("is",[new r.type.Var("M"),new r.type.Term("+",[new r.type.Var("N"),new r.type.Num(1,!1)])]),new r.type.Term("nth",[new r.type.Var("M"),new r.type.Var("O"),new r.type.Var("Xs"),new r.type.Var("Y"),new r.type.Var("Ys")])]))],"length/2":function(c,w,_){var v=_.args[0],g=_.args[1];if(!r.type.is_variable(g)&&!r.type.is_integer(g))c.throw_error(r.error.type("integer",g,_.indicator));else if(r.type.is_integer(g)&&g.value<0)c.throw_error(r.error.domain("not_less_than_zero",g,_.indicator));else{var h=new r.type.Term("length",[v,new r.type.Num(0,!1),g]);r.type.is_integer(g)&&(h=new r.type.Term(",",[h,new r.type.Term("!",[])])),c.prepend([new r.type.State(w.goal.replace(h),w.substitution,w)])}},"length/3":[new r.type.Rule(new r.type.Term("length",[new r.type.Term("[]",[]),new r.type.Var("N"),new r.type.Var("N")]),null),new r.type.Rule(new r.type.Term("length",[new r.type.Term(".",[new r.type.Var("_"),new r.type.Var("X")]),new r.type.Var("A"),new r.type.Var("N")]),new r.type.Term(",",[new r.type.Term("succ",[new r.type.Var("A"),new r.type.Var("B")]),new r.type.Term("length",[new r.type.Var("X"),new r.type.Var("B"),new r.type.Var("N")])]))],"replicate/3":function(c,w,_){var v=_.args[0],g=_.args[1],h=_.args[2];if(r.type.is_variable(g))c.throw_error(r.error.instantiation(_.indicator));else if(!r.type.is_integer(g))c.throw_error(r.error.type("integer",g,_.indicator));else if(g.value<0)c.throw_error(r.error.domain("not_less_than_zero",g,_.indicator));else if(!r.type.is_variable(h)&&!r.type.is_list(h))c.throw_error(r.error.type("list",h,_.indicator));else{for(var x=new r.type.Term("[]"),T=0;T0;b--)T[b].equals(T[b-1])&&T.splice(b,1);for(var C=new r.type.Term("[]"),b=T.length-1;b>=0;b--)C=new r.type.Term(".",[T[b],C]);c.prepend([new r.type.State(w.goal.replace(new r.type.Term("=",[C,g])),w.substitution,w)])}}},"msort/2":function(c,w,_){var v=_.args[0],g=_.args[1];if(r.type.is_variable(v))c.throw_error(r.error.instantiation(_.indicator));else if(!r.type.is_variable(g)&&!r.type.is_fully_list(g))c.throw_error(r.error.type("list",g,_.indicator));else{for(var h=[],x=v;x.indicator==="./2";)h.push(x.args[0]),x=x.args[1];if(r.type.is_variable(x))c.throw_error(r.error.instantiation(_.indicator));else if(!r.type.is_empty_list(x))c.throw_error(r.error.type("list",v,_.indicator));else{for(var T=h.sort(r.compare),b=new r.type.Term("[]"),C=T.length-1;C>=0;C--)b=new r.type.Term(".",[T[C],b]);c.prepend([new r.type.State(w.goal.replace(new r.type.Term("=",[b,g])),w.substitution,w)])}}},"keysort/2":function(c,w,_){var v=_.args[0],g=_.args[1];if(r.type.is_variable(v))c.throw_error(r.error.instantiation(_.indicator));else if(!r.type.is_variable(g)&&!r.type.is_fully_list(g))c.throw_error(r.error.type("list",g,_.indicator));else{for(var h=[],x,T=v;T.indicator==="./2";){if(x=T.args[0],r.type.is_variable(x)){c.throw_error(r.error.instantiation(_.indicator));return}else if(!r.type.is_term(x)||x.indicator!=="-/2"){c.throw_error(r.error.type("pair",x,_.indicator));return}x.args[0].pair=x.args[1],h.push(x.args[0]),T=T.args[1]}if(r.type.is_variable(T))c.throw_error(r.error.instantiation(_.indicator));else if(!r.type.is_empty_list(T))c.throw_error(r.error.type("list",v,_.indicator));else{for(var b=h.sort(r.compare),C=new r.type.Term("[]"),N=b.length-1;N>=0;N--)C=new r.type.Term(".",[new r.type.Term("-",[b[N],b[N].pair]),C]),delete b[N].pair;c.prepend([new r.type.State(w.goal.replace(new r.type.Term("=",[C,g])),w.substitution,w)])}}},"take/3":function(c,w,_){var v=_.args[0],g=_.args[1],h=_.args[2];if(r.type.is_variable(g)||r.type.is_variable(v))c.throw_error(r.error.instantiation(_.indicator));else if(!r.type.is_list(g))c.throw_error(r.error.type("list",g,_.indicator));else if(!r.type.is_integer(v))c.throw_error(r.error.type("integer",v,_.indicator));else if(!r.type.is_variable(h)&&!r.type.is_list(h))c.throw_error(r.error.type("list",h,_.indicator));else{for(var x=v.value,T=[],b=g;x>0&&b.indicator==="./2";)T.push(b.args[0]),b=b.args[1],x--;if(x===0){for(var C=new r.type.Term("[]"),x=T.length-1;x>=0;x--)C=new r.type.Term(".",[T[x],C]);c.prepend([new r.type.State(w.goal.replace(new r.type.Term("=",[C,h])),w.substitution,w)])}}},"drop/3":function(c,w,_){var v=_.args[0],g=_.args[1],h=_.args[2];if(r.type.is_variable(g)||r.type.is_variable(v))c.throw_error(r.error.instantiation(_.indicator));else if(!r.type.is_list(g))c.throw_error(r.error.type("list",g,_.indicator));else if(!r.type.is_integer(v))c.throw_error(r.error.type("integer",v,_.indicator));else if(!r.type.is_variable(h)&&!r.type.is_list(h))c.throw_error(r.error.type("list",h,_.indicator));else{for(var x=v.value,T=[],b=g;x>0&&b.indicator==="./2";)T.push(b.args[0]),b=b.args[1],x--;x===0&&c.prepend([new r.type.State(w.goal.replace(new r.type.Term("=",[b,h])),w.substitution,w)])}},"reverse/2":function(c,w,_){var v=_.args[0],g=_.args[1],h=r.type.is_instantiated_list(v),x=r.type.is_instantiated_list(g);if(r.type.is_variable(v)&&r.type.is_variable(g))c.throw_error(r.error.instantiation(_.indicator));else if(!r.type.is_variable(v)&&!r.type.is_fully_list(v))c.throw_error(r.error.type("list",v,_.indicator));else if(!r.type.is_variable(g)&&!r.type.is_fully_list(g))c.throw_error(r.error.type("list",g,_.indicator));else if(!h&&!x)c.throw_error(r.error.instantiation(_.indicator));else{for(var T=h?v:g,b=new r.type.Term("[]",[]);T.indicator==="./2";)b=new r.type.Term(".",[T.args[0],b]),T=T.args[1];c.prepend([new r.type.State(w.goal.replace(new r.type.Term("=",[b,h?g:v])),w.substitution,w)])}},"list_to_set/2":function(c,w,_){var v=_.args[0],g=_.args[1];if(r.type.is_variable(v))c.throw_error(r.error.instantiation(_.indicator));else{for(var h=v,x=[];h.indicator==="./2";)x.push(h.args[0]),h=h.args[1];if(r.type.is_variable(h))c.throw_error(r.error.instantiation(_.indicator));else if(!r.type.is_term(h)||h.indicator!=="[]/0")c.throw_error(r.error.type("list",v,_.indicator));else{for(var T=[],b=new r.type.Term("[]",[]),C,N=0;N=0;N--)b=new r.type.Term(".",[T[N],b]);c.prepend([new r.type.State(w.goal.replace(new r.type.Term("=",[g,b])),w.substitution,w)])}}}}},p=["append/2","append/3","member/2","permutation/2","maplist/2","maplist/3","maplist/4","maplist/5","maplist/6","maplist/7","maplist/8","include/3","exclude/3","foldl/4","sum_list/2","max_list/2","min_list/2","prod_list/2","last/2","prefix/2","nth0/3","nth1/3","nth0/4","nth1/4","length/2","replicate/3","select/3","sort/2","msort/2","keysort/2","take/3","drop/3","reverse/2","list_to_set/2"];typeof _r!="undefined"?_r.exports=function(c){r=c,new r.type.Module("lists",u(),p)}:new r.type.Module("lists",u(),p)})(Ki)});var et=I(M=>{"use strict";var Ve=process.platform==="win32",wr="aes-256-cbc",ji="sha256",Br="The current environment doesn't support interactive reading from TTY.",z=require("fs"),Fr=process.binding("tty_wrap").TTY,gr=require("child_process"),ye=require("path"),dr={prompt:"> ",hideEchoBack:!1,mask:"*",limit:[],limitMessage:"Input another, please.$<( [)limit(])>",defaultInput:"",trueValue:[],falseValue:[],caseSensitive:!1,keepWhitespace:!1,encoding:"utf8",bufferSize:1024,print:void 0,history:!0,cd:!1,phContent:void 0,preCheck:void 0},ce="none",oe,Ce,zr=!1,_e,Ke,vr,es=0,hr="",Se=[],je,Wr=!1,mr=!1,qe=!1;function Lr(r){function u(p){return p.replace(/[^\w\u0080-\uFFFF]/g,function(c){return"#"+c.charCodeAt(0)+";"})}return Ke.concat(function(p){var c=[];return Object.keys(p).forEach(function(w){p[w]==="boolean"?r[w]&&c.push("--"+w):p[w]==="string"&&r[w]&&c.push("--"+w,u(r[w]))}),c}({display:"string",displayOnly:"boolean",keyIn:"boolean",hideEchoBack:"boolean",mask:"string",limit:"string",caseSensitive:"boolean"}))}function rs(r,u){function p(K){var U,Ue="",Ze;for(vr=vr||require("os").tmpdir();;){U=ye.join(vr,K+Ue);try{Ze=z.openSync(U,"wx")}catch(Qe){if(Qe.code==="EEXIST"){Ue++;continue}else throw Qe}z.closeSync(Ze);break}return U}var c,w,_,v={},g,h,x=p("readline-sync.stdout"),T=p("readline-sync.stderr"),b=p("readline-sync.exit"),C=p("readline-sync.done"),N=require("crypto"),W,ee,te;W=N.createHash(ji),W.update(""+process.pid+es+++Math.random()),te=W.digest("hex"),ee=N.createDecipher(wr,te),c=Lr(r),Ve?(w=process.env.ComSpec||"cmd.exe",process.env.Q='"',_=["/V:ON","/S","/C","(%Q%"+w+"%Q% /V:ON /S /C %Q%%Q%"+_e+"%Q%"+c.map(function(K){return" %Q%"+K+"%Q%"}).join("")+" & (echo !ERRORLEVEL!)>%Q%"+b+"%Q%%Q%) 2>%Q%"+T+"%Q% |%Q%"+process.execPath+"%Q% %Q%"+__dirname+"\\encrypt.js%Q% %Q%"+wr+"%Q% %Q%"+te+"%Q% >%Q%"+x+"%Q% & (echo 1)>%Q%"+C+"%Q%"]):(w="/bin/sh",_=["-c",'("'+_e+'"'+c.map(function(K){return" '"+K.replace(/'/g,"'\\''")+"'"}).join("")+'; echo $?>"'+b+'") 2>"'+T+'" |"'+process.execPath+'" "'+__dirname+'/encrypt.js" "'+wr+'" "'+te+'" >"'+x+'"; echo 1 >"'+C+'"']),qe&&qe("_execFileSync",c);try{gr.spawn(w,_,u)}catch(K){v.error=new Error(K.message),v.error.method="_execFileSync - spawn",v.error.program=w,v.error.args=_}for(;z.readFileSync(C,{encoding:r.encoding}).trim()!=="1";);return(g=z.readFileSync(b,{encoding:r.encoding}).trim())==="0"?v.input=ee.update(z.readFileSync(x,{encoding:"binary"}),"hex",r.encoding)+ee.final(r.encoding):(h=z.readFileSync(T,{encoding:r.encoding}).trim(),v.error=new Error(Br+(h?` -`+h:"")),v.error.method="_execFileSync",v.error.program=w,v.error.args=_,v.error.extMessage=h,v.error.exitCode=+g),z.unlinkSync(x),z.unlinkSync(T),z.unlinkSync(b),z.unlinkSync(C),v}function ts(r){var u,p={},c,w={env:process.env,encoding:r.encoding};if(_e||(Ve?process.env.PSModulePath?(_e="powershell.exe",Ke=["-ExecutionPolicy","Bypass","-File",__dirname+"\\read.ps1"]):(_e="cscript.exe",Ke=["//nologo",__dirname+"\\read.cs.js"]):(_e="/bin/sh",Ke=[__dirname+"/read.sh"])),Ve&&!process.env.PSModulePath&&(w.stdio=[process.stdin]),gr.execFileSync){u=Lr(r),qe&&qe("execFileSync",u);try{p.input=gr.execFileSync(_e,u,w)}catch(_){c=_.stderr?(_.stderr+"").trim():"",p.error=new Error(Br+(c?` -`+c:"")),p.error.method="execFileSync",p.error.program=_e,p.error.args=u,p.error.extMessage=c,p.error.exitCode=_.status,p.error.code=_.code,p.error.signal=_.signal}}else p=rs(r,w);return p.error||(p.input=p.input.replace(/^\s*'|'\s*$/g,""),r.display=""),p}function br(r){var u="",p=r.display,c=!r.display&&r.keyIn&&r.hideEchoBack&&!r.mask;function w(){var _=ts(r);if(_.error)throw _.error;return _.input}return mr&&mr(r),function(){var _,v,g;function h(){return _||(_=process.binding("fs"),v=process.binding("constants")),_}if(typeof ce=="string")if(ce=null,Ve){if(g=function(x){var T=x.replace(/^\D+/,"").split("."),b=0;return(T[0]=+T[0])&&(b+=T[0]*1e4),(T[1]=+T[1])&&(b+=T[1]*100),(T[2]=+T[2])&&(b+=T[2]),b}(process.version),!(g>=20302&&g<40204||g>=5e4&&g<50100||g>=50600&&g<60200)&&process.stdin.isTTY)process.stdin.pause(),ce=process.stdin.fd,Ce=process.stdin._handle;else try{ce=h().open("CONIN$",v.O_RDWR,parseInt("0666",8)),Ce=new Fr(ce,!0)}catch(x){}if(process.stdout.isTTY)oe=process.stdout.fd;else{try{oe=z.openSync("\\\\.\\CON","w")}catch(x){}if(typeof oe!="number")try{oe=h().open("CONOUT$",v.O_RDWR,parseInt("0666",8))}catch(x){}}}else{if(process.stdin.isTTY){process.stdin.pause();try{ce=z.openSync("/dev/tty","r"),Ce=process.stdin._handle}catch(x){}}else try{ce=z.openSync("/dev/tty","r"),Ce=new Fr(ce,!1)}catch(x){}if(process.stdout.isTTY)oe=process.stdout.fd;else try{oe=z.openSync("/dev/tty","w")}catch(x){}}}(),function(){var _,v,g=!r.hideEchoBack&&!r.keyIn,h,x,T,b,C;je="";function N(W){return W===zr?!0:Ce.setRawMode(W)!==0?!1:(zr=W,!0)}if(Wr||!Ce||typeof oe!="number"&&(r.display||!g)){u=w();return}if(r.display&&(z.writeSync(oe,r.display),r.display=""),!r.displayOnly){if(!N(!g)){u=w();return}for(x=r.keyIn?1:r.bufferSize,h=Buffer.allocUnsafe&&Buffer.alloc?Buffer.alloc(x):new Buffer(x),r.keyIn&&r.limit&&(v=new RegExp("[^"+r.limit+"]","g"+(r.caseSensitive?"":"i")));;){T=0;try{T=z.readSync(ce,h,0,x)}catch(W){if(W.code!=="EOF"){N(!1),u+=w();return}}if(T>0?(b=h.toString(r.encoding,0,T),je+=b):(b=` -`,je+=String.fromCharCode(0)),b&&typeof(C=(b.match(/^(.*?)[\r\n]/)||[])[1])=="string"&&(b=C,_=!0),b&&(b=b.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g,"")),b&&v&&(b=b.replace(v,"")),b&&(g||(r.hideEchoBack?r.mask&&z.writeSync(oe,new Array(b.length+1).join(r.mask)):z.writeSync(oe,b)),u+=b),!r.keyIn&&_||r.keyIn&&u.length>=x)break}!g&&!c&&z.writeSync(oe,` -`),N(!1)}}(),r.print&&!c&&r.print(p+(r.displayOnly?"":(r.hideEchoBack?new Array(u.length+1).join(r.mask):u)+` -`),r.encoding),r.displayOnly?"":hr=r.keepWhitespace||r.keyIn?u:u.trim()}function ns(r,u){var p=[];function c(w){w!=null&&(Array.isArray(w)?w.forEach(c):(!u||u(w))&&p.push(w))}return c(r),p}function Tr(r){return r.replace(/[\x00-\x7f]/g,function(u){return"\\x"+("00"+u.charCodeAt().toString(16)).substr(-2)})}function Z(){var r=Array.prototype.slice.call(arguments),u,p;return r.length&&typeof r[0]=="boolean"&&(p=r.shift(),p&&(u=Object.keys(dr),r.unshift(dr))),r.reduce(function(c,w){return w==null||(w.hasOwnProperty("noEchoBack")&&!w.hasOwnProperty("hideEchoBack")&&(w.hideEchoBack=w.noEchoBack,delete w.noEchoBack),w.hasOwnProperty("noTrim")&&!w.hasOwnProperty("keepWhitespace")&&(w.keepWhitespace=w.noTrim,delete w.noTrim),p||(u=Object.keys(w)),u.forEach(function(_){var v;if(!!w.hasOwnProperty(_))switch(v=w[_],_){case"mask":case"limitMessage":case"defaultInput":case"encoding":v=v!=null?v+"":"",v&&_!=="limitMessage"&&(v=v.replace(/[\r\n]/g,"")),c[_]=v;break;case"bufferSize":!isNaN(v=parseInt(v,10))&&typeof v=="number"&&(c[_]=v);break;case"displayOnly":case"keyIn":case"hideEchoBack":case"caseSensitive":case"keepWhitespace":case"history":case"cd":c[_]=!!v;break;case"limit":case"trueValue":case"falseValue":c[_]=ns(v,function(g){var h=typeof g;return h==="string"||h==="number"||h==="function"||g instanceof RegExp}).map(function(g){return typeof g=="string"?g.replace(/[\r\n]/g,""):g});break;case"print":case"phContent":case"preCheck":c[_]=typeof v=="function"?v:void 0;break;case"prompt":case"display":c[_]=v!=null?v:"";break}})),c},{})}function xr(r,u,p){return u.some(function(c){var w=typeof c;return w==="string"?p?r===c:r.toLowerCase()===c.toLowerCase():w==="number"?parseFloat(r)===c:w==="function"?c(r):c instanceof RegExp?c.test(r):!1})}function Vr(r,u){var p=ye.normalize(Ve?(process.env.HOMEDRIVE||"")+(process.env.HOMEPATH||""):process.env.HOME||"").replace(/[\/\\]+$/,"");return r=ye.normalize(r),u?r.replace(/^~(?=\/|\\|$)/,p):r.replace(new RegExp("^"+Tr(p)+"(?=\\/|\\\\|$)",Ve?"i":""),"~")}function Oe(r,u){var p="(?:\\(([\\s\\S]*?)\\))?(\\w+|.-.)(?:\\(([\\s\\S]*?)\\))?",c=new RegExp("(\\$)?(\\$<"+p+">)","g"),w=new RegExp("(\\$)?(\\$\\{"+p+"\\})","g");function _(v,g,h,x,T,b){var C;return g||typeof(C=u(T))!="string"?h:C?(x||"")+C+(b||""):""}return r.replace(c,_).replace(w,_)}function Hr(r,u,p){var c,w=[],_=-1,v=0,g="",h;function x(T,b){return b.length>3?(T.push(b[0]+"..."+b[b.length-1]),h=!0):b.length&&(T=T.concat(b)),T}return c=r.reduce(function(T,b){return T.concat((b+"").split(""))},[]).reduce(function(T,b){var C,N;return u||(b=b.toLowerCase()),C=/^\d$/.test(b)?1:/^[A-Z]$/.test(b)?2:/^[a-z]$/.test(b)?3:0,p&&C===0?g+=b:(N=b.charCodeAt(0),C&&C===_&&N===v+1?w.push(b):(T=x(T,w),w=[b],_=C),v=N),T},[]),c=x(c,w),g&&(c.push(g),h=!0),{values:c,suppressed:h}}function Gr(r,u){return r.join(r.length>2?", ":u?" / ":"/")}function Yr(r,u){var p,c,w={},_;if(u.phContent&&(p=u.phContent(r,u)),typeof p!="string")switch(r){case"hideEchoBack":case"mask":case"defaultInput":case"caseSensitive":case"keepWhitespace":case"encoding":case"bufferSize":case"history":case"cd":p=u.hasOwnProperty(r)?typeof u[r]=="boolean"?u[r]?"on":"off":u[r]+"":"";break;case"limit":case"trueValue":case"falseValue":c=u[u.hasOwnProperty(r+"Src")?r+"Src":r],u.keyIn?(w=Hr(c,u.caseSensitive),c=w.values):c=c.filter(function(v){var g=typeof v;return g==="string"||g==="number"}),p=Gr(c,w.suppressed);break;case"limitCount":case"limitCountNotZero":p=u[u.hasOwnProperty("limitSrc")?"limitSrc":"limit"].length,p=p||r!=="limitCountNotZero"?p+"":"";break;case"lastInput":p=hr;break;case"cwd":case"CWD":case"cwdHome":p=process.cwd(),r==="CWD"?p=ye.basename(p):r==="cwdHome"&&(p=Vr(p));break;case"date":case"time":case"localeDate":case"localeTime":p=new Date()["to"+r.replace(/^./,function(v){return v.toUpperCase()})+"String"]();break;default:typeof(_=(r.match(/^history_m(\d+)$/)||[])[1])=="string"&&(p=Se[Se.length-_]||"")}return p}function Ur(r){var u=/^(.)-(.)$/.exec(r),p="",c,w,_,v;if(!u)return null;for(c=u[1].charCodeAt(0),w=u[2].charCodeAt(0),v=c -And the length must be: $`,trueValue:null,falseValue:null,caseSensitive:!0},u,{history:!1,cd:!1,phContent:function(N){return N==="charlist"?p.text:N==="length"?c+"..."+w:null}}),v,g,h,x,T,b,C;for(u=u||{},v=Oe(u.charlist?u.charlist+"":"$",Ur),(isNaN(c=parseInt(u.min,10))||typeof c!="number")&&(c=12),(isNaN(w=parseInt(u.max,10))||typeof w!="number")&&(w=24),x=new RegExp("^["+Tr(v)+"]{"+c+","+w+"}$"),p=Hr([v],_.caseSensitive,!0),p.text=Gr(p.values,p.suppressed),g=u.confirmMessage!=null?u.confirmMessage:"Reinput a same one to confirm it: ",h=u.unmatchMessage!=null?u.unmatchMessage:"It differs from first one. Hit only the Enter key if you want to retry from first one.",r==null&&(r="Input new password: "),T=_.limitMessage;!C;)_.limit=x,_.limitMessage=T,b=M.question(r,_),_.limit=[b,""],_.limitMessage=h,C=M.question(g,_);return b};function Jr(r,u,p){var c;function w(_){return c=p(_),!isNaN(c)&&typeof c=="number"}return M.question(r,Z({limitMessage:"Input valid number, please."},u,{limit:w,cd:!1})),c}M.questionInt=function(r,u){return Jr(r,u,function(p){return parseInt(p,10)})};M.questionFloat=function(r,u){return Jr(r,u,parseFloat)};M.questionPath=function(r,u){var p,c="",w=Z({hideEchoBack:!1,limitMessage:`$Input valid path, please.$<( Min:)min>$<( Max:)max>`,history:!0,cd:!0},u,{keepWhitespace:!1,limit:function(_){var v,g,h;_=Vr(_,!0),c="";function x(T){T.split(/\/|\\/).reduce(function(b,C){var N=ye.resolve(b+=C+ye.sep);if(!z.existsSync(N))z.mkdirSync(N);else if(!z.statSync(N).isDirectory())throw new Error("Non directory already exists: "+N);return b},"")}try{if(v=z.existsSync(_),p=v?z.realpathSync(_):ye.resolve(_),!u.hasOwnProperty("exists")&&!v||typeof u.exists=="boolean"&&u.exists!==v)return c=(v?"Already exists":"No such file or directory")+": "+p,!1;if(!v&&u.create&&(u.isDirectory?x(p):(x(ye.dirname(p)),z.closeSync(z.openSync(p,"w"))),p=z.realpathSync(p)),v&&(u.min||u.max||u.isFile||u.isDirectory)){if(g=z.statSync(p),u.isFile&&!g.isFile())return c="Not file: "+p,!1;if(u.isDirectory&&!g.isDirectory())return c="Not directory: "+p,!1;if(u.min&&g.size<+u.min||u.max&&g.size>+u.max)return c="Size "+g.size+" is out of range: "+p,!1}if(typeof u.validate=="function"&&(h=u.validate(p))!==!0)return typeof h=="string"&&(c=h),!1}catch(T){return c=T+"",!1}return!0},phContent:function(_){return _==="error"?c:_!=="min"&&_!=="max"?null:u.hasOwnProperty(_)?u[_]+"":""}});return u=u||{},r==null&&(r='Input path (you can "cd" and "pwd"): '),M.question(r,w),p};function Kr(r,u){var p={},c={};return typeof r=="object"?(Object.keys(r).forEach(function(w){typeof r[w]=="function"&&(c[u.caseSensitive?w:w.toLowerCase()]=r[w])}),p.preCheck=function(w){var _;return p.args=Sr(w),_=p.args[0]||"",u.caseSensitive||(_=_.toLowerCase()),p.hRes=_!=="_"&&c.hasOwnProperty(_)?c[_].apply(w,p.args.slice(1)):c.hasOwnProperty("_")?c._.apply(w,p.args):null,{res:w,forceNext:!1}},c.hasOwnProperty("_")||(p.limit=function(){var w=p.args[0]||"";return u.caseSensitive||(w=w.toLowerCase()),c.hasOwnProperty(w)})):p.preCheck=function(w){return p.args=Sr(w),p.hRes=typeof r=="function"?r.apply(w,p.args):!0,{res:w,forceNext:!1}},p}M.promptCL=function(r,u){var p=Z({hideEchoBack:!1,limitMessage:"Requested command is not available.",caseSensitive:!1,history:!0},u),c=Kr(r,p);return p.limit=c.limit,p.preCheck=c.preCheck,M.prompt(p),c.args};M.promptLoop=function(r,u){for(var p=Z({hideEchoBack:!1,trueValue:null,falseValue:null,caseSensitive:!1,history:!0},u);!r(M.prompt(p)););};M.promptCLLoop=function(r,u){var p=Z({hideEchoBack:!1,limitMessage:"Requested command is not available.",caseSensitive:!1,history:!0},u),c=Kr(r,p);for(p.limit=c.limit,p.preCheck=c.preCheck;M.prompt(p),!c.hRes;);};M.promptSimShell=function(r){return M.prompt(Z({hideEchoBack:!1,history:!0},r,{prompt:function(){return Ve?"$>":(process.env.USER||"")+(process.env.HOSTNAME?"@"+process.env.HOSTNAME.replace(/\..*$/,""):"")+":$$ "}()}))};function jr(r,u,p){var c;return r==null&&(r="Are you sure? "),(!u||u.guide!==!1)&&(r+="")&&(r=r.replace(/\s*:?\s*$/,"")+" [y/n]: "),c=M.keyIn(r,Z(u,{hideEchoBack:!1,limit:p,trueValue:"y",falseValue:"n",caseSensitive:!1})),typeof c=="boolean"?c:""}M.keyInYN=function(r,u){return jr(r,u)};M.keyInYNStrict=function(r,u){return jr(r,u,"yn")};M.keyInPause=function(r,u){r==null&&(r="Continue..."),(!u||u.guide!==!1)&&(r+="")&&(r=r.replace(/\s+$/,"")+" (Hit any key)"),M.keyIn(r,Z({limit:null},u,{hideEchoBack:!0,mask:""}))};M.keyInSelect=function(r,u,p){var c=Z({hideEchoBack:!1},p,{trueValue:null,falseValue:null,caseSensitive:!1,phContent:function(h){return h==="itemsCount"?r.length+"":h==="firstItem"?(r[0]+"").trim():h==="lastItem"?(r[r.length-1]+"").trim():null}}),w="",_={},v=49,g=` -`;if(!Array.isArray(r)||!r.length||r.length>35)throw"`items` must be Array (max length: 35).";return r.forEach(function(h,x){var T=String.fromCharCode(v);w+=T,_[T]=x,g+="["+T+"] "+(h+"").trim()+` -`,v=v===57?97:v+1}),(!p||p.cancel!==!1)&&(w+="0",_["0"]=-1,g+="[0] "+(p&&p.cancel!=null&&typeof p.cancel!="boolean"?(p.cancel+"").trim():"CANCEL")+` -`),c.limit=w,g+=` -`,u==null&&(u="Choose one from list: "),(u+="")&&((!p||p.guide!==!1)&&(u=u.replace(/\s*:?\s*$/,"")+" [$]: "),g+=u),_[M.keyIn(g,c).toLowerCase()]};M.getRawInput=function(){return je};function $e(r,u){var p;return u.length&&(p={},p[r]=u[0]),M.setDefaultOptions(p)[r]}M.setPrint=function(){return $e("print",arguments)};M.setPrompt=function(){return $e("prompt",arguments)};M.setEncoding=function(){return $e("encoding",arguments)};M.setMask=function(){return $e("mask",arguments)};M.setBufferSize=function(){return $e("bufferSize",arguments)}});var kr=I((Mu,ie)=>{(function(){var r={major:0,minor:2,patch:66,status:"beta"};tau_file_system={files:{},open:function(e,n,t){var s=tau_file_system.files[e];if(!s){if(t==="read")return null;s={path:e,text:"",type:n,get:function(a,l){return l===this.text.length||l>this.text.length?"end_of_file":this.text.substring(l,l+a)},put:function(a,l){return l==="end_of_file"?(this.text+=a,!0):l==="past_end_of_file"?null:(this.text=this.text.substring(0,l)+a+this.text.substring(l+a.length),!0)},get_byte:function(a){if(a==="end_of_stream")return-1;var l=Math.floor(a/2);if(this.text.length<=l)return-1;var f=_(this.text[Math.floor(a/2)],0);return a%2==0?f&255:f/256>>>0},put_byte:function(a,l){var f=l==="end_of_stream"?this.text.length:Math.floor(l/2);if(this.text.length>>0,y=(y&255)<<8|a&255):(y=y&255,y=(a&255)<<8|y&255),this.text.length===f?this.text+=v(y):this.text=this.text.substring(0,f)+v(y)+this.text.substring(f+1),!0},flush:function(){return!0},close:function(){var a=tau_file_system.files[this.path];return a?!0:null}},tau_file_system.files[e]=s}return t==="write"&&(s.text=""),s}},tau_user_input={buffer:"",get:function(e,n){for(var t;tau_user_input.buffer.length\?\@\^\~\\]+|'(?:[^']*?(?:\\(?:x?\d+)?\\)*(?:'')*(?:\\')*)*')/,number:/^(?:0o[0-7]+|0x[0-9a-fA-F]+|0b[01]+|0'(?:''|\\[abfnrtv\\'"`]|\\x?\d+\\|[^\\])|\d+(?:\.\d+(?:[eE][+-]?\d+)?)?)/,string:/^(?:"([^"]|""|\\")*"|`([^`]|``|\\`)*`)/,l_brace:/^(?:\[)/,r_brace:/^(?:\])/,l_bracket:/^(?:\{)/,r_bracket:/^(?:\})/,bar:/^(?:\|)/,l_paren:/^(?:\()/,r_paren:/^(?:\))/};function te(e,n){return e.get_flag("char_conversion").id==="on"?n.replace(/./g,function(t){return e.get_char_conversion(t)}):n}function K(e){this.thread=e,this.text="",this.tokens=[]}K.prototype.set_last_tokens=function(e){return this.tokens=e},K.prototype.new_text=function(e){this.text=e,this.tokens=[]},K.prototype.get_tokens=function(e){var n,t=0,s=0,a=0,l=[],f=!1;if(e){var y=this.tokens[e-1];t=y.len,n=te(this.thread,this.text.substr(y.len)),s=y.line,a=y.start}else n=this.text;if(/^\s*$/.test(n))return null;for(;n!=="";){var d=[],m=!1;if(/^\n/.exec(n)!==null){s++,a=0,t++,n=n.replace(/\n/,""),f=!0;continue}for(var S in ee)if(ee.hasOwnProperty(S)){var P=ee[S].exec(n);P&&d.push({value:P[0],name:S,matches:P})}if(!d.length)return this.set_last_tokens([{value:n,matches:[],name:"lexical",line:s,start:a}]);var y=p(d,function(B,q){return B.value.length>=q.value.length?B:q});switch(y.start=a,y.line=s,n=n.replace(y.value,""),a+=y.value.length,t+=y.value.length,y.name){case"atom":y.raw=y.value,y.value.charAt(0)==="'"&&(y.value=C(y.value.substr(1,y.value.length-2),"'"),y.value===null&&(y.name="lexical",y.value="unknown escape sequence"));break;case"number":y.float=y.value.substring(0,2)!=="0x"&&y.value.match(/[.eE]/)!==null&&y.value!=="0'.",y.value=W(y.value),y.blank=m;break;case"string":var A=y.value.charAt(0);y.value=C(y.value.substr(1,y.value.length-2),A),y.value===null&&(y.name="lexical",y.value="unknown escape sequence");break;case"whitespace":var R=l[l.length-1];R&&(R.space=!0),m=!0;continue;case"r_bracket":l.length>0&&l[l.length-1].name==="l_bracket"&&(y=l.pop(),y.name="atom",y.value="{}",y.raw="{}",y.space=!1);break;case"r_brace":l.length>0&&l[l.length-1].name==="l_brace"&&(y=l.pop(),y.name="atom",y.value="[]",y.raw="[]",y.space=!1);break}y.len=t,l.push(y),m=!1}var k=this.set_last_tokens(l);return k.length===0?null:k};function U(e,n,t,s,a){if(!n[t])return{type:g,value:i.error.syntax(n[t-1],"expression expected",!0)};var l;if(s==="0"){var f=n[t];switch(f.name){case"number":return{type:h,len:t+1,value:new i.type.Num(f.value,f.float)};case"variable":return{type:h,len:t+1,value:new i.type.Var(f.value)};case"string":var y;switch(e.get_flag("double_quotes").id){case"atom":y=new o(f.value,[]);break;case"codes":y=new o("[]",[]);for(var d=f.value.length-1;d>=0;d--)y=new o(".",[new i.type.Num(_(f.value,d),!1),y]);break;case"chars":y=new o("[]",[]);for(var d=f.value.length-1;d>=0;d--)y=new o(".",[new i.type.Term(f.value.charAt(d),[]),y]);break}return{type:h,len:t+1,value:y};case"l_paren":var k=U(e,n,t+1,e.__get_max_priority(),!0);return k.type!==h?k:n[k.len]&&n[k.len].name==="r_paren"?(k.len++,k):{type:g,derived:!0,value:i.error.syntax(n[k.len]?n[k.len]:n[k.len-1],") or operator expected",!n[k.len])};case"l_bracket":var k=U(e,n,t+1,e.__get_max_priority(),!0);return k.type!==h?k:n[k.len]&&n[k.len].name==="r_bracket"?(k.len++,k.value=new o("{}",[k.value]),k):{type:g,derived:!0,value:i.error.syntax(n[k.len]?n[k.len]:n[k.len-1],"} or operator expected",!n[k.len])}}var m=Ue(e,n,t,a);return m.type===h||m.derived||(m=Ze(e,n,t),m.type===h||m.derived)?m:{type:g,derived:!1,value:i.error.syntax(n[t],"unexpected token")}}var S=e.__get_max_priority(),P=e.__get_next_priority(s),A=t;if(n[t].name==="atom"&&n[t+1]&&(n[t].space||n[t+1].name!=="l_paren")){var f=n[t++],R=e.__lookup_operator_classes(s,f.value);if(R&&R.indexOf("fy")>-1){var k=U(e,n,t,s,a);if(k.type!==g)return f.value==="-"&&!f.space&&i.type.is_number(k.value)?{value:new i.type.Num(-k.value.value,k.value.is_float),len:k.len,type:h}:{value:new i.type.Term(f.value,[k.value]),len:k.len,type:h};l=k}else if(R&&R.indexOf("fx")>-1){var k=U(e,n,t,P,a);if(k.type!==g)return{value:new i.type.Term(f.value,[k.value]),len:k.len,type:h};l=k}}t=A;var k=U(e,n,t,P,a);if(k.type===h){t=k.len;var f=n[t];if(n[t]&&(n[t].name==="atom"&&e.__lookup_operator_classes(s,f.value)||n[t].name==="bar"&&e.__lookup_operator_classes(s,"|"))){var L=P,B=s,R=e.__lookup_operator_classes(s,f.value);if(R.indexOf("xf")>-1)return{value:new i.type.Term(f.value,[k.value]),len:++k.len,type:h};if(R.indexOf("xfx")>-1){var q=U(e,n,t+1,L,a);return q.type===h?{value:new i.type.Term(f.value,[k.value,q.value]),len:q.len,type:h}:(q.derived=!0,q)}else if(R.indexOf("xfy")>-1){var q=U(e,n,t+1,B,a);return q.type===h?{value:new i.type.Term(f.value,[k.value,q.value]),len:q.len,type:h}:(q.derived=!0,q)}else if(k.type!==g)for(;;){t=k.len;var f=n[t];if(f&&f.name==="atom"&&e.__lookup_operator_classes(s,f.value)){var R=e.__lookup_operator_classes(s,f.value);if(R.indexOf("yf")>-1)k={value:new i.type.Term(f.value,[k.value]),len:++t,type:h};else if(R.indexOf("yfx")>-1){var q=U(e,n,++t,L,a);if(q.type===g)return q.derived=!0,q;t=q.len,k={value:new i.type.Term(f.value,[k.value,q.value]),len:t,type:h}}else break}else break}}else l={type:g,value:i.error.syntax(n[k.len-1],"operator expected")};return k}return k}function Ue(e,n,t,s){if(!n[t]||n[t].name==="atom"&&n[t].raw==="."&&!s&&(n[t].space||!n[t+1]||n[t+1].name!=="l_paren"))return{type:g,derived:!1,value:i.error.syntax(n[t-1],"unfounded token")};var a=n[t],l=[];if(n[t].name==="atom"&&n[t].raw!==","){if(t++,n[t-1].space)return{type:h,len:t,value:new i.type.Term(a.value,l)};if(n[t]&&n[t].name==="l_paren"){if(n[t+1]&&n[t+1].name==="r_paren")return{type:g,derived:!0,value:i.error.syntax(n[t+1],"argument expected")};var f=U(e,n,++t,"999",!0);if(f.type===g)return f.derived?f:{type:g,derived:!0,value:i.error.syntax(n[t]?n[t]:n[t-1],"argument expected",!n[t])};for(l.push(f.value),t=f.len;n[t]&&n[t].name==="atom"&&n[t].value===",";){if(f=U(e,n,t+1,"999",!0),f.type===g)return f.derived?f:{type:g,derived:!0,value:i.error.syntax(n[t+1]?n[t+1]:n[t],"argument expected",!n[t+1])};l.push(f.value),t=f.len}if(n[t]&&n[t].name==="r_paren")t++;else return{type:g,derived:!0,value:i.error.syntax(n[t]?n[t]:n[t-1],", or ) expected",!n[t])}}return{type:h,len:t,value:new i.type.Term(a.value,l)}}return{type:g,derived:!1,value:i.error.syntax(n[t],"term expected")}}function Ze(e,n,t){if(!n[t])return{type:g,derived:!1,value:i.error.syntax(n[t-1],"[ expected")};if(n[t]&&n[t].name==="l_brace"){var s=U(e,n,++t,"999",!0),a=[s.value],l=void 0;if(s.type===g)return n[t]&&n[t].name==="r_brace"?{type:h,len:t+1,value:new i.type.Term("[]",[])}:{type:g,derived:!0,value:i.error.syntax(n[t],"] expected")};for(t=s.len;n[t]&&n[t].name==="atom"&&n[t].value===",";){if(s=U(e,n,t+1,"999",!0),s.type===g)return s.derived?s:{type:g,derived:!0,value:i.error.syntax(n[t+1]?n[t+1]:n[t],"argument expected",!n[t+1])};a.push(s.value),t=s.len}var f=!1;if(n[t]&&n[t].name==="bar"){if(f=!0,s=U(e,n,t+1,"999",!0),s.type===g)return s.derived?s:{type:g,derived:!0,value:i.error.syntax(n[t+1]?n[t+1]:n[t],"argument expected",!n[t+1])};l=s.value,t=s.len}return n[t]&&n[t].name==="r_brace"?{type:h,len:t+1,value:he(a,l)}:{type:g,derived:!0,value:i.error.syntax(n[t]?n[t]:n[t-1],f?"] expected":", or | or ] expected",!n[t])}}return{type:g,derived:!1,value:i.error.syntax(n[t],"list expected")}}function Qe(e,n,t){var s=n[t].line,a=U(e,n,t,e.__get_max_priority(),!1),l=null,f;if(a.type!==g)if(t=a.len,n[t]&&n[t].name==="atom"&&n[t].raw===".")if(t++,i.type.is_term(a.value)){if(a.value.indicator===":-/2"?(l=new i.type.Rule(a.value.args[0],ve(a.value.args[1])),f={value:l,len:t,type:h}):a.value.indicator==="-->/2"?(l=Bi(new i.type.Rule(a.value.args[0],a.value.args[1]),e),l.body=ve(l.body),f={value:l,len:t,type:i.type.is_rule(l)?h:g}):(l=new i.type.Rule(a.value,null),f={value:l,len:t,type:h}),l){var y=l.singleton_variables();y.length>0&&e.throw_warning(i.warning.singleton(y,l.head.indicator,s))}return f}else return{type:g,value:i.error.syntax(n[t],"callable expected")};else return{type:g,value:i.error.syntax(n[t]?n[t]:n[t-1],". or operator expected")};return a}function Di(e,n,t){t=t||{},t.from=t.from?t.from:"$tau-js",t.reconsult=t.reconsult!==void 0?t.reconsult:!0;var s=new K(e),a={},l;s.new_text(n);var f=0,y=s.get_tokens(f);do{if(y===null||!y[f])break;var d=Qe(e,y,f);if(d.type===g)return new o("throw",[d.value]);if(d.value.body===null&&d.value.head.indicator==="?-/1"){var m=new X(e.session);m.add_goal(d.value.head.args[0]),m.answer(function(P){i.type.is_error(P)?e.throw_warning(P.args[0]):(P===!1||P===null)&&e.throw_warning(i.warning.failed_goal(d.value.head.args[0],d.len))}),f=d.len;var S=!0}else if(d.value.body===null&&d.value.head.indicator===":-/1"){var S=e.run_directive(d.value.head.args[0]);f=d.len,d.value.head.args[0].indicator==="char_conversion/2"&&(y=s.get_tokens(f),f=0)}else{l=d.value.head.indicator,t.reconsult!==!1&&a[l]!==!0&&!e.is_multifile_predicate(l)&&(e.session.rules[l]=w(e.session.rules[l]||[],function(A){return A.dynamic}),a[l]=!0);var S=e.add_rule(d.value,t);f=d.len}if(!S)return S}while(!0);return!0}function Xi(e,n){var t=new K(e);t.new_text(n);var s=0;do{var a=t.get_tokens(s);if(a===null)break;var l=U(e,a,0,e.__get_max_priority(),!1);if(l.type!==g){var f=l.len,y=f;if(a[f]&&a[f].name==="atom"&&a[f].raw===".")e.add_goal(ve(l.value));else{var d=a[f];return new o("throw",[i.error.syntax(d||a[f-1],". or operator expected",!d)])}s=l.len+1}else return new o("throw",[l.value])}while(!0);return!0}function Bi(e,n){e=e.rename(n);var t=n.next_free_variable(),s=pr(e.body,t,n);return s.error?s.value:(e.body=s.value,e.head.args=e.head.args.concat([t,s.variable]),e.head=new o(e.head.id,e.head.args),e)}function pr(e,n,t){var s;if(i.type.is_term(e)&&e.indicator==="!/0")return{value:e,variable:n,error:!1};if(i.type.is_term(e)&&e.indicator===",/2"){var a=pr(e.args[0],n,t);if(a.error)return a;var l=pr(e.args[1],a.variable,t);return l.error?l:{value:new o(",",[a.value,l.value]),variable:l.variable,error:!1}}else{if(i.type.is_term(e)&&e.indicator==="{}/1")return{value:e.args[0],variable:n,error:!1};if(i.type.is_empty_list(e))return{value:new o("true",[]),variable:n,error:!1};if(i.type.is_list(e)){s=t.next_free_variable();for(var f=e,y;f.indicator==="./2";)y=f,f=f.args[1];return i.type.is_variable(f)?{value:i.error.instantiation("DCG"),variable:n,error:!0}:i.type.is_empty_list(f)?(y.args[1]=s,{value:new o("=",[n,e]),variable:s,error:!1}):{value:i.error.type("list",e,"DCG"),variable:n,error:!0}}else return i.type.is_callable(e)?(s=t.next_free_variable(),e.args=e.args.concat([n,s]),e=new o(e.id,e.args),{value:e,variable:s,error:!1}):{value:i.error.type("callable",e,"DCG"),variable:n,error:!0}}}function ve(e){return i.type.is_variable(e)?new o("call",[e]):i.type.is_term(e)&&[",/2",";/2","->/2"].indexOf(e.indicator)!==-1?new o(e.id,[ve(e.args[0]),ve(e.args[1])]):e}function he(e,n){for(var t=n||new i.type.Term("[]",[]),s=e.length-1;s>=0;s--)t=new i.type.Term(".",[e[s],t]);return t}function Fi(e,n){for(var t=e.length-1;t>=0;t--)e[t]===n&&e.splice(t,1)}function yr(e){for(var n={},t=[],s=0;s=0;n--)if(e.charAt(n)==="/")return new o("/",[new o(e.substring(0,n)),new E(parseInt(e.substring(n+1)),!1)])}function O(e){this.id=e}function E(e,n){this.is_float=n!==void 0?n:parseInt(e)!==e,this.value=this.is_float?e:parseInt(e)}var $r=0;function o(e,n,t){this.ref=t||++$r,this.id=e,this.args=n||[],this.indicator=e+"/"+this.args.length}var Wi=0;function ne(e,n,t,s,a,l){this.id=Wi++,this.stream=e,this.mode=n,this.alias=t,this.type=s!==void 0?s:"text",this.reposition=a!==void 0?a:!0,this.eof_action=l!==void 0?l:"eof_code",this.position=this.mode==="append"?"end_of_stream":0,this.output=this.mode==="write"||this.mode==="append",this.input=this.mode==="read"}function Y(e){e=e||{},this.links=e}function V(e,n,t){n=n||new Y,t=t||null,this.goal=e,this.substitution=n,this.parent=t}function Q(e,n,t){this.head=e,this.body=n,this.dynamic=t||!1}function D(e){e=e===void 0||e<=0?1e3:e,this.rules={},this.src_predicates={},this.rename=0,this.modules=[],this.thread=new X(this),this.total_threads=1,this.renamed_variables={},this.public_predicates={},this.multifile_predicates={},this.limit=e,this.streams={user_input:new ne(typeof ie!="undefined"&&ie.exports?nodejs_user_input:tau_user_input,"read","user_input","text",!1,"reset"),user_output:new ne(typeof ie!="undefined"&&ie.exports?nodejs_user_output:tau_user_output,"write","user_output","text",!1,"eof_code")},this.file_system=typeof ie!="undefined"&&ie.exports?nodejs_file_system:tau_file_system,this.standard_input=this.streams.user_input,this.standard_output=this.streams.user_output,this.current_input=this.streams.user_input,this.current_output=this.streams.user_output,this.format_success=function(n){return n.substitution},this.format_error=function(n){return n.goal},this.flag={bounded:i.flag.bounded.value,max_integer:i.flag.max_integer.value,min_integer:i.flag.min_integer.value,integer_rounding_function:i.flag.integer_rounding_function.value,char_conversion:i.flag.char_conversion.value,debug:i.flag.debug.value,max_arity:i.flag.max_arity.value,unknown:i.flag.unknown.value,double_quotes:i.flag.double_quotes.value,occurs_check:i.flag.occurs_check.value,dialect:i.flag.dialect.value,version_data:i.flag.version_data.value,nodejs:i.flag.nodejs.value},this.__loaded_modules=[],this.__char_conversion={},this.__operators={1200:{":-":["fx","xfx"],"-->":["xfx"],"?-":["fx"]},1100:{";":["xfy"]},1050:{"->":["xfy"]},1e3:{",":["xfy"]},900:{"\\+":["fy"]},700:{"=":["xfx"],"\\=":["xfx"],"==":["xfx"],"\\==":["xfx"],"@<":["xfx"],"@=<":["xfx"],"@>":["xfx"],"@>=":["xfx"],"=..":["xfx"],is:["xfx"],"=:=":["xfx"],"=\\=":["xfx"],"<":["xfx"],"=<":["xfx"],">":["xfx"],">=":["xfx"]},600:{":":["xfy"]},500:{"+":["yfx"],"-":["yfx"],"/\\":["yfx"],"\\/":["yfx"]},400:{"*":["yfx"],"/":["yfx"],"//":["yfx"],rem:["yfx"],mod:["yfx"],"<<":["yfx"],">>":["yfx"]},200:{"**":["xfx"],"^":["xfy"],"-":["fy"],"+":["fy"],"\\":["fy"]}}}function X(e){this.epoch=Date.now(),this.session=e,this.session.total_threads++,this.total_steps=0,this.cpu_time=0,this.cpu_time_last=0,this.points=[],this.debugger=!1,this.debugger_states=[],this.level="top_level/0",this.__calls=[],this.current_limit=this.session.limit,this.warnings=[]}function Dr(e,n,t){this.id=e,this.rules=n,this.exports=t,i.module[e]=this}Dr.prototype.exports_predicate=function(e){return this.exports.indexOf(e)!==-1},O.prototype.unify=function(e,n){if(n&&u(e.variables(),this.id)!==-1&&!i.type.is_variable(e))return null;var t={};return t[this.id]=e,new Y(t)},E.prototype.unify=function(e,n){return i.type.is_number(e)&&this.value===e.value&&this.is_float===e.is_float?new Y:null},o.prototype.unify=function(e,n){if(i.type.is_term(e)&&this.indicator===e.indicator){for(var t=new Y,s=0;s=0){var s=this.args[0].value,a=Math.floor(s/26),l=s%26;return"ABCDEFGHIJKLMNOPQRSTUVWXYZ"[l]+(a!==0?a:"")}switch(this.indicator){case"[]/0":case"{}/0":case"!/0":return this.id;case"{}/1":return"{"+this.args[0].toString(e)+"}";case"./2":for(var f="["+this.args[0].toString(e),y=this.args[1];y.indicator==="./2";)f+=", "+y.args[0].toString(e),y=y.args[1];return y.indicator!=="[]/0"&&(f+="|"+y.toString(e)),f+="]",f;case",/2":return"("+this.args[0].toString(e)+", "+this.args[1].toString(e)+")";default:var d=this.id,m=e.session?e.session.lookup_operator(this.id,this.args.length):null;if(e.session===void 0||e.ignore_ops||m===null)return e.quoted&&!/^(!|,|;|[a-z][0-9a-zA-Z_]*)$/.test(d)&&d!=="{}"&&d!=="[]"&&(d="'"+N(d)+"'"),d+(this.args.length?"("+c(this.args,function(R){return R.toString(e)}).join(", ")+")":"");var S=m.priority>n.priority||m.priority===n.priority&&(m.class==="xfy"&&this.indicator!==n.indicator||m.class==="yfx"&&this.indicator!==n.indicator||this.indicator===n.indicator&&m.class==="yfx"&&t==="right"||this.indicator===n.indicator&&m.class==="xfy"&&t==="left");m.indicator=this.indicator;var P=S?"(":"",A=S?")":"";return this.args.length===0?"("+this.id+")":["fy","fx"].indexOf(m.class)!==-1?P+d+" "+this.args[0].toString(e,m)+A:["yf","xf"].indexOf(m.class)!==-1?P+this.args[0].toString(e,m)+" "+d+A:P+this.args[0].toString(e,m,"left")+" "+this.id+" "+this.args[1].toString(e,m,"right")+A}},ne.prototype.toString=function(e){return"("+this.id+")"},Y.prototype.toString=function(e){var n="{";for(var t in this.links)!this.links.hasOwnProperty(t)||(n!=="{"&&(n+=", "),n+=t+"/"+this.links[t].toString(e));return n+="}",n},V.prototype.toString=function(e){return this.goal===null?"<"+this.substitution.toString(e)+">":"<"+this.goal.toString(e)+", "+this.substitution.toString(e)+">"},Q.prototype.toString=function(e){return this.body?this.head.toString(e)+" :- "+this.body.toString(e)+".":this.head.toString(e)+"."},D.prototype.toString=function(e){for(var n="",t=0;t=0;a--)s=new o(".",[n[a],s]);return s}return new o(this.id,c(this.args,function(l){return l.apply(e)}),this.ref)},ne.prototype.apply=function(e){return this},Q.prototype.apply=function(e){return new Q(this.head.apply(e),this.body!==null?this.body.apply(e):null)},Y.prototype.apply=function(e){var n,t={};for(n in this.links)!this.links.hasOwnProperty(n)||(t[n]=this.links[n].apply(e));return new Y(t)},o.prototype.select=function(){for(var e=this;e.indicator===",/2";)e=e.args[0];return e},o.prototype.replace=function(e){return this.indicator===",/2"?this.args[0].indicator===",/2"?new o(",",[this.args[0].replace(e),this.args[1]]):e===null?this.args[1]:new o(",",[e,this.args[1]]):e},o.prototype.search=function(e){if(i.type.is_term(e)&&e.ref!==void 0&&this.ref===e.ref)return!0;for(var n=0;nn&&s0&&(n=this.head_point().substitution.domain());u(n,i.format_variable(this.session.rename))!==-1;)this.session.rename++;if(e.id==="_")return new O(i.format_variable(this.session.rename));this.session.renamed_variables[e.id]=i.format_variable(this.session.rename)}return new O(this.session.renamed_variables[e.id])},D.prototype.next_free_variable=function(){return this.thread.next_free_variable()},X.prototype.next_free_variable=function(){this.session.rename++;var e=[];for(this.points.length>0&&(e=this.head_point().substitution.domain());u(e,i.format_variable(this.session.rename))!==-1;)this.session.rename++;return new O(i.format_variable(this.session.rename))},D.prototype.is_public_predicate=function(e){return!this.public_predicates.hasOwnProperty(e)||this.public_predicates[e]===!0},X.prototype.is_public_predicate=function(e){return this.session.is_public_predicate(e)},D.prototype.is_multifile_predicate=function(e){return this.multifile_predicates.hasOwnProperty(e)&&this.multifile_predicates[e]===!0},X.prototype.is_multifile_predicate=function(e){return this.session.is_multifile_predicate(e)},D.prototype.prepend=function(e){return this.thread.prepend(e)},X.prototype.prepend=function(e){for(var n=e.length-1;n>=0;n--)this.points.push(e[n])},D.prototype.success=function(e,n){return this.thread.success(e,n)},X.prototype.success=function(e,n){var n=typeof n=="undefined"?e:n;this.prepend([new V(e.goal.replace(null),e.substitution,n)])},D.prototype.throw_error=function(e){return this.thread.throw_error(e)},X.prototype.throw_error=function(e){this.prepend([new V(new o("throw",[e]),new Y,null,null)])},D.prototype.step_rule=function(e,n){return this.thread.step_rule(e,n)},X.prototype.step_rule=function(e,n){var t=n.indicator;if(e==="user"&&(e=null),e===null&&this.session.rules.hasOwnProperty(t))return this.session.rules[t];for(var s=e===null?this.session.modules:u(this.session.modules,e)===-1?[]:[e],a=0;a1)&&this.again()},D.prototype.answers=function(e,n,t){return this.thread.answers(e,n,t)},X.prototype.answers=function(e,n,t){var s=n||1e3,a=this;if(n<=0){t&&t();return}this.answer(function(l){e(l),l!==!1?setTimeout(function(){a.answers(e,n-1,t)},1):t&&t()})},D.prototype.again=function(e){return this.thread.again(e)},X.prototype.again=function(e){for(var n,t=Date.now();this.__calls.length>0;){for(this.warnings=[],e!==!1&&(this.current_limit=this.session.limit);this.current_limit>0&&this.points.length>0&&this.head_point().goal!==null&&!i.type.is_error(this.head_point().goal);)if(this.current_limit--,this.step()===!0)return;var s=Date.now();this.cpu_time_last=s-t,this.cpu_time+=this.cpu_time_last;var a=this.__calls.shift();this.current_limit<=0?a(null):this.points.length===0?a(!1):i.type.is_error(this.head_point().goal)?(n=this.session.format_error(this.points.pop()),this.points=[],a(n)):(this.debugger&&this.debugger_states.push(this.head_point()),n=this.session.format_success(this.points.pop()),a(n))}},D.prototype.unfold=function(e){if(e.body===null)return!1;var n=e.head,t=e.body,s=t.select(),a=new X(this),l=[];a.add_goal(s),a.step();for(var f=a.points.length-1;f>=0;f--){var y=a.points[f],d=n.apply(y.substitution),m=t.replace(y.goal);m!==null&&(m=m.apply(y.substitution)),l.push(new Q(d,m))}var S=this.rules[n.indicator],P=u(S,e);return l.length>0&&P!==-1?(S.splice.apply(S,[P,1].concat(l)),!0):!1},X.prototype.unfold=function(e){return this.session.unfold(e)},O.prototype.interpret=function(e){return i.error.instantiation(e.level)},E.prototype.interpret=function(e){return this},o.prototype.interpret=function(e){return i.type.is_unitary_list(this)?this.args[0].interpret(e):i.operate(e,this)},O.prototype.compare=function(e){return this.ide.id?1:0},E.prototype.compare=function(e){if(this.value===e.value&&this.is_float===e.is_float)return 0;if(this.valuee.value)return 1},o.prototype.compare=function(e){if(this.args.lengthe.args.length||this.args.length===e.args.length&&this.id>e.id)return 1;for(var n=0;ns)return 1;if(e.constructor===E){if(e.is_float&&n.is_float)return 0;if(e.is_float)return-1;if(n.is_float)return 1}return 0},is_substitution:function(e){return e instanceof Y},is_state:function(e){return e instanceof V},is_rule:function(e){return e instanceof Q},is_variable:function(e){return e instanceof O},is_stream:function(e){return e instanceof ne},is_anonymous_var:function(e){return e instanceof O&&e.id==="_"},is_callable:function(e){return e instanceof o},is_number:function(e){return e instanceof E},is_integer:function(e){return e instanceof E&&!e.is_float},is_float:function(e){return e instanceof E&&e.is_float},is_term:function(e){return e instanceof o},is_atom:function(e){return e instanceof o&&e.args.length===0},is_ground:function(e){if(e instanceof O)return!1;if(e instanceof o){for(var n=0;n0},is_list:function(e){return e instanceof o&&(e.indicator==="[]/0"||e.indicator==="./2")},is_empty_list:function(e){return e instanceof o&&e.indicator==="[]/0"},is_non_empty_list:function(e){return e instanceof o&&e.indicator==="./2"},is_fully_list:function(e){for(;e instanceof o&&e.indicator==="./2";)e=e.args[1];return e instanceof O||e instanceof o&&e.indicator==="[]/0"},is_instantiated_list:function(e){for(;e instanceof o&&e.indicator==="./2";)e=e.args[1];return e instanceof o&&e.indicator==="[]/0"},is_unitary_list:function(e){return e instanceof o&&e.indicator==="./2"&&e.args[1]instanceof o&&e.args[1].indicator==="[]/0"},is_character:function(e){return e instanceof o&&(e.id.length===1||e.id.length>0&&e.id.length<=2&&_(e.id,0)>=65536)},is_character_code:function(e){return e instanceof E&&!e.is_float&&e.value>=0&&e.value<=1114111},is_byte:function(e){return e instanceof E&&!e.is_float&&e.value>=0&&e.value<=255},is_operator:function(e){return e instanceof o&&i.arithmetic.evaluation[e.indicator]},is_directive:function(e){return e instanceof o&&i.directive[e.indicator]!==void 0},is_builtin:function(e){return e instanceof o&&i.predicate[e.indicator]!==void 0},is_error:function(e){return e instanceof o&&e.indicator==="throw/1"},is_predicate_indicator:function(e){return e instanceof o&&e.indicator==="//2"&&e.args[0]instanceof o&&e.args[0].args.length===0&&e.args[1]instanceof E&&e.args[1].is_float===!1},is_flag:function(e){return e instanceof o&&e.args.length===0&&i.flag[e.id]!==void 0},is_value_flag:function(e,n){if(!i.type.is_flag(e))return!1;for(var t in i.flag[e.id].allowed)if(!!i.flag[e.id].allowed.hasOwnProperty(t)&&i.flag[e.id].allowed[t].equals(n))return!0;return!1},is_io_mode:function(e){return i.type.is_atom(e)&&["read","write","append"].indexOf(e.id)!==-1},is_stream_option:function(e){return i.type.is_term(e)&&(e.indicator==="alias/1"&&i.type.is_atom(e.args[0])||e.indicator==="reposition/1"&&i.type.is_atom(e.args[0])&&(e.args[0].id==="true"||e.args[0].id==="false")||e.indicator==="type/1"&&i.type.is_atom(e.args[0])&&(e.args[0].id==="text"||e.args[0].id==="binary")||e.indicator==="eof_action/1"&&i.type.is_atom(e.args[0])&&(e.args[0].id==="error"||e.args[0].id==="eof_code"||e.args[0].id==="reset"))},is_stream_position:function(e){return i.type.is_integer(e)&&e.value>=0||i.type.is_atom(e)&&(e.id==="end_of_stream"||e.id==="past_end_of_stream")},is_stream_property:function(e){return i.type.is_term(e)&&(e.indicator==="input/0"||e.indicator==="output/0"||e.indicator==="alias/1"&&(i.type.is_variable(e.args[0])||i.type.is_atom(e.args[0]))||e.indicator==="file_name/1"&&(i.type.is_variable(e.args[0])||i.type.is_atom(e.args[0]))||e.indicator==="position/1"&&(i.type.is_variable(e.args[0])||i.type.is_stream_position(e.args[0]))||e.indicator==="reposition/1"&&(i.type.is_variable(e.args[0])||i.type.is_atom(e.args[0])&&(e.args[0].id==="true"||e.args[0].id==="false"))||e.indicator==="type/1"&&(i.type.is_variable(e.args[0])||i.type.is_atom(e.args[0])&&(e.args[0].id==="text"||e.args[0].id==="binary"))||e.indicator==="mode/1"&&(i.type.is_variable(e.args[0])||i.type.is_atom(e.args[0])&&(e.args[0].id==="read"||e.args[0].id==="write"||e.args[0].id==="append"))||e.indicator==="eof_action/1"&&(i.type.is_variable(e.args[0])||i.type.is_atom(e.args[0])&&(e.args[0].id==="error"||e.args[0].id==="eof_code"||e.args[0].id==="reset"))||e.indicator==="end_of_stream/1"&&(i.type.is_variable(e.args[0])||i.type.is_atom(e.args[0])&&(e.args[0].id==="at"||e.args[0].id==="past"||e.args[0].id==="not")))},is_streamable:function(e){return e.__proto__.stream!==void 0},is_read_option:function(e){return i.type.is_term(e)&&["variables/1","variable_names/1","singletons/1"].indexOf(e.indicator)!==-1},is_write_option:function(e){return i.type.is_term(e)&&(e.indicator==="quoted/1"&&i.type.is_atom(e.args[0])&&(e.args[0].id==="true"||e.args[0].id==="false")||e.indicator==="ignore_ops/1"&&i.type.is_atom(e.args[0])&&(e.args[0].id==="true"||e.args[0].id==="false")||e.indicator==="numbervars/1"&&i.type.is_atom(e.args[0])&&(e.args[0].id==="true"||e.args[0].id==="false"))},is_close_option:function(e){return i.type.is_term(e)&&e.indicator==="force/1"&&i.type.is_atom(e.args[0])&&(e.args[0].id==="true"||e.args[0].id==="false")},is_modifiable_flag:function(e){return i.type.is_flag(e)&&i.flag[e.id].changeable},is_module:function(e){return e instanceof o&&e.indicator==="library/1"&&e.args[0]instanceof o&&e.args[0].args.length===0&&i.module[e.args[0].id]!==void 0}},arithmetic:{evaluation:{"e/0":{type_args:null,type_result:!0,fn:function(e){return Math.E}},"pi/0":{type_args:null,type_result:!0,fn:function(e){return Math.PI}},"tau/0":{type_args:null,type_result:!0,fn:function(e){return 2*Math.PI}},"epsilon/0":{type_args:null,type_result:!0,fn:function(e){return Number.EPSILON}},"+/1":{type_args:null,type_result:null,fn:function(e,n){return e}},"-/1":{type_args:null,type_result:null,fn:function(e,n){return-e}},"\\/1":{type_args:!1,type_result:!1,fn:function(e,n){return~e}},"abs/1":{type_args:null,type_result:null,fn:function(e,n){return Math.abs(e)}},"sign/1":{type_args:null,type_result:null,fn:function(e,n){return Math.sign(e)}},"float_integer_part/1":{type_args:!0,type_result:!1,fn:function(e,n){return parseInt(e)}},"float_fractional_part/1":{type_args:!0,type_result:!0,fn:function(e,n){return e-parseInt(e)}},"float/1":{type_args:null,type_result:!0,fn:function(e,n){return parseFloat(e)}},"floor/1":{type_args:!0,type_result:!1,fn:function(e,n){return Math.floor(e)}},"truncate/1":{type_args:!0,type_result:!1,fn:function(e,n){return parseInt(e)}},"round/1":{type_args:!0,type_result:!1,fn:function(e,n){return Math.round(e)}},"ceiling/1":{type_args:!0,type_result:!1,fn:function(e,n){return Math.ceil(e)}},"sin/1":{type_args:null,type_result:!0,fn:function(e,n){return Math.sin(e)}},"cos/1":{type_args:null,type_result:!0,fn:function(e,n){return Math.cos(e)}},"tan/1":{type_args:null,type_result:!0,fn:function(e,n){return Math.tan(e)}},"asin/1":{type_args:null,type_result:!0,fn:function(e,n){return Math.asin(e)}},"acos/1":{type_args:null,type_result:!0,fn:function(e,n){return Math.acos(e)}},"atan/1":{type_args:null,type_result:!0,fn:function(e,n){return Math.atan(e)}},"atan2/2":{type_args:null,type_result:!0,fn:function(e,n,t){return Math.atan2(e,n)}},"exp/1":{type_args:null,type_result:!0,fn:function(e,n){return Math.exp(e)}},"sqrt/1":{type_args:null,type_result:!0,fn:function(e,n){return Math.sqrt(e)}},"log/1":{type_args:null,type_result:!0,fn:function(e,n){return e>0?Math.log(e):i.error.evaluation("undefined",n.__call_indicator)}},"+/2":{type_args:null,type_result:null,fn:function(e,n,t){return e+n}},"-/2":{type_args:null,type_result:null,fn:function(e,n,t){return e-n}},"*/2":{type_args:null,type_result:null,fn:function(e,n,t){return e*n}},"//2":{type_args:null,type_result:!0,fn:function(e,n,t){return n?e/n:i.error.evaluation("zero_division",t.__call_indicator)}},"///2":{type_args:!1,type_result:!1,fn:function(e,n,t){return n?parseInt(e/n):i.error.evaluation("zero_division",t.__call_indicator)}},"**/2":{type_args:null,type_result:!0,fn:function(e,n,t){return Math.pow(e,n)}},"^/2":{type_args:null,type_result:null,fn:function(e,n,t){return Math.pow(e,n)}},"<>/2":{type_args:!1,type_result:!1,fn:function(e,n,t){return e>>n}},"/\\/2":{type_args:!1,type_result:!1,fn:function(e,n,t){return e&n}},"\\//2":{type_args:!1,type_result:!1,fn:function(e,n,t){return e|n}},"xor/2":{type_args:!1,type_result:!1,fn:function(e,n,t){return e^n}},"rem/2":{type_args:!1,type_result:!1,fn:function(e,n,t){return n?e%n:i.error.evaluation("zero_division",t.__call_indicator)}},"mod/2":{type_args:!1,type_result:!1,fn:function(e,n,t){return n?e-parseInt(e/n)*n:i.error.evaluation("zero_division",t.__call_indicator)}},"max/2":{type_args:null,type_result:null,fn:function(e,n,t){return Math.max(e,n)}},"min/2":{type_args:null,type_result:null,fn:function(e,n,t){return Math.min(e,n)}}}},directive:{"dynamic/1":function(e,n){var t=n.args[0];if(i.type.is_variable(t))e.throw_error(i.error.instantiation(n.indicator));else if(!i.type.is_compound(t)||t.indicator!=="//2")e.throw_error(i.error.type("predicate_indicator",t,n.indicator));else if(i.type.is_variable(t.args[0])||i.type.is_variable(t.args[1]))e.throw_error(i.error.instantiation(n.indicator));else if(!i.type.is_atom(t.args[0]))e.throw_error(i.error.type("atom",t.args[0],n.indicator));else if(!i.type.is_integer(t.args[1]))e.throw_error(i.error.type("integer",t.args[1],n.indicator));else{var s=n.args[0].args[0].id+"/"+n.args[0].args[1].value;e.session.public_predicates[s]=!0,e.session.rules[s]||(e.session.rules[s]=[])}},"multifile/1":function(e,n){var t=n.args[0];i.type.is_variable(t)?e.throw_error(i.error.instantiation(n.indicator)):!i.type.is_compound(t)||t.indicator!=="//2"?e.throw_error(i.error.type("predicate_indicator",t,n.indicator)):i.type.is_variable(t.args[0])||i.type.is_variable(t.args[1])?e.throw_error(i.error.instantiation(n.indicator)):i.type.is_atom(t.args[0])?i.type.is_integer(t.args[1])?e.session.multifile_predicates[n.args[0].args[0].id+"/"+n.args[0].args[1].value]=!0:e.throw_error(i.error.type("integer",t.args[1],n.indicator)):e.throw_error(i.error.type("atom",t.args[0],n.indicator))},"set_prolog_flag/2":function(e,n){var t=n.args[0],s=n.args[1];i.type.is_variable(t)||i.type.is_variable(s)?e.throw_error(i.error.instantiation(n.indicator)):i.type.is_atom(t)?i.type.is_flag(t)?i.type.is_value_flag(t,s)?i.type.is_modifiable_flag(t)?e.session.flag[t.id]=s:e.throw_error(i.error.permission("modify","flag",t)):e.throw_error(i.error.domain("flag_value",new o("+",[t,s]),n.indicator)):e.throw_error(i.error.domain("prolog_flag",t,n.indicator)):e.throw_error(i.error.type("atom",t,n.indicator))},"use_module/1":function(e,n){var t=n.args[0];if(i.type.is_variable(t))e.throw_error(i.error.instantiation(n.indicator));else if(!i.type.is_term(t))e.throw_error(i.error.type("term",t,n.indicator));else if(i.type.is_module(t)){var s=t.args[0].id;u(e.session.modules,s)===-1&&e.session.modules.push(s)}},"char_conversion/2":function(e,n){var t=n.args[0],s=n.args[1];i.type.is_variable(t)||i.type.is_variable(s)?e.throw_error(i.error.instantiation(n.indicator)):i.type.is_character(t)?i.type.is_character(s)?t.id===s.id?delete e.session.__char_conversion[t.id]:e.session.__char_conversion[t.id]=s.id:e.throw_error(i.error.type("character",s,n.indicator)):e.throw_error(i.error.type("character",t,n.indicator))},"op/3":function(e,n){var t=n.args[0],s=n.args[1],a=n.args[2];if(i.type.is_variable(t)||i.type.is_variable(s)||i.type.is_variable(a))e.throw_error(i.error.instantiation(n.indicator));else if(!i.type.is_integer(t))e.throw_error(i.error.type("integer",t,n.indicator));else if(!i.type.is_atom(s))e.throw_error(i.error.type("atom",s,n.indicator));else if(!i.type.is_atom(a))e.throw_error(i.error.type("atom",a,n.indicator));else if(t.value<0||t.value>1200)e.throw_error(i.error.domain("operator_priority",t,n.indicator));else if(a.id===",")e.throw_error(i.error.permission("modify","operator",a,n.indicator));else if(a.id==="|"&&(t.value<1001||s.id.length!==3))e.throw_error(i.error.permission("modify","operator",a,n.indicator));else if(["fy","fx","yf","xf","xfx","yfx","xfy"].indexOf(s.id)===-1)e.throw_error(i.error.domain("operator_specifier",s,n.indicator));else{var l={prefix:null,infix:null,postfix:null};for(var f in e.session.__operators)if(!!e.session.__operators.hasOwnProperty(f)){var y=e.session.__operators[f][a.id];y&&(u(y,"fx")!==-1&&(l.prefix={priority:f,type:"fx"}),u(y,"fy")!==-1&&(l.prefix={priority:f,type:"fy"}),u(y,"xf")!==-1&&(l.postfix={priority:f,type:"xf"}),u(y,"yf")!==-1&&(l.postfix={priority:f,type:"yf"}),u(y,"xfx")!==-1&&(l.infix={priority:f,type:"xfx"}),u(y,"xfy")!==-1&&(l.infix={priority:f,type:"xfy"}),u(y,"yfx")!==-1&&(l.infix={priority:f,type:"yfx"}))}var d;switch(s.id){case"fy":case"fx":d="prefix";break;case"yf":case"xf":d="postfix";break;default:d="infix";break}if(((l.prefix&&d==="prefix"||l.postfix&&d==="postfix"||l.infix&&d==="infix")&&l[d].type!==s.id||l.infix&&d==="postfix"||l.postfix&&d==="infix")&&t.value!==0)e.throw_error(i.error.permission("create","operator",a,n.indicator));else return l[d]&&(Fi(e.session.__operators[l[d].priority][a.id],s.id),e.session.__operators[l[d].priority][a.id].length===0&&delete e.session.__operators[l[d].priority][a.id]),t.value>0&&(e.session.__operators[t.value]||(e.session.__operators[t.value.toString()]={}),e.session.__operators[t.value][a.id]||(e.session.__operators[t.value][a.id]=[]),e.session.__operators[t.value][a.id].push(s.id)),!0}}},predicate:{"op/3":function(e,n,t){i.directive["op/3"](e,t)&&e.success(n)},"current_op/3":function(e,n,t){var s=t.args[0],a=t.args[1],l=t.args[2],f=[];for(var y in e.session.__operators)for(var d in e.session.__operators[y])for(var m=0;m/2"){var s=e.points,a=e.session.format_success,l=e.session.format_error;e.session.format_success=function(m){return m.substitution},e.session.format_error=function(m){return m.goal},e.points=[new V(t.args[0].args[0],n.substitution,n)];var f=function(m){e.points=s,e.session.format_success=a,e.session.format_error=l,m===!1?e.prepend([new V(n.goal.replace(t.args[1]),n.substitution,n)]):i.type.is_error(m)?e.throw_error(m.args[0]):m===null?(e.prepend([n]),e.__calls.shift()(null)):e.prepend([new V(n.goal.replace(t.args[0].args[1]).apply(m),n.substitution.apply(m),n)])};e.__calls.unshift(f)}else{var y=new V(n.goal.replace(t.args[0]),n.substitution,n),d=new V(n.goal.replace(t.args[1]),n.substitution,n);e.prepend([y,d])}},"!/0":function(e,n,t){var s,a,l=[];for(s=n,a=null;s.parent!==null&&s.parent.goal.search(t);)if(a=s,s=s.parent,s.goal!==null){var f=s.goal.select();if(f&&f.id==="call"&&f.search(t)){s=a;break}}for(var y=e.points.length-1;y>=0;y--){for(var d=e.points[y],m=d.parent;m!==null&&m!==s.parent;)m=m.parent;m===null&&m!==s.parent&&l.push(d)}e.points=l.reverse(),e.success(n)},"\\+/1":function(e,n,t){var s=t.args[0];i.type.is_variable(s)?e.throw_error(i.error.instantiation(e.level)):i.type.is_callable(s)?e.prepend([new V(n.goal.replace(new o(",",[new o(",",[new o("call",[s]),new o("!",[])]),new o("fail",[])])),n.substitution,n),new V(n.goal.replace(null),n.substitution,n)]):e.throw_error(i.error.type("callable",s,e.level))},"->/2":function(e,n,t){var s=n.goal.replace(new o(",",[t.args[0],new o(",",[new o("!"),t.args[1]])]));e.prepend([new V(s,n.substitution,n)])},"fail/0":function(e,n,t){},"false/0":function(e,n,t){},"true/0":function(e,n,t){e.success(n)},"call/1":pe(1),"call/2":pe(2),"call/3":pe(3),"call/4":pe(4),"call/5":pe(5),"call/6":pe(6),"call/7":pe(7),"call/8":pe(8),"once/1":function(e,n,t){var s=t.args[0];e.prepend([new V(n.goal.replace(new o(",",[new o("call",[s]),new o("!",[])])),n.substitution,n)])},"forall/2":function(e,n,t){var s=t.args[0],a=t.args[1];e.prepend([new V(n.goal.replace(new o("\\+",[new o(",",[new o("call",[s]),new o("\\+",[new o("call",[a])])])])),n.substitution,n)])},"repeat/0":function(e,n,t){e.prepend([new V(n.goal.replace(null),n.substitution,n),n])},"throw/1":function(e,n,t){i.type.is_variable(t.args[0])?e.throw_error(i.error.instantiation(e.level)):e.throw_error(t.args[0])},"catch/3":function(e,n,t){var s=e.points;e.points=[],e.prepend([new V(t.args[0],n.substitution,n)]);var a=e.session.format_success,l=e.session.format_error;e.session.format_success=function(y){return y.substitution},e.session.format_error=function(y){return y.goal};var f=function(y){var d=e.points;if(e.points=s,e.session.format_success=a,e.session.format_error=l,i.type.is_error(y)){for(var m=[],S=e.points.length-1;S>=0;S--){for(var R=e.points[S],P=R.parent;P!==null&&P!==n.parent;)P=P.parent;P===null&&P!==n.parent&&m.push(R)}e.points=m;var A=e.get_flag("occurs_check").indicator==="true/0",R=new V,k=i.unify(y.args[0],t.args[1],A);k!==null?(R.substitution=n.substitution.apply(k),R.goal=n.goal.replace(t.args[2]).apply(k),R.parent=n,e.prepend([R])):e.throw_error(y.args[0])}else if(y!==!1){for(var L=y===null?[]:[new V(n.goal.apply(y).replace(null),n.substitution.apply(y),n)],B=[],S=d.length-1;S>=0;S--){B.push(d[S]);var q=d[S].goal!==null?d[S].goal.select():null;if(i.type.is_term(q)&&q.indicator==="!/0")break}var F=c(B,function(H){return H.goal===null&&(H.goal=new o("true",[])),H=new V(n.goal.replace(new o("catch",[H.goal,t.args[1],t.args[2]])),n.substitution.apply(H.substitution),H.parent),H.exclude=t.args[0].variables(),H}).reverse();e.prepend(F),e.prepend(L),y===null&&(this.current_limit=0,e.__calls.shift()(null))}};e.__calls.unshift(f)},"=/2":function(e,n,t){var s=e.get_flag("occurs_check").indicator==="true/0",a=new V,l=i.unify(t.args[0],t.args[1],s);l!==null&&(a.goal=n.goal.apply(l).replace(null),a.substitution=n.substitution.apply(l),a.parent=n,e.prepend([a]))},"unify_with_occurs_check/2":function(e,n,t){var s=new V,a=i.unify(t.args[0],t.args[1],!0);a!==null&&(s.goal=n.goal.apply(a).replace(null),s.substitution=n.substitution.apply(a),s.parent=n,e.prepend([s]))},"\\=/2":function(e,n,t){var s=e.get_flag("occurs_check").indicator==="true/0",a=i.unify(t.args[0],t.args[1],s);a===null&&e.success(n)},"subsumes_term/2":function(e,n,t){var s=e.get_flag("occurs_check").indicator==="true/0",a=i.unify(t.args[1],t.args[0],s);a!==null&&t.args[1].apply(a).equals(t.args[1])&&e.success(n)},"findall/3":function(e,n,t){var s=t.args[0],a=t.args[1],l=t.args[2];if(i.type.is_variable(a))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_callable(a))e.throw_error(i.error.type("callable",a,t.indicator));else if(!i.type.is_variable(l)&&!i.type.is_list(l))e.throw_error(i.error.type("list",l,t.indicator));else{var f=e.next_free_variable(),y=new o(",",[a,new o("=",[f,s])]),d=e.points,m=e.session.limit,S=e.session.format_success;e.session.format_success=function(R){return R.substitution},e.add_goal(y,!0,n);var P=[],A=function(R){if(R!==!1&&R!==null&&!i.type.is_error(R))e.__calls.unshift(A),P.push(R.links[f.id]),e.session.limit=e.current_limit;else if(e.points=d,e.session.limit=m,e.session.format_success=S,i.type.is_error(R))e.throw_error(R.args[0]);else if(e.current_limit>0){for(var k=new o("[]"),L=P.length-1;L>=0;L--)k=new o(".",[P[L],k]);e.prepend([new V(n.goal.replace(new o("=",[l,k])),n.substitution,n)])}};e.__calls.unshift(A)}},"bagof/3":function(e,n,t){var s,a=t.args[0],l=t.args[1],f=t.args[2];if(i.type.is_variable(l))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_callable(l))e.throw_error(i.error.type("callable",l,t.indicator));else if(!i.type.is_variable(f)&&!i.type.is_list(f))e.throw_error(i.error.type("list",f,t.indicator));else{var y=e.next_free_variable(),d;l.indicator==="^/2"?(d=l.args[0].variables(),l=l.args[1]):d=[],d=d.concat(a.variables());for(var m=l.variables().filter(function(F){return u(d,F)===-1}),S=new o("[]"),P=m.length-1;P>=0;P--)S=new o(".",[new O(m[P]),S]);var A=new o(",",[l,new o("=",[y,new o(",",[S,a])])]),R=e.points,k=e.session.limit,L=e.session.format_success;e.session.format_success=function(F){return F.substitution},e.add_goal(A,!0,n);var B=[],q=function(F){if(F!==!1&&F!==null&&!i.type.is_error(F)){e.__calls.unshift(q);var H=!1,J=F.links[y.id].args[0],me=F.links[y.id].args[1];for(var be in B)if(!!B.hasOwnProperty(be)){var Re=B[be];if(Re.variables.equals(J)){Re.answers.push(me),H=!0;break}}H||B.push({variables:J,answers:[me]}),e.session.limit=e.current_limit}else if(e.points=R,e.session.limit=k,e.session.format_success=L,i.type.is_error(F))e.throw_error(F.args[0]);else if(e.current_limit>0){for(var Me=[],le=0;le=0;xe--)Te=new o(".",[F[xe],Te]);Me.push(new V(n.goal.replace(new o(",",[new o("=",[S,B[le].variables]),new o("=",[f,Te])])),n.substitution,n))}e.prepend(Me)}};e.__calls.unshift(q)}},"setof/3":function(e,n,t){var s,a=t.args[0],l=t.args[1],f=t.args[2];if(i.type.is_variable(l))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_callable(l))e.throw_error(i.error.type("callable",l,t.indicator));else if(!i.type.is_variable(f)&&!i.type.is_list(f))e.throw_error(i.error.type("list",f,t.indicator));else{var y=e.next_free_variable(),d;l.indicator==="^/2"?(d=l.args[0].variables(),l=l.args[1]):d=[],d=d.concat(a.variables());for(var m=l.variables().filter(function(F){return u(d,F)===-1}),S=new o("[]"),P=m.length-1;P>=0;P--)S=new o(".",[new O(m[P]),S]);var A=new o(",",[l,new o("=",[y,new o(",",[S,a])])]),R=e.points,k=e.session.limit,L=e.session.format_success;e.session.format_success=function(F){return F.substitution},e.add_goal(A,!0,n);var B=[],q=function(F){if(F!==!1&&F!==null&&!i.type.is_error(F)){e.__calls.unshift(q);var H=!1,J=F.links[y.id].args[0],me=F.links[y.id].args[1];for(var be in B)if(!!B.hasOwnProperty(be)){var Re=B[be];if(Re.variables.equals(J)){Re.answers.push(me),H=!0;break}}H||B.push({variables:J,answers:[me]}),e.session.limit=e.current_limit}else if(e.points=R,e.session.limit=k,e.session.format_success=L,i.type.is_error(F))e.throw_error(F.args[0]);else if(e.current_limit>0){for(var Me=[],le=0;le=0;xe--)Te=new o(".",[F[xe],Te]);Me.push(new V(n.goal.replace(new o(",",[new o("=",[S,B[le].variables]),new o("=",[f,Te])])),n.substitution,n))}e.prepend(Me)}};e.__calls.unshift(q)}},"functor/3":function(e,n,t){var s,a=t.args[0],l=t.args[1],f=t.args[2];if(i.type.is_variable(a)&&(i.type.is_variable(l)||i.type.is_variable(f)))e.throw_error(i.error.instantiation("functor/3"));else if(!i.type.is_variable(f)&&!i.type.is_integer(f))e.throw_error(i.error.type("integer",t.args[2],"functor/3"));else if(!i.type.is_variable(l)&&!i.type.is_atomic(l))e.throw_error(i.error.type("atomic",t.args[1],"functor/3"));else if(i.type.is_integer(l)&&i.type.is_integer(f)&&f.value!==0)e.throw_error(i.error.type("atom",t.args[1],"functor/3"));else if(i.type.is_variable(a)){if(t.args[2].value>=0){for(var y=[],d=0;d0&&s<=t.args[1].args.length){var a=new o("=",[t.args[1].args[s-1],t.args[2]]);e.prepend([new V(n.goal.replace(a),n.substitution,n)])}}},"=../2":function(e,n,t){var s;if(i.type.is_variable(t.args[0])&&(i.type.is_variable(t.args[1])||i.type.is_non_empty_list(t.args[1])&&i.type.is_variable(t.args[1].args[0])))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_fully_list(t.args[1]))e.throw_error(i.error.type("list",t.args[1],t.indicator));else if(i.type.is_variable(t.args[0])){if(!i.type.is_variable(t.args[1])){var l=[];for(s=t.args[1].args[1];s.indicator==="./2";)l.push(s.args[0]),s=s.args[1];i.type.is_variable(t.args[0])&&i.type.is_variable(s)?e.throw_error(i.error.instantiation(t.indicator)):l.length===0&&i.type.is_compound(t.args[1].args[0])?e.throw_error(i.error.type("atomic",t.args[1].args[0],t.indicator)):l.length>0&&(i.type.is_compound(t.args[1].args[0])||i.type.is_number(t.args[1].args[0]))?e.throw_error(i.error.type("atom",t.args[1].args[0],t.indicator)):l.length===0?e.prepend([new V(n.goal.replace(new o("=",[t.args[1].args[0],t.args[0]],n)),n.substitution,n)]):e.prepend([new V(n.goal.replace(new o("=",[new o(t.args[1].args[0].id,l),t.args[0]])),n.substitution,n)])}}else{if(i.type.is_atomic(t.args[0]))s=new o(".",[t.args[0],new o("[]")]);else{s=new o("[]");for(var a=t.args[0].args.length-1;a>=0;a--)s=new o(".",[t.args[0].args[a],s]);s=new o(".",[new o(t.args[0].id),s])}e.prepend([new V(n.goal.replace(new o("=",[s,t.args[1]])),n.substitution,n)])}},"copy_term/2":function(e,n,t){var s=t.args[0].rename(e);e.prepend([new V(n.goal.replace(new o("=",[s,t.args[1]])),n.substitution,n.parent)])},"term_variables/2":function(e,n,t){var s=t.args[0],a=t.args[1];if(!i.type.is_fully_list(a))e.throw_error(i.error.type("list",a,t.indicator));else{var l=he(c(yr(s.variables()),function(f){return new O(f)}));e.prepend([new V(n.goal.replace(new o("=",[a,l])),n.substitution,n)])}},"clause/2":function(e,n,t){if(i.type.is_variable(t.args[0]))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_callable(t.args[0]))e.throw_error(i.error.type("callable",t.args[0],t.indicator));else if(!i.type.is_variable(t.args[1])&&!i.type.is_callable(t.args[1]))e.throw_error(i.error.type("callable",t.args[1],t.indicator));else if(e.session.rules[t.args[0].indicator]!==void 0)if(e.is_public_predicate(t.args[0].indicator)){var s=[];for(var a in e.session.rules[t.args[0].indicator])if(!!e.session.rules[t.args[0].indicator].hasOwnProperty(a)){var l=e.session.rules[t.args[0].indicator][a];e.session.renamed_variables={},l=l.rename(e),l.body===null&&(l.body=new o("true"));var f=new o(",",[new o("=",[l.head,t.args[0]]),new o("=",[l.body,t.args[1]])]);s.push(new V(n.goal.replace(f),n.substitution,n))}e.prepend(s)}else e.throw_error(i.error.permission("access","private_procedure",t.args[0].indicator,t.indicator))},"current_predicate/1":function(e,n,t){var s=t.args[0];if(!i.type.is_variable(s)&&(!i.type.is_compound(s)||s.indicator!=="//2"))e.throw_error(i.error.type("predicate_indicator",s,t.indicator));else if(!i.type.is_variable(s)&&!i.type.is_variable(s.args[0])&&!i.type.is_atom(s.args[0]))e.throw_error(i.error.type("atom",s.args[0],t.indicator));else if(!i.type.is_variable(s)&&!i.type.is_variable(s.args[1])&&!i.type.is_integer(s.args[1]))e.throw_error(i.error.type("integer",s.args[1],t.indicator));else{var a=[];for(var l in e.session.rules)if(!!e.session.rules.hasOwnProperty(l)){var f=l.lastIndexOf("/"),y=l.substr(0,f),d=parseInt(l.substr(f+1,l.length-(f+1))),m=new o("/",[new o(y),new E(d,!1)]),S=new o("=",[m,s]);a.push(new V(n.goal.replace(S),n.substitution,n))}e.prepend(a)}},"asserta/1":function(e,n,t){if(i.type.is_variable(t.args[0]))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_callable(t.args[0]))e.throw_error(i.error.type("callable",t.args[0],t.indicator));else{var s,a;t.args[0].indicator===":-/2"?(s=t.args[0].args[0],a=ve(t.args[0].args[1])):(s=t.args[0],a=null),i.type.is_callable(s)?a!==null&&!i.type.is_callable(a)?e.throw_error(i.error.type("callable",a,t.indicator)):e.is_public_predicate(s.indicator)?(e.session.rules[s.indicator]===void 0&&(e.session.rules[s.indicator]=[]),e.session.public_predicates[s.indicator]=!0,e.session.rules[s.indicator]=[new Q(s,a,!0)].concat(e.session.rules[s.indicator]),e.success(n)):e.throw_error(i.error.permission("modify","static_procedure",s.indicator,t.indicator)):e.throw_error(i.error.type("callable",s,t.indicator))}},"assertz/1":function(e,n,t){if(i.type.is_variable(t.args[0]))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_callable(t.args[0]))e.throw_error(i.error.type("callable",t.args[0],t.indicator));else{var s,a;t.args[0].indicator===":-/2"?(s=t.args[0].args[0],a=ve(t.args[0].args[1])):(s=t.args[0],a=null),i.type.is_callable(s)?a!==null&&!i.type.is_callable(a)?e.throw_error(i.error.type("callable",a,t.indicator)):e.is_public_predicate(s.indicator)?(e.session.rules[s.indicator]===void 0&&(e.session.rules[s.indicator]=[]),e.session.public_predicates[s.indicator]=!0,e.session.rules[s.indicator].push(new Q(s,a,!0)),e.success(n)):e.throw_error(i.error.permission("modify","static_procedure",s.indicator,t.indicator)):e.throw_error(i.error.type("callable",s,t.indicator))}},"retract/1":function(e,n,t){if(i.type.is_variable(t.args[0]))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_callable(t.args[0]))e.throw_error(i.error.type("callable",t.args[0],t.indicator));else{var s,a;if(t.args[0].indicator===":-/2"?(s=t.args[0].args[0],a=t.args[0].args[1]):(s=t.args[0],a=new o("true")),typeof n.retract=="undefined")if(e.is_public_predicate(s.indicator)){if(e.session.rules[s.indicator]!==void 0){for(var l=[],f=0;fe.get_flag("max_arity").value)e.throw_error(i.error.representation("max_arity",t.indicator));else{var s=t.args[0].args[0].id+"/"+t.args[0].args[1].value;e.is_public_predicate(s)?(delete e.session.rules[s],e.success(n)):e.throw_error(i.error.permission("modify","static_procedure",s,t.indicator))}},"atom_length/2":function(e,n,t){if(i.type.is_variable(t.args[0]))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_atom(t.args[0]))e.throw_error(i.error.type("atom",t.args[0],t.indicator));else if(!i.type.is_variable(t.args[1])&&!i.type.is_integer(t.args[1]))e.throw_error(i.error.type("integer",t.args[1],t.indicator));else if(i.type.is_integer(t.args[1])&&t.args[1].value<0)e.throw_error(i.error.domain("not_less_than_zero",t.args[1],t.indicator));else{var s=new E(t.args[0].id.length,!1);e.prepend([new V(n.goal.replace(new o("=",[s,t.args[1]])),n.substitution,n)])}},"atom_concat/3":function(e,n,t){var s,a,l=t.args[0],f=t.args[1],y=t.args[2];if(i.type.is_variable(y)&&(i.type.is_variable(l)||i.type.is_variable(f)))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_variable(l)&&!i.type.is_atom(l))e.throw_error(i.error.type("atom",l,t.indicator));else if(!i.type.is_variable(f)&&!i.type.is_atom(f))e.throw_error(i.error.type("atom",f,t.indicator));else if(!i.type.is_variable(y)&&!i.type.is_atom(y))e.throw_error(i.error.type("atom",y,t.indicator));else{var d=i.type.is_variable(l),m=i.type.is_variable(f);if(!d&&!m)a=new o("=",[y,new o(l.id+f.id)]),e.prepend([new V(n.goal.replace(a),n.substitution,n)]);else if(d&&!m)s=y.id.substr(0,y.id.length-f.id.length),s+f.id===y.id&&(a=new o("=",[l,new o(s)]),e.prepend([new V(n.goal.replace(a),n.substitution,n)]));else if(m&&!d)s=y.id.substr(l.id.length),l.id+s===y.id&&(a=new o("=",[f,new o(s)]),e.prepend([new V(n.goal.replace(a),n.substitution,n)]));else{for(var S=[],P=0;P<=y.id.length;P++){var A=new o(y.id.substr(0,P)),R=new o(y.id.substr(P));a=new o(",",[new o("=",[A,l]),new o("=",[R,f])]),S.push(new V(n.goal.replace(a),n.substitution,n))}e.prepend(S)}}},"sub_atom/5":function(e,n,t){var s,a=t.args[0],l=t.args[1],f=t.args[2],y=t.args[3],d=t.args[4];if(i.type.is_variable(a))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_variable(l)&&!i.type.is_integer(l))e.throw_error(i.error.type("integer",l,t.indicator));else if(!i.type.is_variable(f)&&!i.type.is_integer(f))e.throw_error(i.error.type("integer",f,t.indicator));else if(!i.type.is_variable(y)&&!i.type.is_integer(y))e.throw_error(i.error.type("integer",y,t.indicator));else if(i.type.is_integer(l)&&l.value<0)e.throw_error(i.error.domain("not_less_than_zero",l,t.indicator));else if(i.type.is_integer(f)&&f.value<0)e.throw_error(i.error.domain("not_less_than_zero",f,t.indicator));else if(i.type.is_integer(y)&&y.value<0)e.throw_error(i.error.domain("not_less_than_zero",y,t.indicator));else{var m=[],S=[],P=[];if(i.type.is_variable(l))for(s=0;s<=a.id.length;s++)m.push(s);else m.push(l.value);if(i.type.is_variable(f))for(s=0;s<=a.id.length;s++)S.push(s);else S.push(f.value);if(i.type.is_variable(y))for(s=0;s<=a.id.length;s++)P.push(s);else P.push(y.value);var A=[];for(var R in m)if(!!m.hasOwnProperty(R)){s=m[R];for(var k in S)if(!!S.hasOwnProperty(k)){var L=S[k],B=a.id.length-s-L;if(u(P,B)!==-1&&s+L+B===a.id.length){var q=a.id.substr(s,L);if(a.id===a.id.substr(0,s)+q+a.id.substr(s+L,B)){var F=new o("=",[new o(q),d]),H=new o("=",[l,new E(s)]),J=new o("=",[f,new E(L)]),me=new o("=",[y,new E(B)]),be=new o(",",[new o(",",[new o(",",[H,J]),me]),F]);A.push(new V(n.goal.replace(be),n.substitution,n))}}}}e.prepend(A)}},"atom_chars/2":function(e,n,t){var s=t.args[0],a=t.args[1];if(i.type.is_variable(s)&&i.type.is_variable(a))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_variable(s)&&!i.type.is_atom(s))e.throw_error(i.error.type("atom",s,t.indicator));else if(i.type.is_variable(s)){for(var y=a,d=i.type.is_variable(s),m="";y.indicator==="./2";){if(i.type.is_character(y.args[0]))m+=y.args[0].id;else if(i.type.is_variable(y.args[0])&&d){e.throw_error(i.error.instantiation(t.indicator));return}else if(!i.type.is_variable(y.args[0])){e.throw_error(i.error.type("character",y.args[0],t.indicator));return}y=y.args[1]}i.type.is_variable(y)&&d?e.throw_error(i.error.instantiation(t.indicator)):!i.type.is_empty_list(y)&&!i.type.is_variable(y)?e.throw_error(i.error.type("list",a,t.indicator)):e.prepend([new V(n.goal.replace(new o("=",[new o(m),s])),n.substitution,n)])}else{for(var l=new o("[]"),f=s.id.length-1;f>=0;f--)l=new o(".",[new o(s.id.charAt(f)),l]);e.prepend([new V(n.goal.replace(new o("=",[a,l])),n.substitution,n)])}},"atom_codes/2":function(e,n,t){var s=t.args[0],a=t.args[1];if(i.type.is_variable(s)&&i.type.is_variable(a))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_variable(s)&&!i.type.is_atom(s))e.throw_error(i.error.type("atom",s,t.indicator));else if(i.type.is_variable(s)){for(var y=a,d=i.type.is_variable(s),m="";y.indicator==="./2";){if(i.type.is_character_code(y.args[0]))m+=v(y.args[0].value);else if(i.type.is_variable(y.args[0])&&d){e.throw_error(i.error.instantiation(t.indicator));return}else if(!i.type.is_variable(y.args[0])){e.throw_error(i.error.representation("character_code",t.indicator));return}y=y.args[1]}i.type.is_variable(y)&&d?e.throw_error(i.error.instantiation(t.indicator)):!i.type.is_empty_list(y)&&!i.type.is_variable(y)?e.throw_error(i.error.type("list",a,t.indicator)):e.prepend([new V(n.goal.replace(new o("=",[new o(m),s])),n.substitution,n)])}else{for(var l=new o("[]"),f=s.id.length-1;f>=0;f--)l=new o(".",[new E(_(s.id,f),!1),l]);e.prepend([new V(n.goal.replace(new o("=",[a,l])),n.substitution,n)])}},"char_code/2":function(e,n,t){var s=t.args[0],a=t.args[1];if(i.type.is_variable(s)&&i.type.is_variable(a))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_variable(s)&&!i.type.is_character(s))e.throw_error(i.error.type("character",s,t.indicator));else if(!i.type.is_variable(a)&&!i.type.is_integer(a))e.throw_error(i.error.type("integer",a,t.indicator));else if(!i.type.is_variable(a)&&!i.type.is_character_code(a))e.throw_error(i.error.representation("character_code",t.indicator));else if(i.type.is_variable(a)){var l=new E(_(s.id,0),!1);e.prepend([new V(n.goal.replace(new o("=",[l,a])),n.substitution,n)])}else{var f=new o(v(a.value));e.prepend([new V(n.goal.replace(new o("=",[f,s])),n.substitution,n)])}},"number_chars/2":function(e,n,t){var s,a=t.args[0],l=t.args[1];if(i.type.is_variable(a)&&i.type.is_variable(l))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_variable(a)&&!i.type.is_number(a))e.throw_error(i.error.type("number",a,t.indicator));else if(!i.type.is_variable(l)&&!i.type.is_list(l))e.throw_error(i.error.type("list",l,t.indicator));else{var f=i.type.is_variable(a);if(!i.type.is_variable(l)){var y=l,d=!0;for(s="";y.indicator==="./2";){if(i.type.is_character(y.args[0]))s+=y.args[0].id;else if(i.type.is_variable(y.args[0]))d=!1;else if(!i.type.is_variable(y.args[0])){e.throw_error(i.error.type("character",y.args[0],t.indicator));return}y=y.args[1]}if(d=d&&i.type.is_empty_list(y),!i.type.is_empty_list(y)&&!i.type.is_variable(y)){e.throw_error(i.error.type("list",l,t.indicator));return}if(!d&&f){e.throw_error(i.error.instantiation(t.indicator));return}else if(d)if(i.type.is_variable(y)&&f){e.throw_error(i.error.instantiation(t.indicator));return}else{var m=e.parse(s),S=m.value;!i.type.is_number(S)||m.tokens[m.tokens.length-1].space?e.throw_error(i.error.syntax_by_predicate("parseable_number",t.indicator)):e.prepend([new V(n.goal.replace(new o("=",[a,S])),n.substitution,n)]);return}}if(!f){s=a.toString();for(var P=new o("[]"),A=s.length-1;A>=0;A--)P=new o(".",[new o(s.charAt(A)),P]);e.prepend([new V(n.goal.replace(new o("=",[l,P])),n.substitution,n)])}}},"number_codes/2":function(e,n,t){var s,a=t.args[0],l=t.args[1];if(i.type.is_variable(a)&&i.type.is_variable(l))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_variable(a)&&!i.type.is_number(a))e.throw_error(i.error.type("number",a,t.indicator));else if(!i.type.is_variable(l)&&!i.type.is_list(l))e.throw_error(i.error.type("list",l,t.indicator));else{var f=i.type.is_variable(a);if(!i.type.is_variable(l)){var y=l,d=!0;for(s="";y.indicator==="./2";){if(i.type.is_character_code(y.args[0]))s+=v(y.args[0].value);else if(i.type.is_variable(y.args[0]))d=!1;else if(!i.type.is_variable(y.args[0])){e.throw_error(i.error.type("character_code",y.args[0],t.indicator));return}y=y.args[1]}if(d=d&&i.type.is_empty_list(y),!i.type.is_empty_list(y)&&!i.type.is_variable(y)){e.throw_error(i.error.type("list",l,t.indicator));return}if(!d&&f){e.throw_error(i.error.instantiation(t.indicator));return}else if(d)if(i.type.is_variable(y)&&f){e.throw_error(i.error.instantiation(t.indicator));return}else{var m=e.parse(s),S=m.value;!i.type.is_number(S)||m.tokens[m.tokens.length-1].space?e.throw_error(i.error.syntax_by_predicate("parseable_number",t.indicator)):e.prepend([new V(n.goal.replace(new o("=",[a,S])),n.substitution,n)]);return}}if(!f){s=a.toString();for(var P=new o("[]"),A=s.length-1;A>=0;A--)P=new o(".",[new E(_(s,A),!1),P]);e.prepend([new V(n.goal.replace(new o("=",[l,P])),n.substitution,n)])}}},"upcase_atom/2":function(e,n,t){var s=t.args[0],a=t.args[1];i.type.is_variable(s)?e.throw_error(i.error.instantiation(t.indicator)):i.type.is_atom(s)?!i.type.is_variable(a)&&!i.type.is_atom(a)?e.throw_error(i.error.type("atom",a,t.indicator)):e.prepend([new V(n.goal.replace(new o("=",[a,new o(s.id.toUpperCase(),[])])),n.substitution,n)]):e.throw_error(i.error.type("atom",s,t.indicator))},"downcase_atom/2":function(e,n,t){var s=t.args[0],a=t.args[1];i.type.is_variable(s)?e.throw_error(i.error.instantiation(t.indicator)):i.type.is_atom(s)?!i.type.is_variable(a)&&!i.type.is_atom(a)?e.throw_error(i.error.type("atom",a,t.indicator)):e.prepend([new V(n.goal.replace(new o("=",[a,new o(s.id.toLowerCase(),[])])),n.substitution,n)]):e.throw_error(i.error.type("atom",s,t.indicator))},"atomic_list_concat/2":function(e,n,t){var s=t.args[0],a=t.args[1];e.prepend([new V(n.goal.replace(new o("atomic_list_concat",[s,new o("",[]),a])),n.substitution,n)])},"atomic_list_concat/3":function(e,n,t){var s=t.args[0],a=t.args[1],l=t.args[2];if(i.type.is_variable(a)||i.type.is_variable(s)&&i.type.is_variable(l))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_variable(s)&&!i.type.is_list(s))e.throw_error(i.error.type("list",s,t.indicator));else if(!i.type.is_variable(l)&&!i.type.is_atom(l))e.throw_error(i.error.type("atom",l,t.indicator));else if(i.type.is_variable(l)){for(var y="",d=s;i.type.is_term(d)&&d.indicator==="./2";){if(!i.type.is_atom(d.args[0])&&!i.type.is_number(d.args[0])){e.throw_error(i.error.type("atomic",d.args[0],t.indicator));return}y!==""&&(y+=a.id),i.type.is_atom(d.args[0])?y+=d.args[0].id:y+=""+d.args[0].value,d=d.args[1]}y=new o(y,[]),i.type.is_variable(d)?e.throw_error(i.error.instantiation(t.indicator)):!i.type.is_term(d)||d.indicator!=="[]/0"?e.throw_error(i.error.type("list",s,t.indicator)):e.prepend([new V(n.goal.replace(new o("=",[y,l])),n.substitution,n)])}else{var f=he(c(l.id.split(a.id),function(m){return new o(m,[])}));e.prepend([new V(n.goal.replace(new o("=",[f,s])),n.substitution,n)])}},"@=/2":function(e,n,t){i.compare(t.args[0],t.args[1])>0&&e.success(n)},"@>=/2":function(e,n,t){i.compare(t.args[0],t.args[1])>=0&&e.success(n)},"compare/3":function(e,n,t){var s=t.args[0],a=t.args[1],l=t.args[2];if(!i.type.is_variable(s)&&!i.type.is_atom(s))e.throw_error(i.error.type("atom",s,t.indicator));else if(i.type.is_atom(s)&&["<",">","="].indexOf(s.id)===-1)e.throw_error(i.type.domain("order",s,t.indicator));else{var f=i.compare(a,l);f=f===0?"=":f===-1?"<":">",e.prepend([new V(n.goal.replace(new o("=",[s,new o(f,[])])),n.substitution,n)])}},"is/2":function(e,n,t){var s=t.args[1].interpret(e);i.type.is_number(s)?e.prepend([new V(n.goal.replace(new o("=",[t.args[0],s],e.level)),n.substitution,n)]):e.throw_error(s)},"between/3":function(e,n,t){var s=t.args[0],a=t.args[1],l=t.args[2];if(i.type.is_variable(s)||i.type.is_variable(a))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_integer(s))e.throw_error(i.error.type("integer",s,t.indicator));else if(!i.type.is_integer(a))e.throw_error(i.error.type("integer",a,t.indicator));else if(!i.type.is_variable(l)&&!i.type.is_integer(l))e.throw_error(i.error.type("integer",l,t.indicator));else if(i.type.is_variable(l)){var f=[new V(n.goal.replace(new o("=",[l,s])),n.substitution,n)];s.value=l.value&&e.success(n)},"succ/2":function(e,n,t){var s=t.args[0],a=t.args[1];i.type.is_variable(s)&&i.type.is_variable(a)?e.throw_error(i.error.instantiation(t.indicator)):!i.type.is_variable(s)&&!i.type.is_integer(s)?e.throw_error(i.error.type("integer",s,t.indicator)):!i.type.is_variable(a)&&!i.type.is_integer(a)?e.throw_error(i.error.type("integer",a,t.indicator)):!i.type.is_variable(s)&&s.value<0?e.throw_error(i.error.domain("not_less_than_zero",s,t.indicator)):!i.type.is_variable(a)&&a.value<0?e.throw_error(i.error.domain("not_less_than_zero",a,t.indicator)):(i.type.is_variable(a)||a.value>0)&&(i.type.is_variable(s)?e.prepend([new V(n.goal.replace(new o("=",[s,new E(a.value-1,!1)])),n.substitution,n)]):e.prepend([new V(n.goal.replace(new o("=",[a,new E(s.value+1,!1)])),n.substitution,n)]))},"=:=/2":function(e,n,t){var s=i.arithmetic_compare(e,t.args[0],t.args[1]);i.type.is_term(s)?e.throw_error(s):s===0&&e.success(n)},"=\\=/2":function(e,n,t){var s=i.arithmetic_compare(e,t.args[0],t.args[1]);i.type.is_term(s)?e.throw_error(s):s!==0&&e.success(n)},"/2":function(e,n,t){var s=i.arithmetic_compare(e,t.args[0],t.args[1]);i.type.is_term(s)?e.throw_error(s):s>0&&e.success(n)},">=/2":function(e,n,t){var s=i.arithmetic_compare(e,t.args[0],t.args[1]);i.type.is_term(s)?e.throw_error(s):s>=0&&e.success(n)},"var/1":function(e,n,t){i.type.is_variable(t.args[0])&&e.success(n)},"atom/1":function(e,n,t){i.type.is_atom(t.args[0])&&e.success(n)},"atomic/1":function(e,n,t){i.type.is_atomic(t.args[0])&&e.success(n)},"compound/1":function(e,n,t){i.type.is_compound(t.args[0])&&e.success(n)},"integer/1":function(e,n,t){i.type.is_integer(t.args[0])&&e.success(n)},"float/1":function(e,n,t){i.type.is_float(t.args[0])&&e.success(n)},"number/1":function(e,n,t){i.type.is_number(t.args[0])&&e.success(n)},"nonvar/1":function(e,n,t){i.type.is_variable(t.args[0])||e.success(n)},"ground/1":function(e,n,t){t.variables().length===0&&e.success(n)},"acyclic_term/1":function(e,n,t){for(var s=n.substitution.apply(n.substitution),a=t.args[0].variables(),l=0;l0?k[k.length-1]:null,k!==null&&(A=U(e,k,0,e.__get_max_priority(),!1))}if(A.type===h&&A.len===k.length-1&&L.value==="."){A=A.value.rename(e);var B=new o("=",[a,A]);if(y.variables){var q=he(c(yr(A.variables()),function(F){return new O(F)}));B=new o(",",[B,new o("=",[y.variables,q])])}if(y.variable_names){var q=he(c(yr(A.variables()),function(H){var J;for(J in e.session.renamed_variables)if(e.session.renamed_variables.hasOwnProperty(J)&&e.session.renamed_variables[J]===H)break;return new o("=",[new o(J,[]),new O(H)])}));B=new o(",",[B,new o("=",[y.variable_names,q])])}if(y.singletons){var q=he(c(new Q(A,null).singleton_variables(),function(H){var J;for(J in e.session.renamed_variables)if(e.session.renamed_variables.hasOwnProperty(J)&&e.session.renamed_variables[J]===H)break;return new o("=",[new o(J,[]),new O(H)])}));B=new o(",",[B,new o("=",[y.singletons,q])])}e.prepend([new V(n.goal.replace(B),n.substitution,n)])}else A.type===h?e.throw_error(i.error.syntax(k[A.len],"unexpected token",!1)):e.throw_error(A.value)}}},"write/1":function(e,n,t){var s=t.args[0];e.prepend([new V(n.goal.replace(new o(",",[new o("current_output",[new O("S")]),new o("write",[new O("S"),s])])),n.substitution,n)])},"write/2":function(e,n,t){var s=t.args[0],a=t.args[1];e.prepend([new V(n.goal.replace(new o("write_term",[s,a,new o(".",[new o("quoted",[new o("false",[])]),new o(".",[new o("ignore_ops",[new o("false")]),new o(".",[new o("numbervars",[new o("true")]),new o("[]",[])])])])])),n.substitution,n)])},"writeq/1":function(e,n,t){var s=t.args[0];e.prepend([new V(n.goal.replace(new o(",",[new o("current_output",[new O("S")]),new o("writeq",[new O("S"),s])])),n.substitution,n)])},"writeq/2":function(e,n,t){var s=t.args[0],a=t.args[1];e.prepend([new V(n.goal.replace(new o("write_term",[s,a,new o(".",[new o("quoted",[new o("true",[])]),new o(".",[new o("ignore_ops",[new o("false")]),new o(".",[new o("numbervars",[new o("true")]),new o("[]",[])])])])])),n.substitution,n)])},"write_canonical/1":function(e,n,t){var s=t.args[0];e.prepend([new V(n.goal.replace(new o(",",[new o("current_output",[new O("S")]),new o("write_canonical",[new O("S"),s])])),n.substitution,n)])},"write_canonical/2":function(e,n,t){var s=t.args[0],a=t.args[1];e.prepend([new V(n.goal.replace(new o("write_term",[s,a,new o(".",[new o("quoted",[new o("true",[])]),new o(".",[new o("ignore_ops",[new o("true")]),new o(".",[new o("numbervars",[new o("false")]),new o("[]",[])])])])])),n.substitution,n)])},"write_term/2":function(e,n,t){var s=t.args[0],a=t.args[1];e.prepend([new V(n.goal.replace(new o(",",[new o("current_output",[new O("S")]),new o("write_term",[new O("S"),s,a])])),n.substitution,n)])},"write_term/3":function(e,n,t){var s=t.args[0],a=t.args[1],l=t.args[2],f=i.type.is_stream(s)?s:e.get_stream_by_alias(s.id);if(i.type.is_variable(s)||i.type.is_variable(l))e.throw_error(i.error.instantiation(t.indicator));else if(!i.type.is_list(l))e.throw_error(i.error.type("list",l,t.indicator));else if(!i.type.is_stream(s)&&!i.type.is_atom(s))e.throw_error(i.error.domain("stream_or_alias",s,t.indicator));else if(!i.type.is_stream(f)||f.stream===null)e.throw_error(i.error.existence("stream",s,t.indicator));else if(f.input)e.throw_error(i.error.permission("output","stream",s,t.indicator));else if(f.type==="binary")e.throw_error(i.error.permission("output","binary_stream",s,t.indicator));else if(f.position==="past_end_of_stream"&&f.eof_action==="error")e.throw_error(i.error.permission("output","past_end_of_stream",s,t.indicator));else{for(var y={},d=l,m;i.type.is_term(d)&&d.indicator==="./2";){if(m=d.args[0],i.type.is_variable(m)){e.throw_error(i.error.instantiation(t.indicator));return}else if(!i.type.is_write_option(m)){e.throw_error(i.error.domain("write_option",m,t.indicator));return}y[m.id]=m.args[0].id==="true",d=d.args[1]}if(d.indicator!=="[]/0"){i.type.is_variable(d)?e.throw_error(i.error.instantiation(t.indicator)):e.throw_error(i.error.type("list",l,t.indicator));return}else{y.session=e.session;var S=a.toString(y);f.stream.put(S,f.position),typeof f.position=="number"&&(f.position+=S.length),e.success(n)}}},"halt/0":function(e,n,t){e.points=[]},"halt/1":function(e,n,t){var s=t.args[0];i.type.is_variable(s)?e.throw_error(i.error.instantiation(t.indicator)):i.type.is_integer(s)?e.points=[]:e.throw_error(i.error.type("integer",s,t.indicator))},"current_prolog_flag/2":function(e,n,t){var s=t.args[0],a=t.args[1];if(!i.type.is_variable(s)&&!i.type.is_atom(s))e.throw_error(i.error.type("atom",s,t.indicator));else if(!i.type.is_variable(s)&&!i.type.is_flag(s))e.throw_error(i.error.domain("prolog_flag",s,t.indicator));else{var l=[];for(var f in i.flag)if(!!i.flag.hasOwnProperty(f)){var y=new o(",",[new o("=",[new o(f),s]),new o("=",[e.get_flag(f),a])]);l.push(new V(n.goal.replace(y),n.substitution,n))}e.prepend(l)}},"set_prolog_flag/2":function(e,n,t){var s=t.args[0],a=t.args[1];i.type.is_variable(s)||i.type.is_variable(a)?e.throw_error(i.error.instantiation(t.indicator)):i.type.is_atom(s)?i.type.is_flag(s)?i.type.is_value_flag(s,a)?i.type.is_modifiable_flag(s)?(e.session.flag[s.id]=a,e.success(n)):e.throw_error(i.error.permission("modify","flag",s)):e.throw_error(i.error.domain("flag_value",new o("+",[s,a]),t.indicator)):e.throw_error(i.error.domain("prolog_flag",s,t.indicator)):e.throw_error(i.error.type("atom",s,t.indicator))}},flag:{bounded:{allowed:[new o("true"),new o("false")],value:new o("true"),changeable:!1},max_integer:{allowed:[new E(Number.MAX_SAFE_INTEGER)],value:new E(Number.MAX_SAFE_INTEGER),changeable:!1},min_integer:{allowed:[new E(Number.MIN_SAFE_INTEGER)],value:new E(Number.MIN_SAFE_INTEGER),changeable:!1},integer_rounding_function:{allowed:[new o("down"),new o("toward_zero")],value:new o("toward_zero"),changeable:!1},char_conversion:{allowed:[new o("on"),new o("off")],value:new o("on"),changeable:!0},debug:{allowed:[new o("on"),new o("off")],value:new o("off"),changeable:!0},max_arity:{allowed:[new o("unbounded")],value:new o("unbounded"),changeable:!1},unknown:{allowed:[new o("error"),new o("fail"),new o("warning")],value:new o("error"),changeable:!0},double_quotes:{allowed:[new o("chars"),new o("codes"),new o("atom")],value:new o("codes"),changeable:!0},occurs_check:{allowed:[new o("false"),new o("true")],value:new o("false"),changeable:!0},dialect:{allowed:[new o("tau")],value:new o("tau"),changeable:!1},version_data:{allowed:[new o("tau",[new E(r.major,!1),new E(r.minor,!1),new E(r.patch,!1),new o(r.status)])],value:new o("tau",[new E(r.major,!1),new E(r.minor,!1),new E(r.patch,!1),new o(r.status)]),changeable:!1},nodejs:{allowed:[new o("yes"),new o("no")],value:new o(typeof ie!="undefined"&&ie.exports?"yes":"no"),changeable:!1}},unify:function(e,n,t){t=t===void 0?!1:t;for(var s=[{left:e,right:n}],a={};s.length!==0;){var l=s.pop();if(e=l.left,n=l.right,i.type.is_term(e)&&i.type.is_term(n)){if(e.indicator!==n.indicator)return null;for(var f=0;fa.value?1:0:a}else return s},operate:function(e,n){if(i.type.is_operator(n)){for(var t=i.type.is_operator(n),s=[],a,l=!1,f=0;fe.get_flag("max_integer").value||a0?e.start+e.matches[0].length:e.start,a=t?new o("token_not_found"):new o("found",[new o(e.value.toString())]),l=new o(".",[new o("line",[new E(e.line+1)]),new o(".",[new o("column",[new E(s+1)]),new o(".",[a,new o("[]",[])])])]);return new o("error",[new o("syntax_error",[new o(n)]),l])},syntax_by_predicate:function(e,n){return new o("error",[new o("syntax_error",[new o(e)]),ae(n)])}},warning:{singleton:function(e,n,t){for(var s=new o("[]"),a=e.length-1;a>=0;a--)s=new o(".",[new O(e[a]),s]);return new o("warning",[new o("singleton_variables",[s,ae(n)]),new o(".",[new o("line",[new E(t,!1)]),new o("[]")])])},failed_goal:function(e,n){return new o("warning",[new o("failed_goal",[e]),new o(".",[new o("line",[new E(n,!1)]),new o("[]")])])}},format_variable:function(e){return"_"+e},format_answer:function(e,n,t){n instanceof D&&(n=n.thread);var t=t||{};if(t.session=n?n.session:void 0,i.type.is_error(e))return"uncaught exception: "+e.args[0].toString();if(e===!1)return"false.";if(e===null)return"limit exceeded ;";var s=0,a="";if(i.type.is_substitution(e)){var l=e.domain(!0);e=e.filter(function(d,m){return!i.type.is_variable(m)||l.indexOf(m.id)!==-1&&d!==m.id})}for(var f in e.links)!e.links.hasOwnProperty(f)||(s++,a!==""&&(a+=", "),a+=f.toString(t)+" = "+e.links[f].toString(t));var y=typeof n=="undefined"||n.points.length>0?" ;":".";return s===0?"true"+y:a+y},flatten_error:function(e){if(!i.type.is_error(e))return null;e=e.args[0];var n={};return n.type=e.args[0].id,n.thrown=n.type==="syntax_error"?null:e.args[1].id,n.expected=null,n.found=null,n.representation=null,n.existence=null,n.existence_type=null,n.line=null,n.column=null,n.permission_operation=null,n.permission_type=null,n.evaluation_type=null,n.type==="type_error"||n.type==="domain_error"?(n.expected=e.args[0].args[0].id,n.found=e.args[0].args[1].toString()):n.type==="syntax_error"?e.args[1].indicator==="./2"?(n.expected=e.args[0].args[0].id,n.found=e.args[1].args[1].args[1].args[0],n.found=n.found.id==="token_not_found"?n.found.id:n.found.args[0].id,n.line=e.args[1].args[0].args[0].value,n.column=e.args[1].args[1].args[0].args[0].value):n.thrown=e.args[1].id:n.type==="permission_error"?(n.found=e.args[0].args[2].toString(),n.permission_operation=e.args[0].args[0].id,n.permission_type=e.args[0].args[1].id):n.type==="evaluation_error"?n.evaluation_type=e.args[0].args[0].id:n.type==="representation_error"?n.representation=e.args[0].args[0].id:n.type==="existence_error"&&(n.existence=e.args[0].args[1].toString(),n.existence_type=e.args[0].args[0].id),n},create:function(e){return new i.type.Session(e)}};typeof ie!="undefined"?ie.exports=i:window.pl=i})()});var er=I((qu,rt)=>{var is=Array.isArray;rt.exports=is});var nt=I(($u,tt)=>{var ss=typeof global=="object"&&global&&global.Object===Object&&global;tt.exports=ss});var rr=I((Du,it)=>{var as=nt(),os=typeof self=="object"&&self&&self.Object===Object&&self,us=as||os||Function("return this")();it.exports=us});var tr=I((Xu,st)=>{var ls=rr(),cs=ls.Symbol;st.exports=cs});var lt=I((Bu,at)=>{var ot=tr(),ut=Object.prototype,fs=ut.hasOwnProperty,ps=ut.toString,De=ot?ot.toStringTag:void 0;function ys(r){var u=fs.call(r,De),p=r[De];try{r[De]=void 0;var c=!0}catch(_){}var w=ps.call(r);return c&&(u?r[De]=p:delete r[De]),w}at.exports=ys});var ft=I((Fu,ct)=>{var _s=Object.prototype,ws=_s.toString;function gs(r){return ws.call(r)}ct.exports=gs});var Pr=I((zu,pt)=>{var yt=tr(),ds=lt(),vs=ft(),hs="[object Null]",ms="[object Undefined]",_t=yt?yt.toStringTag:void 0;function bs(r){return r==null?r===void 0?ms:hs:_t&&_t in Object(r)?ds(r):vs(r)}pt.exports=bs});var gt=I((Wu,wt)=>{function Ts(r){return r!=null&&typeof r=="object"}wt.exports=Ts});var nr=I((Lu,dt)=>{var xs=Pr(),Vs=gt(),Ss="[object Symbol]";function ks(r){return typeof r=="symbol"||Vs(r)&&xs(r)==Ss}dt.exports=ks});var ht=I((Hu,vt)=>{var Ps=er(),Cs=nr(),Os=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,Is=/^\w*$/;function Es(r,u){if(Ps(r))return!1;var p=typeof r;return p=="number"||p=="symbol"||p=="boolean"||r==null||Cs(r)?!0:Is.test(r)||!Os.test(r)||u!=null&&r in Object(u)}vt.exports=Es});var ir=I((Gu,mt)=>{function As(r){var u=typeof r;return r!=null&&(u=="object"||u=="function")}mt.exports=As});var Tt=I((Yu,bt)=>{var Ns=Pr(),Rs=ir(),Ms="[object AsyncFunction]",qs="[object Function]",$s="[object GeneratorFunction]",Ds="[object Proxy]";function Xs(r){if(!Rs(r))return!1;var u=Ns(r);return u==qs||u==$s||u==Ms||u==Ds}bt.exports=Xs});var Vt=I((Uu,xt)=>{var Bs=rr(),Fs=Bs["__core-js_shared__"];xt.exports=Fs});var Pt=I((Zu,St)=>{var Cr=Vt(),kt=function(){var r=/[^.]+$/.exec(Cr&&Cr.keys&&Cr.keys.IE_PROTO||"");return r?"Symbol(src)_1."+r:""}();function zs(r){return!!kt&&kt in r}St.exports=zs});var Ot=I((Qu,Ct)=>{var Ws=Function.prototype,Ls=Ws.toString;function Hs(r){if(r!=null){try{return Ls.call(r)}catch(u){}try{return r+""}catch(u){}}return""}Ct.exports=Hs});var Et=I((Ju,It)=>{var Gs=Tt(),Ys=Pt(),Us=ir(),Zs=Ot(),Qs=/[\\^$.*+?()[\]{}|]/g,Js=/^\[object .+?Constructor\]$/,Ks=Function.prototype,js=Object.prototype,ea=Ks.toString,ra=js.hasOwnProperty,ta=RegExp("^"+ea.call(ra).replace(Qs,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");function na(r){if(!Us(r)||Ys(r))return!1;var u=Gs(r)?ta:Js;return u.test(Zs(r))}It.exports=na});var Nt=I((Ku,At)=>{function ia(r,u){return r==null?void 0:r[u]}At.exports=ia});var sr=I((ju,Rt)=>{var sa=Et(),aa=Nt();function oa(r,u){var p=aa(r,u);return sa(p)?p:void 0}Rt.exports=oa});var Xe=I((el,Mt)=>{var ua=sr(),la=ua(Object,"create");Mt.exports=la});var Dt=I((rl,qt)=>{var $t=Xe();function ca(){this.__data__=$t?$t(null):{},this.size=0}qt.exports=ca});var Bt=I((tl,Xt)=>{function fa(r){var u=this.has(r)&&delete this.__data__[r];return this.size-=u?1:0,u}Xt.exports=fa});var zt=I((nl,Ft)=>{var pa=Xe(),ya="__lodash_hash_undefined__",_a=Object.prototype,wa=_a.hasOwnProperty;function ga(r){var u=this.__data__;if(pa){var p=u[r];return p===ya?void 0:p}return wa.call(u,r)?u[r]:void 0}Ft.exports=ga});var Lt=I((il,Wt)=>{var da=Xe(),va=Object.prototype,ha=va.hasOwnProperty;function ma(r){var u=this.__data__;return da?u[r]!==void 0:ha.call(u,r)}Wt.exports=ma});var Gt=I((sl,Ht)=>{var ba=Xe(),Ta="__lodash_hash_undefined__";function xa(r,u){var p=this.__data__;return this.size+=this.has(r)?0:1,p[r]=ba&&u===void 0?Ta:u,this}Ht.exports=xa});var Ut=I((al,Yt)=>{var Va=Dt(),Sa=Bt(),ka=zt(),Pa=Lt(),Ca=Gt();function Ie(r){var u=-1,p=r==null?0:r.length;for(this.clear();++u{function Oa(){this.__data__=[],this.size=0}Zt.exports=Oa});var Or=I((ul,Jt)=>{function Ia(r,u){return r===u||r!==r&&u!==u}Jt.exports=Ia});var Be=I((ll,Kt)=>{var Ea=Or();function Aa(r,u){for(var p=r.length;p--;)if(Ea(r[p][0],u))return p;return-1}Kt.exports=Aa});var en=I((cl,jt)=>{var Na=Be(),Ra=Array.prototype,Ma=Ra.splice;function qa(r){var u=this.__data__,p=Na(u,r);if(p<0)return!1;var c=u.length-1;return p==c?u.pop():Ma.call(u,p,1),--this.size,!0}jt.exports=qa});var tn=I((fl,rn)=>{var $a=Be();function Da(r){var u=this.__data__,p=$a(u,r);return p<0?void 0:u[p][1]}rn.exports=Da});var sn=I((pl,nn)=>{var Xa=Be();function Ba(r){return Xa(this.__data__,r)>-1}nn.exports=Ba});var on=I((yl,an)=>{var Fa=Be();function za(r,u){var p=this.__data__,c=Fa(p,r);return c<0?(++this.size,p.push([r,u])):p[c][1]=u,this}an.exports=za});var ln=I((_l,un)=>{var Wa=Qt(),La=en(),Ha=tn(),Ga=sn(),Ya=on();function Ee(r){var u=-1,p=r==null?0:r.length;for(this.clear();++u{var Ua=sr(),Za=rr(),Qa=Ua(Za,"Map");cn.exports=Qa});var _n=I((gl,pn)=>{var yn=Ut(),Ja=ln(),Ka=fn();function ja(){this.size=0,this.__data__={hash:new yn,map:new(Ka||Ja),string:new yn}}pn.exports=ja});var gn=I((dl,wn)=>{function eo(r){var u=typeof r;return u=="string"||u=="number"||u=="symbol"||u=="boolean"?r!=="__proto__":r===null}wn.exports=eo});var Fe=I((vl,dn)=>{var ro=gn();function to(r,u){var p=r.__data__;return ro(u)?p[typeof u=="string"?"string":"hash"]:p.map}dn.exports=to});var hn=I((hl,vn)=>{var no=Fe();function io(r){var u=no(this,r).delete(r);return this.size-=u?1:0,u}vn.exports=io});var bn=I((ml,mn)=>{var so=Fe();function ao(r){return so(this,r).get(r)}mn.exports=ao});var xn=I((bl,Tn)=>{var oo=Fe();function uo(r){return oo(this,r).has(r)}Tn.exports=uo});var Sn=I((Tl,Vn)=>{var lo=Fe();function co(r,u){var p=lo(this,r),c=p.size;return p.set(r,u),this.size+=p.size==c?0:1,this}Vn.exports=co});var Pn=I((xl,kn)=>{var fo=_n(),po=hn(),yo=bn(),_o=xn(),wo=Sn();function Ae(r){var u=-1,p=r==null?0:r.length;for(this.clear();++u{var On=Pn(),go="Expected a function";function Ir(r,u){if(typeof r!="function"||u!=null&&typeof u!="function")throw new TypeError(go);var p=function(){var c=arguments,w=u?u.apply(this,c):c[0],_=p.cache;if(_.has(w))return _.get(w);var v=r.apply(this,c);return p.cache=_.set(w,v)||_,v};return p.cache=new(Ir.Cache||On),p}Ir.Cache=On;Cn.exports=Ir});var An=I((Sl,En)=>{var vo=In(),ho=500;function mo(r){var u=vo(r,function(c){return p.size===ho&&p.clear(),c}),p=u.cache;return u}En.exports=mo});var Rn=I((kl,Nn)=>{var bo=An(),To=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,xo=/\\(\\)?/g,Vo=bo(function(r){var u=[];return r.charCodeAt(0)===46&&u.push(""),r.replace(To,function(p,c,w,_){u.push(w?_.replace(xo,"$1"):c||p)}),u});Nn.exports=Vo});var qn=I((Pl,Mn)=>{function So(r,u){for(var p=-1,c=r==null?0:r.length,w=Array(c);++p{var Dn=tr(),ko=qn(),Po=er(),Co=nr(),Oo=1/0,Xn=Dn?Dn.prototype:void 0,Bn=Xn?Xn.toString:void 0;function Fn(r){if(typeof r=="string")return r;if(Po(r))return ko(r,Fn)+"";if(Co(r))return Bn?Bn.call(r):"";var u=r+"";return u=="0"&&1/r==-Oo?"-0":u}$n.exports=Fn});var Ln=I((Ol,Wn)=>{var Io=zn();function Eo(r){return r==null?"":Io(r)}Wn.exports=Eo});var ar=I((Il,Hn)=>{var Ao=er(),No=ht(),Ro=Rn(),Mo=Ln();function qo(r,u){return Ao(r)?r:No(r,u)?[r]:Ro(Mo(r))}Hn.exports=qo});var or=I((El,Gn)=>{var $o=nr(),Do=1/0;function Xo(r){if(typeof r=="string"||$o(r))return r;var u=r+"";return u=="0"&&1/r==-Do?"-0":u}Gn.exports=Xo});var Er=I((Al,Yn)=>{var Bo=ar(),Fo=or();function zo(r,u){u=Bo(u,r);for(var p=0,c=u.length;r!=null&&p{var Wo=Er();function Lo(r,u,p){var c=r==null?void 0:Wo(r,u);return c===void 0?p:c}Un.exports=Lo});var li=I((Ul,ui)=>{var Jo=sr(),Ko=function(){try{var r=Jo(Object,"defineProperty");return r({},"",{}),r}catch(u){}}();ui.exports=Ko});var pi=I((Zl,ci)=>{var fi=li();function jo(r,u,p){u=="__proto__"&&fi?fi(r,u,{configurable:!0,enumerable:!0,value:p,writable:!0}):r[u]=p}ci.exports=jo});var _i=I((Ql,yi)=>{var eu=pi(),ru=Or(),tu=Object.prototype,nu=tu.hasOwnProperty;function iu(r,u,p){var c=r[u];(!(nu.call(r,u)&&ru(c,p))||p===void 0&&!(u in r))&&eu(r,u,p)}yi.exports=iu});var gi=I((Jl,wi)=>{var su=9007199254740991,au=/^(?:0|[1-9]\d*)$/;function ou(r,u){var p=typeof r;return u=u==null?su:u,!!u&&(p=="number"||p!="symbol"&&au.test(r))&&r>-1&&r%1==0&&r{var uu=_i(),lu=ar(),cu=gi(),vi=ir(),fu=or();function pu(r,u,p,c){if(!vi(r))return r;u=lu(u,r);for(var w=-1,_=u.length,v=_-1,g=r;g!=null&&++w<_;){var h=fu(u[w]),x=p;if(h==="__proto__"||h==="constructor"||h==="prototype")return r;if(w!=v){var T=g[h];x=c?c(T,h,g):void 0,x===void 0&&(x=vi(T)?T:cu(u[w+1])?[]:{})}uu(g,h,x),g=g[h]}return r}di.exports=pu});var bi=I((jl,mi)=>{var yu=hi();function _u(r,u,p){return r==null?r:yu(r,u,p)}mi.exports=_u});var xi=I((ec,Ti)=>{function wu(r){var u=r==null?0:r.length;return u?r[u-1]:void 0}Ti.exports=wu});var Si=I((rc,Vi)=>{function gu(r,u,p){var c=-1,w=r.length;u<0&&(u=-u>w?0:w+u),p=p>w?w:p,p<0&&(p+=w),w=u>p?0:p-u>>>0,u>>>=0;for(var _=Array(w);++c{var du=Er(),vu=Si();function hu(r,u){return u.length<2?r:du(r,vu(u,0,-1))}ki.exports=hu});var Oi=I((nc,Ci)=>{var mu=ar(),bu=xi(),Tu=Pi(),xu=or();function Vu(r,u){return u=mu(u,r),r=Tu(r,u),r==null||delete r[xu(bu(u))]}Ci.exports=Vu});var Ei=I((ic,Ii)=>{var Su=Oi();function ku(r,u){return r==null?!0:Su(r,u)}Ii.exports=ku});var Ou={};Qi(Ou,{default:()=>Eu});var $i=G(require("@yarnpkg/core"));var ni=G(require("@yarnpkg/cli")),ur=G(require("@yarnpkg/core")),ii=G(require("@yarnpkg/core")),Le=G(require("clipanion"));var ge=G(require("@yarnpkg/core")),ue=G(require("@yarnpkg/core")),Ne=G(require("@yarnpkg/fslib")),jn=G(Xr()),ze=G(kr());var Nr=G(require("@yarnpkg/core")),Rr=G(Ar()),re=G(kr()),Zn=G(require("vm")),{is_atom:we,is_variable:Ho,is_instantiated_list:Go}=re.default.type;function Qn(r,u,p){r.prepend(p.map(c=>new re.default.type.State(u.goal.replace(c),u.substitution,u)))}var Jn=new WeakMap;function Mr(r){let u=Jn.get(r.session);if(u==null)throw new Error("Assertion failed: A project should have been registered for the active session");return u}var Yo=new re.default.type.Module("constraints",{["project_workspaces_by_descriptor/3"]:(r,u,p)=>{let[c,w,_]=p.args;if(!we(c)||!we(w)){r.throwError(re.default.error.instantiation(p.indicator));return}let v=Nr.structUtils.parseIdent(c.id),g=Nr.structUtils.makeDescriptor(v,w.id),x=Mr(r).tryWorkspaceByDescriptor(g);Ho(_)&&x!==null&&Qn(r,u,[new re.default.type.Term("=",[_,new re.default.type.Term(String(x.relativeCwd))])]),we(_)&&x!==null&&x.relativeCwd===_.id&&r.success(u)},["workspace_field/3"]:(r,u,p)=>{let[c,w,_]=p.args;if(!we(c)||!we(w)){r.throwError(re.default.error.instantiation(p.indicator));return}let g=Mr(r).tryWorkspaceByCwd(c.id);if(g==null)return;let h=(0,Rr.default)(g.manifest.raw,w.id);typeof h!="undefined"&&Qn(r,u,[new re.default.type.Term("=",[_,new re.default.type.Term(String(h))])])},["workspace_field_test/3"]:(r,u,p)=>{let[c,w,_]=p.args;r.prepend([new re.default.type.State(u.goal.replace(new re.default.type.Term("workspace_field_test",[c,w,_,new re.default.type.Term("[]",[])])),u.substitution,u)])},["workspace_field_test/4"]:(r,u,p)=>{let[c,w,_,v]=p.args;if(!we(c)||!we(w)||!we(_)||!Go(v)){r.throwError(re.default.error.instantiation(p.indicator));return}let h=Mr(r).tryWorkspaceByCwd(c.id);if(h==null)return;let x=(0,Rr.default)(h.manifest.raw,w.id);if(typeof x=="undefined")return;let T={$$:x};for(let[C,N]of v.toJavaScript().entries())T[`$${C}`]=N;Zn.default.runInNewContext(_.id,T)&&r.success(u)}},["project_workspaces_by_descriptor/3","workspace_field/3","workspace_field_test/3","workspace_field_test/4"]);function Kn(r,u){Jn.set(r,u),r.consult(`:- use_module(library(${Yo.id})).`)}(0,jn.default)(ze.default);var We;(function(c){c.Dependencies="dependencies",c.DevDependencies="devDependencies",c.PeerDependencies="peerDependencies"})(We||(We={}));var ei=[We.Dependencies,We.DevDependencies,We.PeerDependencies];function j(r){if(r instanceof ze.default.type.Num)return r.value;if(r instanceof ze.default.type.Term){if(r.args.length===0)return r.id;switch(r.indicator){case"throw/1":return j(r.args[0]);case"error/1":return j(r.args[0]);case"error/2":return Object.assign(j(r.args[0]),...j(r.args[1]));case"syntax_error/1":return new ge.ReportError(ge.MessageName.PROLOG_SYNTAX_ERROR,`Syntax error: ${j(r.args[0])}`);case"existence_error/2":return new ge.ReportError(ge.MessageName.PROLOG_EXISTENCE_ERROR,`Existence error: ${j(r.args[0])} ${j(r.args[1])} not found`);case"line/1":return{line:j(r.args[0])};case"column/1":return{column:j(r.args[0])};case"found/1":return{found:j(r.args[0])};case"./2":return[j(r.args[0])].concat(j(r.args[1]));case"//2":return`${j(r.args[0])}/${j(r.args[1])}`}}throw`couldn't pretty print because of unsupported node ${r}`}function ri(r){let u;try{u=j(r)}catch(p){throw typeof p=="string"?new ge.ReportError(ge.MessageName.PROLOG_UNKNOWN_ERROR,`Unknown error: ${r} (note: ${p})`):p}return typeof u.line!="undefined"&&typeof u.column!="undefined"&&(u.message+=` at line ${u.line}, column ${u.column}`),u}var ti=class{constructor(u,p){this.session=ze.default.create(),Kn(this.session,u),this.session.consult(":- use_module(library(lists))."),this.session.consult(p)}fetchNextAnswer(){return new Promise(u=>{this.session.answer(p=>{u(p)})})}async*makeQuery(u){let p=this.session.query(u);if(p!==!0)throw ri(p);for(;;){let c=await this.fetchNextAnswer();if(!c)break;if(c.id==="throw")throw ri(c);yield c}}};function ke(r){return r.id==="null"?null:`${r.toJavaScript()}`}function Uo(r){if(r.id==="null")return null;{let u=r.toJavaScript();if(typeof u!="string")return JSON.stringify(u);try{return JSON.stringify(JSON.parse(u))}catch{return JSON.stringify(u)}}}var fe=class{constructor(u){this.source="";this.project=u;let p=u.configuration.get("constraintsPath");Ne.xfs.existsSync(p)&&(this.source=Ne.xfs.readFileSync(p,"utf8"))}static async find(u){return new fe(u)}getProjectDatabase(){let u="";for(let p of ei)u+=`dependency_type(${p}). -`;for(let p of this.project.workspacesByCwd.values()){let c=p.relativeCwd;u+=`workspace(${de(c)}). -`,u+=`workspace_ident(${de(c)}, ${de(ue.structUtils.stringifyIdent(p.locator))}). -`,u+=`workspace_version(${de(c)}, ${de(p.manifest.version)}). -`;for(let w of ei)for(let _ of p.manifest[w].values())u+=`workspace_has_dependency(${de(c)}, ${de(ue.structUtils.stringifyIdent(_))}, ${de(_.range)}, ${w}). -`}return u+=`workspace(_) :- false. -`,u+=`workspace_ident(_, _) :- false. -`,u+=`workspace_version(_, _) :- false. -`,u+=`workspace_has_dependency(_, _, _, _) :- false. -`,u}getDeclarations(){let u="";return u+=`gen_enforced_dependency(_, _, _, _) :- false. -`,u+=`gen_enforced_field(_, _, _) :- false. -`,u}get fullSource(){return`${this.getProjectDatabase()} -${this.source} -${this.getDeclarations()}`}createSession(){return new ti(this.project,this.fullSource)}async process(){let u=this.createSession();return{enforcedDependencies:await this.genEnforcedDependencies(u),enforcedFields:await this.genEnforcedFields(u)}}async genEnforcedDependencies(u){let p=[];for await(let c of u.makeQuery("workspace(WorkspaceCwd), dependency_type(DependencyType), gen_enforced_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, DependencyType).")){let w=Ne.ppath.resolve(this.project.cwd,ke(c.links.WorkspaceCwd)),_=ke(c.links.DependencyIdent),v=ke(c.links.DependencyRange),g=ke(c.links.DependencyType);if(w===null||_===null)throw new Error("Invalid rule");let h=this.project.getWorkspaceByCwd(w),x=ue.structUtils.parseIdent(_);p.push({workspace:h,dependencyIdent:x,dependencyRange:v,dependencyType:g})}return ue.miscUtils.sortMap(p,[({dependencyRange:c})=>c!==null?"0":"1",({workspace:c})=>ue.structUtils.stringifyIdent(c.locator),({dependencyIdent:c})=>ue.structUtils.stringifyIdent(c)])}async genEnforcedFields(u){let p=[];for await(let c of u.makeQuery("workspace(WorkspaceCwd), gen_enforced_field(WorkspaceCwd, FieldPath, FieldValue).")){let w=Ne.ppath.resolve(this.project.cwd,ke(c.links.WorkspaceCwd)),_=ke(c.links.FieldPath),v=Uo(c.links.FieldValue);if(w===null||_===null)throw new Error("Invalid rule");let g=this.project.getWorkspaceByCwd(w);p.push({workspace:g,fieldPath:_,fieldValue:v})}return ue.miscUtils.sortMap(p,[({workspace:c})=>ue.structUtils.stringifyIdent(c.locator),({fieldPath:c})=>c])}async*query(u){let p=this.createSession();for await(let c of p.makeQuery(u)){let w={};for(let[_,v]of Object.entries(c.links))_!=="_"&&(w[_]=ke(v));yield w}}};function de(r){return typeof r=="string"?`'${r}'`:"[]"}var He=class extends ni.BaseCommand{constructor(){super(...arguments);this.json=Le.Option.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.query=Le.Option.String()}async execute(){let u=await ur.Configuration.find(this.context.cwd,this.context.plugins),{project:p}=await ur.Project.find(u,this.context.cwd),c=await fe.find(p),w=this.query;return w.endsWith(".")||(w=`${w}.`),(await ii.StreamReport.start({configuration:u,json:this.json,stdout:this.context.stdout},async v=>{for await(let g of c.query(w)){let h=Array.from(Object.entries(g)),x=h.length,T=h.reduce((b,[C])=>Math.max(b,C.length),0);for(let b=0;b{let v=new Set,g=[];for(let h=0,x=this.fix?10:1;h{await h.persistManifest()}));for(let[h,x]of g)_.reportError(h,x)});return w.hasErrors()?w.exitCode():0}};Ye.paths=[["constraints"]],Ye.usage=fr.Command.Usage({category:"Constraints-related commands",description:"check that the project constraints are met",details:` - This command will run constraints on your project and emit errors for each one that is found but isn't met. If any error is emitted the process will exit with a non-zero exit code. - - If the \`--fix\` flag is used, Yarn will attempt to automatically fix the issues the best it can, following a multi-pass process (with a maximum of 10 iterations). Some ambiguous patterns cannot be autofixed, in which case you'll have to manually specify the right resolution. - - For more information as to how to write constraints, please consult our dedicated page on our website: https://yarnpkg.com/features/constraints. - `,examples:[["Check that all constraints are satisfied","yarn constraints"],["Autofix all unmet constraints","yarn constraints --fix"]]});var qi=Ye;async function Pu(r,u,p,{configuration:c,fix:w}){let _=new Map,v=new Map;for(let{workspace:g,dependencyIdent:h,dependencyRange:x,dependencyType:T}of p){let b=v.get(g);typeof b=="undefined"&&v.set(g,b=new Map);let C=b.get(h.identHash);typeof C=="undefined"&&b.set(h.identHash,C=new Map);let N=C.get(T);typeof N=="undefined"&&C.set(T,N=new Set),_.set(h.identHash,h),N.add(x)}for(let[g,h]of v)for(let[x,T]of h){let b=_.get(x);if(typeof b=="undefined")throw new Error("Assertion failed: The ident should have been registered");for(let[C,N]of T){let W=N.has(null)?[null]:[...N];if(W.length>2)u.push([se.MessageName.CONSTRAINTS_AMBIGUITY,`${$.structUtils.prettyWorkspace(c,g)} must depend on ${$.structUtils.prettyIdent(c,b)} via conflicting ranges ${W.slice(0,-1).map(ee=>$.structUtils.prettyRange(c,String(ee))).join(", ")}, and ${$.structUtils.prettyRange(c,String(W[W.length-1]))} (in ${C})`]);else if(W.length>1)u.push([se.MessageName.CONSTRAINTS_AMBIGUITY,`${$.structUtils.prettyWorkspace(c,g)} must depend on ${$.structUtils.prettyIdent(c,b)} via conflicting ranges ${$.structUtils.prettyRange(c,String(W[0]))} and ${$.structUtils.prettyRange(c,String(W[1]))} (in ${C})`]);else{let ee=g.manifest[C].get(b.identHash),[te]=W;te!==null?ee?ee.range!==te&&(w?(g.manifest[C].set(b.identHash,$.structUtils.makeDescriptor(b,te)),r.add(g)):u.push([se.MessageName.CONSTRAINTS_INCOMPATIBLE_DEPENDENCY,`${$.structUtils.prettyWorkspace(c,g)} must depend on ${$.structUtils.prettyIdent(c,b)} via ${$.structUtils.prettyRange(c,te)}, but uses ${$.structUtils.prettyRange(c,ee.range)} instead (in ${C})`])):w?(g.manifest[C].set(b.identHash,$.structUtils.makeDescriptor(b,te)),r.add(g)):u.push([se.MessageName.CONSTRAINTS_MISSING_DEPENDENCY,`${$.structUtils.prettyWorkspace(c,g)} must depend on ${$.structUtils.prettyIdent(c,b)} (via ${$.structUtils.prettyRange(c,te)}), but doesn't (in ${C})`]):ee&&(w?(g.manifest[C].delete(b.identHash),r.add(g)):u.push([se.MessageName.CONSTRAINTS_EXTRANEOUS_DEPENDENCY,`${$.structUtils.prettyWorkspace(c,g)} has an extraneous dependency on ${$.structUtils.prettyIdent(c,b)} (in ${C})`]))}}}}async function Cu(r,u,p,{configuration:c,fix:w}){let _=new Map;for(let{workspace:v,fieldPath:g,fieldValue:h}of p){let x=Pe.miscUtils.getMapWithDefault(_,v);Pe.miscUtils.getSetWithDefault(x,g).add(h)}for(let[v,g]of _)for(let[h,x]of g){let T=[...x];if(T.length>2)u.push([se.MessageName.CONSTRAINTS_AMBIGUITY,`${$.structUtils.prettyWorkspace(c,v)} must have a field ${$.formatUtils.pretty(c,h,"cyan")} set to conflicting values ${T.slice(0,-1).map(b=>$.formatUtils.pretty(c,String(b),"magenta")).join(", ")}, or ${$.formatUtils.pretty(c,String(T[T.length-1]),"magenta")}`]);else if(T.length>1)u.push([se.MessageName.CONSTRAINTS_AMBIGUITY,`${$.structUtils.prettyWorkspace(c,v)} must have a field ${$.formatUtils.pretty(c,h,"cyan")} set to conflicting values ${$.formatUtils.pretty(c,String(T[0]),"magenta")} or ${$.formatUtils.pretty(c,String(T[1]),"magenta")}`]);else{let b=(0,Ni.default)(v.manifest.raw,h),[C]=T;C!==null?b===void 0?w?(await qr(v,h,C),r.add(v)):u.push([se.MessageName.CONSTRAINTS_MISSING_FIELD,`${$.structUtils.prettyWorkspace(c,v)} must have a field ${$.formatUtils.pretty(c,h,"cyan")} set to ${$.formatUtils.pretty(c,String(C),"magenta")}, but doesn't`]):JSON.stringify(b)!==C&&(w?(await qr(v,h,C),r.add(v)):u.push([se.MessageName.CONSTRAINTS_INCOMPATIBLE_FIELD,`${$.structUtils.prettyWorkspace(c,v)} must have a field ${$.formatUtils.pretty(c,h,"cyan")} set to ${$.formatUtils.pretty(c,String(C),"magenta")}, but is set to ${$.formatUtils.pretty(c,JSON.stringify(b),"magenta")} instead`])):b!=null&&(w?(await qr(v,h,null),r.add(v)):u.push([se.MessageName.CONSTRAINTS_EXTRANEOUS_FIELD,`${$.structUtils.prettyWorkspace(c,v)} has an extraneous field ${$.formatUtils.pretty(c,h,"cyan")} set to ${$.formatUtils.pretty(c,JSON.stringify(b),"magenta")}`]))}}}async function qr(r,u,p){p===null?(0,Mi.default)(r.manifest.raw,u):(0,Ri.default)(r.manifest.raw,u,JSON.parse(p))}var Iu={configuration:{constraintsPath:{description:"The path of the constraints file.",type:$i.SettingsType.ABSOLUTE_PATH,default:"./constraints.pro"}},commands:[si,oi,qi]},Eu=Iu;return Ou;})(); -return plugin; -} -}; diff --git a/.yarn/plugins/@yarnpkg/plugin-typescript.cjs b/.yarn/plugins/@yarnpkg/plugin-typescript.cjs deleted file mode 100644 index 5c1859e0b90d..000000000000 --- a/.yarn/plugins/@yarnpkg/plugin-typescript.cjs +++ /dev/null @@ -1,9 +0,0 @@ -/* eslint-disable */ -//prettier-ignore -module.exports = { -name: "@yarnpkg/plugin-typescript", -factory: function (require) { -var plugin=(()=>{var Ft=Object.create,H=Object.defineProperty,Bt=Object.defineProperties,Kt=Object.getOwnPropertyDescriptor,zt=Object.getOwnPropertyDescriptors,Gt=Object.getOwnPropertyNames,Q=Object.getOwnPropertySymbols,$t=Object.getPrototypeOf,ne=Object.prototype.hasOwnProperty,De=Object.prototype.propertyIsEnumerable;var Re=(e,t,r)=>t in e?H(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,u=(e,t)=>{for(var r in t||(t={}))ne.call(t,r)&&Re(e,r,t[r]);if(Q)for(var r of Q(t))De.call(t,r)&&Re(e,r,t[r]);return e},g=(e,t)=>Bt(e,zt(t)),Lt=e=>H(e,"__esModule",{value:!0});var R=(e,t)=>{var r={};for(var s in e)ne.call(e,s)&&t.indexOf(s)<0&&(r[s]=e[s]);if(e!=null&&Q)for(var s of Q(e))t.indexOf(s)<0&&De.call(e,s)&&(r[s]=e[s]);return r};var I=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),Vt=(e,t)=>{for(var r in t)H(e,r,{get:t[r],enumerable:!0})},Qt=(e,t,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of Gt(t))!ne.call(e,s)&&s!=="default"&&H(e,s,{get:()=>t[s],enumerable:!(r=Kt(t,s))||r.enumerable});return e},C=e=>Qt(Lt(H(e!=null?Ft($t(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var xe=I(J=>{"use strict";Object.defineProperty(J,"__esModule",{value:!0});function _(e){let t=[...e.caches],r=t.shift();return r===void 0?ve():{get(s,n,a={miss:()=>Promise.resolve()}){return r.get(s,n,a).catch(()=>_({caches:t}).get(s,n,a))},set(s,n){return r.set(s,n).catch(()=>_({caches:t}).set(s,n))},delete(s){return r.delete(s).catch(()=>_({caches:t}).delete(s))},clear(){return r.clear().catch(()=>_({caches:t}).clear())}}}function ve(){return{get(e,t,r={miss:()=>Promise.resolve()}){return t().then(n=>Promise.all([n,r.miss(n)])).then(([n])=>n)},set(e,t){return Promise.resolve(t)},delete(e){return Promise.resolve()},clear(){return Promise.resolve()}}}J.createFallbackableCache=_;J.createNullCache=ve});var Ee=I(($s,qe)=>{qe.exports=xe()});var Te=I(ae=>{"use strict";Object.defineProperty(ae,"__esModule",{value:!0});function Jt(e={serializable:!0}){let t={};return{get(r,s,n={miss:()=>Promise.resolve()}){let a=JSON.stringify(r);if(a in t)return Promise.resolve(e.serializable?JSON.parse(t[a]):t[a]);let o=s(),d=n&&n.miss||(()=>Promise.resolve());return o.then(y=>d(y)).then(()=>o)},set(r,s){return t[JSON.stringify(r)]=e.serializable?JSON.stringify(s):s,Promise.resolve(s)},delete(r){return delete t[JSON.stringify(r)],Promise.resolve()},clear(){return t={},Promise.resolve()}}}ae.createInMemoryCache=Jt});var we=I((Vs,Me)=>{Me.exports=Te()});var Ce=I(M=>{"use strict";Object.defineProperty(M,"__esModule",{value:!0});function Xt(e,t,r){let s={"x-algolia-api-key":r,"x-algolia-application-id":t};return{headers(){return e===oe.WithinHeaders?s:{}},queryParameters(){return e===oe.WithinQueryParameters?s:{}}}}function Yt(e){let t=0,r=()=>(t++,new Promise(s=>{setTimeout(()=>{s(e(r))},Math.min(100*t,1e3))}));return e(r)}function ke(e,t=(r,s)=>Promise.resolve()){return Object.assign(e,{wait(r){return ke(e.then(s=>Promise.all([t(s,r),s])).then(s=>s[1]))}})}function Zt(e){let t=e.length-1;for(t;t>0;t--){let r=Math.floor(Math.random()*(t+1)),s=e[t];e[t]=e[r],e[r]=s}return e}function er(e,t){return Object.keys(t!==void 0?t:{}).forEach(r=>{e[r]=t[r](e)}),e}function tr(e,...t){let r=0;return e.replace(/%s/g,()=>encodeURIComponent(t[r++]))}var rr="4.2.0",sr=e=>()=>e.transporter.requester.destroy(),oe={WithinQueryParameters:0,WithinHeaders:1};M.AuthMode=oe;M.addMethods=er;M.createAuth=Xt;M.createRetryablePromise=Yt;M.createWaitablePromise=ke;M.destroy=sr;M.encode=tr;M.shuffle=Zt;M.version=rr});var F=I((Js,Ue)=>{Ue.exports=Ce()});var Ne=I(ie=>{"use strict";Object.defineProperty(ie,"__esModule",{value:!0});var nr={Delete:"DELETE",Get:"GET",Post:"POST",Put:"PUT"};ie.MethodEnum=nr});var B=I((Ys,We)=>{We.exports=Ne()});var Ze=I(A=>{"use strict";Object.defineProperty(A,"__esModule",{value:!0});var He=B();function ce(e,t){let r=e||{},s=r.data||{};return Object.keys(r).forEach(n=>{["timeout","headers","queryParameters","data","cacheable"].indexOf(n)===-1&&(s[n]=r[n])}),{data:Object.entries(s).length>0?s:void 0,timeout:r.timeout||t,headers:r.headers||{},queryParameters:r.queryParameters||{},cacheable:r.cacheable}}var X={Read:1,Write:2,Any:3},U={Up:1,Down:2,Timeouted:3},_e=2*60*1e3;function ue(e,t=U.Up){return g(u({},e),{status:t,lastUpdate:Date.now()})}function Fe(e){return e.status===U.Up||Date.now()-e.lastUpdate>_e}function Be(e){return e.status===U.Timeouted&&Date.now()-e.lastUpdate<=_e}function le(e){return{protocol:e.protocol||"https",url:e.url,accept:e.accept||X.Any}}function ar(e,t){return Promise.all(t.map(r=>e.get(r,()=>Promise.resolve(ue(r))))).then(r=>{let s=r.filter(d=>Fe(d)),n=r.filter(d=>Be(d)),a=[...s,...n],o=a.length>0?a.map(d=>le(d)):t;return{getTimeout(d,y){return(n.length===0&&d===0?1:n.length+3+d)*y},statelessHosts:o}})}var or=({isTimedOut:e,status:t})=>!e&&~~t==0,ir=e=>{let t=e.status;return e.isTimedOut||or(e)||~~(t/100)!=2&&~~(t/100)!=4},cr=({status:e})=>~~(e/100)==2,ur=(e,t)=>ir(e)?t.onRetry(e):cr(e)?t.onSucess(e):t.onFail(e);function Qe(e,t,r,s){let n=[],a=$e(r,s),o=Le(e,s),d=r.method,y=r.method!==He.MethodEnum.Get?{}:u(u({},r.data),s.data),b=u(u(u({"x-algolia-agent":e.userAgent.value},e.queryParameters),y),s.queryParameters),f=0,p=(h,S)=>{let O=h.pop();if(O===void 0)throw Ve(de(n));let P={data:a,headers:o,method:d,url:Ge(O,r.path,b),connectTimeout:S(f,e.timeouts.connect),responseTimeout:S(f,s.timeout)},x=j=>{let T={request:P,response:j,host:O,triesLeft:h.length};return n.push(T),T},v={onSucess:j=>Ke(j),onRetry(j){let T=x(j);return j.isTimedOut&&f++,Promise.all([e.logger.info("Retryable failure",pe(T)),e.hostsCache.set(O,ue(O,j.isTimedOut?U.Timeouted:U.Down))]).then(()=>p(h,S))},onFail(j){throw x(j),ze(j,de(n))}};return e.requester.send(P).then(j=>ur(j,v))};return ar(e.hostsCache,t).then(h=>p([...h.statelessHosts].reverse(),h.getTimeout))}function lr(e){let{hostsCache:t,logger:r,requester:s,requestsCache:n,responsesCache:a,timeouts:o,userAgent:d,hosts:y,queryParameters:b,headers:f}=e,p={hostsCache:t,logger:r,requester:s,requestsCache:n,responsesCache:a,timeouts:o,userAgent:d,headers:f,queryParameters:b,hosts:y.map(h=>le(h)),read(h,S){let O=ce(S,p.timeouts.read),P=()=>Qe(p,p.hosts.filter(j=>(j.accept&X.Read)!=0),h,O);if((O.cacheable!==void 0?O.cacheable:h.cacheable)!==!0)return P();let v={request:h,mappedRequestOptions:O,transporter:{queryParameters:p.queryParameters,headers:p.headers}};return p.responsesCache.get(v,()=>p.requestsCache.get(v,()=>p.requestsCache.set(v,P()).then(j=>Promise.all([p.requestsCache.delete(v),j]),j=>Promise.all([p.requestsCache.delete(v),Promise.reject(j)])).then(([j,T])=>T)),{miss:j=>p.responsesCache.set(v,j)})},write(h,S){return Qe(p,p.hosts.filter(O=>(O.accept&X.Write)!=0),h,ce(S,p.timeouts.write))}};return p}function dr(e){let t={value:`Algolia for JavaScript (${e})`,add(r){let s=`; ${r.segment}${r.version!==void 0?` (${r.version})`:""}`;return t.value.indexOf(s)===-1&&(t.value=`${t.value}${s}`),t}};return t}function Ke(e){try{return JSON.parse(e.content)}catch(t){throw Je(t.message,e)}}function ze({content:e,status:t},r){let s=e;try{s=JSON.parse(e).message}catch(n){}return Xe(s,t,r)}function pr(e,...t){let r=0;return e.replace(/%s/g,()=>encodeURIComponent(t[r++]))}function Ge(e,t,r){let s=Ye(r),n=`${e.protocol}://${e.url}/${t.charAt(0)==="/"?t.substr(1):t}`;return s.length&&(n+=`?${s}`),n}function Ye(e){let t=r=>Object.prototype.toString.call(r)==="[object Object]"||Object.prototype.toString.call(r)==="[object Array]";return Object.keys(e).map(r=>pr("%s=%s",r,t(e[r])?JSON.stringify(e[r]):e[r])).join("&")}function $e(e,t){if(e.method===He.MethodEnum.Get||e.data===void 0&&t.data===void 0)return;let r=Array.isArray(e.data)?e.data:u(u({},e.data),t.data);return JSON.stringify(r)}function Le(e,t){let r=u(u({},e.headers),t.headers),s={};return Object.keys(r).forEach(n=>{let a=r[n];s[n.toLowerCase()]=a}),s}function de(e){return e.map(t=>pe(t))}function pe(e){let t=e.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return g(u({},e),{request:g(u({},e.request),{headers:u(u({},e.request.headers),t)})})}function Xe(e,t,r){return{name:"ApiError",message:e,status:t,transporterStackTrace:r}}function Je(e,t){return{name:"DeserializationError",message:e,response:t}}function Ve(e){return{name:"RetryError",message:"Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.",transporterStackTrace:e}}A.CallEnum=X;A.HostStatusEnum=U;A.createApiError=Xe;A.createDeserializationError=Je;A.createMappedRequestOptions=ce;A.createRetryError=Ve;A.createStatefulHost=ue;A.createStatelessHost=le;A.createTransporter=lr;A.createUserAgent=dr;A.deserializeFailure=ze;A.deserializeSuccess=Ke;A.isStatefulHostTimeouted=Be;A.isStatefulHostUp=Fe;A.serializeData=$e;A.serializeHeaders=Le;A.serializeQueryParameters=Ye;A.serializeUrl=Ge;A.stackFrameWithoutCredentials=pe;A.stackTraceWithoutCredentials=de});var K=I((en,et)=>{et.exports=Ze()});var tt=I(w=>{"use strict";Object.defineProperty(w,"__esModule",{value:!0});var N=F(),mr=K(),z=B(),hr=e=>{let t=e.region||"us",r=N.createAuth(N.AuthMode.WithinHeaders,e.appId,e.apiKey),s=mr.createTransporter(g(u({hosts:[{url:`analytics.${t}.algolia.com`}]},e),{headers:u(g(u({},r.headers()),{"content-type":"application/json"}),e.headers),queryParameters:u(u({},r.queryParameters()),e.queryParameters)})),n=e.appId;return N.addMethods({appId:n,transporter:s},e.methods)},yr=e=>(t,r)=>e.transporter.write({method:z.MethodEnum.Post,path:"2/abtests",data:t},r),gr=e=>(t,r)=>e.transporter.write({method:z.MethodEnum.Delete,path:N.encode("2/abtests/%s",t)},r),fr=e=>(t,r)=>e.transporter.read({method:z.MethodEnum.Get,path:N.encode("2/abtests/%s",t)},r),br=e=>t=>e.transporter.read({method:z.MethodEnum.Get,path:"2/abtests"},t),Pr=e=>(t,r)=>e.transporter.write({method:z.MethodEnum.Post,path:N.encode("2/abtests/%s/stop",t)},r);w.addABTest=yr;w.createAnalyticsClient=hr;w.deleteABTest=gr;w.getABTest=fr;w.getABTests=br;w.stopABTest=Pr});var st=I((rn,rt)=>{rt.exports=tt()});var at=I(G=>{"use strict";Object.defineProperty(G,"__esModule",{value:!0});var me=F(),jr=K(),nt=B(),Or=e=>{let t=e.region||"us",r=me.createAuth(me.AuthMode.WithinHeaders,e.appId,e.apiKey),s=jr.createTransporter(g(u({hosts:[{url:`recommendation.${t}.algolia.com`}]},e),{headers:u(g(u({},r.headers()),{"content-type":"application/json"}),e.headers),queryParameters:u(u({},r.queryParameters()),e.queryParameters)}));return me.addMethods({appId:e.appId,transporter:s},e.methods)},Ir=e=>t=>e.transporter.read({method:nt.MethodEnum.Get,path:"1/strategies/personalization"},t),Ar=e=>(t,r)=>e.transporter.write({method:nt.MethodEnum.Post,path:"1/strategies/personalization",data:t},r);G.createRecommendationClient=Or;G.getPersonalizationStrategy=Ir;G.setPersonalizationStrategy=Ar});var it=I((nn,ot)=>{ot.exports=at()});var jt=I(i=>{"use strict";Object.defineProperty(i,"__esModule",{value:!0});var l=F(),q=K(),m=B(),Sr=require("crypto");function Y(e){let t=r=>e.request(r).then(s=>{if(e.batch!==void 0&&e.batch(s.hits),!e.shouldStop(s))return s.cursor?t({cursor:s.cursor}):t({page:(r.page||0)+1})});return t({})}var Dr=e=>{let t=e.appId,r=l.createAuth(e.authMode!==void 0?e.authMode:l.AuthMode.WithinHeaders,t,e.apiKey),s=q.createTransporter(g(u({hosts:[{url:`${t}-dsn.algolia.net`,accept:q.CallEnum.Read},{url:`${t}.algolia.net`,accept:q.CallEnum.Write}].concat(l.shuffle([{url:`${t}-1.algolianet.com`},{url:`${t}-2.algolianet.com`},{url:`${t}-3.algolianet.com`}]))},e),{headers:u(g(u({},r.headers()),{"content-type":"application/x-www-form-urlencoded"}),e.headers),queryParameters:u(u({},r.queryParameters()),e.queryParameters)})),n={transporter:s,appId:t,addAlgoliaAgent(a,o){s.userAgent.add({segment:a,version:o})},clearCache(){return Promise.all([s.requestsCache.clear(),s.responsesCache.clear()]).then(()=>{})}};return l.addMethods(n,e.methods)};function ct(){return{name:"MissingObjectIDError",message:"All objects must have an unique objectID (like a primary key) to be valid. Algolia is also able to generate objectIDs automatically but *it's not recommended*. To do it, use the `{'autoGenerateObjectIDIfNotExist': true}` option."}}function ut(){return{name:"ObjectNotFoundError",message:"Object not found."}}function lt(){return{name:"ValidUntilNotFoundError",message:"ValidUntil not found in given secured api key."}}var Rr=e=>(t,r)=>{let d=r||{},{queryParameters:s}=d,n=R(d,["queryParameters"]),a=u({acl:t},s!==void 0?{queryParameters:s}:{}),o=(y,b)=>l.createRetryablePromise(f=>$(e)(y.key,b).catch(p=>{if(p.status!==404)throw p;return f()}));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:"1/keys",data:a},n),o)},vr=e=>(t,r,s)=>{let n=q.createMappedRequestOptions(s);return n.queryParameters["X-Algolia-User-ID"]=t,e.transporter.write({method:m.MethodEnum.Post,path:"1/clusters/mapping",data:{cluster:r}},n)},xr=e=>(t,r,s)=>e.transporter.write({method:m.MethodEnum.Post,path:"1/clusters/mapping/batch",data:{users:t,cluster:r}},s),Z=e=>(t,r,s)=>{let n=(a,o)=>L(e)(t,{methods:{waitTask:D}}).waitTask(a.taskID,o);return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/operation",t),data:{operation:"copy",destination:r}},s),n)},qr=e=>(t,r,s)=>Z(e)(t,r,g(u({},s),{scope:[ee.Rules]})),Er=e=>(t,r,s)=>Z(e)(t,r,g(u({},s),{scope:[ee.Settings]})),Tr=e=>(t,r,s)=>Z(e)(t,r,g(u({},s),{scope:[ee.Synonyms]})),Mr=e=>(t,r)=>{let s=(n,a)=>l.createRetryablePromise(o=>$(e)(t,a).then(o).catch(d=>{if(d.status!==404)throw d}));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/keys/%s",t)},r),s)},wr=()=>(e,t)=>{let r=q.serializeQueryParameters(t),s=Sr.createHmac("sha256",e).update(r).digest("hex");return Buffer.from(s+r).toString("base64")},$=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/keys/%s",t)},r),kr=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/logs"},t),Cr=()=>e=>{let t=Buffer.from(e,"base64").toString("ascii"),r=/validUntil=(\d+)/,s=t.match(r);if(s===null)throw lt();return parseInt(s[1],10)-Math.round(new Date().getTime()/1e3)},Ur=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters/mapping/top"},t),Nr=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/clusters/mapping/%s",t)},r),Wr=e=>t=>{let n=t||{},{retrieveMappings:r}=n,s=R(n,["retrieveMappings"]);return r===!0&&(s.getClusters=!0),e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters/mapping/pending"},s)},L=e=>(t,r={})=>{let s={transporter:e.transporter,appId:e.appId,indexName:t};return l.addMethods(s,r.methods)},Hr=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/keys"},t),_r=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters"},t),Fr=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/indexes"},t),Br=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters/mapping"},t),Kr=e=>(t,r,s)=>{let n=(a,o)=>L(e)(t,{methods:{waitTask:D}}).waitTask(a.taskID,o);return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/operation",t),data:{operation:"move",destination:r}},s),n)},zr=e=>(t,r)=>{let s=(n,a)=>Promise.all(Object.keys(n.taskID).map(o=>L(e)(o,{methods:{waitTask:D}}).waitTask(n.taskID[o],a)));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:"1/indexes/*/batch",data:{requests:t}},r),s)},Gr=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:"1/indexes/*/objects",data:{requests:t}},r),$r=e=>(t,r)=>{let s=t.map(n=>g(u({},n),{params:q.serializeQueryParameters(n.params||{})}));return e.transporter.read({method:m.MethodEnum.Post,path:"1/indexes/*/queries",data:{requests:s},cacheable:!0},r)},Lr=e=>(t,r)=>Promise.all(t.map(s=>{let d=s.params,{facetName:n,facetQuery:a}=d,o=R(d,["facetName","facetQuery"]);return L(e)(s.indexName,{methods:{searchForFacetValues:dt}}).searchForFacetValues(n,a,u(u({},r),o))})),Vr=e=>(t,r)=>{let s=q.createMappedRequestOptions(r);return s.queryParameters["X-Algolia-User-ID"]=t,e.transporter.write({method:m.MethodEnum.Delete,path:"1/clusters/mapping"},s)},Qr=e=>(t,r)=>{let s=(n,a)=>l.createRetryablePromise(o=>$(e)(t,a).catch(d=>{if(d.status!==404)throw d;return o()}));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/keys/%s/restore",t)},r),s)},Jr=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:"1/clusters/mapping/search",data:{query:t}},r),Xr=e=>(t,r)=>{let s=Object.assign({},r),f=r||{},{queryParameters:n}=f,a=R(f,["queryParameters"]),o=n?{queryParameters:n}:{},d=["acl","indexes","referers","restrictSources","queryParameters","description","maxQueriesPerIPPerHour","maxHitsPerQuery"],y=p=>Object.keys(s).filter(h=>d.indexOf(h)!==-1).every(h=>p[h]===s[h]),b=(p,h)=>l.createRetryablePromise(S=>$(e)(t,h).then(O=>y(O)?Promise.resolve():S()));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Put,path:l.encode("1/keys/%s",t),data:o},a),b)},pt=e=>(t,r)=>{let s=(n,a)=>D(e)(n.taskID,a);return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/batch",e.indexName),data:{requests:t}},r),s)},Yr=e=>t=>Y(g(u({},t),{shouldStop:r=>r.cursor===void 0,request:r=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/browse",e.indexName),data:r},t)})),Zr=e=>t=>{let r=u({hitsPerPage:1e3},t);return Y(g(u({},r),{shouldStop:s=>s.hits.lengthg(u({},n),{hits:n.hits.map(a=>(delete a._highlightResult,a))}))}}))},es=e=>t=>{let r=u({hitsPerPage:1e3},t);return Y(g(u({},r),{shouldStop:s=>s.hits.lengthg(u({},n),{hits:n.hits.map(a=>(delete a._highlightResult,a))}))}}))},te=e=>(t,r,s)=>{let y=s||{},{batchSize:n}=y,a=R(y,["batchSize"]),o={taskIDs:[],objectIDs:[]},d=(b=0)=>{let f=[],p;for(p=b;p({action:r,body:h})),a).then(h=>(o.objectIDs=o.objectIDs.concat(h.objectIDs),o.taskIDs.push(h.taskID),p++,d(p)))};return l.createWaitablePromise(d(),(b,f)=>Promise.all(b.taskIDs.map(p=>D(e)(p,f))))},ts=e=>t=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/clear",e.indexName)},t),(r,s)=>D(e)(r.taskID,s)),rs=e=>t=>{let a=t||{},{forwardToReplicas:r}=a,s=R(a,["forwardToReplicas"]),n=q.createMappedRequestOptions(s);return r&&(n.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/rules/clear",e.indexName)},n),(o,d)=>D(e)(o.taskID,d))},ss=e=>t=>{let a=t||{},{forwardToReplicas:r}=a,s=R(a,["forwardToReplicas"]),n=q.createMappedRequestOptions(s);return r&&(n.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/synonyms/clear",e.indexName)},n),(o,d)=>D(e)(o.taskID,d))},ns=e=>(t,r)=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/deleteByQuery",e.indexName),data:t},r),(s,n)=>D(e)(s.taskID,n)),as=e=>t=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/indexes/%s",e.indexName)},t),(r,s)=>D(e)(r.taskID,s)),os=e=>(t,r)=>l.createWaitablePromise(yt(e)([t],r).then(s=>({taskID:s.taskIDs[0]})),(s,n)=>D(e)(s.taskID,n)),yt=e=>(t,r)=>{let s=t.map(n=>({objectID:n}));return te(e)(s,k.DeleteObject,r)},is=e=>(t,r)=>{let o=r||{},{forwardToReplicas:s}=o,n=R(o,["forwardToReplicas"]),a=q.createMappedRequestOptions(n);return s&&(a.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/indexes/%s/rules/%s",e.indexName,t)},a),(d,y)=>D(e)(d.taskID,y))},cs=e=>(t,r)=>{let o=r||{},{forwardToReplicas:s}=o,n=R(o,["forwardToReplicas"]),a=q.createMappedRequestOptions(n);return s&&(a.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/indexes/%s/synonyms/%s",e.indexName,t)},a),(d,y)=>D(e)(d.taskID,y))},us=e=>t=>gt(e)(t).then(()=>!0).catch(r=>{if(r.status!==404)throw r;return!1}),ls=e=>(t,r)=>{let y=r||{},{query:s,paginate:n}=y,a=R(y,["query","paginate"]),o=0,d=()=>ft(e)(s||"",g(u({},a),{page:o})).then(b=>{for(let[f,p]of Object.entries(b.hits))if(t(p))return{object:p,position:parseInt(f,10),page:o};if(o++,n===!1||o>=b.nbPages)throw ut();return d()});return d()},ds=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/%s",e.indexName,t)},r),ps=()=>(e,t)=>{for(let[r,s]of Object.entries(e.hits))if(s.objectID===t)return parseInt(r,10);return-1},ms=e=>(t,r)=>{let o=r||{},{attributesToRetrieve:s}=o,n=R(o,["attributesToRetrieve"]),a=t.map(d=>u({indexName:e.indexName,objectID:d},s?{attributesToRetrieve:s}:{}));return e.transporter.read({method:m.MethodEnum.Post,path:"1/indexes/*/objects",data:{requests:a}},n)},hs=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/rules/%s",e.indexName,t)},r),gt=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/settings",e.indexName),data:{getVersion:2}},t),ys=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/synonyms/%s",e.indexName,t)},r),bt=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/task/%s",e.indexName,t.toString())},r),gs=e=>(t,r)=>l.createWaitablePromise(Pt(e)([t],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,n)=>D(e)(s.taskID,n)),Pt=e=>(t,r)=>{let o=r||{},{createIfNotExists:s}=o,n=R(o,["createIfNotExists"]),a=s?k.PartialUpdateObject:k.PartialUpdateObjectNoCreate;return te(e)(t,a,n)},fs=e=>(t,r)=>{let O=r||{},{safe:s,autoGenerateObjectIDIfNotExist:n,batchSize:a}=O,o=R(O,["safe","autoGenerateObjectIDIfNotExist","batchSize"]),d=(P,x,v,j)=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/operation",P),data:{operation:v,destination:x}},j),(T,V)=>D(e)(T.taskID,V)),y=Math.random().toString(36).substring(7),b=`${e.indexName}_tmp_${y}`,f=he({appId:e.appId,transporter:e.transporter,indexName:b}),p=[],h=d(e.indexName,b,"copy",g(u({},o),{scope:["settings","synonyms","rules"]}));p.push(h);let S=(s?h.wait(o):h).then(()=>{let P=f(t,g(u({},o),{autoGenerateObjectIDIfNotExist:n,batchSize:a}));return p.push(P),s?P.wait(o):P}).then(()=>{let P=d(b,e.indexName,"move",o);return p.push(P),s?P.wait(o):P}).then(()=>Promise.all(p)).then(([P,x,v])=>({objectIDs:x.objectIDs,taskIDs:[P.taskID,...x.taskIDs,v.taskID]}));return l.createWaitablePromise(S,(P,x)=>Promise.all(p.map(v=>v.wait(x))))},bs=e=>(t,r)=>ye(e)(t,g(u({},r),{clearExistingRules:!0})),Ps=e=>(t,r)=>ge(e)(t,g(u({},r),{replaceExistingSynonyms:!0})),js=e=>(t,r)=>l.createWaitablePromise(he(e)([t],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,n)=>D(e)(s.taskID,n)),he=e=>(t,r)=>{let o=r||{},{autoGenerateObjectIDIfNotExist:s}=o,n=R(o,["autoGenerateObjectIDIfNotExist"]),a=s?k.AddObject:k.UpdateObject;if(a===k.UpdateObject){for(let d of t)if(d.objectID===void 0)return l.createWaitablePromise(Promise.reject(ct()))}return te(e)(t,a,n)},Os=e=>(t,r)=>ye(e)([t],r),ye=e=>(t,r)=>{let d=r||{},{forwardToReplicas:s,clearExistingRules:n}=d,a=R(d,["forwardToReplicas","clearExistingRules"]),o=q.createMappedRequestOptions(a);return s&&(o.queryParameters.forwardToReplicas=1),n&&(o.queryParameters.clearExistingRules=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/rules/batch",e.indexName),data:t},o),(y,b)=>D(e)(y.taskID,b))},Is=e=>(t,r)=>ge(e)([t],r),ge=e=>(t,r)=>{let d=r||{},{forwardToReplicas:s,replaceExistingSynonyms:n}=d,a=R(d,["forwardToReplicas","replaceExistingSynonyms"]),o=q.createMappedRequestOptions(a);return s&&(o.queryParameters.forwardToReplicas=1),n&&(o.queryParameters.replaceExistingSynonyms=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/synonyms/batch",e.indexName),data:t},o),(y,b)=>D(e)(y.taskID,b))},ft=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/query",e.indexName),data:{query:t},cacheable:!0},r),dt=e=>(t,r,s)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/facets/%s/query",e.indexName,t),data:{facetQuery:r},cacheable:!0},s),mt=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/rules/search",e.indexName),data:{query:t}},r),ht=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/synonyms/search",e.indexName),data:{query:t}},r),As=e=>(t,r)=>{let o=r||{},{forwardToReplicas:s}=o,n=R(o,["forwardToReplicas"]),a=q.createMappedRequestOptions(n);return s&&(a.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Put,path:l.encode("1/indexes/%s/settings",e.indexName),data:t},a),(d,y)=>D(e)(d.taskID,y))},D=e=>(t,r)=>l.createRetryablePromise(s=>bt(e)(t,r).then(n=>n.status!=="published"?s():void 0)),Ss={AddObject:"addObject",Analytics:"analytics",Browser:"browse",DeleteIndex:"deleteIndex",DeleteObject:"deleteObject",EditSettings:"editSettings",ListIndexes:"listIndexes",Logs:"logs",Recommendation:"recommendation",Search:"search",SeeUnretrievableAttributes:"seeUnretrievableAttributes",Settings:"settings",Usage:"usage"},k={AddObject:"addObject",UpdateObject:"updateObject",PartialUpdateObject:"partialUpdateObject",PartialUpdateObjectNoCreate:"partialUpdateObjectNoCreate",DeleteObject:"deleteObject"},ee={Settings:"settings",Synonyms:"synonyms",Rules:"rules"},Ds={None:"none",StopIfEnoughMatches:"stopIfEnoughMatches"},Rs={Synonym:"synonym",OneWaySynonym:"oneWaySynonym",AltCorrection1:"altCorrection1",AltCorrection2:"altCorrection2",Placeholder:"placeholder"};i.ApiKeyACLEnum=Ss;i.BatchActionEnum=k;i.ScopeEnum=ee;i.StrategyEnum=Ds;i.SynonymEnum=Rs;i.addApiKey=Rr;i.assignUserID=vr;i.assignUserIDs=xr;i.batch=pt;i.browseObjects=Yr;i.browseRules=Zr;i.browseSynonyms=es;i.chunkedBatch=te;i.clearObjects=ts;i.clearRules=rs;i.clearSynonyms=ss;i.copyIndex=Z;i.copyRules=qr;i.copySettings=Er;i.copySynonyms=Tr;i.createBrowsablePromise=Y;i.createMissingObjectIDError=ct;i.createObjectNotFoundError=ut;i.createSearchClient=Dr;i.createValidUntilNotFoundError=lt;i.deleteApiKey=Mr;i.deleteBy=ns;i.deleteIndex=as;i.deleteObject=os;i.deleteObjects=yt;i.deleteRule=is;i.deleteSynonym=cs;i.exists=us;i.findObject=ls;i.generateSecuredApiKey=wr;i.getApiKey=$;i.getLogs=kr;i.getObject=ds;i.getObjectPosition=ps;i.getObjects=ms;i.getRule=hs;i.getSecuredApiKeyRemainingValidity=Cr;i.getSettings=gt;i.getSynonym=ys;i.getTask=bt;i.getTopUserIDs=Ur;i.getUserID=Nr;i.hasPendingMappings=Wr;i.initIndex=L;i.listApiKeys=Hr;i.listClusters=_r;i.listIndices=Fr;i.listUserIDs=Br;i.moveIndex=Kr;i.multipleBatch=zr;i.multipleGetObjects=Gr;i.multipleQueries=$r;i.multipleSearchForFacetValues=Lr;i.partialUpdateObject=gs;i.partialUpdateObjects=Pt;i.removeUserID=Vr;i.replaceAllObjects=fs;i.replaceAllRules=bs;i.replaceAllSynonyms=Ps;i.restoreApiKey=Qr;i.saveObject=js;i.saveObjects=he;i.saveRule=Os;i.saveRules=ye;i.saveSynonym=Is;i.saveSynonyms=ge;i.search=ft;i.searchForFacetValues=dt;i.searchRules=mt;i.searchSynonyms=ht;i.searchUserIDs=Jr;i.setSettings=As;i.updateApiKey=Xr;i.waitTask=D});var It=I((on,Ot)=>{Ot.exports=jt()});var At=I(re=>{"use strict";Object.defineProperty(re,"__esModule",{value:!0});function vs(){return{debug(e,t){return Promise.resolve()},info(e,t){return Promise.resolve()},error(e,t){return Promise.resolve()}}}var xs={Debug:1,Info:2,Error:3};re.LogLevelEnum=xs;re.createNullLogger=vs});var Dt=I((un,St)=>{St.exports=At()});var xt=I(fe=>{"use strict";Object.defineProperty(fe,"__esModule",{value:!0});var Rt=require("http"),vt=require("https"),qs=require("url");function Es(){let e={keepAlive:!0},t=new Rt.Agent(e),r=new vt.Agent(e);return{send(s){return new Promise(n=>{let a=qs.parse(s.url),o=a.query===null?a.pathname:`${a.pathname}?${a.query}`,d=u({agent:a.protocol==="https:"?r:t,hostname:a.hostname,path:o,method:s.method,headers:s.headers},a.port!==void 0?{port:a.port||""}:{}),y=(a.protocol==="https:"?vt:Rt).request(d,h=>{let S="";h.on("data",O=>S+=O),h.on("end",()=>{clearTimeout(f),clearTimeout(p),n({status:h.statusCode||0,content:S,isTimedOut:!1})})}),b=(h,S)=>setTimeout(()=>{y.abort(),n({status:0,content:S,isTimedOut:!0})},h*1e3),f=b(s.connectTimeout,"Connection timeout"),p;y.on("error",h=>{clearTimeout(f),clearTimeout(p),n({status:0,content:h.message,isTimedOut:!1})}),y.once("response",()=>{clearTimeout(f),p=b(s.responseTimeout,"Socket timeout")}),s.data!==void 0&&y.write(s.data),y.end()})},destroy(){return t.destroy(),r.destroy(),Promise.resolve()}}}fe.createNodeHttpRequester=Es});var Et=I((dn,qt)=>{qt.exports=xt()});var kt=I((pn,Tt)=>{"use strict";var Mt=Ee(),Ts=we(),W=st(),be=F(),Pe=it(),c=It(),Ms=Dt(),ws=Et(),ks=K();function wt(e,t,r){let s={appId:e,apiKey:t,timeouts:{connect:2,read:5,write:30},requester:ws.createNodeHttpRequester(),logger:Ms.createNullLogger(),responsesCache:Mt.createNullCache(),requestsCache:Mt.createNullCache(),hostsCache:Ts.createInMemoryCache(),userAgent:ks.createUserAgent(be.version).add({segment:"Node.js",version:process.versions.node})};return c.createSearchClient(g(u(u({},s),r),{methods:{search:c.multipleQueries,searchForFacetValues:c.multipleSearchForFacetValues,multipleBatch:c.multipleBatch,multipleGetObjects:c.multipleGetObjects,multipleQueries:c.multipleQueries,copyIndex:c.copyIndex,copySettings:c.copySettings,copyRules:c.copyRules,copySynonyms:c.copySynonyms,moveIndex:c.moveIndex,listIndices:c.listIndices,getLogs:c.getLogs,listClusters:c.listClusters,multipleSearchForFacetValues:c.multipleSearchForFacetValues,getApiKey:c.getApiKey,addApiKey:c.addApiKey,listApiKeys:c.listApiKeys,updateApiKey:c.updateApiKey,deleteApiKey:c.deleteApiKey,restoreApiKey:c.restoreApiKey,assignUserID:c.assignUserID,assignUserIDs:c.assignUserIDs,getUserID:c.getUserID,searchUserIDs:c.searchUserIDs,listUserIDs:c.listUserIDs,getTopUserIDs:c.getTopUserIDs,removeUserID:c.removeUserID,hasPendingMappings:c.hasPendingMappings,generateSecuredApiKey:c.generateSecuredApiKey,getSecuredApiKeyRemainingValidity:c.getSecuredApiKeyRemainingValidity,destroy:be.destroy,initIndex:n=>a=>c.initIndex(n)(a,{methods:{batch:c.batch,delete:c.deleteIndex,getObject:c.getObject,getObjects:c.getObjects,saveObject:c.saveObject,saveObjects:c.saveObjects,search:c.search,searchForFacetValues:c.searchForFacetValues,waitTask:c.waitTask,setSettings:c.setSettings,getSettings:c.getSettings,partialUpdateObject:c.partialUpdateObject,partialUpdateObjects:c.partialUpdateObjects,deleteObject:c.deleteObject,deleteObjects:c.deleteObjects,deleteBy:c.deleteBy,clearObjects:c.clearObjects,browseObjects:c.browseObjects,getObjectPosition:c.getObjectPosition,findObject:c.findObject,exists:c.exists,saveSynonym:c.saveSynonym,saveSynonyms:c.saveSynonyms,getSynonym:c.getSynonym,searchSynonyms:c.searchSynonyms,browseSynonyms:c.browseSynonyms,deleteSynonym:c.deleteSynonym,clearSynonyms:c.clearSynonyms,replaceAllObjects:c.replaceAllObjects,replaceAllSynonyms:c.replaceAllSynonyms,searchRules:c.searchRules,getRule:c.getRule,deleteRule:c.deleteRule,saveRule:c.saveRule,saveRules:c.saveRules,replaceAllRules:c.replaceAllRules,browseRules:c.browseRules,clearRules:c.clearRules}}),initAnalytics:()=>n=>W.createAnalyticsClient(g(u(u({},s),n),{methods:{addABTest:W.addABTest,getABTest:W.getABTest,getABTests:W.getABTests,stopABTest:W.stopABTest,deleteABTest:W.deleteABTest}})),initRecommendation:()=>n=>Pe.createRecommendationClient(g(u(u({},s),n),{methods:{getPersonalizationStrategy:Pe.getPersonalizationStrategy,setPersonalizationStrategy:Pe.setPersonalizationStrategy}}))}}))}wt.version=be.version;Tt.exports=wt});var Ut=I((mn,je)=>{var Ct=kt();je.exports=Ct;je.exports.default=Ct});var Ws={};Vt(Ws,{default:()=>Ks});var Oe=C(require("@yarnpkg/core")),E=C(require("@yarnpkg/core")),Ie=C(require("@yarnpkg/plugin-essentials")),Ht=C(require("semver"));var se=C(require("@yarnpkg/core")),Nt=C(Ut()),Cs="e8e1bd300d860104bb8c58453ffa1eb4",Us="OFCNCOG2CU",Wt=async(e,t)=>{var a;let r=se.structUtils.stringifyIdent(e),n=Ns(t).initIndex("npm-search");try{return((a=(await n.getObject(r,{attributesToRetrieve:["types"]})).types)==null?void 0:a.ts)==="definitely-typed"}catch(o){return!1}},Ns=e=>(0,Nt.default)(Us,Cs,{requester:{async send(r){try{let s=await se.httpUtils.request(r.url,r.data||null,{configuration:e,headers:r.headers});return{content:s.body,isTimedOut:!1,status:s.statusCode}}catch(s){return{content:s.response.body,isTimedOut:!1,status:s.response.statusCode}}}}});var _t=e=>e.scope?`${e.scope}__${e.name}`:`${e.name}`,Hs=async(e,t,r,s)=>{if(r.scope==="types")return;let{project:n}=e,{configuration:a}=n,o=a.makeResolver(),d={project:n,resolver:o,report:new E.ThrowReport};if(!await Wt(r,a))return;let b=_t(r),f=E.structUtils.parseRange(r.range).selector;if(!E.semverUtils.validRange(f)){let P=await o.getCandidates(r,new Map,d);f=E.structUtils.parseRange(P[0].reference).selector}let p=Ht.default.coerce(f);if(p===null)return;let h=`${Ie.suggestUtils.Modifier.CARET}${p.major}`,S=E.structUtils.makeDescriptor(E.structUtils.makeIdent("types",b),h),O=E.miscUtils.mapAndFind(n.workspaces,P=>{var T,V;let x=(T=P.manifest.dependencies.get(r.identHash))==null?void 0:T.descriptorHash,v=(V=P.manifest.devDependencies.get(r.identHash))==null?void 0:V.descriptorHash;if(x!==r.descriptorHash&&v!==r.descriptorHash)return E.miscUtils.mapAndFind.skip;let j=[];for(let Ae of Oe.Manifest.allDependencies){let Se=P.manifest[Ae].get(S.identHash);typeof Se!="undefined"&&j.push([Ae,Se])}return j.length===0?E.miscUtils.mapAndFind.skip:j});if(typeof O!="undefined")for(let[P,x]of O)e.manifest[P].set(x.identHash,x);else{try{if((await o.getCandidates(S,new Map,d)).length===0)return}catch{return}e.manifest[Ie.suggestUtils.Target.DEVELOPMENT].set(S.identHash,S)}},_s=async(e,t,r)=>{if(r.scope==="types")return;let s=_t(r),n=E.structUtils.makeIdent("types",s);for(let a of Oe.Manifest.allDependencies)typeof e.manifest[a].get(n.identHash)!="undefined"&&e.manifest[a].delete(n.identHash)},Fs=(e,t)=>{t.publishConfig&&t.publishConfig.typings&&(t.typings=t.publishConfig.typings),t.publishConfig&&t.publishConfig.types&&(t.types=t.publishConfig.types)},Bs={hooks:{afterWorkspaceDependencyAddition:Hs,afterWorkspaceDependencyRemoval:_s,beforeWorkspacePacking:Fs}},Ks=Bs;return Ws;})(); -return plugin; -} -}; diff --git a/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs b/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs deleted file mode 100644 index 799c5be98b78..000000000000 --- a/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-disable */ -//prettier-ignore -module.exports = { -name: "@yarnpkg/plugin-workspace-tools", -factory: function (require) { -var plugin=(()=>{var Cr=Object.create,ge=Object.defineProperty,wr=Object.defineProperties,Sr=Object.getOwnPropertyDescriptor,vr=Object.getOwnPropertyDescriptors,Hr=Object.getOwnPropertyNames,Je=Object.getOwnPropertySymbols,$r=Object.getPrototypeOf,et=Object.prototype.hasOwnProperty,Tr=Object.prototype.propertyIsEnumerable;var tt=(e,t,r)=>t in e?ge(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,N=(e,t)=>{for(var r in t||(t={}))et.call(t,r)&&tt(e,r,t[r]);if(Je)for(var r of Je(t))Tr.call(t,r)&&tt(e,r,t[r]);return e},Q=(e,t)=>wr(e,vr(t)),kr=e=>ge(e,"__esModule",{value:!0});var q=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),Lr=(e,t)=>{for(var r in t)ge(e,r,{get:t[r],enumerable:!0})},Or=(e,t,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Hr(t))!et.call(e,n)&&n!=="default"&&ge(e,n,{get:()=>t[n],enumerable:!(r=Sr(t,n))||r.enumerable});return e},Y=e=>Or(kr(ge(e!=null?Cr($r(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var He=q(J=>{"use strict";J.isInteger=e=>typeof e=="number"?Number.isInteger(e):typeof e=="string"&&e.trim()!==""?Number.isInteger(Number(e)):!1;J.find=(e,t)=>e.nodes.find(r=>r.type===t);J.exceedsLimit=(e,t,r=1,n)=>n===!1||!J.isInteger(e)||!J.isInteger(t)?!1:(Number(t)-Number(e))/Number(r)>=n;J.escapeNode=(e,t=0,r)=>{let n=e.nodes[t];!n||(r&&n.type===r||n.type==="open"||n.type==="close")&&n.escaped!==!0&&(n.value="\\"+n.value,n.escaped=!0)};J.encloseBrace=e=>e.type!=="brace"?!1:e.commas>>0+e.ranges>>0==0?(e.invalid=!0,!0):!1;J.isInvalidBrace=e=>e.type!=="brace"?!1:e.invalid===!0||e.dollar?!0:e.commas>>0+e.ranges>>0==0||e.open!==!0||e.close!==!0?(e.invalid=!0,!0):!1;J.isOpenOrClose=e=>e.type==="open"||e.type==="close"?!0:e.open===!0||e.close===!0;J.reduce=e=>e.reduce((t,r)=>(r.type==="text"&&t.push(r.value),r.type==="range"&&(r.type="text"),t),[]);J.flatten=(...e)=>{let t=[],r=n=>{for(let s=0;s{"use strict";var at=He();st.exports=(e,t={})=>{let r=(n,s={})=>{let a=t.escapeInvalid&&at.isInvalidBrace(s),i=n.invalid===!0&&t.escapeInvalid===!0,o="";if(n.value)return(a||i)&&at.isOpenOrClose(n)?"\\"+n.value:n.value;if(n.value)return n.value;if(n.nodes)for(let h of n.nodes)o+=r(h);return o};return r(e)}});var ot=q((is,it)=>{"use strict";it.exports=function(e){return typeof e=="number"?e-e==0:typeof e=="string"&&e.trim()!==""?Number.isFinite?Number.isFinite(+e):isFinite(+e):!1}});var At=q((os,ut)=>{"use strict";var ct=ot(),pe=(e,t,r)=>{if(ct(e)===!1)throw new TypeError("toRegexRange: expected the first argument to be a number");if(t===void 0||e===t)return String(e);if(ct(t)===!1)throw new TypeError("toRegexRange: expected the second argument to be a number.");let n=N({relaxZeros:!0},r);typeof n.strictZeros=="boolean"&&(n.relaxZeros=n.strictZeros===!1);let s=String(n.relaxZeros),a=String(n.shorthand),i=String(n.capture),o=String(n.wrap),h=e+":"+t+"="+s+a+i+o;if(pe.cache.hasOwnProperty(h))return pe.cache[h].result;let A=Math.min(e,t),f=Math.max(e,t);if(Math.abs(A-f)===1){let R=e+"|"+t;return n.capture?`(${R})`:n.wrap===!1?R:`(?:${R})`}let m=pt(e)||pt(t),p={min:e,max:t,a:A,b:f},H=[],_=[];if(m&&(p.isPadded=m,p.maxLen=String(p.max).length),A<0){let R=f<0?Math.abs(f):1;_=lt(R,Math.abs(A),p,n),A=p.a=0}return f>=0&&(H=lt(A,f,p,n)),p.negatives=_,p.positives=H,p.result=Nr(_,H,n),n.capture===!0?p.result=`(${p.result})`:n.wrap!==!1&&H.length+_.length>1&&(p.result=`(?:${p.result})`),pe.cache[h]=p,p.result};function Nr(e,t,r){let n=Pe(e,t,"-",!1,r)||[],s=Pe(t,e,"",!1,r)||[],a=Pe(e,t,"-?",!0,r)||[];return n.concat(a).concat(s).join("|")}function Br(e,t){let r=1,n=1,s=ft(e,r),a=new Set([t]);for(;e<=s&&s<=t;)a.add(s),r+=1,s=ft(e,r);for(s=ht(t+1,n)-1;e1&&o.count.pop(),o.count.push(f.count[0]),o.string=o.pattern+dt(o.count),i=A+1;continue}r.isPadded&&(m=Gr(A,r,n)),f.string=m+f.pattern+dt(f.count),a.push(f),i=A+1,o=f}return a}function Pe(e,t,r,n,s){let a=[];for(let i of e){let{string:o}=i;!n&&!gt(t,"string",o)&&a.push(r+o),n&>(t,"string",o)&&a.push(r+o)}return a}function Mr(e,t){let r=[];for(let n=0;nt?1:t>e?-1:0}function gt(e,t,r){return e.some(n=>n[t]===r)}function ft(e,t){return Number(String(e).slice(0,-t)+"9".repeat(t))}function ht(e,t){return e-e%Math.pow(10,t)}function dt(e){let[t=0,r=""]=e;return r||t>1?`{${t+(r?","+r:"")}}`:""}function Pr(e,t,r){return`[${e}${t-e==1?"":"-"}${t}]`}function pt(e){return/^-?(0+)\d/.test(e)}function Gr(e,t,r){if(!t.isPadded)return e;let n=Math.abs(t.maxLen-String(e).length),s=r.relaxZeros!==!1;switch(n){case 0:return"";case 1:return s?"0?":"0";case 2:return s?"0{0,2}":"00";default:return s?`0{0,${n}}`:`0{${n}}`}}pe.cache={};pe.clearCache=()=>pe.cache={};ut.exports=pe});var Ue=q((us,mt)=>{"use strict";var Ur=require("util"),Rt=At(),yt=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),qr=e=>t=>e===!0?Number(t):String(t),De=e=>typeof e=="number"||typeof e=="string"&&e!=="",me=e=>Number.isInteger(+e),Ge=e=>{let t=`${e}`,r=-1;if(t[0]==="-"&&(t=t.slice(1)),t==="0")return!1;for(;t[++r]==="0";);return r>0},Kr=(e,t,r)=>typeof e=="string"||typeof t=="string"?!0:r.stringify===!0,Wr=(e,t,r)=>{if(t>0){let n=e[0]==="-"?"-":"";n&&(e=e.slice(1)),e=n+e.padStart(n?t-1:t,"0")}return r===!1?String(e):e},_t=(e,t)=>{let r=e[0]==="-"?"-":"";for(r&&(e=e.slice(1),t--);e.length{e.negatives.sort((i,o)=>io?1:0),e.positives.sort((i,o)=>io?1:0);let r=t.capture?"":"?:",n="",s="",a;return e.positives.length&&(n=e.positives.join("|")),e.negatives.length&&(s=`-(${r}${e.negatives.join("|")})`),n&&s?a=`${n}|${s}`:a=n||s,t.wrap?`(${r}${a})`:a},Et=(e,t,r,n)=>{if(r)return Rt(e,t,N({wrap:!1},n));let s=String.fromCharCode(e);if(e===t)return s;let a=String.fromCharCode(t);return`[${s}-${a}]`},xt=(e,t,r)=>{if(Array.isArray(e)){let n=r.wrap===!0,s=r.capture?"":"?:";return n?`(${s}${e.join("|")})`:e.join("|")}return Rt(e,t,r)},bt=(...e)=>new RangeError("Invalid range arguments: "+Ur.inspect(...e)),Ct=(e,t,r)=>{if(r.strictRanges===!0)throw bt([e,t]);return[]},Fr=(e,t)=>{if(t.strictRanges===!0)throw new TypeError(`Expected step "${e}" to be a number`);return[]},Qr=(e,t,r=1,n={})=>{let s=Number(e),a=Number(t);if(!Number.isInteger(s)||!Number.isInteger(a)){if(n.strictRanges===!0)throw bt([e,t]);return[]}s===0&&(s=0),a===0&&(a=0);let i=s>a,o=String(e),h=String(t),A=String(r);r=Math.max(Math.abs(r),1);let f=Ge(o)||Ge(h)||Ge(A),m=f?Math.max(o.length,h.length,A.length):0,p=f===!1&&Kr(e,t,n)===!1,H=n.transform||qr(p);if(n.toRegex&&r===1)return Et(_t(e,m),_t(t,m),!0,n);let _={negatives:[],positives:[]},R=T=>_[T<0?"negatives":"positives"].push(Math.abs(T)),b=[],C=0;for(;i?s>=a:s<=a;)n.toRegex===!0&&r>1?R(s):b.push(Wr(H(s,C),m,p)),s=i?s-r:s+r,C++;return n.toRegex===!0?r>1?jr(_,n):xt(b,null,N({wrap:!1},n)):b},Xr=(e,t,r=1,n={})=>{if(!me(e)&&e.length>1||!me(t)&&t.length>1)return Ct(e,t,n);let s=n.transform||(p=>String.fromCharCode(p)),a=`${e}`.charCodeAt(0),i=`${t}`.charCodeAt(0),o=a>i,h=Math.min(a,i),A=Math.max(a,i);if(n.toRegex&&r===1)return Et(h,A,!1,n);let f=[],m=0;for(;o?a>=i:a<=i;)f.push(s(a,m)),a=o?a-r:a+r,m++;return n.toRegex===!0?xt(f,null,{wrap:!1,options:n}):f},Te=(e,t,r,n={})=>{if(t==null&&De(e))return[e];if(!De(e)||!De(t))return Ct(e,t,n);if(typeof r=="function")return Te(e,t,1,{transform:r});if(yt(r))return Te(e,t,0,r);let s=N({},n);return s.capture===!0&&(s.wrap=!0),r=r||s.step||1,me(r)?me(e)&&me(t)?Qr(e,t,r,s):Xr(e,t,Math.max(Math.abs(r),1),s):r!=null&&!yt(r)?Fr(r,s):Te(e,t,1,r)};mt.exports=Te});var vt=q((cs,wt)=>{"use strict";var Zr=Ue(),St=He(),Yr=(e,t={})=>{let r=(n,s={})=>{let a=St.isInvalidBrace(s),i=n.invalid===!0&&t.escapeInvalid===!0,o=a===!0||i===!0,h=t.escapeInvalid===!0?"\\":"",A="";if(n.isOpen===!0||n.isClose===!0)return h+n.value;if(n.type==="open")return o?h+n.value:"(";if(n.type==="close")return o?h+n.value:")";if(n.type==="comma")return n.prev.type==="comma"?"":o?n.value:"|";if(n.value)return n.value;if(n.nodes&&n.ranges>0){let f=St.reduce(n.nodes),m=Zr(...f,Q(N({},t),{wrap:!1,toRegex:!0}));if(m.length!==0)return f.length>1&&m.length>1?`(${m})`:m}if(n.nodes)for(let f of n.nodes)A+=r(f,n);return A};return r(e)};wt.exports=Yr});var Tt=q((ls,Ht)=>{"use strict";var zr=Ue(),$t=$e(),he=He(),fe=(e="",t="",r=!1)=>{let n=[];if(e=[].concat(e),t=[].concat(t),!t.length)return e;if(!e.length)return r?he.flatten(t).map(s=>`{${s}}`):t;for(let s of e)if(Array.isArray(s))for(let a of s)n.push(fe(a,t,r));else for(let a of t)r===!0&&typeof a=="string"&&(a=`{${a}}`),n.push(Array.isArray(a)?fe(s,a,r):s+a);return he.flatten(n)},Vr=(e,t={})=>{let r=t.rangeLimit===void 0?1e3:t.rangeLimit,n=(s,a={})=>{s.queue=[];let i=a,o=a.queue;for(;i.type!=="brace"&&i.type!=="root"&&i.parent;)i=i.parent,o=i.queue;if(s.invalid||s.dollar){o.push(fe(o.pop(),$t(s,t)));return}if(s.type==="brace"&&s.invalid!==!0&&s.nodes.length===2){o.push(fe(o.pop(),["{}"]));return}if(s.nodes&&s.ranges>0){let m=he.reduce(s.nodes);if(he.exceedsLimit(...m,t.step,r))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let p=zr(...m,t);p.length===0&&(p=$t(s,t)),o.push(fe(o.pop(),p)),s.nodes=[];return}let h=he.encloseBrace(s),A=s.queue,f=s;for(;f.type!=="brace"&&f.type!=="root"&&f.parent;)f=f.parent,A=f.queue;for(let m=0;m{"use strict";kt.exports={MAX_LENGTH:1024*64,CHAR_0:"0",CHAR_9:"9",CHAR_UPPERCASE_A:"A",CHAR_LOWERCASE_A:"a",CHAR_UPPERCASE_Z:"Z",CHAR_LOWERCASE_Z:"z",CHAR_LEFT_PARENTHESES:"(",CHAR_RIGHT_PARENTHESES:")",CHAR_ASTERISK:"*",CHAR_AMPERSAND:"&",CHAR_AT:"@",CHAR_BACKSLASH:"\\",CHAR_BACKTICK:"`",CHAR_CARRIAGE_RETURN:"\r",CHAR_CIRCUMFLEX_ACCENT:"^",CHAR_COLON:":",CHAR_COMMA:",",CHAR_DOLLAR:"$",CHAR_DOT:".",CHAR_DOUBLE_QUOTE:'"',CHAR_EQUAL:"=",CHAR_EXCLAMATION_MARK:"!",CHAR_FORM_FEED:"\f",CHAR_FORWARD_SLASH:"/",CHAR_HASH:"#",CHAR_HYPHEN_MINUS:"-",CHAR_LEFT_ANGLE_BRACKET:"<",CHAR_LEFT_CURLY_BRACE:"{",CHAR_LEFT_SQUARE_BRACKET:"[",CHAR_LINE_FEED:` -`,CHAR_NO_BREAK_SPACE:"\xA0",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:" ",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\uFEFF"}});var Mt=q((fs,Ot)=>{"use strict";var Jr=$e(),{MAX_LENGTH:Nt,CHAR_BACKSLASH:qe,CHAR_BACKTICK:en,CHAR_COMMA:tn,CHAR_DOT:rn,CHAR_LEFT_PARENTHESES:nn,CHAR_RIGHT_PARENTHESES:sn,CHAR_LEFT_CURLY_BRACE:an,CHAR_RIGHT_CURLY_BRACE:on,CHAR_LEFT_SQUARE_BRACKET:It,CHAR_RIGHT_SQUARE_BRACKET:Bt,CHAR_DOUBLE_QUOTE:un,CHAR_SINGLE_QUOTE:cn,CHAR_NO_BREAK_SPACE:ln,CHAR_ZERO_WIDTH_NOBREAK_SPACE:pn}=Lt(),fn=(e,t={})=>{if(typeof e!="string")throw new TypeError("Expected a string");let r=t||{},n=typeof r.maxLength=="number"?Math.min(Nt,r.maxLength):Nt;if(e.length>n)throw new SyntaxError(`Input length (${e.length}), exceeds max characters (${n})`);let s={type:"root",input:e,nodes:[]},a=[s],i=s,o=s,h=0,A=e.length,f=0,m=0,p,H={},_=()=>e[f++],R=b=>{if(b.type==="text"&&o.type==="dot"&&(o.type="text"),o&&o.type==="text"&&b.type==="text"){o.value+=b.value;return}return i.nodes.push(b),b.parent=i,b.prev=o,o=b,b};for(R({type:"bos"});f0){if(i.ranges>0){i.ranges=0;let b=i.nodes.shift();i.nodes=[b,{type:"text",value:Jr(i)}]}R({type:"comma",value:p}),i.commas++;continue}if(p===rn&&m>0&&i.commas===0){let b=i.nodes;if(m===0||b.length===0){R({type:"text",value:p});continue}if(o.type==="dot"){if(i.range=[],o.value+=p,o.type="range",i.nodes.length!==3&&i.nodes.length!==5){i.invalid=!0,i.ranges=0,o.type="text";continue}i.ranges++,i.args=[];continue}if(o.type==="range"){b.pop();let C=b[b.length-1];C.value+=o.value+p,o=C,i.ranges--;continue}R({type:"dot",value:p});continue}R({type:"text",value:p})}do if(i=a.pop(),i.type!=="root"){i.nodes.forEach(T=>{T.nodes||(T.type==="open"&&(T.isOpen=!0),T.type==="close"&&(T.isClose=!0),T.nodes||(T.type="text"),T.invalid=!0)});let b=a[a.length-1],C=b.nodes.indexOf(i);b.nodes.splice(C,1,...i.nodes)}while(a.length>0);return R({type:"eos"}),s};Ot.exports=fn});var Gt=q((hs,Pt)=>{"use strict";var Dt=$e(),hn=vt(),dn=Tt(),gn=Mt(),z=(e,t={})=>{let r=[];if(Array.isArray(e))for(let n of e){let s=z.create(n,t);Array.isArray(s)?r.push(...s):r.push(s)}else r=[].concat(z.create(e,t));return t&&t.expand===!0&&t.nodupes===!0&&(r=[...new Set(r)]),r};z.parse=(e,t={})=>gn(e,t);z.stringify=(e,t={})=>typeof e=="string"?Dt(z.parse(e,t),t):Dt(e,t);z.compile=(e,t={})=>(typeof e=="string"&&(e=z.parse(e,t)),hn(e,t));z.expand=(e,t={})=>{typeof e=="string"&&(e=z.parse(e,t));let r=dn(e,t);return t.noempty===!0&&(r=r.filter(Boolean)),t.nodupes===!0&&(r=[...new Set(r)]),r};z.create=(e,t={})=>e===""||e.length<3?[e]:t.expand!==!0?z.compile(e,t):z.expand(e,t);Pt.exports=z});var Re=q((ds,Ut)=>{"use strict";var An=require("path"),se="\\\\/",qt=`[^${se}]`,oe="\\.",mn="\\+",Rn="\\?",ke="\\/",yn="(?=.)",Kt="[^/]",Ke=`(?:${ke}|$)`,Wt=`(?:^|${ke})`,We=`${oe}{1,2}${Ke}`,_n=`(?!${oe})`,En=`(?!${Wt}${We})`,xn=`(?!${oe}{0,1}${Ke})`,bn=`(?!${We})`,Cn=`[^.${ke}]`,wn=`${Kt}*?`,jt={DOT_LITERAL:oe,PLUS_LITERAL:mn,QMARK_LITERAL:Rn,SLASH_LITERAL:ke,ONE_CHAR:yn,QMARK:Kt,END_ANCHOR:Ke,DOTS_SLASH:We,NO_DOT:_n,NO_DOTS:En,NO_DOT_SLASH:xn,NO_DOTS_SLASH:bn,QMARK_NO_DOT:Cn,STAR:wn,START_ANCHOR:Wt},Sn=Q(N({},jt),{SLASH_LITERAL:`[${se}]`,QMARK:qt,STAR:`${qt}*?`,DOTS_SLASH:`${oe}{1,2}(?:[${se}]|$)`,NO_DOT:`(?!${oe})`,NO_DOTS:`(?!(?:^|[${se}])${oe}{1,2}(?:[${se}]|$))`,NO_DOT_SLASH:`(?!${oe}{0,1}(?:[${se}]|$))`,NO_DOTS_SLASH:`(?!${oe}{1,2}(?:[${se}]|$))`,QMARK_NO_DOT:`[^.${se}]`,START_ANCHOR:`(?:^|[${se}])`,END_ANCHOR:`(?:[${se}]|$)`}),vn={alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"};Ut.exports={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:vn,REGEX_BACKSLASH:/\\(?![*+?^${}(|)[\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\].,$*+?^{}()|\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\?)((\W)(\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\[.*?[^\\]\]|\\(?=.))/g,REPLACEMENTS:{"***":"*","**/**":"**","**/**/**":"**"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:An.sep,extglobChars(e){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${e.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}},globChars(e){return e===!0?Sn:jt}}});var ye=q(X=>{"use strict";var Hn=require("path"),$n=process.platform==="win32",{REGEX_BACKSLASH:Tn,REGEX_REMOVE_BACKSLASH:kn,REGEX_SPECIAL_CHARS:Ln,REGEX_SPECIAL_CHARS_GLOBAL:On}=Re();X.isObject=e=>e!==null&&typeof e=="object"&&!Array.isArray(e);X.hasRegexChars=e=>Ln.test(e);X.isRegexChar=e=>e.length===1&&X.hasRegexChars(e);X.escapeRegex=e=>e.replace(On,"\\$1");X.toPosixSlashes=e=>e.replace(Tn,"/");X.removeBackslashes=e=>e.replace(kn,t=>t==="\\"?"":t);X.supportsLookbehinds=()=>{let e=process.version.slice(1).split(".").map(Number);return e.length===3&&e[0]>=9||e[0]===8&&e[1]>=10};X.isWindows=e=>e&&typeof e.windows=="boolean"?e.windows:$n===!0||Hn.sep==="\\";X.escapeLast=(e,t,r)=>{let n=e.lastIndexOf(t,r);return n===-1?e:e[n-1]==="\\"?X.escapeLast(e,t,n-1):`${e.slice(0,n)}\\${e.slice(n)}`};X.removePrefix=(e,t={})=>{let r=e;return r.startsWith("./")&&(r=r.slice(2),t.prefix="./"),r};X.wrapOutput=(e,t={},r={})=>{let n=r.contains?"":"^",s=r.contains?"":"$",a=`${n}(?:${e})${s}`;return t.negated===!0&&(a=`(?:^(?!${a}).*$)`),a}});var er=q((As,Ft)=>{"use strict";var Qt=ye(),{CHAR_ASTERISK:je,CHAR_AT:Nn,CHAR_BACKWARD_SLASH:_e,CHAR_COMMA:In,CHAR_DOT:Fe,CHAR_EXCLAMATION_MARK:Xt,CHAR_FORWARD_SLASH:Zt,CHAR_LEFT_CURLY_BRACE:Qe,CHAR_LEFT_PARENTHESES:Xe,CHAR_LEFT_SQUARE_BRACKET:Bn,CHAR_PLUS:Mn,CHAR_QUESTION_MARK:Yt,CHAR_RIGHT_CURLY_BRACE:Pn,CHAR_RIGHT_PARENTHESES:zt,CHAR_RIGHT_SQUARE_BRACKET:Dn}=Re(),Vt=e=>e===Zt||e===_e,Jt=e=>{e.isPrefix!==!0&&(e.depth=e.isGlobstar?Infinity:1)},Gn=(e,t)=>{let r=t||{},n=e.length-1,s=r.parts===!0||r.scanToEnd===!0,a=[],i=[],o=[],h=e,A=-1,f=0,m=0,p=!1,H=!1,_=!1,R=!1,b=!1,C=!1,T=!1,k=!1,E=!1,ee=0,j,y,x={value:"",depth:0,isGlob:!1},M=()=>A>=n,$=()=>h.charCodeAt(A+1),u=()=>(j=y,h.charCodeAt(++A));for(;A0&&(W=h.slice(0,f),h=h.slice(f),m-=f),w&&_===!0&&m>0?(w=h.slice(0,m),P=h.slice(m)):_===!0?(w="",P=h):w=h,w&&w!==""&&w!=="/"&&w!==h&&Vt(w.charCodeAt(w.length-1))&&(w=w.slice(0,-1)),r.unescape===!0&&(P&&(P=Qt.removeBackslashes(P)),w&&T===!0&&(w=Qt.removeBackslashes(w)));let l={prefix:W,input:e,start:f,base:w,glob:P,isBrace:p,isBracket:H,isGlob:_,isExtglob:R,isGlobstar:b,negated:k};if(r.tokens===!0&&(l.maxDepth=0,Vt(y)||i.push(x),l.tokens=i),r.parts===!0||r.tokens===!0){let c;for(let D=0;D{"use strict";var Le=Re(),V=ye(),{MAX_LENGTH:Oe,POSIX_REGEX_SOURCE:Un,REGEX_NON_SPECIAL_CHARS:qn,REGEX_SPECIAL_CHARS_BACKREF:Kn,REPLACEMENTS:rr}=Le,Wn=(e,t)=>{if(typeof t.expandRange=="function")return t.expandRange(...e,t);e.sort();let r=`[${e.join("-")}]`;try{new RegExp(r)}catch(n){return e.map(s=>V.escapeRegex(s)).join("..")}return r},de=(e,t)=>`Missing ${e}: "${t}" - use "\\\\${t}" to match literal characters`,nr=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");e=rr[e]||e;let r=N({},t),n=typeof r.maxLength=="number"?Math.min(Oe,r.maxLength):Oe,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);let a={type:"bos",value:"",output:r.prepend||""},i=[a],o=r.capture?"":"?:",h=V.isWindows(t),A=Le.globChars(h),f=Le.extglobChars(A),{DOT_LITERAL:m,PLUS_LITERAL:p,SLASH_LITERAL:H,ONE_CHAR:_,DOTS_SLASH:R,NO_DOT:b,NO_DOT_SLASH:C,NO_DOTS_SLASH:T,QMARK:k,QMARK_NO_DOT:E,STAR:ee,START_ANCHOR:j}=A,y=g=>`(${o}(?:(?!${j}${g.dot?R:m}).)*?)`,x=r.dot?"":b,M=r.dot?k:E,$=r.bash===!0?y(r):ee;r.capture&&($=`(${$})`),typeof r.noext=="boolean"&&(r.noextglob=r.noext);let u={input:e,index:-1,start:0,dot:r.dot===!0,consumed:"",output:"",prefix:"",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:i};e=V.removePrefix(e,u),s=e.length;let w=[],W=[],P=[],l=a,c,D=()=>u.index===s-1,G=u.peek=(g=1)=>e[u.index+g],te=u.advance=()=>e[++u.index],re=()=>e.slice(u.index+1),ie=(g="",L=0)=>{u.consumed+=g,u.index+=L},be=g=>{u.output+=g.output!=null?g.output:g.value,ie(g.value)},xr=()=>{let g=1;for(;G()==="!"&&(G(2)!=="("||G(3)==="?");)te(),u.start++,g++;return g%2==0?!1:(u.negated=!0,u.start++,!0)},Ce=g=>{u[g]++,P.push(g)},ce=g=>{u[g]--,P.pop()},S=g=>{if(l.type==="globstar"){let L=u.braces>0&&(g.type==="comma"||g.type==="brace"),d=g.extglob===!0||w.length&&(g.type==="pipe"||g.type==="paren");g.type!=="slash"&&g.type!=="paren"&&!L&&!d&&(u.output=u.output.slice(0,-l.output.length),l.type="star",l.value="*",l.output=$,u.output+=l.output)}if(w.length&&g.type!=="paren"&&!f[g.value]&&(w[w.length-1].inner+=g.value),(g.value||g.output)&&be(g),l&&l.type==="text"&&g.type==="text"){l.value+=g.value,l.output=(l.output||"")+g.value;return}g.prev=l,i.push(g),l=g},we=(g,L)=>{let d=Q(N({},f[L]),{conditions:1,inner:""});d.prev=l,d.parens=u.parens,d.output=u.output;let v=(r.capture?"(":"")+d.open;Ce("parens"),S({type:g,value:L,output:u.output?"":_}),S({type:"paren",extglob:!0,value:te(),output:v}),w.push(d)},br=g=>{let L=g.close+(r.capture?")":"");if(g.type==="negate"){let d=$;g.inner&&g.inner.length>1&&g.inner.includes("/")&&(d=y(r)),(d!==$||D()||/^\)+$/.test(re()))&&(L=g.close=`)$))${d}`),g.prev.type==="bos"&&(u.negatedExtglob=!0)}S({type:"paren",extglob:!0,value:c,output:L}),ce("parens")};if(r.fastpaths!==!1&&!/(^[*!]|[/()[\]{}"])/.test(e)){let g=!1,L=e.replace(Kn,(d,v,I,F,U,Me)=>F==="\\"?(g=!0,d):F==="?"?v?v+F+(U?k.repeat(U.length):""):Me===0?M+(U?k.repeat(U.length):""):k.repeat(I.length):F==="."?m.repeat(I.length):F==="*"?v?v+F+(U?$:""):$:v?d:`\\${d}`);return g===!0&&(r.unescape===!0?L=L.replace(/\\/g,""):L=L.replace(/\\+/g,d=>d.length%2==0?"\\\\":d?"\\":"")),L===e&&r.contains===!0?(u.output=e,u):(u.output=V.wrapOutput(L,u,t),u)}for(;!D();){if(c=te(),c==="\0")continue;if(c==="\\"){let d=G();if(d==="/"&&r.bash!==!0||d==="."||d===";")continue;if(!d){c+="\\",S({type:"text",value:c});continue}let v=/^\\+/.exec(re()),I=0;if(v&&v[0].length>2&&(I=v[0].length,u.index+=I,I%2!=0&&(c+="\\")),r.unescape===!0?c=te()||"":c+=te()||"",u.brackets===0){S({type:"text",value:c});continue}}if(u.brackets>0&&(c!=="]"||l.value==="["||l.value==="[^")){if(r.posix!==!1&&c===":"){let d=l.value.slice(1);if(d.includes("[")&&(l.posix=!0,d.includes(":"))){let v=l.value.lastIndexOf("["),I=l.value.slice(0,v),F=l.value.slice(v+2),U=Un[F];if(U){l.value=I+U,u.backtrack=!0,te(),!a.output&&i.indexOf(l)===1&&(a.output=_);continue}}}(c==="["&&G()!==":"||c==="-"&&G()==="]")&&(c=`\\${c}`),c==="]"&&(l.value==="["||l.value==="[^")&&(c=`\\${c}`),r.posix===!0&&c==="!"&&l.value==="["&&(c="^"),l.value+=c,be({value:c});continue}if(u.quotes===1&&c!=='"'){c=V.escapeRegex(c),l.value+=c,be({value:c});continue}if(c==='"'){u.quotes=u.quotes===1?0:1,r.keepQuotes===!0&&S({type:"text",value:c});continue}if(c==="("){Ce("parens"),S({type:"paren",value:c});continue}if(c===")"){if(u.parens===0&&r.strictBrackets===!0)throw new SyntaxError(de("opening","("));let d=w[w.length-1];if(d&&u.parens===d.parens+1){br(w.pop());continue}S({type:"paren",value:c,output:u.parens?")":"\\)"}),ce("parens");continue}if(c==="["){if(r.nobracket===!0||!re().includes("]")){if(r.nobracket!==!0&&r.strictBrackets===!0)throw new SyntaxError(de("closing","]"));c=`\\${c}`}else Ce("brackets");S({type:"bracket",value:c});continue}if(c==="]"){if(r.nobracket===!0||l&&l.type==="bracket"&&l.value.length===1){S({type:"text",value:c,output:`\\${c}`});continue}if(u.brackets===0){if(r.strictBrackets===!0)throw new SyntaxError(de("opening","["));S({type:"text",value:c,output:`\\${c}`});continue}ce("brackets");let d=l.value.slice(1);if(l.posix!==!0&&d[0]==="^"&&!d.includes("/")&&(c=`/${c}`),l.value+=c,be({value:c}),r.literalBrackets===!1||V.hasRegexChars(d))continue;let v=V.escapeRegex(l.value);if(u.output=u.output.slice(0,-l.value.length),r.literalBrackets===!0){u.output+=v,l.value=v;continue}l.value=`(${o}${v}|${l.value})`,u.output+=l.value;continue}if(c==="{"&&r.nobrace!==!0){Ce("braces");let d={type:"brace",value:c,output:"(",outputIndex:u.output.length,tokensIndex:u.tokens.length};W.push(d),S(d);continue}if(c==="}"){let d=W[W.length-1];if(r.nobrace===!0||!d){S({type:"text",value:c,output:c});continue}let v=")";if(d.dots===!0){let I=i.slice(),F=[];for(let U=I.length-1;U>=0&&(i.pop(),I[U].type!=="brace");U--)I[U].type!=="dots"&&F.unshift(I[U].value);v=Wn(F,r),u.backtrack=!0}if(d.comma!==!0&&d.dots!==!0){let I=u.output.slice(0,d.outputIndex),F=u.tokens.slice(d.tokensIndex);d.value=d.output="\\{",c=v="\\}",u.output=I;for(let U of F)u.output+=U.output||U.value}S({type:"brace",value:c,output:v}),ce("braces"),W.pop();continue}if(c==="|"){w.length>0&&w[w.length-1].conditions++,S({type:"text",value:c});continue}if(c===","){let d=c,v=W[W.length-1];v&&P[P.length-1]==="braces"&&(v.comma=!0,d="|"),S({type:"comma",value:c,output:d});continue}if(c==="/"){if(l.type==="dot"&&u.index===u.start+1){u.start=u.index+1,u.consumed="",u.output="",i.pop(),l=a;continue}S({type:"slash",value:c,output:H});continue}if(c==="."){if(u.braces>0&&l.type==="dot"){l.value==="."&&(l.output=m);let d=W[W.length-1];l.type="dots",l.output+=c,l.value+=c,d.dots=!0;continue}if(u.braces+u.parens===0&&l.type!=="bos"&&l.type!=="slash"){S({type:"text",value:c,output:m});continue}S({type:"dot",value:c,output:m});continue}if(c==="?"){if(!(l&&l.value==="(")&&r.noextglob!==!0&&G()==="("&&G(2)!=="?"){we("qmark",c);continue}if(l&&l.type==="paren"){let v=G(),I=c;if(v==="<"&&!V.supportsLookbehinds())throw new Error("Node.js v10 or higher is required for regex lookbehinds");(l.value==="("&&!/[!=<:]/.test(v)||v==="<"&&!/<([!=]|\w+>)/.test(re()))&&(I=`\\${c}`),S({type:"text",value:c,output:I});continue}if(r.dot!==!0&&(l.type==="slash"||l.type==="bos")){S({type:"qmark",value:c,output:E});continue}S({type:"qmark",value:c,output:k});continue}if(c==="!"){if(r.noextglob!==!0&&G()==="("&&(G(2)!=="?"||!/[!=<:]/.test(G(3)))){we("negate",c);continue}if(r.nonegate!==!0&&u.index===0){xr();continue}}if(c==="+"){if(r.noextglob!==!0&&G()==="("&&G(2)!=="?"){we("plus",c);continue}if(l&&l.value==="("||r.regex===!1){S({type:"plus",value:c,output:p});continue}if(l&&(l.type==="bracket"||l.type==="paren"||l.type==="brace")||u.parens>0){S({type:"plus",value:c});continue}S({type:"plus",value:p});continue}if(c==="@"){if(r.noextglob!==!0&&G()==="("&&G(2)!=="?"){S({type:"at",extglob:!0,value:c,output:""});continue}S({type:"text",value:c});continue}if(c!=="*"){(c==="$"||c==="^")&&(c=`\\${c}`);let d=qn.exec(re());d&&(c+=d[0],u.index+=d[0].length),S({type:"text",value:c});continue}if(l&&(l.type==="globstar"||l.star===!0)){l.type="star",l.star=!0,l.value+=c,l.output=$,u.backtrack=!0,u.globstar=!0,ie(c);continue}let g=re();if(r.noextglob!==!0&&/^\([^?]/.test(g)){we("star",c);continue}if(l.type==="star"){if(r.noglobstar===!0){ie(c);continue}let d=l.prev,v=d.prev,I=d.type==="slash"||d.type==="bos",F=v&&(v.type==="star"||v.type==="globstar");if(r.bash===!0&&(!I||g[0]&&g[0]!=="/")){S({type:"star",value:c,output:""});continue}let U=u.braces>0&&(d.type==="comma"||d.type==="brace"),Me=w.length&&(d.type==="pipe"||d.type==="paren");if(!I&&d.type!=="paren"&&!U&&!Me){S({type:"star",value:c,output:""});continue}for(;g.slice(0,3)==="/**";){let Se=e[u.index+4];if(Se&&Se!=="/")break;g=g.slice(3),ie("/**",3)}if(d.type==="bos"&&D()){l.type="globstar",l.value+=c,l.output=y(r),u.output=l.output,u.globstar=!0,ie(c);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&!F&&D()){u.output=u.output.slice(0,-(d.output+l.output).length),d.output=`(?:${d.output}`,l.type="globstar",l.output=y(r)+(r.strictSlashes?")":"|$)"),l.value+=c,u.globstar=!0,u.output+=d.output+l.output,ie(c);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&g[0]==="/"){let Se=g[1]!==void 0?"|$":"";u.output=u.output.slice(0,-(d.output+l.output).length),d.output=`(?:${d.output}`,l.type="globstar",l.output=`${y(r)}${H}|${H}${Se})`,l.value+=c,u.output+=d.output+l.output,u.globstar=!0,ie(c+te()),S({type:"slash",value:"/",output:""});continue}if(d.type==="bos"&&g[0]==="/"){l.type="globstar",l.value+=c,l.output=`(?:^|${H}|${y(r)}${H})`,u.output=l.output,u.globstar=!0,ie(c+te()),S({type:"slash",value:"/",output:""});continue}u.output=u.output.slice(0,-l.output.length),l.type="globstar",l.output=y(r),l.value+=c,u.output+=l.output,u.globstar=!0,ie(c);continue}let L={type:"star",value:c,output:$};if(r.bash===!0){L.output=".*?",(l.type==="bos"||l.type==="slash")&&(L.output=x+L.output),S(L);continue}if(l&&(l.type==="bracket"||l.type==="paren")&&r.regex===!0){L.output=c,S(L);continue}(u.index===u.start||l.type==="slash"||l.type==="dot")&&(l.type==="dot"?(u.output+=C,l.output+=C):r.dot===!0?(u.output+=T,l.output+=T):(u.output+=x,l.output+=x),G()!=="*"&&(u.output+=_,l.output+=_)),S(L)}for(;u.brackets>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing","]"));u.output=V.escapeLast(u.output,"["),ce("brackets")}for(;u.parens>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing",")"));u.output=V.escapeLast(u.output,"("),ce("parens")}for(;u.braces>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing","}"));u.output=V.escapeLast(u.output,"{"),ce("braces")}if(r.strictSlashes!==!0&&(l.type==="star"||l.type==="bracket")&&S({type:"maybe_slash",value:"",output:`${H}?`}),u.backtrack===!0){u.output="";for(let g of u.tokens)u.output+=g.output!=null?g.output:g.value,g.suffix&&(u.output+=g.suffix)}return u};nr.fastpaths=(e,t)=>{let r=N({},t),n=typeof r.maxLength=="number"?Math.min(Oe,r.maxLength):Oe,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);e=rr[e]||e;let a=V.isWindows(t),{DOT_LITERAL:i,SLASH_LITERAL:o,ONE_CHAR:h,DOTS_SLASH:A,NO_DOT:f,NO_DOTS:m,NO_DOTS_SLASH:p,STAR:H,START_ANCHOR:_}=Le.globChars(a),R=r.dot?m:f,b=r.dot?p:f,C=r.capture?"":"?:",T={negated:!1,prefix:""},k=r.bash===!0?".*?":H;r.capture&&(k=`(${k})`);let E=x=>x.noglobstar===!0?k:`(${C}(?:(?!${_}${x.dot?A:i}).)*?)`,ee=x=>{switch(x){case"*":return`${R}${h}${k}`;case".*":return`${i}${h}${k}`;case"*.*":return`${R}${k}${i}${h}${k}`;case"*/*":return`${R}${k}${o}${h}${b}${k}`;case"**":return R+E(r);case"**/*":return`(?:${R}${E(r)}${o})?${b}${h}${k}`;case"**/*.*":return`(?:${R}${E(r)}${o})?${b}${k}${i}${h}${k}`;case"**/.*":return`(?:${R}${E(r)}${o})?${i}${h}${k}`;default:{let M=/^(.*?)\.(\w+)$/.exec(x);if(!M)return;let $=ee(M[1]);return $?$+i+M[2]:void 0}}},j=V.removePrefix(e,T),y=ee(j);return y&&r.strictSlashes!==!0&&(y+=`${o}?`),y};tr.exports=nr});var ir=q((Rs,ar)=>{"use strict";var jn=require("path"),Fn=er(),Ze=sr(),Ye=ye(),Qn=Re(),Xn=e=>e&&typeof e=="object"&&!Array.isArray(e),B=(e,t,r=!1)=>{if(Array.isArray(e)){let f=e.map(p=>B(p,t,r));return p=>{for(let H of f){let _=H(p);if(_)return _}return!1}}let n=Xn(e)&&e.tokens&&e.input;if(e===""||typeof e!="string"&&!n)throw new TypeError("Expected pattern to be a non-empty string");let s=t||{},a=Ye.isWindows(t),i=n?B.compileRe(e,t):B.makeRe(e,t,!1,!0),o=i.state;delete i.state;let h=()=>!1;if(s.ignore){let f=Q(N({},t),{ignore:null,onMatch:null,onResult:null});h=B(s.ignore,f,r)}let A=(f,m=!1)=>{let{isMatch:p,match:H,output:_}=B.test(f,i,t,{glob:e,posix:a}),R={glob:e,state:o,regex:i,posix:a,input:f,output:_,match:H,isMatch:p};return typeof s.onResult=="function"&&s.onResult(R),p===!1?(R.isMatch=!1,m?R:!1):h(f)?(typeof s.onIgnore=="function"&&s.onIgnore(R),R.isMatch=!1,m?R:!1):(typeof s.onMatch=="function"&&s.onMatch(R),m?R:!0)};return r&&(A.state=o),A};B.test=(e,t,r,{glob:n,posix:s}={})=>{if(typeof e!="string")throw new TypeError("Expected input to be a string");if(e==="")return{isMatch:!1,output:""};let a=r||{},i=a.format||(s?Ye.toPosixSlashes:null),o=e===n,h=o&&i?i(e):e;return o===!1&&(h=i?i(e):e,o=h===n),(o===!1||a.capture===!0)&&(a.matchBase===!0||a.basename===!0?o=B.matchBase(e,t,r,s):o=t.exec(h)),{isMatch:Boolean(o),match:o,output:h}};B.matchBase=(e,t,r,n=Ye.isWindows(r))=>(t instanceof RegExp?t:B.makeRe(t,r)).test(jn.basename(e));B.isMatch=(e,t,r)=>B(t,r)(e);B.parse=(e,t)=>Array.isArray(e)?e.map(r=>B.parse(r,t)):Ze(e,Q(N({},t),{fastpaths:!1}));B.scan=(e,t)=>Fn(e,t);B.compileRe=(e,t,r=!1,n=!1)=>{if(r===!0)return e.output;let s=t||{},a=s.contains?"":"^",i=s.contains?"":"$",o=`${a}(?:${e.output})${i}`;e&&e.negated===!0&&(o=`^(?!${o}).*$`);let h=B.toRegex(o,t);return n===!0&&(h.state=e),h};B.makeRe=(e,t,r=!1,n=!1)=>{if(!e||typeof e!="string")throw new TypeError("Expected a non-empty string");let s=t||{},a={negated:!1,fastpaths:!0},i="",o;return e.startsWith("./")&&(e=e.slice(2),i=a.prefix="./"),s.fastpaths!==!1&&(e[0]==="."||e[0]==="*")&&(o=Ze.fastpaths(e,t)),o===void 0?(a=Ze(e,t),a.prefix=i+(a.prefix||"")):a.output=o,B.compileRe(a,t,r,n)};B.toRegex=(e,t)=>{try{let r=t||{};return new RegExp(e,r.flags||(r.nocase?"i":""))}catch(r){if(t&&t.debug===!0)throw r;return/$^/}};B.constants=Qn;ar.exports=B});var ur=q((ys,or)=>{"use strict";or.exports=ir()});var hr=q((_s,cr)=>{"use strict";var lr=require("util"),pr=Gt(),ae=ur(),ze=ye(),fr=e=>typeof e=="string"&&(e===""||e==="./"),O=(e,t,r)=>{t=[].concat(t),e=[].concat(e);let n=new Set,s=new Set,a=new Set,i=0,o=f=>{a.add(f.output),r&&r.onResult&&r.onResult(f)};for(let f=0;f!n.has(f));if(r&&A.length===0){if(r.failglob===!0)throw new Error(`No matches found for "${t.join(", ")}"`);if(r.nonull===!0||r.nullglob===!0)return r.unescape?t.map(f=>f.replace(/\\/g,"")):t}return A};O.match=O;O.matcher=(e,t)=>ae(e,t);O.isMatch=(e,t,r)=>ae(t,r)(e);O.any=O.isMatch;O.not=(e,t,r={})=>{t=[].concat(t).map(String);let n=new Set,s=[],a=o=>{r.onResult&&r.onResult(o),s.push(o.output)},i=O(e,t,Q(N({},r),{onResult:a}));for(let o of s)i.includes(o)||n.add(o);return[...n]};O.contains=(e,t,r)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${lr.inspect(e)}"`);if(Array.isArray(t))return t.some(n=>O.contains(e,n,r));if(typeof t=="string"){if(fr(e)||fr(t))return!1;if(e.includes(t)||e.startsWith("./")&&e.slice(2).includes(t))return!0}return O.isMatch(e,t,Q(N({},r),{contains:!0}))};O.matchKeys=(e,t,r)=>{if(!ze.isObject(e))throw new TypeError("Expected the first argument to be an object");let n=O(Object.keys(e),t,r),s={};for(let a of n)s[a]=e[a];return s};O.some=(e,t,r)=>{let n=[].concat(e);for(let s of[].concat(t)){let a=ae(String(s),r);if(n.some(i=>a(i)))return!0}return!1};O.every=(e,t,r)=>{let n=[].concat(e);for(let s of[].concat(t)){let a=ae(String(s),r);if(!n.every(i=>a(i)))return!1}return!0};O.all=(e,t,r)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${lr.inspect(e)}"`);return[].concat(t).every(n=>ae(n,r)(e))};O.capture=(e,t,r)=>{let n=ze.isWindows(r),a=ae.makeRe(String(e),Q(N({},r),{capture:!0})).exec(n?ze.toPosixSlashes(t):t);if(a)return a.slice(1).map(i=>i===void 0?"":i)};O.makeRe=(...e)=>ae.makeRe(...e);O.scan=(...e)=>ae.scan(...e);O.parse=(e,t)=>{let r=[];for(let n of[].concat(e||[]))for(let s of pr(String(n),t))r.push(ae.parse(s,t));return r};O.braces=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");return t&&t.nobrace===!0||!/\{.*\}/.test(e)?[e]:pr(e,t)};O.braceExpand=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");return O.braces(e,Q(N({},t),{expand:!0}))};cr.exports=O});var gr=q((Es,dr)=>{"use strict";dr.exports=(e,...t)=>new Promise(r=>{r(e(...t))})});var mr=q((xs,Ve)=>{"use strict";var Zn=gr(),Ar=e=>{if(e<1)throw new TypeError("Expected `concurrency` to be a number from 1 and up");let t=[],r=0,n=()=>{r--,t.length>0&&t.shift()()},s=(o,h,...A)=>{r++;let f=Zn(o,...A);h(f),f.then(n,n)},a=(o,h,...A)=>{rnew Promise(A=>a(o,A,...h));return Object.defineProperties(i,{activeCount:{get:()=>r},pendingCount:{get:()=>t.length}}),i};Ve.exports=Ar;Ve.exports.default=Ar});var zn={};Lr(zn,{default:()=>Jn});var ve=Y(require("@yarnpkg/cli")),ne=Y(require("@yarnpkg/core")),rt=Y(require("@yarnpkg/core")),le=Y(require("clipanion")),Ae=class extends ve.BaseCommand{constructor(){super(...arguments);this.json=le.Option.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.production=le.Option.Boolean("--production",!1,{description:"Only install regular dependencies by omitting dev dependencies"});this.all=le.Option.Boolean("-A,--all",!1,{description:"Install the entire project"});this.workspaces=le.Option.Rest()}async execute(){let t=await ne.Configuration.find(this.context.cwd,this.context.plugins),{project:r,workspace:n}=await ne.Project.find(t,this.context.cwd),s=await ne.Cache.find(t);await r.restoreInstallState({restoreResolutions:!1});let a;if(this.all)a=new Set(r.workspaces);else if(this.workspaces.length===0){if(!n)throw new ve.WorkspaceRequiredError(r.cwd,this.context.cwd);a=new Set([n])}else a=new Set(this.workspaces.map(o=>r.getWorkspaceByIdent(rt.structUtils.parseIdent(o))));for(let o of a)for(let h of this.production?["dependencies"]:ne.Manifest.hardDependencies)for(let A of o.manifest.getForScope(h).values()){let f=r.tryWorkspaceByDescriptor(A);f!==null&&a.add(f)}for(let o of r.workspaces)a.has(o)?this.production&&o.manifest.devDependencies.clear():(o.manifest.dependencies.clear(),o.manifest.devDependencies.clear(),o.manifest.peerDependencies.clear(),o.manifest.scripts.clear());return(await ne.StreamReport.start({configuration:t,json:this.json,stdout:this.context.stdout,includeLogs:!0},async o=>{await r.install({cache:s,report:o,persistProject:!1})})).exitCode()}};Ae.paths=[["workspaces","focus"]],Ae.usage=le.Command.Usage({category:"Workspace-related commands",description:"install a single workspace and its dependencies",details:"\n This command will run an install as if the specified workspaces (and all other workspaces they depend on) were the only ones in the project. If no workspaces are explicitly listed, the active one will be assumed.\n\n Note that this command is only very moderately useful when using zero-installs, since the cache will contain all the packages anyway - meaning that the only difference between a full install and a focused install would just be a few extra lines in the `.pnp.cjs` file, at the cost of introducing an extra complexity.\n\n If the `-A,--all` flag is set, the entire project will be installed. Combine with `--production` to replicate the old `yarn install --production`.\n "});var nt=Ae;var Ne=Y(require("@yarnpkg/cli")),Ie=Y(require("@yarnpkg/core")),Ee=Y(require("@yarnpkg/core")),Z=Y(require("@yarnpkg/core")),K=Y(require("clipanion")),Be=Y(hr()),Rr=Y(require("os")),yr=Y(mr()),ue=Y(require("typanion")),xe=class extends Ne.BaseCommand{constructor(){super(...arguments);this.recursive=K.Option.Boolean("-R,--recursive",!1,{description:"Find packages via dependencies/devDependencies instead of using the workspaces field"});this.from=K.Option.Array("--from",[],{description:"An array of glob pattern idents from which to base any recursion"});this.all=K.Option.Boolean("-A,--all",!1,{description:"Run the command on all workspaces of a project"});this.verbose=K.Option.Boolean("-v,--verbose",!1,{description:"Prefix each output line with the name of the originating workspace"});this.parallel=K.Option.Boolean("-p,--parallel",!1,{description:"Run the commands in parallel"});this.interlaced=K.Option.Boolean("-i,--interlaced",!1,{description:"Print the output of commands in real-time instead of buffering it"});this.jobs=K.Option.String("-j,--jobs",{description:"The maximum number of parallel tasks that the execution will be limited to",validator:ue.applyCascade(ue.isNumber(),[ue.isInteger(),ue.isAtLeast(2)])});this.topological=K.Option.Boolean("-t,--topological",!1,{description:"Run the command after all workspaces it depends on (regular) have finished"});this.topologicalDev=K.Option.Boolean("--topological-dev",!1,{description:"Run the command after all workspaces it depends on (regular + dev) have finished"});this.include=K.Option.Array("--include",[],{description:"An array of glob pattern idents; only matching workspaces will be traversed"});this.exclude=K.Option.Array("--exclude",[],{description:"An array of glob pattern idents; matching workspaces won't be traversed"});this.publicOnly=K.Option.Boolean("--no-private",{description:"Avoid running the command on private workspaces"});this.commandName=K.Option.String();this.args=K.Option.Proxy()}async execute(){let t=await Ie.Configuration.find(this.context.cwd,this.context.plugins),{project:r,workspace:n}=await Ie.Project.find(t,this.context.cwd);if(!this.all&&!n)throw new Ne.WorkspaceRequiredError(r.cwd,this.context.cwd);let s=this.cli.process([this.commandName,...this.args]),a=s.path.length===1&&s.path[0]==="run"&&typeof s.scriptName!="undefined"?s.scriptName:null;if(s.path.length===0)throw new K.UsageError("Invalid subcommand name for iteration - use the 'run' keyword if you wish to execute a script");let i=this.all?r.topLevelWorkspace:n,o=E=>Be.default.isMatch(Z.structUtils.stringifyIdent(E.locator),this.from),h=this.from.length>0?[i,...i.getRecursiveWorkspaceChildren()].filter(o):[i],A=this.recursive?[...h,...h.map(E=>[...E.getRecursiveWorkspaceDependencies()]).flat()]:[...h,...h.map(E=>[...E.getRecursiveWorkspaceChildren()]).flat()],f=[];for(let E of A)a&&!E.manifest.scripts.has(a)&&!a.includes(":")||a===process.env.npm_lifecycle_event&&E.cwd===n.cwd||this.include.length>0&&!Be.default.isMatch(Z.structUtils.stringifyIdent(E.locator),this.include)||this.exclude.length>0&&Be.default.isMatch(Z.structUtils.stringifyIdent(E.locator),this.exclude)||this.publicOnly&&E.manifest.private===!0||f.push(E);let m=this.interlaced;this.parallel||(m=!0);let p=new Map,H=new Set,_=this.parallel?Math.max(1,(0,Rr.cpus)().length/2):1,R=(0,yr.default)(this.jobs||_),b=0,C=null,T=!1,k=await Ee.StreamReport.start({configuration:t,stdout:this.context.stdout},async E=>{let ee=async(j,{commandIndex:y})=>{if(T)return-1;!this.parallel&&this.verbose&&y>1&&E.reportSeparator();let x=Yn(j,{configuration:t,verbose:this.verbose,commandIndex:y}),[M,$]=_r(E,{prefix:x,interlaced:m}),[u,w]=_r(E,{prefix:x,interlaced:m});try{this.verbose&&E.reportInfo(null,`${x} Process started`);let W=Date.now(),P=await this.cli.run([this.commandName,...this.args],{cwd:j.cwd,stdout:M,stderr:u})||0;M.end(),u.end(),await $,await w;let l=Date.now();if(this.verbose){let c=t.get("enableTimers")?`, completed in ${Z.formatUtils.pretty(t,l-W,Z.formatUtils.Type.DURATION)}`:"";E.reportInfo(null,`${x} Process exited (exit code ${P})${c}`)}return P===130&&(T=!0,C=P),P}catch(W){throw M.end(),u.end(),await $,await w,W}};for(let j of f)p.set(j.anchoredLocator.locatorHash,j);for(;p.size>0&&!E.hasErrors();){let j=[];for(let[M,$]of p){if(H.has($.anchoredDescriptor.descriptorHash))continue;let u=!0;if(this.topological||this.topologicalDev){let w=this.topologicalDev?new Map([...$.manifest.dependencies,...$.manifest.devDependencies]):$.manifest.dependencies;for(let W of w.values()){let P=r.tryWorkspaceByDescriptor(W);if(u=P===null||!p.has(P.anchoredLocator.locatorHash),!u)break}}if(!!u&&(H.add($.anchoredDescriptor.descriptorHash),j.push(R(async()=>{let w=await ee($,{commandIndex:++b});return p.delete(M),H.delete($.anchoredDescriptor.descriptorHash),w})),!this.parallel))break}if(j.length===0){let M=Array.from(p.values()).map($=>Z.structUtils.prettyLocator(t,$.anchoredLocator)).join(", ");E.reportError(Ee.MessageName.CYCLIC_DEPENDENCIES,`Dependency cycle detected (${M})`);return}let x=(await Promise.all(j)).find(M=>M!==0);C===null&&(C=typeof x!="undefined"?1:C),(this.topological||this.topologicalDev)&&typeof x!="undefined"&&E.reportError(Ee.MessageName.UNNAMED,"The command failed for workspaces that are depended upon by other workspaces; can't satisfy the dependency graph")}});return C!==null?C:k.exitCode()}};xe.paths=[["workspaces","foreach"]],xe.usage=K.Command.Usage({category:"Workspace-related commands",description:"run a command on all workspaces",details:"\n This command will run a given sub-command on current and all its descendant workspaces. Various flags can alter the exact behavior of the command:\n\n - If `-p,--parallel` is set, the commands will be ran in parallel; they'll by default be limited to a number of parallel tasks roughly equal to half your core number, but that can be overridden via `-j,--jobs`.\n\n - If `-p,--parallel` and `-i,--interlaced` are both set, Yarn will print the lines from the output as it receives them. If `-i,--interlaced` wasn't set, it would instead buffer the output from each process and print the resulting buffers only after their source processes have exited.\n\n - If `-t,--topological` is set, Yarn will only run the command after all workspaces that it depends on through the `dependencies` field have successfully finished executing. If `--topological-dev` is set, both the `dependencies` and `devDependencies` fields will be considered when figuring out the wait points.\n\n - If `-A,--all` is set, Yarn will run the command on all the workspaces of a project. By default yarn runs the command only on current and all its descendant workspaces.\n\n - If `-R,--recursive` is set, Yarn will find workspaces to run the command on by recursively evaluating `dependencies` and `devDependencies` fields, instead of looking at the `workspaces` fields.\n\n - If `--from` is set, Yarn will use the packages matching the 'from' glob as the starting point for any recursive search.\n\n - The command may apply to only some workspaces through the use of `--include` which acts as a whitelist. The `--exclude` flag will do the opposite and will be a list of packages that mustn't execute the script. Both flags accept glob patterns (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n\n Adding the `-v,--verbose` flag will cause Yarn to print more information; in particular the name of the workspace that generated the output will be printed at the front of each line.\n\n If the command is `run` and the script being run does not exist the child workspace will be skipped without error.\n ",examples:[["Publish current and all descendant packages","yarn workspaces foreach npm publish --tolerate-republish"],["Run build script on current and all descendant packages","yarn workspaces foreach run build"],["Run build script on current and all descendant packages in parallel, building package dependencies first","yarn workspaces foreach -pt run build"],["Run build script on several packages and all their dependencies, building dependencies first","yarn workspaces foreach -ptR --from '{workspace-a,workspace-b}' run build"]]});var Er=xe;function _r(e,{prefix:t,interlaced:r}){let n=e.createStreamReporter(t),s=new Z.miscUtils.DefaultStream;s.pipe(n,{end:!1}),s.on("finish",()=>{n.end()});let a=new Promise(o=>{n.on("finish",()=>{o(s.active)})});if(r)return[s,a];let i=new Z.miscUtils.BufferStream;return i.pipe(s,{end:!1}),i.on("finish",()=>{s.end()}),[i,a]}function Yn(e,{configuration:t,commandIndex:r,verbose:n}){if(!n)return null;let s=Z.structUtils.convertToIdent(e.locator),i=`[${Z.structUtils.stringifyIdent(s)}]:`,o=["#2E86AB","#A23B72","#F18F01","#C73E1D","#CCE2A3"],h=o[r%o.length];return Z.formatUtils.pretty(t,i,h)}var Vn={commands:[nt,Er]},Jn=Vn;return zn;})(); -/*! - * fill-range - * - * Copyright (c) 2014-present, Jon Schlinkert. - * Licensed under the MIT License. - */ -/*! - * is-number - * - * Copyright (c) 2014-present, Jon Schlinkert. - * Released under the MIT License. - */ -/*! - * to-regex-range - * - * Copyright (c) 2015-present, Jon Schlinkert. - * Released under the MIT License. - */ -return plugin; -} -}; diff --git a/.yarn/releases/yarn-3.5.0.cjs b/.yarn/releases/yarn-3.5.0.cjs deleted file mode 100755 index 093e64a9fe46..000000000000 --- a/.yarn/releases/yarn-3.5.0.cjs +++ /dev/null @@ -1,873 +0,0 @@ -#!/usr/bin/env node -/* eslint-disable */ -//prettier-ignore -(()=>{var Qge=Object.create;var AS=Object.defineProperty;var bge=Object.getOwnPropertyDescriptor;var Sge=Object.getOwnPropertyNames;var vge=Object.getPrototypeOf,xge=Object.prototype.hasOwnProperty;var J=(r=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(r,{get:(e,t)=>(typeof require<"u"?require:e)[t]}):r)(function(r){if(typeof require<"u")return require.apply(this,arguments);throw new Error('Dynamic require of "'+r+'" is not supported')});var Pge=(r,e)=>()=>(r&&(e=r(r=0)),e);var w=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports),ut=(r,e)=>{for(var t in e)AS(r,t,{get:e[t],enumerable:!0})},Dge=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Sge(e))!xge.call(r,n)&&n!==t&&AS(r,n,{get:()=>e[n],enumerable:!(i=bge(e,n))||i.enumerable});return r};var Pe=(r,e,t)=>(t=r!=null?Qge(vge(r)):{},Dge(e||!r||!r.__esModule?AS(t,"default",{value:r,enumerable:!0}):t,r));var QK=w((GXe,BK)=>{BK.exports=wK;wK.sync=Zge;var IK=J("fs");function Xge(r,e){var t=e.pathExt!==void 0?e.pathExt:process.env.PATHEXT;if(!t||(t=t.split(";"),t.indexOf("")!==-1))return!0;for(var i=0;i{xK.exports=SK;SK.sync=_ge;var bK=J("fs");function SK(r,e,t){bK.stat(r,function(i,n){t(i,i?!1:vK(n,e))})}function _ge(r,e){return vK(bK.statSync(r),e)}function vK(r,e){return r.isFile()&&$ge(r,e)}function $ge(r,e){var t=r.mode,i=r.uid,n=r.gid,s=e.uid!==void 0?e.uid:process.getuid&&process.getuid(),o=e.gid!==void 0?e.gid:process.getgid&&process.getgid(),a=parseInt("100",8),l=parseInt("010",8),c=parseInt("001",8),u=a|l,g=t&c||t&l&&n===o||t&a&&i===s||t&u&&s===0;return g}});var kK=w((qXe,DK)=>{var jXe=J("fs"),sI;process.platform==="win32"||global.TESTING_WINDOWS?sI=QK():sI=PK();DK.exports=SS;SS.sync=efe;function SS(r,e,t){if(typeof e=="function"&&(t=e,e={}),!t){if(typeof Promise!="function")throw new TypeError("callback not provided");return new Promise(function(i,n){SS(r,e||{},function(s,o){s?n(s):i(o)})})}sI(r,e||{},function(i,n){i&&(i.code==="EACCES"||e&&e.ignoreErrors)&&(i=null,n=!1),t(i,n)})}function efe(r,e){try{return sI.sync(r,e||{})}catch(t){if(e&&e.ignoreErrors||t.code==="EACCES")return!1;throw t}}});var MK=w((JXe,OK)=>{var vg=process.platform==="win32"||process.env.OSTYPE==="cygwin"||process.env.OSTYPE==="msys",RK=J("path"),tfe=vg?";":":",FK=kK(),NK=r=>Object.assign(new Error(`not found: ${r}`),{code:"ENOENT"}),LK=(r,e)=>{let t=e.colon||tfe,i=r.match(/\//)||vg&&r.match(/\\/)?[""]:[...vg?[process.cwd()]:[],...(e.path||process.env.PATH||"").split(t)],n=vg?e.pathExt||process.env.PATHEXT||".EXE;.CMD;.BAT;.COM":"",s=vg?n.split(t):[""];return vg&&r.indexOf(".")!==-1&&s[0]!==""&&s.unshift(""),{pathEnv:i,pathExt:s,pathExtExe:n}},TK=(r,e,t)=>{typeof e=="function"&&(t=e,e={}),e||(e={});let{pathEnv:i,pathExt:n,pathExtExe:s}=LK(r,e),o=[],a=c=>new Promise((u,g)=>{if(c===i.length)return e.all&&o.length?u(o):g(NK(r));let f=i[c],h=/^".*"$/.test(f)?f.slice(1,-1):f,p=RK.join(h,r),C=!h&&/^\.[\\\/]/.test(r)?r.slice(0,2)+p:p;u(l(C,c,0))}),l=(c,u,g)=>new Promise((f,h)=>{if(g===n.length)return f(a(u+1));let p=n[g];FK(c+p,{pathExt:s},(C,y)=>{if(!C&&y)if(e.all)o.push(c+p);else return f(c+p);return f(l(c,u,g+1))})});return t?a(0).then(c=>t(null,c),t):a(0)},rfe=(r,e)=>{e=e||{};let{pathEnv:t,pathExt:i,pathExtExe:n}=LK(r,e),s=[];for(let o=0;o{"use strict";var KK=(r={})=>{let e=r.env||process.env;return(r.platform||process.platform)!=="win32"?"PATH":Object.keys(e).reverse().find(i=>i.toUpperCase()==="PATH")||"Path"};vS.exports=KK;vS.exports.default=KK});var jK=w((zXe,YK)=>{"use strict";var HK=J("path"),ife=MK(),nfe=UK();function GK(r,e){let t=r.options.env||process.env,i=process.cwd(),n=r.options.cwd!=null,s=n&&process.chdir!==void 0&&!process.chdir.disabled;if(s)try{process.chdir(r.options.cwd)}catch{}let o;try{o=ife.sync(r.command,{path:t[nfe({env:t})],pathExt:e?HK.delimiter:void 0})}catch{}finally{s&&process.chdir(i)}return o&&(o=HK.resolve(n?r.options.cwd:"",o)),o}function sfe(r){return GK(r)||GK(r,!0)}YK.exports=sfe});var qK=w((VXe,PS)=>{"use strict";var xS=/([()\][%!^"`<>&|;, *?])/g;function ofe(r){return r=r.replace(xS,"^$1"),r}function afe(r,e){return r=`${r}`,r=r.replace(/(\\*)"/g,'$1$1\\"'),r=r.replace(/(\\*)$/,"$1$1"),r=`"${r}"`,r=r.replace(xS,"^$1"),e&&(r=r.replace(xS,"^$1")),r}PS.exports.command=ofe;PS.exports.argument=afe});var WK=w((XXe,JK)=>{"use strict";JK.exports=/^#!(.*)/});var VK=w((ZXe,zK)=>{"use strict";var Afe=WK();zK.exports=(r="")=>{let e=r.match(Afe);if(!e)return null;let[t,i]=e[0].replace(/#! ?/,"").split(" "),n=t.split("/").pop();return n==="env"?i:i?`${n} ${i}`:n}});var ZK=w((_Xe,XK)=>{"use strict";var DS=J("fs"),lfe=VK();function cfe(r){let t=Buffer.alloc(150),i;try{i=DS.openSync(r,"r"),DS.readSync(i,t,0,150,0),DS.closeSync(i)}catch{}return lfe(t.toString())}XK.exports=cfe});var tU=w(($Xe,eU)=>{"use strict";var ufe=J("path"),_K=jK(),$K=qK(),gfe=ZK(),ffe=process.platform==="win32",hfe=/\.(?:com|exe)$/i,pfe=/node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;function dfe(r){r.file=_K(r);let e=r.file&&gfe(r.file);return e?(r.args.unshift(r.file),r.command=e,_K(r)):r.file}function Cfe(r){if(!ffe)return r;let e=dfe(r),t=!hfe.test(e);if(r.options.forceShell||t){let i=pfe.test(e);r.command=ufe.normalize(r.command),r.command=$K.command(r.command),r.args=r.args.map(s=>$K.argument(s,i));let n=[r.command].concat(r.args).join(" ");r.args=["/d","/s","/c",`"${n}"`],r.command=process.env.comspec||"cmd.exe",r.options.windowsVerbatimArguments=!0}return r}function mfe(r,e,t){e&&!Array.isArray(e)&&(t=e,e=null),e=e?e.slice(0):[],t=Object.assign({},t);let i={command:r,args:e,options:t,file:void 0,original:{command:r,args:e}};return t.shell?i:Cfe(i)}eU.exports=mfe});var nU=w((eZe,iU)=>{"use strict";var kS=process.platform==="win32";function RS(r,e){return Object.assign(new Error(`${e} ${r.command} ENOENT`),{code:"ENOENT",errno:"ENOENT",syscall:`${e} ${r.command}`,path:r.command,spawnargs:r.args})}function Efe(r,e){if(!kS)return;let t=r.emit;r.emit=function(i,n){if(i==="exit"){let s=rU(n,e,"spawn");if(s)return t.call(r,"error",s)}return t.apply(r,arguments)}}function rU(r,e){return kS&&r===1&&!e.file?RS(e.original,"spawn"):null}function Ife(r,e){return kS&&r===1&&!e.file?RS(e.original,"spawnSync"):null}iU.exports={hookChildProcess:Efe,verifyENOENT:rU,verifyENOENTSync:Ife,notFoundError:RS}});var LS=w((tZe,xg)=>{"use strict";var sU=J("child_process"),FS=tU(),NS=nU();function oU(r,e,t){let i=FS(r,e,t),n=sU.spawn(i.command,i.args,i.options);return NS.hookChildProcess(n,i),n}function yfe(r,e,t){let i=FS(r,e,t),n=sU.spawnSync(i.command,i.args,i.options);return n.error=n.error||NS.verifyENOENTSync(n.status,i),n}xg.exports=oU;xg.exports.spawn=oU;xg.exports.sync=yfe;xg.exports._parse=FS;xg.exports._enoent=NS});var AU=w((rZe,aU)=>{"use strict";function wfe(r,e){function t(){this.constructor=r}t.prototype=e.prototype,r.prototype=new t}function Wl(r,e,t,i){this.message=r,this.expected=e,this.found=t,this.location=i,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,Wl)}wfe(Wl,Error);Wl.buildMessage=function(r,e){var t={literal:function(c){return'"'+n(c.text)+'"'},class:function(c){var u="",g;for(g=0;g0){for(g=1,f=1;g>",ie=me(">>",!1),de=">&",_e=me(">&",!1),Pt=">",It=me(">",!1),Or="<<<",ii=me("<<<",!1),gi="<&",hr=me("<&",!1),fi="<",ni=me("<",!1),Os=function(m){return{type:"argument",segments:[].concat(...m)}},pr=function(m){return m},Ii="$'",es=me("$'",!1),ua="'",pA=me("'",!1),ag=function(m){return[{type:"text",text:m}]},ts='""',dA=me('""',!1),ga=function(){return{type:"text",text:""}},yp='"',CA=me('"',!1),mA=function(m){return m},wr=function(m){return{type:"arithmetic",arithmetic:m,quoted:!0}},kl=function(m){return{type:"shell",shell:m,quoted:!0}},Ag=function(m){return{type:"variable",...m,quoted:!0}},Io=function(m){return{type:"text",text:m}},lg=function(m){return{type:"arithmetic",arithmetic:m,quoted:!1}},wp=function(m){return{type:"shell",shell:m,quoted:!1}},Bp=function(m){return{type:"variable",...m,quoted:!1}},vr=function(m){return{type:"glob",pattern:m}},se=/^[^']/,yo=Je(["'"],!0,!1),kn=function(m){return m.join("")},cg=/^[^$"]/,Qt=Je(["$",'"'],!0,!1),Rl=`\\ -`,Rn=me(`\\ -`,!1),rs=function(){return""},is="\\",gt=me("\\",!1),wo=/^[\\$"`]/,At=Je(["\\","$",'"',"`"],!1,!1),an=function(m){return m},S="\\a",Tt=me("\\a",!1),ug=function(){return"a"},Fl="\\b",Qp=me("\\b",!1),bp=function(){return"\b"},Sp=/^[Ee]/,vp=Je(["E","e"],!1,!1),xp=function(){return"\x1B"},G="\\f",yt=me("\\f",!1),EA=function(){return"\f"},Ji="\\n",Nl=me("\\n",!1),Xe=function(){return` -`},fa="\\r",gg=me("\\r",!1),FE=function(){return"\r"},Pp="\\t",NE=me("\\t",!1),ar=function(){return" "},Fn="\\v",Ll=me("\\v",!1),Dp=function(){return"\v"},Ms=/^[\\'"?]/,ha=Je(["\\","'",'"',"?"],!1,!1),An=function(m){return String.fromCharCode(parseInt(m,16))},Te="\\x",fg=me("\\x",!1),Tl="\\u",Ks=me("\\u",!1),Ol="\\U",IA=me("\\U",!1),hg=function(m){return String.fromCodePoint(parseInt(m,16))},pg=/^[0-7]/,pa=Je([["0","7"]],!1,!1),da=/^[0-9a-fA-f]/,rt=Je([["0","9"],["a","f"],["A","f"]],!1,!1),Bo=nt(),yA="-",Ml=me("-",!1),Us="+",Kl=me("+",!1),LE=".",kp=me(".",!1),dg=function(m,b,N){return{type:"number",value:(m==="-"?-1:1)*parseFloat(b.join("")+"."+N.join(""))}},Rp=function(m,b){return{type:"number",value:(m==="-"?-1:1)*parseInt(b.join(""))}},TE=function(m){return{type:"variable",...m}},Ul=function(m){return{type:"variable",name:m}},OE=function(m){return m},Cg="*",wA=me("*",!1),Rr="/",ME=me("/",!1),Hs=function(m,b,N){return{type:b==="*"?"multiplication":"division",right:N}},Gs=function(m,b){return b.reduce((N,U)=>({left:N,...U}),m)},mg=function(m,b,N){return{type:b==="+"?"addition":"subtraction",right:N}},BA="$((",R=me("$((",!1),q="))",Ce=me("))",!1),Ke=function(m){return m},Re="$(",ze=me("$(",!1),dt=function(m){return m},Ft="${",Nn=me("${",!1),qb=":-",S1=me(":-",!1),v1=function(m,b){return{name:m,defaultValue:b}},Jb=":-}",x1=me(":-}",!1),P1=function(m){return{name:m,defaultValue:[]}},Wb=":+",D1=me(":+",!1),k1=function(m,b){return{name:m,alternativeValue:b}},zb=":+}",R1=me(":+}",!1),F1=function(m){return{name:m,alternativeValue:[]}},Vb=function(m){return{name:m}},N1="$",L1=me("$",!1),T1=function(m){return e.isGlobPattern(m)},O1=function(m){return m},Xb=/^[a-zA-Z0-9_]/,Zb=Je([["a","z"],["A","Z"],["0","9"],"_"],!1,!1),_b=function(){return T()},$b=/^[$@*?#a-zA-Z0-9_\-]/,eS=Je(["$","@","*","?","#",["a","z"],["A","Z"],["0","9"],"_","-"],!1,!1),M1=/^[(){}<>$|&; \t"']/,Eg=Je(["(",")","{","}","<",">","$","|","&",";"," "," ",'"',"'"],!1,!1),tS=/^[<>&; \t"']/,rS=Je(["<",">","&",";"," "," ",'"',"'"],!1,!1),KE=/^[ \t]/,UE=Je([" "," "],!1,!1),Q=0,Me=0,QA=[{line:1,column:1}],d=0,E=[],I=0,k;if("startRule"in e){if(!(e.startRule in i))throw new Error(`Can't start parsing from rule "`+e.startRule+'".');n=i[e.startRule]}function T(){return r.substring(Me,Q)}function Z(){return Et(Me,Q)}function te(m,b){throw b=b!==void 0?b:Et(Me,Q),Ri([lt(m)],r.substring(Me,Q),b)}function Be(m,b){throw b=b!==void 0?b:Et(Me,Q),Ln(m,b)}function me(m,b){return{type:"literal",text:m,ignoreCase:b}}function Je(m,b,N){return{type:"class",parts:m,inverted:b,ignoreCase:N}}function nt(){return{type:"any"}}function wt(){return{type:"end"}}function lt(m){return{type:"other",description:m}}function it(m){var b=QA[m],N;if(b)return b;for(N=m-1;!QA[N];)N--;for(b=QA[N],b={line:b.line,column:b.column};Nd&&(d=Q,E=[]),E.push(m))}function Ln(m,b){return new Wl(m,null,null,b)}function Ri(m,b,N){return new Wl(Wl.buildMessage(m,b),m,b,N)}function bA(){var m,b;return m=Q,b=Mr(),b===t&&(b=null),b!==t&&(Me=m,b=s(b)),m=b,m}function Mr(){var m,b,N,U,ce;if(m=Q,b=Kr(),b!==t){for(N=[],U=He();U!==t;)N.push(U),U=He();N!==t?(U=Ca(),U!==t?(ce=ns(),ce===t&&(ce=null),ce!==t?(Me=m,b=o(b,U,ce),m=b):(Q=m,m=t)):(Q=m,m=t)):(Q=m,m=t)}else Q=m,m=t;if(m===t)if(m=Q,b=Kr(),b!==t){for(N=[],U=He();U!==t;)N.push(U),U=He();N!==t?(U=Ca(),U===t&&(U=null),U!==t?(Me=m,b=a(b,U),m=b):(Q=m,m=t)):(Q=m,m=t)}else Q=m,m=t;return m}function ns(){var m,b,N,U,ce;for(m=Q,b=[],N=He();N!==t;)b.push(N),N=He();if(b!==t)if(N=Mr(),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();U!==t?(Me=m,b=l(N),m=b):(Q=m,m=t)}else Q=m,m=t;else Q=m,m=t;return m}function Ca(){var m;return r.charCodeAt(Q)===59?(m=c,Q++):(m=t,I===0&&Qe(u)),m===t&&(r.charCodeAt(Q)===38?(m=g,Q++):(m=t,I===0&&Qe(f))),m}function Kr(){var m,b,N;return m=Q,b=K1(),b!==t?(N=age(),N===t&&(N=null),N!==t?(Me=m,b=h(b,N),m=b):(Q=m,m=t)):(Q=m,m=t),m}function age(){var m,b,N,U,ce,Se,ht;for(m=Q,b=[],N=He();N!==t;)b.push(N),N=He();if(b!==t)if(N=Age(),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();if(U!==t)if(ce=Kr(),ce!==t){for(Se=[],ht=He();ht!==t;)Se.push(ht),ht=He();Se!==t?(Me=m,b=p(N,ce),m=b):(Q=m,m=t)}else Q=m,m=t;else Q=m,m=t}else Q=m,m=t;else Q=m,m=t;return m}function Age(){var m;return r.substr(Q,2)===C?(m=C,Q+=2):(m=t,I===0&&Qe(y)),m===t&&(r.substr(Q,2)===B?(m=B,Q+=2):(m=t,I===0&&Qe(v))),m}function K1(){var m,b,N;return m=Q,b=uge(),b!==t?(N=lge(),N===t&&(N=null),N!==t?(Me=m,b=D(b,N),m=b):(Q=m,m=t)):(Q=m,m=t),m}function lge(){var m,b,N,U,ce,Se,ht;for(m=Q,b=[],N=He();N!==t;)b.push(N),N=He();if(b!==t)if(N=cge(),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();if(U!==t)if(ce=K1(),ce!==t){for(Se=[],ht=He();ht!==t;)Se.push(ht),ht=He();Se!==t?(Me=m,b=L(N,ce),m=b):(Q=m,m=t)}else Q=m,m=t;else Q=m,m=t}else Q=m,m=t;else Q=m,m=t;return m}function cge(){var m;return r.substr(Q,2)===H?(m=H,Q+=2):(m=t,I===0&&Qe(j)),m===t&&(r.charCodeAt(Q)===124?(m=$,Q++):(m=t,I===0&&Qe(V))),m}function HE(){var m,b,N,U,ce,Se;if(m=Q,b=Z1(),b!==t)if(r.charCodeAt(Q)===61?(N=W,Q++):(N=t,I===0&&Qe(_)),N!==t)if(U=G1(),U!==t){for(ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();ce!==t?(Me=m,b=A(b,U),m=b):(Q=m,m=t)}else Q=m,m=t;else Q=m,m=t;else Q=m,m=t;if(m===t)if(m=Q,b=Z1(),b!==t)if(r.charCodeAt(Q)===61?(N=W,Q++):(N=t,I===0&&Qe(_)),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();U!==t?(Me=m,b=ae(b),m=b):(Q=m,m=t)}else Q=m,m=t;else Q=m,m=t;return m}function uge(){var m,b,N,U,ce,Se,ht,Bt,Jr,hi,ss;for(m=Q,b=[],N=He();N!==t;)b.push(N),N=He();if(b!==t)if(r.charCodeAt(Q)===40?(N=ge,Q++):(N=t,I===0&&Qe(re)),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();if(U!==t)if(ce=Mr(),ce!==t){for(Se=[],ht=He();ht!==t;)Se.push(ht),ht=He();if(Se!==t)if(r.charCodeAt(Q)===41?(ht=O,Q++):(ht=t,I===0&&Qe(F)),ht!==t){for(Bt=[],Jr=He();Jr!==t;)Bt.push(Jr),Jr=He();if(Bt!==t){for(Jr=[],hi=Fp();hi!==t;)Jr.push(hi),hi=Fp();if(Jr!==t){for(hi=[],ss=He();ss!==t;)hi.push(ss),ss=He();hi!==t?(Me=m,b=ue(ce,Jr),m=b):(Q=m,m=t)}else Q=m,m=t}else Q=m,m=t}else Q=m,m=t;else Q=m,m=t}else Q=m,m=t;else Q=m,m=t}else Q=m,m=t;else Q=m,m=t;if(m===t){for(m=Q,b=[],N=He();N!==t;)b.push(N),N=He();if(b!==t)if(r.charCodeAt(Q)===123?(N=he,Q++):(N=t,I===0&&Qe(ke)),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();if(U!==t)if(ce=Mr(),ce!==t){for(Se=[],ht=He();ht!==t;)Se.push(ht),ht=He();if(Se!==t)if(r.charCodeAt(Q)===125?(ht=Fe,Q++):(ht=t,I===0&&Qe(Ne)),ht!==t){for(Bt=[],Jr=He();Jr!==t;)Bt.push(Jr),Jr=He();if(Bt!==t){for(Jr=[],hi=Fp();hi!==t;)Jr.push(hi),hi=Fp();if(Jr!==t){for(hi=[],ss=He();ss!==t;)hi.push(ss),ss=He();hi!==t?(Me=m,b=oe(ce,Jr),m=b):(Q=m,m=t)}else Q=m,m=t}else Q=m,m=t}else Q=m,m=t;else Q=m,m=t}else Q=m,m=t;else Q=m,m=t}else Q=m,m=t;else Q=m,m=t;if(m===t){for(m=Q,b=[],N=He();N!==t;)b.push(N),N=He();if(b!==t){for(N=[],U=HE();U!==t;)N.push(U),U=HE();if(N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();if(U!==t){if(ce=[],Se=H1(),Se!==t)for(;Se!==t;)ce.push(Se),Se=H1();else ce=t;if(ce!==t){for(Se=[],ht=He();ht!==t;)Se.push(ht),ht=He();Se!==t?(Me=m,b=le(N,ce),m=b):(Q=m,m=t)}else Q=m,m=t}else Q=m,m=t}else Q=m,m=t}else Q=m,m=t;if(m===t){for(m=Q,b=[],N=He();N!==t;)b.push(N),N=He();if(b!==t){if(N=[],U=HE(),U!==t)for(;U!==t;)N.push(U),U=HE();else N=t;if(N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();U!==t?(Me=m,b=we(N),m=b):(Q=m,m=t)}else Q=m,m=t}else Q=m,m=t}}}return m}function U1(){var m,b,N,U,ce;for(m=Q,b=[],N=He();N!==t;)b.push(N),N=He();if(b!==t){if(N=[],U=GE(),U!==t)for(;U!==t;)N.push(U),U=GE();else N=t;if(N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();U!==t?(Me=m,b=fe(N),m=b):(Q=m,m=t)}else Q=m,m=t}else Q=m,m=t;return m}function H1(){var m,b,N;for(m=Q,b=[],N=He();N!==t;)b.push(N),N=He();if(b!==t?(N=Fp(),N!==t?(Me=m,b=Ae(N),m=b):(Q=m,m=t)):(Q=m,m=t),m===t){for(m=Q,b=[],N=He();N!==t;)b.push(N),N=He();b!==t?(N=GE(),N!==t?(Me=m,b=Ae(N),m=b):(Q=m,m=t)):(Q=m,m=t)}return m}function Fp(){var m,b,N,U,ce;for(m=Q,b=[],N=He();N!==t;)b.push(N),N=He();return b!==t?(qe.test(r.charAt(Q))?(N=r.charAt(Q),Q++):(N=t,I===0&&Qe(ne)),N===t&&(N=null),N!==t?(U=gge(),U!==t?(ce=GE(),ce!==t?(Me=m,b=Y(N,U,ce),m=b):(Q=m,m=t)):(Q=m,m=t)):(Q=m,m=t)):(Q=m,m=t),m}function gge(){var m;return r.substr(Q,2)===pe?(m=pe,Q+=2):(m=t,I===0&&Qe(ie)),m===t&&(r.substr(Q,2)===de?(m=de,Q+=2):(m=t,I===0&&Qe(_e)),m===t&&(r.charCodeAt(Q)===62?(m=Pt,Q++):(m=t,I===0&&Qe(It)),m===t&&(r.substr(Q,3)===Or?(m=Or,Q+=3):(m=t,I===0&&Qe(ii)),m===t&&(r.substr(Q,2)===gi?(m=gi,Q+=2):(m=t,I===0&&Qe(hr)),m===t&&(r.charCodeAt(Q)===60?(m=fi,Q++):(m=t,I===0&&Qe(ni))))))),m}function GE(){var m,b,N;for(m=Q,b=[],N=He();N!==t;)b.push(N),N=He();return b!==t?(N=G1(),N!==t?(Me=m,b=Ae(N),m=b):(Q=m,m=t)):(Q=m,m=t),m}function G1(){var m,b,N;if(m=Q,b=[],N=Y1(),N!==t)for(;N!==t;)b.push(N),N=Y1();else b=t;return b!==t&&(Me=m,b=Os(b)),m=b,m}function Y1(){var m,b;return m=Q,b=fge(),b!==t&&(Me=m,b=pr(b)),m=b,m===t&&(m=Q,b=hge(),b!==t&&(Me=m,b=pr(b)),m=b,m===t&&(m=Q,b=pge(),b!==t&&(Me=m,b=pr(b)),m=b,m===t&&(m=Q,b=dge(),b!==t&&(Me=m,b=pr(b)),m=b))),m}function fge(){var m,b,N,U;return m=Q,r.substr(Q,2)===Ii?(b=Ii,Q+=2):(b=t,I===0&&Qe(es)),b!==t?(N=Ege(),N!==t?(r.charCodeAt(Q)===39?(U=ua,Q++):(U=t,I===0&&Qe(pA)),U!==t?(Me=m,b=ag(N),m=b):(Q=m,m=t)):(Q=m,m=t)):(Q=m,m=t),m}function hge(){var m,b,N,U;return m=Q,r.charCodeAt(Q)===39?(b=ua,Q++):(b=t,I===0&&Qe(pA)),b!==t?(N=Cge(),N!==t?(r.charCodeAt(Q)===39?(U=ua,Q++):(U=t,I===0&&Qe(pA)),U!==t?(Me=m,b=ag(N),m=b):(Q=m,m=t)):(Q=m,m=t)):(Q=m,m=t),m}function pge(){var m,b,N,U;if(m=Q,r.substr(Q,2)===ts?(b=ts,Q+=2):(b=t,I===0&&Qe(dA)),b!==t&&(Me=m,b=ga()),m=b,m===t)if(m=Q,r.charCodeAt(Q)===34?(b=yp,Q++):(b=t,I===0&&Qe(CA)),b!==t){for(N=[],U=j1();U!==t;)N.push(U),U=j1();N!==t?(r.charCodeAt(Q)===34?(U=yp,Q++):(U=t,I===0&&Qe(CA)),U!==t?(Me=m,b=mA(N),m=b):(Q=m,m=t)):(Q=m,m=t)}else Q=m,m=t;return m}function dge(){var m,b,N;if(m=Q,b=[],N=q1(),N!==t)for(;N!==t;)b.push(N),N=q1();else b=t;return b!==t&&(Me=m,b=mA(b)),m=b,m}function j1(){var m,b;return m=Q,b=V1(),b!==t&&(Me=m,b=wr(b)),m=b,m===t&&(m=Q,b=X1(),b!==t&&(Me=m,b=kl(b)),m=b,m===t&&(m=Q,b=oS(),b!==t&&(Me=m,b=Ag(b)),m=b,m===t&&(m=Q,b=mge(),b!==t&&(Me=m,b=Io(b)),m=b))),m}function q1(){var m,b;return m=Q,b=V1(),b!==t&&(Me=m,b=lg(b)),m=b,m===t&&(m=Q,b=X1(),b!==t&&(Me=m,b=wp(b)),m=b,m===t&&(m=Q,b=oS(),b!==t&&(Me=m,b=Bp(b)),m=b,m===t&&(m=Q,b=wge(),b!==t&&(Me=m,b=vr(b)),m=b,m===t&&(m=Q,b=yge(),b!==t&&(Me=m,b=Io(b)),m=b)))),m}function Cge(){var m,b,N;for(m=Q,b=[],se.test(r.charAt(Q))?(N=r.charAt(Q),Q++):(N=t,I===0&&Qe(yo));N!==t;)b.push(N),se.test(r.charAt(Q))?(N=r.charAt(Q),Q++):(N=t,I===0&&Qe(yo));return b!==t&&(Me=m,b=kn(b)),m=b,m}function mge(){var m,b,N;if(m=Q,b=[],N=J1(),N===t&&(cg.test(r.charAt(Q))?(N=r.charAt(Q),Q++):(N=t,I===0&&Qe(Qt))),N!==t)for(;N!==t;)b.push(N),N=J1(),N===t&&(cg.test(r.charAt(Q))?(N=r.charAt(Q),Q++):(N=t,I===0&&Qe(Qt)));else b=t;return b!==t&&(Me=m,b=kn(b)),m=b,m}function J1(){var m,b,N;return m=Q,r.substr(Q,2)===Rl?(b=Rl,Q+=2):(b=t,I===0&&Qe(Rn)),b!==t&&(Me=m,b=rs()),m=b,m===t&&(m=Q,r.charCodeAt(Q)===92?(b=is,Q++):(b=t,I===0&&Qe(gt)),b!==t?(wo.test(r.charAt(Q))?(N=r.charAt(Q),Q++):(N=t,I===0&&Qe(At)),N!==t?(Me=m,b=an(N),m=b):(Q=m,m=t)):(Q=m,m=t)),m}function Ege(){var m,b,N;for(m=Q,b=[],N=W1(),N===t&&(se.test(r.charAt(Q))?(N=r.charAt(Q),Q++):(N=t,I===0&&Qe(yo)));N!==t;)b.push(N),N=W1(),N===t&&(se.test(r.charAt(Q))?(N=r.charAt(Q),Q++):(N=t,I===0&&Qe(yo)));return b!==t&&(Me=m,b=kn(b)),m=b,m}function W1(){var m,b,N;return m=Q,r.substr(Q,2)===S?(b=S,Q+=2):(b=t,I===0&&Qe(Tt)),b!==t&&(Me=m,b=ug()),m=b,m===t&&(m=Q,r.substr(Q,2)===Fl?(b=Fl,Q+=2):(b=t,I===0&&Qe(Qp)),b!==t&&(Me=m,b=bp()),m=b,m===t&&(m=Q,r.charCodeAt(Q)===92?(b=is,Q++):(b=t,I===0&&Qe(gt)),b!==t?(Sp.test(r.charAt(Q))?(N=r.charAt(Q),Q++):(N=t,I===0&&Qe(vp)),N!==t?(Me=m,b=xp(),m=b):(Q=m,m=t)):(Q=m,m=t),m===t&&(m=Q,r.substr(Q,2)===G?(b=G,Q+=2):(b=t,I===0&&Qe(yt)),b!==t&&(Me=m,b=EA()),m=b,m===t&&(m=Q,r.substr(Q,2)===Ji?(b=Ji,Q+=2):(b=t,I===0&&Qe(Nl)),b!==t&&(Me=m,b=Xe()),m=b,m===t&&(m=Q,r.substr(Q,2)===fa?(b=fa,Q+=2):(b=t,I===0&&Qe(gg)),b!==t&&(Me=m,b=FE()),m=b,m===t&&(m=Q,r.substr(Q,2)===Pp?(b=Pp,Q+=2):(b=t,I===0&&Qe(NE)),b!==t&&(Me=m,b=ar()),m=b,m===t&&(m=Q,r.substr(Q,2)===Fn?(b=Fn,Q+=2):(b=t,I===0&&Qe(Ll)),b!==t&&(Me=m,b=Dp()),m=b,m===t&&(m=Q,r.charCodeAt(Q)===92?(b=is,Q++):(b=t,I===0&&Qe(gt)),b!==t?(Ms.test(r.charAt(Q))?(N=r.charAt(Q),Q++):(N=t,I===0&&Qe(ha)),N!==t?(Me=m,b=an(N),m=b):(Q=m,m=t)):(Q=m,m=t),m===t&&(m=Ige()))))))))),m}function Ige(){var m,b,N,U,ce,Se,ht,Bt,Jr,hi,ss,aS;return m=Q,r.charCodeAt(Q)===92?(b=is,Q++):(b=t,I===0&&Qe(gt)),b!==t?(N=iS(),N!==t?(Me=m,b=An(N),m=b):(Q=m,m=t)):(Q=m,m=t),m===t&&(m=Q,r.substr(Q,2)===Te?(b=Te,Q+=2):(b=t,I===0&&Qe(fg)),b!==t?(N=Q,U=Q,ce=iS(),ce!==t?(Se=Tn(),Se!==t?(ce=[ce,Se],U=ce):(Q=U,U=t)):(Q=U,U=t),U===t&&(U=iS()),U!==t?N=r.substring(N,Q):N=U,N!==t?(Me=m,b=An(N),m=b):(Q=m,m=t)):(Q=m,m=t),m===t&&(m=Q,r.substr(Q,2)===Tl?(b=Tl,Q+=2):(b=t,I===0&&Qe(Ks)),b!==t?(N=Q,U=Q,ce=Tn(),ce!==t?(Se=Tn(),Se!==t?(ht=Tn(),ht!==t?(Bt=Tn(),Bt!==t?(ce=[ce,Se,ht,Bt],U=ce):(Q=U,U=t)):(Q=U,U=t)):(Q=U,U=t)):(Q=U,U=t),U!==t?N=r.substring(N,Q):N=U,N!==t?(Me=m,b=An(N),m=b):(Q=m,m=t)):(Q=m,m=t),m===t&&(m=Q,r.substr(Q,2)===Ol?(b=Ol,Q+=2):(b=t,I===0&&Qe(IA)),b!==t?(N=Q,U=Q,ce=Tn(),ce!==t?(Se=Tn(),Se!==t?(ht=Tn(),ht!==t?(Bt=Tn(),Bt!==t?(Jr=Tn(),Jr!==t?(hi=Tn(),hi!==t?(ss=Tn(),ss!==t?(aS=Tn(),aS!==t?(ce=[ce,Se,ht,Bt,Jr,hi,ss,aS],U=ce):(Q=U,U=t)):(Q=U,U=t)):(Q=U,U=t)):(Q=U,U=t)):(Q=U,U=t)):(Q=U,U=t)):(Q=U,U=t)):(Q=U,U=t),U!==t?N=r.substring(N,Q):N=U,N!==t?(Me=m,b=hg(N),m=b):(Q=m,m=t)):(Q=m,m=t)))),m}function iS(){var m;return pg.test(r.charAt(Q))?(m=r.charAt(Q),Q++):(m=t,I===0&&Qe(pa)),m}function Tn(){var m;return da.test(r.charAt(Q))?(m=r.charAt(Q),Q++):(m=t,I===0&&Qe(rt)),m}function yge(){var m,b,N,U,ce;if(m=Q,b=[],N=Q,r.charCodeAt(Q)===92?(U=is,Q++):(U=t,I===0&&Qe(gt)),U!==t?(r.length>Q?(ce=r.charAt(Q),Q++):(ce=t,I===0&&Qe(Bo)),ce!==t?(Me=N,U=an(ce),N=U):(Q=N,N=t)):(Q=N,N=t),N===t&&(N=Q,U=Q,I++,ce=_1(),I--,ce===t?U=void 0:(Q=U,U=t),U!==t?(r.length>Q?(ce=r.charAt(Q),Q++):(ce=t,I===0&&Qe(Bo)),ce!==t?(Me=N,U=an(ce),N=U):(Q=N,N=t)):(Q=N,N=t)),N!==t)for(;N!==t;)b.push(N),N=Q,r.charCodeAt(Q)===92?(U=is,Q++):(U=t,I===0&&Qe(gt)),U!==t?(r.length>Q?(ce=r.charAt(Q),Q++):(ce=t,I===0&&Qe(Bo)),ce!==t?(Me=N,U=an(ce),N=U):(Q=N,N=t)):(Q=N,N=t),N===t&&(N=Q,U=Q,I++,ce=_1(),I--,ce===t?U=void 0:(Q=U,U=t),U!==t?(r.length>Q?(ce=r.charAt(Q),Q++):(ce=t,I===0&&Qe(Bo)),ce!==t?(Me=N,U=an(ce),N=U):(Q=N,N=t)):(Q=N,N=t));else b=t;return b!==t&&(Me=m,b=kn(b)),m=b,m}function nS(){var m,b,N,U,ce,Se;if(m=Q,r.charCodeAt(Q)===45?(b=yA,Q++):(b=t,I===0&&Qe(Ml)),b===t&&(r.charCodeAt(Q)===43?(b=Us,Q++):(b=t,I===0&&Qe(Kl))),b===t&&(b=null),b!==t){if(N=[],qe.test(r.charAt(Q))?(U=r.charAt(Q),Q++):(U=t,I===0&&Qe(ne)),U!==t)for(;U!==t;)N.push(U),qe.test(r.charAt(Q))?(U=r.charAt(Q),Q++):(U=t,I===0&&Qe(ne));else N=t;if(N!==t)if(r.charCodeAt(Q)===46?(U=LE,Q++):(U=t,I===0&&Qe(kp)),U!==t){if(ce=[],qe.test(r.charAt(Q))?(Se=r.charAt(Q),Q++):(Se=t,I===0&&Qe(ne)),Se!==t)for(;Se!==t;)ce.push(Se),qe.test(r.charAt(Q))?(Se=r.charAt(Q),Q++):(Se=t,I===0&&Qe(ne));else ce=t;ce!==t?(Me=m,b=dg(b,N,ce),m=b):(Q=m,m=t)}else Q=m,m=t;else Q=m,m=t}else Q=m,m=t;if(m===t){if(m=Q,r.charCodeAt(Q)===45?(b=yA,Q++):(b=t,I===0&&Qe(Ml)),b===t&&(r.charCodeAt(Q)===43?(b=Us,Q++):(b=t,I===0&&Qe(Kl))),b===t&&(b=null),b!==t){if(N=[],qe.test(r.charAt(Q))?(U=r.charAt(Q),Q++):(U=t,I===0&&Qe(ne)),U!==t)for(;U!==t;)N.push(U),qe.test(r.charAt(Q))?(U=r.charAt(Q),Q++):(U=t,I===0&&Qe(ne));else N=t;N!==t?(Me=m,b=Rp(b,N),m=b):(Q=m,m=t)}else Q=m,m=t;if(m===t&&(m=Q,b=oS(),b!==t&&(Me=m,b=TE(b)),m=b,m===t&&(m=Q,b=Hl(),b!==t&&(Me=m,b=Ul(b)),m=b,m===t)))if(m=Q,r.charCodeAt(Q)===40?(b=ge,Q++):(b=t,I===0&&Qe(re)),b!==t){for(N=[],U=He();U!==t;)N.push(U),U=He();if(N!==t)if(U=z1(),U!==t){for(ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();ce!==t?(r.charCodeAt(Q)===41?(Se=O,Q++):(Se=t,I===0&&Qe(F)),Se!==t?(Me=m,b=OE(U),m=b):(Q=m,m=t)):(Q=m,m=t)}else Q=m,m=t;else Q=m,m=t}else Q=m,m=t}return m}function sS(){var m,b,N,U,ce,Se,ht,Bt;if(m=Q,b=nS(),b!==t){for(N=[],U=Q,ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();if(ce!==t)if(r.charCodeAt(Q)===42?(Se=Cg,Q++):(Se=t,I===0&&Qe(wA)),Se===t&&(r.charCodeAt(Q)===47?(Se=Rr,Q++):(Se=t,I===0&&Qe(ME))),Se!==t){for(ht=[],Bt=He();Bt!==t;)ht.push(Bt),Bt=He();ht!==t?(Bt=nS(),Bt!==t?(Me=U,ce=Hs(b,Se,Bt),U=ce):(Q=U,U=t)):(Q=U,U=t)}else Q=U,U=t;else Q=U,U=t;for(;U!==t;){for(N.push(U),U=Q,ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();if(ce!==t)if(r.charCodeAt(Q)===42?(Se=Cg,Q++):(Se=t,I===0&&Qe(wA)),Se===t&&(r.charCodeAt(Q)===47?(Se=Rr,Q++):(Se=t,I===0&&Qe(ME))),Se!==t){for(ht=[],Bt=He();Bt!==t;)ht.push(Bt),Bt=He();ht!==t?(Bt=nS(),Bt!==t?(Me=U,ce=Hs(b,Se,Bt),U=ce):(Q=U,U=t)):(Q=U,U=t)}else Q=U,U=t;else Q=U,U=t}N!==t?(Me=m,b=Gs(b,N),m=b):(Q=m,m=t)}else Q=m,m=t;return m}function z1(){var m,b,N,U,ce,Se,ht,Bt;if(m=Q,b=sS(),b!==t){for(N=[],U=Q,ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();if(ce!==t)if(r.charCodeAt(Q)===43?(Se=Us,Q++):(Se=t,I===0&&Qe(Kl)),Se===t&&(r.charCodeAt(Q)===45?(Se=yA,Q++):(Se=t,I===0&&Qe(Ml))),Se!==t){for(ht=[],Bt=He();Bt!==t;)ht.push(Bt),Bt=He();ht!==t?(Bt=sS(),Bt!==t?(Me=U,ce=mg(b,Se,Bt),U=ce):(Q=U,U=t)):(Q=U,U=t)}else Q=U,U=t;else Q=U,U=t;for(;U!==t;){for(N.push(U),U=Q,ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();if(ce!==t)if(r.charCodeAt(Q)===43?(Se=Us,Q++):(Se=t,I===0&&Qe(Kl)),Se===t&&(r.charCodeAt(Q)===45?(Se=yA,Q++):(Se=t,I===0&&Qe(Ml))),Se!==t){for(ht=[],Bt=He();Bt!==t;)ht.push(Bt),Bt=He();ht!==t?(Bt=sS(),Bt!==t?(Me=U,ce=mg(b,Se,Bt),U=ce):(Q=U,U=t)):(Q=U,U=t)}else Q=U,U=t;else Q=U,U=t}N!==t?(Me=m,b=Gs(b,N),m=b):(Q=m,m=t)}else Q=m,m=t;return m}function V1(){var m,b,N,U,ce,Se;if(m=Q,r.substr(Q,3)===BA?(b=BA,Q+=3):(b=t,I===0&&Qe(R)),b!==t){for(N=[],U=He();U!==t;)N.push(U),U=He();if(N!==t)if(U=z1(),U!==t){for(ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();ce!==t?(r.substr(Q,2)===q?(Se=q,Q+=2):(Se=t,I===0&&Qe(Ce)),Se!==t?(Me=m,b=Ke(U),m=b):(Q=m,m=t)):(Q=m,m=t)}else Q=m,m=t;else Q=m,m=t}else Q=m,m=t;return m}function X1(){var m,b,N,U;return m=Q,r.substr(Q,2)===Re?(b=Re,Q+=2):(b=t,I===0&&Qe(ze)),b!==t?(N=Mr(),N!==t?(r.charCodeAt(Q)===41?(U=O,Q++):(U=t,I===0&&Qe(F)),U!==t?(Me=m,b=dt(N),m=b):(Q=m,m=t)):(Q=m,m=t)):(Q=m,m=t),m}function oS(){var m,b,N,U,ce,Se;return m=Q,r.substr(Q,2)===Ft?(b=Ft,Q+=2):(b=t,I===0&&Qe(Nn)),b!==t?(N=Hl(),N!==t?(r.substr(Q,2)===qb?(U=qb,Q+=2):(U=t,I===0&&Qe(S1)),U!==t?(ce=U1(),ce!==t?(r.charCodeAt(Q)===125?(Se=Fe,Q++):(Se=t,I===0&&Qe(Ne)),Se!==t?(Me=m,b=v1(N,ce),m=b):(Q=m,m=t)):(Q=m,m=t)):(Q=m,m=t)):(Q=m,m=t)):(Q=m,m=t),m===t&&(m=Q,r.substr(Q,2)===Ft?(b=Ft,Q+=2):(b=t,I===0&&Qe(Nn)),b!==t?(N=Hl(),N!==t?(r.substr(Q,3)===Jb?(U=Jb,Q+=3):(U=t,I===0&&Qe(x1)),U!==t?(Me=m,b=P1(N),m=b):(Q=m,m=t)):(Q=m,m=t)):(Q=m,m=t),m===t&&(m=Q,r.substr(Q,2)===Ft?(b=Ft,Q+=2):(b=t,I===0&&Qe(Nn)),b!==t?(N=Hl(),N!==t?(r.substr(Q,2)===Wb?(U=Wb,Q+=2):(U=t,I===0&&Qe(D1)),U!==t?(ce=U1(),ce!==t?(r.charCodeAt(Q)===125?(Se=Fe,Q++):(Se=t,I===0&&Qe(Ne)),Se!==t?(Me=m,b=k1(N,ce),m=b):(Q=m,m=t)):(Q=m,m=t)):(Q=m,m=t)):(Q=m,m=t)):(Q=m,m=t),m===t&&(m=Q,r.substr(Q,2)===Ft?(b=Ft,Q+=2):(b=t,I===0&&Qe(Nn)),b!==t?(N=Hl(),N!==t?(r.substr(Q,3)===zb?(U=zb,Q+=3):(U=t,I===0&&Qe(R1)),U!==t?(Me=m,b=F1(N),m=b):(Q=m,m=t)):(Q=m,m=t)):(Q=m,m=t),m===t&&(m=Q,r.substr(Q,2)===Ft?(b=Ft,Q+=2):(b=t,I===0&&Qe(Nn)),b!==t?(N=Hl(),N!==t?(r.charCodeAt(Q)===125?(U=Fe,Q++):(U=t,I===0&&Qe(Ne)),U!==t?(Me=m,b=Vb(N),m=b):(Q=m,m=t)):(Q=m,m=t)):(Q=m,m=t),m===t&&(m=Q,r.charCodeAt(Q)===36?(b=N1,Q++):(b=t,I===0&&Qe(L1)),b!==t?(N=Hl(),N!==t?(Me=m,b=Vb(N),m=b):(Q=m,m=t)):(Q=m,m=t)))))),m}function wge(){var m,b,N;return m=Q,b=Bge(),b!==t?(Me=Q,N=T1(b),N?N=void 0:N=t,N!==t?(Me=m,b=O1(b),m=b):(Q=m,m=t)):(Q=m,m=t),m}function Bge(){var m,b,N,U,ce;if(m=Q,b=[],N=Q,U=Q,I++,ce=$1(),I--,ce===t?U=void 0:(Q=U,U=t),U!==t?(r.length>Q?(ce=r.charAt(Q),Q++):(ce=t,I===0&&Qe(Bo)),ce!==t?(Me=N,U=an(ce),N=U):(Q=N,N=t)):(Q=N,N=t),N!==t)for(;N!==t;)b.push(N),N=Q,U=Q,I++,ce=$1(),I--,ce===t?U=void 0:(Q=U,U=t),U!==t?(r.length>Q?(ce=r.charAt(Q),Q++):(ce=t,I===0&&Qe(Bo)),ce!==t?(Me=N,U=an(ce),N=U):(Q=N,N=t)):(Q=N,N=t);else b=t;return b!==t&&(Me=m,b=kn(b)),m=b,m}function Z1(){var m,b,N;if(m=Q,b=[],Xb.test(r.charAt(Q))?(N=r.charAt(Q),Q++):(N=t,I===0&&Qe(Zb)),N!==t)for(;N!==t;)b.push(N),Xb.test(r.charAt(Q))?(N=r.charAt(Q),Q++):(N=t,I===0&&Qe(Zb));else b=t;return b!==t&&(Me=m,b=_b()),m=b,m}function Hl(){var m,b,N;if(m=Q,b=[],$b.test(r.charAt(Q))?(N=r.charAt(Q),Q++):(N=t,I===0&&Qe(eS)),N!==t)for(;N!==t;)b.push(N),$b.test(r.charAt(Q))?(N=r.charAt(Q),Q++):(N=t,I===0&&Qe(eS));else b=t;return b!==t&&(Me=m,b=_b()),m=b,m}function _1(){var m;return M1.test(r.charAt(Q))?(m=r.charAt(Q),Q++):(m=t,I===0&&Qe(Eg)),m}function $1(){var m;return tS.test(r.charAt(Q))?(m=r.charAt(Q),Q++):(m=t,I===0&&Qe(rS)),m}function He(){var m,b;if(m=[],KE.test(r.charAt(Q))?(b=r.charAt(Q),Q++):(b=t,I===0&&Qe(UE)),b!==t)for(;b!==t;)m.push(b),KE.test(r.charAt(Q))?(b=r.charAt(Q),Q++):(b=t,I===0&&Qe(UE));else m=t;return m}if(k=n(),k!==t&&Q===r.length)return k;throw k!==t&&Q{"use strict";function Qfe(r,e){function t(){this.constructor=r}t.prototype=e.prototype,r.prototype=new t}function Vl(r,e,t,i){this.message=r,this.expected=e,this.found=t,this.location=i,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,Vl)}Qfe(Vl,Error);Vl.buildMessage=function(r,e){var t={literal:function(c){return'"'+n(c.text)+'"'},class:function(c){var u="",g;for(g=0;g0){for(g=1,f=1;gH&&(H=v,j=[]),j.push(ne))}function Ne(ne,Y){return new Vl(ne,null,null,Y)}function oe(ne,Y,pe){return new Vl(Vl.buildMessage(ne,Y),ne,Y,pe)}function le(){var ne,Y,pe,ie;return ne=v,Y=we(),Y!==t?(r.charCodeAt(v)===47?(pe=s,v++):(pe=t,$===0&&Fe(o)),pe!==t?(ie=we(),ie!==t?(D=ne,Y=a(Y,ie),ne=Y):(v=ne,ne=t)):(v=ne,ne=t)):(v=ne,ne=t),ne===t&&(ne=v,Y=we(),Y!==t&&(D=ne,Y=l(Y)),ne=Y),ne}function we(){var ne,Y,pe,ie;return ne=v,Y=fe(),Y!==t?(r.charCodeAt(v)===64?(pe=c,v++):(pe=t,$===0&&Fe(u)),pe!==t?(ie=qe(),ie!==t?(D=ne,Y=g(Y,ie),ne=Y):(v=ne,ne=t)):(v=ne,ne=t)):(v=ne,ne=t),ne===t&&(ne=v,Y=fe(),Y!==t&&(D=ne,Y=f(Y)),ne=Y),ne}function fe(){var ne,Y,pe,ie,de;return ne=v,r.charCodeAt(v)===64?(Y=c,v++):(Y=t,$===0&&Fe(u)),Y!==t?(pe=Ae(),pe!==t?(r.charCodeAt(v)===47?(ie=s,v++):(ie=t,$===0&&Fe(o)),ie!==t?(de=Ae(),de!==t?(D=ne,Y=h(),ne=Y):(v=ne,ne=t)):(v=ne,ne=t)):(v=ne,ne=t)):(v=ne,ne=t),ne===t&&(ne=v,Y=Ae(),Y!==t&&(D=ne,Y=h()),ne=Y),ne}function Ae(){var ne,Y,pe;if(ne=v,Y=[],p.test(r.charAt(v))?(pe=r.charAt(v),v++):(pe=t,$===0&&Fe(C)),pe!==t)for(;pe!==t;)Y.push(pe),p.test(r.charAt(v))?(pe=r.charAt(v),v++):(pe=t,$===0&&Fe(C));else Y=t;return Y!==t&&(D=ne,Y=h()),ne=Y,ne}function qe(){var ne,Y,pe;if(ne=v,Y=[],y.test(r.charAt(v))?(pe=r.charAt(v),v++):(pe=t,$===0&&Fe(B)),pe!==t)for(;pe!==t;)Y.push(pe),y.test(r.charAt(v))?(pe=r.charAt(v),v++):(pe=t,$===0&&Fe(B));else Y=t;return Y!==t&&(D=ne,Y=h()),ne=Y,ne}if(V=n(),V!==t&&v===r.length)return V;throw V!==t&&v{"use strict";function fU(r){return typeof r>"u"||r===null}function Sfe(r){return typeof r=="object"&&r!==null}function vfe(r){return Array.isArray(r)?r:fU(r)?[]:[r]}function xfe(r,e){var t,i,n,s;if(e)for(s=Object.keys(e),t=0,i=s.length;t{"use strict";function Wp(r,e){Error.call(this),this.name="YAMLException",this.reason=r,this.mark=e,this.message=(this.reason||"(unknown reason)")+(this.mark?" "+this.mark.toString():""),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack||""}Wp.prototype=Object.create(Error.prototype);Wp.prototype.constructor=Wp;Wp.prototype.toString=function(e){var t=this.name+": ";return t+=this.reason||"(unknown reason)",!e&&this.mark&&(t+=" "+this.mark.toString()),t};hU.exports=Wp});var CU=w((IZe,dU)=>{"use strict";var pU=Zl();function HS(r,e,t,i,n){this.name=r,this.buffer=e,this.position=t,this.line=i,this.column=n}HS.prototype.getSnippet=function(e,t){var i,n,s,o,a;if(!this.buffer)return null;for(e=e||4,t=t||75,i="",n=this.position;n>0&&`\0\r -\x85\u2028\u2029`.indexOf(this.buffer.charAt(n-1))===-1;)if(n-=1,this.position-n>t/2-1){i=" ... ",n+=5;break}for(s="",o=this.position;ot/2-1){s=" ... ",o-=5;break}return a=this.buffer.slice(n,o),pU.repeat(" ",e)+i+a+s+` -`+pU.repeat(" ",e+this.position-n+i.length)+"^"};HS.prototype.toString=function(e){var t,i="";return this.name&&(i+='in "'+this.name+'" '),i+="at line "+(this.line+1)+", column "+(this.column+1),e||(t=this.getSnippet(),t&&(i+=`: -`+t)),i};dU.exports=HS});var si=w((yZe,EU)=>{"use strict";var mU=kg(),kfe=["kind","resolve","construct","instanceOf","predicate","represent","defaultStyle","styleAliases"],Rfe=["scalar","sequence","mapping"];function Ffe(r){var e={};return r!==null&&Object.keys(r).forEach(function(t){r[t].forEach(function(i){e[String(i)]=t})}),e}function Nfe(r,e){if(e=e||{},Object.keys(e).forEach(function(t){if(kfe.indexOf(t)===-1)throw new mU('Unknown option "'+t+'" is met in definition of "'+r+'" YAML type.')}),this.tag=r,this.kind=e.kind||null,this.resolve=e.resolve||function(){return!0},this.construct=e.construct||function(t){return t},this.instanceOf=e.instanceOf||null,this.predicate=e.predicate||null,this.represent=e.represent||null,this.defaultStyle=e.defaultStyle||null,this.styleAliases=Ffe(e.styleAliases||null),Rfe.indexOf(this.kind)===-1)throw new mU('Unknown kind "'+this.kind+'" is specified for "'+r+'" YAML type.')}EU.exports=Nfe});var _l=w((wZe,yU)=>{"use strict";var IU=Zl(),gI=kg(),Lfe=si();function GS(r,e,t){var i=[];return r.include.forEach(function(n){t=GS(n,e,t)}),r[e].forEach(function(n){t.forEach(function(s,o){s.tag===n.tag&&s.kind===n.kind&&i.push(o)}),t.push(n)}),t.filter(function(n,s){return i.indexOf(s)===-1})}function Tfe(){var r={scalar:{},sequence:{},mapping:{},fallback:{}},e,t;function i(n){r[n.kind][n.tag]=r.fallback[n.tag]=n}for(e=0,t=arguments.length;e{"use strict";var Ofe=si();wU.exports=new Ofe("tag:yaml.org,2002:str",{kind:"scalar",construct:function(r){return r!==null?r:""}})});var bU=w((QZe,QU)=>{"use strict";var Mfe=si();QU.exports=new Mfe("tag:yaml.org,2002:seq",{kind:"sequence",construct:function(r){return r!==null?r:[]}})});var vU=w((bZe,SU)=>{"use strict";var Kfe=si();SU.exports=new Kfe("tag:yaml.org,2002:map",{kind:"mapping",construct:function(r){return r!==null?r:{}}})});var fI=w((SZe,xU)=>{"use strict";var Ufe=_l();xU.exports=new Ufe({explicit:[BU(),bU(),vU()]})});var DU=w((vZe,PU)=>{"use strict";var Hfe=si();function Gfe(r){if(r===null)return!0;var e=r.length;return e===1&&r==="~"||e===4&&(r==="null"||r==="Null"||r==="NULL")}function Yfe(){return null}function jfe(r){return r===null}PU.exports=new Hfe("tag:yaml.org,2002:null",{kind:"scalar",resolve:Gfe,construct:Yfe,predicate:jfe,represent:{canonical:function(){return"~"},lowercase:function(){return"null"},uppercase:function(){return"NULL"},camelcase:function(){return"Null"}},defaultStyle:"lowercase"})});var RU=w((xZe,kU)=>{"use strict";var qfe=si();function Jfe(r){if(r===null)return!1;var e=r.length;return e===4&&(r==="true"||r==="True"||r==="TRUE")||e===5&&(r==="false"||r==="False"||r==="FALSE")}function Wfe(r){return r==="true"||r==="True"||r==="TRUE"}function zfe(r){return Object.prototype.toString.call(r)==="[object Boolean]"}kU.exports=new qfe("tag:yaml.org,2002:bool",{kind:"scalar",resolve:Jfe,construct:Wfe,predicate:zfe,represent:{lowercase:function(r){return r?"true":"false"},uppercase:function(r){return r?"TRUE":"FALSE"},camelcase:function(r){return r?"True":"False"}},defaultStyle:"lowercase"})});var NU=w((PZe,FU)=>{"use strict";var Vfe=Zl(),Xfe=si();function Zfe(r){return 48<=r&&r<=57||65<=r&&r<=70||97<=r&&r<=102}function _fe(r){return 48<=r&&r<=55}function $fe(r){return 48<=r&&r<=57}function ehe(r){if(r===null)return!1;var e=r.length,t=0,i=!1,n;if(!e)return!1;if(n=r[t],(n==="-"||n==="+")&&(n=r[++t]),n==="0"){if(t+1===e)return!0;if(n=r[++t],n==="b"){for(t++;t=0?"0b"+r.toString(2):"-0b"+r.toString(2).slice(1)},octal:function(r){return r>=0?"0"+r.toString(8):"-0"+r.toString(8).slice(1)},decimal:function(r){return r.toString(10)},hexadecimal:function(r){return r>=0?"0x"+r.toString(16).toUpperCase():"-0x"+r.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}})});var OU=w((DZe,TU)=>{"use strict";var LU=Zl(),ihe=si(),nhe=new RegExp("^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");function she(r){return!(r===null||!nhe.test(r)||r[r.length-1]==="_")}function ohe(r){var e,t,i,n;return e=r.replace(/_/g,"").toLowerCase(),t=e[0]==="-"?-1:1,n=[],"+-".indexOf(e[0])>=0&&(e=e.slice(1)),e===".inf"?t===1?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:e===".nan"?NaN:e.indexOf(":")>=0?(e.split(":").forEach(function(s){n.unshift(parseFloat(s,10))}),e=0,i=1,n.forEach(function(s){e+=s*i,i*=60}),t*e):t*parseFloat(e,10)}var ahe=/^[-+]?[0-9]+e/;function Ahe(r,e){var t;if(isNaN(r))switch(e){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===r)switch(e){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===r)switch(e){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(LU.isNegativeZero(r))return"-0.0";return t=r.toString(10),ahe.test(t)?t.replace("e",".e"):t}function lhe(r){return Object.prototype.toString.call(r)==="[object Number]"&&(r%1!==0||LU.isNegativeZero(r))}TU.exports=new ihe("tag:yaml.org,2002:float",{kind:"scalar",resolve:she,construct:ohe,predicate:lhe,represent:Ahe,defaultStyle:"lowercase"})});var YS=w((kZe,MU)=>{"use strict";var che=_l();MU.exports=new che({include:[fI()],implicit:[DU(),RU(),NU(),OU()]})});var jS=w((RZe,KU)=>{"use strict";var uhe=_l();KU.exports=new uhe({include:[YS()]})});var YU=w((FZe,GU)=>{"use strict";var ghe=si(),UU=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),HU=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");function fhe(r){return r===null?!1:UU.exec(r)!==null||HU.exec(r)!==null}function hhe(r){var e,t,i,n,s,o,a,l=0,c=null,u,g,f;if(e=UU.exec(r),e===null&&(e=HU.exec(r)),e===null)throw new Error("Date resolve error");if(t=+e[1],i=+e[2]-1,n=+e[3],!e[4])return new Date(Date.UTC(t,i,n));if(s=+e[4],o=+e[5],a=+e[6],e[7]){for(l=e[7].slice(0,3);l.length<3;)l+="0";l=+l}return e[9]&&(u=+e[10],g=+(e[11]||0),c=(u*60+g)*6e4,e[9]==="-"&&(c=-c)),f=new Date(Date.UTC(t,i,n,s,o,a,l)),c&&f.setTime(f.getTime()-c),f}function phe(r){return r.toISOString()}GU.exports=new ghe("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:fhe,construct:hhe,instanceOf:Date,represent:phe})});var qU=w((NZe,jU)=>{"use strict";var dhe=si();function Che(r){return r==="<<"||r===null}jU.exports=new dhe("tag:yaml.org,2002:merge",{kind:"scalar",resolve:Che})});var zU=w((LZe,WU)=>{"use strict";var $l;try{JU=J,$l=JU("buffer").Buffer}catch{}var JU,mhe=si(),qS=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/= -\r`;function Ehe(r){if(r===null)return!1;var e,t,i=0,n=r.length,s=qS;for(t=0;t64)){if(e<0)return!1;i+=6}return i%8===0}function Ihe(r){var e,t,i=r.replace(/[\r\n=]/g,""),n=i.length,s=qS,o=0,a=[];for(e=0;e>16&255),a.push(o>>8&255),a.push(o&255)),o=o<<6|s.indexOf(i.charAt(e));return t=n%4*6,t===0?(a.push(o>>16&255),a.push(o>>8&255),a.push(o&255)):t===18?(a.push(o>>10&255),a.push(o>>2&255)):t===12&&a.push(o>>4&255),$l?$l.from?$l.from(a):new $l(a):a}function yhe(r){var e="",t=0,i,n,s=r.length,o=qS;for(i=0;i>18&63],e+=o[t>>12&63],e+=o[t>>6&63],e+=o[t&63]),t=(t<<8)+r[i];return n=s%3,n===0?(e+=o[t>>18&63],e+=o[t>>12&63],e+=o[t>>6&63],e+=o[t&63]):n===2?(e+=o[t>>10&63],e+=o[t>>4&63],e+=o[t<<2&63],e+=o[64]):n===1&&(e+=o[t>>2&63],e+=o[t<<4&63],e+=o[64],e+=o[64]),e}function whe(r){return $l&&$l.isBuffer(r)}WU.exports=new mhe("tag:yaml.org,2002:binary",{kind:"scalar",resolve:Ehe,construct:Ihe,predicate:whe,represent:yhe})});var XU=w((TZe,VU)=>{"use strict";var Bhe=si(),Qhe=Object.prototype.hasOwnProperty,bhe=Object.prototype.toString;function She(r){if(r===null)return!0;var e=[],t,i,n,s,o,a=r;for(t=0,i=a.length;t{"use strict";var xhe=si(),Phe=Object.prototype.toString;function Dhe(r){if(r===null)return!0;var e,t,i,n,s,o=r;for(s=new Array(o.length),e=0,t=o.length;e{"use strict";var Rhe=si(),Fhe=Object.prototype.hasOwnProperty;function Nhe(r){if(r===null)return!0;var e,t=r;for(e in t)if(Fhe.call(t,e)&&t[e]!==null)return!1;return!0}function Lhe(r){return r!==null?r:{}}$U.exports=new Rhe("tag:yaml.org,2002:set",{kind:"mapping",resolve:Nhe,construct:Lhe})});var Fg=w((KZe,t2)=>{"use strict";var The=_l();t2.exports=new The({include:[jS()],implicit:[YU(),qU()],explicit:[zU(),XU(),_U(),e2()]})});var i2=w((UZe,r2)=>{"use strict";var Ohe=si();function Mhe(){return!0}function Khe(){}function Uhe(){return""}function Hhe(r){return typeof r>"u"}r2.exports=new Ohe("tag:yaml.org,2002:js/undefined",{kind:"scalar",resolve:Mhe,construct:Khe,predicate:Hhe,represent:Uhe})});var s2=w((HZe,n2)=>{"use strict";var Ghe=si();function Yhe(r){if(r===null||r.length===0)return!1;var e=r,t=/\/([gim]*)$/.exec(r),i="";return!(e[0]==="/"&&(t&&(i=t[1]),i.length>3||e[e.length-i.length-1]!=="/"))}function jhe(r){var e=r,t=/\/([gim]*)$/.exec(r),i="";return e[0]==="/"&&(t&&(i=t[1]),e=e.slice(1,e.length-i.length-1)),new RegExp(e,i)}function qhe(r){var e="/"+r.source+"/";return r.global&&(e+="g"),r.multiline&&(e+="m"),r.ignoreCase&&(e+="i"),e}function Jhe(r){return Object.prototype.toString.call(r)==="[object RegExp]"}n2.exports=new Ghe("tag:yaml.org,2002:js/regexp",{kind:"scalar",resolve:Yhe,construct:jhe,predicate:Jhe,represent:qhe})});var A2=w((GZe,a2)=>{"use strict";var hI;try{o2=J,hI=o2("esprima")}catch{typeof window<"u"&&(hI=window.esprima)}var o2,Whe=si();function zhe(r){if(r===null)return!1;try{var e="("+r+")",t=hI.parse(e,{range:!0});return!(t.type!=="Program"||t.body.length!==1||t.body[0].type!=="ExpressionStatement"||t.body[0].expression.type!=="ArrowFunctionExpression"&&t.body[0].expression.type!=="FunctionExpression")}catch{return!1}}function Vhe(r){var e="("+r+")",t=hI.parse(e,{range:!0}),i=[],n;if(t.type!=="Program"||t.body.length!==1||t.body[0].type!=="ExpressionStatement"||t.body[0].expression.type!=="ArrowFunctionExpression"&&t.body[0].expression.type!=="FunctionExpression")throw new Error("Failed to resolve function");return t.body[0].expression.params.forEach(function(s){i.push(s.name)}),n=t.body[0].expression.body.range,t.body[0].expression.body.type==="BlockStatement"?new Function(i,e.slice(n[0]+1,n[1]-1)):new Function(i,"return "+e.slice(n[0],n[1]))}function Xhe(r){return r.toString()}function Zhe(r){return Object.prototype.toString.call(r)==="[object Function]"}a2.exports=new Whe("tag:yaml.org,2002:js/function",{kind:"scalar",resolve:zhe,construct:Vhe,predicate:Zhe,represent:Xhe})});var zp=w((YZe,c2)=>{"use strict";var l2=_l();c2.exports=l2.DEFAULT=new l2({include:[Fg()],explicit:[i2(),s2(),A2()]})});var P2=w((jZe,Vp)=>{"use strict";var ya=Zl(),C2=kg(),_he=CU(),m2=Fg(),$he=zp(),DA=Object.prototype.hasOwnProperty,pI=1,E2=2,I2=3,dI=4,JS=1,epe=2,u2=3,tpe=/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,rpe=/[\x85\u2028\u2029]/,ipe=/[,\[\]\{\}]/,y2=/^(?:!|!!|![a-z\-]+!)$/i,w2=/^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i;function g2(r){return Object.prototype.toString.call(r)}function vo(r){return r===10||r===13}function tc(r){return r===9||r===32}function un(r){return r===9||r===32||r===10||r===13}function Ng(r){return r===44||r===91||r===93||r===123||r===125}function npe(r){var e;return 48<=r&&r<=57?r-48:(e=r|32,97<=e&&e<=102?e-97+10:-1)}function spe(r){return r===120?2:r===117?4:r===85?8:0}function ope(r){return 48<=r&&r<=57?r-48:-1}function f2(r){return r===48?"\0":r===97?"\x07":r===98?"\b":r===116||r===9?" ":r===110?` -`:r===118?"\v":r===102?"\f":r===114?"\r":r===101?"\x1B":r===32?" ":r===34?'"':r===47?"/":r===92?"\\":r===78?"\x85":r===95?"\xA0":r===76?"\u2028":r===80?"\u2029":""}function ape(r){return r<=65535?String.fromCharCode(r):String.fromCharCode((r-65536>>10)+55296,(r-65536&1023)+56320)}var B2=new Array(256),Q2=new Array(256);for(ec=0;ec<256;ec++)B2[ec]=f2(ec)?1:0,Q2[ec]=f2(ec);var ec;function Ape(r,e){this.input=r,this.filename=e.filename||null,this.schema=e.schema||$he,this.onWarning=e.onWarning||null,this.legacy=e.legacy||!1,this.json=e.json||!1,this.listener=e.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=r.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.documents=[]}function b2(r,e){return new C2(e,new _he(r.filename,r.input,r.position,r.line,r.position-r.lineStart))}function ft(r,e){throw b2(r,e)}function CI(r,e){r.onWarning&&r.onWarning.call(null,b2(r,e))}var h2={YAML:function(e,t,i){var n,s,o;e.version!==null&&ft(e,"duplication of %YAML directive"),i.length!==1&&ft(e,"YAML directive accepts exactly one argument"),n=/^([0-9]+)\.([0-9]+)$/.exec(i[0]),n===null&&ft(e,"ill-formed argument of the YAML directive"),s=parseInt(n[1],10),o=parseInt(n[2],10),s!==1&&ft(e,"unacceptable YAML version of the document"),e.version=i[0],e.checkLineBreaks=o<2,o!==1&&o!==2&&CI(e,"unsupported YAML version of the document")},TAG:function(e,t,i){var n,s;i.length!==2&&ft(e,"TAG directive accepts exactly two arguments"),n=i[0],s=i[1],y2.test(n)||ft(e,"ill-formed tag handle (first argument) of the TAG directive"),DA.call(e.tagMap,n)&&ft(e,'there is a previously declared suffix for "'+n+'" tag handle'),w2.test(s)||ft(e,"ill-formed tag prefix (second argument) of the TAG directive"),e.tagMap[n]=s}};function PA(r,e,t,i){var n,s,o,a;if(e1&&(r.result+=ya.repeat(` -`,e-1))}function lpe(r,e,t){var i,n,s,o,a,l,c,u,g=r.kind,f=r.result,h;if(h=r.input.charCodeAt(r.position),un(h)||Ng(h)||h===35||h===38||h===42||h===33||h===124||h===62||h===39||h===34||h===37||h===64||h===96||(h===63||h===45)&&(n=r.input.charCodeAt(r.position+1),un(n)||t&&Ng(n)))return!1;for(r.kind="scalar",r.result="",s=o=r.position,a=!1;h!==0;){if(h===58){if(n=r.input.charCodeAt(r.position+1),un(n)||t&&Ng(n))break}else if(h===35){if(i=r.input.charCodeAt(r.position-1),un(i))break}else{if(r.position===r.lineStart&&mI(r)||t&&Ng(h))break;if(vo(h))if(l=r.line,c=r.lineStart,u=r.lineIndent,zr(r,!1,-1),r.lineIndent>=e){a=!0,h=r.input.charCodeAt(r.position);continue}else{r.position=o,r.line=l,r.lineStart=c,r.lineIndent=u;break}}a&&(PA(r,s,o,!1),zS(r,r.line-l),s=o=r.position,a=!1),tc(h)||(o=r.position+1),h=r.input.charCodeAt(++r.position)}return PA(r,s,o,!1),r.result?!0:(r.kind=g,r.result=f,!1)}function cpe(r,e){var t,i,n;if(t=r.input.charCodeAt(r.position),t!==39)return!1;for(r.kind="scalar",r.result="",r.position++,i=n=r.position;(t=r.input.charCodeAt(r.position))!==0;)if(t===39)if(PA(r,i,r.position,!0),t=r.input.charCodeAt(++r.position),t===39)i=r.position,r.position++,n=r.position;else return!0;else vo(t)?(PA(r,i,n,!0),zS(r,zr(r,!1,e)),i=n=r.position):r.position===r.lineStart&&mI(r)?ft(r,"unexpected end of the document within a single quoted scalar"):(r.position++,n=r.position);ft(r,"unexpected end of the stream within a single quoted scalar")}function upe(r,e){var t,i,n,s,o,a;if(a=r.input.charCodeAt(r.position),a!==34)return!1;for(r.kind="scalar",r.result="",r.position++,t=i=r.position;(a=r.input.charCodeAt(r.position))!==0;){if(a===34)return PA(r,t,r.position,!0),r.position++,!0;if(a===92){if(PA(r,t,r.position,!0),a=r.input.charCodeAt(++r.position),vo(a))zr(r,!1,e);else if(a<256&&B2[a])r.result+=Q2[a],r.position++;else if((o=spe(a))>0){for(n=o,s=0;n>0;n--)a=r.input.charCodeAt(++r.position),(o=npe(a))>=0?s=(s<<4)+o:ft(r,"expected hexadecimal character");r.result+=ape(s),r.position++}else ft(r,"unknown escape sequence");t=i=r.position}else vo(a)?(PA(r,t,i,!0),zS(r,zr(r,!1,e)),t=i=r.position):r.position===r.lineStart&&mI(r)?ft(r,"unexpected end of the document within a double quoted scalar"):(r.position++,i=r.position)}ft(r,"unexpected end of the stream within a double quoted scalar")}function gpe(r,e){var t=!0,i,n=r.tag,s,o=r.anchor,a,l,c,u,g,f={},h,p,C,y;if(y=r.input.charCodeAt(r.position),y===91)l=93,g=!1,s=[];else if(y===123)l=125,g=!0,s={};else return!1;for(r.anchor!==null&&(r.anchorMap[r.anchor]=s),y=r.input.charCodeAt(++r.position);y!==0;){if(zr(r,!0,e),y=r.input.charCodeAt(r.position),y===l)return r.position++,r.tag=n,r.anchor=o,r.kind=g?"mapping":"sequence",r.result=s,!0;t||ft(r,"missed comma between flow collection entries"),p=h=C=null,c=u=!1,y===63&&(a=r.input.charCodeAt(r.position+1),un(a)&&(c=u=!0,r.position++,zr(r,!0,e))),i=r.line,Tg(r,e,pI,!1,!0),p=r.tag,h=r.result,zr(r,!0,e),y=r.input.charCodeAt(r.position),(u||r.line===i)&&y===58&&(c=!0,y=r.input.charCodeAt(++r.position),zr(r,!0,e),Tg(r,e,pI,!1,!0),C=r.result),g?Lg(r,s,f,p,h,C):c?s.push(Lg(r,null,f,p,h,C)):s.push(h),zr(r,!0,e),y=r.input.charCodeAt(r.position),y===44?(t=!0,y=r.input.charCodeAt(++r.position)):t=!1}ft(r,"unexpected end of the stream within a flow collection")}function fpe(r,e){var t,i,n=JS,s=!1,o=!1,a=e,l=0,c=!1,u,g;if(g=r.input.charCodeAt(r.position),g===124)i=!1;else if(g===62)i=!0;else return!1;for(r.kind="scalar",r.result="";g!==0;)if(g=r.input.charCodeAt(++r.position),g===43||g===45)JS===n?n=g===43?u2:epe:ft(r,"repeat of a chomping mode identifier");else if((u=ope(g))>=0)u===0?ft(r,"bad explicit indentation width of a block scalar; it cannot be less than one"):o?ft(r,"repeat of an indentation width identifier"):(a=e+u-1,o=!0);else break;if(tc(g)){do g=r.input.charCodeAt(++r.position);while(tc(g));if(g===35)do g=r.input.charCodeAt(++r.position);while(!vo(g)&&g!==0)}for(;g!==0;){for(WS(r),r.lineIndent=0,g=r.input.charCodeAt(r.position);(!o||r.lineIndenta&&(a=r.lineIndent),vo(g)){l++;continue}if(r.lineIndente)&&l!==0)ft(r,"bad indentation of a sequence entry");else if(r.lineIndente)&&(Tg(r,e,dI,!0,n)&&(p?f=r.result:h=r.result),p||(Lg(r,c,u,g,f,h,s,o),g=f=h=null),zr(r,!0,-1),y=r.input.charCodeAt(r.position)),r.lineIndent>e&&y!==0)ft(r,"bad indentation of a mapping entry");else if(r.lineIndente?l=1:r.lineIndent===e?l=0:r.lineIndente?l=1:r.lineIndent===e?l=0:r.lineIndent tag; it should be "scalar", not "'+r.kind+'"'),g=0,f=r.implicitTypes.length;g tag; it should be "'+h.kind+'", not "'+r.kind+'"'),h.resolve(r.result)?(r.result=h.construct(r.result),r.anchor!==null&&(r.anchorMap[r.anchor]=r.result)):ft(r,"cannot resolve a node with !<"+r.tag+"> explicit tag")):ft(r,"unknown tag !<"+r.tag+">");return r.listener!==null&&r.listener("close",r),r.tag!==null||r.anchor!==null||u}function mpe(r){var e=r.position,t,i,n,s=!1,o;for(r.version=null,r.checkLineBreaks=r.legacy,r.tagMap={},r.anchorMap={};(o=r.input.charCodeAt(r.position))!==0&&(zr(r,!0,-1),o=r.input.charCodeAt(r.position),!(r.lineIndent>0||o!==37));){for(s=!0,o=r.input.charCodeAt(++r.position),t=r.position;o!==0&&!un(o);)o=r.input.charCodeAt(++r.position);for(i=r.input.slice(t,r.position),n=[],i.length<1&&ft(r,"directive name must not be less than one character in length");o!==0;){for(;tc(o);)o=r.input.charCodeAt(++r.position);if(o===35){do o=r.input.charCodeAt(++r.position);while(o!==0&&!vo(o));break}if(vo(o))break;for(t=r.position;o!==0&&!un(o);)o=r.input.charCodeAt(++r.position);n.push(r.input.slice(t,r.position))}o!==0&&WS(r),DA.call(h2,i)?h2[i](r,i,n):CI(r,'unknown document directive "'+i+'"')}if(zr(r,!0,-1),r.lineIndent===0&&r.input.charCodeAt(r.position)===45&&r.input.charCodeAt(r.position+1)===45&&r.input.charCodeAt(r.position+2)===45?(r.position+=3,zr(r,!0,-1)):s&&ft(r,"directives end mark is expected"),Tg(r,r.lineIndent-1,dI,!1,!0),zr(r,!0,-1),r.checkLineBreaks&&rpe.test(r.input.slice(e,r.position))&&CI(r,"non-ASCII line breaks are interpreted as content"),r.documents.push(r.result),r.position===r.lineStart&&mI(r)){r.input.charCodeAt(r.position)===46&&(r.position+=3,zr(r,!0,-1));return}if(r.position"u"&&(t=e,e=null);var i=S2(r,t);if(typeof e!="function")return i;for(var n=0,s=i.length;n"u"&&(t=e,e=null),v2(r,e,ya.extend({schema:m2},t))}function Ipe(r,e){return x2(r,ya.extend({schema:m2},e))}Vp.exports.loadAll=v2;Vp.exports.load=x2;Vp.exports.safeLoadAll=Epe;Vp.exports.safeLoad=Ipe});var _2=w((qZe,_S)=>{"use strict";var Zp=Zl(),_p=kg(),ype=zp(),wpe=Fg(),O2=Object.prototype.toString,M2=Object.prototype.hasOwnProperty,Bpe=9,Xp=10,Qpe=13,bpe=32,Spe=33,vpe=34,K2=35,xpe=37,Ppe=38,Dpe=39,kpe=42,U2=44,Rpe=45,H2=58,Fpe=61,Npe=62,Lpe=63,Tpe=64,G2=91,Y2=93,Ope=96,j2=123,Mpe=124,q2=125,Ni={};Ni[0]="\\0";Ni[7]="\\a";Ni[8]="\\b";Ni[9]="\\t";Ni[10]="\\n";Ni[11]="\\v";Ni[12]="\\f";Ni[13]="\\r";Ni[27]="\\e";Ni[34]='\\"';Ni[92]="\\\\";Ni[133]="\\N";Ni[160]="\\_";Ni[8232]="\\L";Ni[8233]="\\P";var Kpe=["y","Y","yes","Yes","YES","on","On","ON","n","N","no","No","NO","off","Off","OFF"];function Upe(r,e){var t,i,n,s,o,a,l;if(e===null)return{};for(t={},i=Object.keys(e),n=0,s=i.length;n0?r.charCodeAt(s-1):null,f=f&&R2(o,a)}else{for(s=0;si&&r[g+1]!==" ",g=s);else if(!Og(o))return EI;a=s>0?r.charCodeAt(s-1):null,f=f&&R2(o,a)}c=c||u&&s-g-1>i&&r[g+1]!==" "}return!l&&!c?f&&!n(r)?W2:z2:t>9&&J2(r)?EI:c?X2:V2}function Jpe(r,e,t,i){r.dump=function(){if(e.length===0)return"''";if(!r.noCompatMode&&Kpe.indexOf(e)!==-1)return"'"+e+"'";var n=r.indent*Math.max(1,t),s=r.lineWidth===-1?-1:Math.max(Math.min(r.lineWidth,40),r.lineWidth-n),o=i||r.flowLevel>-1&&t>=r.flowLevel;function a(l){return Gpe(r,l)}switch(qpe(e,o,r.indent,s,a)){case W2:return e;case z2:return"'"+e.replace(/'/g,"''")+"'";case V2:return"|"+F2(e,r.indent)+N2(k2(e,n));case X2:return">"+F2(e,r.indent)+N2(k2(Wpe(e,s),n));case EI:return'"'+zpe(e,s)+'"';default:throw new _p("impossible error: invalid scalar style")}}()}function F2(r,e){var t=J2(r)?String(e):"",i=r[r.length-1]===` -`,n=i&&(r[r.length-2]===` -`||r===` -`),s=n?"+":i?"":"-";return t+s+` -`}function N2(r){return r[r.length-1]===` -`?r.slice(0,-1):r}function Wpe(r,e){for(var t=/(\n+)([^\n]*)/g,i=function(){var c=r.indexOf(` -`);return c=c!==-1?c:r.length,t.lastIndex=c,L2(r.slice(0,c),e)}(),n=r[0]===` -`||r[0]===" ",s,o;o=t.exec(r);){var a=o[1],l=o[2];s=l[0]===" ",i+=a+(!n&&!s&&l!==""?` -`:"")+L2(l,e),n=s}return i}function L2(r,e){if(r===""||r[0]===" ")return r;for(var t=/ [^ ]/g,i,n=0,s,o=0,a=0,l="";i=t.exec(r);)a=i.index,a-n>e&&(s=o>n?o:a,l+=` -`+r.slice(n,s),n=s+1),o=a;return l+=` -`,r.length-n>e&&o>n?l+=r.slice(n,o)+` -`+r.slice(o+1):l+=r.slice(n),l.slice(1)}function zpe(r){for(var e="",t,i,n,s=0;s=55296&&t<=56319&&(i=r.charCodeAt(s+1),i>=56320&&i<=57343)){e+=D2((t-55296)*1024+i-56320+65536),s++;continue}n=Ni[t],e+=!n&&Og(t)?r[s]:n||D2(t)}return e}function Vpe(r,e,t){var i="",n=r.tag,s,o;for(s=0,o=t.length;s1024&&(u+="? "),u+=r.dump+(r.condenseFlow?'"':"")+":"+(r.condenseFlow?"":" "),rc(r,e,c,!1,!1)&&(u+=r.dump,i+=u));r.tag=n,r.dump="{"+i+"}"}function _pe(r,e,t,i){var n="",s=r.tag,o=Object.keys(t),a,l,c,u,g,f;if(r.sortKeys===!0)o.sort();else if(typeof r.sortKeys=="function")o.sort(r.sortKeys);else if(r.sortKeys)throw new _p("sortKeys must be a boolean or a function");for(a=0,l=o.length;a1024,g&&(r.dump&&Xp===r.dump.charCodeAt(0)?f+="?":f+="? "),f+=r.dump,g&&(f+=VS(r,e)),rc(r,e+1,u,!0,g)&&(r.dump&&Xp===r.dump.charCodeAt(0)?f+=":":f+=": ",f+=r.dump,n+=f));r.tag=s,r.dump=n||"{}"}function T2(r,e,t){var i,n,s,o,a,l;for(n=t?r.explicitTypes:r.implicitTypes,s=0,o=n.length;s tag resolver accepts not "'+l+'" style');r.dump=i}return!0}return!1}function rc(r,e,t,i,n,s){r.tag=null,r.dump=t,T2(r,t,!1)||T2(r,t,!0);var o=O2.call(r.dump);i&&(i=r.flowLevel<0||r.flowLevel>e);var a=o==="[object Object]"||o==="[object Array]",l,c;if(a&&(l=r.duplicates.indexOf(t),c=l!==-1),(r.tag!==null&&r.tag!=="?"||c||r.indent!==2&&e>0)&&(n=!1),c&&r.usedDuplicates[l])r.dump="*ref_"+l;else{if(a&&c&&!r.usedDuplicates[l]&&(r.usedDuplicates[l]=!0),o==="[object Object]")i&&Object.keys(r.dump).length!==0?(_pe(r,e,r.dump,n),c&&(r.dump="&ref_"+l+r.dump)):(Zpe(r,e,r.dump),c&&(r.dump="&ref_"+l+" "+r.dump));else if(o==="[object Array]"){var u=r.noArrayIndent&&e>0?e-1:e;i&&r.dump.length!==0?(Xpe(r,u,r.dump,n),c&&(r.dump="&ref_"+l+r.dump)):(Vpe(r,u,r.dump),c&&(r.dump="&ref_"+l+" "+r.dump))}else if(o==="[object String]")r.tag!=="?"&&Jpe(r,r.dump,e,s);else{if(r.skipInvalid)return!1;throw new _p("unacceptable kind of an object to dump "+o)}r.tag!==null&&r.tag!=="?"&&(r.dump="!<"+r.tag+"> "+r.dump)}return!0}function $pe(r,e){var t=[],i=[],n,s;for(XS(r,t,i),n=0,s=i.length;n{"use strict";var II=P2(),$2=_2();function yI(r){return function(){throw new Error("Function "+r+" is deprecated and cannot be used.")}}Fr.exports.Type=si();Fr.exports.Schema=_l();Fr.exports.FAILSAFE_SCHEMA=fI();Fr.exports.JSON_SCHEMA=YS();Fr.exports.CORE_SCHEMA=jS();Fr.exports.DEFAULT_SAFE_SCHEMA=Fg();Fr.exports.DEFAULT_FULL_SCHEMA=zp();Fr.exports.load=II.load;Fr.exports.loadAll=II.loadAll;Fr.exports.safeLoad=II.safeLoad;Fr.exports.safeLoadAll=II.safeLoadAll;Fr.exports.dump=$2.dump;Fr.exports.safeDump=$2.safeDump;Fr.exports.YAMLException=kg();Fr.exports.MINIMAL_SCHEMA=fI();Fr.exports.SAFE_SCHEMA=Fg();Fr.exports.DEFAULT_SCHEMA=zp();Fr.exports.scan=yI("scan");Fr.exports.parse=yI("parse");Fr.exports.compose=yI("compose");Fr.exports.addConstructor=yI("addConstructor")});var rH=w((WZe,tH)=>{"use strict";var tde=eH();tH.exports=tde});var nH=w((zZe,iH)=>{"use strict";function rde(r,e){function t(){this.constructor=r}t.prototype=e.prototype,r.prototype=new t}function ic(r,e,t,i){this.message=r,this.expected=e,this.found=t,this.location=i,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,ic)}rde(ic,Error);ic.buildMessage=function(r,e){var t={literal:function(c){return'"'+n(c.text)+'"'},class:function(c){var u="",g;for(g=0;g0){for(g=1,f=1;g({[Ke]:Ce})))},H=function(R){return R},j=function(R){return R},$=Ms("correct indentation"),V=" ",W=ar(" ",!1),_=function(R){return R.length===BA*mg},A=function(R){return R.length===(BA+1)*mg},ae=function(){return BA++,!0},ge=function(){return BA--,!0},re=function(){return gg()},O=Ms("pseudostring"),F=/^[^\r\n\t ?:,\][{}#&*!|>'"%@`\-]/,ue=Fn(["\r",` -`," "," ","?",":",",","]","[","{","}","#","&","*","!","|",">","'",'"',"%","@","`","-"],!0,!1),he=/^[^\r\n\t ,\][{}:#"']/,ke=Fn(["\r",` -`," "," ",",","]","[","{","}",":","#",'"',"'"],!0,!1),Fe=function(){return gg().replace(/^ *| *$/g,"")},Ne="--",oe=ar("--",!1),le=/^[a-zA-Z\/0-9]/,we=Fn([["a","z"],["A","Z"],"/",["0","9"]],!1,!1),fe=/^[^\r\n\t :,]/,Ae=Fn(["\r",` -`," "," ",":",","],!0,!1),qe="null",ne=ar("null",!1),Y=function(){return null},pe="true",ie=ar("true",!1),de=function(){return!0},_e="false",Pt=ar("false",!1),It=function(){return!1},Or=Ms("string"),ii='"',gi=ar('"',!1),hr=function(){return""},fi=function(R){return R},ni=function(R){return R.join("")},Os=/^[^"\\\0-\x1F\x7F]/,pr=Fn(['"',"\\",["\0",""],"\x7F"],!0,!1),Ii='\\"',es=ar('\\"',!1),ua=function(){return'"'},pA="\\\\",ag=ar("\\\\",!1),ts=function(){return"\\"},dA="\\/",ga=ar("\\/",!1),yp=function(){return"/"},CA="\\b",mA=ar("\\b",!1),wr=function(){return"\b"},kl="\\f",Ag=ar("\\f",!1),Io=function(){return"\f"},lg="\\n",wp=ar("\\n",!1),Bp=function(){return` -`},vr="\\r",se=ar("\\r",!1),yo=function(){return"\r"},kn="\\t",cg=ar("\\t",!1),Qt=function(){return" "},Rl="\\u",Rn=ar("\\u",!1),rs=function(R,q,Ce,Ke){return String.fromCharCode(parseInt(`0x${R}${q}${Ce}${Ke}`))},is=/^[0-9a-fA-F]/,gt=Fn([["0","9"],["a","f"],["A","F"]],!1,!1),wo=Ms("blank space"),At=/^[ \t]/,an=Fn([" "," "],!1,!1),S=Ms("white space"),Tt=/^[ \t\n\r]/,ug=Fn([" "," ",` -`,"\r"],!1,!1),Fl=`\r -`,Qp=ar(`\r -`,!1),bp=` -`,Sp=ar(` -`,!1),vp="\r",xp=ar("\r",!1),G=0,yt=0,EA=[{line:1,column:1}],Ji=0,Nl=[],Xe=0,fa;if("startRule"in e){if(!(e.startRule in i))throw new Error(`Can't start parsing from rule "`+e.startRule+'".');n=i[e.startRule]}function gg(){return r.substring(yt,G)}function FE(){return An(yt,G)}function Pp(R,q){throw q=q!==void 0?q:An(yt,G),Tl([Ms(R)],r.substring(yt,G),q)}function NE(R,q){throw q=q!==void 0?q:An(yt,G),fg(R,q)}function ar(R,q){return{type:"literal",text:R,ignoreCase:q}}function Fn(R,q,Ce){return{type:"class",parts:R,inverted:q,ignoreCase:Ce}}function Ll(){return{type:"any"}}function Dp(){return{type:"end"}}function Ms(R){return{type:"other",description:R}}function ha(R){var q=EA[R],Ce;if(q)return q;for(Ce=R-1;!EA[Ce];)Ce--;for(q=EA[Ce],q={line:q.line,column:q.column};CeJi&&(Ji=G,Nl=[]),Nl.push(R))}function fg(R,q){return new ic(R,null,null,q)}function Tl(R,q,Ce){return new ic(ic.buildMessage(R,q),R,q,Ce)}function Ks(){var R;return R=hg(),R}function Ol(){var R,q,Ce;for(R=G,q=[],Ce=IA();Ce!==t;)q.push(Ce),Ce=IA();return q!==t&&(yt=R,q=s(q)),R=q,R}function IA(){var R,q,Ce,Ke,Re;return R=G,q=da(),q!==t?(r.charCodeAt(G)===45?(Ce=o,G++):(Ce=t,Xe===0&&Te(a)),Ce!==t?(Ke=Rr(),Ke!==t?(Re=pa(),Re!==t?(yt=R,q=l(Re),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t),R}function hg(){var R,q,Ce;for(R=G,q=[],Ce=pg();Ce!==t;)q.push(Ce),Ce=pg();return q!==t&&(yt=R,q=c(q)),R=q,R}function pg(){var R,q,Ce,Ke,Re,ze,dt,Ft,Nn;if(R=G,q=Rr(),q===t&&(q=null),q!==t){if(Ce=G,r.charCodeAt(G)===35?(Ke=u,G++):(Ke=t,Xe===0&&Te(g)),Ke!==t){if(Re=[],ze=G,dt=G,Xe++,Ft=Gs(),Xe--,Ft===t?dt=void 0:(G=dt,dt=t),dt!==t?(r.length>G?(Ft=r.charAt(G),G++):(Ft=t,Xe===0&&Te(f)),Ft!==t?(dt=[dt,Ft],ze=dt):(G=ze,ze=t)):(G=ze,ze=t),ze!==t)for(;ze!==t;)Re.push(ze),ze=G,dt=G,Xe++,Ft=Gs(),Xe--,Ft===t?dt=void 0:(G=dt,dt=t),dt!==t?(r.length>G?(Ft=r.charAt(G),G++):(Ft=t,Xe===0&&Te(f)),Ft!==t?(dt=[dt,Ft],ze=dt):(G=ze,ze=t)):(G=ze,ze=t);else Re=t;Re!==t?(Ke=[Ke,Re],Ce=Ke):(G=Ce,Ce=t)}else G=Ce,Ce=t;if(Ce===t&&(Ce=null),Ce!==t){if(Ke=[],Re=Hs(),Re!==t)for(;Re!==t;)Ke.push(Re),Re=Hs();else Ke=t;Ke!==t?(yt=R,q=h(),R=q):(G=R,R=t)}else G=R,R=t}else G=R,R=t;if(R===t&&(R=G,q=da(),q!==t?(Ce=Ml(),Ce!==t?(Ke=Rr(),Ke===t&&(Ke=null),Ke!==t?(r.charCodeAt(G)===58?(Re=p,G++):(Re=t,Xe===0&&Te(C)),Re!==t?(ze=Rr(),ze===t&&(ze=null),ze!==t?(dt=pa(),dt!==t?(yt=R,q=y(Ce,dt),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t),R===t&&(R=G,q=da(),q!==t?(Ce=Us(),Ce!==t?(Ke=Rr(),Ke===t&&(Ke=null),Ke!==t?(r.charCodeAt(G)===58?(Re=p,G++):(Re=t,Xe===0&&Te(C)),Re!==t?(ze=Rr(),ze===t&&(ze=null),ze!==t?(dt=pa(),dt!==t?(yt=R,q=y(Ce,dt),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t),R===t))){if(R=G,q=da(),q!==t)if(Ce=Us(),Ce!==t)if(Ke=Rr(),Ke!==t)if(Re=LE(),Re!==t){if(ze=[],dt=Hs(),dt!==t)for(;dt!==t;)ze.push(dt),dt=Hs();else ze=t;ze!==t?(yt=R,q=y(Ce,Re),R=q):(G=R,R=t)}else G=R,R=t;else G=R,R=t;else G=R,R=t;else G=R,R=t;if(R===t)if(R=G,q=da(),q!==t)if(Ce=Us(),Ce!==t){if(Ke=[],Re=G,ze=Rr(),ze===t&&(ze=null),ze!==t?(r.charCodeAt(G)===44?(dt=B,G++):(dt=t,Xe===0&&Te(v)),dt!==t?(Ft=Rr(),Ft===t&&(Ft=null),Ft!==t?(Nn=Us(),Nn!==t?(yt=Re,ze=D(Ce,Nn),Re=ze):(G=Re,Re=t)):(G=Re,Re=t)):(G=Re,Re=t)):(G=Re,Re=t),Re!==t)for(;Re!==t;)Ke.push(Re),Re=G,ze=Rr(),ze===t&&(ze=null),ze!==t?(r.charCodeAt(G)===44?(dt=B,G++):(dt=t,Xe===0&&Te(v)),dt!==t?(Ft=Rr(),Ft===t&&(Ft=null),Ft!==t?(Nn=Us(),Nn!==t?(yt=Re,ze=D(Ce,Nn),Re=ze):(G=Re,Re=t)):(G=Re,Re=t)):(G=Re,Re=t)):(G=Re,Re=t);else Ke=t;Ke!==t?(Re=Rr(),Re===t&&(Re=null),Re!==t?(r.charCodeAt(G)===58?(ze=p,G++):(ze=t,Xe===0&&Te(C)),ze!==t?(dt=Rr(),dt===t&&(dt=null),dt!==t?(Ft=pa(),Ft!==t?(yt=R,q=L(Ce,Ke,Ft),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)}else G=R,R=t;else G=R,R=t}return R}function pa(){var R,q,Ce,Ke,Re,ze,dt;if(R=G,q=G,Xe++,Ce=G,Ke=Gs(),Ke!==t?(Re=rt(),Re!==t?(r.charCodeAt(G)===45?(ze=o,G++):(ze=t,Xe===0&&Te(a)),ze!==t?(dt=Rr(),dt!==t?(Ke=[Ke,Re,ze,dt],Ce=Ke):(G=Ce,Ce=t)):(G=Ce,Ce=t)):(G=Ce,Ce=t)):(G=Ce,Ce=t),Xe--,Ce!==t?(G=q,q=void 0):q=t,q!==t?(Ce=Hs(),Ce!==t?(Ke=Bo(),Ke!==t?(Re=Ol(),Re!==t?(ze=yA(),ze!==t?(yt=R,q=H(Re),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t),R===t&&(R=G,q=Gs(),q!==t?(Ce=Bo(),Ce!==t?(Ke=hg(),Ke!==t?(Re=yA(),Re!==t?(yt=R,q=H(Ke),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t),R===t))if(R=G,q=Kl(),q!==t){if(Ce=[],Ke=Hs(),Ke!==t)for(;Ke!==t;)Ce.push(Ke),Ke=Hs();else Ce=t;Ce!==t?(yt=R,q=j(q),R=q):(G=R,R=t)}else G=R,R=t;return R}function da(){var R,q,Ce;for(Xe++,R=G,q=[],r.charCodeAt(G)===32?(Ce=V,G++):(Ce=t,Xe===0&&Te(W));Ce!==t;)q.push(Ce),r.charCodeAt(G)===32?(Ce=V,G++):(Ce=t,Xe===0&&Te(W));return q!==t?(yt=G,Ce=_(q),Ce?Ce=void 0:Ce=t,Ce!==t?(q=[q,Ce],R=q):(G=R,R=t)):(G=R,R=t),Xe--,R===t&&(q=t,Xe===0&&Te($)),R}function rt(){var R,q,Ce;for(R=G,q=[],r.charCodeAt(G)===32?(Ce=V,G++):(Ce=t,Xe===0&&Te(W));Ce!==t;)q.push(Ce),r.charCodeAt(G)===32?(Ce=V,G++):(Ce=t,Xe===0&&Te(W));return q!==t?(yt=G,Ce=A(q),Ce?Ce=void 0:Ce=t,Ce!==t?(q=[q,Ce],R=q):(G=R,R=t)):(G=R,R=t),R}function Bo(){var R;return yt=G,R=ae(),R?R=void 0:R=t,R}function yA(){var R;return yt=G,R=ge(),R?R=void 0:R=t,R}function Ml(){var R;return R=Ul(),R===t&&(R=kp()),R}function Us(){var R,q,Ce;if(R=Ul(),R===t){if(R=G,q=[],Ce=dg(),Ce!==t)for(;Ce!==t;)q.push(Ce),Ce=dg();else q=t;q!==t&&(yt=R,q=re()),R=q}return R}function Kl(){var R;return R=Rp(),R===t&&(R=TE(),R===t&&(R=Ul(),R===t&&(R=kp()))),R}function LE(){var R;return R=Rp(),R===t&&(R=Ul(),R===t&&(R=dg())),R}function kp(){var R,q,Ce,Ke,Re,ze;if(Xe++,R=G,F.test(r.charAt(G))?(q=r.charAt(G),G++):(q=t,Xe===0&&Te(ue)),q!==t){for(Ce=[],Ke=G,Re=Rr(),Re===t&&(Re=null),Re!==t?(he.test(r.charAt(G))?(ze=r.charAt(G),G++):(ze=t,Xe===0&&Te(ke)),ze!==t?(Re=[Re,ze],Ke=Re):(G=Ke,Ke=t)):(G=Ke,Ke=t);Ke!==t;)Ce.push(Ke),Ke=G,Re=Rr(),Re===t&&(Re=null),Re!==t?(he.test(r.charAt(G))?(ze=r.charAt(G),G++):(ze=t,Xe===0&&Te(ke)),ze!==t?(Re=[Re,ze],Ke=Re):(G=Ke,Ke=t)):(G=Ke,Ke=t);Ce!==t?(yt=R,q=Fe(),R=q):(G=R,R=t)}else G=R,R=t;return Xe--,R===t&&(q=t,Xe===0&&Te(O)),R}function dg(){var R,q,Ce,Ke,Re;if(R=G,r.substr(G,2)===Ne?(q=Ne,G+=2):(q=t,Xe===0&&Te(oe)),q===t&&(q=null),q!==t)if(le.test(r.charAt(G))?(Ce=r.charAt(G),G++):(Ce=t,Xe===0&&Te(we)),Ce!==t){for(Ke=[],fe.test(r.charAt(G))?(Re=r.charAt(G),G++):(Re=t,Xe===0&&Te(Ae));Re!==t;)Ke.push(Re),fe.test(r.charAt(G))?(Re=r.charAt(G),G++):(Re=t,Xe===0&&Te(Ae));Ke!==t?(yt=R,q=Fe(),R=q):(G=R,R=t)}else G=R,R=t;else G=R,R=t;return R}function Rp(){var R,q;return R=G,r.substr(G,4)===qe?(q=qe,G+=4):(q=t,Xe===0&&Te(ne)),q!==t&&(yt=R,q=Y()),R=q,R}function TE(){var R,q;return R=G,r.substr(G,4)===pe?(q=pe,G+=4):(q=t,Xe===0&&Te(ie)),q!==t&&(yt=R,q=de()),R=q,R===t&&(R=G,r.substr(G,5)===_e?(q=_e,G+=5):(q=t,Xe===0&&Te(Pt)),q!==t&&(yt=R,q=It()),R=q),R}function Ul(){var R,q,Ce,Ke;return Xe++,R=G,r.charCodeAt(G)===34?(q=ii,G++):(q=t,Xe===0&&Te(gi)),q!==t?(r.charCodeAt(G)===34?(Ce=ii,G++):(Ce=t,Xe===0&&Te(gi)),Ce!==t?(yt=R,q=hr(),R=q):(G=R,R=t)):(G=R,R=t),R===t&&(R=G,r.charCodeAt(G)===34?(q=ii,G++):(q=t,Xe===0&&Te(gi)),q!==t?(Ce=OE(),Ce!==t?(r.charCodeAt(G)===34?(Ke=ii,G++):(Ke=t,Xe===0&&Te(gi)),Ke!==t?(yt=R,q=fi(Ce),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)),Xe--,R===t&&(q=t,Xe===0&&Te(Or)),R}function OE(){var R,q,Ce;if(R=G,q=[],Ce=Cg(),Ce!==t)for(;Ce!==t;)q.push(Ce),Ce=Cg();else q=t;return q!==t&&(yt=R,q=ni(q)),R=q,R}function Cg(){var R,q,Ce,Ke,Re,ze;return Os.test(r.charAt(G))?(R=r.charAt(G),G++):(R=t,Xe===0&&Te(pr)),R===t&&(R=G,r.substr(G,2)===Ii?(q=Ii,G+=2):(q=t,Xe===0&&Te(es)),q!==t&&(yt=R,q=ua()),R=q,R===t&&(R=G,r.substr(G,2)===pA?(q=pA,G+=2):(q=t,Xe===0&&Te(ag)),q!==t&&(yt=R,q=ts()),R=q,R===t&&(R=G,r.substr(G,2)===dA?(q=dA,G+=2):(q=t,Xe===0&&Te(ga)),q!==t&&(yt=R,q=yp()),R=q,R===t&&(R=G,r.substr(G,2)===CA?(q=CA,G+=2):(q=t,Xe===0&&Te(mA)),q!==t&&(yt=R,q=wr()),R=q,R===t&&(R=G,r.substr(G,2)===kl?(q=kl,G+=2):(q=t,Xe===0&&Te(Ag)),q!==t&&(yt=R,q=Io()),R=q,R===t&&(R=G,r.substr(G,2)===lg?(q=lg,G+=2):(q=t,Xe===0&&Te(wp)),q!==t&&(yt=R,q=Bp()),R=q,R===t&&(R=G,r.substr(G,2)===vr?(q=vr,G+=2):(q=t,Xe===0&&Te(se)),q!==t&&(yt=R,q=yo()),R=q,R===t&&(R=G,r.substr(G,2)===kn?(q=kn,G+=2):(q=t,Xe===0&&Te(cg)),q!==t&&(yt=R,q=Qt()),R=q,R===t&&(R=G,r.substr(G,2)===Rl?(q=Rl,G+=2):(q=t,Xe===0&&Te(Rn)),q!==t?(Ce=wA(),Ce!==t?(Ke=wA(),Ke!==t?(Re=wA(),Re!==t?(ze=wA(),ze!==t?(yt=R,q=rs(Ce,Ke,Re,ze),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)))))))))),R}function wA(){var R;return is.test(r.charAt(G))?(R=r.charAt(G),G++):(R=t,Xe===0&&Te(gt)),R}function Rr(){var R,q;if(Xe++,R=[],At.test(r.charAt(G))?(q=r.charAt(G),G++):(q=t,Xe===0&&Te(an)),q!==t)for(;q!==t;)R.push(q),At.test(r.charAt(G))?(q=r.charAt(G),G++):(q=t,Xe===0&&Te(an));else R=t;return Xe--,R===t&&(q=t,Xe===0&&Te(wo)),R}function ME(){var R,q;if(Xe++,R=[],Tt.test(r.charAt(G))?(q=r.charAt(G),G++):(q=t,Xe===0&&Te(ug)),q!==t)for(;q!==t;)R.push(q),Tt.test(r.charAt(G))?(q=r.charAt(G),G++):(q=t,Xe===0&&Te(ug));else R=t;return Xe--,R===t&&(q=t,Xe===0&&Te(S)),R}function Hs(){var R,q,Ce,Ke,Re,ze;if(R=G,q=Gs(),q!==t){for(Ce=[],Ke=G,Re=Rr(),Re===t&&(Re=null),Re!==t?(ze=Gs(),ze!==t?(Re=[Re,ze],Ke=Re):(G=Ke,Ke=t)):(G=Ke,Ke=t);Ke!==t;)Ce.push(Ke),Ke=G,Re=Rr(),Re===t&&(Re=null),Re!==t?(ze=Gs(),ze!==t?(Re=[Re,ze],Ke=Re):(G=Ke,Ke=t)):(G=Ke,Ke=t);Ce!==t?(q=[q,Ce],R=q):(G=R,R=t)}else G=R,R=t;return R}function Gs(){var R;return r.substr(G,2)===Fl?(R=Fl,G+=2):(R=t,Xe===0&&Te(Qp)),R===t&&(r.charCodeAt(G)===10?(R=bp,G++):(R=t,Xe===0&&Te(Sp)),R===t&&(r.charCodeAt(G)===13?(R=vp,G++):(R=t,Xe===0&&Te(xp)))),R}let mg=2,BA=0;if(fa=n(),fa!==t&&G===r.length)return fa;throw fa!==t&&G{"use strict";var Ade=r=>{let e=!1,t=!1,i=!1;for(let n=0;n{if(!(typeof r=="string"||Array.isArray(r)))throw new TypeError("Expected the input to be `string | string[]`");e=Object.assign({pascalCase:!1},e);let t=n=>e.pascalCase?n.charAt(0).toUpperCase()+n.slice(1):n;return Array.isArray(r)?r=r.map(n=>n.trim()).filter(n=>n.length).join("-"):r=r.trim(),r.length===0?"":r.length===1?e.pascalCase?r.toUpperCase():r.toLowerCase():(r!==r.toLowerCase()&&(r=Ade(r)),r=r.replace(/^[_.\- ]+/,"").toLowerCase().replace(/[_.\- ]+(\w|$)/g,(n,s)=>s.toUpperCase()).replace(/\d+(\w|$)/g,n=>n.toUpperCase()),t(r))};ev.exports=lH;ev.exports.default=lH});var uH=w((e_e,lde)=>{lde.exports=[{name:"AppVeyor",constant:"APPVEYOR",env:"APPVEYOR",pr:"APPVEYOR_PULL_REQUEST_NUMBER"},{name:"Azure Pipelines",constant:"AZURE_PIPELINES",env:"SYSTEM_TEAMFOUNDATIONCOLLECTIONURI",pr:"SYSTEM_PULLREQUEST_PULLREQUESTID"},{name:"Appcircle",constant:"APPCIRCLE",env:"AC_APPCIRCLE"},{name:"Bamboo",constant:"BAMBOO",env:"bamboo_planKey"},{name:"Bitbucket Pipelines",constant:"BITBUCKET",env:"BITBUCKET_COMMIT",pr:"BITBUCKET_PR_ID"},{name:"Bitrise",constant:"BITRISE",env:"BITRISE_IO",pr:"BITRISE_PULL_REQUEST"},{name:"Buddy",constant:"BUDDY",env:"BUDDY_WORKSPACE_ID",pr:"BUDDY_EXECUTION_PULL_REQUEST_ID"},{name:"Buildkite",constant:"BUILDKITE",env:"BUILDKITE",pr:{env:"BUILDKITE_PULL_REQUEST",ne:"false"}},{name:"CircleCI",constant:"CIRCLE",env:"CIRCLECI",pr:"CIRCLE_PULL_REQUEST"},{name:"Cirrus CI",constant:"CIRRUS",env:"CIRRUS_CI",pr:"CIRRUS_PR"},{name:"AWS CodeBuild",constant:"CODEBUILD",env:"CODEBUILD_BUILD_ARN"},{name:"Codefresh",constant:"CODEFRESH",env:"CF_BUILD_ID",pr:{any:["CF_PULL_REQUEST_NUMBER","CF_PULL_REQUEST_ID"]}},{name:"Codeship",constant:"CODESHIP",env:{CI_NAME:"codeship"}},{name:"Drone",constant:"DRONE",env:"DRONE",pr:{DRONE_BUILD_EVENT:"pull_request"}},{name:"dsari",constant:"DSARI",env:"DSARI"},{name:"GitHub Actions",constant:"GITHUB_ACTIONS",env:"GITHUB_ACTIONS",pr:{GITHUB_EVENT_NAME:"pull_request"}},{name:"GitLab CI",constant:"GITLAB",env:"GITLAB_CI",pr:"CI_MERGE_REQUEST_ID"},{name:"GoCD",constant:"GOCD",env:"GO_PIPELINE_LABEL"},{name:"LayerCI",constant:"LAYERCI",env:"LAYERCI",pr:"LAYERCI_PULL_REQUEST"},{name:"Hudson",constant:"HUDSON",env:"HUDSON_URL"},{name:"Jenkins",constant:"JENKINS",env:["JENKINS_URL","BUILD_ID"],pr:{any:["ghprbPullId","CHANGE_ID"]}},{name:"Magnum CI",constant:"MAGNUM",env:"MAGNUM"},{name:"Netlify CI",constant:"NETLIFY",env:"NETLIFY",pr:{env:"PULL_REQUEST",ne:"false"}},{name:"Nevercode",constant:"NEVERCODE",env:"NEVERCODE",pr:{env:"NEVERCODE_PULL_REQUEST",ne:"false"}},{name:"Render",constant:"RENDER",env:"RENDER",pr:{IS_PULL_REQUEST:"true"}},{name:"Sail CI",constant:"SAIL",env:"SAILCI",pr:"SAIL_PULL_REQUEST_NUMBER"},{name:"Semaphore",constant:"SEMAPHORE",env:"SEMAPHORE",pr:"PULL_REQUEST_NUMBER"},{name:"Screwdriver",constant:"SCREWDRIVER",env:"SCREWDRIVER",pr:{env:"SD_PULL_REQUEST",ne:"false"}},{name:"Shippable",constant:"SHIPPABLE",env:"SHIPPABLE",pr:{IS_PULL_REQUEST:"true"}},{name:"Solano CI",constant:"SOLANO",env:"TDDIUM",pr:"TDDIUM_PR_ID"},{name:"Strider CD",constant:"STRIDER",env:"STRIDER"},{name:"TaskCluster",constant:"TASKCLUSTER",env:["TASK_ID","RUN_ID"]},{name:"TeamCity",constant:"TEAMCITY",env:"TEAMCITY_VERSION"},{name:"Travis CI",constant:"TRAVIS",env:"TRAVIS",pr:{env:"TRAVIS_PULL_REQUEST",ne:"false"}},{name:"Vercel",constant:"VERCEL",env:"NOW_BUILDER"},{name:"Visual Studio App Center",constant:"APPCENTER",env:"APPCENTER_BUILD_ID"}]});var nc=w(Mn=>{"use strict";var fH=uH(),xo=process.env;Object.defineProperty(Mn,"_vendors",{value:fH.map(function(r){return r.constant})});Mn.name=null;Mn.isPR=null;fH.forEach(function(r){let t=(Array.isArray(r.env)?r.env:[r.env]).every(function(i){return gH(i)});if(Mn[r.constant]=t,t)switch(Mn.name=r.name,typeof r.pr){case"string":Mn.isPR=!!xo[r.pr];break;case"object":"env"in r.pr?Mn.isPR=r.pr.env in xo&&xo[r.pr.env]!==r.pr.ne:"any"in r.pr?Mn.isPR=r.pr.any.some(function(i){return!!xo[i]}):Mn.isPR=gH(r.pr);break;default:Mn.isPR=null}});Mn.isCI=!!(xo.CI||xo.CONTINUOUS_INTEGRATION||xo.BUILD_NUMBER||xo.RUN_ID||Mn.name);function gH(r){return typeof r=="string"?!!xo[r]:Object.keys(r).every(function(e){return xo[e]===r[e]})}});var gn={};ut(gn,{KeyRelationship:()=>sc,applyCascade:()=>nd,base64RegExp:()=>mH,colorStringAlphaRegExp:()=>CH,colorStringRegExp:()=>dH,computeKey:()=>kA,getPrintable:()=>Vr,hasExactLength:()=>BH,hasForbiddenKeys:()=>Hde,hasKeyRelationship:()=>av,hasMaxLength:()=>Qde,hasMinLength:()=>Bde,hasMutuallyExclusiveKeys:()=>Gde,hasRequiredKeys:()=>Ude,hasUniqueItems:()=>bde,isArray:()=>pde,isAtLeast:()=>xde,isAtMost:()=>Pde,isBase64:()=>Mde,isBoolean:()=>gde,isDate:()=>hde,isDict:()=>Cde,isEnum:()=>Vi,isHexColor:()=>Ode,isISO8601:()=>Tde,isInExclusiveRange:()=>kde,isInInclusiveRange:()=>Dde,isInstanceOf:()=>Ede,isInteger:()=>Rde,isJSON:()=>Kde,isLiteral:()=>cde,isLowerCase:()=>Fde,isNegative:()=>Sde,isNullable:()=>wde,isNumber:()=>fde,isObject:()=>mde,isOneOf:()=>Ide,isOptional:()=>yde,isPositive:()=>vde,isString:()=>id,isTuple:()=>dde,isUUID4:()=>Lde,isUnknown:()=>wH,isUpperCase:()=>Nde,iso8601RegExp:()=>ov,makeCoercionFn:()=>oc,makeSetter:()=>yH,makeTrait:()=>IH,makeValidator:()=>bt,matchesRegExp:()=>sd,plural:()=>vI,pushError:()=>pt,simpleKeyRegExp:()=>pH,uuid4RegExp:()=>EH});function bt({test:r}){return IH(r)()}function Vr(r){return r===null?"null":r===void 0?"undefined":r===""?"an empty string":JSON.stringify(r)}function kA(r,e){var t,i,n;return typeof e=="number"?`${(t=r==null?void 0:r.p)!==null&&t!==void 0?t:"."}[${e}]`:pH.test(e)?`${(i=r==null?void 0:r.p)!==null&&i!==void 0?i:""}.${e}`:`${(n=r==null?void 0:r.p)!==null&&n!==void 0?n:"."}[${JSON.stringify(e)}]`}function oc(r,e){return t=>{let i=r[e];return r[e]=t,oc(r,e).bind(null,i)}}function yH(r,e){return t=>{r[e]=t}}function vI(r,e,t){return r===1?e:t}function pt({errors:r,p:e}={},t){return r==null||r.push(`${e!=null?e:"."}: ${t}`),!1}function cde(r){return bt({test:(e,t)=>e!==r?pt(t,`Expected a literal (got ${Vr(r)})`):!0})}function Vi(r){let e=Array.isArray(r)?r:Object.values(r),t=new Set(e);return bt({test:(i,n)=>t.has(i)?!0:pt(n,`Expected a valid enumeration value (got ${Vr(i)})`)})}var pH,dH,CH,mH,EH,ov,IH,wH,id,ude,gde,fde,hde,pde,dde,Cde,mde,Ede,Ide,nd,yde,wde,Bde,Qde,BH,bde,Sde,vde,xde,Pde,Dde,kde,Rde,sd,Fde,Nde,Lde,Tde,Ode,Mde,Kde,Ude,Hde,Gde,sc,Yde,av,as=Pge(()=>{pH=/^[a-zA-Z_][a-zA-Z0-9_]*$/,dH=/^#[0-9a-f]{6}$/i,CH=/^#[0-9a-f]{6}([0-9a-f]{2})?$/i,mH=/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/,EH=/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}$/i,ov=/^(?:[1-9]\d{3}(-?)(?:(?:0[1-9]|1[0-2])\1(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])\1(?:29|30)|(?:0[13578]|1[02])(?:\1)31|00[1-9]|0[1-9]\d|[12]\d{2}|3(?:[0-5]\d|6[0-5]))|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)(?:(-?)02(?:\2)29|-?366))T(?:[01]\d|2[0-3])(:?)[0-5]\d(?:\3[0-5]\d)?(?:Z|[+-][01]\d(?:\3[0-5]\d)?)$/,IH=r=>()=>r;wH=()=>bt({test:(r,e)=>!0});id=()=>bt({test:(r,e)=>typeof r!="string"?pt(e,`Expected a string (got ${Vr(r)})`):!0});ude=new Map([["true",!0],["True",!0],["1",!0],[1,!0],["false",!1],["False",!1],["0",!1],[0,!1]]),gde=()=>bt({test:(r,e)=>{var t;if(typeof r!="boolean"){if(typeof(e==null?void 0:e.coercions)<"u"){if(typeof(e==null?void 0:e.coercion)>"u")return pt(e,"Unbound coercion result");let i=ude.get(r);if(typeof i<"u")return e.coercions.push([(t=e.p)!==null&&t!==void 0?t:".",e.coercion.bind(null,i)]),!0}return pt(e,`Expected a boolean (got ${Vr(r)})`)}return!0}}),fde=()=>bt({test:(r,e)=>{var t;if(typeof r!="number"){if(typeof(e==null?void 0:e.coercions)<"u"){if(typeof(e==null?void 0:e.coercion)>"u")return pt(e,"Unbound coercion result");let i;if(typeof r=="string"){let n;try{n=JSON.parse(r)}catch{}if(typeof n=="number")if(JSON.stringify(n)===r)i=n;else return pt(e,`Received a number that can't be safely represented by the runtime (${r})`)}if(typeof i<"u")return e.coercions.push([(t=e.p)!==null&&t!==void 0?t:".",e.coercion.bind(null,i)]),!0}return pt(e,`Expected a number (got ${Vr(r)})`)}return!0}}),hde=()=>bt({test:(r,e)=>{var t;if(!(r instanceof Date)){if(typeof(e==null?void 0:e.coercions)<"u"){if(typeof(e==null?void 0:e.coercion)>"u")return pt(e,"Unbound coercion result");let i;if(typeof r=="string"&&ov.test(r))i=new Date(r);else{let n;if(typeof r=="string"){let s;try{s=JSON.parse(r)}catch{}typeof s=="number"&&(n=s)}else typeof r=="number"&&(n=r);if(typeof n<"u")if(Number.isSafeInteger(n)||!Number.isSafeInteger(n*1e3))i=new Date(n*1e3);else return pt(e,`Received a timestamp that can't be safely represented by the runtime (${r})`)}if(typeof i<"u")return e.coercions.push([(t=e.p)!==null&&t!==void 0?t:".",e.coercion.bind(null,i)]),!0}return pt(e,`Expected a date (got ${Vr(r)})`)}return!0}}),pde=(r,{delimiter:e}={})=>bt({test:(t,i)=>{var n;if(typeof t=="string"&&typeof e<"u"&&typeof(i==null?void 0:i.coercions)<"u"){if(typeof(i==null?void 0:i.coercion)>"u")return pt(i,"Unbound coercion result");t=t.split(e),i.coercions.push([(n=i.p)!==null&&n!==void 0?n:".",i.coercion.bind(null,t)])}if(!Array.isArray(t))return pt(i,`Expected an array (got ${Vr(t)})`);let s=!0;for(let o=0,a=t.length;o{let t=BH(r.length);return bt({test:(i,n)=>{var s;if(typeof i=="string"&&typeof e<"u"&&typeof(n==null?void 0:n.coercions)<"u"){if(typeof(n==null?void 0:n.coercion)>"u")return pt(n,"Unbound coercion result");i=i.split(e),n.coercions.push([(s=n.p)!==null&&s!==void 0?s:".",n.coercion.bind(null,i)])}if(!Array.isArray(i))return pt(n,`Expected a tuple (got ${Vr(i)})`);let o=t(i,Object.assign({},n));for(let a=0,l=i.length;abt({test:(t,i)=>{if(typeof t!="object"||t===null)return pt(i,`Expected an object (got ${Vr(t)})`);let n=Object.keys(t),s=!0;for(let o=0,a=n.length;o{let t=Object.keys(r);return bt({test:(i,n)=>{if(typeof i!="object"||i===null)return pt(n,`Expected an object (got ${Vr(i)})`);let s=new Set([...t,...Object.keys(i)]),o={},a=!0;for(let l of s){if(l==="constructor"||l==="__proto__")a=pt(Object.assign(Object.assign({},n),{p:kA(n,l)}),"Unsafe property name");else{let c=Object.prototype.hasOwnProperty.call(r,l)?r[l]:void 0,u=Object.prototype.hasOwnProperty.call(i,l)?i[l]:void 0;typeof c<"u"?a=c(u,Object.assign(Object.assign({},n),{p:kA(n,l),coercion:oc(i,l)}))&&a:e===null?a=pt(Object.assign(Object.assign({},n),{p:kA(n,l)}),`Extraneous property (got ${Vr(u)})`):Object.defineProperty(o,l,{enumerable:!0,get:()=>u,set:yH(i,l)})}if(!a&&(n==null?void 0:n.errors)==null)break}return e!==null&&(a||(n==null?void 0:n.errors)!=null)&&(a=e(o,n)&&a),a}})},Ede=r=>bt({test:(e,t)=>e instanceof r?!0:pt(t,`Expected an instance of ${r.name} (got ${Vr(e)})`)}),Ide=(r,{exclusive:e=!1}={})=>bt({test:(t,i)=>{var n,s,o;let a=[],l=typeof(i==null?void 0:i.errors)<"u"?[]:void 0;for(let c=0,u=r.length;c1?pt(i,`Expected to match exactly a single predicate (matched ${a.join(", ")})`):(o=i==null?void 0:i.errors)===null||o===void 0||o.push(...l),!1}}),nd=(r,e)=>bt({test:(t,i)=>{var n,s;let o={value:t},a=typeof(i==null?void 0:i.coercions)<"u"?oc(o,"value"):void 0,l=typeof(i==null?void 0:i.coercions)<"u"?[]:void 0;if(!r(t,Object.assign(Object.assign({},i),{coercion:a,coercions:l})))return!1;let c=[];if(typeof l<"u")for(let[,u]of l)c.push(u());try{if(typeof(i==null?void 0:i.coercions)<"u"){if(o.value!==t){if(typeof(i==null?void 0:i.coercion)>"u")return pt(i,"Unbound coercion result");i.coercions.push([(n=i.p)!==null&&n!==void 0?n:".",i.coercion.bind(null,o.value)])}(s=i==null?void 0:i.coercions)===null||s===void 0||s.push(...l)}return e.every(u=>u(o.value,i))}finally{for(let u of c)u()}}}),yde=r=>bt({test:(e,t)=>typeof e>"u"?!0:r(e,t)}),wde=r=>bt({test:(e,t)=>e===null?!0:r(e,t)}),Bde=r=>bt({test:(e,t)=>e.length>=r?!0:pt(t,`Expected to have a length of at least ${r} elements (got ${e.length})`)}),Qde=r=>bt({test:(e,t)=>e.length<=r?!0:pt(t,`Expected to have a length of at most ${r} elements (got ${e.length})`)}),BH=r=>bt({test:(e,t)=>e.length!==r?pt(t,`Expected to have a length of exactly ${r} elements (got ${e.length})`):!0}),bde=({map:r}={})=>bt({test:(e,t)=>{let i=new Set,n=new Set;for(let s=0,o=e.length;sbt({test:(r,e)=>r<=0?!0:pt(e,`Expected to be negative (got ${r})`)}),vde=()=>bt({test:(r,e)=>r>=0?!0:pt(e,`Expected to be positive (got ${r})`)}),xde=r=>bt({test:(e,t)=>e>=r?!0:pt(t,`Expected to be at least ${r} (got ${e})`)}),Pde=r=>bt({test:(e,t)=>e<=r?!0:pt(t,`Expected to be at most ${r} (got ${e})`)}),Dde=(r,e)=>bt({test:(t,i)=>t>=r&&t<=e?!0:pt(i,`Expected to be in the [${r}; ${e}] range (got ${t})`)}),kde=(r,e)=>bt({test:(t,i)=>t>=r&&tbt({test:(e,t)=>e!==Math.round(e)?pt(t,`Expected to be an integer (got ${e})`):Number.isSafeInteger(e)?!0:pt(t,`Expected to be a safe integer (got ${e})`)}),sd=r=>bt({test:(e,t)=>r.test(e)?!0:pt(t,`Expected to match the pattern ${r.toString()} (got ${Vr(e)})`)}),Fde=()=>bt({test:(r,e)=>r!==r.toLowerCase()?pt(e,`Expected to be all-lowercase (got ${r})`):!0}),Nde=()=>bt({test:(r,e)=>r!==r.toUpperCase()?pt(e,`Expected to be all-uppercase (got ${r})`):!0}),Lde=()=>bt({test:(r,e)=>EH.test(r)?!0:pt(e,`Expected to be a valid UUID v4 (got ${Vr(r)})`)}),Tde=()=>bt({test:(r,e)=>ov.test(r)?!1:pt(e,`Expected to be a valid ISO 8601 date string (got ${Vr(r)})`)}),Ode=({alpha:r=!1})=>bt({test:(e,t)=>(r?dH.test(e):CH.test(e))?!0:pt(t,`Expected to be a valid hexadecimal color string (got ${Vr(e)})`)}),Mde=()=>bt({test:(r,e)=>mH.test(r)?!0:pt(e,`Expected to be a valid base 64 string (got ${Vr(r)})`)}),Kde=(r=wH())=>bt({test:(e,t)=>{let i;try{i=JSON.parse(e)}catch{return pt(t,`Expected to be a valid JSON string (got ${Vr(e)})`)}return r(i,t)}}),Ude=r=>{let e=new Set(r);return bt({test:(t,i)=>{let n=new Set(Object.keys(t)),s=[];for(let o of e)n.has(o)||s.push(o);return s.length>0?pt(i,`Missing required ${vI(s.length,"property","properties")} ${s.map(o=>`"${o}"`).join(", ")}`):!0}})},Hde=r=>{let e=new Set(r);return bt({test:(t,i)=>{let n=new Set(Object.keys(t)),s=[];for(let o of e)n.has(o)&&s.push(o);return s.length>0?pt(i,`Forbidden ${vI(s.length,"property","properties")} ${s.map(o=>`"${o}"`).join(", ")}`):!0}})},Gde=r=>{let e=new Set(r);return bt({test:(t,i)=>{let n=new Set(Object.keys(t)),s=[];for(let o of e)n.has(o)&&s.push(o);return s.length>1?pt(i,`Mutually exclusive properties ${s.map(o=>`"${o}"`).join(", ")}`):!0}})};(function(r){r.Forbids="Forbids",r.Requires="Requires"})(sc||(sc={}));Yde={[sc.Forbids]:{expect:!1,message:"forbids using"},[sc.Requires]:{expect:!0,message:"requires using"}},av=(r,e,t,{ignore:i=[]}={})=>{let n=new Set(i),s=new Set(t),o=Yde[e];return bt({test:(a,l)=>{let c=new Set(Object.keys(a));if(!c.has(r)||n.has(a[r]))return!0;let u=[];for(let g of s)(c.has(g)&&!n.has(a[g]))!==o.expect&&u.push(g);return u.length>=1?pt(l,`Property "${r}" ${o.message} ${vI(u.length,"property","properties")} ${u.map(g=>`"${g}"`).join(", ")}`):!0}})}});var UH=w((e$e,KH)=>{"use strict";KH.exports=(r,...e)=>new Promise(t=>{t(r(...e))})});var Yg=w((t$e,pv)=>{"use strict";var oCe=UH(),HH=r=>{if(r<1)throw new TypeError("Expected `concurrency` to be a number from 1 and up");let e=[],t=0,i=()=>{t--,e.length>0&&e.shift()()},n=(a,l,...c)=>{t++;let u=oCe(a,...c);l(u),u.then(i,i)},s=(a,l,...c)=>{tnew Promise(c=>s(a,c,...l));return Object.defineProperties(o,{activeCount:{get:()=>t},pendingCount:{get:()=>e.length}}),o};pv.exports=HH;pv.exports.default=HH});var cd=w((i$e,GH)=>{var aCe="2.0.0",ACe=Number.MAX_SAFE_INTEGER||9007199254740991,lCe=16;GH.exports={SEMVER_SPEC_VERSION:aCe,MAX_LENGTH:256,MAX_SAFE_INTEGER:ACe,MAX_SAFE_COMPONENT_LENGTH:lCe}});var ud=w((n$e,YH)=>{var cCe=typeof process=="object"&&process.env&&process.env.NODE_DEBUG&&/\bsemver\b/i.test(process.env.NODE_DEBUG)?(...r)=>console.error("SEMVER",...r):()=>{};YH.exports=cCe});var ac=w((FA,jH)=>{var{MAX_SAFE_COMPONENT_LENGTH:dv}=cd(),uCe=ud();FA=jH.exports={};var gCe=FA.re=[],et=FA.src=[],tt=FA.t={},fCe=0,St=(r,e,t)=>{let i=fCe++;uCe(i,e),tt[r]=i,et[i]=e,gCe[i]=new RegExp(e,t?"g":void 0)};St("NUMERICIDENTIFIER","0|[1-9]\\d*");St("NUMERICIDENTIFIERLOOSE","[0-9]+");St("NONNUMERICIDENTIFIER","\\d*[a-zA-Z-][a-zA-Z0-9-]*");St("MAINVERSION",`(${et[tt.NUMERICIDENTIFIER]})\\.(${et[tt.NUMERICIDENTIFIER]})\\.(${et[tt.NUMERICIDENTIFIER]})`);St("MAINVERSIONLOOSE",`(${et[tt.NUMERICIDENTIFIERLOOSE]})\\.(${et[tt.NUMERICIDENTIFIERLOOSE]})\\.(${et[tt.NUMERICIDENTIFIERLOOSE]})`);St("PRERELEASEIDENTIFIER",`(?:${et[tt.NUMERICIDENTIFIER]}|${et[tt.NONNUMERICIDENTIFIER]})`);St("PRERELEASEIDENTIFIERLOOSE",`(?:${et[tt.NUMERICIDENTIFIERLOOSE]}|${et[tt.NONNUMERICIDENTIFIER]})`);St("PRERELEASE",`(?:-(${et[tt.PRERELEASEIDENTIFIER]}(?:\\.${et[tt.PRERELEASEIDENTIFIER]})*))`);St("PRERELEASELOOSE",`(?:-?(${et[tt.PRERELEASEIDENTIFIERLOOSE]}(?:\\.${et[tt.PRERELEASEIDENTIFIERLOOSE]})*))`);St("BUILDIDENTIFIER","[0-9A-Za-z-]+");St("BUILD",`(?:\\+(${et[tt.BUILDIDENTIFIER]}(?:\\.${et[tt.BUILDIDENTIFIER]})*))`);St("FULLPLAIN",`v?${et[tt.MAINVERSION]}${et[tt.PRERELEASE]}?${et[tt.BUILD]}?`);St("FULL",`^${et[tt.FULLPLAIN]}$`);St("LOOSEPLAIN",`[v=\\s]*${et[tt.MAINVERSIONLOOSE]}${et[tt.PRERELEASELOOSE]}?${et[tt.BUILD]}?`);St("LOOSE",`^${et[tt.LOOSEPLAIN]}$`);St("GTLT","((?:<|>)?=?)");St("XRANGEIDENTIFIERLOOSE",`${et[tt.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`);St("XRANGEIDENTIFIER",`${et[tt.NUMERICIDENTIFIER]}|x|X|\\*`);St("XRANGEPLAIN",`[v=\\s]*(${et[tt.XRANGEIDENTIFIER]})(?:\\.(${et[tt.XRANGEIDENTIFIER]})(?:\\.(${et[tt.XRANGEIDENTIFIER]})(?:${et[tt.PRERELEASE]})?${et[tt.BUILD]}?)?)?`);St("XRANGEPLAINLOOSE",`[v=\\s]*(${et[tt.XRANGEIDENTIFIERLOOSE]})(?:\\.(${et[tt.XRANGEIDENTIFIERLOOSE]})(?:\\.(${et[tt.XRANGEIDENTIFIERLOOSE]})(?:${et[tt.PRERELEASELOOSE]})?${et[tt.BUILD]}?)?)?`);St("XRANGE",`^${et[tt.GTLT]}\\s*${et[tt.XRANGEPLAIN]}$`);St("XRANGELOOSE",`^${et[tt.GTLT]}\\s*${et[tt.XRANGEPLAINLOOSE]}$`);St("COERCE",`(^|[^\\d])(\\d{1,${dv}})(?:\\.(\\d{1,${dv}}))?(?:\\.(\\d{1,${dv}}))?(?:$|[^\\d])`);St("COERCERTL",et[tt.COERCE],!0);St("LONETILDE","(?:~>?)");St("TILDETRIM",`(\\s*)${et[tt.LONETILDE]}\\s+`,!0);FA.tildeTrimReplace="$1~";St("TILDE",`^${et[tt.LONETILDE]}${et[tt.XRANGEPLAIN]}$`);St("TILDELOOSE",`^${et[tt.LONETILDE]}${et[tt.XRANGEPLAINLOOSE]}$`);St("LONECARET","(?:\\^)");St("CARETTRIM",`(\\s*)${et[tt.LONECARET]}\\s+`,!0);FA.caretTrimReplace="$1^";St("CARET",`^${et[tt.LONECARET]}${et[tt.XRANGEPLAIN]}$`);St("CARETLOOSE",`^${et[tt.LONECARET]}${et[tt.XRANGEPLAINLOOSE]}$`);St("COMPARATORLOOSE",`^${et[tt.GTLT]}\\s*(${et[tt.LOOSEPLAIN]})$|^$`);St("COMPARATOR",`^${et[tt.GTLT]}\\s*(${et[tt.FULLPLAIN]})$|^$`);St("COMPARATORTRIM",`(\\s*)${et[tt.GTLT]}\\s*(${et[tt.LOOSEPLAIN]}|${et[tt.XRANGEPLAIN]})`,!0);FA.comparatorTrimReplace="$1$2$3";St("HYPHENRANGE",`^\\s*(${et[tt.XRANGEPLAIN]})\\s+-\\s+(${et[tt.XRANGEPLAIN]})\\s*$`);St("HYPHENRANGELOOSE",`^\\s*(${et[tt.XRANGEPLAINLOOSE]})\\s+-\\s+(${et[tt.XRANGEPLAINLOOSE]})\\s*$`);St("STAR","(<|>)?=?\\s*\\*");St("GTE0","^\\s*>=\\s*0.0.0\\s*$");St("GTE0PRE","^\\s*>=\\s*0.0.0-0\\s*$")});var gd=w((s$e,qH)=>{var hCe=["includePrerelease","loose","rtl"],pCe=r=>r?typeof r!="object"?{loose:!0}:hCe.filter(e=>r[e]).reduce((e,t)=>(e[t]=!0,e),{}):{};qH.exports=pCe});var FI=w((o$e,zH)=>{var JH=/^[0-9]+$/,WH=(r,e)=>{let t=JH.test(r),i=JH.test(e);return t&&i&&(r=+r,e=+e),r===e?0:t&&!i?-1:i&&!t?1:rWH(e,r);zH.exports={compareIdentifiers:WH,rcompareIdentifiers:dCe}});var Ti=w((a$e,_H)=>{var NI=ud(),{MAX_LENGTH:VH,MAX_SAFE_INTEGER:LI}=cd(),{re:XH,t:ZH}=ac(),CCe=gd(),{compareIdentifiers:fd}=FI(),Hn=class{constructor(e,t){if(t=CCe(t),e instanceof Hn){if(e.loose===!!t.loose&&e.includePrerelease===!!t.includePrerelease)return e;e=e.version}else if(typeof e!="string")throw new TypeError(`Invalid Version: ${e}`);if(e.length>VH)throw new TypeError(`version is longer than ${VH} characters`);NI("SemVer",e,t),this.options=t,this.loose=!!t.loose,this.includePrerelease=!!t.includePrerelease;let i=e.trim().match(t.loose?XH[ZH.LOOSE]:XH[ZH.FULL]);if(!i)throw new TypeError(`Invalid Version: ${e}`);if(this.raw=e,this.major=+i[1],this.minor=+i[2],this.patch=+i[3],this.major>LI||this.major<0)throw new TypeError("Invalid major version");if(this.minor>LI||this.minor<0)throw new TypeError("Invalid minor version");if(this.patch>LI||this.patch<0)throw new TypeError("Invalid patch version");i[4]?this.prerelease=i[4].split(".").map(n=>{if(/^[0-9]+$/.test(n)){let s=+n;if(s>=0&&s=0;)typeof this.prerelease[i]=="number"&&(this.prerelease[i]++,i=-2);i===-1&&this.prerelease.push(0)}t&&(this.prerelease[0]===t?isNaN(this.prerelease[1])&&(this.prerelease=[t,0]):this.prerelease=[t,0]);break;default:throw new Error(`invalid increment argument: ${e}`)}return this.format(),this.raw=this.version,this}};_H.exports=Hn});var Ac=w((A$e,rG)=>{var{MAX_LENGTH:mCe}=cd(),{re:$H,t:eG}=ac(),tG=Ti(),ECe=gd(),ICe=(r,e)=>{if(e=ECe(e),r instanceof tG)return r;if(typeof r!="string"||r.length>mCe||!(e.loose?$H[eG.LOOSE]:$H[eG.FULL]).test(r))return null;try{return new tG(r,e)}catch{return null}};rG.exports=ICe});var nG=w((l$e,iG)=>{var yCe=Ac(),wCe=(r,e)=>{let t=yCe(r,e);return t?t.version:null};iG.exports=wCe});var oG=w((c$e,sG)=>{var BCe=Ac(),QCe=(r,e)=>{let t=BCe(r.trim().replace(/^[=v]+/,""),e);return t?t.version:null};sG.exports=QCe});var AG=w((u$e,aG)=>{var bCe=Ti(),SCe=(r,e,t,i)=>{typeof t=="string"&&(i=t,t=void 0);try{return new bCe(r,t).inc(e,i).version}catch{return null}};aG.exports=SCe});var As=w((g$e,cG)=>{var lG=Ti(),vCe=(r,e,t)=>new lG(r,t).compare(new lG(e,t));cG.exports=vCe});var TI=w((f$e,uG)=>{var xCe=As(),PCe=(r,e,t)=>xCe(r,e,t)===0;uG.exports=PCe});var hG=w((h$e,fG)=>{var gG=Ac(),DCe=TI(),kCe=(r,e)=>{if(DCe(r,e))return null;{let t=gG(r),i=gG(e),n=t.prerelease.length||i.prerelease.length,s=n?"pre":"",o=n?"prerelease":"";for(let a in t)if((a==="major"||a==="minor"||a==="patch")&&t[a]!==i[a])return s+a;return o}};fG.exports=kCe});var dG=w((p$e,pG)=>{var RCe=Ti(),FCe=(r,e)=>new RCe(r,e).major;pG.exports=FCe});var mG=w((d$e,CG)=>{var NCe=Ti(),LCe=(r,e)=>new NCe(r,e).minor;CG.exports=LCe});var IG=w((C$e,EG)=>{var TCe=Ti(),OCe=(r,e)=>new TCe(r,e).patch;EG.exports=OCe});var wG=w((m$e,yG)=>{var MCe=Ac(),KCe=(r,e)=>{let t=MCe(r,e);return t&&t.prerelease.length?t.prerelease:null};yG.exports=KCe});var QG=w((E$e,BG)=>{var UCe=As(),HCe=(r,e,t)=>UCe(e,r,t);BG.exports=HCe});var SG=w((I$e,bG)=>{var GCe=As(),YCe=(r,e)=>GCe(r,e,!0);bG.exports=YCe});var OI=w((y$e,xG)=>{var vG=Ti(),jCe=(r,e,t)=>{let i=new vG(r,t),n=new vG(e,t);return i.compare(n)||i.compareBuild(n)};xG.exports=jCe});var DG=w((w$e,PG)=>{var qCe=OI(),JCe=(r,e)=>r.sort((t,i)=>qCe(t,i,e));PG.exports=JCe});var RG=w((B$e,kG)=>{var WCe=OI(),zCe=(r,e)=>r.sort((t,i)=>WCe(i,t,e));kG.exports=zCe});var hd=w((Q$e,FG)=>{var VCe=As(),XCe=(r,e,t)=>VCe(r,e,t)>0;FG.exports=XCe});var MI=w((b$e,NG)=>{var ZCe=As(),_Ce=(r,e,t)=>ZCe(r,e,t)<0;NG.exports=_Ce});var Cv=w((S$e,LG)=>{var $Ce=As(),eme=(r,e,t)=>$Ce(r,e,t)!==0;LG.exports=eme});var KI=w((v$e,TG)=>{var tme=As(),rme=(r,e,t)=>tme(r,e,t)>=0;TG.exports=rme});var UI=w((x$e,OG)=>{var ime=As(),nme=(r,e,t)=>ime(r,e,t)<=0;OG.exports=nme});var mv=w((P$e,MG)=>{var sme=TI(),ome=Cv(),ame=hd(),Ame=KI(),lme=MI(),cme=UI(),ume=(r,e,t,i)=>{switch(e){case"===":return typeof r=="object"&&(r=r.version),typeof t=="object"&&(t=t.version),r===t;case"!==":return typeof r=="object"&&(r=r.version),typeof t=="object"&&(t=t.version),r!==t;case"":case"=":case"==":return sme(r,t,i);case"!=":return ome(r,t,i);case">":return ame(r,t,i);case">=":return Ame(r,t,i);case"<":return lme(r,t,i);case"<=":return cme(r,t,i);default:throw new TypeError(`Invalid operator: ${e}`)}};MG.exports=ume});var UG=w((D$e,KG)=>{var gme=Ti(),fme=Ac(),{re:HI,t:GI}=ac(),hme=(r,e)=>{if(r instanceof gme)return r;if(typeof r=="number"&&(r=String(r)),typeof r!="string")return null;e=e||{};let t=null;if(!e.rtl)t=r.match(HI[GI.COERCE]);else{let i;for(;(i=HI[GI.COERCERTL].exec(r))&&(!t||t.index+t[0].length!==r.length);)(!t||i.index+i[0].length!==t.index+t[0].length)&&(t=i),HI[GI.COERCERTL].lastIndex=i.index+i[1].length+i[2].length;HI[GI.COERCERTL].lastIndex=-1}return t===null?null:fme(`${t[2]}.${t[3]||"0"}.${t[4]||"0"}`,e)};KG.exports=hme});var GG=w((k$e,HG)=>{"use strict";HG.exports=function(r){r.prototype[Symbol.iterator]=function*(){for(let e=this.head;e;e=e.next)yield e.value}}});var YI=w((R$e,YG)=>{"use strict";YG.exports=Ht;Ht.Node=lc;Ht.create=Ht;function Ht(r){var e=this;if(e instanceof Ht||(e=new Ht),e.tail=null,e.head=null,e.length=0,r&&typeof r.forEach=="function")r.forEach(function(n){e.push(n)});else if(arguments.length>0)for(var t=0,i=arguments.length;t1)t=e;else if(this.head)i=this.head.next,t=this.head.value;else throw new TypeError("Reduce of empty list with no initial value");for(var n=0;i!==null;n++)t=r(t,i.value,n),i=i.next;return t};Ht.prototype.reduceReverse=function(r,e){var t,i=this.tail;if(arguments.length>1)t=e;else if(this.tail)i=this.tail.prev,t=this.tail.value;else throw new TypeError("Reduce of empty list with no initial value");for(var n=this.length-1;i!==null;n--)t=r(t,i.value,n),i=i.prev;return t};Ht.prototype.toArray=function(){for(var r=new Array(this.length),e=0,t=this.head;t!==null;e++)r[e]=t.value,t=t.next;return r};Ht.prototype.toArrayReverse=function(){for(var r=new Array(this.length),e=0,t=this.tail;t!==null;e++)r[e]=t.value,t=t.prev;return r};Ht.prototype.slice=function(r,e){e=e||this.length,e<0&&(e+=this.length),r=r||0,r<0&&(r+=this.length);var t=new Ht;if(ethis.length&&(e=this.length);for(var i=0,n=this.head;n!==null&&ithis.length&&(e=this.length);for(var i=this.length,n=this.tail;n!==null&&i>e;i--)n=n.prev;for(;n!==null&&i>r;i--,n=n.prev)t.push(n.value);return t};Ht.prototype.splice=function(r,e,...t){r>this.length&&(r=this.length-1),r<0&&(r=this.length+r);for(var i=0,n=this.head;n!==null&&i{"use strict";var mme=YI(),cc=Symbol("max"),ba=Symbol("length"),jg=Symbol("lengthCalculator"),dd=Symbol("allowStale"),uc=Symbol("maxAge"),Qa=Symbol("dispose"),jG=Symbol("noDisposeOnSet"),di=Symbol("lruList"),Vs=Symbol("cache"),JG=Symbol("updateAgeOnGet"),Ev=()=>1,yv=class{constructor(e){if(typeof e=="number"&&(e={max:e}),e||(e={}),e.max&&(typeof e.max!="number"||e.max<0))throw new TypeError("max must be a non-negative number");let t=this[cc]=e.max||1/0,i=e.length||Ev;if(this[jg]=typeof i!="function"?Ev:i,this[dd]=e.stale||!1,e.maxAge&&typeof e.maxAge!="number")throw new TypeError("maxAge must be a number");this[uc]=e.maxAge||0,this[Qa]=e.dispose,this[jG]=e.noDisposeOnSet||!1,this[JG]=e.updateAgeOnGet||!1,this.reset()}set max(e){if(typeof e!="number"||e<0)throw new TypeError("max must be a non-negative number");this[cc]=e||1/0,pd(this)}get max(){return this[cc]}set allowStale(e){this[dd]=!!e}get allowStale(){return this[dd]}set maxAge(e){if(typeof e!="number")throw new TypeError("maxAge must be a non-negative number");this[uc]=e,pd(this)}get maxAge(){return this[uc]}set lengthCalculator(e){typeof e!="function"&&(e=Ev),e!==this[jg]&&(this[jg]=e,this[ba]=0,this[di].forEach(t=>{t.length=this[jg](t.value,t.key),this[ba]+=t.length})),pd(this)}get lengthCalculator(){return this[jg]}get length(){return this[ba]}get itemCount(){return this[di].length}rforEach(e,t){t=t||this;for(let i=this[di].tail;i!==null;){let n=i.prev;qG(this,e,i,t),i=n}}forEach(e,t){t=t||this;for(let i=this[di].head;i!==null;){let n=i.next;qG(this,e,i,t),i=n}}keys(){return this[di].toArray().map(e=>e.key)}values(){return this[di].toArray().map(e=>e.value)}reset(){this[Qa]&&this[di]&&this[di].length&&this[di].forEach(e=>this[Qa](e.key,e.value)),this[Vs]=new Map,this[di]=new mme,this[ba]=0}dump(){return this[di].map(e=>jI(this,e)?!1:{k:e.key,v:e.value,e:e.now+(e.maxAge||0)}).toArray().filter(e=>e)}dumpLru(){return this[di]}set(e,t,i){if(i=i||this[uc],i&&typeof i!="number")throw new TypeError("maxAge must be a number");let n=i?Date.now():0,s=this[jg](t,e);if(this[Vs].has(e)){if(s>this[cc])return qg(this,this[Vs].get(e)),!1;let l=this[Vs].get(e).value;return this[Qa]&&(this[jG]||this[Qa](e,l.value)),l.now=n,l.maxAge=i,l.value=t,this[ba]+=s-l.length,l.length=s,this.get(e),pd(this),!0}let o=new wv(e,t,s,n,i);return o.length>this[cc]?(this[Qa]&&this[Qa](e,t),!1):(this[ba]+=o.length,this[di].unshift(o),this[Vs].set(e,this[di].head),pd(this),!0)}has(e){if(!this[Vs].has(e))return!1;let t=this[Vs].get(e).value;return!jI(this,t)}get(e){return Iv(this,e,!0)}peek(e){return Iv(this,e,!1)}pop(){let e=this[di].tail;return e?(qg(this,e),e.value):null}del(e){qg(this,this[Vs].get(e))}load(e){this.reset();let t=Date.now();for(let i=e.length-1;i>=0;i--){let n=e[i],s=n.e||0;if(s===0)this.set(n.k,n.v);else{let o=s-t;o>0&&this.set(n.k,n.v,o)}}}prune(){this[Vs].forEach((e,t)=>Iv(this,t,!1))}},Iv=(r,e,t)=>{let i=r[Vs].get(e);if(i){let n=i.value;if(jI(r,n)){if(qg(r,i),!r[dd])return}else t&&(r[JG]&&(i.value.now=Date.now()),r[di].unshiftNode(i));return n.value}},jI=(r,e)=>{if(!e||!e.maxAge&&!r[uc])return!1;let t=Date.now()-e.now;return e.maxAge?t>e.maxAge:r[uc]&&t>r[uc]},pd=r=>{if(r[ba]>r[cc])for(let e=r[di].tail;r[ba]>r[cc]&&e!==null;){let t=e.prev;qg(r,e),e=t}},qg=(r,e)=>{if(e){let t=e.value;r[Qa]&&r[Qa](t.key,t.value),r[ba]-=t.length,r[Vs].delete(t.key),r[di].removeNode(e)}},wv=class{constructor(e,t,i,n,s){this.key=e,this.value=t,this.length=i,this.now=n,this.maxAge=s||0}},qG=(r,e,t,i)=>{let n=t.value;jI(r,n)&&(qg(r,t),r[dd]||(n=void 0)),n&&e.call(i,n.value,n.key,r)};WG.exports=yv});var ls=w((N$e,_G)=>{var gc=class{constructor(e,t){if(t=Ime(t),e instanceof gc)return e.loose===!!t.loose&&e.includePrerelease===!!t.includePrerelease?e:new gc(e.raw,t);if(e instanceof Bv)return this.raw=e.value,this.set=[[e]],this.format(),this;if(this.options=t,this.loose=!!t.loose,this.includePrerelease=!!t.includePrerelease,this.raw=e,this.set=e.split(/\s*\|\|\s*/).map(i=>this.parseRange(i.trim())).filter(i=>i.length),!this.set.length)throw new TypeError(`Invalid SemVer Range: ${e}`);if(this.set.length>1){let i=this.set[0];if(this.set=this.set.filter(n=>!XG(n[0])),this.set.length===0)this.set=[i];else if(this.set.length>1){for(let n of this.set)if(n.length===1&&bme(n[0])){this.set=[n];break}}}this.format()}format(){return this.range=this.set.map(e=>e.join(" ").trim()).join("||").trim(),this.range}toString(){return this.range}parseRange(e){e=e.trim();let i=`parseRange:${Object.keys(this.options).join(",")}:${e}`,n=VG.get(i);if(n)return n;let s=this.options.loose,o=s?Oi[Qi.HYPHENRANGELOOSE]:Oi[Qi.HYPHENRANGE];e=e.replace(o,Lme(this.options.includePrerelease)),Gr("hyphen replace",e),e=e.replace(Oi[Qi.COMPARATORTRIM],wme),Gr("comparator trim",e,Oi[Qi.COMPARATORTRIM]),e=e.replace(Oi[Qi.TILDETRIM],Bme),e=e.replace(Oi[Qi.CARETTRIM],Qme),e=e.split(/\s+/).join(" ");let a=s?Oi[Qi.COMPARATORLOOSE]:Oi[Qi.COMPARATOR],l=e.split(" ").map(f=>Sme(f,this.options)).join(" ").split(/\s+/).map(f=>Nme(f,this.options)).filter(this.options.loose?f=>!!f.match(a):()=>!0).map(f=>new Bv(f,this.options)),c=l.length,u=new Map;for(let f of l){if(XG(f))return[f];u.set(f.value,f)}u.size>1&&u.has("")&&u.delete("");let g=[...u.values()];return VG.set(i,g),g}intersects(e,t){if(!(e instanceof gc))throw new TypeError("a Range is required");return this.set.some(i=>ZG(i,t)&&e.set.some(n=>ZG(n,t)&&i.every(s=>n.every(o=>s.intersects(o,t)))))}test(e){if(!e)return!1;if(typeof e=="string")try{e=new yme(e,this.options)}catch{return!1}for(let t=0;tr.value==="<0.0.0-0",bme=r=>r.value==="",ZG=(r,e)=>{let t=!0,i=r.slice(),n=i.pop();for(;t&&i.length;)t=i.every(s=>n.intersects(s,e)),n=i.pop();return t},Sme=(r,e)=>(Gr("comp",r,e),r=Pme(r,e),Gr("caret",r),r=vme(r,e),Gr("tildes",r),r=kme(r,e),Gr("xrange",r),r=Fme(r,e),Gr("stars",r),r),Zi=r=>!r||r.toLowerCase()==="x"||r==="*",vme=(r,e)=>r.trim().split(/\s+/).map(t=>xme(t,e)).join(" "),xme=(r,e)=>{let t=e.loose?Oi[Qi.TILDELOOSE]:Oi[Qi.TILDE];return r.replace(t,(i,n,s,o,a)=>{Gr("tilde",r,i,n,s,o,a);let l;return Zi(n)?l="":Zi(s)?l=`>=${n}.0.0 <${+n+1}.0.0-0`:Zi(o)?l=`>=${n}.${s}.0 <${n}.${+s+1}.0-0`:a?(Gr("replaceTilde pr",a),l=`>=${n}.${s}.${o}-${a} <${n}.${+s+1}.0-0`):l=`>=${n}.${s}.${o} <${n}.${+s+1}.0-0`,Gr("tilde return",l),l})},Pme=(r,e)=>r.trim().split(/\s+/).map(t=>Dme(t,e)).join(" "),Dme=(r,e)=>{Gr("caret",r,e);let t=e.loose?Oi[Qi.CARETLOOSE]:Oi[Qi.CARET],i=e.includePrerelease?"-0":"";return r.replace(t,(n,s,o,a,l)=>{Gr("caret",r,n,s,o,a,l);let c;return Zi(s)?c="":Zi(o)?c=`>=${s}.0.0${i} <${+s+1}.0.0-0`:Zi(a)?s==="0"?c=`>=${s}.${o}.0${i} <${s}.${+o+1}.0-0`:c=`>=${s}.${o}.0${i} <${+s+1}.0.0-0`:l?(Gr("replaceCaret pr",l),s==="0"?o==="0"?c=`>=${s}.${o}.${a}-${l} <${s}.${o}.${+a+1}-0`:c=`>=${s}.${o}.${a}-${l} <${s}.${+o+1}.0-0`:c=`>=${s}.${o}.${a}-${l} <${+s+1}.0.0-0`):(Gr("no pr"),s==="0"?o==="0"?c=`>=${s}.${o}.${a}${i} <${s}.${o}.${+a+1}-0`:c=`>=${s}.${o}.${a}${i} <${s}.${+o+1}.0-0`:c=`>=${s}.${o}.${a} <${+s+1}.0.0-0`),Gr("caret return",c),c})},kme=(r,e)=>(Gr("replaceXRanges",r,e),r.split(/\s+/).map(t=>Rme(t,e)).join(" ")),Rme=(r,e)=>{r=r.trim();let t=e.loose?Oi[Qi.XRANGELOOSE]:Oi[Qi.XRANGE];return r.replace(t,(i,n,s,o,a,l)=>{Gr("xRange",r,i,n,s,o,a,l);let c=Zi(s),u=c||Zi(o),g=u||Zi(a),f=g;return n==="="&&f&&(n=""),l=e.includePrerelease?"-0":"",c?n===">"||n==="<"?i="<0.0.0-0":i="*":n&&f?(u&&(o=0),a=0,n===">"?(n=">=",u?(s=+s+1,o=0,a=0):(o=+o+1,a=0)):n==="<="&&(n="<",u?s=+s+1:o=+o+1),n==="<"&&(l="-0"),i=`${n+s}.${o}.${a}${l}`):u?i=`>=${s}.0.0${l} <${+s+1}.0.0-0`:g&&(i=`>=${s}.${o}.0${l} <${s}.${+o+1}.0-0`),Gr("xRange return",i),i})},Fme=(r,e)=>(Gr("replaceStars",r,e),r.trim().replace(Oi[Qi.STAR],"")),Nme=(r,e)=>(Gr("replaceGTE0",r,e),r.trim().replace(Oi[e.includePrerelease?Qi.GTE0PRE:Qi.GTE0],"")),Lme=r=>(e,t,i,n,s,o,a,l,c,u,g,f,h)=>(Zi(i)?t="":Zi(n)?t=`>=${i}.0.0${r?"-0":""}`:Zi(s)?t=`>=${i}.${n}.0${r?"-0":""}`:o?t=`>=${t}`:t=`>=${t}${r?"-0":""}`,Zi(c)?l="":Zi(u)?l=`<${+c+1}.0.0-0`:Zi(g)?l=`<${c}.${+u+1}.0-0`:f?l=`<=${c}.${u}.${g}-${f}`:r?l=`<${c}.${u}.${+g+1}-0`:l=`<=${l}`,`${t} ${l}`.trim()),Tme=(r,e,t)=>{for(let i=0;i0){let n=r[i].semver;if(n.major===e.major&&n.minor===e.minor&&n.patch===e.patch)return!0}return!1}return!0}});var Cd=w((L$e,iY)=>{var md=Symbol("SemVer ANY"),Jg=class{static get ANY(){return md}constructor(e,t){if(t=Ome(t),e instanceof Jg){if(e.loose===!!t.loose)return e;e=e.value}bv("comparator",e,t),this.options=t,this.loose=!!t.loose,this.parse(e),this.semver===md?this.value="":this.value=this.operator+this.semver.version,bv("comp",this)}parse(e){let t=this.options.loose?$G[eY.COMPARATORLOOSE]:$G[eY.COMPARATOR],i=e.match(t);if(!i)throw new TypeError(`Invalid comparator: ${e}`);this.operator=i[1]!==void 0?i[1]:"",this.operator==="="&&(this.operator=""),i[2]?this.semver=new tY(i[2],this.options.loose):this.semver=md}toString(){return this.value}test(e){if(bv("Comparator.test",e,this.options.loose),this.semver===md||e===md)return!0;if(typeof e=="string")try{e=new tY(e,this.options)}catch{return!1}return Qv(e,this.operator,this.semver,this.options)}intersects(e,t){if(!(e instanceof Jg))throw new TypeError("a Comparator is required");if((!t||typeof t!="object")&&(t={loose:!!t,includePrerelease:!1}),this.operator==="")return this.value===""?!0:new rY(e.value,t).test(this.value);if(e.operator==="")return e.value===""?!0:new rY(this.value,t).test(e.semver);let i=(this.operator===">="||this.operator===">")&&(e.operator===">="||e.operator===">"),n=(this.operator==="<="||this.operator==="<")&&(e.operator==="<="||e.operator==="<"),s=this.semver.version===e.semver.version,o=(this.operator===">="||this.operator==="<=")&&(e.operator===">="||e.operator==="<="),a=Qv(this.semver,"<",e.semver,t)&&(this.operator===">="||this.operator===">")&&(e.operator==="<="||e.operator==="<"),l=Qv(this.semver,">",e.semver,t)&&(this.operator==="<="||this.operator==="<")&&(e.operator===">="||e.operator===">");return i||n||s&&o||a||l}};iY.exports=Jg;var Ome=gd(),{re:$G,t:eY}=ac(),Qv=mv(),bv=ud(),tY=Ti(),rY=ls()});var Ed=w((T$e,nY)=>{var Mme=ls(),Kme=(r,e,t)=>{try{e=new Mme(e,t)}catch{return!1}return e.test(r)};nY.exports=Kme});var oY=w((O$e,sY)=>{var Ume=ls(),Hme=(r,e)=>new Ume(r,e).set.map(t=>t.map(i=>i.value).join(" ").trim().split(" "));sY.exports=Hme});var AY=w((M$e,aY)=>{var Gme=Ti(),Yme=ls(),jme=(r,e,t)=>{let i=null,n=null,s=null;try{s=new Yme(e,t)}catch{return null}return r.forEach(o=>{s.test(o)&&(!i||n.compare(o)===-1)&&(i=o,n=new Gme(i,t))}),i};aY.exports=jme});var cY=w((K$e,lY)=>{var qme=Ti(),Jme=ls(),Wme=(r,e,t)=>{let i=null,n=null,s=null;try{s=new Jme(e,t)}catch{return null}return r.forEach(o=>{s.test(o)&&(!i||n.compare(o)===1)&&(i=o,n=new qme(i,t))}),i};lY.exports=Wme});var fY=w((U$e,gY)=>{var Sv=Ti(),zme=ls(),uY=hd(),Vme=(r,e)=>{r=new zme(r,e);let t=new Sv("0.0.0");if(r.test(t)||(t=new Sv("0.0.0-0"),r.test(t)))return t;t=null;for(let i=0;i{let a=new Sv(o.semver.version);switch(o.operator){case">":a.prerelease.length===0?a.patch++:a.prerelease.push(0),a.raw=a.format();case"":case">=":(!s||uY(a,s))&&(s=a);break;case"<":case"<=":break;default:throw new Error(`Unexpected operation: ${o.operator}`)}}),s&&(!t||uY(t,s))&&(t=s)}return t&&r.test(t)?t:null};gY.exports=Vme});var pY=w((H$e,hY)=>{var Xme=ls(),Zme=(r,e)=>{try{return new Xme(r,e).range||"*"}catch{return null}};hY.exports=Zme});var qI=w((G$e,EY)=>{var _me=Ti(),mY=Cd(),{ANY:$me}=mY,eEe=ls(),tEe=Ed(),dY=hd(),CY=MI(),rEe=UI(),iEe=KI(),nEe=(r,e,t,i)=>{r=new _me(r,i),e=new eEe(e,i);let n,s,o,a,l;switch(t){case">":n=dY,s=rEe,o=CY,a=">",l=">=";break;case"<":n=CY,s=iEe,o=dY,a="<",l="<=";break;default:throw new TypeError('Must provide a hilo val of "<" or ">"')}if(tEe(r,e,i))return!1;for(let c=0;c{h.semver===$me&&(h=new mY(">=0.0.0")),g=g||h,f=f||h,n(h.semver,g.semver,i)?g=h:o(h.semver,f.semver,i)&&(f=h)}),g.operator===a||g.operator===l||(!f.operator||f.operator===a)&&s(r,f.semver))return!1;if(f.operator===l&&o(r,f.semver))return!1}return!0};EY.exports=nEe});var yY=w((Y$e,IY)=>{var sEe=qI(),oEe=(r,e,t)=>sEe(r,e,">",t);IY.exports=oEe});var BY=w((j$e,wY)=>{var aEe=qI(),AEe=(r,e,t)=>aEe(r,e,"<",t);wY.exports=AEe});var SY=w((q$e,bY)=>{var QY=ls(),lEe=(r,e,t)=>(r=new QY(r,t),e=new QY(e,t),r.intersects(e));bY.exports=lEe});var xY=w((J$e,vY)=>{var cEe=Ed(),uEe=As();vY.exports=(r,e,t)=>{let i=[],n=null,s=null,o=r.sort((u,g)=>uEe(u,g,t));for(let u of o)cEe(u,e,t)?(s=u,n||(n=u)):(s&&i.push([n,s]),s=null,n=null);n&&i.push([n,null]);let a=[];for(let[u,g]of i)u===g?a.push(u):!g&&u===o[0]?a.push("*"):g?u===o[0]?a.push(`<=${g}`):a.push(`${u} - ${g}`):a.push(`>=${u}`);let l=a.join(" || "),c=typeof e.raw=="string"?e.raw:String(e);return l.length{var PY=ls(),JI=Cd(),{ANY:vv}=JI,Id=Ed(),xv=As(),gEe=(r,e,t={})=>{if(r===e)return!0;r=new PY(r,t),e=new PY(e,t);let i=!1;e:for(let n of r.set){for(let s of e.set){let o=fEe(n,s,t);if(i=i||o!==null,o)continue e}if(i)return!1}return!0},fEe=(r,e,t)=>{if(r===e)return!0;if(r.length===1&&r[0].semver===vv){if(e.length===1&&e[0].semver===vv)return!0;t.includePrerelease?r=[new JI(">=0.0.0-0")]:r=[new JI(">=0.0.0")]}if(e.length===1&&e[0].semver===vv){if(t.includePrerelease)return!0;e=[new JI(">=0.0.0")]}let i=new Set,n,s;for(let h of r)h.operator===">"||h.operator===">="?n=DY(n,h,t):h.operator==="<"||h.operator==="<="?s=kY(s,h,t):i.add(h.semver);if(i.size>1)return null;let o;if(n&&s){if(o=xv(n.semver,s.semver,t),o>0)return null;if(o===0&&(n.operator!==">="||s.operator!=="<="))return null}for(let h of i){if(n&&!Id(h,String(n),t)||s&&!Id(h,String(s),t))return null;for(let p of e)if(!Id(h,String(p),t))return!1;return!0}let a,l,c,u,g=s&&!t.includePrerelease&&s.semver.prerelease.length?s.semver:!1,f=n&&!t.includePrerelease&&n.semver.prerelease.length?n.semver:!1;g&&g.prerelease.length===1&&s.operator==="<"&&g.prerelease[0]===0&&(g=!1);for(let h of e){if(u=u||h.operator===">"||h.operator===">=",c=c||h.operator==="<"||h.operator==="<=",n){if(f&&h.semver.prerelease&&h.semver.prerelease.length&&h.semver.major===f.major&&h.semver.minor===f.minor&&h.semver.patch===f.patch&&(f=!1),h.operator===">"||h.operator===">="){if(a=DY(n,h,t),a===h&&a!==n)return!1}else if(n.operator===">="&&!Id(n.semver,String(h),t))return!1}if(s){if(g&&h.semver.prerelease&&h.semver.prerelease.length&&h.semver.major===g.major&&h.semver.minor===g.minor&&h.semver.patch===g.patch&&(g=!1),h.operator==="<"||h.operator==="<="){if(l=kY(s,h,t),l===h&&l!==s)return!1}else if(s.operator==="<="&&!Id(s.semver,String(h),t))return!1}if(!h.operator&&(s||n)&&o!==0)return!1}return!(n&&c&&!s&&o!==0||s&&u&&!n&&o!==0||f||g)},DY=(r,e,t)=>{if(!r)return e;let i=xv(r.semver,e.semver,t);return i>0?r:i<0||e.operator===">"&&r.operator===">="?e:r},kY=(r,e,t)=>{if(!r)return e;let i=xv(r.semver,e.semver,t);return i<0?r:i>0||e.operator==="<"&&r.operator==="<="?e:r};RY.exports=gEe});var Xr=w((z$e,NY)=>{var Pv=ac();NY.exports={re:Pv.re,src:Pv.src,tokens:Pv.t,SEMVER_SPEC_VERSION:cd().SEMVER_SPEC_VERSION,SemVer:Ti(),compareIdentifiers:FI().compareIdentifiers,rcompareIdentifiers:FI().rcompareIdentifiers,parse:Ac(),valid:nG(),clean:oG(),inc:AG(),diff:hG(),major:dG(),minor:mG(),patch:IG(),prerelease:wG(),compare:As(),rcompare:QG(),compareLoose:SG(),compareBuild:OI(),sort:DG(),rsort:RG(),gt:hd(),lt:MI(),eq:TI(),neq:Cv(),gte:KI(),lte:UI(),cmp:mv(),coerce:UG(),Comparator:Cd(),Range:ls(),satisfies:Ed(),toComparators:oY(),maxSatisfying:AY(),minSatisfying:cY(),minVersion:fY(),validRange:pY(),outside:qI(),gtr:yY(),ltr:BY(),intersects:SY(),simplifyRange:xY(),subset:FY()}});var Dv=w(WI=>{"use strict";Object.defineProperty(WI,"__esModule",{value:!0});WI.VERSION=void 0;WI.VERSION="9.1.0"});var Gt=w((exports,module)=>{"use strict";var __spreadArray=exports&&exports.__spreadArray||function(r,e,t){if(t||arguments.length===2)for(var i=0,n=e.length,s;i{(function(r,e){typeof define=="function"&&define.amd?define([],e):typeof zI=="object"&&zI.exports?zI.exports=e():r.regexpToAst=e()})(typeof self<"u"?self:LY,function(){function r(){}r.prototype.saveState=function(){return{idx:this.idx,input:this.input,groupIdx:this.groupIdx}},r.prototype.restoreState=function(p){this.idx=p.idx,this.input=p.input,this.groupIdx=p.groupIdx},r.prototype.pattern=function(p){this.idx=0,this.input=p,this.groupIdx=0,this.consumeChar("/");var C=this.disjunction();this.consumeChar("/");for(var y={type:"Flags",loc:{begin:this.idx,end:p.length},global:!1,ignoreCase:!1,multiLine:!1,unicode:!1,sticky:!1};this.isRegExpFlag();)switch(this.popChar()){case"g":o(y,"global");break;case"i":o(y,"ignoreCase");break;case"m":o(y,"multiLine");break;case"u":o(y,"unicode");break;case"y":o(y,"sticky");break}if(this.idx!==this.input.length)throw Error("Redundant input: "+this.input.substring(this.idx));return{type:"Pattern",flags:y,value:C,loc:this.loc(0)}},r.prototype.disjunction=function(){var p=[],C=this.idx;for(p.push(this.alternative());this.peekChar()==="|";)this.consumeChar("|"),p.push(this.alternative());return{type:"Disjunction",value:p,loc:this.loc(C)}},r.prototype.alternative=function(){for(var p=[],C=this.idx;this.isTerm();)p.push(this.term());return{type:"Alternative",value:p,loc:this.loc(C)}},r.prototype.term=function(){return this.isAssertion()?this.assertion():this.atom()},r.prototype.assertion=function(){var p=this.idx;switch(this.popChar()){case"^":return{type:"StartAnchor",loc:this.loc(p)};case"$":return{type:"EndAnchor",loc:this.loc(p)};case"\\":switch(this.popChar()){case"b":return{type:"WordBoundary",loc:this.loc(p)};case"B":return{type:"NonWordBoundary",loc:this.loc(p)}}throw Error("Invalid Assertion Escape");case"(":this.consumeChar("?");var C;switch(this.popChar()){case"=":C="Lookahead";break;case"!":C="NegativeLookahead";break}a(C);var y=this.disjunction();return this.consumeChar(")"),{type:C,value:y,loc:this.loc(p)}}l()},r.prototype.quantifier=function(p){var C,y=this.idx;switch(this.popChar()){case"*":C={atLeast:0,atMost:1/0};break;case"+":C={atLeast:1,atMost:1/0};break;case"?":C={atLeast:0,atMost:1};break;case"{":var B=this.integerIncludingZero();switch(this.popChar()){case"}":C={atLeast:B,atMost:B};break;case",":var v;this.isDigit()?(v=this.integerIncludingZero(),C={atLeast:B,atMost:v}):C={atLeast:B,atMost:1/0},this.consumeChar("}");break}if(p===!0&&C===void 0)return;a(C);break}if(!(p===!0&&C===void 0))return a(C),this.peekChar(0)==="?"?(this.consumeChar("?"),C.greedy=!1):C.greedy=!0,C.type="Quantifier",C.loc=this.loc(y),C},r.prototype.atom=function(){var p,C=this.idx;switch(this.peekChar()){case".":p=this.dotAll();break;case"\\":p=this.atomEscape();break;case"[":p=this.characterClass();break;case"(":p=this.group();break}return p===void 0&&this.isPatternCharacter()&&(p=this.patternCharacter()),a(p),p.loc=this.loc(C),this.isQuantifier()&&(p.quantifier=this.quantifier()),p},r.prototype.dotAll=function(){return this.consumeChar("."),{type:"Set",complement:!0,value:[n(` -`),n("\r"),n("\u2028"),n("\u2029")]}},r.prototype.atomEscape=function(){switch(this.consumeChar("\\"),this.peekChar()){case"1":case"2":case"3":case"4":case"5":case"6":case"7":case"8":case"9":return this.decimalEscapeAtom();case"d":case"D":case"s":case"S":case"w":case"W":return this.characterClassEscape();case"f":case"n":case"r":case"t":case"v":return this.controlEscapeAtom();case"c":return this.controlLetterEscapeAtom();case"0":return this.nulCharacterAtom();case"x":return this.hexEscapeSequenceAtom();case"u":return this.regExpUnicodeEscapeSequenceAtom();default:return this.identityEscapeAtom()}},r.prototype.decimalEscapeAtom=function(){var p=this.positiveInteger();return{type:"GroupBackReference",value:p}},r.prototype.characterClassEscape=function(){var p,C=!1;switch(this.popChar()){case"d":p=u;break;case"D":p=u,C=!0;break;case"s":p=f;break;case"S":p=f,C=!0;break;case"w":p=g;break;case"W":p=g,C=!0;break}return a(p),{type:"Set",value:p,complement:C}},r.prototype.controlEscapeAtom=function(){var p;switch(this.popChar()){case"f":p=n("\f");break;case"n":p=n(` -`);break;case"r":p=n("\r");break;case"t":p=n(" ");break;case"v":p=n("\v");break}return a(p),{type:"Character",value:p}},r.prototype.controlLetterEscapeAtom=function(){this.consumeChar("c");var p=this.popChar();if(/[a-zA-Z]/.test(p)===!1)throw Error("Invalid ");var C=p.toUpperCase().charCodeAt(0)-64;return{type:"Character",value:C}},r.prototype.nulCharacterAtom=function(){return this.consumeChar("0"),{type:"Character",value:n("\0")}},r.prototype.hexEscapeSequenceAtom=function(){return this.consumeChar("x"),this.parseHexDigits(2)},r.prototype.regExpUnicodeEscapeSequenceAtom=function(){return this.consumeChar("u"),this.parseHexDigits(4)},r.prototype.identityEscapeAtom=function(){var p=this.popChar();return{type:"Character",value:n(p)}},r.prototype.classPatternCharacterAtom=function(){switch(this.peekChar()){case` -`:case"\r":case"\u2028":case"\u2029":case"\\":case"]":throw Error("TBD");default:var p=this.popChar();return{type:"Character",value:n(p)}}},r.prototype.characterClass=function(){var p=[],C=!1;for(this.consumeChar("["),this.peekChar(0)==="^"&&(this.consumeChar("^"),C=!0);this.isClassAtom();){var y=this.classAtom(),B=y.type==="Character";if(B&&this.isRangeDash()){this.consumeChar("-");var v=this.classAtom(),D=v.type==="Character";if(D){if(v.value=this.input.length)throw Error("Unexpected end of input");this.idx++},r.prototype.loc=function(p){return{begin:p,end:this.idx}};var e=/[0-9a-fA-F]/,t=/[0-9]/,i=/[1-9]/;function n(p){return p.charCodeAt(0)}function s(p,C){p.length!==void 0?p.forEach(function(y){C.push(y)}):C.push(p)}function o(p,C){if(p[C]===!0)throw"duplicate flag "+C;p[C]=!0}function a(p){if(p===void 0)throw Error("Internal Error - Should never get here!")}function l(){throw Error("Internal Error - Should never get here!")}var c,u=[];for(c=n("0");c<=n("9");c++)u.push(c);var g=[n("_")].concat(u);for(c=n("a");c<=n("z");c++)g.push(c);for(c=n("A");c<=n("Z");c++)g.push(c);var f=[n(" "),n("\f"),n(` -`),n("\r"),n(" "),n("\v"),n(" "),n("\xA0"),n("\u1680"),n("\u2000"),n("\u2001"),n("\u2002"),n("\u2003"),n("\u2004"),n("\u2005"),n("\u2006"),n("\u2007"),n("\u2008"),n("\u2009"),n("\u200A"),n("\u2028"),n("\u2029"),n("\u202F"),n("\u205F"),n("\u3000"),n("\uFEFF")];function h(){}return h.prototype.visitChildren=function(p){for(var C in p){var y=p[C];p.hasOwnProperty(C)&&(y.type!==void 0?this.visit(y):Array.isArray(y)&&y.forEach(function(B){this.visit(B)},this))}},h.prototype.visit=function(p){switch(p.type){case"Pattern":this.visitPattern(p);break;case"Flags":this.visitFlags(p);break;case"Disjunction":this.visitDisjunction(p);break;case"Alternative":this.visitAlternative(p);break;case"StartAnchor":this.visitStartAnchor(p);break;case"EndAnchor":this.visitEndAnchor(p);break;case"WordBoundary":this.visitWordBoundary(p);break;case"NonWordBoundary":this.visitNonWordBoundary(p);break;case"Lookahead":this.visitLookahead(p);break;case"NegativeLookahead":this.visitNegativeLookahead(p);break;case"Character":this.visitCharacter(p);break;case"Set":this.visitSet(p);break;case"Group":this.visitGroup(p);break;case"GroupBackReference":this.visitGroupBackReference(p);break;case"Quantifier":this.visitQuantifier(p);break}this.visitChildren(p)},h.prototype.visitPattern=function(p){},h.prototype.visitFlags=function(p){},h.prototype.visitDisjunction=function(p){},h.prototype.visitAlternative=function(p){},h.prototype.visitStartAnchor=function(p){},h.prototype.visitEndAnchor=function(p){},h.prototype.visitWordBoundary=function(p){},h.prototype.visitNonWordBoundary=function(p){},h.prototype.visitLookahead=function(p){},h.prototype.visitNegativeLookahead=function(p){},h.prototype.visitCharacter=function(p){},h.prototype.visitSet=function(p){},h.prototype.visitGroup=function(p){},h.prototype.visitGroupBackReference=function(p){},h.prototype.visitQuantifier=function(p){},{RegExpParser:r,BaseRegExpVisitor:h,VERSION:"0.5.0"}})});var ZI=w(Wg=>{"use strict";Object.defineProperty(Wg,"__esModule",{value:!0});Wg.clearRegExpParserCache=Wg.getRegExpAst=void 0;var hEe=VI(),XI={},pEe=new hEe.RegExpParser;function dEe(r){var e=r.toString();if(XI.hasOwnProperty(e))return XI[e];var t=pEe.pattern(e);return XI[e]=t,t}Wg.getRegExpAst=dEe;function CEe(){XI={}}Wg.clearRegExpParserCache=CEe});var UY=w(pn=>{"use strict";var mEe=pn&&pn.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(pn,"__esModule",{value:!0});pn.canMatchCharCode=pn.firstCharOptimizedIndices=pn.getOptimizedStartCodesIndices=pn.failedOptimizationPrefixMsg=void 0;var OY=VI(),cs=Gt(),MY=ZI(),Sa=Rv(),KY="Complement Sets are not supported for first char optimization";pn.failedOptimizationPrefixMsg=`Unable to use "first char" lexer optimizations: -`;function EEe(r,e){e===void 0&&(e=!1);try{var t=(0,MY.getRegExpAst)(r),i=$I(t.value,{},t.flags.ignoreCase);return i}catch(s){if(s.message===KY)e&&(0,cs.PRINT_WARNING)(""+pn.failedOptimizationPrefixMsg+(" Unable to optimize: < "+r.toString()+` > -`)+` Complement Sets cannot be automatically optimized. - This will disable the lexer's first char optimizations. - See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#COMPLEMENT for details.`);else{var n="";e&&(n=` - This will disable the lexer's first char optimizations. - See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#REGEXP_PARSING for details.`),(0,cs.PRINT_ERROR)(pn.failedOptimizationPrefixMsg+` -`+(" Failed parsing: < "+r.toString()+` > -`)+(" Using the regexp-to-ast library version: "+OY.VERSION+` -`)+" Please open an issue at: https://github.com/bd82/regexp-to-ast/issues"+n)}}return[]}pn.getOptimizedStartCodesIndices=EEe;function $I(r,e,t){switch(r.type){case"Disjunction":for(var i=0;i=Sa.minOptimizationVal)for(var f=u.from>=Sa.minOptimizationVal?u.from:Sa.minOptimizationVal,h=u.to,p=(0,Sa.charCodeToOptimizedIndex)(f),C=(0,Sa.charCodeToOptimizedIndex)(h),y=p;y<=C;y++)e[y]=y}}});break;case"Group":$I(o.value,e,t);break;default:throw Error("Non Exhaustive Match")}var a=o.quantifier!==void 0&&o.quantifier.atLeast===0;if(o.type==="Group"&&kv(o)===!1||o.type!=="Group"&&a===!1)break}break;default:throw Error("non exhaustive match!")}return(0,cs.values)(e)}pn.firstCharOptimizedIndices=$I;function _I(r,e,t){var i=(0,Sa.charCodeToOptimizedIndex)(r);e[i]=i,t===!0&&IEe(r,e)}function IEe(r,e){var t=String.fromCharCode(r),i=t.toUpperCase();if(i!==t){var n=(0,Sa.charCodeToOptimizedIndex)(i.charCodeAt(0));e[n]=n}else{var s=t.toLowerCase();if(s!==t){var n=(0,Sa.charCodeToOptimizedIndex)(s.charCodeAt(0));e[n]=n}}}function TY(r,e){return(0,cs.find)(r.value,function(t){if(typeof t=="number")return(0,cs.contains)(e,t);var i=t;return(0,cs.find)(e,function(n){return i.from<=n&&n<=i.to})!==void 0})}function kv(r){return r.quantifier&&r.quantifier.atLeast===0?!0:r.value?(0,cs.isArray)(r.value)?(0,cs.every)(r.value,kv):kv(r.value):!1}var yEe=function(r){mEe(e,r);function e(t){var i=r.call(this)||this;return i.targetCharCodes=t,i.found=!1,i}return e.prototype.visitChildren=function(t){if(this.found!==!0){switch(t.type){case"Lookahead":this.visitLookahead(t);return;case"NegativeLookahead":this.visitNegativeLookahead(t);return}r.prototype.visitChildren.call(this,t)}},e.prototype.visitCharacter=function(t){(0,cs.contains)(this.targetCharCodes,t.value)&&(this.found=!0)},e.prototype.visitSet=function(t){t.complement?TY(t,this.targetCharCodes)===void 0&&(this.found=!0):TY(t,this.targetCharCodes)!==void 0&&(this.found=!0)},e}(OY.BaseRegExpVisitor);function wEe(r,e){if(e instanceof RegExp){var t=(0,MY.getRegExpAst)(e),i=new yEe(r);return i.visit(t),i.found}else return(0,cs.find)(e,function(n){return(0,cs.contains)(r,n.charCodeAt(0))})!==void 0}pn.canMatchCharCode=wEe});var Rv=w(Ve=>{"use strict";var HY=Ve&&Ve.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(Ve,"__esModule",{value:!0});Ve.charCodeToOptimizedIndex=Ve.minOptimizationVal=Ve.buildLineBreakIssueMessage=Ve.LineTerminatorOptimizedTester=Ve.isShortPattern=Ve.isCustomPattern=Ve.cloneEmptyGroups=Ve.performWarningRuntimeChecks=Ve.performRuntimeChecks=Ve.addStickyFlag=Ve.addStartOfInput=Ve.findUnreachablePatterns=Ve.findModesThatDoNotExist=Ve.findInvalidGroupType=Ve.findDuplicatePatterns=Ve.findUnsupportedFlags=Ve.findStartOfInputAnchor=Ve.findEmptyMatchRegExps=Ve.findEndOfInputAnchor=Ve.findInvalidPatterns=Ve.findMissingPatterns=Ve.validatePatterns=Ve.analyzeTokenTypes=Ve.enableSticky=Ve.disableSticky=Ve.SUPPORT_STICKY=Ve.MODES=Ve.DEFAULT_MODE=void 0;var GY=VI(),ir=yd(),xe=Gt(),zg=UY(),YY=ZI(),Do="PATTERN";Ve.DEFAULT_MODE="defaultMode";Ve.MODES="modes";Ve.SUPPORT_STICKY=typeof new RegExp("(?:)").sticky=="boolean";function BEe(){Ve.SUPPORT_STICKY=!1}Ve.disableSticky=BEe;function QEe(){Ve.SUPPORT_STICKY=!0}Ve.enableSticky=QEe;function bEe(r,e){e=(0,xe.defaults)(e,{useSticky:Ve.SUPPORT_STICKY,debug:!1,safeMode:!1,positionTracking:"full",lineTerminatorCharacters:["\r",` -`],tracer:function(v,D){return D()}});var t=e.tracer;t("initCharCodeToOptimizedIndexMap",function(){LEe()});var i;t("Reject Lexer.NA",function(){i=(0,xe.reject)(r,function(v){return v[Do]===ir.Lexer.NA})});var n=!1,s;t("Transform Patterns",function(){n=!1,s=(0,xe.map)(i,function(v){var D=v[Do];if((0,xe.isRegExp)(D)){var L=D.source;return L.length===1&&L!=="^"&&L!=="$"&&L!=="."&&!D.ignoreCase?L:L.length===2&&L[0]==="\\"&&!(0,xe.contains)(["d","D","s","S","t","r","n","t","0","c","b","B","f","v","w","W"],L[1])?L[1]:e.useSticky?Lv(D):Nv(D)}else{if((0,xe.isFunction)(D))return n=!0,{exec:D};if((0,xe.has)(D,"exec"))return n=!0,D;if(typeof D=="string"){if(D.length===1)return D;var H=D.replace(/[\\^$.*+?()[\]{}|]/g,"\\$&"),j=new RegExp(H);return e.useSticky?Lv(j):Nv(j)}else throw Error("non exhaustive match")}})});var o,a,l,c,u;t("misc mapping",function(){o=(0,xe.map)(i,function(v){return v.tokenTypeIdx}),a=(0,xe.map)(i,function(v){var D=v.GROUP;if(D!==ir.Lexer.SKIPPED){if((0,xe.isString)(D))return D;if((0,xe.isUndefined)(D))return!1;throw Error("non exhaustive match")}}),l=(0,xe.map)(i,function(v){var D=v.LONGER_ALT;if(D){var L=(0,xe.isArray)(D)?(0,xe.map)(D,function(H){return(0,xe.indexOf)(i,H)}):[(0,xe.indexOf)(i,D)];return L}}),c=(0,xe.map)(i,function(v){return v.PUSH_MODE}),u=(0,xe.map)(i,function(v){return(0,xe.has)(v,"POP_MODE")})});var g;t("Line Terminator Handling",function(){var v=ij(e.lineTerminatorCharacters);g=(0,xe.map)(i,function(D){return!1}),e.positionTracking!=="onlyOffset"&&(g=(0,xe.map)(i,function(D){if((0,xe.has)(D,"LINE_BREAKS"))return D.LINE_BREAKS;if(tj(D,v)===!1)return(0,zg.canMatchCharCode)(v,D.PATTERN)}))});var f,h,p,C;t("Misc Mapping #2",function(){f=(0,xe.map)(i,Ov),h=(0,xe.map)(s,ej),p=(0,xe.reduce)(i,function(v,D){var L=D.GROUP;return(0,xe.isString)(L)&&L!==ir.Lexer.SKIPPED&&(v[L]=[]),v},{}),C=(0,xe.map)(s,function(v,D){return{pattern:s[D],longerAlt:l[D],canLineTerminator:g[D],isCustom:f[D],short:h[D],group:a[D],push:c[D],pop:u[D],tokenTypeIdx:o[D],tokenType:i[D]}})});var y=!0,B=[];return e.safeMode||t("First Char Optimization",function(){B=(0,xe.reduce)(i,function(v,D,L){if(typeof D.PATTERN=="string"){var H=D.PATTERN.charCodeAt(0),j=Tv(H);Fv(v,j,C[L])}else if((0,xe.isArray)(D.START_CHARS_HINT)){var $;(0,xe.forEach)(D.START_CHARS_HINT,function(W){var _=typeof W=="string"?W.charCodeAt(0):W,A=Tv(_);$!==A&&($=A,Fv(v,A,C[L]))})}else if((0,xe.isRegExp)(D.PATTERN))if(D.PATTERN.unicode)y=!1,e.ensureOptimizations&&(0,xe.PRINT_ERROR)(""+zg.failedOptimizationPrefixMsg+(" Unable to analyze < "+D.PATTERN.toString()+` > pattern. -`)+` The regexp unicode flag is not currently supported by the regexp-to-ast library. - This will disable the lexer's first char optimizations. - For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#UNICODE_OPTIMIZE`);else{var V=(0,zg.getOptimizedStartCodesIndices)(D.PATTERN,e.ensureOptimizations);(0,xe.isEmpty)(V)&&(y=!1),(0,xe.forEach)(V,function(W){Fv(v,W,C[L])})}else e.ensureOptimizations&&(0,xe.PRINT_ERROR)(""+zg.failedOptimizationPrefixMsg+(" TokenType: <"+D.name+`> is using a custom token pattern without providing parameter. -`)+` This will disable the lexer's first char optimizations. - For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#CUSTOM_OPTIMIZE`),y=!1;return v},[])}),t("ArrayPacking",function(){B=(0,xe.packArray)(B)}),{emptyGroups:p,patternIdxToConfig:C,charCodeToPatternIdxToConfig:B,hasCustom:n,canBeOptimized:y}}Ve.analyzeTokenTypes=bEe;function SEe(r,e){var t=[],i=jY(r);t=t.concat(i.errors);var n=qY(i.valid),s=n.valid;return t=t.concat(n.errors),t=t.concat(vEe(s)),t=t.concat(ZY(s)),t=t.concat(_Y(s,e)),t=t.concat($Y(s)),t}Ve.validatePatterns=SEe;function vEe(r){var e=[],t=(0,xe.filter)(r,function(i){return(0,xe.isRegExp)(i[Do])});return e=e.concat(JY(t)),e=e.concat(zY(t)),e=e.concat(VY(t)),e=e.concat(XY(t)),e=e.concat(WY(t)),e}function jY(r){var e=(0,xe.filter)(r,function(n){return!(0,xe.has)(n,Do)}),t=(0,xe.map)(e,function(n){return{message:"Token Type: ->"+n.name+"<- missing static 'PATTERN' property",type:ir.LexerDefinitionErrorType.MISSING_PATTERN,tokenTypes:[n]}}),i=(0,xe.difference)(r,e);return{errors:t,valid:i}}Ve.findMissingPatterns=jY;function qY(r){var e=(0,xe.filter)(r,function(n){var s=n[Do];return!(0,xe.isRegExp)(s)&&!(0,xe.isFunction)(s)&&!(0,xe.has)(s,"exec")&&!(0,xe.isString)(s)}),t=(0,xe.map)(e,function(n){return{message:"Token Type: ->"+n.name+"<- static 'PATTERN' can only be a RegExp, a Function matching the {CustomPatternMatcherFunc} type or an Object matching the {ICustomPattern} interface.",type:ir.LexerDefinitionErrorType.INVALID_PATTERN,tokenTypes:[n]}}),i=(0,xe.difference)(r,e);return{errors:t,valid:i}}Ve.findInvalidPatterns=qY;var xEe=/[^\\][\$]/;function JY(r){var e=function(n){HY(s,n);function s(){var o=n!==null&&n.apply(this,arguments)||this;return o.found=!1,o}return s.prototype.visitEndAnchor=function(o){this.found=!0},s}(GY.BaseRegExpVisitor),t=(0,xe.filter)(r,function(n){var s=n[Do];try{var o=(0,YY.getRegExpAst)(s),a=new e;return a.visit(o),a.found}catch{return xEe.test(s.source)}}),i=(0,xe.map)(t,function(n){return{message:`Unexpected RegExp Anchor Error: - Token Type: ->`+n.name+`<- static 'PATTERN' cannot contain end of input anchor '$' - See chevrotain.io/docs/guide/resolving_lexer_errors.html#ANCHORS for details.`,type:ir.LexerDefinitionErrorType.EOI_ANCHOR_FOUND,tokenTypes:[n]}});return i}Ve.findEndOfInputAnchor=JY;function WY(r){var e=(0,xe.filter)(r,function(i){var n=i[Do];return n.test("")}),t=(0,xe.map)(e,function(i){return{message:"Token Type: ->"+i.name+"<- static 'PATTERN' must not match an empty string",type:ir.LexerDefinitionErrorType.EMPTY_MATCH_PATTERN,tokenTypes:[i]}});return t}Ve.findEmptyMatchRegExps=WY;var PEe=/[^\\[][\^]|^\^/;function zY(r){var e=function(n){HY(s,n);function s(){var o=n!==null&&n.apply(this,arguments)||this;return o.found=!1,o}return s.prototype.visitStartAnchor=function(o){this.found=!0},s}(GY.BaseRegExpVisitor),t=(0,xe.filter)(r,function(n){var s=n[Do];try{var o=(0,YY.getRegExpAst)(s),a=new e;return a.visit(o),a.found}catch{return PEe.test(s.source)}}),i=(0,xe.map)(t,function(n){return{message:`Unexpected RegExp Anchor Error: - Token Type: ->`+n.name+`<- static 'PATTERN' cannot contain start of input anchor '^' - See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#ANCHORS for details.`,type:ir.LexerDefinitionErrorType.SOI_ANCHOR_FOUND,tokenTypes:[n]}});return i}Ve.findStartOfInputAnchor=zY;function VY(r){var e=(0,xe.filter)(r,function(i){var n=i[Do];return n instanceof RegExp&&(n.multiline||n.global)}),t=(0,xe.map)(e,function(i){return{message:"Token Type: ->"+i.name+"<- static 'PATTERN' may NOT contain global('g') or multiline('m')",type:ir.LexerDefinitionErrorType.UNSUPPORTED_FLAGS_FOUND,tokenTypes:[i]}});return t}Ve.findUnsupportedFlags=VY;function XY(r){var e=[],t=(0,xe.map)(r,function(s){return(0,xe.reduce)(r,function(o,a){return s.PATTERN.source===a.PATTERN.source&&!(0,xe.contains)(e,a)&&a.PATTERN!==ir.Lexer.NA&&(e.push(a),o.push(a)),o},[])});t=(0,xe.compact)(t);var i=(0,xe.filter)(t,function(s){return s.length>1}),n=(0,xe.map)(i,function(s){var o=(0,xe.map)(s,function(l){return l.name}),a=(0,xe.first)(s).PATTERN;return{message:"The same RegExp pattern ->"+a+"<-"+("has been used in all of the following Token Types: "+o.join(", ")+" <-"),type:ir.LexerDefinitionErrorType.DUPLICATE_PATTERNS_FOUND,tokenTypes:s}});return n}Ve.findDuplicatePatterns=XY;function ZY(r){var e=(0,xe.filter)(r,function(i){if(!(0,xe.has)(i,"GROUP"))return!1;var n=i.GROUP;return n!==ir.Lexer.SKIPPED&&n!==ir.Lexer.NA&&!(0,xe.isString)(n)}),t=(0,xe.map)(e,function(i){return{message:"Token Type: ->"+i.name+"<- static 'GROUP' can only be Lexer.SKIPPED/Lexer.NA/A String",type:ir.LexerDefinitionErrorType.INVALID_GROUP_TYPE_FOUND,tokenTypes:[i]}});return t}Ve.findInvalidGroupType=ZY;function _Y(r,e){var t=(0,xe.filter)(r,function(n){return n.PUSH_MODE!==void 0&&!(0,xe.contains)(e,n.PUSH_MODE)}),i=(0,xe.map)(t,function(n){var s="Token Type: ->"+n.name+"<- static 'PUSH_MODE' value cannot refer to a Lexer Mode ->"+n.PUSH_MODE+"<-which does not exist";return{message:s,type:ir.LexerDefinitionErrorType.PUSH_MODE_DOES_NOT_EXIST,tokenTypes:[n]}});return i}Ve.findModesThatDoNotExist=_Y;function $Y(r){var e=[],t=(0,xe.reduce)(r,function(i,n,s){var o=n.PATTERN;return o===ir.Lexer.NA||((0,xe.isString)(o)?i.push({str:o,idx:s,tokenType:n}):(0,xe.isRegExp)(o)&&kEe(o)&&i.push({str:o.source,idx:s,tokenType:n})),i},[]);return(0,xe.forEach)(r,function(i,n){(0,xe.forEach)(t,function(s){var o=s.str,a=s.idx,l=s.tokenType;if(n"+i.name+"<-")+`in the lexer's definition. -See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#UNREACHABLE`;e.push({message:c,type:ir.LexerDefinitionErrorType.UNREACHABLE_PATTERN,tokenTypes:[i,l]})}})}),e}Ve.findUnreachablePatterns=$Y;function DEe(r,e){if((0,xe.isRegExp)(e)){var t=e.exec(r);return t!==null&&t.index===0}else{if((0,xe.isFunction)(e))return e(r,0,[],{});if((0,xe.has)(e,"exec"))return e.exec(r,0,[],{});if(typeof e=="string")return e===r;throw Error("non exhaustive match")}}function kEe(r){var e=[".","\\","[","]","|","^","$","(",")","?","*","+","{"];return(0,xe.find)(e,function(t){return r.source.indexOf(t)!==-1})===void 0}function Nv(r){var e=r.ignoreCase?"i":"";return new RegExp("^(?:"+r.source+")",e)}Ve.addStartOfInput=Nv;function Lv(r){var e=r.ignoreCase?"iy":"y";return new RegExp(""+r.source,e)}Ve.addStickyFlag=Lv;function REe(r,e,t){var i=[];return(0,xe.has)(r,Ve.DEFAULT_MODE)||i.push({message:"A MultiMode Lexer cannot be initialized without a <"+Ve.DEFAULT_MODE+`> property in its definition -`,type:ir.LexerDefinitionErrorType.MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE}),(0,xe.has)(r,Ve.MODES)||i.push({message:"A MultiMode Lexer cannot be initialized without a <"+Ve.MODES+`> property in its definition -`,type:ir.LexerDefinitionErrorType.MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY}),(0,xe.has)(r,Ve.MODES)&&(0,xe.has)(r,Ve.DEFAULT_MODE)&&!(0,xe.has)(r.modes,r.defaultMode)&&i.push({message:"A MultiMode Lexer cannot be initialized with a "+Ve.DEFAULT_MODE+": <"+r.defaultMode+`>which does not exist -`,type:ir.LexerDefinitionErrorType.MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST}),(0,xe.has)(r,Ve.MODES)&&(0,xe.forEach)(r.modes,function(n,s){(0,xe.forEach)(n,function(o,a){(0,xe.isUndefined)(o)&&i.push({message:"A Lexer cannot be initialized using an undefined Token Type. Mode:"+("<"+s+"> at index: <"+a+`> -`),type:ir.LexerDefinitionErrorType.LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED})})}),i}Ve.performRuntimeChecks=REe;function FEe(r,e,t){var i=[],n=!1,s=(0,xe.compact)((0,xe.flatten)((0,xe.mapValues)(r.modes,function(l){return l}))),o=(0,xe.reject)(s,function(l){return l[Do]===ir.Lexer.NA}),a=ij(t);return e&&(0,xe.forEach)(o,function(l){var c=tj(l,a);if(c!==!1){var u=rj(l,c),g={message:u,type:c.issue,tokenType:l};i.push(g)}else(0,xe.has)(l,"LINE_BREAKS")?l.LINE_BREAKS===!0&&(n=!0):(0,zg.canMatchCharCode)(a,l.PATTERN)&&(n=!0)}),e&&!n&&i.push({message:`Warning: No LINE_BREAKS Found. - This Lexer has been defined to track line and column information, - But none of the Token Types can be identified as matching a line terminator. - See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#LINE_BREAKS - for details.`,type:ir.LexerDefinitionErrorType.NO_LINE_BREAKS_FLAGS}),i}Ve.performWarningRuntimeChecks=FEe;function NEe(r){var e={},t=(0,xe.keys)(r);return(0,xe.forEach)(t,function(i){var n=r[i];if((0,xe.isArray)(n))e[i]=[];else throw Error("non exhaustive match")}),e}Ve.cloneEmptyGroups=NEe;function Ov(r){var e=r.PATTERN;if((0,xe.isRegExp)(e))return!1;if((0,xe.isFunction)(e))return!0;if((0,xe.has)(e,"exec"))return!0;if((0,xe.isString)(e))return!1;throw Error("non exhaustive match")}Ve.isCustomPattern=Ov;function ej(r){return(0,xe.isString)(r)&&r.length===1?r.charCodeAt(0):!1}Ve.isShortPattern=ej;Ve.LineTerminatorOptimizedTester={test:function(r){for(var e=r.length,t=this.lastIndex;t Token Type -`)+(" Root cause: "+e.errMsg+`. -`)+" For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#IDENTIFY_TERMINATOR";if(e.issue===ir.LexerDefinitionErrorType.CUSTOM_LINE_BREAK)return`Warning: A Custom Token Pattern should specify the option. -`+(" The problem is in the <"+r.name+`> Token Type -`)+" For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#CUSTOM_LINE_BREAK";throw Error("non exhaustive match")}Ve.buildLineBreakIssueMessage=rj;function ij(r){var e=(0,xe.map)(r,function(t){return(0,xe.isString)(t)&&t.length>0?t.charCodeAt(0):t});return e}function Fv(r,e,t){r[e]===void 0?r[e]=[t]:r[e].push(t)}Ve.minOptimizationVal=256;var ey=[];function Tv(r){return r255?255+~~(r/255):r}}});var Vg=w(Nt=>{"use strict";Object.defineProperty(Nt,"__esModule",{value:!0});Nt.isTokenType=Nt.hasExtendingTokensTypesMapProperty=Nt.hasExtendingTokensTypesProperty=Nt.hasCategoriesProperty=Nt.hasShortKeyProperty=Nt.singleAssignCategoriesToksMap=Nt.assignCategoriesMapProp=Nt.assignCategoriesTokensProp=Nt.assignTokenDefaultProps=Nt.expandCategories=Nt.augmentTokenTypes=Nt.tokenIdxToClass=Nt.tokenShortNameIdx=Nt.tokenStructuredMatcherNoCategories=Nt.tokenStructuredMatcher=void 0;var Zr=Gt();function TEe(r,e){var t=r.tokenTypeIdx;return t===e.tokenTypeIdx?!0:e.isParent===!0&&e.categoryMatchesMap[t]===!0}Nt.tokenStructuredMatcher=TEe;function OEe(r,e){return r.tokenTypeIdx===e.tokenTypeIdx}Nt.tokenStructuredMatcherNoCategories=OEe;Nt.tokenShortNameIdx=1;Nt.tokenIdxToClass={};function MEe(r){var e=nj(r);sj(e),aj(e),oj(e),(0,Zr.forEach)(e,function(t){t.isParent=t.categoryMatches.length>0})}Nt.augmentTokenTypes=MEe;function nj(r){for(var e=(0,Zr.cloneArr)(r),t=r,i=!0;i;){t=(0,Zr.compact)((0,Zr.flatten)((0,Zr.map)(t,function(s){return s.CATEGORIES})));var n=(0,Zr.difference)(t,e);e=e.concat(n),(0,Zr.isEmpty)(n)?i=!1:t=n}return e}Nt.expandCategories=nj;function sj(r){(0,Zr.forEach)(r,function(e){Aj(e)||(Nt.tokenIdxToClass[Nt.tokenShortNameIdx]=e,e.tokenTypeIdx=Nt.tokenShortNameIdx++),Mv(e)&&!(0,Zr.isArray)(e.CATEGORIES)&&(e.CATEGORIES=[e.CATEGORIES]),Mv(e)||(e.CATEGORIES=[]),lj(e)||(e.categoryMatches=[]),cj(e)||(e.categoryMatchesMap={})})}Nt.assignTokenDefaultProps=sj;function oj(r){(0,Zr.forEach)(r,function(e){e.categoryMatches=[],(0,Zr.forEach)(e.categoryMatchesMap,function(t,i){e.categoryMatches.push(Nt.tokenIdxToClass[i].tokenTypeIdx)})})}Nt.assignCategoriesTokensProp=oj;function aj(r){(0,Zr.forEach)(r,function(e){Kv([],e)})}Nt.assignCategoriesMapProp=aj;function Kv(r,e){(0,Zr.forEach)(r,function(t){e.categoryMatchesMap[t.tokenTypeIdx]=!0}),(0,Zr.forEach)(e.CATEGORIES,function(t){var i=r.concat(e);(0,Zr.contains)(i,t)||Kv(i,t)})}Nt.singleAssignCategoriesToksMap=Kv;function Aj(r){return(0,Zr.has)(r,"tokenTypeIdx")}Nt.hasShortKeyProperty=Aj;function Mv(r){return(0,Zr.has)(r,"CATEGORIES")}Nt.hasCategoriesProperty=Mv;function lj(r){return(0,Zr.has)(r,"categoryMatches")}Nt.hasExtendingTokensTypesProperty=lj;function cj(r){return(0,Zr.has)(r,"categoryMatchesMap")}Nt.hasExtendingTokensTypesMapProperty=cj;function KEe(r){return(0,Zr.has)(r,"tokenTypeIdx")}Nt.isTokenType=KEe});var Uv=w(ty=>{"use strict";Object.defineProperty(ty,"__esModule",{value:!0});ty.defaultLexerErrorProvider=void 0;ty.defaultLexerErrorProvider={buildUnableToPopLexerModeMessage:function(r){return"Unable to pop Lexer Mode after encountering Token ->"+r.image+"<- The Mode Stack is empty"},buildUnexpectedCharactersMessage:function(r,e,t,i,n){return"unexpected character: ->"+r.charAt(e)+"<- at offset: "+e+","+(" skipped "+t+" characters.")}}});var yd=w(fc=>{"use strict";Object.defineProperty(fc,"__esModule",{value:!0});fc.Lexer=fc.LexerDefinitionErrorType=void 0;var Xs=Rv(),nr=Gt(),UEe=Vg(),HEe=Uv(),GEe=ZI(),YEe;(function(r){r[r.MISSING_PATTERN=0]="MISSING_PATTERN",r[r.INVALID_PATTERN=1]="INVALID_PATTERN",r[r.EOI_ANCHOR_FOUND=2]="EOI_ANCHOR_FOUND",r[r.UNSUPPORTED_FLAGS_FOUND=3]="UNSUPPORTED_FLAGS_FOUND",r[r.DUPLICATE_PATTERNS_FOUND=4]="DUPLICATE_PATTERNS_FOUND",r[r.INVALID_GROUP_TYPE_FOUND=5]="INVALID_GROUP_TYPE_FOUND",r[r.PUSH_MODE_DOES_NOT_EXIST=6]="PUSH_MODE_DOES_NOT_EXIST",r[r.MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE=7]="MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE",r[r.MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY=8]="MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY",r[r.MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST=9]="MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST",r[r.LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED=10]="LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED",r[r.SOI_ANCHOR_FOUND=11]="SOI_ANCHOR_FOUND",r[r.EMPTY_MATCH_PATTERN=12]="EMPTY_MATCH_PATTERN",r[r.NO_LINE_BREAKS_FLAGS=13]="NO_LINE_BREAKS_FLAGS",r[r.UNREACHABLE_PATTERN=14]="UNREACHABLE_PATTERN",r[r.IDENTIFY_TERMINATOR=15]="IDENTIFY_TERMINATOR",r[r.CUSTOM_LINE_BREAK=16]="CUSTOM_LINE_BREAK"})(YEe=fc.LexerDefinitionErrorType||(fc.LexerDefinitionErrorType={}));var wd={deferDefinitionErrorsHandling:!1,positionTracking:"full",lineTerminatorsPattern:/\n|\r\n?/g,lineTerminatorCharacters:[` -`,"\r"],ensureOptimizations:!1,safeMode:!1,errorMessageProvider:HEe.defaultLexerErrorProvider,traceInitPerf:!1,skipValidations:!1};Object.freeze(wd);var jEe=function(){function r(e,t){var i=this;if(t===void 0&&(t=wd),this.lexerDefinition=e,this.lexerDefinitionErrors=[],this.lexerDefinitionWarning=[],this.patternIdxToConfig={},this.charCodeToPatternIdxToConfig={},this.modes=[],this.emptyGroups={},this.config=void 0,this.trackStartLines=!0,this.trackEndLines=!0,this.hasCustom=!1,this.canModeBeOptimized={},typeof t=="boolean")throw Error(`The second argument to the Lexer constructor is now an ILexerConfig Object. -a boolean 2nd argument is no longer supported`);this.config=(0,nr.merge)(wd,t);var n=this.config.traceInitPerf;n===!0?(this.traceInitMaxIdent=1/0,this.traceInitPerf=!0):typeof n=="number"&&(this.traceInitMaxIdent=n,this.traceInitPerf=!0),this.traceInitIndent=-1,this.TRACE_INIT("Lexer Constructor",function(){var s,o=!0;i.TRACE_INIT("Lexer Config handling",function(){if(i.config.lineTerminatorsPattern===wd.lineTerminatorsPattern)i.config.lineTerminatorsPattern=Xs.LineTerminatorOptimizedTester;else if(i.config.lineTerminatorCharacters===wd.lineTerminatorCharacters)throw Error(`Error: Missing property on the Lexer config. - For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#MISSING_LINE_TERM_CHARS`);if(t.safeMode&&t.ensureOptimizations)throw Error('"safeMode" and "ensureOptimizations" flags are mutually exclusive.');i.trackStartLines=/full|onlyStart/i.test(i.config.positionTracking),i.trackEndLines=/full/i.test(i.config.positionTracking),(0,nr.isArray)(e)?(s={modes:{}},s.modes[Xs.DEFAULT_MODE]=(0,nr.cloneArr)(e),s[Xs.DEFAULT_MODE]=Xs.DEFAULT_MODE):(o=!1,s=(0,nr.cloneObj)(e))}),i.config.skipValidations===!1&&(i.TRACE_INIT("performRuntimeChecks",function(){i.lexerDefinitionErrors=i.lexerDefinitionErrors.concat((0,Xs.performRuntimeChecks)(s,i.trackStartLines,i.config.lineTerminatorCharacters))}),i.TRACE_INIT("performWarningRuntimeChecks",function(){i.lexerDefinitionWarning=i.lexerDefinitionWarning.concat((0,Xs.performWarningRuntimeChecks)(s,i.trackStartLines,i.config.lineTerminatorCharacters))})),s.modes=s.modes?s.modes:{},(0,nr.forEach)(s.modes,function(u,g){s.modes[g]=(0,nr.reject)(u,function(f){return(0,nr.isUndefined)(f)})});var a=(0,nr.keys)(s.modes);if((0,nr.forEach)(s.modes,function(u,g){i.TRACE_INIT("Mode: <"+g+"> processing",function(){if(i.modes.push(g),i.config.skipValidations===!1&&i.TRACE_INIT("validatePatterns",function(){i.lexerDefinitionErrors=i.lexerDefinitionErrors.concat((0,Xs.validatePatterns)(u,a))}),(0,nr.isEmpty)(i.lexerDefinitionErrors)){(0,UEe.augmentTokenTypes)(u);var f;i.TRACE_INIT("analyzeTokenTypes",function(){f=(0,Xs.analyzeTokenTypes)(u,{lineTerminatorCharacters:i.config.lineTerminatorCharacters,positionTracking:t.positionTracking,ensureOptimizations:t.ensureOptimizations,safeMode:t.safeMode,tracer:i.TRACE_INIT.bind(i)})}),i.patternIdxToConfig[g]=f.patternIdxToConfig,i.charCodeToPatternIdxToConfig[g]=f.charCodeToPatternIdxToConfig,i.emptyGroups=(0,nr.merge)(i.emptyGroups,f.emptyGroups),i.hasCustom=f.hasCustom||i.hasCustom,i.canModeBeOptimized[g]=f.canBeOptimized}})}),i.defaultMode=s.defaultMode,!(0,nr.isEmpty)(i.lexerDefinitionErrors)&&!i.config.deferDefinitionErrorsHandling){var l=(0,nr.map)(i.lexerDefinitionErrors,function(u){return u.message}),c=l.join(`----------------------- -`);throw new Error(`Errors detected in definition of Lexer: -`+c)}(0,nr.forEach)(i.lexerDefinitionWarning,function(u){(0,nr.PRINT_WARNING)(u.message)}),i.TRACE_INIT("Choosing sub-methods implementations",function(){if(Xs.SUPPORT_STICKY?(i.chopInput=nr.IDENTITY,i.match=i.matchWithTest):(i.updateLastIndex=nr.NOOP,i.match=i.matchWithExec),o&&(i.handleModes=nr.NOOP),i.trackStartLines===!1&&(i.computeNewColumn=nr.IDENTITY),i.trackEndLines===!1&&(i.updateTokenEndLineColumnLocation=nr.NOOP),/full/i.test(i.config.positionTracking))i.createTokenInstance=i.createFullToken;else if(/onlyStart/i.test(i.config.positionTracking))i.createTokenInstance=i.createStartOnlyToken;else if(/onlyOffset/i.test(i.config.positionTracking))i.createTokenInstance=i.createOffsetOnlyToken;else throw Error('Invalid config option: "'+i.config.positionTracking+'"');i.hasCustom?(i.addToken=i.addTokenUsingPush,i.handlePayload=i.handlePayloadWithCustom):(i.addToken=i.addTokenUsingMemberAccess,i.handlePayload=i.handlePayloadNoCustom)}),i.TRACE_INIT("Failed Optimization Warnings",function(){var u=(0,nr.reduce)(i.canModeBeOptimized,function(g,f,h){return f===!1&&g.push(h),g},[]);if(t.ensureOptimizations&&!(0,nr.isEmpty)(u))throw Error("Lexer Modes: < "+u.join(", ")+` > cannot be optimized. - Disable the "ensureOptimizations" lexer config flag to silently ignore this and run the lexer in an un-optimized mode. - Or inspect the console log for details on how to resolve these issues.`)}),i.TRACE_INIT("clearRegExpParserCache",function(){(0,GEe.clearRegExpParserCache)()}),i.TRACE_INIT("toFastProperties",function(){(0,nr.toFastProperties)(i)})})}return r.prototype.tokenize=function(e,t){if(t===void 0&&(t=this.defaultMode),!(0,nr.isEmpty)(this.lexerDefinitionErrors)){var i=(0,nr.map)(this.lexerDefinitionErrors,function(o){return o.message}),n=i.join(`----------------------- -`);throw new Error(`Unable to Tokenize because Errors detected in definition of Lexer: -`+n)}var s=this.tokenizeInternal(e,t);return s},r.prototype.tokenizeInternal=function(e,t){var i=this,n,s,o,a,l,c,u,g,f,h,p,C,y,B,v,D,L=e,H=L.length,j=0,$=0,V=this.hasCustom?0:Math.floor(e.length/10),W=new Array(V),_=[],A=this.trackStartLines?1:void 0,ae=this.trackStartLines?1:void 0,ge=(0,Xs.cloneEmptyGroups)(this.emptyGroups),re=this.trackStartLines,O=this.config.lineTerminatorsPattern,F=0,ue=[],he=[],ke=[],Fe=[];Object.freeze(Fe);var Ne=void 0;function oe(){return ue}function le(pr){var Ii=(0,Xs.charCodeToOptimizedIndex)(pr),es=he[Ii];return es===void 0?Fe:es}var we=function(pr){if(ke.length===1&&pr.tokenType.PUSH_MODE===void 0){var Ii=i.config.errorMessageProvider.buildUnableToPopLexerModeMessage(pr);_.push({offset:pr.startOffset,line:pr.startLine!==void 0?pr.startLine:void 0,column:pr.startColumn!==void 0?pr.startColumn:void 0,length:pr.image.length,message:Ii})}else{ke.pop();var es=(0,nr.last)(ke);ue=i.patternIdxToConfig[es],he=i.charCodeToPatternIdxToConfig[es],F=ue.length;var ua=i.canModeBeOptimized[es]&&i.config.safeMode===!1;he&&ua?Ne=le:Ne=oe}};function fe(pr){ke.push(pr),he=this.charCodeToPatternIdxToConfig[pr],ue=this.patternIdxToConfig[pr],F=ue.length,F=ue.length;var Ii=this.canModeBeOptimized[pr]&&this.config.safeMode===!1;he&&Ii?Ne=le:Ne=oe}fe.call(this,t);for(var Ae;jc.length){c=a,u=g,Ae=_e;break}}}break}}if(c!==null){if(f=c.length,h=Ae.group,h!==void 0&&(p=Ae.tokenTypeIdx,C=this.createTokenInstance(c,j,p,Ae.tokenType,A,ae,f),this.handlePayload(C,u),h===!1?$=this.addToken(W,$,C):ge[h].push(C)),e=this.chopInput(e,f),j=j+f,ae=this.computeNewColumn(ae,f),re===!0&&Ae.canLineTerminator===!0){var It=0,Or=void 0,ii=void 0;O.lastIndex=0;do Or=O.test(c),Or===!0&&(ii=O.lastIndex-1,It++);while(Or===!0);It!==0&&(A=A+It,ae=f-ii,this.updateTokenEndLineColumnLocation(C,h,ii,It,A,ae,f))}this.handleModes(Ae,we,fe,C)}else{for(var gi=j,hr=A,fi=ae,ni=!1;!ni&&j <"+e+">");var n=(0,nr.timer)(t),s=n.time,o=n.value,a=s>10?console.warn:console.log;return this.traceInitIndent time: "+s+"ms"),this.traceInitIndent--,o}else return t()},r.SKIPPED="This marks a skipped Token pattern, this means each token identified by it willbe consumed and then thrown into oblivion, this can be used to for example to completely ignore whitespace.",r.NA=/NOT_APPLICABLE/,r}();fc.Lexer=jEe});var NA=w(bi=>{"use strict";Object.defineProperty(bi,"__esModule",{value:!0});bi.tokenMatcher=bi.createTokenInstance=bi.EOF=bi.createToken=bi.hasTokenLabel=bi.tokenName=bi.tokenLabel=void 0;var Zs=Gt(),qEe=yd(),Hv=Vg();function JEe(r){return Ej(r)?r.LABEL:r.name}bi.tokenLabel=JEe;function WEe(r){return r.name}bi.tokenName=WEe;function Ej(r){return(0,Zs.isString)(r.LABEL)&&r.LABEL!==""}bi.hasTokenLabel=Ej;var zEe="parent",uj="categories",gj="label",fj="group",hj="push_mode",pj="pop_mode",dj="longer_alt",Cj="line_breaks",mj="start_chars_hint";function Ij(r){return VEe(r)}bi.createToken=Ij;function VEe(r){var e=r.pattern,t={};if(t.name=r.name,(0,Zs.isUndefined)(e)||(t.PATTERN=e),(0,Zs.has)(r,zEe))throw`The parent property is no longer supported. -See: https://github.com/chevrotain/chevrotain/issues/564#issuecomment-349062346 for details.`;return(0,Zs.has)(r,uj)&&(t.CATEGORIES=r[uj]),(0,Hv.augmentTokenTypes)([t]),(0,Zs.has)(r,gj)&&(t.LABEL=r[gj]),(0,Zs.has)(r,fj)&&(t.GROUP=r[fj]),(0,Zs.has)(r,pj)&&(t.POP_MODE=r[pj]),(0,Zs.has)(r,hj)&&(t.PUSH_MODE=r[hj]),(0,Zs.has)(r,dj)&&(t.LONGER_ALT=r[dj]),(0,Zs.has)(r,Cj)&&(t.LINE_BREAKS=r[Cj]),(0,Zs.has)(r,mj)&&(t.START_CHARS_HINT=r[mj]),t}bi.EOF=Ij({name:"EOF",pattern:qEe.Lexer.NA});(0,Hv.augmentTokenTypes)([bi.EOF]);function XEe(r,e,t,i,n,s,o,a){return{image:e,startOffset:t,endOffset:i,startLine:n,endLine:s,startColumn:o,endColumn:a,tokenTypeIdx:r.tokenTypeIdx,tokenType:r}}bi.createTokenInstance=XEe;function ZEe(r,e){return(0,Hv.tokenStructuredMatcher)(r,e)}bi.tokenMatcher=ZEe});var dn=w(zt=>{"use strict";var va=zt&&zt.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(zt,"__esModule",{value:!0});zt.serializeProduction=zt.serializeGrammar=zt.Terminal=zt.Alternation=zt.RepetitionWithSeparator=zt.Repetition=zt.RepetitionMandatoryWithSeparator=zt.RepetitionMandatory=zt.Option=zt.Alternative=zt.Rule=zt.NonTerminal=zt.AbstractProduction=void 0;var Ar=Gt(),_Ee=NA(),ko=function(){function r(e){this._definition=e}return Object.defineProperty(r.prototype,"definition",{get:function(){return this._definition},set:function(e){this._definition=e},enumerable:!1,configurable:!0}),r.prototype.accept=function(e){e.visit(this),(0,Ar.forEach)(this.definition,function(t){t.accept(e)})},r}();zt.AbstractProduction=ko;var yj=function(r){va(e,r);function e(t){var i=r.call(this,[])||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return Object.defineProperty(e.prototype,"definition",{get:function(){return this.referencedRule!==void 0?this.referencedRule.definition:[]},set:function(t){},enumerable:!1,configurable:!0}),e.prototype.accept=function(t){t.visit(this)},e}(ko);zt.NonTerminal=yj;var wj=function(r){va(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.orgText="",(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(ko);zt.Rule=wj;var Bj=function(r){va(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.ignoreAmbiguities=!1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(ko);zt.Alternative=Bj;var Qj=function(r){va(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(ko);zt.Option=Qj;var bj=function(r){va(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(ko);zt.RepetitionMandatory=bj;var Sj=function(r){va(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(ko);zt.RepetitionMandatoryWithSeparator=Sj;var vj=function(r){va(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(ko);zt.Repetition=vj;var xj=function(r){va(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(ko);zt.RepetitionWithSeparator=xj;var Pj=function(r){va(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,i.ignoreAmbiguities=!1,i.hasPredicates=!1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return Object.defineProperty(e.prototype,"definition",{get:function(){return this._definition},set:function(t){this._definition=t},enumerable:!1,configurable:!0}),e}(ko);zt.Alternation=Pj;var ry=function(){function r(e){this.idx=1,(0,Ar.assign)(this,(0,Ar.pick)(e,function(t){return t!==void 0}))}return r.prototype.accept=function(e){e.visit(this)},r}();zt.Terminal=ry;function $Ee(r){return(0,Ar.map)(r,Bd)}zt.serializeGrammar=$Ee;function Bd(r){function e(s){return(0,Ar.map)(s,Bd)}if(r instanceof yj){var t={type:"NonTerminal",name:r.nonTerminalName,idx:r.idx};return(0,Ar.isString)(r.label)&&(t.label=r.label),t}else{if(r instanceof Bj)return{type:"Alternative",definition:e(r.definition)};if(r instanceof Qj)return{type:"Option",idx:r.idx,definition:e(r.definition)};if(r instanceof bj)return{type:"RepetitionMandatory",idx:r.idx,definition:e(r.definition)};if(r instanceof Sj)return{type:"RepetitionMandatoryWithSeparator",idx:r.idx,separator:Bd(new ry({terminalType:r.separator})),definition:e(r.definition)};if(r instanceof xj)return{type:"RepetitionWithSeparator",idx:r.idx,separator:Bd(new ry({terminalType:r.separator})),definition:e(r.definition)};if(r instanceof vj)return{type:"Repetition",idx:r.idx,definition:e(r.definition)};if(r instanceof Pj)return{type:"Alternation",idx:r.idx,definition:e(r.definition)};if(r instanceof ry){var i={type:"Terminal",name:r.terminalType.name,label:(0,_Ee.tokenLabel)(r.terminalType),idx:r.idx};(0,Ar.isString)(r.label)&&(i.terminalLabel=r.label);var n=r.terminalType.PATTERN;return r.terminalType.PATTERN&&(i.pattern=(0,Ar.isRegExp)(n)?n.source:n),i}else{if(r instanceof wj)return{type:"Rule",name:r.name,orgText:r.orgText,definition:e(r.definition)};throw Error("non exhaustive match")}}}zt.serializeProduction=Bd});var ny=w(iy=>{"use strict";Object.defineProperty(iy,"__esModule",{value:!0});iy.RestWalker=void 0;var Gv=Gt(),Cn=dn(),eIe=function(){function r(){}return r.prototype.walk=function(e,t){var i=this;t===void 0&&(t=[]),(0,Gv.forEach)(e.definition,function(n,s){var o=(0,Gv.drop)(e.definition,s+1);if(n instanceof Cn.NonTerminal)i.walkProdRef(n,o,t);else if(n instanceof Cn.Terminal)i.walkTerminal(n,o,t);else if(n instanceof Cn.Alternative)i.walkFlat(n,o,t);else if(n instanceof Cn.Option)i.walkOption(n,o,t);else if(n instanceof Cn.RepetitionMandatory)i.walkAtLeastOne(n,o,t);else if(n instanceof Cn.RepetitionMandatoryWithSeparator)i.walkAtLeastOneSep(n,o,t);else if(n instanceof Cn.RepetitionWithSeparator)i.walkManySep(n,o,t);else if(n instanceof Cn.Repetition)i.walkMany(n,o,t);else if(n instanceof Cn.Alternation)i.walkOr(n,o,t);else throw Error("non exhaustive match")})},r.prototype.walkTerminal=function(e,t,i){},r.prototype.walkProdRef=function(e,t,i){},r.prototype.walkFlat=function(e,t,i){var n=t.concat(i);this.walk(e,n)},r.prototype.walkOption=function(e,t,i){var n=t.concat(i);this.walk(e,n)},r.prototype.walkAtLeastOne=function(e,t,i){var n=[new Cn.Option({definition:e.definition})].concat(t,i);this.walk(e,n)},r.prototype.walkAtLeastOneSep=function(e,t,i){var n=Dj(e,t,i);this.walk(e,n)},r.prototype.walkMany=function(e,t,i){var n=[new Cn.Option({definition:e.definition})].concat(t,i);this.walk(e,n)},r.prototype.walkManySep=function(e,t,i){var n=Dj(e,t,i);this.walk(e,n)},r.prototype.walkOr=function(e,t,i){var n=this,s=t.concat(i);(0,Gv.forEach)(e.definition,function(o){var a=new Cn.Alternative({definition:[o]});n.walk(a,s)})},r}();iy.RestWalker=eIe;function Dj(r,e,t){var i=[new Cn.Option({definition:[new Cn.Terminal({terminalType:r.separator})].concat(r.definition)})],n=i.concat(e,t);return n}});var Xg=w(sy=>{"use strict";Object.defineProperty(sy,"__esModule",{value:!0});sy.GAstVisitor=void 0;var Ro=dn(),tIe=function(){function r(){}return r.prototype.visit=function(e){var t=e;switch(t.constructor){case Ro.NonTerminal:return this.visitNonTerminal(t);case Ro.Alternative:return this.visitAlternative(t);case Ro.Option:return this.visitOption(t);case Ro.RepetitionMandatory:return this.visitRepetitionMandatory(t);case Ro.RepetitionMandatoryWithSeparator:return this.visitRepetitionMandatoryWithSeparator(t);case Ro.RepetitionWithSeparator:return this.visitRepetitionWithSeparator(t);case Ro.Repetition:return this.visitRepetition(t);case Ro.Alternation:return this.visitAlternation(t);case Ro.Terminal:return this.visitTerminal(t);case Ro.Rule:return this.visitRule(t);default:throw Error("non exhaustive match")}},r.prototype.visitNonTerminal=function(e){},r.prototype.visitAlternative=function(e){},r.prototype.visitOption=function(e){},r.prototype.visitRepetition=function(e){},r.prototype.visitRepetitionMandatory=function(e){},r.prototype.visitRepetitionMandatoryWithSeparator=function(e){},r.prototype.visitRepetitionWithSeparator=function(e){},r.prototype.visitAlternation=function(e){},r.prototype.visitTerminal=function(e){},r.prototype.visitRule=function(e){},r}();sy.GAstVisitor=tIe});var bd=w(Mi=>{"use strict";var rIe=Mi&&Mi.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(Mi,"__esModule",{value:!0});Mi.collectMethods=Mi.DslMethodsCollectorVisitor=Mi.getProductionDslName=Mi.isBranchingProd=Mi.isOptionalProd=Mi.isSequenceProd=void 0;var Qd=Gt(),Qr=dn(),iIe=Xg();function nIe(r){return r instanceof Qr.Alternative||r instanceof Qr.Option||r instanceof Qr.Repetition||r instanceof Qr.RepetitionMandatory||r instanceof Qr.RepetitionMandatoryWithSeparator||r instanceof Qr.RepetitionWithSeparator||r instanceof Qr.Terminal||r instanceof Qr.Rule}Mi.isSequenceProd=nIe;function Yv(r,e){e===void 0&&(e=[]);var t=r instanceof Qr.Option||r instanceof Qr.Repetition||r instanceof Qr.RepetitionWithSeparator;return t?!0:r instanceof Qr.Alternation?(0,Qd.some)(r.definition,function(i){return Yv(i,e)}):r instanceof Qr.NonTerminal&&(0,Qd.contains)(e,r)?!1:r instanceof Qr.AbstractProduction?(r instanceof Qr.NonTerminal&&e.push(r),(0,Qd.every)(r.definition,function(i){return Yv(i,e)})):!1}Mi.isOptionalProd=Yv;function sIe(r){return r instanceof Qr.Alternation}Mi.isBranchingProd=sIe;function oIe(r){if(r instanceof Qr.NonTerminal)return"SUBRULE";if(r instanceof Qr.Option)return"OPTION";if(r instanceof Qr.Alternation)return"OR";if(r instanceof Qr.RepetitionMandatory)return"AT_LEAST_ONE";if(r instanceof Qr.RepetitionMandatoryWithSeparator)return"AT_LEAST_ONE_SEP";if(r instanceof Qr.RepetitionWithSeparator)return"MANY_SEP";if(r instanceof Qr.Repetition)return"MANY";if(r instanceof Qr.Terminal)return"CONSUME";throw Error("non exhaustive match")}Mi.getProductionDslName=oIe;var kj=function(r){rIe(e,r);function e(){var t=r!==null&&r.apply(this,arguments)||this;return t.separator="-",t.dslMethods={option:[],alternation:[],repetition:[],repetitionWithSeparator:[],repetitionMandatory:[],repetitionMandatoryWithSeparator:[]},t}return e.prototype.reset=function(){this.dslMethods={option:[],alternation:[],repetition:[],repetitionWithSeparator:[],repetitionMandatory:[],repetitionMandatoryWithSeparator:[]}},e.prototype.visitTerminal=function(t){var i=t.terminalType.name+this.separator+"Terminal";(0,Qd.has)(this.dslMethods,i)||(this.dslMethods[i]=[]),this.dslMethods[i].push(t)},e.prototype.visitNonTerminal=function(t){var i=t.nonTerminalName+this.separator+"Terminal";(0,Qd.has)(this.dslMethods,i)||(this.dslMethods[i]=[]),this.dslMethods[i].push(t)},e.prototype.visitOption=function(t){this.dslMethods.option.push(t)},e.prototype.visitRepetitionWithSeparator=function(t){this.dslMethods.repetitionWithSeparator.push(t)},e.prototype.visitRepetitionMandatory=function(t){this.dslMethods.repetitionMandatory.push(t)},e.prototype.visitRepetitionMandatoryWithSeparator=function(t){this.dslMethods.repetitionMandatoryWithSeparator.push(t)},e.prototype.visitRepetition=function(t){this.dslMethods.repetition.push(t)},e.prototype.visitAlternation=function(t){this.dslMethods.alternation.push(t)},e}(iIe.GAstVisitor);Mi.DslMethodsCollectorVisitor=kj;var oy=new kj;function aIe(r){oy.reset(),r.accept(oy);var e=oy.dslMethods;return oy.reset(),e}Mi.collectMethods=aIe});var qv=w(Fo=>{"use strict";Object.defineProperty(Fo,"__esModule",{value:!0});Fo.firstForTerminal=Fo.firstForBranching=Fo.firstForSequence=Fo.first=void 0;var ay=Gt(),Rj=dn(),jv=bd();function Ay(r){if(r instanceof Rj.NonTerminal)return Ay(r.referencedRule);if(r instanceof Rj.Terminal)return Lj(r);if((0,jv.isSequenceProd)(r))return Fj(r);if((0,jv.isBranchingProd)(r))return Nj(r);throw Error("non exhaustive match")}Fo.first=Ay;function Fj(r){for(var e=[],t=r.definition,i=0,n=t.length>i,s,o=!0;n&&o;)s=t[i],o=(0,jv.isOptionalProd)(s),e=e.concat(Ay(s)),i=i+1,n=t.length>i;return(0,ay.uniq)(e)}Fo.firstForSequence=Fj;function Nj(r){var e=(0,ay.map)(r.definition,function(t){return Ay(t)});return(0,ay.uniq)((0,ay.flatten)(e))}Fo.firstForBranching=Nj;function Lj(r){return[r.terminalType]}Fo.firstForTerminal=Lj});var Jv=w(ly=>{"use strict";Object.defineProperty(ly,"__esModule",{value:!0});ly.IN=void 0;ly.IN="_~IN~_"});var Uj=w(us=>{"use strict";var AIe=us&&us.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(us,"__esModule",{value:!0});us.buildInProdFollowPrefix=us.buildBetweenProdsFollowPrefix=us.computeAllProdsFollows=us.ResyncFollowsWalker=void 0;var lIe=ny(),cIe=qv(),Tj=Gt(),Oj=Jv(),uIe=dn(),Mj=function(r){AIe(e,r);function e(t){var i=r.call(this)||this;return i.topProd=t,i.follows={},i}return e.prototype.startWalking=function(){return this.walk(this.topProd),this.follows},e.prototype.walkTerminal=function(t,i,n){},e.prototype.walkProdRef=function(t,i,n){var s=Kj(t.referencedRule,t.idx)+this.topProd.name,o=i.concat(n),a=new uIe.Alternative({definition:o}),l=(0,cIe.first)(a);this.follows[s]=l},e}(lIe.RestWalker);us.ResyncFollowsWalker=Mj;function gIe(r){var e={};return(0,Tj.forEach)(r,function(t){var i=new Mj(t).startWalking();(0,Tj.assign)(e,i)}),e}us.computeAllProdsFollows=gIe;function Kj(r,e){return r.name+e+Oj.IN}us.buildBetweenProdsFollowPrefix=Kj;function fIe(r){var e=r.terminalType.name;return e+r.idx+Oj.IN}us.buildInProdFollowPrefix=fIe});var Sd=w(xa=>{"use strict";Object.defineProperty(xa,"__esModule",{value:!0});xa.defaultGrammarValidatorErrorProvider=xa.defaultGrammarResolverErrorProvider=xa.defaultParserErrorProvider=void 0;var Zg=NA(),hIe=Gt(),_s=Gt(),Wv=dn(),Hj=bd();xa.defaultParserErrorProvider={buildMismatchTokenMessage:function(r){var e=r.expected,t=r.actual,i=r.previous,n=r.ruleName,s=(0,Zg.hasTokenLabel)(e),o=s?"--> "+(0,Zg.tokenLabel)(e)+" <--":"token of type --> "+e.name+" <--",a="Expecting "+o+" but found --> '"+t.image+"' <--";return a},buildNotAllInputParsedMessage:function(r){var e=r.firstRedundant,t=r.ruleName;return"Redundant input, expecting EOF but found: "+e.image},buildNoViableAltMessage:function(r){var e=r.expectedPathsPerAlt,t=r.actual,i=r.previous,n=r.customUserDescription,s=r.ruleName,o="Expecting: ",a=(0,_s.first)(t).image,l=` -but found: '`+a+"'";if(n)return o+n+l;var c=(0,_s.reduce)(e,function(h,p){return h.concat(p)},[]),u=(0,_s.map)(c,function(h){return"["+(0,_s.map)(h,function(p){return(0,Zg.tokenLabel)(p)}).join(", ")+"]"}),g=(0,_s.map)(u,function(h,p){return" "+(p+1)+". "+h}),f=`one of these possible Token sequences: -`+g.join(` -`);return o+f+l},buildEarlyExitMessage:function(r){var e=r.expectedIterationPaths,t=r.actual,i=r.customUserDescription,n=r.ruleName,s="Expecting: ",o=(0,_s.first)(t).image,a=` -but found: '`+o+"'";if(i)return s+i+a;var l=(0,_s.map)(e,function(u){return"["+(0,_s.map)(u,function(g){return(0,Zg.tokenLabel)(g)}).join(",")+"]"}),c=`expecting at least one iteration which starts with one of these possible Token sequences:: - `+("<"+l.join(" ,")+">");return s+c+a}};Object.freeze(xa.defaultParserErrorProvider);xa.defaultGrammarResolverErrorProvider={buildRuleNotFoundError:function(r,e){var t="Invalid grammar, reference to a rule which is not defined: ->"+e.nonTerminalName+`<- -inside top level rule: ->`+r.name+"<-";return t}};xa.defaultGrammarValidatorErrorProvider={buildDuplicateFoundError:function(r,e){function t(u){return u instanceof Wv.Terminal?u.terminalType.name:u instanceof Wv.NonTerminal?u.nonTerminalName:""}var i=r.name,n=(0,_s.first)(e),s=n.idx,o=(0,Hj.getProductionDslName)(n),a=t(n),l=s>0,c="->"+o+(l?s:"")+"<- "+(a?"with argument: ->"+a+"<-":"")+` - appears more than once (`+e.length+" times) in the top level rule: ->"+i+`<-. - For further details see: https://chevrotain.io/docs/FAQ.html#NUMERICAL_SUFFIXES - `;return c=c.replace(/[ \t]+/g," "),c=c.replace(/\s\s+/g,` -`),c},buildNamespaceConflictError:function(r){var e=`Namespace conflict found in grammar. -`+("The grammar has both a Terminal(Token) and a Non-Terminal(Rule) named: <"+r.name+`>. -`)+`To resolve this make sure each Terminal and Non-Terminal names are unique -This is easy to accomplish by using the convention that Terminal names start with an uppercase letter -and Non-Terminal names start with a lower case letter.`;return e},buildAlternationPrefixAmbiguityError:function(r){var e=(0,_s.map)(r.prefixPath,function(n){return(0,Zg.tokenLabel)(n)}).join(", "),t=r.alternation.idx===0?"":r.alternation.idx,i="Ambiguous alternatives: <"+r.ambiguityIndices.join(" ,")+`> due to common lookahead prefix -`+("in inside <"+r.topLevelRule.name+`> Rule, -`)+("<"+e+`> may appears as a prefix path in all these alternatives. -`)+`See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#COMMON_PREFIX -For Further details.`;return i},buildAlternationAmbiguityError:function(r){var e=(0,_s.map)(r.prefixPath,function(n){return(0,Zg.tokenLabel)(n)}).join(", "),t=r.alternation.idx===0?"":r.alternation.idx,i="Ambiguous Alternatives Detected: <"+r.ambiguityIndices.join(" ,")+"> in "+(" inside <"+r.topLevelRule.name+`> Rule, -`)+("<"+e+`> may appears as a prefix path in all these alternatives. -`);return i=i+`See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#AMBIGUOUS_ALTERNATIVES -For Further details.`,i},buildEmptyRepetitionError:function(r){var e=(0,Hj.getProductionDslName)(r.repetition);r.repetition.idx!==0&&(e+=r.repetition.idx);var t="The repetition <"+e+"> within Rule <"+r.topLevelRule.name+`> can never consume any tokens. -This could lead to an infinite loop.`;return t},buildTokenNameError:function(r){return"deprecated"},buildEmptyAlternationError:function(r){var e="Ambiguous empty alternative: <"+(r.emptyChoiceIdx+1)+">"+(" in inside <"+r.topLevelRule.name+`> Rule. -`)+"Only the last alternative may be an empty alternative.";return e},buildTooManyAlternativesError:function(r){var e=`An Alternation cannot have more than 256 alternatives: -`+(" inside <"+r.topLevelRule.name+`> Rule. - has `+(r.alternation.definition.length+1)+" alternatives.");return e},buildLeftRecursionError:function(r){var e=r.topLevelRule.name,t=hIe.map(r.leftRecursionPath,function(s){return s.name}),i=e+" --> "+t.concat([e]).join(" --> "),n=`Left Recursion found in grammar. -`+("rule: <"+e+`> can be invoked from itself (directly or indirectly) -`)+(`without consuming any Tokens. The grammar path that causes this is: - `+i+` -`)+` To fix this refactor your grammar to remove the left recursion. -see: https://en.wikipedia.org/wiki/LL_parser#Left_Factoring.`;return n},buildInvalidRuleNameError:function(r){return"deprecated"},buildDuplicateRuleNameError:function(r){var e;r.topLevelRule instanceof Wv.Rule?e=r.topLevelRule.name:e=r.topLevelRule;var t="Duplicate definition, rule: ->"+e+"<- is already defined in the grammar: ->"+r.grammarName+"<-";return t}}});var jj=w(LA=>{"use strict";var pIe=LA&&LA.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(LA,"__esModule",{value:!0});LA.GastRefResolverVisitor=LA.resolveGrammar=void 0;var dIe=Gn(),Gj=Gt(),CIe=Xg();function mIe(r,e){var t=new Yj(r,e);return t.resolveRefs(),t.errors}LA.resolveGrammar=mIe;var Yj=function(r){pIe(e,r);function e(t,i){var n=r.call(this)||this;return n.nameToTopRule=t,n.errMsgProvider=i,n.errors=[],n}return e.prototype.resolveRefs=function(){var t=this;(0,Gj.forEach)((0,Gj.values)(this.nameToTopRule),function(i){t.currTopLevel=i,i.accept(t)})},e.prototype.visitNonTerminal=function(t){var i=this.nameToTopRule[t.nonTerminalName];if(i)t.referencedRule=i;else{var n=this.errMsgProvider.buildRuleNotFoundError(this.currTopLevel,t);this.errors.push({message:n,type:dIe.ParserDefinitionErrorType.UNRESOLVED_SUBRULE_REF,ruleName:this.currTopLevel.name,unresolvedRefName:t.nonTerminalName})}},e}(CIe.GAstVisitor);LA.GastRefResolverVisitor=Yj});var xd=w(Nr=>{"use strict";var hc=Nr&&Nr.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(Nr,"__esModule",{value:!0});Nr.nextPossibleTokensAfter=Nr.possiblePathsFrom=Nr.NextTerminalAfterAtLeastOneSepWalker=Nr.NextTerminalAfterAtLeastOneWalker=Nr.NextTerminalAfterManySepWalker=Nr.NextTerminalAfterManyWalker=Nr.AbstractNextTerminalAfterProductionWalker=Nr.NextAfterTokenWalker=Nr.AbstractNextPossibleTokensWalker=void 0;var qj=ny(),Kt=Gt(),EIe=qv(),kt=dn(),Jj=function(r){hc(e,r);function e(t,i){var n=r.call(this)||this;return n.topProd=t,n.path=i,n.possibleTokTypes=[],n.nextProductionName="",n.nextProductionOccurrence=0,n.found=!1,n.isAtEndOfPath=!1,n}return e.prototype.startWalking=function(){if(this.found=!1,this.path.ruleStack[0]!==this.topProd.name)throw Error("The path does not start with the walker's top Rule!");return this.ruleStack=(0,Kt.cloneArr)(this.path.ruleStack).reverse(),this.occurrenceStack=(0,Kt.cloneArr)(this.path.occurrenceStack).reverse(),this.ruleStack.pop(),this.occurrenceStack.pop(),this.updateExpectedNext(),this.walk(this.topProd),this.possibleTokTypes},e.prototype.walk=function(t,i){i===void 0&&(i=[]),this.found||r.prototype.walk.call(this,t,i)},e.prototype.walkProdRef=function(t,i,n){if(t.referencedRule.name===this.nextProductionName&&t.idx===this.nextProductionOccurrence){var s=i.concat(n);this.updateExpectedNext(),this.walk(t.referencedRule,s)}},e.prototype.updateExpectedNext=function(){(0,Kt.isEmpty)(this.ruleStack)?(this.nextProductionName="",this.nextProductionOccurrence=0,this.isAtEndOfPath=!0):(this.nextProductionName=this.ruleStack.pop(),this.nextProductionOccurrence=this.occurrenceStack.pop())},e}(qj.RestWalker);Nr.AbstractNextPossibleTokensWalker=Jj;var IIe=function(r){hc(e,r);function e(t,i){var n=r.call(this,t,i)||this;return n.path=i,n.nextTerminalName="",n.nextTerminalOccurrence=0,n.nextTerminalName=n.path.lastTok.name,n.nextTerminalOccurrence=n.path.lastTokOccurrence,n}return e.prototype.walkTerminal=function(t,i,n){if(this.isAtEndOfPath&&t.terminalType.name===this.nextTerminalName&&t.idx===this.nextTerminalOccurrence&&!this.found){var s=i.concat(n),o=new kt.Alternative({definition:s});this.possibleTokTypes=(0,EIe.first)(o),this.found=!0}},e}(Jj);Nr.NextAfterTokenWalker=IIe;var vd=function(r){hc(e,r);function e(t,i){var n=r.call(this)||this;return n.topRule=t,n.occurrence=i,n.result={token:void 0,occurrence:void 0,isEndOfRule:void 0},n}return e.prototype.startWalking=function(){return this.walk(this.topRule),this.result},e}(qj.RestWalker);Nr.AbstractNextTerminalAfterProductionWalker=vd;var yIe=function(r){hc(e,r);function e(){return r!==null&&r.apply(this,arguments)||this}return e.prototype.walkMany=function(t,i,n){if(t.idx===this.occurrence){var s=(0,Kt.first)(i.concat(n));this.result.isEndOfRule=s===void 0,s instanceof kt.Terminal&&(this.result.token=s.terminalType,this.result.occurrence=s.idx)}else r.prototype.walkMany.call(this,t,i,n)},e}(vd);Nr.NextTerminalAfterManyWalker=yIe;var wIe=function(r){hc(e,r);function e(){return r!==null&&r.apply(this,arguments)||this}return e.prototype.walkManySep=function(t,i,n){if(t.idx===this.occurrence){var s=(0,Kt.first)(i.concat(n));this.result.isEndOfRule=s===void 0,s instanceof kt.Terminal&&(this.result.token=s.terminalType,this.result.occurrence=s.idx)}else r.prototype.walkManySep.call(this,t,i,n)},e}(vd);Nr.NextTerminalAfterManySepWalker=wIe;var BIe=function(r){hc(e,r);function e(){return r!==null&&r.apply(this,arguments)||this}return e.prototype.walkAtLeastOne=function(t,i,n){if(t.idx===this.occurrence){var s=(0,Kt.first)(i.concat(n));this.result.isEndOfRule=s===void 0,s instanceof kt.Terminal&&(this.result.token=s.terminalType,this.result.occurrence=s.idx)}else r.prototype.walkAtLeastOne.call(this,t,i,n)},e}(vd);Nr.NextTerminalAfterAtLeastOneWalker=BIe;var QIe=function(r){hc(e,r);function e(){return r!==null&&r.apply(this,arguments)||this}return e.prototype.walkAtLeastOneSep=function(t,i,n){if(t.idx===this.occurrence){var s=(0,Kt.first)(i.concat(n));this.result.isEndOfRule=s===void 0,s instanceof kt.Terminal&&(this.result.token=s.terminalType,this.result.occurrence=s.idx)}else r.prototype.walkAtLeastOneSep.call(this,t,i,n)},e}(vd);Nr.NextTerminalAfterAtLeastOneSepWalker=QIe;function Wj(r,e,t){t===void 0&&(t=[]),t=(0,Kt.cloneArr)(t);var i=[],n=0;function s(c){return c.concat((0,Kt.drop)(r,n+1))}function o(c){var u=Wj(s(c),e,t);return i.concat(u)}for(;t.length=0;ge--){var re=B.definition[ge],O={idx:p,def:re.definition.concat((0,Kt.drop)(h)),ruleStack:C,occurrenceStack:y};g.push(O),g.push(o)}else if(B instanceof kt.Alternative)g.push({idx:p,def:B.definition.concat((0,Kt.drop)(h)),ruleStack:C,occurrenceStack:y});else if(B instanceof kt.Rule)g.push(SIe(B,p,C,y));else throw Error("non exhaustive match")}}return u}Nr.nextPossibleTokensAfter=bIe;function SIe(r,e,t,i){var n=(0,Kt.cloneArr)(t);n.push(r.name);var s=(0,Kt.cloneArr)(i);return s.push(1),{idx:e,def:r.definition,ruleStack:n,occurrenceStack:s}}});var Pd=w(Zt=>{"use strict";var Xj=Zt&&Zt.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(Zt,"__esModule",{value:!0});Zt.areTokenCategoriesNotUsed=Zt.isStrictPrefixOfPath=Zt.containsPath=Zt.getLookaheadPathsForOptionalProd=Zt.getLookaheadPathsForOr=Zt.lookAheadSequenceFromAlternatives=Zt.buildSingleAlternativeLookaheadFunction=Zt.buildAlternativesLookAheadFunc=Zt.buildLookaheadFuncForOptionalProd=Zt.buildLookaheadFuncForOr=Zt.getProdType=Zt.PROD_TYPE=void 0;var sr=Gt(),zj=xd(),vIe=ny(),cy=Vg(),TA=dn(),xIe=Xg(),oi;(function(r){r[r.OPTION=0]="OPTION",r[r.REPETITION=1]="REPETITION",r[r.REPETITION_MANDATORY=2]="REPETITION_MANDATORY",r[r.REPETITION_MANDATORY_WITH_SEPARATOR=3]="REPETITION_MANDATORY_WITH_SEPARATOR",r[r.REPETITION_WITH_SEPARATOR=4]="REPETITION_WITH_SEPARATOR",r[r.ALTERNATION=5]="ALTERNATION"})(oi=Zt.PROD_TYPE||(Zt.PROD_TYPE={}));function PIe(r){if(r instanceof TA.Option)return oi.OPTION;if(r instanceof TA.Repetition)return oi.REPETITION;if(r instanceof TA.RepetitionMandatory)return oi.REPETITION_MANDATORY;if(r instanceof TA.RepetitionMandatoryWithSeparator)return oi.REPETITION_MANDATORY_WITH_SEPARATOR;if(r instanceof TA.RepetitionWithSeparator)return oi.REPETITION_WITH_SEPARATOR;if(r instanceof TA.Alternation)return oi.ALTERNATION;throw Error("non exhaustive match")}Zt.getProdType=PIe;function DIe(r,e,t,i,n,s){var o=_j(r,e,t),a=Xv(o)?cy.tokenStructuredMatcherNoCategories:cy.tokenStructuredMatcher;return s(o,i,a,n)}Zt.buildLookaheadFuncForOr=DIe;function kIe(r,e,t,i,n,s){var o=$j(r,e,n,t),a=Xv(o)?cy.tokenStructuredMatcherNoCategories:cy.tokenStructuredMatcher;return s(o[0],a,i)}Zt.buildLookaheadFuncForOptionalProd=kIe;function RIe(r,e,t,i){var n=r.length,s=(0,sr.every)(r,function(l){return(0,sr.every)(l,function(c){return c.length===1})});if(e)return function(l){for(var c=(0,sr.map)(l,function(D){return D.GATE}),u=0;u{"use strict";var Zv=Vt&&Vt.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(Vt,"__esModule",{value:!0});Vt.checkPrefixAlternativesAmbiguities=Vt.validateSomeNonEmptyLookaheadPath=Vt.validateTooManyAlts=Vt.RepetionCollector=Vt.validateAmbiguousAlternationAlternatives=Vt.validateEmptyOrAlternative=Vt.getFirstNoneTerminal=Vt.validateNoLeftRecursion=Vt.validateRuleIsOverridden=Vt.validateRuleDoesNotAlreadyExist=Vt.OccurrenceValidationCollector=Vt.identifyProductionForDuplicates=Vt.validateGrammar=void 0;var er=Gt(),br=Gt(),No=Gn(),_v=bd(),_g=Pd(),OIe=xd(),$s=dn(),$v=Xg();function MIe(r,e,t,i,n){var s=er.map(r,function(h){return KIe(h,i)}),o=er.map(r,function(h){return ex(h,h,i)}),a=[],l=[],c=[];(0,br.every)(o,br.isEmpty)&&(a=(0,br.map)(r,function(h){return sq(h,i)}),l=(0,br.map)(r,function(h){return oq(h,e,i)}),c=lq(r,e,i));var u=GIe(r,t,i),g=(0,br.map)(r,function(h){return Aq(h,i)}),f=(0,br.map)(r,function(h){return nq(h,r,n,i)});return er.flatten(s.concat(c,o,a,l,u,g,f))}Vt.validateGrammar=MIe;function KIe(r,e){var t=new iq;r.accept(t);var i=t.allProductions,n=er.groupBy(i,tq),s=er.pick(n,function(a){return a.length>1}),o=er.map(er.values(s),function(a){var l=er.first(a),c=e.buildDuplicateFoundError(r,a),u=(0,_v.getProductionDslName)(l),g={message:c,type:No.ParserDefinitionErrorType.DUPLICATE_PRODUCTIONS,ruleName:r.name,dslName:u,occurrence:l.idx},f=rq(l);return f&&(g.parameter=f),g});return o}function tq(r){return(0,_v.getProductionDslName)(r)+"_#_"+r.idx+"_#_"+rq(r)}Vt.identifyProductionForDuplicates=tq;function rq(r){return r instanceof $s.Terminal?r.terminalType.name:r instanceof $s.NonTerminal?r.nonTerminalName:""}var iq=function(r){Zv(e,r);function e(){var t=r!==null&&r.apply(this,arguments)||this;return t.allProductions=[],t}return e.prototype.visitNonTerminal=function(t){this.allProductions.push(t)},e.prototype.visitOption=function(t){this.allProductions.push(t)},e.prototype.visitRepetitionWithSeparator=function(t){this.allProductions.push(t)},e.prototype.visitRepetitionMandatory=function(t){this.allProductions.push(t)},e.prototype.visitRepetitionMandatoryWithSeparator=function(t){this.allProductions.push(t)},e.prototype.visitRepetition=function(t){this.allProductions.push(t)},e.prototype.visitAlternation=function(t){this.allProductions.push(t)},e.prototype.visitTerminal=function(t){this.allProductions.push(t)},e}($v.GAstVisitor);Vt.OccurrenceValidationCollector=iq;function nq(r,e,t,i){var n=[],s=(0,br.reduce)(e,function(a,l){return l.name===r.name?a+1:a},0);if(s>1){var o=i.buildDuplicateRuleNameError({topLevelRule:r,grammarName:t});n.push({message:o,type:No.ParserDefinitionErrorType.DUPLICATE_RULE_NAME,ruleName:r.name})}return n}Vt.validateRuleDoesNotAlreadyExist=nq;function UIe(r,e,t){var i=[],n;return er.contains(e,r)||(n="Invalid rule override, rule: ->"+r+"<- cannot be overridden in the grammar: ->"+t+"<-as it is not defined in any of the super grammars ",i.push({message:n,type:No.ParserDefinitionErrorType.INVALID_RULE_OVERRIDE,ruleName:r})),i}Vt.validateRuleIsOverridden=UIe;function ex(r,e,t,i){i===void 0&&(i=[]);var n=[],s=Dd(e.definition);if(er.isEmpty(s))return[];var o=r.name,a=er.contains(s,r);a&&n.push({message:t.buildLeftRecursionError({topLevelRule:r,leftRecursionPath:i}),type:No.ParserDefinitionErrorType.LEFT_RECURSION,ruleName:o});var l=er.difference(s,i.concat([r])),c=er.map(l,function(u){var g=er.cloneArr(i);return g.push(u),ex(r,u,t,g)});return n.concat(er.flatten(c))}Vt.validateNoLeftRecursion=ex;function Dd(r){var e=[];if(er.isEmpty(r))return e;var t=er.first(r);if(t instanceof $s.NonTerminal)e.push(t.referencedRule);else if(t instanceof $s.Alternative||t instanceof $s.Option||t instanceof $s.RepetitionMandatory||t instanceof $s.RepetitionMandatoryWithSeparator||t instanceof $s.RepetitionWithSeparator||t instanceof $s.Repetition)e=e.concat(Dd(t.definition));else if(t instanceof $s.Alternation)e=er.flatten(er.map(t.definition,function(o){return Dd(o.definition)}));else if(!(t instanceof $s.Terminal))throw Error("non exhaustive match");var i=(0,_v.isOptionalProd)(t),n=r.length>1;if(i&&n){var s=er.drop(r);return e.concat(Dd(s))}else return e}Vt.getFirstNoneTerminal=Dd;var tx=function(r){Zv(e,r);function e(){var t=r!==null&&r.apply(this,arguments)||this;return t.alternations=[],t}return e.prototype.visitAlternation=function(t){this.alternations.push(t)},e}($v.GAstVisitor);function sq(r,e){var t=new tx;r.accept(t);var i=t.alternations,n=er.reduce(i,function(s,o){var a=er.dropRight(o.definition),l=er.map(a,function(c,u){var g=(0,OIe.nextPossibleTokensAfter)([c],[],null,1);return er.isEmpty(g)?{message:e.buildEmptyAlternationError({topLevelRule:r,alternation:o,emptyChoiceIdx:u}),type:No.ParserDefinitionErrorType.NONE_LAST_EMPTY_ALT,ruleName:r.name,occurrence:o.idx,alternative:u+1}:null});return s.concat(er.compact(l))},[]);return n}Vt.validateEmptyOrAlternative=sq;function oq(r,e,t){var i=new tx;r.accept(i);var n=i.alternations;n=(0,br.reject)(n,function(o){return o.ignoreAmbiguities===!0});var s=er.reduce(n,function(o,a){var l=a.idx,c=a.maxLookahead||e,u=(0,_g.getLookaheadPathsForOr)(l,r,c,a),g=HIe(u,a,r,t),f=cq(u,a,r,t);return o.concat(g,f)},[]);return s}Vt.validateAmbiguousAlternationAlternatives=oq;var aq=function(r){Zv(e,r);function e(){var t=r!==null&&r.apply(this,arguments)||this;return t.allProductions=[],t}return e.prototype.visitRepetitionWithSeparator=function(t){this.allProductions.push(t)},e.prototype.visitRepetitionMandatory=function(t){this.allProductions.push(t)},e.prototype.visitRepetitionMandatoryWithSeparator=function(t){this.allProductions.push(t)},e.prototype.visitRepetition=function(t){this.allProductions.push(t)},e}($v.GAstVisitor);Vt.RepetionCollector=aq;function Aq(r,e){var t=new tx;r.accept(t);var i=t.alternations,n=er.reduce(i,function(s,o){return o.definition.length>255&&s.push({message:e.buildTooManyAlternativesError({topLevelRule:r,alternation:o}),type:No.ParserDefinitionErrorType.TOO_MANY_ALTS,ruleName:r.name,occurrence:o.idx}),s},[]);return n}Vt.validateTooManyAlts=Aq;function lq(r,e,t){var i=[];return(0,br.forEach)(r,function(n){var s=new aq;n.accept(s);var o=s.allProductions;(0,br.forEach)(o,function(a){var l=(0,_g.getProdType)(a),c=a.maxLookahead||e,u=a.idx,g=(0,_g.getLookaheadPathsForOptionalProd)(u,n,l,c),f=g[0];if((0,br.isEmpty)((0,br.flatten)(f))){var h=t.buildEmptyRepetitionError({topLevelRule:n,repetition:a});i.push({message:h,type:No.ParserDefinitionErrorType.NO_NON_EMPTY_LOOKAHEAD,ruleName:n.name})}})}),i}Vt.validateSomeNonEmptyLookaheadPath=lq;function HIe(r,e,t,i){var n=[],s=(0,br.reduce)(r,function(a,l,c){return e.definition[c].ignoreAmbiguities===!0||(0,br.forEach)(l,function(u){var g=[c];(0,br.forEach)(r,function(f,h){c!==h&&(0,_g.containsPath)(f,u)&&e.definition[h].ignoreAmbiguities!==!0&&g.push(h)}),g.length>1&&!(0,_g.containsPath)(n,u)&&(n.push(u),a.push({alts:g,path:u}))}),a},[]),o=er.map(s,function(a){var l=(0,br.map)(a.alts,function(u){return u+1}),c=i.buildAlternationAmbiguityError({topLevelRule:t,alternation:e,ambiguityIndices:l,prefixPath:a.path});return{message:c,type:No.ParserDefinitionErrorType.AMBIGUOUS_ALTS,ruleName:t.name,occurrence:e.idx,alternatives:[a.alts]}});return o}function cq(r,e,t,i){var n=[],s=(0,br.reduce)(r,function(o,a,l){var c=(0,br.map)(a,function(u){return{idx:l,path:u}});return o.concat(c)},[]);return(0,br.forEach)(s,function(o){var a=e.definition[o.idx];if(a.ignoreAmbiguities!==!0){var l=o.idx,c=o.path,u=(0,br.findAll)(s,function(f){return e.definition[f.idx].ignoreAmbiguities!==!0&&f.idx{"use strict";Object.defineProperty($g,"__esModule",{value:!0});$g.validateGrammar=$g.resolveGrammar=void 0;var ix=Gt(),YIe=jj(),jIe=rx(),uq=Sd();function qIe(r){r=(0,ix.defaults)(r,{errMsgProvider:uq.defaultGrammarResolverErrorProvider});var e={};return(0,ix.forEach)(r.rules,function(t){e[t.name]=t}),(0,YIe.resolveGrammar)(e,r.errMsgProvider)}$g.resolveGrammar=qIe;function JIe(r){return r=(0,ix.defaults)(r,{errMsgProvider:uq.defaultGrammarValidatorErrorProvider}),(0,jIe.validateGrammar)(r.rules,r.maxLookahead,r.tokenTypes,r.errMsgProvider,r.grammarName)}$g.validateGrammar=JIe});var ef=w(mn=>{"use strict";var kd=mn&&mn.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(mn,"__esModule",{value:!0});mn.EarlyExitException=mn.NotAllInputParsedException=mn.NoViableAltException=mn.MismatchedTokenException=mn.isRecognitionException=void 0;var WIe=Gt(),fq="MismatchedTokenException",hq="NoViableAltException",pq="EarlyExitException",dq="NotAllInputParsedException",Cq=[fq,hq,pq,dq];Object.freeze(Cq);function zIe(r){return(0,WIe.contains)(Cq,r.name)}mn.isRecognitionException=zIe;var uy=function(r){kd(e,r);function e(t,i){var n=this.constructor,s=r.call(this,t)||this;return s.token=i,s.resyncedTokens=[],Object.setPrototypeOf(s,n.prototype),Error.captureStackTrace&&Error.captureStackTrace(s,s.constructor),s}return e}(Error),VIe=function(r){kd(e,r);function e(t,i,n){var s=r.call(this,t,i)||this;return s.previousToken=n,s.name=fq,s}return e}(uy);mn.MismatchedTokenException=VIe;var XIe=function(r){kd(e,r);function e(t,i,n){var s=r.call(this,t,i)||this;return s.previousToken=n,s.name=hq,s}return e}(uy);mn.NoViableAltException=XIe;var ZIe=function(r){kd(e,r);function e(t,i){var n=r.call(this,t,i)||this;return n.name=dq,n}return e}(uy);mn.NotAllInputParsedException=ZIe;var _Ie=function(r){kd(e,r);function e(t,i,n){var s=r.call(this,t,i)||this;return s.previousToken=n,s.name=pq,s}return e}(uy);mn.EarlyExitException=_Ie});var sx=w(Ki=>{"use strict";Object.defineProperty(Ki,"__esModule",{value:!0});Ki.attemptInRepetitionRecovery=Ki.Recoverable=Ki.InRuleRecoveryException=Ki.IN_RULE_RECOVERY_EXCEPTION=Ki.EOF_FOLLOW_KEY=void 0;var gy=NA(),gs=Gt(),$Ie=ef(),eye=Jv(),tye=Gn();Ki.EOF_FOLLOW_KEY={};Ki.IN_RULE_RECOVERY_EXCEPTION="InRuleRecoveryException";function nx(r){this.name=Ki.IN_RULE_RECOVERY_EXCEPTION,this.message=r}Ki.InRuleRecoveryException=nx;nx.prototype=Error.prototype;var rye=function(){function r(){}return r.prototype.initRecoverable=function(e){this.firstAfterRepMap={},this.resyncFollows={},this.recoveryEnabled=(0,gs.has)(e,"recoveryEnabled")?e.recoveryEnabled:tye.DEFAULT_PARSER_CONFIG.recoveryEnabled,this.recoveryEnabled&&(this.attemptInRepetitionRecovery=mq)},r.prototype.getTokenToInsert=function(e){var t=(0,gy.createTokenInstance)(e,"",NaN,NaN,NaN,NaN,NaN,NaN);return t.isInsertedInRecovery=!0,t},r.prototype.canTokenTypeBeInsertedInRecovery=function(e){return!0},r.prototype.tryInRepetitionRecovery=function(e,t,i,n){for(var s=this,o=this.findReSyncTokenType(),a=this.exportLexerState(),l=[],c=!1,u=this.LA(1),g=this.LA(1),f=function(){var h=s.LA(0),p=s.errorMessageProvider.buildMismatchTokenMessage({expected:n,actual:u,previous:h,ruleName:s.getCurrRuleFullName()}),C=new $Ie.MismatchedTokenException(p,u,s.LA(0));C.resyncedTokens=(0,gs.dropRight)(l),s.SAVE_ERROR(C)};!c;)if(this.tokenMatcher(g,n)){f();return}else if(i.call(this)){f(),e.apply(this,t);return}else this.tokenMatcher(g,o)?c=!0:(g=this.SKIP_TOKEN(),this.addToResyncTokens(g,l));this.importLexerState(a)},r.prototype.shouldInRepetitionRecoveryBeTried=function(e,t,i){return!(i===!1||e===void 0||t===void 0||this.tokenMatcher(this.LA(1),e)||this.isBackTracking()||this.canPerformInRuleRecovery(e,this.getFollowsForInRuleRecovery(e,t)))},r.prototype.getFollowsForInRuleRecovery=function(e,t){var i=this.getCurrentGrammarPath(e,t),n=this.getNextPossibleTokenTypes(i);return n},r.prototype.tryInRuleRecovery=function(e,t){if(this.canRecoverWithSingleTokenInsertion(e,t)){var i=this.getTokenToInsert(e);return i}if(this.canRecoverWithSingleTokenDeletion(e)){var n=this.SKIP_TOKEN();return this.consumeToken(),n}throw new nx("sad sad panda")},r.prototype.canPerformInRuleRecovery=function(e,t){return this.canRecoverWithSingleTokenInsertion(e,t)||this.canRecoverWithSingleTokenDeletion(e)},r.prototype.canRecoverWithSingleTokenInsertion=function(e,t){var i=this;if(!this.canTokenTypeBeInsertedInRecovery(e)||(0,gs.isEmpty)(t))return!1;var n=this.LA(1),s=(0,gs.find)(t,function(o){return i.tokenMatcher(n,o)})!==void 0;return s},r.prototype.canRecoverWithSingleTokenDeletion=function(e){var t=this.tokenMatcher(this.LA(2),e);return t},r.prototype.isInCurrentRuleReSyncSet=function(e){var t=this.getCurrFollowKey(),i=this.getFollowSetFromFollowKey(t);return(0,gs.contains)(i,e)},r.prototype.findReSyncTokenType=function(){for(var e=this.flattenFollowSet(),t=this.LA(1),i=2;;){var n=t.tokenType;if((0,gs.contains)(e,n))return n;t=this.LA(i),i++}},r.prototype.getCurrFollowKey=function(){if(this.RULE_STACK.length===1)return Ki.EOF_FOLLOW_KEY;var e=this.getLastExplicitRuleShortName(),t=this.getLastExplicitRuleOccurrenceIndex(),i=this.getPreviousExplicitRuleShortName();return{ruleName:this.shortRuleNameToFullName(e),idxInCallingRule:t,inRule:this.shortRuleNameToFullName(i)}},r.prototype.buildFullFollowKeyStack=function(){var e=this,t=this.RULE_STACK,i=this.RULE_OCCURRENCE_STACK;return(0,gs.map)(t,function(n,s){return s===0?Ki.EOF_FOLLOW_KEY:{ruleName:e.shortRuleNameToFullName(n),idxInCallingRule:i[s],inRule:e.shortRuleNameToFullName(t[s-1])}})},r.prototype.flattenFollowSet=function(){var e=this,t=(0,gs.map)(this.buildFullFollowKeyStack(),function(i){return e.getFollowSetFromFollowKey(i)});return(0,gs.flatten)(t)},r.prototype.getFollowSetFromFollowKey=function(e){if(e===Ki.EOF_FOLLOW_KEY)return[gy.EOF];var t=e.ruleName+e.idxInCallingRule+eye.IN+e.inRule;return this.resyncFollows[t]},r.prototype.addToResyncTokens=function(e,t){return this.tokenMatcher(e,gy.EOF)||t.push(e),t},r.prototype.reSyncTo=function(e){for(var t=[],i=this.LA(1);this.tokenMatcher(i,e)===!1;)i=this.SKIP_TOKEN(),this.addToResyncTokens(i,t);return(0,gs.dropRight)(t)},r.prototype.attemptInRepetitionRecovery=function(e,t,i,n,s,o,a){},r.prototype.getCurrentGrammarPath=function(e,t){var i=this.getHumanReadableRuleStack(),n=(0,gs.cloneArr)(this.RULE_OCCURRENCE_STACK),s={ruleStack:i,occurrenceStack:n,lastTok:e,lastTokOccurrence:t};return s},r.prototype.getHumanReadableRuleStack=function(){var e=this;return(0,gs.map)(this.RULE_STACK,function(t){return e.shortRuleNameToFullName(t)})},r}();Ki.Recoverable=rye;function mq(r,e,t,i,n,s,o){var a=this.getKeyForAutomaticLookahead(i,n),l=this.firstAfterRepMap[a];if(l===void 0){var c=this.getCurrRuleFullName(),u=this.getGAstProductions()[c],g=new s(u,n);l=g.startWalking(),this.firstAfterRepMap[a]=l}var f=l.token,h=l.occurrence,p=l.isEndOfRule;this.RULE_STACK.length===1&&p&&f===void 0&&(f=gy.EOF,h=1),this.shouldInRepetitionRecoveryBeTried(f,h,o)&&this.tryInRepetitionRecovery(r,e,t,f)}Ki.attemptInRepetitionRecovery=mq});var fy=w(Jt=>{"use strict";Object.defineProperty(Jt,"__esModule",{value:!0});Jt.getKeyForAutomaticLookahead=Jt.AT_LEAST_ONE_SEP_IDX=Jt.MANY_SEP_IDX=Jt.AT_LEAST_ONE_IDX=Jt.MANY_IDX=Jt.OPTION_IDX=Jt.OR_IDX=Jt.BITS_FOR_ALT_IDX=Jt.BITS_FOR_RULE_IDX=Jt.BITS_FOR_OCCURRENCE_IDX=Jt.BITS_FOR_METHOD_TYPE=void 0;Jt.BITS_FOR_METHOD_TYPE=4;Jt.BITS_FOR_OCCURRENCE_IDX=8;Jt.BITS_FOR_RULE_IDX=12;Jt.BITS_FOR_ALT_IDX=8;Jt.OR_IDX=1<{"use strict";Object.defineProperty(hy,"__esModule",{value:!0});hy.LooksAhead=void 0;var Pa=Pd(),eo=Gt(),Eq=Gn(),Da=fy(),pc=bd(),nye=function(){function r(){}return r.prototype.initLooksAhead=function(e){this.dynamicTokensEnabled=(0,eo.has)(e,"dynamicTokensEnabled")?e.dynamicTokensEnabled:Eq.DEFAULT_PARSER_CONFIG.dynamicTokensEnabled,this.maxLookahead=(0,eo.has)(e,"maxLookahead")?e.maxLookahead:Eq.DEFAULT_PARSER_CONFIG.maxLookahead,this.lookAheadFuncsCache=(0,eo.isES2015MapSupported)()?new Map:[],(0,eo.isES2015MapSupported)()?(this.getLaFuncFromCache=this.getLaFuncFromMap,this.setLaFuncCache=this.setLaFuncCacheUsingMap):(this.getLaFuncFromCache=this.getLaFuncFromObj,this.setLaFuncCache=this.setLaFuncUsingObj)},r.prototype.preComputeLookaheadFunctions=function(e){var t=this;(0,eo.forEach)(e,function(i){t.TRACE_INIT(i.name+" Rule Lookahead",function(){var n=(0,pc.collectMethods)(i),s=n.alternation,o=n.repetition,a=n.option,l=n.repetitionMandatory,c=n.repetitionMandatoryWithSeparator,u=n.repetitionWithSeparator;(0,eo.forEach)(s,function(g){var f=g.idx===0?"":g.idx;t.TRACE_INIT(""+(0,pc.getProductionDslName)(g)+f,function(){var h=(0,Pa.buildLookaheadFuncForOr)(g.idx,i,g.maxLookahead||t.maxLookahead,g.hasPredicates,t.dynamicTokensEnabled,t.lookAheadBuilderForAlternatives),p=(0,Da.getKeyForAutomaticLookahead)(t.fullRuleNameToShort[i.name],Da.OR_IDX,g.idx);t.setLaFuncCache(p,h)})}),(0,eo.forEach)(o,function(g){t.computeLookaheadFunc(i,g.idx,Da.MANY_IDX,Pa.PROD_TYPE.REPETITION,g.maxLookahead,(0,pc.getProductionDslName)(g))}),(0,eo.forEach)(a,function(g){t.computeLookaheadFunc(i,g.idx,Da.OPTION_IDX,Pa.PROD_TYPE.OPTION,g.maxLookahead,(0,pc.getProductionDslName)(g))}),(0,eo.forEach)(l,function(g){t.computeLookaheadFunc(i,g.idx,Da.AT_LEAST_ONE_IDX,Pa.PROD_TYPE.REPETITION_MANDATORY,g.maxLookahead,(0,pc.getProductionDslName)(g))}),(0,eo.forEach)(c,function(g){t.computeLookaheadFunc(i,g.idx,Da.AT_LEAST_ONE_SEP_IDX,Pa.PROD_TYPE.REPETITION_MANDATORY_WITH_SEPARATOR,g.maxLookahead,(0,pc.getProductionDslName)(g))}),(0,eo.forEach)(u,function(g){t.computeLookaheadFunc(i,g.idx,Da.MANY_SEP_IDX,Pa.PROD_TYPE.REPETITION_WITH_SEPARATOR,g.maxLookahead,(0,pc.getProductionDslName)(g))})})})},r.prototype.computeLookaheadFunc=function(e,t,i,n,s,o){var a=this;this.TRACE_INIT(""+o+(t===0?"":t),function(){var l=(0,Pa.buildLookaheadFuncForOptionalProd)(t,e,s||a.maxLookahead,a.dynamicTokensEnabled,n,a.lookAheadBuilderForOptional),c=(0,Da.getKeyForAutomaticLookahead)(a.fullRuleNameToShort[e.name],i,t);a.setLaFuncCache(c,l)})},r.prototype.lookAheadBuilderForOptional=function(e,t,i){return(0,Pa.buildSingleAlternativeLookaheadFunction)(e,t,i)},r.prototype.lookAheadBuilderForAlternatives=function(e,t,i,n){return(0,Pa.buildAlternativesLookAheadFunc)(e,t,i,n)},r.prototype.getKeyForAutomaticLookahead=function(e,t){var i=this.getLastExplicitRuleShortName();return(0,Da.getKeyForAutomaticLookahead)(i,e,t)},r.prototype.getLaFuncFromCache=function(e){},r.prototype.getLaFuncFromMap=function(e){return this.lookAheadFuncsCache.get(e)},r.prototype.getLaFuncFromObj=function(e){return this.lookAheadFuncsCache[e]},r.prototype.setLaFuncCache=function(e,t){},r.prototype.setLaFuncCacheUsingMap=function(e,t){this.lookAheadFuncsCache.set(e,t)},r.prototype.setLaFuncUsingObj=function(e,t){this.lookAheadFuncsCache[e]=t},r}();hy.LooksAhead=nye});var yq=w(Lo=>{"use strict";Object.defineProperty(Lo,"__esModule",{value:!0});Lo.addNoneTerminalToCst=Lo.addTerminalToCst=Lo.setNodeLocationFull=Lo.setNodeLocationOnlyOffset=void 0;function sye(r,e){isNaN(r.startOffset)===!0?(r.startOffset=e.startOffset,r.endOffset=e.endOffset):r.endOffset{"use strict";Object.defineProperty(OA,"__esModule",{value:!0});OA.defineNameProp=OA.functionName=OA.classNameFromInstance=void 0;var lye=Gt();function cye(r){return Bq(r.constructor)}OA.classNameFromInstance=cye;var wq="name";function Bq(r){var e=r.name;return e||"anonymous"}OA.functionName=Bq;function uye(r,e){var t=Object.getOwnPropertyDescriptor(r,wq);return(0,lye.isUndefined)(t)||t.configurable?(Object.defineProperty(r,wq,{enumerable:!1,configurable:!0,writable:!1,value:e}),!0):!1}OA.defineNameProp=uye});var xq=w(Si=>{"use strict";Object.defineProperty(Si,"__esModule",{value:!0});Si.validateRedundantMethods=Si.validateMissingCstMethods=Si.validateVisitor=Si.CstVisitorDefinitionError=Si.createBaseVisitorConstructorWithDefaults=Si.createBaseSemanticVisitorConstructor=Si.defaultVisit=void 0;var fs=Gt(),Rd=ox();function Qq(r,e){for(var t=(0,fs.keys)(r),i=t.length,n=0;n: - `+(""+s.join(` - -`).replace(/\n/g,` - `)))}}};return t.prototype=i,t.prototype.constructor=t,t._RULE_NAMES=e,t}Si.createBaseSemanticVisitorConstructor=gye;function fye(r,e,t){var i=function(){};(0,Rd.defineNameProp)(i,r+"BaseSemanticsWithDefaults");var n=Object.create(t.prototype);return(0,fs.forEach)(e,function(s){n[s]=Qq}),i.prototype=n,i.prototype.constructor=i,i}Si.createBaseVisitorConstructorWithDefaults=fye;var ax;(function(r){r[r.REDUNDANT_METHOD=0]="REDUNDANT_METHOD",r[r.MISSING_METHOD=1]="MISSING_METHOD"})(ax=Si.CstVisitorDefinitionError||(Si.CstVisitorDefinitionError={}));function bq(r,e){var t=Sq(r,e),i=vq(r,e);return t.concat(i)}Si.validateVisitor=bq;function Sq(r,e){var t=(0,fs.map)(e,function(i){if(!(0,fs.isFunction)(r[i]))return{msg:"Missing visitor method: <"+i+"> on "+(0,Rd.functionName)(r.constructor)+" CST Visitor.",type:ax.MISSING_METHOD,methodName:i}});return(0,fs.compact)(t)}Si.validateMissingCstMethods=Sq;var hye=["constructor","visit","validateVisitor"];function vq(r,e){var t=[];for(var i in r)(0,fs.isFunction)(r[i])&&!(0,fs.contains)(hye,i)&&!(0,fs.contains)(e,i)&&t.push({msg:"Redundant visitor method: <"+i+"> on "+(0,Rd.functionName)(r.constructor)+` CST Visitor -There is no Grammar Rule corresponding to this method's name. -`,type:ax.REDUNDANT_METHOD,methodName:i});return t}Si.validateRedundantMethods=vq});var Dq=w(py=>{"use strict";Object.defineProperty(py,"__esModule",{value:!0});py.TreeBuilder=void 0;var tf=yq(),_r=Gt(),Pq=xq(),pye=Gn(),dye=function(){function r(){}return r.prototype.initTreeBuilder=function(e){if(this.CST_STACK=[],this.outputCst=e.outputCst,this.nodeLocationTracking=(0,_r.has)(e,"nodeLocationTracking")?e.nodeLocationTracking:pye.DEFAULT_PARSER_CONFIG.nodeLocationTracking,!this.outputCst)this.cstInvocationStateUpdate=_r.NOOP,this.cstFinallyStateUpdate=_r.NOOP,this.cstPostTerminal=_r.NOOP,this.cstPostNonTerminal=_r.NOOP,this.cstPostRule=_r.NOOP;else if(/full/i.test(this.nodeLocationTracking))this.recoveryEnabled?(this.setNodeLocationFromToken=tf.setNodeLocationFull,this.setNodeLocationFromNode=tf.setNodeLocationFull,this.cstPostRule=_r.NOOP,this.setInitialNodeLocation=this.setInitialNodeLocationFullRecovery):(this.setNodeLocationFromToken=_r.NOOP,this.setNodeLocationFromNode=_r.NOOP,this.cstPostRule=this.cstPostRuleFull,this.setInitialNodeLocation=this.setInitialNodeLocationFullRegular);else if(/onlyOffset/i.test(this.nodeLocationTracking))this.recoveryEnabled?(this.setNodeLocationFromToken=tf.setNodeLocationOnlyOffset,this.setNodeLocationFromNode=tf.setNodeLocationOnlyOffset,this.cstPostRule=_r.NOOP,this.setInitialNodeLocation=this.setInitialNodeLocationOnlyOffsetRecovery):(this.setNodeLocationFromToken=_r.NOOP,this.setNodeLocationFromNode=_r.NOOP,this.cstPostRule=this.cstPostRuleOnlyOffset,this.setInitialNodeLocation=this.setInitialNodeLocationOnlyOffsetRegular);else if(/none/i.test(this.nodeLocationTracking))this.setNodeLocationFromToken=_r.NOOP,this.setNodeLocationFromNode=_r.NOOP,this.cstPostRule=_r.NOOP,this.setInitialNodeLocation=_r.NOOP;else throw Error('Invalid config option: "'+e.nodeLocationTracking+'"')},r.prototype.setInitialNodeLocationOnlyOffsetRecovery=function(e){e.location={startOffset:NaN,endOffset:NaN}},r.prototype.setInitialNodeLocationOnlyOffsetRegular=function(e){e.location={startOffset:this.LA(1).startOffset,endOffset:NaN}},r.prototype.setInitialNodeLocationFullRecovery=function(e){e.location={startOffset:NaN,startLine:NaN,startColumn:NaN,endOffset:NaN,endLine:NaN,endColumn:NaN}},r.prototype.setInitialNodeLocationFullRegular=function(e){var t=this.LA(1);e.location={startOffset:t.startOffset,startLine:t.startLine,startColumn:t.startColumn,endOffset:NaN,endLine:NaN,endColumn:NaN}},r.prototype.cstInvocationStateUpdate=function(e,t){var i={name:e,children:{}};this.setInitialNodeLocation(i),this.CST_STACK.push(i)},r.prototype.cstFinallyStateUpdate=function(){this.CST_STACK.pop()},r.prototype.cstPostRuleFull=function(e){var t=this.LA(0),i=e.location;i.startOffset<=t.startOffset?(i.endOffset=t.endOffset,i.endLine=t.endLine,i.endColumn=t.endColumn):(i.startOffset=NaN,i.startLine=NaN,i.startColumn=NaN)},r.prototype.cstPostRuleOnlyOffset=function(e){var t=this.LA(0),i=e.location;i.startOffset<=t.startOffset?i.endOffset=t.endOffset:i.startOffset=NaN},r.prototype.cstPostTerminal=function(e,t){var i=this.CST_STACK[this.CST_STACK.length-1];(0,tf.addTerminalToCst)(i,t,e),this.setNodeLocationFromToken(i.location,t)},r.prototype.cstPostNonTerminal=function(e,t){var i=this.CST_STACK[this.CST_STACK.length-1];(0,tf.addNoneTerminalToCst)(i,t,e),this.setNodeLocationFromNode(i.location,e.location)},r.prototype.getBaseCstVisitorConstructor=function(){if((0,_r.isUndefined)(this.baseCstVisitorConstructor)){var e=(0,Pq.createBaseSemanticVisitorConstructor)(this.className,(0,_r.keys)(this.gastProductionsCache));return this.baseCstVisitorConstructor=e,e}return this.baseCstVisitorConstructor},r.prototype.getBaseCstVisitorConstructorWithDefaults=function(){if((0,_r.isUndefined)(this.baseCstVisitorWithDefaultsConstructor)){var e=(0,Pq.createBaseVisitorConstructorWithDefaults)(this.className,(0,_r.keys)(this.gastProductionsCache),this.getBaseCstVisitorConstructor());return this.baseCstVisitorWithDefaultsConstructor=e,e}return this.baseCstVisitorWithDefaultsConstructor},r.prototype.getLastExplicitRuleShortName=function(){var e=this.RULE_STACK;return e[e.length-1]},r.prototype.getPreviousExplicitRuleShortName=function(){var e=this.RULE_STACK;return e[e.length-2]},r.prototype.getLastExplicitRuleOccurrenceIndex=function(){var e=this.RULE_OCCURRENCE_STACK;return e[e.length-1]},r}();py.TreeBuilder=dye});var Rq=w(dy=>{"use strict";Object.defineProperty(dy,"__esModule",{value:!0});dy.LexerAdapter=void 0;var kq=Gn(),Cye=function(){function r(){}return r.prototype.initLexerAdapter=function(){this.tokVector=[],this.tokVectorLength=0,this.currIdx=-1},Object.defineProperty(r.prototype,"input",{get:function(){return this.tokVector},set:function(e){if(this.selfAnalysisDone!==!0)throw Error("Missing invocation at the end of the Parser's constructor.");this.reset(),this.tokVector=e,this.tokVectorLength=e.length},enumerable:!1,configurable:!0}),r.prototype.SKIP_TOKEN=function(){return this.currIdx<=this.tokVector.length-2?(this.consumeToken(),this.LA(1)):kq.END_OF_FILE},r.prototype.LA=function(e){var t=this.currIdx+e;return t<0||this.tokVectorLength<=t?kq.END_OF_FILE:this.tokVector[t]},r.prototype.consumeToken=function(){this.currIdx++},r.prototype.exportLexerState=function(){return this.currIdx},r.prototype.importLexerState=function(e){this.currIdx=e},r.prototype.resetLexerState=function(){this.currIdx=-1},r.prototype.moveToTerminatedState=function(){this.currIdx=this.tokVector.length-1},r.prototype.getLexerPosition=function(){return this.exportLexerState()},r}();dy.LexerAdapter=Cye});var Nq=w(Cy=>{"use strict";Object.defineProperty(Cy,"__esModule",{value:!0});Cy.RecognizerApi=void 0;var Fq=Gt(),mye=ef(),Ax=Gn(),Eye=Sd(),Iye=rx(),yye=dn(),wye=function(){function r(){}return r.prototype.ACTION=function(e){return e.call(this)},r.prototype.consume=function(e,t,i){return this.consumeInternal(t,e,i)},r.prototype.subrule=function(e,t,i){return this.subruleInternal(t,e,i)},r.prototype.option=function(e,t){return this.optionInternal(t,e)},r.prototype.or=function(e,t){return this.orInternal(t,e)},r.prototype.many=function(e,t){return this.manyInternal(e,t)},r.prototype.atLeastOne=function(e,t){return this.atLeastOneInternal(e,t)},r.prototype.CONSUME=function(e,t){return this.consumeInternal(e,0,t)},r.prototype.CONSUME1=function(e,t){return this.consumeInternal(e,1,t)},r.prototype.CONSUME2=function(e,t){return this.consumeInternal(e,2,t)},r.prototype.CONSUME3=function(e,t){return this.consumeInternal(e,3,t)},r.prototype.CONSUME4=function(e,t){return this.consumeInternal(e,4,t)},r.prototype.CONSUME5=function(e,t){return this.consumeInternal(e,5,t)},r.prototype.CONSUME6=function(e,t){return this.consumeInternal(e,6,t)},r.prototype.CONSUME7=function(e,t){return this.consumeInternal(e,7,t)},r.prototype.CONSUME8=function(e,t){return this.consumeInternal(e,8,t)},r.prototype.CONSUME9=function(e,t){return this.consumeInternal(e,9,t)},r.prototype.SUBRULE=function(e,t){return this.subruleInternal(e,0,t)},r.prototype.SUBRULE1=function(e,t){return this.subruleInternal(e,1,t)},r.prototype.SUBRULE2=function(e,t){return this.subruleInternal(e,2,t)},r.prototype.SUBRULE3=function(e,t){return this.subruleInternal(e,3,t)},r.prototype.SUBRULE4=function(e,t){return this.subruleInternal(e,4,t)},r.prototype.SUBRULE5=function(e,t){return this.subruleInternal(e,5,t)},r.prototype.SUBRULE6=function(e,t){return this.subruleInternal(e,6,t)},r.prototype.SUBRULE7=function(e,t){return this.subruleInternal(e,7,t)},r.prototype.SUBRULE8=function(e,t){return this.subruleInternal(e,8,t)},r.prototype.SUBRULE9=function(e,t){return this.subruleInternal(e,9,t)},r.prototype.OPTION=function(e){return this.optionInternal(e,0)},r.prototype.OPTION1=function(e){return this.optionInternal(e,1)},r.prototype.OPTION2=function(e){return this.optionInternal(e,2)},r.prototype.OPTION3=function(e){return this.optionInternal(e,3)},r.prototype.OPTION4=function(e){return this.optionInternal(e,4)},r.prototype.OPTION5=function(e){return this.optionInternal(e,5)},r.prototype.OPTION6=function(e){return this.optionInternal(e,6)},r.prototype.OPTION7=function(e){return this.optionInternal(e,7)},r.prototype.OPTION8=function(e){return this.optionInternal(e,8)},r.prototype.OPTION9=function(e){return this.optionInternal(e,9)},r.prototype.OR=function(e){return this.orInternal(e,0)},r.prototype.OR1=function(e){return this.orInternal(e,1)},r.prototype.OR2=function(e){return this.orInternal(e,2)},r.prototype.OR3=function(e){return this.orInternal(e,3)},r.prototype.OR4=function(e){return this.orInternal(e,4)},r.prototype.OR5=function(e){return this.orInternal(e,5)},r.prototype.OR6=function(e){return this.orInternal(e,6)},r.prototype.OR7=function(e){return this.orInternal(e,7)},r.prototype.OR8=function(e){return this.orInternal(e,8)},r.prototype.OR9=function(e){return this.orInternal(e,9)},r.prototype.MANY=function(e){this.manyInternal(0,e)},r.prototype.MANY1=function(e){this.manyInternal(1,e)},r.prototype.MANY2=function(e){this.manyInternal(2,e)},r.prototype.MANY3=function(e){this.manyInternal(3,e)},r.prototype.MANY4=function(e){this.manyInternal(4,e)},r.prototype.MANY5=function(e){this.manyInternal(5,e)},r.prototype.MANY6=function(e){this.manyInternal(6,e)},r.prototype.MANY7=function(e){this.manyInternal(7,e)},r.prototype.MANY8=function(e){this.manyInternal(8,e)},r.prototype.MANY9=function(e){this.manyInternal(9,e)},r.prototype.MANY_SEP=function(e){this.manySepFirstInternal(0,e)},r.prototype.MANY_SEP1=function(e){this.manySepFirstInternal(1,e)},r.prototype.MANY_SEP2=function(e){this.manySepFirstInternal(2,e)},r.prototype.MANY_SEP3=function(e){this.manySepFirstInternal(3,e)},r.prototype.MANY_SEP4=function(e){this.manySepFirstInternal(4,e)},r.prototype.MANY_SEP5=function(e){this.manySepFirstInternal(5,e)},r.prototype.MANY_SEP6=function(e){this.manySepFirstInternal(6,e)},r.prototype.MANY_SEP7=function(e){this.manySepFirstInternal(7,e)},r.prototype.MANY_SEP8=function(e){this.manySepFirstInternal(8,e)},r.prototype.MANY_SEP9=function(e){this.manySepFirstInternal(9,e)},r.prototype.AT_LEAST_ONE=function(e){this.atLeastOneInternal(0,e)},r.prototype.AT_LEAST_ONE1=function(e){return this.atLeastOneInternal(1,e)},r.prototype.AT_LEAST_ONE2=function(e){this.atLeastOneInternal(2,e)},r.prototype.AT_LEAST_ONE3=function(e){this.atLeastOneInternal(3,e)},r.prototype.AT_LEAST_ONE4=function(e){this.atLeastOneInternal(4,e)},r.prototype.AT_LEAST_ONE5=function(e){this.atLeastOneInternal(5,e)},r.prototype.AT_LEAST_ONE6=function(e){this.atLeastOneInternal(6,e)},r.prototype.AT_LEAST_ONE7=function(e){this.atLeastOneInternal(7,e)},r.prototype.AT_LEAST_ONE8=function(e){this.atLeastOneInternal(8,e)},r.prototype.AT_LEAST_ONE9=function(e){this.atLeastOneInternal(9,e)},r.prototype.AT_LEAST_ONE_SEP=function(e){this.atLeastOneSepFirstInternal(0,e)},r.prototype.AT_LEAST_ONE_SEP1=function(e){this.atLeastOneSepFirstInternal(1,e)},r.prototype.AT_LEAST_ONE_SEP2=function(e){this.atLeastOneSepFirstInternal(2,e)},r.prototype.AT_LEAST_ONE_SEP3=function(e){this.atLeastOneSepFirstInternal(3,e)},r.prototype.AT_LEAST_ONE_SEP4=function(e){this.atLeastOneSepFirstInternal(4,e)},r.prototype.AT_LEAST_ONE_SEP5=function(e){this.atLeastOneSepFirstInternal(5,e)},r.prototype.AT_LEAST_ONE_SEP6=function(e){this.atLeastOneSepFirstInternal(6,e)},r.prototype.AT_LEAST_ONE_SEP7=function(e){this.atLeastOneSepFirstInternal(7,e)},r.prototype.AT_LEAST_ONE_SEP8=function(e){this.atLeastOneSepFirstInternal(8,e)},r.prototype.AT_LEAST_ONE_SEP9=function(e){this.atLeastOneSepFirstInternal(9,e)},r.prototype.RULE=function(e,t,i){if(i===void 0&&(i=Ax.DEFAULT_RULE_CONFIG),(0,Fq.contains)(this.definedRulesNames,e)){var n=Eye.defaultGrammarValidatorErrorProvider.buildDuplicateRuleNameError({topLevelRule:e,grammarName:this.className}),s={message:n,type:Ax.ParserDefinitionErrorType.DUPLICATE_RULE_NAME,ruleName:e};this.definitionErrors.push(s)}this.definedRulesNames.push(e);var o=this.defineRule(e,t,i);return this[e]=o,o},r.prototype.OVERRIDE_RULE=function(e,t,i){i===void 0&&(i=Ax.DEFAULT_RULE_CONFIG);var n=[];n=n.concat((0,Iye.validateRuleIsOverridden)(e,this.definedRulesNames,this.className)),this.definitionErrors=this.definitionErrors.concat(n);var s=this.defineRule(e,t,i);return this[e]=s,s},r.prototype.BACKTRACK=function(e,t){return function(){this.isBackTrackingStack.push(1);var i=this.saveRecogState();try{return e.apply(this,t),!0}catch(n){if((0,mye.isRecognitionException)(n))return!1;throw n}finally{this.reloadRecogState(i),this.isBackTrackingStack.pop()}}},r.prototype.getGAstProductions=function(){return this.gastProductionsCache},r.prototype.getSerializedGastProductions=function(){return(0,yye.serializeGrammar)((0,Fq.values)(this.gastProductionsCache))},r}();Cy.RecognizerApi=wye});var Mq=w(Ey=>{"use strict";Object.defineProperty(Ey,"__esModule",{value:!0});Ey.RecognizerEngine=void 0;var Pr=Gt(),Yn=fy(),my=ef(),Lq=Pd(),rf=xd(),Tq=Gn(),Bye=sx(),Oq=NA(),Fd=Vg(),Qye=ox(),bye=function(){function r(){}return r.prototype.initRecognizerEngine=function(e,t){if(this.className=(0,Qye.classNameFromInstance)(this),this.shortRuleNameToFull={},this.fullRuleNameToShort={},this.ruleShortNameIdx=256,this.tokenMatcher=Fd.tokenStructuredMatcherNoCategories,this.definedRulesNames=[],this.tokensMap={},this.isBackTrackingStack=[],this.RULE_STACK=[],this.RULE_OCCURRENCE_STACK=[],this.gastProductionsCache={},(0,Pr.has)(t,"serializedGrammar"))throw Error(`The Parser's configuration can no longer contain a property. - See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_6-0-0 - For Further details.`);if((0,Pr.isArray)(e)){if((0,Pr.isEmpty)(e))throw Error(`A Token Vocabulary cannot be empty. - Note that the first argument for the parser constructor - is no longer a Token vector (since v4.0).`);if(typeof e[0].startOffset=="number")throw Error(`The Parser constructor no longer accepts a token vector as the first argument. - See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_4-0-0 - For Further details.`)}if((0,Pr.isArray)(e))this.tokensMap=(0,Pr.reduce)(e,function(o,a){return o[a.name]=a,o},{});else if((0,Pr.has)(e,"modes")&&(0,Pr.every)((0,Pr.flatten)((0,Pr.values)(e.modes)),Fd.isTokenType)){var i=(0,Pr.flatten)((0,Pr.values)(e.modes)),n=(0,Pr.uniq)(i);this.tokensMap=(0,Pr.reduce)(n,function(o,a){return o[a.name]=a,o},{})}else if((0,Pr.isObject)(e))this.tokensMap=(0,Pr.cloneObj)(e);else throw new Error(" argument must be An Array of Token constructors, A dictionary of Token constructors or an IMultiModeLexerDefinition");this.tokensMap.EOF=Oq.EOF;var s=(0,Pr.every)((0,Pr.values)(e),function(o){return(0,Pr.isEmpty)(o.categoryMatches)});this.tokenMatcher=s?Fd.tokenStructuredMatcherNoCategories:Fd.tokenStructuredMatcher,(0,Fd.augmentTokenTypes)((0,Pr.values)(this.tokensMap))},r.prototype.defineRule=function(e,t,i){if(this.selfAnalysisDone)throw Error("Grammar rule <"+e+`> may not be defined after the 'performSelfAnalysis' method has been called' -Make sure that all grammar rule definitions are done before 'performSelfAnalysis' is called.`);var n=(0,Pr.has)(i,"resyncEnabled")?i.resyncEnabled:Tq.DEFAULT_RULE_CONFIG.resyncEnabled,s=(0,Pr.has)(i,"recoveryValueFunc")?i.recoveryValueFunc:Tq.DEFAULT_RULE_CONFIG.recoveryValueFunc,o=this.ruleShortNameIdx<t},r.prototype.orInternal=function(e,t){var i=this.getKeyForAutomaticLookahead(Yn.OR_IDX,t),n=(0,Pr.isArray)(e)?e:e.DEF,s=this.getLaFuncFromCache(i),o=s.call(this,n);if(o!==void 0){var a=n[o];return a.ALT.call(this)}this.raiseNoAltException(t,e.ERR_MSG)},r.prototype.ruleFinallyStateUpdate=function(){if(this.RULE_STACK.pop(),this.RULE_OCCURRENCE_STACK.pop(),this.cstFinallyStateUpdate(),this.RULE_STACK.length===0&&this.isAtEndOfInput()===!1){var e=this.LA(1),t=this.errorMessageProvider.buildNotAllInputParsedMessage({firstRedundant:e,ruleName:this.getCurrRuleFullName()});this.SAVE_ERROR(new my.NotAllInputParsedException(t,e))}},r.prototype.subruleInternal=function(e,t,i){var n;try{var s=i!==void 0?i.ARGS:void 0;return n=e.call(this,t,s),this.cstPostNonTerminal(n,i!==void 0&&i.LABEL!==void 0?i.LABEL:e.ruleName),n}catch(o){this.subruleInternalError(o,i,e.ruleName)}},r.prototype.subruleInternalError=function(e,t,i){throw(0,my.isRecognitionException)(e)&&e.partialCstResult!==void 0&&(this.cstPostNonTerminal(e.partialCstResult,t!==void 0&&t.LABEL!==void 0?t.LABEL:i),delete e.partialCstResult),e},r.prototype.consumeInternal=function(e,t,i){var n;try{var s=this.LA(1);this.tokenMatcher(s,e)===!0?(this.consumeToken(),n=s):this.consumeInternalError(e,s,i)}catch(o){n=this.consumeInternalRecovery(e,t,o)}return this.cstPostTerminal(i!==void 0&&i.LABEL!==void 0?i.LABEL:e.name,n),n},r.prototype.consumeInternalError=function(e,t,i){var n,s=this.LA(0);throw i!==void 0&&i.ERR_MSG?n=i.ERR_MSG:n=this.errorMessageProvider.buildMismatchTokenMessage({expected:e,actual:t,previous:s,ruleName:this.getCurrRuleFullName()}),this.SAVE_ERROR(new my.MismatchedTokenException(n,t,s))},r.prototype.consumeInternalRecovery=function(e,t,i){if(this.recoveryEnabled&&i.name==="MismatchedTokenException"&&!this.isBackTracking()){var n=this.getFollowsForInRuleRecovery(e,t);try{return this.tryInRuleRecovery(e,n)}catch(s){throw s.name===Bye.IN_RULE_RECOVERY_EXCEPTION?i:s}}else throw i},r.prototype.saveRecogState=function(){var e=this.errors,t=(0,Pr.cloneArr)(this.RULE_STACK);return{errors:e,lexerState:this.exportLexerState(),RULE_STACK:t,CST_STACK:this.CST_STACK}},r.prototype.reloadRecogState=function(e){this.errors=e.errors,this.importLexerState(e.lexerState),this.RULE_STACK=e.RULE_STACK},r.prototype.ruleInvocationStateUpdate=function(e,t,i){this.RULE_OCCURRENCE_STACK.push(i),this.RULE_STACK.push(e),this.cstInvocationStateUpdate(t,e)},r.prototype.isBackTracking=function(){return this.isBackTrackingStack.length!==0},r.prototype.getCurrRuleFullName=function(){var e=this.getLastExplicitRuleShortName();return this.shortRuleNameToFull[e]},r.prototype.shortRuleNameToFullName=function(e){return this.shortRuleNameToFull[e]},r.prototype.isAtEndOfInput=function(){return this.tokenMatcher(this.LA(1),Oq.EOF)},r.prototype.reset=function(){this.resetLexerState(),this.isBackTrackingStack=[],this.errors=[],this.RULE_STACK=[],this.CST_STACK=[],this.RULE_OCCURRENCE_STACK=[]},r}();Ey.RecognizerEngine=bye});var Uq=w(Iy=>{"use strict";Object.defineProperty(Iy,"__esModule",{value:!0});Iy.ErrorHandler=void 0;var lx=ef(),cx=Gt(),Kq=Pd(),Sye=Gn(),vye=function(){function r(){}return r.prototype.initErrorHandler=function(e){this._errors=[],this.errorMessageProvider=(0,cx.has)(e,"errorMessageProvider")?e.errorMessageProvider:Sye.DEFAULT_PARSER_CONFIG.errorMessageProvider},r.prototype.SAVE_ERROR=function(e){if((0,lx.isRecognitionException)(e))return e.context={ruleStack:this.getHumanReadableRuleStack(),ruleOccurrenceStack:(0,cx.cloneArr)(this.RULE_OCCURRENCE_STACK)},this._errors.push(e),e;throw Error("Trying to save an Error which is not a RecognitionException")},Object.defineProperty(r.prototype,"errors",{get:function(){return(0,cx.cloneArr)(this._errors)},set:function(e){this._errors=e},enumerable:!1,configurable:!0}),r.prototype.raiseEarlyExitException=function(e,t,i){for(var n=this.getCurrRuleFullName(),s=this.getGAstProductions()[n],o=(0,Kq.getLookaheadPathsForOptionalProd)(e,s,t,this.maxLookahead),a=o[0],l=[],c=1;c<=this.maxLookahead;c++)l.push(this.LA(c));var u=this.errorMessageProvider.buildEarlyExitMessage({expectedIterationPaths:a,actual:l,previous:this.LA(0),customUserDescription:i,ruleName:n});throw this.SAVE_ERROR(new lx.EarlyExitException(u,this.LA(1),this.LA(0)))},r.prototype.raiseNoAltException=function(e,t){for(var i=this.getCurrRuleFullName(),n=this.getGAstProductions()[i],s=(0,Kq.getLookaheadPathsForOr)(e,n,this.maxLookahead),o=[],a=1;a<=this.maxLookahead;a++)o.push(this.LA(a));var l=this.LA(0),c=this.errorMessageProvider.buildNoViableAltMessage({expectedPathsPerAlt:s,actual:o,previous:l,customUserDescription:t,ruleName:this.getCurrRuleFullName()});throw this.SAVE_ERROR(new lx.NoViableAltException(c,this.LA(1),l))},r}();Iy.ErrorHandler=vye});var Yq=w(yy=>{"use strict";Object.defineProperty(yy,"__esModule",{value:!0});yy.ContentAssist=void 0;var Hq=xd(),Gq=Gt(),xye=function(){function r(){}return r.prototype.initContentAssist=function(){},r.prototype.computeContentAssist=function(e,t){var i=this.gastProductionsCache[e];if((0,Gq.isUndefined)(i))throw Error("Rule ->"+e+"<- does not exist in this grammar.");return(0,Hq.nextPossibleTokensAfter)([i],t,this.tokenMatcher,this.maxLookahead)},r.prototype.getNextPossibleTokenTypes=function(e){var t=(0,Gq.first)(e.ruleStack),i=this.getGAstProductions(),n=i[t],s=new Hq.NextAfterTokenWalker(n,e).startWalking();return s},r}();yy.ContentAssist=xye});var Zq=w(Qy=>{"use strict";Object.defineProperty(Qy,"__esModule",{value:!0});Qy.GastRecorder=void 0;var En=Gt(),To=dn(),Pye=yd(),Wq=Vg(),zq=NA(),Dye=Gn(),kye=fy(),By={description:"This Object indicates the Parser is during Recording Phase"};Object.freeze(By);var jq=!0,qq=Math.pow(2,kye.BITS_FOR_OCCURRENCE_IDX)-1,Vq=(0,zq.createToken)({name:"RECORDING_PHASE_TOKEN",pattern:Pye.Lexer.NA});(0,Wq.augmentTokenTypes)([Vq]);var Xq=(0,zq.createTokenInstance)(Vq,`This IToken indicates the Parser is in Recording Phase - See: https://chevrotain.io/docs/guide/internals.html#grammar-recording for details`,-1,-1,-1,-1,-1,-1);Object.freeze(Xq);var Rye={name:`This CSTNode indicates the Parser is in Recording Phase - See: https://chevrotain.io/docs/guide/internals.html#grammar-recording for details`,children:{}},Fye=function(){function r(){}return r.prototype.initGastRecorder=function(e){this.recordingProdStack=[],this.RECORDING_PHASE=!1},r.prototype.enableRecording=function(){var e=this;this.RECORDING_PHASE=!0,this.TRACE_INIT("Enable Recording",function(){for(var t=function(n){var s=n>0?n:"";e["CONSUME"+s]=function(o,a){return this.consumeInternalRecord(o,n,a)},e["SUBRULE"+s]=function(o,a){return this.subruleInternalRecord(o,n,a)},e["OPTION"+s]=function(o){return this.optionInternalRecord(o,n)},e["OR"+s]=function(o){return this.orInternalRecord(o,n)},e["MANY"+s]=function(o){this.manyInternalRecord(n,o)},e["MANY_SEP"+s]=function(o){this.manySepFirstInternalRecord(n,o)},e["AT_LEAST_ONE"+s]=function(o){this.atLeastOneInternalRecord(n,o)},e["AT_LEAST_ONE_SEP"+s]=function(o){this.atLeastOneSepFirstInternalRecord(n,o)}},i=0;i<10;i++)t(i);e.consume=function(n,s,o){return this.consumeInternalRecord(s,n,o)},e.subrule=function(n,s,o){return this.subruleInternalRecord(s,n,o)},e.option=function(n,s){return this.optionInternalRecord(s,n)},e.or=function(n,s){return this.orInternalRecord(s,n)},e.many=function(n,s){this.manyInternalRecord(n,s)},e.atLeastOne=function(n,s){this.atLeastOneInternalRecord(n,s)},e.ACTION=e.ACTION_RECORD,e.BACKTRACK=e.BACKTRACK_RECORD,e.LA=e.LA_RECORD})},r.prototype.disableRecording=function(){var e=this;this.RECORDING_PHASE=!1,this.TRACE_INIT("Deleting Recording methods",function(){for(var t=0;t<10;t++){var i=t>0?t:"";delete e["CONSUME"+i],delete e["SUBRULE"+i],delete e["OPTION"+i],delete e["OR"+i],delete e["MANY"+i],delete e["MANY_SEP"+i],delete e["AT_LEAST_ONE"+i],delete e["AT_LEAST_ONE_SEP"+i]}delete e.consume,delete e.subrule,delete e.option,delete e.or,delete e.many,delete e.atLeastOne,delete e.ACTION,delete e.BACKTRACK,delete e.LA})},r.prototype.ACTION_RECORD=function(e){},r.prototype.BACKTRACK_RECORD=function(e,t){return function(){return!0}},r.prototype.LA_RECORD=function(e){return Dye.END_OF_FILE},r.prototype.topLevelRuleRecord=function(e,t){try{var i=new To.Rule({definition:[],name:e});return i.name=e,this.recordingProdStack.push(i),t.call(this),this.recordingProdStack.pop(),i}catch(n){if(n.KNOWN_RECORDER_ERROR!==!0)try{n.message=n.message+` - This error was thrown during the "grammar recording phase" For more info see: - https://chevrotain.io/docs/guide/internals.html#grammar-recording`}catch{throw n}throw n}},r.prototype.optionInternalRecord=function(e,t){return Nd.call(this,To.Option,e,t)},r.prototype.atLeastOneInternalRecord=function(e,t){Nd.call(this,To.RepetitionMandatory,t,e)},r.prototype.atLeastOneSepFirstInternalRecord=function(e,t){Nd.call(this,To.RepetitionMandatoryWithSeparator,t,e,jq)},r.prototype.manyInternalRecord=function(e,t){Nd.call(this,To.Repetition,t,e)},r.prototype.manySepFirstInternalRecord=function(e,t){Nd.call(this,To.RepetitionWithSeparator,t,e,jq)},r.prototype.orInternalRecord=function(e,t){return Nye.call(this,e,t)},r.prototype.subruleInternalRecord=function(e,t,i){if(wy(t),!e||(0,En.has)(e,"ruleName")===!1){var n=new Error(" argument is invalid"+(" expecting a Parser method reference but got: <"+JSON.stringify(e)+">")+(` - inside top level rule: <`+this.recordingProdStack[0].name+">"));throw n.KNOWN_RECORDER_ERROR=!0,n}var s=(0,En.peek)(this.recordingProdStack),o=e.ruleName,a=new To.NonTerminal({idx:t,nonTerminalName:o,label:i==null?void 0:i.LABEL,referencedRule:void 0});return s.definition.push(a),this.outputCst?Rye:By},r.prototype.consumeInternalRecord=function(e,t,i){if(wy(t),!(0,Wq.hasShortKeyProperty)(e)){var n=new Error(" argument is invalid"+(" expecting a TokenType reference but got: <"+JSON.stringify(e)+">")+(` - inside top level rule: <`+this.recordingProdStack[0].name+">"));throw n.KNOWN_RECORDER_ERROR=!0,n}var s=(0,En.peek)(this.recordingProdStack),o=new To.Terminal({idx:t,terminalType:e,label:i==null?void 0:i.LABEL});return s.definition.push(o),Xq},r}();Qy.GastRecorder=Fye;function Nd(r,e,t,i){i===void 0&&(i=!1),wy(t);var n=(0,En.peek)(this.recordingProdStack),s=(0,En.isFunction)(e)?e:e.DEF,o=new r({definition:[],idx:t});return i&&(o.separator=e.SEP),(0,En.has)(e,"MAX_LOOKAHEAD")&&(o.maxLookahead=e.MAX_LOOKAHEAD),this.recordingProdStack.push(o),s.call(this),n.definition.push(o),this.recordingProdStack.pop(),By}function Nye(r,e){var t=this;wy(e);var i=(0,En.peek)(this.recordingProdStack),n=(0,En.isArray)(r)===!1,s=n===!1?r:r.DEF,o=new To.Alternation({definition:[],idx:e,ignoreAmbiguities:n&&r.IGNORE_AMBIGUITIES===!0});(0,En.has)(r,"MAX_LOOKAHEAD")&&(o.maxLookahead=r.MAX_LOOKAHEAD);var a=(0,En.some)(s,function(l){return(0,En.isFunction)(l.GATE)});return o.hasPredicates=a,i.definition.push(o),(0,En.forEach)(s,function(l){var c=new To.Alternative({definition:[]});o.definition.push(c),(0,En.has)(l,"IGNORE_AMBIGUITIES")?c.ignoreAmbiguities=l.IGNORE_AMBIGUITIES:(0,En.has)(l,"GATE")&&(c.ignoreAmbiguities=!0),t.recordingProdStack.push(c),l.ALT.call(t),t.recordingProdStack.pop()}),By}function Jq(r){return r===0?"":""+r}function wy(r){if(r<0||r>qq){var e=new Error("Invalid DSL Method idx value: <"+r+`> - `+("Idx value must be a none negative value smaller than "+(qq+1)));throw e.KNOWN_RECORDER_ERROR=!0,e}}});var $q=w(by=>{"use strict";Object.defineProperty(by,"__esModule",{value:!0});by.PerformanceTracer=void 0;var _q=Gt(),Lye=Gn(),Tye=function(){function r(){}return r.prototype.initPerformanceTracer=function(e){if((0,_q.has)(e,"traceInitPerf")){var t=e.traceInitPerf,i=typeof t=="number";this.traceInitMaxIdent=i?t:1/0,this.traceInitPerf=i?t>0:t}else this.traceInitMaxIdent=0,this.traceInitPerf=Lye.DEFAULT_PARSER_CONFIG.traceInitPerf;this.traceInitIndent=-1},r.prototype.TRACE_INIT=function(e,t){if(this.traceInitPerf===!0){this.traceInitIndent++;var i=new Array(this.traceInitIndent+1).join(" ");this.traceInitIndent <"+e+">");var n=(0,_q.timer)(t),s=n.time,o=n.value,a=s>10?console.warn:console.log;return this.traceInitIndent time: "+s+"ms"),this.traceInitIndent--,o}else return t()},r}();by.PerformanceTracer=Tye});var eJ=w(Sy=>{"use strict";Object.defineProperty(Sy,"__esModule",{value:!0});Sy.applyMixins=void 0;function Oye(r,e){e.forEach(function(t){var i=t.prototype;Object.getOwnPropertyNames(i).forEach(function(n){if(n!=="constructor"){var s=Object.getOwnPropertyDescriptor(i,n);s&&(s.get||s.set)?Object.defineProperty(r.prototype,n,s):r.prototype[n]=t.prototype[n]}})})}Sy.applyMixins=Oye});var Gn=w(dr=>{"use strict";var iJ=dr&&dr.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(dr,"__esModule",{value:!0});dr.EmbeddedActionsParser=dr.CstParser=dr.Parser=dr.EMPTY_ALT=dr.ParserDefinitionErrorType=dr.DEFAULT_RULE_CONFIG=dr.DEFAULT_PARSER_CONFIG=dr.END_OF_FILE=void 0;var _i=Gt(),Mye=Uj(),tJ=NA(),nJ=Sd(),rJ=gq(),Kye=sx(),Uye=Iq(),Hye=Dq(),Gye=Rq(),Yye=Nq(),jye=Mq(),qye=Uq(),Jye=Yq(),Wye=Zq(),zye=$q(),Vye=eJ();dr.END_OF_FILE=(0,tJ.createTokenInstance)(tJ.EOF,"",NaN,NaN,NaN,NaN,NaN,NaN);Object.freeze(dr.END_OF_FILE);dr.DEFAULT_PARSER_CONFIG=Object.freeze({recoveryEnabled:!1,maxLookahead:3,dynamicTokensEnabled:!1,outputCst:!0,errorMessageProvider:nJ.defaultParserErrorProvider,nodeLocationTracking:"none",traceInitPerf:!1,skipValidations:!1});dr.DEFAULT_RULE_CONFIG=Object.freeze({recoveryValueFunc:function(){},resyncEnabled:!0});var Xye;(function(r){r[r.INVALID_RULE_NAME=0]="INVALID_RULE_NAME",r[r.DUPLICATE_RULE_NAME=1]="DUPLICATE_RULE_NAME",r[r.INVALID_RULE_OVERRIDE=2]="INVALID_RULE_OVERRIDE",r[r.DUPLICATE_PRODUCTIONS=3]="DUPLICATE_PRODUCTIONS",r[r.UNRESOLVED_SUBRULE_REF=4]="UNRESOLVED_SUBRULE_REF",r[r.LEFT_RECURSION=5]="LEFT_RECURSION",r[r.NONE_LAST_EMPTY_ALT=6]="NONE_LAST_EMPTY_ALT",r[r.AMBIGUOUS_ALTS=7]="AMBIGUOUS_ALTS",r[r.CONFLICT_TOKENS_RULES_NAMESPACE=8]="CONFLICT_TOKENS_RULES_NAMESPACE",r[r.INVALID_TOKEN_NAME=9]="INVALID_TOKEN_NAME",r[r.NO_NON_EMPTY_LOOKAHEAD=10]="NO_NON_EMPTY_LOOKAHEAD",r[r.AMBIGUOUS_PREFIX_ALTS=11]="AMBIGUOUS_PREFIX_ALTS",r[r.TOO_MANY_ALTS=12]="TOO_MANY_ALTS"})(Xye=dr.ParserDefinitionErrorType||(dr.ParserDefinitionErrorType={}));function Zye(r){return r===void 0&&(r=void 0),function(){return r}}dr.EMPTY_ALT=Zye;var vy=function(){function r(e,t){this.definitionErrors=[],this.selfAnalysisDone=!1;var i=this;if(i.initErrorHandler(t),i.initLexerAdapter(),i.initLooksAhead(t),i.initRecognizerEngine(e,t),i.initRecoverable(t),i.initTreeBuilder(t),i.initContentAssist(),i.initGastRecorder(t),i.initPerformanceTracer(t),(0,_i.has)(t,"ignoredIssues"))throw new Error(`The IParserConfig property has been deprecated. - Please use the flag on the relevant DSL method instead. - See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#IGNORING_AMBIGUITIES - For further details.`);this.skipValidations=(0,_i.has)(t,"skipValidations")?t.skipValidations:dr.DEFAULT_PARSER_CONFIG.skipValidations}return r.performSelfAnalysis=function(e){throw Error("The **static** `performSelfAnalysis` method has been deprecated. \nUse the **instance** method with the same name instead.")},r.prototype.performSelfAnalysis=function(){var e=this;this.TRACE_INIT("performSelfAnalysis",function(){var t;e.selfAnalysisDone=!0;var i=e.className;e.TRACE_INIT("toFastProps",function(){(0,_i.toFastProperties)(e)}),e.TRACE_INIT("Grammar Recording",function(){try{e.enableRecording(),(0,_i.forEach)(e.definedRulesNames,function(s){var o=e[s],a=o.originalGrammarAction,l=void 0;e.TRACE_INIT(s+" Rule",function(){l=e.topLevelRuleRecord(s,a)}),e.gastProductionsCache[s]=l})}finally{e.disableRecording()}});var n=[];if(e.TRACE_INIT("Grammar Resolving",function(){n=(0,rJ.resolveGrammar)({rules:(0,_i.values)(e.gastProductionsCache)}),e.definitionErrors=e.definitionErrors.concat(n)}),e.TRACE_INIT("Grammar Validations",function(){if((0,_i.isEmpty)(n)&&e.skipValidations===!1){var s=(0,rJ.validateGrammar)({rules:(0,_i.values)(e.gastProductionsCache),maxLookahead:e.maxLookahead,tokenTypes:(0,_i.values)(e.tokensMap),errMsgProvider:nJ.defaultGrammarValidatorErrorProvider,grammarName:i});e.definitionErrors=e.definitionErrors.concat(s)}}),(0,_i.isEmpty)(e.definitionErrors)&&(e.recoveryEnabled&&e.TRACE_INIT("computeAllProdsFollows",function(){var s=(0,Mye.computeAllProdsFollows)((0,_i.values)(e.gastProductionsCache));e.resyncFollows=s}),e.TRACE_INIT("ComputeLookaheadFunctions",function(){e.preComputeLookaheadFunctions((0,_i.values)(e.gastProductionsCache))})),!r.DEFER_DEFINITION_ERRORS_HANDLING&&!(0,_i.isEmpty)(e.definitionErrors))throw t=(0,_i.map)(e.definitionErrors,function(s){return s.message}),new Error(`Parser Definition Errors detected: - `+t.join(` -------------------------------- -`))})},r.DEFER_DEFINITION_ERRORS_HANDLING=!1,r}();dr.Parser=vy;(0,Vye.applyMixins)(vy,[Kye.Recoverable,Uye.LooksAhead,Hye.TreeBuilder,Gye.LexerAdapter,jye.RecognizerEngine,Yye.RecognizerApi,qye.ErrorHandler,Jye.ContentAssist,Wye.GastRecorder,zye.PerformanceTracer]);var _ye=function(r){iJ(e,r);function e(t,i){i===void 0&&(i=dr.DEFAULT_PARSER_CONFIG);var n=this,s=(0,_i.cloneObj)(i);return s.outputCst=!0,n=r.call(this,t,s)||this,n}return e}(vy);dr.CstParser=_ye;var $ye=function(r){iJ(e,r);function e(t,i){i===void 0&&(i=dr.DEFAULT_PARSER_CONFIG);var n=this,s=(0,_i.cloneObj)(i);return s.outputCst=!1,n=r.call(this,t,s)||this,n}return e}(vy);dr.EmbeddedActionsParser=$ye});var oJ=w(xy=>{"use strict";Object.defineProperty(xy,"__esModule",{value:!0});xy.createSyntaxDiagramsCode=void 0;var sJ=Dv();function ewe(r,e){var t=e===void 0?{}:e,i=t.resourceBase,n=i===void 0?"https://unpkg.com/chevrotain@"+sJ.VERSION+"/diagrams/":i,s=t.css,o=s===void 0?"https://unpkg.com/chevrotain@"+sJ.VERSION+"/diagrams/diagrams.css":s,a=` - - - - - -`,l=` - -`,c=` - + +
{children}
+ + + ) +} diff --git a/__fixtures__/test-project-rsa/web/src/Routes.tsx b/__fixtures__/test-project-rsa/web/src/Routes.tsx new file mode 100644 index 000000000000..89a1df33eef0 --- /dev/null +++ b/__fixtures__/test-project-rsa/web/src/Routes.tsx @@ -0,0 +1,31 @@ +// In this file, all Page components from 'src/pages` are auto-imported. Nested +// directories are supported, and should be uppercase. Each subdirectory will be +// prepended onto the component name. +// +// Examples: +// +// 'src/pages/HomePage/HomePage.js' -> HomePage +// 'src/pages/Admin/BooksPage/BooksPage.js' -> AdminBooksPage + +import { Router, Route, Set } from '@redwoodjs/router' +import { serve } from '@redwoodjs/vite/client' + +import NavigationLayout from './layouts/NavigationLayout/NavigationLayout' +import NotFoundPage from './pages/NotFoundPage/NotFoundPage' + +const AboutPage = serve('AboutPage') +const HomePage = serve('HomePage') + +const Routes = () => { + return ( + + + + + + + + ) +} + +export default Routes diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/directory-named-imports/TSWithIndex/index.js b/__fixtures__/test-project-rsa/web/src/components/.keep similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/directory-named-imports/TSWithIndex/index.js rename to __fixtures__/test-project-rsa/web/src/components/.keep diff --git a/__fixtures__/test-project-rsa/web/src/components/Counter/AboutCounter.tsx b/__fixtures__/test-project-rsa/web/src/components/Counter/AboutCounter.tsx new file mode 100644 index 000000000000..c86915e87f8b --- /dev/null +++ b/__fixtures__/test-project-rsa/web/src/components/Counter/AboutCounter.tsx @@ -0,0 +1,20 @@ +'use client' + +import React from 'react' + +// @ts-expect-error no types +import styles from './Counter.module.css' +import './Counter.css' + +export const AboutCounter = () => { + const [count, setCount] = React.useState(0) + + return ( +
+

Count: {count}

+ +

This is a client component.

+

RSC on client: {globalThis.RWJS_EXP_RSC ? 'enabled' : 'disabled'}

+
+ ) +} diff --git a/__fixtures__/test-project-rsa/web/src/components/Counter/Counter.css b/__fixtures__/test-project-rsa/web/src/components/Counter/Counter.css new file mode 100644 index 000000000000..4cbd74d7d5b6 --- /dev/null +++ b/__fixtures__/test-project-rsa/web/src/components/Counter/Counter.css @@ -0,0 +1,7 @@ +/** + * This should affect all h3 elements on the page, both server components and + * client components. This is just standard CSS stuff + */ +h3 { + color: orange; +} diff --git a/__fixtures__/test-project-rsa/web/src/components/Counter/Counter.module.css b/__fixtures__/test-project-rsa/web/src/components/Counter/Counter.module.css new file mode 100644 index 000000000000..736b0da8688c --- /dev/null +++ b/__fixtures__/test-project-rsa/web/src/components/Counter/Counter.module.css @@ -0,0 +1,3 @@ +.header { + font-style: italic; +} diff --git a/__fixtures__/test-project-rsa/web/src/components/Counter/Counter.tsx b/__fixtures__/test-project-rsa/web/src/components/Counter/Counter.tsx new file mode 100644 index 000000000000..5f21e7cdac2d --- /dev/null +++ b/__fixtures__/test-project-rsa/web/src/components/Counter/Counter.tsx @@ -0,0 +1,21 @@ +'use client' + +import React from 'react' + +import 'client-only' + +// @ts-expect-error no types +import styles from './Counter.module.css' +import './Counter.css' + +export const Counter = () => { + const [count, setCount] = React.useState(0) + + return ( +
+

Count: {count}

+ +

This is a client component.

+
+ ) +} diff --git a/__fixtures__/test-project-rsa/web/src/entries.ts b/__fixtures__/test-project-rsa/web/src/entries.ts new file mode 100644 index 000000000000..6259057e245b --- /dev/null +++ b/__fixtures__/test-project-rsa/web/src/entries.ts @@ -0,0 +1,15 @@ +import { defineEntries } from '@redwoodjs/vite/entries' + +export default defineEntries( + // getEntry + async (id) => { + switch (id) { + case 'AboutPage': + return import('./pages/AboutPage/AboutPage') + case 'HomePage': + return import('./pages/HomePage/HomePage') + default: + return null + } + } +) diff --git a/__fixtures__/test-project-rsa/web/src/entry.client.tsx b/__fixtures__/test-project-rsa/web/src/entry.client.tsx new file mode 100644 index 000000000000..d55036f35465 --- /dev/null +++ b/__fixtures__/test-project-rsa/web/src/entry.client.tsx @@ -0,0 +1,23 @@ +import { hydrateRoot, createRoot } from 'react-dom/client' + +import App from './App' +/** + * When `#redwood-app` isn't empty then it's very likely that you're using + * prerendering. So React attaches event listeners to the existing markup + * rather than replacing it. + * https://reactjs.org/docs/react-dom-client.html#hydrateroot + */ +const redwoodAppElement = document.getElementById('redwood-app') + +if (!redwoodAppElement) { + throw new Error( + "Could not find an element with ID 'redwood-app'. Please ensure it exists in your 'web/src/index.html' file." + ) +} + +if (redwoodAppElement.children?.length > 0) { + hydrateRoot(redwoodAppElement, ) +} else { + const root = createRoot(redwoodAppElement) + root.render() +} diff --git a/__fixtures__/test-project-rsa/web/src/entry.server.tsx b/__fixtures__/test-project-rsa/web/src/entry.server.tsx new file mode 100644 index 000000000000..a52b268b771d --- /dev/null +++ b/__fixtures__/test-project-rsa/web/src/entry.server.tsx @@ -0,0 +1,15 @@ +import App from './App' +import { Document } from './Document' + +interface Props { + css: string[] + meta?: any[] +} + +export const ServerEntry: React.FC = ({ css, meta }) => { + return ( + + + + ) +} diff --git a/__fixtures__/test-project-rsa/web/src/index.css b/__fixtures__/test-project-rsa/web/src/index.css new file mode 100644 index 000000000000..57c14ee231a9 --- /dev/null +++ b/__fixtures__/test-project-rsa/web/src/index.css @@ -0,0 +1,4 @@ +html, body { + margin: 0; + padding: 0; +} diff --git a/__fixtures__/test-project-rsa/web/src/index.html b/__fixtures__/test-project-rsa/web/src/index.html new file mode 100644 index 000000000000..6b3b066be037 --- /dev/null +++ b/__fixtures__/test-project-rsa/web/src/index.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + +
+ + + diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/directory-named-imports/TSX/TSX.tsx b/__fixtures__/test-project-rsa/web/src/layouts/.keep similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/directory-named-imports/TSX/TSX.tsx rename to __fixtures__/test-project-rsa/web/src/layouts/.keep diff --git a/__fixtures__/test-project-rsa/web/src/layouts/NavigationLayout/NavigationLayout.css b/__fixtures__/test-project-rsa/web/src/layouts/NavigationLayout/NavigationLayout.css new file mode 100644 index 000000000000..a3e7be665c31 --- /dev/null +++ b/__fixtures__/test-project-rsa/web/src/layouts/NavigationLayout/NavigationLayout.css @@ -0,0 +1,32 @@ +.navigation-layout { + & nav { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + background-color: color-mix(in srgb, yellow 50%, transparent); + border-bottom: 2px dashed color-mix(in srgb, yellow 90%, black); + } + + & ul { + list-style: none; + display: flex; + margin: 0; + padding: 0; + } + + & li { + margin-right: 10px; + } + + & a { + text-decoration: none; + color: #333; + padding: 5px; + border-bottom: 2px solid transparent; + } + + & a:hover { + border-bottom: 2px solid #333; + } +} diff --git a/__fixtures__/test-project-rsa/web/src/layouts/NavigationLayout/NavigationLayout.stories.tsx b/__fixtures__/test-project-rsa/web/src/layouts/NavigationLayout/NavigationLayout.stories.tsx new file mode 100644 index 000000000000..4ec5ddf88eb3 --- /dev/null +++ b/__fixtures__/test-project-rsa/web/src/layouts/NavigationLayout/NavigationLayout.stories.tsx @@ -0,0 +1,13 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import NavigationLayout from './NavigationLayout' + +const meta: Meta = { + component: NavigationLayout, +} + +export default meta + +type Story = StoryObj + +export const Primary: Story = {} diff --git a/__fixtures__/test-project-rsa/web/src/layouts/NavigationLayout/NavigationLayout.test.tsx b/__fixtures__/test-project-rsa/web/src/layouts/NavigationLayout/NavigationLayout.test.tsx new file mode 100644 index 000000000000..74fff5fc1509 --- /dev/null +++ b/__fixtures__/test-project-rsa/web/src/layouts/NavigationLayout/NavigationLayout.test.tsx @@ -0,0 +1,14 @@ +import { render } from '@redwoodjs/testing/web' + +import NavigationLayout from './NavigationLayout' + +// Improve this test with help from the Redwood Testing Doc: +// https://redwoodjs.com/docs/testing#testing-pages-layouts + +describe('NavigationLayout', () => { + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/__fixtures__/test-project-rsa/web/src/layouts/NavigationLayout/NavigationLayout.tsx b/__fixtures__/test-project-rsa/web/src/layouts/NavigationLayout/NavigationLayout.tsx new file mode 100644 index 000000000000..4f13e197309a --- /dev/null +++ b/__fixtures__/test-project-rsa/web/src/layouts/NavigationLayout/NavigationLayout.tsx @@ -0,0 +1,27 @@ +import { Link, routes } from '@redwoodjs/router' + +import './NavigationLayout.css' + +type NavigationLayoutProps = { + children?: React.ReactNode +} + +const NavigationLayout = ({ children }: NavigationLayoutProps) => { + return ( +
+ +
{children}
+
+ ) +} + +export default NavigationLayout diff --git a/__fixtures__/test-project-rsa/web/src/pages/AboutPage/AboutPage.css b/__fixtures__/test-project-rsa/web/src/pages/AboutPage/AboutPage.css new file mode 100644 index 000000000000..995b3bbde1e0 --- /dev/null +++ b/__fixtures__/test-project-rsa/web/src/pages/AboutPage/AboutPage.css @@ -0,0 +1,2 @@ +.about-page { +} diff --git a/__fixtures__/test-project-rsa/web/src/pages/AboutPage/AboutPage.tsx b/__fixtures__/test-project-rsa/web/src/pages/AboutPage/AboutPage.tsx new file mode 100644 index 000000000000..2706e12e63db --- /dev/null +++ b/__fixtures__/test-project-rsa/web/src/pages/AboutPage/AboutPage.tsx @@ -0,0 +1,27 @@ +import { Assets } from '@redwoodjs/vite/assets' +import { ProdRwRscServerGlobal } from '@redwoodjs/vite/rwRscGlobal' + +import { AboutCounter } from '../../components/Counter/AboutCounter' + +import './AboutPage.css' + +// TODO (RSC) Something like this will probably be needed +// const RwRscGlobal = import.meta.env.PROD ? ProdRwRscServerGlobal : DevRwRscServerGlobal; + +globalThis.rwRscGlobal = new ProdRwRscServerGlobal() + +const AboutPage = () => { + return ( +
+ {/* TODO (RSC) should be part of the router later */} + +
+

About Redwood

+ +

RSC on server: {globalThis.RWJS_EXP_RSC ? 'enabled' : 'disabled'}

+
+
+ ) +} + +export default AboutPage diff --git a/__fixtures__/test-project-rsa/web/src/pages/FatalErrorPage/FatalErrorPage.tsx b/__fixtures__/test-project-rsa/web/src/pages/FatalErrorPage/FatalErrorPage.tsx new file mode 100644 index 000000000000..b2bb436f8ed0 --- /dev/null +++ b/__fixtures__/test-project-rsa/web/src/pages/FatalErrorPage/FatalErrorPage.tsx @@ -0,0 +1,57 @@ +// This page will be rendered when an error makes it all the way to the top of the +// application without being handled by a Javascript catch statement or React error +// boundary. +// +// You can modify this page as you wish, but it is important to keep things simple to +// avoid the possibility that it will cause its own error. If it does, Redwood will +// still render a generic error page, but your users will prefer something a bit more +// thoughtful :) + +// This import will be automatically removed when building for production +import { DevFatalErrorPage } from '@redwoodjs/web/dist/components/DevFatalErrorPage' + +export default DevFatalErrorPage || + (() => ( +
+ + +
{children}
+ + + ) +} diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/Routes.tsx b/__fixtures__/test-project-rsc-external-packages/web/src/Routes.tsx new file mode 100644 index 000000000000..89a1df33eef0 --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages/web/src/Routes.tsx @@ -0,0 +1,31 @@ +// In this file, all Page components from 'src/pages` are auto-imported. Nested +// directories are supported, and should be uppercase. Each subdirectory will be +// prepended onto the component name. +// +// Examples: +// +// 'src/pages/HomePage/HomePage.js' -> HomePage +// 'src/pages/Admin/BooksPage/BooksPage.js' -> AdminBooksPage + +import { Router, Route, Set } from '@redwoodjs/router' +import { serve } from '@redwoodjs/vite/client' + +import NavigationLayout from './layouts/NavigationLayout/NavigationLayout' +import NotFoundPage from './pages/NotFoundPage/NotFoundPage' + +const AboutPage = serve('AboutPage') +const HomePage = serve('HomePage') + +const Routes = () => { + return ( + + + + + + + + ) +} + +export default Routes diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/import-dir/__fixtures__/types.d.ts b/__fixtures__/test-project-rsc-external-packages/web/src/components/.keep similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/import-dir/__fixtures__/types.d.ts rename to __fixtures__/test-project-rsc-external-packages/web/src/components/.keep diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/components/Counter/AboutCounter.tsx b/__fixtures__/test-project-rsc-external-packages/web/src/components/Counter/AboutCounter.tsx new file mode 100644 index 000000000000..c86915e87f8b --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages/web/src/components/Counter/AboutCounter.tsx @@ -0,0 +1,20 @@ +'use client' + +import React from 'react' + +// @ts-expect-error no types +import styles from './Counter.module.css' +import './Counter.css' + +export const AboutCounter = () => { + const [count, setCount] = React.useState(0) + + return ( +
+

Count: {count}

+ +

This is a client component.

+

RSC on client: {globalThis.RWJS_EXP_RSC ? 'enabled' : 'disabled'}

+
+ ) +} diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/components/Counter/Counter.css b/__fixtures__/test-project-rsc-external-packages/web/src/components/Counter/Counter.css new file mode 100644 index 000000000000..4cbd74d7d5b6 --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages/web/src/components/Counter/Counter.css @@ -0,0 +1,7 @@ +/** + * This should affect all h3 elements on the page, both server components and + * client components. This is just standard CSS stuff + */ +h3 { + color: orange; +} diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/components/Counter/Counter.module.css b/__fixtures__/test-project-rsc-external-packages/web/src/components/Counter/Counter.module.css new file mode 100644 index 000000000000..736b0da8688c --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages/web/src/components/Counter/Counter.module.css @@ -0,0 +1,3 @@ +.header { + font-style: italic; +} diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/components/Counter/Counter.tsx b/__fixtures__/test-project-rsc-external-packages/web/src/components/Counter/Counter.tsx new file mode 100644 index 000000000000..5f21e7cdac2d --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages/web/src/components/Counter/Counter.tsx @@ -0,0 +1,21 @@ +'use client' + +import React from 'react' + +import 'client-only' + +// @ts-expect-error no types +import styles from './Counter.module.css' +import './Counter.css' + +export const Counter = () => { + const [count, setCount] = React.useState(0) + + return ( +
+

Count: {count}

+ +

This is a client component.

+
+ ) +} diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/entries.ts b/__fixtures__/test-project-rsc-external-packages/web/src/entries.ts new file mode 100644 index 000000000000..6259057e245b --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages/web/src/entries.ts @@ -0,0 +1,15 @@ +import { defineEntries } from '@redwoodjs/vite/entries' + +export default defineEntries( + // getEntry + async (id) => { + switch (id) { + case 'AboutPage': + return import('./pages/AboutPage/AboutPage') + case 'HomePage': + return import('./pages/HomePage/HomePage') + default: + return null + } + } +) diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/entry.client.tsx b/__fixtures__/test-project-rsc-external-packages/web/src/entry.client.tsx new file mode 100644 index 000000000000..d55036f35465 --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages/web/src/entry.client.tsx @@ -0,0 +1,23 @@ +import { hydrateRoot, createRoot } from 'react-dom/client' + +import App from './App' +/** + * When `#redwood-app` isn't empty then it's very likely that you're using + * prerendering. So React attaches event listeners to the existing markup + * rather than replacing it. + * https://reactjs.org/docs/react-dom-client.html#hydrateroot + */ +const redwoodAppElement = document.getElementById('redwood-app') + +if (!redwoodAppElement) { + throw new Error( + "Could not find an element with ID 'redwood-app'. Please ensure it exists in your 'web/src/index.html' file." + ) +} + +if (redwoodAppElement.children?.length > 0) { + hydrateRoot(redwoodAppElement, ) +} else { + const root = createRoot(redwoodAppElement) + root.render() +} diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/entry.server.tsx b/__fixtures__/test-project-rsc-external-packages/web/src/entry.server.tsx new file mode 100644 index 000000000000..a52b268b771d --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages/web/src/entry.server.tsx @@ -0,0 +1,15 @@ +import App from './App' +import { Document } from './Document' + +interface Props { + css: string[] + meta?: any[] +} + +export const ServerEntry: React.FC = ({ css, meta }) => { + return ( + + + + ) +} diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/index.css b/__fixtures__/test-project-rsc-external-packages/web/src/index.css new file mode 100644 index 000000000000..57c14ee231a9 --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages/web/src/index.css @@ -0,0 +1,4 @@ +html, body { + margin: 0; + padding: 0; +} diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/index.html b/__fixtures__/test-project-rsc-external-packages/web/src/index.html new file mode 100644 index 000000000000..6b3b066be037 --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages/web/src/index.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + +
+ + + diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/layouts/.keep b/__fixtures__/test-project-rsc-external-packages/web/src/layouts/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/layouts/NavigationLayout/NavigationLayout.css b/__fixtures__/test-project-rsc-external-packages/web/src/layouts/NavigationLayout/NavigationLayout.css new file mode 100644 index 000000000000..a3e7be665c31 --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages/web/src/layouts/NavigationLayout/NavigationLayout.css @@ -0,0 +1,32 @@ +.navigation-layout { + & nav { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + background-color: color-mix(in srgb, yellow 50%, transparent); + border-bottom: 2px dashed color-mix(in srgb, yellow 90%, black); + } + + & ul { + list-style: none; + display: flex; + margin: 0; + padding: 0; + } + + & li { + margin-right: 10px; + } + + & a { + text-decoration: none; + color: #333; + padding: 5px; + border-bottom: 2px solid transparent; + } + + & a:hover { + border-bottom: 2px solid #333; + } +} diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/layouts/NavigationLayout/NavigationLayout.stories.tsx b/__fixtures__/test-project-rsc-external-packages/web/src/layouts/NavigationLayout/NavigationLayout.stories.tsx new file mode 100644 index 000000000000..4ec5ddf88eb3 --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages/web/src/layouts/NavigationLayout/NavigationLayout.stories.tsx @@ -0,0 +1,13 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import NavigationLayout from './NavigationLayout' + +const meta: Meta = { + component: NavigationLayout, +} + +export default meta + +type Story = StoryObj + +export const Primary: Story = {} diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/layouts/NavigationLayout/NavigationLayout.test.tsx b/__fixtures__/test-project-rsc-external-packages/web/src/layouts/NavigationLayout/NavigationLayout.test.tsx new file mode 100644 index 000000000000..74fff5fc1509 --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages/web/src/layouts/NavigationLayout/NavigationLayout.test.tsx @@ -0,0 +1,14 @@ +import { render } from '@redwoodjs/testing/web' + +import NavigationLayout from './NavigationLayout' + +// Improve this test with help from the Redwood Testing Doc: +// https://redwoodjs.com/docs/testing#testing-pages-layouts + +describe('NavigationLayout', () => { + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/layouts/NavigationLayout/NavigationLayout.tsx b/__fixtures__/test-project-rsc-external-packages/web/src/layouts/NavigationLayout/NavigationLayout.tsx new file mode 100644 index 000000000000..4f13e197309a --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages/web/src/layouts/NavigationLayout/NavigationLayout.tsx @@ -0,0 +1,27 @@ +import { Link, routes } from '@redwoodjs/router' + +import './NavigationLayout.css' + +type NavigationLayoutProps = { + children?: React.ReactNode +} + +const NavigationLayout = ({ children }: NavigationLayoutProps) => { + return ( +
+ +
{children}
+
+ ) +} + +export default NavigationLayout diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/pages/AboutPage/AboutPage.css b/__fixtures__/test-project-rsc-external-packages/web/src/pages/AboutPage/AboutPage.css new file mode 100644 index 000000000000..995b3bbde1e0 --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages/web/src/pages/AboutPage/AboutPage.css @@ -0,0 +1,2 @@ +.about-page { +} diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/pages/AboutPage/AboutPage.tsx b/__fixtures__/test-project-rsc-external-packages/web/src/pages/AboutPage/AboutPage.tsx new file mode 100644 index 000000000000..2706e12e63db --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages/web/src/pages/AboutPage/AboutPage.tsx @@ -0,0 +1,27 @@ +import { Assets } from '@redwoodjs/vite/assets' +import { ProdRwRscServerGlobal } from '@redwoodjs/vite/rwRscGlobal' + +import { AboutCounter } from '../../components/Counter/AboutCounter' + +import './AboutPage.css' + +// TODO (RSC) Something like this will probably be needed +// const RwRscGlobal = import.meta.env.PROD ? ProdRwRscServerGlobal : DevRwRscServerGlobal; + +globalThis.rwRscGlobal = new ProdRwRscServerGlobal() + +const AboutPage = () => { + return ( +
+ {/* TODO (RSC) should be part of the router later */} + +
+

About Redwood

+ +

RSC on server: {globalThis.RWJS_EXP_RSC ? 'enabled' : 'disabled'}

+
+
+ ) +} + +export default AboutPage diff --git a/__fixtures__/test-project-rsc-external-packages/web/src/pages/FatalErrorPage/FatalErrorPage.tsx b/__fixtures__/test-project-rsc-external-packages/web/src/pages/FatalErrorPage/FatalErrorPage.tsx new file mode 100644 index 000000000000..b2bb436f8ed0 --- /dev/null +++ b/__fixtures__/test-project-rsc-external-packages/web/src/pages/FatalErrorPage/FatalErrorPage.tsx @@ -0,0 +1,57 @@ +// This page will be rendered when an error makes it all the way to the top of the +// application without being handled by a Javascript catch statement or React error +// boundary. +// +// You can modify this page as you wish, but it is important to keep things simple to +// avoid the possibility that it will cause its own error. If it does, Redwood will +// still render a generic error page, but your users will prefer something a bit more +// thoughtful :) + +// This import will be automatically removed when building for production +import { DevFatalErrorPage } from '@redwoodjs/web/dist/components/DevFatalErrorPage' + +export default DevFatalErrorPage || + (() => ( +
+ - -`,l=` - -`,c=` - + Redwood App | Redwood App + + + + + + + + + + +
+
+

Redwood Blog

+ +
+
+

This site was created to demonstrate my mastery of Redwood: Look on my works, ye mighty, and + despair!

+ +
+
+ + + diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app-fallback/web/dist/index.html b/packages/api-server/src/__tests__/fixtures/redwood-app-fallback/web/dist/index.html new file mode 100644 index 000000000000..0e54fa2690c7 --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app-fallback/web/dist/index.html @@ -0,0 +1,79 @@ + + + + + Redwood App | Redwood App + + + + + + + + + + +
+
+

Redwood Blog

+ +
+
+
+
+
+

October 13, 2023 - By: User One + (user.one@example.com)

+

Welcome to the + blog!

+
+
I'm baby single- origin coffee kickstarter lo - fi paleo + skateboard.Tumblr hashtag austin whatever DIY plaid knausgaard fanny pack messenger bag blog next level + woke.Ethical bitters fixie freegan,helvetica pitchfork 90's tbh chillwave mustache godard subway tile ramps + art party. Hammock sustainable twee yr bushwick disrupt unicorn, before they sold out direct trade + chicharrones etsy polaroid hoodie. Gentrify offal hoodie fingerstache.
+
+
+
+

October 13, 2023 - By: User Two + (user.two@example.com)

+

What is the + meaning of life?

+
+
Meh waistcoat succulents umami asymmetrical, hoodie + post-ironic paleo chillwave tote bag. Trust fund kitsch waistcoat vape, cray offal gochujang food truck + cloud bread enamel pin forage. Roof party chambray ugh occupy fam stumptown. Dreamcatcher tousled snackwave, + typewriter lyft unicorn pabst portland blue bottle locavore squid PBR&B tattooed.
+
+
+
+

October 13, 2023 - By: User One + (user.one@example.com)

+

A little more + about me

+
+
Raclette shoreditch before they sold out lyft. Ethical bicycle + rights meh prism twee. Tote bag ennui vice, slow-carb taiyaki crucifix whatever you probably haven't heard + of them jianbing raw denim DIY hot chicken. Chillwave blog succulents freegan synth af ramps poutine + wayfarers yr seitan roof party squid. Jianbing flexitarian gentrify hexagon portland single-origin coffee + raclette gluten-free. Coloring book cloud bread street art kitsch lumbersexual af distillery ethical ugh + thundercats roof party poke chillwave. 90's palo santo green juice subway tile, prism viral butcher selvage + etsy pitchfork sriracha tumeric bushwick.
+
+
+ +
+
+ + + diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app-number-functions/api/dist/functions/1/1.js b/packages/api-server/src/__tests__/fixtures/redwood-app-number-functions/api/dist/functions/1/1.js new file mode 100644 index 000000000000..15cfe208cf15 --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app-number-functions/api/dist/functions/1/1.js @@ -0,0 +1,19 @@ +/** + * @typedef { import('aws-lambda').APIGatewayEvent } APIGatewayEvent + * @typedef { import('aws-lambda').Context } Context + * @param { APIGatewayEvent } event + * @param { Context } context + */ +const handler = async (event, _context) => { + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + data: 'number function', + }), + } +} + +module.exports = { handler } diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app-number-functions/api/dist/functions/graphql.js b/packages/api-server/src/__tests__/fixtures/redwood-app-number-functions/api/dist/functions/graphql.js new file mode 100644 index 000000000000..9fb67748eb5d --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app-number-functions/api/dist/functions/graphql.js @@ -0,0 +1,29 @@ +/** + * @typedef { import('aws-lambda').APIGatewayEvent } APIGatewayEvent + * @typedef { import('aws-lambda').Context } Context + * @param { APIGatewayEvent } event + * @param { Context } context + */ +const handler = async (event, _context) => { + const { query } = event.queryStringParameters + + if (query.trim() !== "{redwood{version}}") { + return { + statusCode: 400 + } + } + + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + data: { + version: 42 + }, + }), + } +} + +module.exports = { handler } diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app-number-functions/redwood.toml b/packages/api-server/src/__tests__/fixtures/redwood-app-number-functions/redwood.toml new file mode 100644 index 000000000000..147631de6159 --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app-number-functions/redwood.toml @@ -0,0 +1,21 @@ +# This file contains the configuration settings for your Redwood app. +# This file is also what makes your Redwood app a Redwood app. +# If you remove it and try to run `yarn rw dev`, you'll get an error. +# +# For the full list of options, see the "App Configuration: redwood.toml" doc: +# https://redwoodjs.com/docs/app-configuration-redwood-toml + +[web] + title = "Redwood App" + port = 8910 + apiUrl = "/.redwood/functions" # You can customize graphql and dbauth urls individually too: see https://redwoodjs.com/docs/app-configuration-redwood-toml#api-paths + includeEnvironmentVariables = [ + # Add any ENV vars that should be available to the web side to this array + # See https://redwoodjs.com/docs/environment-variables#web + ] +[api] + port = 8911 +[browser] + open = true +[notifications] + versionUpdates = ["latest"] diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/.env.defaults b/packages/api-server/src/__tests__/fixtures/redwood-app/.env.defaults new file mode 100644 index 000000000000..f644ff583db7 --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/.env.defaults @@ -0,0 +1 @@ +LOAD_ENV_DEFAULTS_TEST=42 diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/deeplyNested/nestedDir/deeplyNested.js b/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/deeplyNested/nestedDir/deeplyNested.js new file mode 100644 index 000000000000..8f3f42e5b4da --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/deeplyNested/nestedDir/deeplyNested.js @@ -0,0 +1,19 @@ +/** + * @typedef { import('aws-lambda').APIGatewayEvent } APIGatewayEvent + * @typedef { import('aws-lambda').Context } Context + * @param { APIGatewayEvent } event + * @param { Context } context + */ +const handler = async (event, _context) => { + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + data: 'deeply nested function', + }), + } +} + +module.exports = { handler } diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/env.js b/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/env.js new file mode 100644 index 000000000000..93df345b952a --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/env.js @@ -0,0 +1,19 @@ +/** + * @typedef { import('aws-lambda').APIGatewayEvent } APIGatewayEvent + * @typedef { import('aws-lambda').Context } Context + * @param { APIGatewayEvent } event + * @param { Context } context + */ +const handler = async (event, _context) => { + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + data: process.env.LOAD_ENV_DEFAULTS_TEST, + }), + } +} + +module.exports = { handler } diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/graphql.js b/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/graphql.js new file mode 100644 index 000000000000..9fb67748eb5d --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/graphql.js @@ -0,0 +1,29 @@ +/** + * @typedef { import('aws-lambda').APIGatewayEvent } APIGatewayEvent + * @typedef { import('aws-lambda').Context } Context + * @param { APIGatewayEvent } event + * @param { Context } context + */ +const handler = async (event, _context) => { + const { query } = event.queryStringParameters + + if (query.trim() !== "{redwood{version}}") { + return { + statusCode: 400 + } + } + + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + data: { + version: 42 + }, + }), + } +} + +module.exports = { handler } diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/health.js b/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/health.js new file mode 100644 index 000000000000..3be65e235cbd --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/health.js @@ -0,0 +1,7 @@ +const handler = async () => { + return { + statusCode: 200, + } +} + +module.exports = { handler } diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/hello.js b/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/hello.js new file mode 100644 index 000000000000..62a749d44e8b --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/hello.js @@ -0,0 +1,19 @@ +/** + * @typedef { import('aws-lambda').APIGatewayEvent } APIGatewayEvent + * @typedef { import('aws-lambda').Context } Context + * @param { APIGatewayEvent } event + * @param { Context } context + */ +const handler = async (event, _context) => { + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + data: 'hello function', + }), + } +} + +module.exports = { handler } diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/nested/nested.js b/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/nested/nested.js new file mode 100644 index 000000000000..11e3048bc928 --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/nested/nested.js @@ -0,0 +1,19 @@ +/** + * @typedef { import('aws-lambda').APIGatewayEvent } APIGatewayEvent + * @typedef { import('aws-lambda').Context } Context + * @param { APIGatewayEvent } event + * @param { Context } context + */ +const handler = async (event, _context) => { + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + data: 'nested function', + }), + } +} + +module.exports = { handler } diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/noHandler.js b/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/noHandler.js new file mode 100644 index 000000000000..3d4b958aa487 --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/api/dist/functions/noHandler.js @@ -0,0 +1,3 @@ +const version = '42' + +module.exports = { version } diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/api/server.config.js b/packages/api-server/src/__tests__/fixtures/redwood-app/api/server.config.js new file mode 100644 index 000000000000..2d56f961257d --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/api/server.config.js @@ -0,0 +1,64 @@ +/** + * This file allows you to configure the Fastify Server settings + * used by the RedwoodJS dev server. + * + * It also applies when running RedwoodJS with `yarn rw serve`. + * + * For the Fastify server options that you can set, see: + * https://www.fastify.io/docs/latest/Reference/Server/#factory + * + * Examples include: logger settings, timeouts, maximum payload limits, and more. + * + * Note: This configuration does not apply in a serverless deploy. + */ + +/** @type {import('fastify').FastifyServerOptions} */ +const config = { + requestTimeout: 15_000, + logger: false, +} + +/** + * You can also register Fastify plugins and additional routes for the API and Web sides + * in the configureFastify function. + * + * This function has access to the Fastify instance and options, such as the side + * (web, api, or proxy) that is being configured and other settings like the apiRootPath + * of the functions endpoint. + * + * Note: This configuration does not apply in a serverless deploy. + */ + +/** @type {import('@redwoodjs/api-server/dist/types').FastifySideConfigFn} */ +const configureFastify = async (fastify, options) => { + if (options.side === 'api') { + fastify.log.trace({ custom: { options } }, 'Configuring api side') + + fastify.get( + `/rest/v1/users/get/:userId`, + async function (request, reply) { + const { userId } = request.params + + return reply.send({ + id: 1 + }) + } + ) + } + + if (options.side === 'web') { + fastify.log.trace({ custom: { options } }, 'Configuring web side') + + fastify.get('/test-route', async (_request, _reply) => { + return { message: options.message } + }) + } + + return fastify +} + +module.exports = { + config, + configureFastify, +} + diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/redwood.toml b/packages/api-server/src/__tests__/fixtures/redwood-app/redwood.toml new file mode 100644 index 000000000000..147631de6159 --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/redwood.toml @@ -0,0 +1,21 @@ +# This file contains the configuration settings for your Redwood app. +# This file is also what makes your Redwood app a Redwood app. +# If you remove it and try to run `yarn rw dev`, you'll get an error. +# +# For the full list of options, see the "App Configuration: redwood.toml" doc: +# https://redwoodjs.com/docs/app-configuration-redwood-toml + +[web] + title = "Redwood App" + port = 8910 + apiUrl = "/.redwood/functions" # You can customize graphql and dbauth urls individually too: see https://redwoodjs.com/docs/app-configuration-redwood-toml#api-paths + includeEnvironmentVariables = [ + # Add any ENV vars that should be available to the web side to this array + # See https://redwoodjs.com/docs/environment-variables#web + ] +[api] + port = 8911 +[browser] + open = true +[notifications] + versionUpdates = ["latest"] diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/200.html b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/200.html new file mode 100644 index 000000000000..355801d52690 --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/200.html @@ -0,0 +1,17 @@ + + + + + + + + + + + + + +
+ + + diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/404.html b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/404.html new file mode 100644 index 000000000000..f6d55df34ba6 --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/404.html @@ -0,0 +1,65 @@ + + + + + + Redwood App | Redwood App + + + + + + + + + + +
+
+ +
+

404 Page Not Found

+
+
+ +
+ + + diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/README.md b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/README.md new file mode 100644 index 000000000000..345ab0cd5acf --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/README.md @@ -0,0 +1,54 @@ +# Static Assets + +Use this folder to add static files directly to your app. All included files and +folders will be copied directly into the `/dist` folder (created when Vite +builds for production). They will also be available during development when you +run `yarn rw dev`. >Note: files will _not_ hot reload while the development +server is running. You'll need to manually stop/start to access file changes. + +### Example Use + +A file like `favicon.png` will be copied to `/dist/favicon.png`. A folder +containing a file such as `static-files/my-logo.jpg` will be copied to +`/dist/static-files/my-logo.jpg`. These can be referenced in your code directly +without any special handling, e.g. + +``` + +``` + +and + +``` + alt="Logo" /> +``` + +## Best Practices + +Because assets in this folder are bypassing the javascript module system, **this +folder should be used sparingly** for assets such as favicons, robots.txt, +manifests, libraries incompatible with Vite, etc. + +In general, it's best to import files directly into a template, page, or +component. This allows Vite to include that file in the bundle when small +enough, or to copy it over to the `dist` folder with a hash. + +### Example Asset Import with Vite + +Instead of handling our logo image as a static file per the example above, we +can do the following: + +``` +import React from "react" +import logo from "./my-logo.jpg" + + +function Header() { + return Logo +} + +export default Header +``` + +See Vite's docs for +[static asset handling](https://vitejs.dev/guide/assets.html) diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/about.html b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/about.html new file mode 100644 index 000000000000..9daca9e2fa83 --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/about.html @@ -0,0 +1,40 @@ + + + + + + Redwood App | Redwood App + + + + + + + + + + +
+
+

Redwood Blog

+ +
+
+

This site was created to demonstrate my mastery of Redwood: Look on my works, ye mighty, and + despair!

+ +
+
+ + + diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/assets/AboutPage-7ec0f8df.js b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/assets/AboutPage-7ec0f8df.js new file mode 100644 index 000000000000..a679b2cfce14 --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/assets/AboutPage-7ec0f8df.js @@ -0,0 +1,3 @@ +import{j as t}from"./index-ff057e8f.js";const o=()=>t.jsx("p",{className:"font-light",children:"This site was created to demonstrate my mastery of Redwood: Look on my works, ye mighty, and despair!"});export{o as default}; +globalThis.__REDWOOD__PRERENDER_PAGES = globalThis.__REDWOOD__PRERENDER_PAGES || {}; +globalThis.__REDWOOD__PRERENDER_PAGES.AboutPage=o; diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/assets/index-613d397d.css b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/assets/index-613d397d.css new file mode 100644 index 000000000000..a46c81a539ee --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/assets/index-613d397d.css @@ -0,0 +1,2 @@ +.rw-scaffold{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity))}.rw-scaffold h1,.rw-scaffold h2{margin:0}.rw-scaffold a{background-color:transparent}.rw-scaffold ul{margin:0;padding:0}.rw-scaffold input::-moz-placeholder{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity))}.rw-scaffold input::placeholder{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity))}.rw-header{display:flex;justify-content:space-between;padding:1rem 2rem}.rw-main{margin-left:1rem;margin-right:1rem;padding-bottom:1rem}.rw-segment{width:100%;overflow:hidden;border-radius:.5rem;border-width:1px;--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity));scrollbar-color:#a1a1aa transparent}.rw-segment::-webkit-scrollbar{height:initial}.rw-segment::-webkit-scrollbar-track{border-radius:0 0 10px 10px/0px 0px 10px 10px;border-width:0px;border-top-width:1px;border-style:solid;--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity));background-color:transparent;padding:2px}.rw-segment::-webkit-scrollbar-thumb{border-radius:9999px;border-width:3px;border-style:solid;border-color:transparent;--tw-bg-opacity: 1;background-color:rgb(161 161 170 / var(--tw-bg-opacity));background-clip:content-box}.rw-segment-header{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity));padding:.75rem 1rem;--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity))}.rw-segment-main{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity));padding:1rem}.rw-link{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity));text-decoration-line:underline}.rw-link:hover{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity))}.rw-forgot-link{margin-top:.25rem;text-align:right;font-size:.75rem;line-height:1rem;--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity));text-decoration-line:underline}.rw-forgot-link:hover{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity))}.rw-heading{font-weight:600}.rw-heading.rw-heading-primary{font-size:1.25rem;line-height:1.75rem}.rw-heading.rw-heading-secondary{font-size:.875rem;line-height:1.25rem}.rw-heading .rw-link{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity));text-decoration-line:none}.rw-heading .rw-link:hover{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity));text-decoration-line:underline}.rw-cell-error{font-size:.875rem;line-height:1.25rem;font-weight:600}.rw-form-wrapper{margin-top:-1rem;font-size:.875rem;line-height:1.25rem}.rw-cell-error,.rw-form-error-wrapper{margin-top:1rem;margin-bottom:1rem;border-radius:.25rem;border-width:1px;--tw-border-opacity: 1;border-color:rgb(254 226 226 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity));padding:1rem;--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity))}.rw-form-error-title{margin:0;font-weight:600}.rw-form-error-list{margin-top:.5rem;list-style-position:inside;list-style-type:disc}.rw-button{display:flex;cursor:pointer;justify-content:center;border-radius:.25rem;border-width:0px;--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity));padding:.25rem 1rem;font-size:.75rem;line-height:1rem;font-weight:600;text-transform:uppercase;line-height:2;letter-spacing:.025em;--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity));text-decoration-line:none;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.1s}.rw-button:hover{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.rw-button.rw-button-small{border-radius:.125rem;padding:.25rem .5rem;font-size:.75rem;line-height:1rem}.rw-button.rw-button-green{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.rw-button.rw-button-green:hover{--tw-bg-opacity: 1;background-color:rgb(21 128 61 / var(--tw-bg-opacity))}.rw-button.rw-button-blue{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.rw-button.rw-button-blue:hover{--tw-bg-opacity: 1;background-color:rgb(29 78 216 / var(--tw-bg-opacity))}.rw-button.rw-button-red{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.rw-button.rw-button-red:hover{--tw-bg-opacity: 1;background-color:rgb(185 28 28 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.rw-button-icon{margin-right:.25rem;font-size:1.25rem;line-height:1.25rem}.rw-button-group{margin:.75rem .5rem;display:flex;justify-content:center}.rw-button-group .rw-button{margin-left:.25rem;margin-right:.25rem}.rw-form-wrapper .rw-button-group{margin-top:2rem}.rw-label{margin-top:1.5rem;display:block;text-align:left;font-weight:600;--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity))}.rw-label.rw-label-error{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity))}.rw-input{margin-top:.5rem;display:block;width:100%;border-radius:.25rem;border-width:1px;--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity));padding:.5rem;outline:2px solid transparent;outline-offset:2px}.rw-check-radio-items{display:flex;justify-items:center}.rw-check-radio-item-none{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity))}.rw-input[type=checkbox],.rw-input[type=radio]{margin-left:0;margin-right:.25rem;margin-top:.25rem;display:inline;width:1rem}.rw-input:focus{--tw-border-opacity: 1;border-color:rgb(156 163 175 / var(--tw-border-opacity))}.rw-input-error{--tw-border-opacity: 1;border-color:rgb(220 38 38 / var(--tw-border-opacity));--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity))}.rw-input-error:focus{--tw-border-opacity: 1;border-color:rgb(220 38 38 / var(--tw-border-opacity));outline:2px solid transparent;outline-offset:2px;box-shadow:0 0 5px #c53030}.rw-field-error{margin-top:.25rem;display:block;font-size:.75rem;line-height:1rem;font-weight:600;text-transform:uppercase;--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity))}.rw-table-wrapper-responsive{overflow-x:auto}.rw-table-wrapper-responsive .rw-table{min-width:48rem}.rw-table{width:100%;font-size:.875rem;line-height:1.25rem}.rw-table th,.rw-table td{padding:.75rem}.rw-table td{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity))}.rw-table tr:nth-child(odd) td,.rw-table tr:nth-child(odd) th{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity))}.rw-table thead tr{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity))}.rw-table th{text-align:left;font-weight:600}.rw-table thead th{text-align:left}.rw-table tbody th{text-align:right}@media (min-width: 768px){.rw-table tbody th{width:20%}}.rw-table tbody tr{border-top-width:1px;--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity))}.rw-table input{margin-left:0}.rw-table-actions{display:flex;height:1rem;align-items:center;justify-content:flex-end;padding-right:.25rem}.rw-table-actions .rw-button{background-color:transparent}.rw-table-actions .rw-button:hover{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.rw-table-actions .rw-button-blue{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity))}.rw-table-actions .rw-button-blue:hover{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.rw-table-actions .rw-button-red{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity))}.rw-table-actions .rw-button-red:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity));--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.rw-text-center{text-align:center}.rw-login-container{margin-left:auto;margin-right:auto;margin-top:4rem;margin-bottom:4rem;display:flex;width:24rem;flex-wrap:wrap;align-items:center;justify-content:center}.rw-login-container .rw-form-wrapper{width:100%;text-align:center}.rw-login-link{margin-top:1rem;width:100%;text-align:center;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity))}.rw-webauthn-wrapper{margin-left:1rem;margin-right:1rem;margin-top:1.5rem;line-height:1.5rem}.rw-webauthn-wrapper h2{margin-bottom:1rem;font-size:1.25rem;line-height:1.75rem;font-weight:700}/*! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com + */*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.relative{position:relative}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-4{margin-bottom:1rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.block{display:block}.flex{display:flex}.table{display:table}.max-w-4xl{max-width:56rem}.items-center{align-items:center}.justify-between{justify-content:space-between}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-sm{border-radius:.125rem}.rounded-b{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.border{border-width:1px}.border-red-700{--tw-border-opacity: 1;border-color:rgb(185 28 28 / var(--tw-border-opacity))}.bg-blue-700{--tw-bg-opacity: 1;background-color:rgb(29 78 216 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.p-12{padding:3rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.text-left{text-align:left}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-light{font-weight:300}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-tight{letter-spacing:-.025em}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity))}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity))}.text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.underline{text-decoration-line:underline}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-100{transition-duration:.1s}.visited\:text-purple-600:visited{color:#9333ea}.hover\:bg-blue-600:hover{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity))}.hover\:text-blue-100:hover{--tw-text-opacity: 1;color:rgb(219 234 254 / var(--tw-text-opacity))}.hover\:text-blue-600:hover{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity))}.hover\:text-blue-800:hover{--tw-text-opacity: 1;color:rgb(30 64 175 / var(--tw-text-opacity))} diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/build-manifest.json b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/build-manifest.json new file mode 100644 index 000000000000..ac9125cd9908 --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/build-manifest.json @@ -0,0 +1,230 @@ +{ + "_ContactForm-d76f67ab.js": { + "file": "assets/ContactForm-d76f67ab.js", + "imports": [ + "index.html", + "_index-77bc0912.js" + ] + }, + "_PostForm-4b7853da.js": { + "file": "assets/PostForm-4b7853da.js", + "imports": [ + "index.html", + "_index-77bc0912.js" + ] + }, + "_formatters-2fce1756.js": { + "file": "assets/formatters-2fce1756.js", + "imports": [ + "index.html" + ] + }, + "_index-77bc0912.js": { + "file": "assets/index-77bc0912.js", + "imports": [ + "index.html" + ] + }, + "index.css": { + "file": "assets/index-613d397d.css", + "src": "index.css" + }, + "index.html": { + "css": [ + "assets/index-613d397d.css" + ], + "dynamicImports": [ + "pages/AboutPage/AboutPage.tsx", + "pages/BlogPostPage/BlogPostPage.tsx", + "pages/ContactUsPage/ContactUsPage.tsx", + "pages/DoublePage/DoublePage.tsx", + "pages/ForgotPasswordPage/ForgotPasswordPage.tsx", + "pages/LoginPage/LoginPage.tsx", + "pages/NotFoundPage/NotFoundPage.tsx", + "pages/ProfilePage/ProfilePage.tsx", + "pages/ResetPasswordPage/ResetPasswordPage.tsx", + "pages/SignupPage/SignupPage.tsx", + "pages/WaterfallPage/WaterfallPage.tsx", + "pages/Contact/ContactPage/ContactPage.tsx", + "pages/Contact/ContactsPage/ContactsPage.tsx", + "pages/Contact/EditContactPage/EditContactPage.tsx", + "pages/Contact/NewContactPage/NewContactPage.tsx", + "pages/Post/EditPostPage/EditPostPage.tsx", + "pages/Post/NewPostPage/NewPostPage.tsx", + "pages/Post/PostPage/PostPage.tsx", + "pages/Post/PostsPage/PostsPage.tsx" + ], + "file": "assets/index-ff057e8f.js", + "isEntry": true, + "src": "index.html" + }, + "pages/AboutPage/AboutPage.tsx": { + "file": "assets/AboutPage-7ec0f8df.js", + "imports": [ + "index.html" + ], + "isDynamicEntry": true, + "src": "pages/AboutPage/AboutPage.tsx" + }, + "pages/BlogPostPage/BlogPostPage.tsx": { + "file": "assets/BlogPostPage-526c7060.js", + "imports": [ + "index.html" + ], + "isDynamicEntry": true, + "src": "pages/BlogPostPage/BlogPostPage.tsx" + }, + "pages/Contact/ContactPage/ContactPage.tsx": { + "file": "assets/ContactPage-4a851c42.js", + "imports": [ + "index.html", + "_formatters-2fce1756.js" + ], + "isDynamicEntry": true, + "src": "pages/Contact/ContactPage/ContactPage.tsx" + }, + "pages/Contact/ContactsPage/ContactsPage.tsx": { + "file": "assets/ContactsPage-1fcf6187.js", + "imports": [ + "index.html", + "_formatters-2fce1756.js" + ], + "isDynamicEntry": true, + "src": "pages/Contact/ContactsPage/ContactsPage.tsx" + }, + "pages/Contact/EditContactPage/EditContactPage.tsx": { + "file": "assets/EditContactPage-1622b085.js", + "imports": [ + "index.html", + "_ContactForm-d76f67ab.js", + "_index-77bc0912.js" + ], + "isDynamicEntry": true, + "src": "pages/Contact/EditContactPage/EditContactPage.tsx" + }, + "pages/Contact/NewContactPage/NewContactPage.tsx": { + "file": "assets/NewContactPage-5935f0db.js", + "imports": [ + "index.html", + "_ContactForm-d76f67ab.js", + "_index-77bc0912.js" + ], + "isDynamicEntry": true, + "src": "pages/Contact/NewContactPage/NewContactPage.tsx" + }, + "pages/ContactUsPage/ContactUsPage.tsx": { + "file": "assets/ContactUsPage-71f00589.js", + "imports": [ + "index.html", + "_index-77bc0912.js" + ], + "isDynamicEntry": true, + "src": "pages/ContactUsPage/ContactUsPage.tsx" + }, + "pages/DoublePage/DoublePage.tsx": { + "file": "assets/DoublePage-0bee4876.js", + "imports": [ + "index.html" + ], + "isDynamicEntry": true, + "src": "pages/DoublePage/DoublePage.tsx" + }, + "pages/ForgotPasswordPage/ForgotPasswordPage.tsx": { + "file": "assets/ForgotPasswordPage-15d7cf2f.js", + "imports": [ + "index.html", + "_index-77bc0912.js" + ], + "isDynamicEntry": true, + "src": "pages/ForgotPasswordPage/ForgotPasswordPage.tsx" + }, + "pages/LoginPage/LoginPage.tsx": { + "file": "assets/LoginPage-5f6d498c.js", + "imports": [ + "index.html", + "_index-77bc0912.js" + ], + "isDynamicEntry": true, + "src": "pages/LoginPage/LoginPage.tsx" + }, + "pages/NotFoundPage/NotFoundPage.tsx": { + "file": "assets/NotFoundPage-0903a03f.js", + "imports": [ + "index.html" + ], + "isDynamicEntry": true, + "src": "pages/NotFoundPage/NotFoundPage.tsx" + }, + "pages/Post/EditPostPage/EditPostPage.tsx": { + "file": "assets/EditPostPage-abe727e6.js", + "imports": [ + "index.html", + "_PostForm-4b7853da.js", + "_index-77bc0912.js" + ], + "isDynamicEntry": true, + "src": "pages/Post/EditPostPage/EditPostPage.tsx" + }, + "pages/Post/NewPostPage/NewPostPage.tsx": { + "file": "assets/NewPostPage-dcbeffd5.js", + "imports": [ + "index.html", + "_PostForm-4b7853da.js", + "_index-77bc0912.js" + ], + "isDynamicEntry": true, + "src": "pages/Post/NewPostPage/NewPostPage.tsx" + }, + "pages/Post/PostPage/PostPage.tsx": { + "file": "assets/PostPage-292888c6.js", + "imports": [ + "index.html", + "_formatters-2fce1756.js" + ], + "isDynamicEntry": true, + "src": "pages/Post/PostPage/PostPage.tsx" + }, + "pages/Post/PostsPage/PostsPage.tsx": { + "file": "assets/PostsPage-cacd5a1e.js", + "imports": [ + "index.html", + "_formatters-2fce1756.js" + ], + "isDynamicEntry": true, + "src": "pages/Post/PostsPage/PostsPage.tsx" + }, + "pages/ProfilePage/ProfilePage.tsx": { + "file": "assets/ProfilePage-133e6e05.js", + "imports": [ + "index.html" + ], + "isDynamicEntry": true, + "src": "pages/ProfilePage/ProfilePage.tsx" + }, + "pages/ResetPasswordPage/ResetPasswordPage.tsx": { + "file": "assets/ResetPasswordPage-a3399e1b.js", + "imports": [ + "index.html", + "_index-77bc0912.js" + ], + "isDynamicEntry": true, + "src": "pages/ResetPasswordPage/ResetPasswordPage.tsx" + }, + "pages/SignupPage/SignupPage.tsx": { + "file": "assets/SignupPage-44411fe1.js", + "imports": [ + "index.html", + "_index-77bc0912.js" + ], + "isDynamicEntry": true, + "src": "pages/SignupPage/SignupPage.tsx" + }, + "pages/WaterfallPage/WaterfallPage.tsx": { + "file": "assets/WaterfallPage-46b80a6f.js", + "imports": [ + "index.html" + ], + "isDynamicEntry": true, + "src": "pages/WaterfallPage/WaterfallPage.tsx" + } +} diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/contacts/new.html b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/contacts/new.html new file mode 100644 index 000000000000..a3d4460288bb --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/contacts/new.html @@ -0,0 +1,50 @@ + + + + + + Redwood App | Redwood App + + + + + + + + + + +
+
+
+
+

Contacts

+
+
New Contact +
+
+
+
+
+

New Contact

+
+
+
+
+
+
+
+
+
+ +
+
+
+ + + diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/favicon.png b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/favicon.png new file mode 100644 index 000000000000..47414294173c Binary files /dev/null and b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/favicon.png differ diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/index.html b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/index.html new file mode 100644 index 000000000000..0e54fa2690c7 --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/index.html @@ -0,0 +1,79 @@ + + + + + Redwood App | Redwood App + + + + + + + + + + +
+
+

Redwood Blog

+ +
+
+
+
+
+

October 13, 2023 - By: User One + (user.one@example.com)

+

Welcome to the + blog!

+
+
I'm baby single- origin coffee kickstarter lo - fi paleo + skateboard.Tumblr hashtag austin whatever DIY plaid knausgaard fanny pack messenger bag blog next level + woke.Ethical bitters fixie freegan,helvetica pitchfork 90's tbh chillwave mustache godard subway tile ramps + art party. Hammock sustainable twee yr bushwick disrupt unicorn, before they sold out direct trade + chicharrones etsy polaroid hoodie. Gentrify offal hoodie fingerstache.
+
+
+
+

October 13, 2023 - By: User Two + (user.two@example.com)

+

What is the + meaning of life?

+
+
Meh waistcoat succulents umami asymmetrical, hoodie + post-ironic paleo chillwave tote bag. Trust fund kitsch waistcoat vape, cray offal gochujang food truck + cloud bread enamel pin forage. Roof party chambray ugh occupy fam stumptown. Dreamcatcher tousled snackwave, + typewriter lyft unicorn pabst portland blue bottle locavore squid PBR&B tattooed.
+
+
+
+

October 13, 2023 - By: User One + (user.one@example.com)

+

A little more + about me

+
+
Raclette shoreditch before they sold out lyft. Ethical bicycle + rights meh prism twee. Tote bag ennui vice, slow-carb taiyaki crucifix whatever you probably haven't heard + of them jianbing raw denim DIY hot chicken. Chillwave blog succulents freegan synth af ramps poutine + wayfarers yr seitan roof party squid. Jianbing flexitarian gentrify hexagon portland single-origin coffee + raclette gluten-free. Coloring book cloud bread street art kitsch lumbersexual af distillery ethical ugh + thundercats roof party poke chillwave. 90's palo santo green juice subway tile, prism viral butcher selvage + etsy pitchfork sriracha tumeric bushwick.
+
+
+ +
+
+ + + diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/nested/index.html b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/nested/index.html new file mode 100644 index 000000000000..355801d52690 --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/nested/index.html @@ -0,0 +1,17 @@ + + + + + + + + + + + + + +
+ + + diff --git a/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/robots.txt b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/robots.txt new file mode 100644 index 000000000000..eb0536286f30 --- /dev/null +++ b/packages/api-server/src/__tests__/fixtures/redwood-app/web/dist/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/packages/api-server/src/__tests__/lambdaLoader.test.ts b/packages/api-server/src/__tests__/lambdaLoader.test.ts new file mode 100644 index 000000000000..3228ff0d68fe --- /dev/null +++ b/packages/api-server/src/__tests__/lambdaLoader.test.ts @@ -0,0 +1,75 @@ +import path from 'path' + +import { + LAMBDA_FUNCTIONS, + loadFunctionsFromDist, +} from '../plugins/lambdaLoader' + +// Suppress terminal logging. +console.log = jest.fn() +console.warn = jest.fn() + +// Set up RWJS_CWD. +let original_RWJS_CWD + +beforeAll(() => { + original_RWJS_CWD = process.env.RWJS_CWD + process.env.RWJS_CWD = path.resolve(__dirname, 'fixtures/redwood-app') +}) + +afterAll(() => { + process.env.RWJS_CWD = original_RWJS_CWD +}) + +// Reset the LAMBDA_FUNCTIONS object after each test. +afterEach(() => { + for (const key in LAMBDA_FUNCTIONS) { + delete LAMBDA_FUNCTIONS[key] + } +}) + +describe('loadFunctionsFromDist', () => { + it('loads functions from the api/dist directory', async () => { + expect(LAMBDA_FUNCTIONS).toEqual({}) + + await loadFunctionsFromDist() + + expect(LAMBDA_FUNCTIONS).toEqual({ + env: expect.any(Function), + graphql: expect.any(Function), + health: expect.any(Function), + hello: expect.any(Function), + nested: expect.any(Function), + }) + }) + + // We have logic that specifically puts the graphql function at the front. + // Though it's not clear why or if this is actually respected by how JS objects work. + // See the complementary lambdaLoaderNumberFunctions test. + it('puts the graphql function first', async () => { + expect(LAMBDA_FUNCTIONS).toEqual({}) + + await loadFunctionsFromDist() + + expect(Object.keys(LAMBDA_FUNCTIONS)[0]).toEqual('graphql') + }) + + // `loadFunctionsFromDist` loads files that don't export a handler into the object as `undefined`. + // This is probably harmless, but we could also probably go without it. + it("warns if a function doesn't have a handler and sets it to `undefined`", async () => { + expect(LAMBDA_FUNCTIONS).toEqual({}) + + await loadFunctionsFromDist() + + expect(LAMBDA_FUNCTIONS).toMatchObject({ + noHandler: undefined, + }) + + expect(console.warn).toHaveBeenCalledWith( + 'noHandler', + 'at', + expect.any(String), + 'does not have a function called handler defined.' + ) + }) +}) diff --git a/packages/api-server/src/__tests__/lambdaLoaderNumberFunctions.test.ts b/packages/api-server/src/__tests__/lambdaLoaderNumberFunctions.test.ts new file mode 100644 index 000000000000..ddeaba9bc151 --- /dev/null +++ b/packages/api-server/src/__tests__/lambdaLoaderNumberFunctions.test.ts @@ -0,0 +1,32 @@ +import path from 'path' + +import { + LAMBDA_FUNCTIONS, + loadFunctionsFromDist, +} from '../plugins/lambdaLoader' + +// Suppress terminal logging. +console.log = jest.fn() + +// Set up RWJS_CWD. +let original_RWJS_CWD + +beforeAll(() => { + original_RWJS_CWD = process.env.RWJS_CWD + process.env.RWJS_CWD = path.resolve( + __dirname, + 'fixtures/redwood-app-number-functions' + ) +}) + +afterAll(() => { + process.env.RWJS_CWD = original_RWJS_CWD +}) + +test('loadFunctionsFromDist puts functions named with numbers before the graphql function', async () => { + expect(LAMBDA_FUNCTIONS).toEqual({}) + + await loadFunctionsFromDist() + + expect(Object.keys(LAMBDA_FUNCTIONS)[0]).toEqual('1') +}) diff --git a/packages/api-server/src/__tests__/logFormatter.test.ts b/packages/api-server/src/__tests__/logFormatter.test.ts index 4f71aa127583..d258472da76d 100644 --- a/packages/api-server/src/__tests__/logFormatter.test.ts +++ b/packages/api-server/src/__tests__/logFormatter.test.ts @@ -4,63 +4,63 @@ const logFormatter = LogFormatter() describe('LogFormatter', () => { describe('Formats log levels as emoji', () => { - test('Formats Trace level', () => { + it('Formats Trace level', () => { expect(logFormatter({ level: 10 })).toMatch('🧵') }) - test('Formats Debug level', () => { + it('Formats Debug level', () => { expect(logFormatter({ level: 20 })).toMatch('🐛') }) - test('Formats Info level', () => { + it('Formats Info level', () => { expect(logFormatter({ level: 30 })).toMatch('🌲') }) - test('Formats Warn level', () => { + it('Formats Warn level', () => { expect(logFormatter({ level: 40 })).toMatch('🚦') }) - test('Formats Error level', () => { + it('Formats Error level', () => { expect(logFormatter({ level: 50 })).toMatch('🚨') }) }) describe('Formats log messages', () => { - test('Formats newline-delimited json data with a message', () => { + it('Formats newline-delimited json data with a message', () => { expect( logFormatter({ level: 10, message: 'Message in a bottle' }) ).toMatch('Message in a bottle') }) - test('Formats newline-delimited json data with a msg', () => { + it('Formats newline-delimited json data with a msg', () => { expect(logFormatter({ level: 10, msg: 'Message in a bottle' })).toMatch( 'Message in a bottle' ) }) - test('Formats a text message', () => { + it('Formats a text message', () => { expect(logFormatter('Handles text data')).toMatch('Handles text data') }) - test('Formats Get Method and Status Code', () => { + it('Formats Get Method and Status Code', () => { const logData = { level: 10, method: 'GET', statusCode: 200 } expect(logFormatter(logData)).toMatch('GET') expect(logFormatter(logData)).toMatch('200') }) - test('Formats Post Method and Status Code', () => { + it('Formats Post Method and Status Code', () => { const logData = { level: 10, method: 'POST', statusCode: 200 } expect(logFormatter(logData)).toMatch('POST') expect(logFormatter(logData)).toMatch('200') }) - test('Should not format Status Code without a Method', () => { + it('Should not format Status Code without a Method', () => { expect(logFormatter({ level: 10, statusCode: 200 })).not.toMatch('200') }) }) describe('Formats GraphQL injected log data from useRedwoodLogger plugin', () => { - test('Handles query', () => { + it('Handles query', () => { expect( logFormatter({ level: 10, @@ -71,13 +71,13 @@ describe('LogFormatter', () => { ).toMatch('"id": 1') }) - test('Handles operation name', () => { + it('Handles operation name', () => { expect( logFormatter({ level: 10, operationName: 'GET_BLOG_POST_BY_ID' }) ).toMatch('GET_BLOG_POST_BY_ID') }) - test('Handles GraphQL data', () => { + it('Handles GraphQL data', () => { expect( logFormatter({ level: 10, @@ -86,7 +86,7 @@ describe('LogFormatter', () => { ).toMatch('My Blog Post') }) - test('Handles browser user agent', () => { + it('Handles browser user agent', () => { const userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15' expect( @@ -98,19 +98,8 @@ describe('LogFormatter', () => { }) }) - describe('Unknown log data', () => { - test('Should not include an unknown log data attribute', () => { - expect( - logFormatter({ - level: 10, - unknown: 'I should not see this', - }) - ).not.toMatch('I should not see this') - }) - }) - describe('Custom log data', () => { - test('Should include the custom log attribute text', () => { + it('Should include the custom log attribute text', () => { expect( logFormatter({ level: 10, @@ -119,7 +108,7 @@ describe('LogFormatter', () => { ).toMatch('I should see this') }) - test('Should include the custom log attribute info a custom emoji and label', () => { + it('Should include the custom log attribute info a custom emoji and label', () => { expect( logFormatter({ level: 10, @@ -128,55 +117,76 @@ describe('LogFormatter', () => { ).toMatch('🗒 Custom') }) - test('Should include the custom log attribute info with nested text message', () => { + it('Should include the custom log attribute info with nested text message', () => { expect( logFormatter({ level: 10, custom: { - msg: 'I should see this custom message in the log', + string: 'I should see this custom message in the log', }, }) ).toMatch('I should see this custom message in the log') }) - }) - test('Should include the custom log attribute info with a number attribute', () => { - expect( - logFormatter({ - level: 10, - custom: { - msg: 'I should see this custom message and number in the log', - number: 100, - }, - }) - ).toMatch('100') - }) + it('Should include the custom log attribute info with a number attribute', () => { + expect( + logFormatter({ + level: 10, + custom: { + string: 'I should see this custom message and number in the log', + number: 100, + }, + }) + ).toMatch('100') + }) - test('Should include the custom log attribute info with a nested object attribute', () => { - expect( - logFormatter({ - level: 10, - custom: { - msg: 'I should see this custom object in the log', - obj: { foo: 'bar' }, - }, - }) - ).toMatch('"foo": "bar"') - }) + it('Should include the custom log attribute info with a nested object attribute', () => { + expect( + logFormatter({ + level: 10, + custom: { + string: 'I should see this custom object in the log', + obj: { foo: 'bar' }, + }, + }) + ).toMatch('"foo": "bar"') + }) - test('Should include the custom log attribute info with a nested object attribute', () => { - expect( - logFormatter({ - level: 10, - custom: { - msg: 'I should see this custom object in the log', - obj: { foo: 'bar' }, - }, - }) - ).toMatch('"foo": "bar"') + it('Should include the custom log attribute info with a nested object attribute', () => { + expect( + logFormatter({ + level: 10, + custom: { + string: 'I should see this custom object in the log', + obj: { foo: 'bar' }, + }, + }) + ).toMatch('"foo": "bar"') + }) + + it('Should filter out overly verbose custom log attributes', () => { + expect( + logFormatter({ + level: 10, + custom: { + time: 1, + pid: 1, + hostname: 'should not appear', + reqId: 'should not appear', + req: { + method: 'should not appear', + url: 'should not appear', + hostname: 'should not appear', + remoteAddress: 'should not appear', + remotePort: 1, + }, + }, + }) + ).not.toMatch('should not appear') + }) }) - test('Should format error stack traces', () => { + it('Should format error stack traces', () => { expect( logFormatter({ level: 50, @@ -189,7 +199,7 @@ describe('LogFormatter', () => { ).toMatch(/at some line number/) }) - test('Should format error and include the error type', () => { + it('Should format error and include the error type', () => { expect( logFormatter({ level: 50, @@ -202,4 +212,72 @@ describe('LogFormatter', () => { }) ).toMatch(/GraphQL Error Info/) }) + + describe('When there are additional options', () => { + it('Should format and include additional options without custom tag', () => { + expect( + logFormatter({ + level: 10, + apiVersion: '4.2.1', + environment: 'staging', + }) + ).toMatch('"apiVersion": "4.2.1"') + + expect( + logFormatter({ + level: 10, + apiVersion: '4.2.1', + environment: 'staging', + }) + ).toMatch('"environment": "staging"') + }) + + it('Should format and include additional nested options without custom tag', () => { + expect( + logFormatter({ + level: 10, + deploy: { + environment: 'staging', + version: '4.2.1', + }, + }) + ).toMatch('"deploy"') + + expect( + logFormatter({ + level: 10, + deploy: { + environment: 'staging', + version: '4.2.1', + }, + }) + ).toMatch('"environment": "staging"') + + logFormatter({ + level: 10, + deploy: { + environment: 'staging', + version: '4.2.1', + }, + }) // ? + + expect( + logFormatter({ + level: 10, + deploy: { + environment: 'staging', + version: '4.2.1', + }, + }) + ).toMatch('"version": "4.2.1"') + }) + }) + + it('Should not have any undefined ns, name, or message', () => { + expect( + logFormatter({ + level: 10, + }) + ).not.toContain('undefined') + }) }) diff --git a/packages/api-server/src/__tests__/withApiProxy.test.ts b/packages/api-server/src/__tests__/withApiProxy.test.ts index f0c6502d9ca4..593bba043d99 100644 --- a/packages/api-server/src/__tests__/withApiProxy.test.ts +++ b/packages/api-server/src/__tests__/withApiProxy.test.ts @@ -1,65 +1,23 @@ -import path from 'path' - -import { FastifyInstance } from 'fastify' +import httpProxy from '@fastify/http-proxy' +import type { FastifyInstance } from 'fastify' import withApiProxy from '../plugins/withApiProxy' -const FIXTURE_PATH = path.resolve( - __dirname, - '../../../../__fixtures__/example-todo-main' -) - -// Mock the dist folder from fixtures, -// because its gitignored -jest.mock('@redwoodjs/internal', () => { - return { - ...jest.requireActual('@redwoodjs/internal'), - } -}) - -jest.mock('../fastify', () => { - return { - ...jest.requireActual('../fastify'), - loadFastifyConfig: jest.fn().mockReturnValue({ - config: {}, - configureFastify: jest.fn((fastify) => fastify), - }), +test('withApiProxy registers `@fastify/http-proxy`', async () => { + const mockedFastifyInstance = { + register: jest.fn(), } -}) -describe('Configures the ApiProxy', () => { - beforeAll(() => { - process.env.RWJS_CWD = FIXTURE_PATH + // `apiUrl` is unfortunately named. It isn't a URL, it's just a prefix. Meanwhile, `apiHost` _is_ a URL. + // See https://github.com/fastify/fastify-http-proxy and https://github.com/fastify/fastify-reply-from. + await withApiProxy(mockedFastifyInstance as unknown as FastifyInstance, { + apiUrl: 'my-api-host', + apiHost: 'http://localhost:8910', }) - afterAll(() => { - delete process.env.RWJS_CWD - }) - - beforeEach(() => { - jest.clearAllMocks() - }) - - test('Checks that the fastify http-proxy plugin is configured correctly', async () => { - const mockedFastifyInstance = { - register: jest.fn(), - get: jest.fn(), - all: jest.fn(), - addContentTypeParser: jest.fn(), - log: console, - } - - await withApiProxy(mockedFastifyInstance as unknown as FastifyInstance, { - apiUrl: 'http://localhost', - apiHost: 'my-api-host', - }) - - const mockedFastifyInstanceOptions = - mockedFastifyInstance.register.mock.calls[0][1] - expect(mockedFastifyInstanceOptions).toEqual({ - disableCache: true, - prefix: 'http://localhost', - upstream: 'my-api-host', - }) + expect(mockedFastifyInstance.register).toHaveBeenCalledWith(httpProxy, { + disableCache: true, + prefix: 'my-api-host', + upstream: 'http://localhost:8910', }) }) diff --git a/packages/api-server/src/__tests__/withFunctions.test.ts b/packages/api-server/src/__tests__/withFunctions.test.ts index f39b7a23c1bf..45cd602f5d67 100644 --- a/packages/api-server/src/__tests__/withFunctions.test.ts +++ b/packages/api-server/src/__tests__/withFunctions.test.ts @@ -1,141 +1,151 @@ import path from 'path' -import { FastifyInstance, FastifyPluginCallback } from 'fastify' - -import { loadFastifyConfig } from '../fastify' +import createFastifyInstance from '../fastify' import withFunctions from '../plugins/withFunctions' -const FIXTURE_PATH = path.resolve( - __dirname, - '../../../../__fixtures__/example-todo-main' -) - -// Mock the dist folder from fixtures, -// because its gitignored -jest.mock('@redwoodjs/internal', () => { - return { - ...jest.requireActual('@redwoodjs/internal'), - } -}) +// Suppress terminal logging. +console.log = jest.fn() +console.warn = jest.fn() -jest.mock('../fastify', () => { - return { - ...jest.requireActual('../fastify'), - loadFastifyConfig: jest.fn(), - } +// Set up RWJS_CWD. +let original_RWJS_CWD + +beforeAll(() => { + original_RWJS_CWD = process.env.RWJS_CWD + process.env.RWJS_CWD = path.resolve(__dirname, 'fixtures/redwood-app') }) -jest.mock('../plugins/lambdaLoader', () => { - return { - loadFunctionsFromDist: jest.fn(), - lambdaRequestHandler: jest.fn(), - } +afterAll(() => { + process.env.RWJS_CWD = original_RWJS_CWD }) -describe('Checks that configureFastify is called for the api side', () => { - beforeAll(() => { - process.env.RWJS_CWD = FIXTURE_PATH +// Set up and teardown the fastify instance for each test. +let fastifyInstance +let returnedFastifyInstance + +beforeAll(async () => { + fastifyInstance = createFastifyInstance() + + returnedFastifyInstance = await withFunctions(fastifyInstance, { + port: 8911, + apiRootPath: '/', }) - afterAll(() => { - delete process.env.RWJS_CWD + + await fastifyInstance.ready() +}) + +afterAll(async () => { + await fastifyInstance.close() +}) + +describe('withFunctions', () => { + // Deliberately using `toBe` here to check for referential equality. + it('returns the same fastify instance', async () => { + expect(returnedFastifyInstance).toBe(fastifyInstance) }) - beforeEach(() => { - jest.clearAllMocks() + it('configures the `@fastify/url-data` and `fastify-raw-body` plugins', async () => { + const plugins = fastifyInstance.printPlugins() + + expect(plugins.includes('@fastify/url-data')).toEqual(true) + expect(plugins.includes('fastify-raw-body')).toEqual(true) }) - const mockedFastifyInstance = { - register: jest.fn(), - get: jest.fn((routeName) => routeName), - all: jest.fn(), - addContentTypeParser: jest.fn(), - setNotFoundHandler: jest.fn(), - log: jest.fn(), - } as unknown as FastifyInstance - - // We're mocking a fake plugin, so don't worry about the type - const registerCustomPlugin = - 'I was registered by the custom configureFastify function' as unknown as FastifyPluginCallback - - // Mock the load fastify config function - ;(loadFastifyConfig as jest.Mock).mockReturnValue({ - config: {}, - configureFastify: jest.fn((fastify) => { - fastify.register(registerCustomPlugin) - - fastify.get( - `/rest/v1/users/get/:userId`, - async function (request, reply) { - const { userId } = request.params as any - - return reply.send(`Get User ${userId}!`) - } - ) - fastify.version = 'bazinga' - return fastify - }), + it('configures two additional content type parsers, `application/x-www-form-urlencoded` and `multipart/form-data`', async () => { + expect( + fastifyInstance.hasContentTypeParser('application/x-www-form-urlencoded') + ).toEqual(true) + expect(fastifyInstance.hasContentTypeParser('multipart/form-data')).toEqual( + true + ) }) - it('Verify that configureFastify is called with the expected side and options', async () => { - const { configureFastify } = loadFastifyConfig() - await withFunctions(mockedFastifyInstance, { - apiRootPath: '/kittens', - port: 5555, + it('can be configured by the user', async () => { + const res = await fastifyInstance.inject({ + method: 'GET', + url: '/rest/v1/users/get/1', }) - expect(configureFastify).toHaveBeenCalledTimes(1) + expect(res.body).toEqual(JSON.stringify({ id: 1 })) + }) - expect(configureFastify).toHaveBeenCalledWith(expect.anything(), { - side: 'api', - apiRootPath: '/kittens', - port: 5555, - }) + // We use `fastify.all` to register functions, which means they're invoked for all HTTP verbs. + // Only testing GET and POST here at the moment. + // + // We can use `printRoutes` with a method for debugging, but not without one. + // See https://fastify.dev/docs/latest/Reference/Server#printroutes + it('builds a tree of routes for GET and POST', async () => { + expect(fastifyInstance.printRoutes({ method: 'GET' })) + .toMatchInlineSnapshot(` + "└── / + ├── rest/v1/users/get/ + │ └── :userId (GET) + └── :routeName (GET) + └── / + └── * (GET) + " + `) + + expect(fastifyInstance.printRoutes({ method: 'POST' })) + .toMatchInlineSnapshot(` + "└── / + └── :routeName (POST) + └── / + └── * (POST) + " + `) }) - it('Check that configureFastify registers a plugin', async () => { - await withFunctions(mockedFastifyInstance, { - apiRootPath: '/kittens', - port: 5555, + describe('serves functions', () => { + it('serves hello.js', async () => { + const res = await fastifyInstance.inject({ + method: 'GET', + url: '/hello', + }) + + expect(res.statusCode).toEqual(200) + expect(res.json()).toEqual({ data: 'hello function' }) }) - expect(mockedFastifyInstance.register).toHaveBeenCalledWith( - 'I was registered by the custom configureFastify function' - ) - }) + it('it serves graphql.js', async () => { + const res = await fastifyInstance.inject({ + method: 'POST', + url: '/graphql?query={redwood{version}}', + }) - // Note: This tests an undocumented use of configureFastify to register a route - it('Check that configureFastify registers a route', async () => { - await withFunctions(mockedFastifyInstance, { - apiRootPath: '/boots', - port: 5554, + expect(res.statusCode).toEqual(200) + expect(res.json()).toEqual({ data: { version: 42 } }) }) - expect(mockedFastifyInstance.get).toHaveBeenCalledWith( - `/rest/v1/users/get/:userId`, - expect.any(Function) - ) - }) + it('serves health.js', async () => { + const res = await fastifyInstance.inject({ + method: 'GET', + url: '/health', + }) - it('Check that withFunctions returns the same Fastify instance, and not a new one', async () => { - await withFunctions(mockedFastifyInstance, { - apiRootPath: '/bazinga', - port: 5556, + expect(res.statusCode).toEqual(200) }) - expect(mockedFastifyInstance.version).toBe('bazinga') - }) + it('serves a nested function, nested.js', async () => { + const res = await fastifyInstance.inject({ + method: 'GET', + url: '/nested/nested', + }) - it('Does not throw when configureFastify is missing from server config', () => { - ;(loadFastifyConfig as jest.Mock).mockReturnValue({ - config: {}, - configureFastify: null, + expect(res.statusCode).toEqual(200) + expect(res.json()).toEqual({ data: 'nested function' }) }) - expect( - withFunctions(mockedFastifyInstance, { - apiRootPath: '/bazinga', - port: 5556, + it("doesn't serve deeply-nested functions", async () => { + const res = await fastifyInstance.inject({ + method: 'GET', + url: '/deeplyNested/nestedDir/deeplyNested', }) - ).resolves.not.toThrowError() + + expect(res.statusCode).toEqual(404) + expect(res.body).toEqual( + 'Function "deeplyNested" was not found.' + ) + }) }) }) diff --git a/packages/api-server/src/__tests__/withWebServer.test.ts b/packages/api-server/src/__tests__/withWebServer.test.ts index e1aad9497270..07a5f7eabe80 100644 --- a/packages/api-server/src/__tests__/withWebServer.test.ts +++ b/packages/api-server/src/__tests__/withWebServer.test.ts @@ -1,143 +1,303 @@ +import fs from 'fs' import path from 'path' -import { FastifyInstance, FastifyPluginCallback } from 'fastify' +import { getPaths } from '@redwoodjs/project-config' -import { loadFastifyConfig } from '../fastify' +import { createFastifyInstance } from '../fastify' import withWebServer from '../plugins/withWebServer' -const FIXTURE_PATH = path.resolve( - __dirname, - '../../../../__fixtures__/example-todo-main' -) - -// Mock the dist folder from fixtures, -// because its gitignored -jest.mock('@redwoodjs/internal/dist/files', () => { - return { - ...jest.requireActual('@redwoodjs/internal/dist/files'), - findPrerenderedHtml: () => { - return ['about.html', 'mocked.html', 'posts/new.html', 'index.html'] - }, - } -}) +// Suppress terminal logging. +console.log = jest.fn() -jest.mock('../fastify', () => { - return { - ...jest.requireActual('../fastify'), - loadFastifyConfig: jest.fn(), - } -}) +// Set up RWJS_CWD. +let original_RWJS_CWD beforeAll(() => { - process.env.RWJS_CWD = FIXTURE_PATH + original_RWJS_CWD = process.env.RWJS_CWD + process.env.RWJS_CWD = path.join(__dirname, 'fixtures/redwood-app') }) + afterAll(() => { - delete process.env.RWJS_CWD + process.env.RWJS_CWD = original_RWJS_CWD }) -test('Attach handlers for prerendered files', async () => { - const mockedFastifyInstance = { - register: jest.fn(), - get: jest.fn(), - setNotFoundHandler: jest.fn(), - log: console, - } as unknown as FastifyInstance - - await withWebServer(mockedFastifyInstance, { port: 3000 }) - - expect(mockedFastifyInstance.get).toHaveBeenCalledWith( - '/about', - expect.anything() - ) - expect(mockedFastifyInstance.get).toHaveBeenCalledWith( - '/mocked', - expect.anything() - ) - expect(mockedFastifyInstance.get).toHaveBeenCalledWith( - '/posts/new', - expect.anything() - ) - - // Ignore index.html - expect(mockedFastifyInstance.get).not.toHaveBeenCalledWith( - '/index', - expect.anything() - ) -}) +// Set up and teardown the fastify instance with options. +let fastifyInstance +let returnedFastifyInstance -test('Adds SPA fallback', async () => { - const mockedFastifyInstance = { - register: jest.fn(), - get: jest.fn(), - setNotFoundHandler: jest.fn(), - log: console, - } as unknown as FastifyInstance +const port = 8910 +const message = 'hello from server.config.js' + +beforeAll(async () => { + fastifyInstance = createFastifyInstance() + + returnedFastifyInstance = await withWebServer(fastifyInstance, { + port, + // @ts-expect-error just testing that options can be passed through + message, + }) - await withWebServer(mockedFastifyInstance, { port: 3000 }) + await fastifyInstance.ready() +}) - expect(mockedFastifyInstance.setNotFoundHandler).toHaveBeenCalled() +afterAll(async () => { + await fastifyInstance.close() }) -describe('Checks that configureFastify is called for the web side', () => { - beforeEach(() => { - jest.clearAllMocks() +describe('withWebServer', () => { + // Deliberately using `toBe` here to check for referential equality. + it('returns the same fastify instance', async () => { + expect(returnedFastifyInstance).toBe(fastifyInstance) }) - const mockedFastifyInstance = { - register: jest.fn(), - get: jest.fn(), - setNotFoundHandler: jest.fn(), - log: jest.fn(), - } as unknown as FastifyInstance - - // We're mocking a fake plugin, so don't worry about the type - const fakeFastifyPlugin = - 'Fake bazinga plugin' as unknown as FastifyPluginCallback - - // Mock the load fastify config function - ;(loadFastifyConfig as jest.Mock).mockReturnValue({ - config: {}, - configureFastify: jest.fn((fastify) => { - fastify.register(fakeFastifyPlugin) - fastify.version = 'bazinga' - return fastify - }), + it('can be configured by the user', async () => { + const res = await fastifyInstance.inject({ + method: 'GET', + url: '/test-route', + }) + + expect(res.body).toBe(JSON.stringify({ message })) }) - it('Check that configureFastify is called with the expected side and options', async () => { - await withWebServer(mockedFastifyInstance, { port: 3001 }) + // We can use `printRoutes` with a method for debugging, but not without one. + // See https://fastify.dev/docs/latest/Reference/Server#printroutes + it('builds a tree of routes for GET', async () => { + expect(fastifyInstance.printRoutes({ method: 'GET' })) + .toMatchInlineSnapshot(` + "└── / + ├── about (GET) + ├── contacts/new (GET) + ├── nested/index (GET) + ├── test-route (GET) + └── * (GET) + " + `) + }) - const { configureFastify } = loadFastifyConfig() + describe('serves prerendered files', () => { + it('serves the prerendered about page', async () => { + const url = '/about' - expect(configureFastify).toHaveBeenCalledTimes(1) + const res = await fastifyInstance.inject({ + method: 'GET', + url, + }) - // We don't care about the first argument - expect(configureFastify).toHaveBeenCalledWith(expect.anything(), { - side: 'web', - port: 3001, + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toBe('text/html; charset=UTF-8') + expect(res.body).toBe( + fs.readFileSync(path.join(getPaths().web.dist, `${url}.html`), 'utf-8') + ) + }) + + it('serves the prerendered new contact page', async () => { + const url = '/contacts/new' + + const res = await fastifyInstance.inject({ + method: 'GET', + url, + }) + + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toBe('text/html; charset=UTF-8') + expect(res.body).toBe( + fs.readFileSync(path.join(getPaths().web.dist, `${url}.html`), 'utf-8') + ) + }) + + // We don't serve files named index.js at the root level. + // This logic ensures nested files aren't affected. + it('serves the prerendered nested index page', async () => { + const url = '/nested/index' + + const res = await fastifyInstance.inject({ + method: 'GET', + url, + }) + + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toBe('text/html; charset=UTF-8') + expect(res.body).toBe( + fs.readFileSync(path.join(getPaths().web.dist, `${url}.html`), 'utf-8') + ) + }) + + it('serves prerendered files with certain headers', async () => { + await fastifyInstance.listen({ port }) + + const res = await fetch(`http://localhost:${port}/about`) + const headers = [...res.headers.keys()] + + expect(headers).toMatchInlineSnapshot(` + [ + "accept-ranges", + "cache-control", + "connection", + "content-length", + "content-type", + "date", + "etag", + "keep-alive", + "last-modified", + ] + `) + }) + + // I'm not sure if this was intentional, but we support it. + // We may want to use the `@fastify/static` plugin's `allowedPath` option. + // See https://github.com/fastify/fastify-static?tab=readme-ov-file#allowedpath. + it('serves prerendered files at `${routeName}.html`', async () => { + const url = '/about.html' + + const res = await fastifyInstance.inject({ + method: 'GET', + url, + }) + + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toBe('text/html; charset=UTF-8') + expect(res.body).toBe( + fs.readFileSync(path.join(getPaths().web.dist, url), 'utf-8') + ) }) - }) - it('Check that configureFastify will register in Fastify a plugin', async () => { - await withWebServer(mockedFastifyInstance, { port: 3001 }) - expect(mockedFastifyInstance.register).toHaveBeenCalledWith( - 'Fake bazinga plugin' - ) + it('handles not found by serving a fallback', async () => { + const res = await fastifyInstance.inject({ + method: 'GET', + url: '/absent.html', + }) + + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toBe('text/html; charset=UTF-8') + expect(res.body).toBe( + fs.readFileSync(path.join(getPaths().web.dist, '200.html'), 'utf-8') + ) + }) }) - it('Check that withWebServer returns the same Fastify instance, and not a new one', async () => { - await withWebServer(mockedFastifyInstance, { port: 3001 }) - expect(mockedFastifyInstance.version).toBe('bazinga') + describe('serves pretty much anything in web dist', () => { + it('serves the built AboutPage.js', async () => { + const relativeFilePath = '/assets/AboutPage-7ec0f8df.js' + + const res = await fastifyInstance.inject({ + method: 'GET', + url: relativeFilePath, + }) + + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toBe( + 'application/javascript; charset=UTF-8' + ) + expect(res.body).toBe( + fs.readFileSync( + path.join(getPaths().web.dist, relativeFilePath), + 'utf-8' + ) + ) + }) + + it('serves the built index.css', async () => { + const relativeFilePath = '/assets/index-613d397d.css' + + const res = await fastifyInstance.inject({ + method: 'GET', + url: relativeFilePath, + }) + + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toBe('text/css; charset=UTF-8') + expect(res.body).toBe( + fs.readFileSync( + path.join(getPaths().web.dist, relativeFilePath), + 'utf-8' + ) + ) + }) + + it('serves build-manifest.json', async () => { + const relativeFilePath = '/build-manifest.json' + + const res = await fastifyInstance.inject({ + method: 'GET', + url: relativeFilePath, + }) + + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toBe( + 'application/json; charset=UTF-8' + ) + expect(res.body).toBe( + fs.readFileSync( + path.join(getPaths().web.dist, relativeFilePath), + 'utf-8' + ) + ) + }) + + it('serves favicon.png', async () => { + const res = await fastifyInstance.inject({ + method: 'GET', + url: '/favicon.png', + }) + + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toBe('image/png') + }) + + it('serves README.md', async () => { + const relativeFilePath = '/README.md' + + const res = await fastifyInstance.inject({ + method: 'GET', + url: relativeFilePath, + }) + + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toBe('text/markdown; charset=UTF-8') + expect(res.body).toBe( + fs.readFileSync( + path.join(getPaths().web.dist, relativeFilePath), + 'utf-8' + ) + ) + }) + + it('serves robots.txt', async () => { + const relativeFilePath = '/robots.txt' + + const res = await fastifyInstance.inject({ + method: 'GET', + url: relativeFilePath, + }) + + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toBe('text/plain; charset=UTF-8') + expect(res.body).toBe( + fs.readFileSync( + path.join(getPaths().web.dist, relativeFilePath), + 'utf-8' + ) + ) + }) }) - it('When configureFastify is missing from server config, it does not throw', () => { - ;(loadFastifyConfig as jest.Mock).mockReturnValue({ - config: {}, - configureFastify: null, + describe("returns a 404 for assets that can't be found", () => { + it("returns a 404 for non-html assets that can't be found", async () => { + const res = await fastifyInstance.inject({ + method: 'GET', + url: '/kittens.png', + }) + + expect(res.statusCode).toBe(404) }) - expect( - withWebServer(mockedFastifyInstance, { port: 3001 }) - ).resolves.not.toThrowError() + it('handles "."s in routes', async () => { + const res = await fastifyInstance.inject({ + method: 'GET', + url: '/my-page?loading=spinner.blue', + }) + + expect(res.statusCode).toBe(200) + }) }) }) diff --git a/packages/api-server/src/__tests__/withWebServerFallback.test.ts b/packages/api-server/src/__tests__/withWebServerFallback.test.ts new file mode 100644 index 000000000000..d962b26bcf5b --- /dev/null +++ b/packages/api-server/src/__tests__/withWebServerFallback.test.ts @@ -0,0 +1,43 @@ +import fs from 'fs' +import path from 'path' + +import { getPaths } from '@redwoodjs/project-config' + +import { createFastifyInstance } from '../fastify' +import withWebServer from '../plugins/withWebServer' + +// Set up RWJS_CWD. +let original_RWJS_CWD + +beforeAll(() => { + original_RWJS_CWD = process.env.RWJS_CWD + process.env.RWJS_CWD = path.join(__dirname, 'fixtures/redwood-app-fallback') +}) + +afterAll(() => { + process.env.RWJS_CWD = original_RWJS_CWD +}) + +test("handles not found by serving index.html if 200.html doesn't exist", async () => { + const fastifyInstance = await withWebServer( + createFastifyInstance({ logger: false }), + { + port: 8910, + } + ) + + const url = '/index.html' + + const res = await fastifyInstance.inject({ + method: 'GET', + url, + }) + + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toBe('text/html; charset=UTF-8') + expect(res.body).toBe( + fs.readFileSync(path.join(getPaths().web.dist, url), 'utf-8') + ) + + await fastifyInstance.close() +}) diff --git a/packages/api-server/src/__tests__/withWebServerLoadFastifyConfig.test.ts b/packages/api-server/src/__tests__/withWebServerLoadFastifyConfig.test.ts new file mode 100644 index 000000000000..33ca81da8192 --- /dev/null +++ b/packages/api-server/src/__tests__/withWebServerLoadFastifyConfig.test.ts @@ -0,0 +1,92 @@ +import { vol } from 'memfs' + +import { createFastifyInstance } from '../fastify' +import withWebServer from '../plugins/withWebServer' + +// Suppress terminal logging. +console.log = jest.fn() + +// Set up RWJS_CWD. +let original_RWJS_CWD +const FIXTURE_PATH = '/redwood-app' + +beforeAll(() => { + original_RWJS_CWD = process.env.RWJS_CWD + process.env.RWJS_CWD = FIXTURE_PATH +}) + +afterAll(() => { + process.env.RWJS_CWD = original_RWJS_CWD +}) + +// Mock server.config.js. +jest.mock('fs', () => require('memfs').fs) + +jest.mock( + '/redwood-app/api/server.config.js', + () => { + return { + config: {}, + configureFastify: async (fastify, options) => { + if (options.side === 'web') { + fastify.get('/about.html', async (_request, _reply) => { + return { virtualAboutHtml: true } + }) + } + + return fastify + }, + } + }, + { virtual: true } +) + +jest.mock( + '\\redwood-app\\api\\server.config.js', + () => { + return { + config: {}, + configureFastify: async (fastify, options) => { + if (options.side === 'web') { + fastify.get('/about.html', async (_request, _reply) => { + return { virtualAboutHtml: true } + }) + } + + return fastify + }, + } + }, + { virtual: true } +) + +test("the user can overwrite static files that weren't set specifically ", async () => { + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + 'server.config.js': '', + }, + web: { + dist: { + 'about.html': '

About

', + }, + }, + }, + FIXTURE_PATH + ) + + const fastifyInstance = await withWebServer(createFastifyInstance(), { + port: 8910, + }) + + const res = await fastifyInstance.inject({ + method: 'GET', + url: '/about.html', + }) + + expect(res.statusCode).toBe(200) + expect(res.body).toBe(JSON.stringify({ virtualAboutHtml: true })) + + await fastifyInstance.close() +}) diff --git a/packages/api-server/src/__tests__/withWebServerLoadFastifyConfigError.test.ts b/packages/api-server/src/__tests__/withWebServerLoadFastifyConfigError.test.ts new file mode 100644 index 000000000000..115e927bec2c --- /dev/null +++ b/packages/api-server/src/__tests__/withWebServerLoadFastifyConfigError.test.ts @@ -0,0 +1,88 @@ +import { vol } from 'memfs' + +import { createFastifyInstance } from '../fastify' +import withWebServer from '../plugins/withWebServer' + +// Suppress terminal logging. +console.log = jest.fn() + +// Set up RWJS_CWD. +let original_RWJS_CWD +const FIXTURE_PATH = '/redwood-app' + +beforeAll(() => { + original_RWJS_CWD = process.env.RWJS_CWD + process.env.RWJS_CWD = FIXTURE_PATH +}) + +afterAll(() => { + process.env.RWJS_CWD = original_RWJS_CWD +}) + +// Mock server.config.js. +jest.mock('fs', () => require('memfs').fs) + +const aboutHTML = '

About

' + +jest.mock( + '/redwood-app/api/server.config.js', + () => { + return { + config: {}, + configureFastify: async (fastify, options) => { + if (options.side === 'web') { + fastify.get('/about', async (_request, _reply) => { + return { virtualAboutHtml: true } + }) + } + + return fastify + }, + } + }, + { virtual: true } +) + +jest.mock( + '\\redwood-app\\api\\server.config.js', + () => { + return { + config: {}, + configureFastify: async (fastify, options) => { + if (options.side === 'web') { + fastify.get('/about', async (_request, _reply) => { + return { virtualAboutHtml: true } + }) + } + + return fastify + }, + } + }, + { virtual: true } +) + +test("the user can't overwrite prerendered files", async () => { + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + 'server.config.js': '', + }, + web: { + dist: { + 'about.html': aboutHTML, + }, + }, + }, + FIXTURE_PATH + ) + + try { + await withWebServer(createFastifyInstance(), { + port: 8910, + }) + } catch (e) { + expect(e.code).toBe('FST_ERR_DUPLICATED_ROUTE') + } +}) diff --git a/packages/api-server/src/cliHandlers.ts b/packages/api-server/src/cliHandlers.ts index 0f96c68099a4..ad04725e46ee 100644 --- a/packages/api-server/src/cliHandlers.ts +++ b/packages/api-server/src/cliHandlers.ts @@ -1,13 +1,15 @@ +import path from 'path' + import c from 'ansi-colors' -import { getConfig } from '@redwoodjs/project-config' +import { getPaths, getConfig } from '@redwoodjs/project-config' import createFastifyInstance from './fastify' import withApiProxy from './plugins/withApiProxy' import withFunctions from './plugins/withFunctions' import withWebServer from './plugins/withWebServer' import { startServer as startFastifyServer } from './server' -import { BothServerArgs, WebServerArgs, ApiServerArgs } from './types' +import type { BothServerArgs, WebServerArgs, ApiServerArgs } from './types' /* * This file has defines CLI handlers used by the redwood cli, for `rw serve` @@ -33,6 +35,12 @@ export const apiCliOptions = { desc: 'Root path where your api functions are served', coerce: coerceRootPath, }, + loadEnvFiles: { + description: 'Load .env and .env.defaults files', + type: 'boolean', + // We have to default to `false` for backwards compatibility. + default: false, + }, } as const export const webCliOptions = { @@ -46,10 +54,21 @@ export const webCliOptions = { } as const export const apiServerHandler = async (options: ApiServerArgs) => { - const { port, socket, apiRootPath } = options + const { port, socket, apiRootPath, loadEnvFiles } = options const tsApiServer = Date.now() process.stdout.write(c.dim(c.italic('Starting API Server...\n'))) + if (loadEnvFiles) { + // @ts-expect-error for some reason ts can't find the types here but can find them for other packages + const { config } = await import('dotenv-defaults') + + config({ + path: path.join(getPaths().base, '.env'), + defaults: path.join(getPaths().base, '.env.defaults'), + multiline: true, + }) + } + let fastify = createFastifyInstance() // Import Server Functions. @@ -131,7 +150,7 @@ export const webServerHandler = async (options: WebServerArgs) => { } startFastifyServer({ - port: port, + port, socket, fastify, }).ready(() => { diff --git a/packages/api-server/src/fastify.ts b/packages/api-server/src/fastify.ts index 025cef1f08f1..f6d419176c89 100644 --- a/packages/api-server/src/fastify.ts +++ b/packages/api-server/src/fastify.ts @@ -4,11 +4,14 @@ import path from 'path' import type { FastifyInstance, FastifyServerOptions } from 'fastify' import Fastify from 'fastify' +import type { GlobalContext } from '@redwoodjs/context' +import { getAsyncStoreInstance } from '@redwoodjs/context/dist/store' import { getPaths, getConfig } from '@redwoodjs/project-config' -import { FastifySideConfigFn } from './types' +import type { FastifySideConfigFn } from './types' -const DEFAULT_OPTIONS = { +// Exported for testing. +export const DEFAULT_OPTIONS = { logger: { level: process.env.NODE_ENV === 'development' ? 'debug' : 'info', }, @@ -21,7 +24,7 @@ let serverConfigFile: { } = { config: DEFAULT_OPTIONS, configureFastify: async (fastify, options) => { - fastify.log.info( + fastify.log.trace( options, `In configureFastify hook for side: ${options?.side}` ) @@ -59,6 +62,11 @@ export const createFastifyInstance = ( const fastify = Fastify(options || config || DEFAULT_OPTIONS) + // Ensure that each request has a unique global context + fastify.addHook('onRequest', (_req, _reply, done) => { + getAsyncStoreInstance().run(new Map(), done) + }) + return fastify } diff --git a/packages/api-server/src/logFormatter/formatters.ts b/packages/api-server/src/logFormatter/formatters.ts new file mode 100644 index 000000000000..b0ba0716f0c7 --- /dev/null +++ b/packages/api-server/src/logFormatter/formatters.ts @@ -0,0 +1,234 @@ +import chalk from 'chalk' +import prettyBytes from 'pretty-bytes' +import prettyMs from 'pretty-ms' + +export const NEWLINE = '\n' + +export const emojiLog: Record = { + warn: '🚦', + info: '🌲', + error: '🚨', + debug: '🐛', + fatal: '💀', + trace: '🧵', +} + +export const ignoredCustomData: Array = [ + 'time', + 'pid', + 'hostname', + 'msg', + 'res', + 'req', + 'reqId', + 'responseTime', +] + +export const isObject = (object?: Record) => { + return object && Object.prototype.toString.apply(object) === '[object Object]' +} + +export const isEmptyObject = (object?: Record) => { + return object && !Object.keys(object).length +} + +export const isPinoLog = (log?: Record) => { + return log && Object.prototype.hasOwnProperty.call(log, 'level') +} + +export const isWideEmoji = (character: string) => { + return character !== '🚦' +} + +export const formatBundleSize = (bundle: string) => { + const bytes = parseInt(bundle, 10) + const size = prettyBytes(bytes).replace(/ /, '') + return chalk.gray(size) +} + +export const formatCustom = (query?: Record) => { + if (!query) { + return + } + + ignoredCustomData.forEach((key) => { + delete query[key] + }) + + if (!isEmptyObject(query)) { + return chalk.white( + NEWLINE + '🗒 Custom' + NEWLINE + JSON.stringify(query, null, 2) + ) + } + + return +} + +export const formatData = (data?: Record) => { + if (!isEmptyObject(data)) { + return chalk.white( + NEWLINE + '📦 Result Data' + NEWLINE + JSON.stringify(data, null, 2) + ) + } + + return +} + +export const formatDate = (instant: Date) => { + const date = new Date(instant) + const hours = date.getHours().toString().padStart(2, '0') + const minutes = date.getMinutes().toString().padStart(2, '0') + const seconds = date.getSeconds().toString().padStart(2, '0') + const prettyDate = hours + ':' + minutes + ':' + seconds + return chalk.gray(prettyDate) +} + +export const formatErrorProp = (errorPropValue: Record) => { + const errorType = errorPropValue['type'] || 'Error' + + delete errorPropValue['message'] + delete errorPropValue['stack'] + delete errorPropValue['type'] + + return chalk.redBright( + NEWLINE + + NEWLINE + + `🚨 ${errorType} Info` + + NEWLINE + + NEWLINE + + JSON.stringify(errorPropValue, null, 2) + + NEWLINE + ) +} + +export const formatLevel = (level: any) => { + const emoji = emojiLog[level] + const padding = isWideEmoji(emoji) ? '' : ' ' + return emoji + padding +} + +export const formatLoadTime = (elapsedTime: any) => { + const elapsed = parseInt(elapsedTime, 10) + const time = prettyMs(elapsed) + return chalk.gray(time) +} + +export const formatMessage = (logData: any) => { + const { level, message } = logData + + const msg = formatMessageName(message) + let pretty + if (level === 'error') { + pretty = chalk.red(msg) + } + if (level === 'trace') { + pretty = chalk.white(msg) + } + if (level === 'warn') { + pretty = chalk.magenta(msg) + } + if (level === 'debug') { + pretty = chalk.yellow(msg) + } + if (level === 'info' || level === 'customlevel') { + pretty = chalk.green(msg) + } + if (level === 'fatal') { + pretty = chalk.white.bgRed(msg) + } + return pretty +} + +export const formatMethod = (method: string) => { + return method && chalk.white(method) +} + +export const formatRequestId = (requestId: string) => { + return requestId && chalk.cyan(requestId) +} + +export const formatNs = (ns: string) => { + return ns && chalk.cyan(ns) +} + +export const formatName = (name: string) => { + return name && chalk.blue(name) +} + +export const formatMessageName = (message: string) => { + if (message === undefined) { + return '' + } + + if (message === 'request') { + return '<--' + } + if (message === 'response') { + return '-->' + } + return message +} + +export const formatOperationName = (operationName: string) => { + return chalk.white(NEWLINE + '🏷 ' + operationName) +} + +export const formatQuery = (query?: Record) => { + if (!isEmptyObject(query)) { + return chalk.white( + NEWLINE + '🔭 Query' + NEWLINE + JSON.stringify(query, null, 2) + ) + } + + return +} + +export const formatResponseCache = ( + responseCache?: Record +) => { + if (!isEmptyObject(responseCache)) { + return chalk.white( + NEWLINE + + '💾 Response Cache' + + NEWLINE + + JSON.stringify(responseCache, null, 2) + ) + } + + return +} + +export const formatStatusCode = (statusCode: string) => { + statusCode = statusCode || 'xxx' + return chalk.white(statusCode) +} + +export const formatStack = (stack?: string | Record) => { + return chalk.redBright( + stack + ? NEWLINE + '🥞 Error Stack' + NEWLINE + NEWLINE + stack + NEWLINE + : '' + ) +} + +export const formatTracing = (data?: Record) => { + if (!isEmptyObject(data)) { + return chalk.white( + NEWLINE + '⏰ Timing' + NEWLINE + JSON.stringify(data, null, 2) + ) + } + + return +} + +export const formatUrl = (url: string) => { + return chalk.white(url) +} + +export const formatUserAgent = (userAgent: string) => { + return chalk.grey(NEWLINE + '🕵️‍♀️ ' + userAgent) +} + +export const noEmpty = (value: any) => { + return !!value +} diff --git a/packages/api-server/src/logFormatter/index.ts b/packages/api-server/src/logFormatter/index.ts index 9c8b2c704be5..0064606c9e0a 100644 --- a/packages/api-server/src/logFormatter/index.ts +++ b/packages/api-server/src/logFormatter/index.ts @@ -1,52 +1,50 @@ -import chalk from 'chalk' import jsonParse from 'fast-json-parse' -import prettyBytes from 'pretty-bytes' -import prettyMs from 'pretty-ms' - -const newline = '\n' - -const emojiLog: any = { - warn: '🚦', - info: '🌲', - error: '🚨', - debug: '🐛', - fatal: '💀', - trace: '🧵', -} - -const isObject = (input: any) => { - return Object.prototype.toString.apply(input) === '[object Object]' -} - -const isEmptyObject = (object: any) => { - return object && !Object.keys(object).length -} - -const isPinoLog = (log: any) => { - return log && Object.prototype.hasOwnProperty.call(log, 'level') -} - -const isWideEmoji = (character: any) => { - return character !== '🚦' -} +import type { FastifyRequest, FastifyReply } from 'fastify' + +import { + NEWLINE, + isObject, + isPinoLog, + noEmpty, + formatDate, + formatLevel, + formatBundleSize, + formatCustom, + formatData, + formatErrorProp, + formatLoadTime, + formatMessage, + formatMethod, + formatName, + formatNs, + formatOperationName, + formatQuery, + formatRequestId, + formatResponseCache, + formatStack, + formatStatusCode, + formatTracing, + formatUserAgent, + formatUrl, +} from './formatters' export const LogFormatter = () => { - const parse = (inputData: any) => { + const parse = (inputData: string | Record) => { let logData if (typeof inputData === 'string') { const parsedData = jsonParse(inputData) if (!parsedData.value || parsedData.err || !isPinoLog(parsedData.value)) { - return inputData + newline + return inputData + NEWLINE } logData = parsedData.value } else if (isObject(inputData) && isPinoLog(inputData)) { logData = inputData } else { - return inputData + newline + return inputData + NEWLINE } if (!logData.level) { - return inputData + newline + return inputData + NEWLINE } if (!logData.message) { @@ -57,10 +55,10 @@ export const LogFormatter = () => { convertLogNumber(logData) } - return output(logData) + newline + return output(logData) + NEWLINE } - const convertLogNumber = (logData: any) => { + const convertLogNumber = (logData: Record) => { if (logData.level === 10) { logData.level = 'trace' } @@ -81,286 +79,139 @@ export const LogFormatter = () => { } } - const output = (logData: any) => { + const output = (logData: Record) => { const output = [] - if (!logData.level) { - logData.level = 'customlevel' - } - - if (!logData.name) { - logData.name = '' - } - - if (!logData.ns) { - logData.ns = '' - } - - output.push(formatDate(logData.time || Date.now())) + output.push(formatDate((logData.time as Date) || Date.now())) output.push(formatLevel(logData.level)) - output.push(formatNs(logData.ns)) - output.push(formatName(logData.name)) - output.push(formatRequestId(logData.requestId)) + output.push(formatNs(logData.ns as string)) + output.push(formatName(logData.name as string)) + output.push(formatRequestId(logData.requestId as string)) output.push(formatMessage(logData)) - const req = logData.req - const res = logData.res - - const statusCode = res ? res.statusCode : logData.statusCode - const responseTime = logData.responseTime || logData.elapsed - const method = req ? req.method : logData.method - const custom = logData.custom - const contentLength = logData.contentLength - const operationName = logData.operationName - const query = logData.query - const graphQLData = logData.data - const responseCache = logData.responseCache - const tracing = logData.tracing - const url = req ? req.url : logData.url - const userAgent = logData.userAgent + const req = logData.req as FastifyRequest + const res = logData.res as FastifyReply + + const { statusCode: responseStatusCode } = res || {} + const { method: requestMethod, url: requestUrl } = req || {} + + const { + level, + message, + name, + ns, + err: logDataErr, + stack: logDataStack, + statusCode: logDataStatusCode, + elapsed, + responseTime: logDataResponseTime, + method: logDataMethod, + custom, + contentLength, + operationName, + query, + data: graphQLData, + responseCache, + tracing, + url: logDataUrl, + userAgent, + ...rest + }: Record = logData + + const statusCode = responseStatusCode || logDataStatusCode + const responseTime = logDataResponseTime || elapsed + const method = requestMethod || logDataMethod + const url = requestUrl || logDataUrl + + const logDataErrStack = logDataErr && (logDataErr as Error).stack + const stack = - logData.level === 'fatal' || logData.level === 'error' - ? logData.stack || (logData.err && logData.err.stack) + level === 'fatal' || level === 'error' + ? logDataStack || (logDataErr && logDataErrStack) : null // Output err if it has more keys than 'stack' const err = - (logData.level === 'fatal' || logData.level === 'error') && - logData.err && - Object.keys(logData.err).find((key) => key !== 'stack') - ? logData.err + (level === 'fatal' || level === 'error') && + logDataErr && + Object.keys(logDataErr).find((key) => key !== 'stack') + ? logDataErr : null + if (!message) { + logData.message = '' + } + + if (!level) { + logData.level = 'customlevel' + } + + if (!name) { + logData.name = '' + } + + if (!ns) { + logData.ns = '' + } + if (method != null) { - output.push(formatMethod(method)) - output.push(formatStatusCode(statusCode)) + output.push(formatMethod(method as string)) + output.push(formatStatusCode(statusCode as string)) } if (url != null) { - output.push(formatUrl(url)) + output.push(formatUrl(url as string)) } if (contentLength != null) { - output.push(formatBundleSize(contentLength)) + output.push(formatBundleSize(contentLength as string)) } if (custom) { - output.push(formatCustom(custom)) + output.push(formatCustom(custom as Record)) } if (responseTime != null) { - output.push(formatLoadTime(responseTime)) + output.push(formatLoadTime(responseTime as string)) } if (userAgent != null) { - output.push(formatUserAgent(userAgent)) + output.push(formatUserAgent(userAgent as string)) } if (operationName != null) { - output.push(formatOperationName(operationName)) + output.push(formatOperationName(operationName as string)) } if (query != null) { - output.push(formatQuery(query)) + output.push(formatQuery(query as Record)) } if (graphQLData != null) { - output.push(formatData(graphQLData)) + output.push(formatData(graphQLData as Record)) } if (responseCache != null) { - output.push(formatResponseCache(responseCache)) + output.push(formatResponseCache(responseCache as Record)) } if (tracing != null) { - output.push(formatTracing(tracing)) + output.push(formatTracing(tracing as Record)) } if (err != null) { - output.push(formatErrorProp(err)) + output.push(formatErrorProp(err as Record)) } if (stack != null) { - output.push(formatStack(stack)) + output.push(formatStack(stack as Record)) } - return output.filter(noEmpty).join(' ') - } - - const formatBundleSize = (bundle: any) => { - const bytes = parseInt(bundle, 10) - const size = prettyBytes(bytes).replace(/ /, '') - return chalk.gray(size) - } - - const formatCustom = (query: any) => { - if (!isEmptyObject(query)) { - return chalk.white( - newline + '🗒 Custom' + newline + JSON.stringify(query, null, 2) - ) + if (rest) { + output.push(formatCustom(rest as Record)) } - return - } - - const formatData = (data: any) => { - if (!isEmptyObject(data)) { - return chalk.white( - newline + '📦 Result Data' + newline + JSON.stringify(data, null, 2) - ) - } - - return - } - - const formatDate = (instant: Date) => { - const date = new Date(instant) - const hours = date.getHours().toString().padStart(2, '0') - const minutes = date.getMinutes().toString().padStart(2, '0') - const seconds = date.getSeconds().toString().padStart(2, '0') - const prettyDate = hours + ':' + minutes + ':' + seconds - return chalk.gray(prettyDate) - } - - const formatErrorProp = (errorPropValue: any) => { - const errorType = errorPropValue['type'] || 'Error' - delete errorPropValue['message'] - delete errorPropValue['stack'] - delete errorPropValue['type'] - - return chalk.redBright( - newline + - newline + - `🚨 ${errorType} Info` + - newline + - newline + - JSON.stringify(errorPropValue, null, 2) + - newline - ) - } - - const formatLevel = (level: any) => { - const emoji = emojiLog[level] - const padding = isWideEmoji(emoji) ? '' : ' ' - return emoji + padding - } - - const formatLoadTime = (elapsedTime: any) => { - const elapsed = parseInt(elapsedTime, 10) - const time = prettyMs(elapsed) - return chalk.gray(time) - } - - const formatMessage = (logData: any) => { - const msg = formatMessageName(logData.message) - let pretty - if (logData.level === 'error') { - pretty = chalk.red(msg) - } - if (logData.level === 'trace') { - pretty = chalk.white(msg) - } - if (logData.level === 'warn') { - pretty = chalk.magenta(msg) - } - if (logData.level === 'debug') { - pretty = chalk.yellow(msg) - } - if (logData.level === 'info' || logData.level === 'customlevel') { - pretty = chalk.green(msg) - } - if (logData.level === 'fatal') { - pretty = chalk.white.bgRed(msg) - } - return pretty - } - - const formatMethod = (method: any) => { - return chalk.white(method) - } - - const formatRequestId = (requestId: any) => { - return requestId && chalk.cyan(requestId) - } - - const formatNs = (name: any) => { - return chalk.cyan(name) - } - - const formatName = (name: any) => { - return chalk.blue(name) - } - - const formatMessageName = (message: any) => { - if (message === 'request') { - return '<--' - } - if (message === 'response') { - return '-->' - } - return message - } - - const formatOperationName = (operationName: any) => { - return chalk.white(newline + '🏷 ' + operationName) - } - - const formatQuery = (query: any) => { - if (!isEmptyObject(query)) { - return chalk.white( - newline + '🔭 Query' + newline + JSON.stringify(query, null, 2) - ) - } - - return - } - - const formatResponseCache = (responseCache: any) => { - if (!isEmptyObject(responseCache)) { - return chalk.white( - newline + - '💾 Response Cache' + - newline + - JSON.stringify(responseCache, null, 2) - ) - } - - return - } - - const formatStatusCode = (statusCode: any) => { - statusCode = statusCode || 'xxx' - return chalk.white(statusCode) - } - - const formatStack = (stack: any) => { - return chalk.redBright( - stack - ? newline + '🥞 Error Stack' + newline + newline + stack + newline - : '' - ) - } - - const formatTracing = (data: any) => { - if (!isEmptyObject(data)) { - return chalk.white( - newline + '⏰ Timing' + newline + JSON.stringify(data, null, 2) - ) - } - - return - } - - const formatUrl = (url: any) => { - return chalk.white(url) - } - - const formatUserAgent = (userAgent: any) => { - return chalk.grey(newline + '🕵️‍♀️ ' + userAgent) - } - - const noEmpty = (value: any) => { - return !!value + return output.filter(noEmpty).join(' ') } return parse diff --git a/packages/api-server/src/plugins/findPrerenderedHtml.ts b/packages/api-server/src/plugins/findPrerenderedHtml.ts new file mode 100644 index 000000000000..01c1bed9af48 --- /dev/null +++ b/packages/api-server/src/plugins/findPrerenderedHtml.ts @@ -0,0 +1,9 @@ +import fg from 'fast-glob' + +import { getPaths } from '@redwoodjs/project-config' + +// NOTE: This function was copied from @redwoodjs/internal/dist/files to avoid depending on @redwoodjs/internal. +// import { findPrerenderedHtml } from '@redwoodjs/internal/dist/files' +export function findPrerenderedHtml(cwd = getPaths().web.dist) { + return fg.sync('**/*.html', { cwd, ignore: ['200.html', '404.html'] }) +} diff --git a/packages/api-server/src/plugins/lambdaLoader.ts b/packages/api-server/src/plugins/lambdaLoader.ts index b5693a89cb2d..c345150c36b4 100644 --- a/packages/api-server/src/plugins/lambdaLoader.ts +++ b/packages/api-server/src/plugins/lambdaLoader.ts @@ -2,10 +2,15 @@ import path from 'path' import c from 'ansi-colors' import type { Handler } from 'aws-lambda' -import { FastifyReply, FastifyRequest, RequestGenericInterface } from 'fastify' -import escape from 'lodash.escape' +import fg from 'fast-glob' +import type { + FastifyReply, + FastifyRequest, + RequestGenericInterface, +} from 'fastify' +import { escape } from 'lodash' -import { findApiDistFunctions } from '@redwoodjs/internal/dist/files' +import { getPaths } from '@redwoodjs/project-config' import { requestHandler } from '../requestHandlers/awsLambdaFastify' @@ -61,6 +66,16 @@ export const loadFunctionsFromDist = async () => { await setLambdaFunctions(serverFunctions) } +// NOTE: Copied from @redwoodjs/internal/dist/files to avoid depending on @redwoodjs/internal. +// import { findApiDistFunctions } from '@redwoodjs/internal/dist/files' +function findApiDistFunctions(cwd: string = getPaths().api.base) { + return fg.sync('dist/functions/**/*.{ts,js}', { + cwd, + deep: 2, // We don't support deeply nested api functions, to maximise compatibility with deployment providers + absolute: true, + }) +} + interface LambdaHandlerRequest extends RequestGenericInterface { Params: { routeName: string diff --git a/packages/api-server/src/plugins/withApiProxy.ts b/packages/api-server/src/plugins/withApiProxy.ts index 5b428f316fac..9d194816dd4a 100644 --- a/packages/api-server/src/plugins/withApiProxy.ts +++ b/packages/api-server/src/plugins/withApiProxy.ts @@ -1,5 +1,6 @@ -import httpProxy, { FastifyHttpProxyOptions } from '@fastify/http-proxy' -import { FastifyInstance } from 'fastify' +import type { FastifyHttpProxyOptions } from '@fastify/http-proxy' +import httpProxy from '@fastify/http-proxy' +import type { FastifyInstance } from 'fastify' export interface ApiProxyOptions { apiUrl: string diff --git a/packages/api-server/src/plugins/withFunctions.ts b/packages/api-server/src/plugins/withFunctions.ts index d59927c71303..d985fcbb0697 100644 --- a/packages/api-server/src/plugins/withFunctions.ts +++ b/packages/api-server/src/plugins/withFunctions.ts @@ -1,5 +1,5 @@ import fastifyUrlData from '@fastify/url-data' -import { FastifyInstance } from 'fastify' +import type { FastifyInstance } from 'fastify' import fastifyRawBody from 'fastify-raw-body' import { loadFastifyConfig } from '../fastify' @@ -9,7 +9,7 @@ import { lambdaRequestHandler, loadFunctionsFromDist } from './lambdaLoader' const withFunctions = async ( fastify: FastifyInstance, - options: ApiServerArgs + options: Omit ) => { const { apiRootPath } = options // Add extra fastify plugins diff --git a/packages/api-server/src/plugins/withWebServer.ts b/packages/api-server/src/plugins/withWebServer.ts index 8ab51c2439d1..98c5b9d8ce6c 100644 --- a/packages/api-server/src/plugins/withWebServer.ts +++ b/packages/api-server/src/plugins/withWebServer.ts @@ -2,13 +2,15 @@ import fs from 'fs' import path from 'path' import fastifyStatic from '@fastify/static' -import { FastifyInstance, FastifyReply } from 'fastify' +import fastifyUrlData from '@fastify/url-data' +import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify' -import { findPrerenderedHtml } from '@redwoodjs/internal/dist/files' import { getPaths } from '@redwoodjs/project-config' import { loadFastifyConfig } from '../fastify' -import { WebServerArgs } from '../types' +import type { WebServerArgs } from '../types' + +import { findPrerenderedHtml } from './findPrerenderedHtml' export const getFallbackIndexPath = () => { const prerenderIndexPath = path.join(getPaths().web.dist, '/200.html') @@ -26,6 +28,8 @@ const withWebServer = async ( fastify: FastifyInstance, options: WebServerArgs ) => { + fastify.register(fastifyUrlData) + const prerenderedFiles = findPrerenderedHtml() const indexPath = getFallbackIndexPath() @@ -53,10 +57,23 @@ const withWebServer = async ( // For SPA routing fallback on unmatched routes // And let JS routing take over - fastify.setNotFoundHandler({}, function (_, reply: FastifyReply) { - reply.header('Content-Type', 'text/html; charset=UTF-8') - reply.sendFile(indexPath) - }) + fastify.setNotFoundHandler( + {}, + function (req: FastifyRequest, reply: FastifyReply) { + const urlData = req.urlData() + const requestedExtension = path.extname(urlData.path ?? '') + + // If it's requesting some sort of asset, e.g. .js or .jpg files + // Html files should fallback to the index.html + if (requestedExtension !== '' && requestedExtension !== '.html') { + reply.code(404) + return reply.send('Not Found') + } + + reply.header('Content-Type', 'text/html; charset=UTF-8') + return reply.sendFile(indexPath) + } + ) return fastify } diff --git a/packages/api-server/src/server.ts b/packages/api-server/src/server.ts index d8f66d896c41..eda581d3d3fb 100644 --- a/packages/api-server/src/server.ts +++ b/packages/api-server/src/server.ts @@ -1,4 +1,4 @@ -import { FastifyInstance } from 'fastify' +import type { FastifyInstance } from 'fastify' export interface HttpServerParams { port: number @@ -17,11 +17,11 @@ export const startServer = ({ fastify.listen({ port: serverPort, host }) fastify.ready(() => { - fastify.log.debug( + fastify.log.trace( { custom: { ...fastify.initialConfig } }, 'Fastify server configuration' ) - fastify.log.debug(`Registered plugins \n${fastify.printPlugins()}`) + fastify.log.trace(`Registered plugins \n${fastify.printPlugins()}`) }) return fastify diff --git a/packages/api-server/src/types.ts b/packages/api-server/src/types.ts index af56b50b4cff..fc049055a904 100644 --- a/packages/api-server/src/types.ts +++ b/packages/api-server/src/types.ts @@ -1,6 +1,6 @@ -import { FastifyInstance } from 'fastify' +import type { FastifyInstance } from 'fastify' -import { HttpServerParams } from './server' +import type { HttpServerParams } from './server' export interface WebServerArgs extends Omit { apiHost?: string @@ -8,6 +8,7 @@ export interface WebServerArgs extends Omit { export interface ApiServerArgs extends Omit { apiRootPath: string // either user supplied or '/' + loadEnvFiles: boolean } export type BothServerArgs = Omit diff --git a/packages/api-server/src/watch.ts b/packages/api-server/src/watch.ts index 0be01404f60c..33ced351c4a2 100644 --- a/packages/api-server/src/watch.ts +++ b/packages/api-server/src/watch.ts @@ -2,9 +2,11 @@ import { fork } from 'child_process' import type { ChildProcess } from 'child_process' +import fs from 'fs' import path from 'path' import c from 'ansi-colors' +import chalk from 'chalk' import chokidar from 'chokidar' import dotenv from 'dotenv' import { debounce } from 'lodash' @@ -13,7 +15,12 @@ import yargs from 'yargs/yargs' import { buildApi } from '@redwoodjs/internal/dist/build/api' import { loadAndValidateSdls } from '@redwoodjs/internal/dist/validateSchema' -import { getPaths, ensurePosixPath, getConfig } from '@redwoodjs/project-config' +import { + getPaths, + ensurePosixPath, + getConfig, + resolveFile, +} from '@redwoodjs/project-config' const argv = yargs(hideBin(process.argv)) .option('debug-port', { @@ -74,6 +81,27 @@ const rebuildApiServer = async () => { const forkOpts = { execArgv: process.execArgv, } + + // OpenTelemetry SDK Setup + if (getConfig().experimental.opentelemetry.enabled) { + const opentelemetrySDKScriptPath = + getConfig().experimental.opentelemetry.apiSdk + if (opentelemetrySDKScriptPath) { + console.log( + `Setting up OpenTelemetry using the setup file: ${opentelemetrySDKScriptPath}` + ) + if (fs.existsSync(opentelemetrySDKScriptPath)) { + forkOpts.execArgv = forkOpts.execArgv.concat([ + `--require=${opentelemetrySDKScriptPath}`, + ]) + } else { + console.error( + `OpenTelemetry setup file does not exist at ${opentelemetrySDKScriptPath}` + ) + } + } + } + const debugPort = argv['debug-port'] if (debugPort) { forkOpts.execArgv = forkOpts.execArgv.concat([`--inspect=${debugPort}`]) @@ -82,11 +110,30 @@ const rebuildApiServer = async () => { const port = argv.port ?? getConfig().api.port // Start API server - httpServerProcess = fork( - path.join(__dirname, 'index.js'), - ['api', '--port', port.toString()], - forkOpts - ) + + // Check if experimental server file exists + const serverFile = resolveFile(`${rwjsPaths.api.dist}/server`) + if (serverFile) { + const separator = chalk.hex('#ff845e')( + '------------------------------------------------------------------' + ) + console.log( + [ + separator, + `🧪 ${chalk.green('Experimental Feature')} 🧪`, + separator, + 'Using the experimental API server file at api/dist/server.js', + separator, + ].join('\n') + ) + httpServerProcess = fork(serverFile, [], forkOpts) + } else { + httpServerProcess = fork( + path.join(__dirname, 'index.js'), + ['api', '--port', port.toString()], + forkOpts + ) + } } catch (e) { console.error(e) } @@ -140,6 +187,13 @@ chokidar await validate() }) .on('all', async (eventName, filePath) => { + // On sufficiently large projects (500+ files, or >= 2000 ms build times) on older machines, esbuild writing to the api directory + // makes chokidar emit an `addDir` event. This starts an infinite loop where the api starts building itself as soon as it's finished. + // This could probably be fixed with some sort of build caching. + if (eventName === 'addDir' && filePath === rwjsPaths.api.base) { + return + } + // We validate here, so that developers will see the error // As they're running the dev server if (filePath.includes('.sdl')) { diff --git a/packages/api-server/tsconfig.json b/packages/api-server/tsconfig.json index ca58c848ae21..06fe65a2387a 100644 --- a/packages/api-server/tsconfig.json +++ b/packages/api-server/tsconfig.json @@ -3,7 +3,6 @@ "compilerOptions": { "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist", }, "include": ["src", "ambient.d.ts"], diff --git a/packages/api/package.json b/packages/api/package.json index 376f0a7d1aa3..7572e80032d3 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/api", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -23,42 +23,42 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\"", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\"", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@prisma/client": "4.12.0", - "@whatwg-node/fetch": "0.8.4", - "core-js": "3.29.1", + "@babel/runtime-corejs3": "7.23.6", + "@prisma/client": "5.7.0", + "@whatwg-node/fetch": "0.9.14", + "core-js": "3.34.0", "humanize-string": "2.1.0", - "jsonwebtoken": "9.0.0", + "jsonwebtoken": "9.0.2", "pascalcase": "1.0.0", - "pino": "8.11.0", + "pino": "8.16.2", "title-case": "3.0.3" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@types/aws-lambda": "8.10.114", - "@types/jsonwebtoken": "9.0.1", + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@types/aws-lambda": "8.10.126", + "@types/jsonwebtoken": "9.0.5", "@types/memjs": "1", - "@types/pascalcase": "1.0.1", - "@types/split2": "3.2.1", - "jest": "29.5.0", + "@types/pascalcase": "1.0.3", + "@types/split2": "4.2.3", + "jest": "29.7.0", "memjs": "1.3.1", - "redis": "4.6.5", + "redis": "4.6.7", "split2": "4.2.0", "ts-toolbelt": "9.6.0", - "typescript": "5.0.3" + "typescript": "5.3.3" }, "peerDependencies": { "memjs": "1.3.1", - "redis": "4.6.5" + "redis": "4.6.7" }, "peerDependenciesMeta": { "memjs": { diff --git a/packages/api/src/auth/__tests__/getAuthenticationContext.test.ts b/packages/api/src/auth/__tests__/getAuthenticationContext.test.ts index 3d6253168d30..727c2460415e 100644 --- a/packages/api/src/auth/__tests__/getAuthenticationContext.test.ts +++ b/packages/api/src/auth/__tests__/getAuthenticationContext.test.ts @@ -1,4 +1,4 @@ -import { APIGatewayProxyEvent, Context } from 'aws-lambda' +import type { APIGatewayProxyEvent, Context } from 'aws-lambda' import { getAuthenticationContext } from '../index' diff --git a/packages/api/src/auth/index.ts b/packages/api/src/auth/index.ts index 328817547eb5..ecb189696aa7 100644 --- a/packages/api/src/auth/index.ts +++ b/packages/api/src/auth/index.ts @@ -9,7 +9,13 @@ export type { Decoded } const AUTH_PROVIDER_HEADER = 'auth-provider' export const getAuthProviderHeader = (event: APIGatewayProxyEvent) => { - return event?.headers[AUTH_PROVIDER_HEADER] + const authProviderKey = Object.keys(event?.headers ?? {}).find( + (key) => key.toLowerCase() === AUTH_PROVIDER_HEADER + ) + if (authProviderKey) { + return event?.headers[authProviderKey] + } + return undefined } export interface AuthorizationHeader { diff --git a/packages/api/src/auth/verifiers/common.ts b/packages/api/src/auth/verifiers/common.ts index bc8ecd0bbff4..f2bfc42237e7 100644 --- a/packages/api/src/auth/verifiers/common.ts +++ b/packages/api/src/auth/verifiers/common.ts @@ -39,7 +39,7 @@ export type SupportedVerifiers = export type SupportedVerifierTypes = keyof typeof verifierLookup -export const DEFAULT_WEBHOOK_SECRET = process.env['WEBHOOK_SECRET'] ?? '' +export const DEFAULT_WEBHOOK_SECRET = process.env.WEBHOOK_SECRET ?? '' export const VERIFICATION_ERROR_MESSAGE = "You don't have access to invoke this function." diff --git a/packages/api/src/cache/__tests__/shared.test.ts b/packages/api/src/cache/__tests__/shared.test.ts index 7befe1a0bbbc..302d8e5d5e37 100644 --- a/packages/api/src/cache/__tests__/shared.test.ts +++ b/packages/api/src/cache/__tests__/shared.test.ts @@ -26,7 +26,7 @@ describe('formatCacheKey', () => { expect(formatCacheKey(['bar', 'baz'], 'foo')).toEqual('foo-bar-baz') }) - it('does not appent the prefix more than once', () => { + it('does not append the prefix more than once', () => { expect(formatCacheKey('foo-bar', 'foo')).toEqual('foo-bar') expect(formatCacheKey(['foo', 'bar'], 'foo')).toEqual('foo-bar') // needs a - to match against the prefix diff --git a/packages/api/src/cache/index.ts b/packages/api/src/cache/index.ts index 991d53bdd206..bc32e37cfe83 100644 --- a/packages/api/src/cache/index.ts +++ b/packages/api/src/cache/index.ts @@ -1,6 +1,6 @@ import type { Logger } from '../logger' -import BaseClient from './clients/BaseClient' +import type BaseClient from './clients/BaseClient' import { CacheTimeoutError } from './errors' export { default as MemcachedClient } from './clients/MemcachedClient' diff --git a/packages/api/src/logger/index.ts b/packages/api/src/logger/index.ts index 6672b398304f..421f20b18841 100644 --- a/packages/api/src/logger/index.ts +++ b/packages/api/src/logger/index.ts @@ -159,10 +159,10 @@ export const logLevel: LevelWithSilent | string = (() => { * * @see {@link https://github.com/pinojs/pino/blob/master/docs/api.md} */ -export const defaultLoggerOptions: LoggerOptions = { +export const defaultLoggerOptions = { level: logLevel, redact: redactionsList, -} +} satisfies LoggerOptions /** * RedwoodLoggerOptions defines custom logger options that extend those available in LoggerOptions diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts index e2adcc015380..3217855f3cfe 100644 --- a/packages/api/src/types.ts +++ b/packages/api/src/types.ts @@ -2,7 +2,7 @@ * Houses utility types commonly used on the api side */ -import { O, A } from 'ts-toolbelt' +import type { O, A } from 'ts-toolbelt' /** * ---- Prisma SDL Type Merge ---- diff --git a/packages/api/src/webhooks/index.ts b/packages/api/src/webhooks/index.ts index b3de5c9ca363..341795828245 100644 --- a/packages/api/src/webhooks/index.ts +++ b/packages/api/src/webhooks/index.ts @@ -1,11 +1,10 @@ import type { APIGatewayProxyEvent } from 'aws-lambda' +import type { VerifyOptions, SupportedVerifierTypes } from '../auth/verifiers' import { createVerifier, - VerifyOptions, WebhookVerificationError, DEFAULT_WEBHOOK_SECRET, - SupportedVerifierTypes, DEFAULT_TOLERANCE, } from '../auth/verifiers' @@ -33,7 +32,7 @@ const eventBody = (event: APIGatewayProxyEvent) => { /** * Extracts signature from Lambda Event. * - * @param {APIGatewayProxyEvent} event - The event that incudes the request details, like headers + * @param {APIGatewayProxyEvent} event - The event that includes the request details, like headers * @param {string} signatureHeader - The name of header key that contains the signature; defaults to DEFAULT_WEBHOOK_SIGNATURE_HEADER * @return {string} - The signature found in the headers specified by signatureHeader * diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 1b82fffaba16..e9440671d9f5 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -3,7 +3,6 @@ "compilerOptions": { "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist", }, "include": ["src/**/*"] diff --git a/packages/auth-providers/auth0/api/README.md b/packages/auth-providers/auth0/api/README.md index d27ea3626dac..10ba2ea0959a 100644 --- a/packages/auth-providers/auth0/api/README.md +++ b/packages/auth-providers/auth0/api/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/auth0/api/package.json b/packages/auth-providers/auth0/api/package.json index 8f14cf480659..3977fc985cbc 100644 --- a/packages/auth-providers/auth0/api/package.json +++ b/packages/auth-providers/auth0/api/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-auth0-api", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,26 +14,26 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "core-js": "3.29.1", - "jsonwebtoken": "9.0.0", - "jwks-rsa": "3.0.1" + "@babel/runtime-corejs3": "7.23.6", + "core-js": "3.34.0", + "jsonwebtoken": "9.0.2", + "jwks-rsa": "3.1.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@redwoodjs/api": "4.0.0", - "@types/jsonwebtoken": "9.0.1", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@redwoodjs/api": "6.0.7", + "@types/jsonwebtoken": "9.0.5", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/auth0/api/src/decoder.ts b/packages/auth-providers/auth0/api/src/decoder.ts index 0045dd98f340..1ffb391713e5 100644 --- a/packages/auth-providers/auth0/api/src/decoder.ts +++ b/packages/auth-providers/auth0/api/src/decoder.ts @@ -1,7 +1,7 @@ import jwt from 'jsonwebtoken' import jwksClient from 'jwks-rsa' -import { Decoder } from '@redwoodjs/api' +import type { Decoder } from '@redwoodjs/api' /** * This takes an auth0 jwt and verifies it. It returns something like this: @@ -17,8 +17,8 @@ import { Decoder } from '@redwoodjs/api' * } * ``` * - * You can use `sub` as a stable reference to your user, but if you want the email - * addres you can set a context object[^0] in rules[^1]: + * You can use `sub` as a stable reference to your user, but if you want the email + * address you can set a context object[^0] in rules[^1]: * * ^0: https://auth0.com/docs/rules/references/context-object * ^1: https://manage.auth0.com/#/rules/new diff --git a/packages/auth-providers/auth0/api/tsconfig.json b/packages/auth-providers/auth0/api/tsconfig.json index bb9ec4a21fd1..992f3e8dfddf 100644 --- a/packages/auth-providers/auth0/api/tsconfig.json +++ b/packages/auth-providers/auth0/api/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/auth0/setup/README.md b/packages/auth-providers/auth0/setup/README.md index 661208df586e..cba1dea094ac 100644 --- a/packages/auth-providers/auth0/setup/README.md +++ b/packages/auth-providers/auth0/setup/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/auth0/setup/package.json b/packages/auth-providers/auth0/setup/package.json index 8fe01c2f6cfd..3cb539fed5b7 100644 --- a/packages/auth-providers/auth0/setup/package.json +++ b/packages/auth-providers/auth0/setup/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-auth0-setup", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,24 +14,24 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@redwoodjs/cli-helpers": "4.0.0", - "core-js": "3.29.1" + "@babel/runtime-corejs3": "7.23.6", + "@redwoodjs/cli-helpers": "6.0.7", + "core-js": "3.34.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@types/yargs": "17.0.24", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@types/yargs": "17.0.32", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/auth0/setup/src/setup.ts b/packages/auth-providers/auth0/setup/src/setup.ts index 6b16fea81063..3a7264e036ae 100644 --- a/packages/auth-providers/auth0/setup/src/setup.ts +++ b/packages/auth-providers/auth0/setup/src/setup.ts @@ -1,4 +1,4 @@ -import yargs from 'yargs' +import type * as yargs from 'yargs' import { standardAuthBuilder } from '@redwoodjs/cli-helpers' diff --git a/packages/auth-providers/auth0/setup/src/setupHandler.ts b/packages/auth-providers/auth0/setup/src/setupHandler.ts index f045008d7edb..ff8323e2f463 100644 --- a/packages/auth-providers/auth0/setup/src/setupHandler.ts +++ b/packages/auth-providers/auth0/setup/src/setupHandler.ts @@ -3,7 +3,7 @@ import path from 'path' import { standardAuthHandler } from '@redwoodjs/cli-helpers' -import { Args } from './setup' +import type { Args } from './setup' const { version } = JSON.parse( fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8') @@ -36,9 +36,9 @@ export async function handler({ force: forceArg }: Args) { '', '```toml title="redwood.toml"', 'includeEnvironmentVariables = [', - ' "AUTH0_DOMAIN"', - ' "AUTH0_CLIENT_ID"', - ' "AUTH0_REDIRECT_URI"', + ' "AUTH0_DOMAIN",', + ' "AUTH0_CLIENT_ID",', + ' "AUTH0_REDIRECT_URI",', ' "AUTH0_AUDIENCE"', ']', '```', diff --git a/packages/auth-providers/auth0/setup/tsconfig.json b/packages/auth-providers/auth0/setup/tsconfig.json index abb54b70112a..5be9707b2f05 100644 --- a/packages/auth-providers/auth0/setup/tsconfig.json +++ b/packages/auth-providers/auth0/setup/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/auth0/web/README.md b/packages/auth-providers/auth0/web/README.md index 661208df586e..cba1dea094ac 100644 --- a/packages/auth-providers/auth0/web/README.md +++ b/packages/auth-providers/auth0/web/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/auth0/web/package.json b/packages/auth-providers/auth0/web/package.json index 024fa4475266..b4a82997b3a5 100644 --- a/packages/auth-providers/auth0/web/package.json +++ b/packages/auth-providers/auth0/web/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-auth0-web", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,29 +14,29 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@redwoodjs/auth": "4.0.0", - "core-js": "3.29.1" + "@babel/runtime-corejs3": "7.23.6", + "@redwoodjs/auth": "6.0.7", + "core-js": "3.34.0" }, "devDependencies": { - "@auth0/auth0-spa-js": "2.0.4", - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@types/react": "18.0.31", - "jest": "29.5.0", - "react": "18.2.0", - "typescript": "5.0.3" + "@auth0/auth0-spa-js": "2.1.2", + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@types/react": "18.2.37", + "jest": "29.7.0", + "react": "0.0.0-experimental-e5205658f-20230913", + "typescript": "5.3.3" }, "peerDependencies": { - "@auth0/auth0-spa-js": "2.0.4" + "@auth0/auth0-spa-js": "2.1.2" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/auth0/web/src/__tests__/auth0.test.tsx b/packages/auth-providers/auth0/web/src/__tests__/auth0.test.tsx index 845f872f24a1..d939ea483e70 100644 --- a/packages/auth-providers/auth0/web/src/__tests__/auth0.test.tsx +++ b/packages/auth-providers/auth0/web/src/__tests__/auth0.test.tsx @@ -6,7 +6,7 @@ import type { } from '@auth0/auth0-spa-js' import { renderHook, act } from '@testing-library/react' -import { CurrentUser } from '@redwoodjs/auth' +import type { CurrentUser } from '@redwoodjs/auth' import { createAuth } from '../auth0' diff --git a/packages/auth-providers/auth0/web/src/auth0.ts b/packages/auth-providers/auth0/web/src/auth0.ts index 298bb9bdd7e5..54cb1a5fe787 100644 --- a/packages/auth-providers/auth0/web/src/auth0.ts +++ b/packages/auth-providers/auth0/web/src/auth0.ts @@ -4,7 +4,8 @@ import type { RedirectLoginOptions, } from '@auth0/auth0-spa-js' -import { CurrentUser, createAuthentication } from '@redwoodjs/auth' +import type { CurrentUser } from '@redwoodjs/auth' +import { createAuthentication } from '@redwoodjs/auth' // TODO: Map out this user properly. export interface Auth0User {} diff --git a/packages/auth-providers/auth0/web/tsconfig.json b/packages/auth-providers/auth0/web/tsconfig.json index a925964c5e44..bf6fbe8951bd 100644 --- a/packages/auth-providers/auth0/web/tsconfig.json +++ b/packages/auth-providers/auth0/web/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/azureActiveDirectory/api/README.md b/packages/auth-providers/azureActiveDirectory/api/README.md index d27ea3626dac..10ba2ea0959a 100644 --- a/packages/auth-providers/azureActiveDirectory/api/README.md +++ b/packages/auth-providers/azureActiveDirectory/api/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/azureActiveDirectory/api/package.json b/packages/auth-providers/azureActiveDirectory/api/package.json index d4d45afb1bf5..7a39ab9505ab 100644 --- a/packages/auth-providers/azureActiveDirectory/api/package.json +++ b/packages/auth-providers/azureActiveDirectory/api/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-azure-active-directory-api", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,27 +14,27 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "core-js": "3.29.1", - "jsonwebtoken": "9.0.0", - "jwks-rsa": "3.0.1" + "@babel/runtime-corejs3": "7.23.6", + "core-js": "3.34.0", + "jsonwebtoken": "9.0.2", + "jwks-rsa": "3.1.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@redwoodjs/api": "4.0.0", - "@types/aws-lambda": "8.10.114", - "@types/jsonwebtoken": "9.0.1", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@redwoodjs/api": "6.0.7", + "@types/aws-lambda": "8.10.126", + "@types/jsonwebtoken": "9.0.5", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/azureActiveDirectory/api/src/decoder.ts b/packages/auth-providers/azureActiveDirectory/api/src/decoder.ts index 9cc07ee70928..447233db4611 100644 --- a/packages/auth-providers/azureActiveDirectory/api/src/decoder.ts +++ b/packages/auth-providers/azureActiveDirectory/api/src/decoder.ts @@ -1,7 +1,7 @@ import jwt from 'jsonwebtoken' import jwksClient from 'jwks-rsa' -import { Decoder } from '@redwoodjs/api' +import type { Decoder } from '@redwoodjs/api' export const authDecoder: Decoder = async (token: string, type: string) => { if (type !== 'azureActiveDirectory') { diff --git a/packages/auth-providers/azureActiveDirectory/api/tsconfig.json b/packages/auth-providers/azureActiveDirectory/api/tsconfig.json index bb9ec4a21fd1..992f3e8dfddf 100644 --- a/packages/auth-providers/azureActiveDirectory/api/tsconfig.json +++ b/packages/auth-providers/azureActiveDirectory/api/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/azureActiveDirectory/setup/README.md b/packages/auth-providers/azureActiveDirectory/setup/README.md index 661208df586e..cba1dea094ac 100644 --- a/packages/auth-providers/azureActiveDirectory/setup/README.md +++ b/packages/auth-providers/azureActiveDirectory/setup/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/azureActiveDirectory/setup/package.json b/packages/auth-providers/azureActiveDirectory/setup/package.json index d88e57cfd022..ea219efb9cc5 100644 --- a/packages/auth-providers/azureActiveDirectory/setup/package.json +++ b/packages/auth-providers/azureActiveDirectory/setup/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-azure-active-directory-setup", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,24 +14,24 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@redwoodjs/cli-helpers": "4.0.0", - "core-js": "3.29.1" + "@babel/runtime-corejs3": "7.23.6", + "@redwoodjs/cli-helpers": "6.0.7", + "core-js": "3.34.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@types/yargs": "17.0.24", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@types/yargs": "17.0.32", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/azureActiveDirectory/setup/src/setup.ts b/packages/auth-providers/azureActiveDirectory/setup/src/setup.ts index b19891ae7da0..486ddde4704b 100644 --- a/packages/auth-providers/azureActiveDirectory/setup/src/setup.ts +++ b/packages/auth-providers/azureActiveDirectory/setup/src/setup.ts @@ -1,4 +1,4 @@ -import yargs from 'yargs' +import type yargs from 'yargs' import { standardAuthBuilder } from '@redwoodjs/cli-helpers' @@ -14,6 +14,6 @@ export interface Args { } export async function handler(options: Args) { - const { handler } = await import('./setupHandler') + const { handler } = await import('./setupHandler.js') return handler(options) } diff --git a/packages/auth-providers/azureActiveDirectory/setup/src/setupHandler.ts b/packages/auth-providers/azureActiveDirectory/setup/src/setupHandler.ts index c8bdd8bb94bf..1cb20c87a605 100644 --- a/packages/auth-providers/azureActiveDirectory/setup/src/setupHandler.ts +++ b/packages/auth-providers/azureActiveDirectory/setup/src/setupHandler.ts @@ -3,7 +3,7 @@ import path from 'path' import { standardAuthHandler } from '@redwoodjs/cli-helpers' -import { Args } from './setup' +import type { Args } from './setup' const { version } = JSON.parse( fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8') @@ -37,9 +37,9 @@ export async function handler({ force: forceArg }: Args) { '', '```toml title="redwood.toml"', 'includeEnvironmentVariables = [', - ' "AZURE_ACTIVE_DIRECTORY_CLIENT_ID"', - ' "AZURE_ACTIVE_DIRECTORY_AUTHORITY"', - ' "AZURE_ACTIVE_DIRECTORY_REDIRECT_URI"', + ' "AZURE_ACTIVE_DIRECTORY_CLIENT_ID",', + ' "AZURE_ACTIVE_DIRECTORY_AUTHORITY",', + ' "AZURE_ACTIVE_DIRECTORY_REDIRECT_URI",', ' "AZURE_ACTIVE_DIRECTORY_LOGOUT_REDIRECT_URI"', ']', '```', diff --git a/packages/auth-providers/azureActiveDirectory/setup/tsconfig.json b/packages/auth-providers/azureActiveDirectory/setup/tsconfig.json index abb54b70112a..5be9707b2f05 100644 --- a/packages/auth-providers/azureActiveDirectory/setup/tsconfig.json +++ b/packages/auth-providers/azureActiveDirectory/setup/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/azureActiveDirectory/web/README.md b/packages/auth-providers/azureActiveDirectory/web/README.md index 661208df586e..cba1dea094ac 100644 --- a/packages/auth-providers/azureActiveDirectory/web/README.md +++ b/packages/auth-providers/azureActiveDirectory/web/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/azureActiveDirectory/web/package.json b/packages/auth-providers/azureActiveDirectory/web/package.json index b54b135d218b..aa266eede2e7 100644 --- a/packages/auth-providers/azureActiveDirectory/web/package.json +++ b/packages/auth-providers/azureActiveDirectory/web/package.json @@ -1,10 +1,10 @@ { "name": "@redwoodjs/auth-azure-active-directory-web", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", - "directory": "packages/auth-providers/authActiveDirectory/web" + "directory": "packages/auth-providers/azureActiveDirectory/web" }, "license": "MIT", "main": "./dist/index.js", @@ -14,30 +14,30 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@redwoodjs/auth": "4.0.0", - "core-js": "3.29.1" + "@babel/runtime-corejs3": "7.23.6", + "@redwoodjs/auth": "6.0.7", + "core-js": "3.34.0" }, "devDependencies": { - "@azure/msal-browser": "2.34.0", - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@types/netlify-identity-widget": "1.9.3", - "@types/react": "18.0.31", - "jest": "29.5.0", - "react": "18.2.0", - "typescript": "5.0.3" + "@azure/msal-browser": "2.38.3", + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@types/netlify-identity-widget": "1.9.6", + "@types/react": "18.2.37", + "jest": "29.7.0", + "react": "0.0.0-experimental-e5205658f-20230913", + "typescript": "5.3.3" }, "peerDependencies": { - "@azure/msal-browser": "2.34.0" + "@azure/msal-browser": "2.38.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/azureActiveDirectory/web/src/__tests__/azureActiveDirectory.test.tsx b/packages/auth-providers/azureActiveDirectory/web/src/__tests__/azureActiveDirectory.test.tsx index 9e022aae5015..f34c4103bcd1 100644 --- a/packages/auth-providers/azureActiveDirectory/web/src/__tests__/azureActiveDirectory.test.tsx +++ b/packages/auth-providers/azureActiveDirectory/web/src/__tests__/azureActiveDirectory.test.tsx @@ -5,7 +5,7 @@ import type { } from '@azure/msal-browser' import { renderHook, act } from '@testing-library/react' -import { CurrentUser } from '@redwoodjs/auth' +import type { CurrentUser } from '@redwoodjs/auth' import { createAuth } from '../azureActiveDirectory' diff --git a/packages/auth-providers/azureActiveDirectory/web/src/azureActiveDirectory.ts b/packages/auth-providers/azureActiveDirectory/web/src/azureActiveDirectory.ts index 966b7f232b88..bbf37be373aa 100644 --- a/packages/auth-providers/azureActiveDirectory/web/src/azureActiveDirectory.ts +++ b/packages/auth-providers/azureActiveDirectory/web/src/azureActiveDirectory.ts @@ -1,12 +1,13 @@ -import { +import type { EndSessionRequest, PublicClientApplication as AzureActiveDirectoryClient, RedirectRequest, SilentRequest, - InteractionRequiredAuthError, } from '@azure/msal-browser' +import { InteractionRequiredAuthError } from '@azure/msal-browser' -import { CurrentUser, createAuthentication } from '@redwoodjs/auth' +import type { CurrentUser } from '@redwoodjs/auth' +import { createAuthentication } from '@redwoodjs/auth' export function createAuth( azureActiveDirectoryClient: AzureActiveDirectoryClient, diff --git a/packages/auth-providers/azureActiveDirectory/web/tsconfig.json b/packages/auth-providers/azureActiveDirectory/web/tsconfig.json index a925964c5e44..bf6fbe8951bd 100644 --- a/packages/auth-providers/azureActiveDirectory/web/tsconfig.json +++ b/packages/auth-providers/azureActiveDirectory/web/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/clerk/api/README.md b/packages/auth-providers/clerk/api/README.md index d27ea3626dac..10ba2ea0959a 100644 --- a/packages/auth-providers/clerk/api/README.md +++ b/packages/auth-providers/clerk/api/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/clerk/api/package.json b/packages/auth-providers/clerk/api/package.json index da0003fad385..4edc3a188230 100644 --- a/packages/auth-providers/clerk/api/package.json +++ b/packages/auth-providers/clerk/api/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-clerk-api", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,25 +14,25 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@clerk/clerk-sdk-node": "4.8.1", - "core-js": "3.29.1" + "@babel/runtime-corejs3": "7.23.6", + "@clerk/clerk-sdk-node": "4.13.1", + "core-js": "3.34.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@redwoodjs/api": "4.0.0", - "@types/aws-lambda": "8.10.114", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@redwoodjs/api": "6.0.7", + "@types/aws-lambda": "8.10.126", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/clerk/api/src/__tests__/clerk.test.ts b/packages/auth-providers/clerk/api/src/__tests__/clerk.test.ts index 50643326c471..d80fe627d223 100644 --- a/packages/auth-providers/clerk/api/src/__tests__/clerk.test.ts +++ b/packages/auth-providers/clerk/api/src/__tests__/clerk.test.ts @@ -1,6 +1,6 @@ import type { APIGatewayProxyEvent, Context as LambdaContext } from 'aws-lambda' -import { authDecoder } from '../decoder' +import { authDecoder, clerkAuthDecoder } from '../decoder' const req = { event: {} as APIGatewayProxyEvent, @@ -18,14 +18,32 @@ afterAll(() => { console.error = consoleError }) -test('returns null for unsupported type', async () => { - const decoded = await authDecoder('token', 'netlify', req) +describe('deprecated authDecoder', () => { + test('returns null for unsupported type', async () => { + const decoded = await authDecoder('token', 'netlify', req) - expect(decoded).toBe(null) + expect(decoded).toBe(null) + }) + + test('rejects when the token is invalid', async () => { + process.env.CLERK_JWT_KEY = 'jwt-key' + + await expect(authDecoder('invalid-token', 'clerk', req)).rejects.toThrow() + }) }) -test('rejects when the token is invalid', async () => { - process.env.CLERK_JWT_KEY = 'jwt-key' +describe('clerkAuthDecoder', () => { + test('returns null for unsupported type', async () => { + const decoded = await clerkAuthDecoder('token', 'netlify', req) + + expect(decoded).toBe(null) + }) + + test('rejects when the token is invalid', async () => { + process.env.CLERK_JWT_KEY = 'jwt-key' - await expect(authDecoder('invalid-token', 'clerk', req)).rejects.toThrow() + await expect( + clerkAuthDecoder('invalid-token', 'clerk', req) + ).rejects.toThrow() + }) }) diff --git a/packages/auth-providers/clerk/api/src/decoder.ts b/packages/auth-providers/clerk/api/src/decoder.ts index 8335fd25be66..28f2c5dab15e 100644 --- a/packages/auth-providers/clerk/api/src/decoder.ts +++ b/packages/auth-providers/clerk/api/src/decoder.ts @@ -1,5 +1,8 @@ -import { Decoder } from '@redwoodjs/api' +import type { Decoder } from '@redwoodjs/api' +/** + * @deprecated This function will be removed; it uses a rate-limited API. Use `clerkAuthDecoder` instead. + */ export const authDecoder: Decoder = async (token: string, type: string) => { if (type !== 'clerk') { return null @@ -34,3 +37,39 @@ export const authDecoder: Decoder = async (token: string, type: string) => { return Promise.reject(error) } } + +export const clerkAuthDecoder: Decoder = async ( + token: string, + type: string +) => { + if (type !== 'clerk') { + return null + } + + const { verifyToken } = await import('@clerk/clerk-sdk-node') + + try { + const issuer = (iss: string) => + iss.startsWith('https://clerk.') || iss.includes('.clerk.accounts') + + const jwtPayload = await verifyToken(token, { + issuer, + apiUrl: process.env.CLERK_API_URL || 'https://api.clerk.dev', + jwtKey: process.env.CLERK_JWT_KEY, + apiKey: process.env.CLERK_API_KEY, + secretKey: process.env.CLERK_SECRET_KEY, + }) + + if (!jwtPayload.sub) { + return Promise.reject(new Error('Session invalid')) + } + + return { + ...jwtPayload, + id: jwtPayload.sub, + } + } catch (error) { + console.error(error) + return Promise.reject(error) + } +} diff --git a/packages/auth-providers/clerk/api/src/index.ts b/packages/auth-providers/clerk/api/src/index.ts index ead5bdde8676..6d1498a92969 100644 --- a/packages/auth-providers/clerk/api/src/index.ts +++ b/packages/auth-providers/clerk/api/src/index.ts @@ -1 +1 @@ -export { authDecoder } from './decoder' +export { authDecoder, clerkAuthDecoder } from './decoder' diff --git a/packages/auth-providers/clerk/api/tsconfig.json b/packages/auth-providers/clerk/api/tsconfig.json index bb9ec4a21fd1..992f3e8dfddf 100644 --- a/packages/auth-providers/clerk/api/tsconfig.json +++ b/packages/auth-providers/clerk/api/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/clerk/setup/README.md b/packages/auth-providers/clerk/setup/README.md index 661208df586e..cba1dea094ac 100644 --- a/packages/auth-providers/clerk/setup/README.md +++ b/packages/auth-providers/clerk/setup/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/clerk/setup/package.json b/packages/auth-providers/clerk/setup/package.json index 939bae3cf624..f90dc0ba596e 100644 --- a/packages/auth-providers/clerk/setup/package.json +++ b/packages/auth-providers/clerk/setup/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-clerk-setup", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,24 +14,24 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src --passWithNoTests", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@redwoodjs/cli-helpers": "4.0.0", - "core-js": "3.29.1" + "@babel/runtime-corejs3": "7.23.6", + "@redwoodjs/cli-helpers": "6.0.7", + "core-js": "3.34.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@types/yargs": "17.0.24", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@types/yargs": "17.0.32", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/clerk/setup/src/setup.ts b/packages/auth-providers/clerk/setup/src/setup.ts index 846bc8e8dc50..59374fb7b262 100644 --- a/packages/auth-providers/clerk/setup/src/setup.ts +++ b/packages/auth-providers/clerk/setup/src/setup.ts @@ -1,4 +1,4 @@ -import yargs from 'yargs' +import type yargs from 'yargs' import { standardAuthBuilder } from '@redwoodjs/cli-helpers' @@ -14,6 +14,6 @@ export interface Args { } export async function handler(options: Args) { - const { handler } = await import('./setupHandler') + const { handler } = await import('./setupHandler.js') return handler(options) } diff --git a/packages/auth-providers/clerk/setup/src/setupHandler.ts b/packages/auth-providers/clerk/setup/src/setupHandler.ts index ba42638f863e..d990fb20c9d6 100644 --- a/packages/auth-providers/clerk/setup/src/setupHandler.ts +++ b/packages/auth-providers/clerk/setup/src/setupHandler.ts @@ -3,7 +3,7 @@ import path from 'path' import { standardAuthHandler } from '@redwoodjs/cli-helpers' -import { Args } from './setup' +import type { Args } from './setup' const { version } = JSON.parse( fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8') @@ -13,7 +13,7 @@ export const handler = async ({ force: forceArg }: Args) => { standardAuthHandler({ basedir: __dirname, forceArg, - authDecoderImport: `import { authDecoder } from '@redwoodjs/auth-clerk-api'`, + authDecoderImport: `import { clerkAuthDecoder as authDecoder } from '@redwoodjs/auth-clerk-api'`, provider: 'clerk', webPackages: [ '@clerk/clerk-react@^4', @@ -21,14 +21,11 @@ export const handler = async ({ force: forceArg }: Args) => { ], apiPackages: [`@redwoodjs/auth-clerk-api@${version}`], notes: [ - "You'll need to add three env vars to your .env file:", + "You'll need to add two env vars to your .env file:", '', '```title=".env"', 'CLERK_PUBLISHABLE_KEY="..."', 'CLERK_SECRET_KEY="..."', - 'CLERK_JWT_KEY="-----BEGIN PUBLIC KEY-----', - '...', - '-----END PUBLIC KEY-----"', '```', '', `You can find their values under "API Keys" on your Clerk app's dashboard.`, diff --git a/packages/auth-providers/clerk/setup/src/templates/api/lib/auth.ts.template b/packages/auth-providers/clerk/setup/src/templates/api/lib/auth.ts.template index 92f1ee761481..10339861cb1b 100644 --- a/packages/auth-providers/clerk/setup/src/templates/api/lib/auth.ts.template +++ b/packages/auth-providers/clerk/setup/src/templates/api/lib/auth.ts.template @@ -1,12 +1,11 @@ -import { parseJWT } from '@redwoodjs/api' import { AuthenticationError, ForbiddenError } from '@redwoodjs/graphql-server' import { logger } from 'src/lib/logger' /** - * getCurrentUser returns the user information together with - * an optional collection of roles used by requireAuth() to check - * if the user is authenticated or has role-based access + * getCurrentUser returns the user information. + * Once you're ready you can also return a collection of roles + * for `requireAuth` and the Router to use. * * @param decoded - The decoded access token containing user info and JWT claims like `sub`. Note could be null. * @param { token, SupportedAuthTypes type } - The access token itself as well as the auth provider type @@ -27,16 +26,10 @@ export const getCurrentUser = async ( return null } - const { roles } = parseJWT({ decoded }) + const { id, ..._rest } = decoded - // Remove privateMetadata property from CurrentUser as it should not be accessible on the web - const { privateMetadata, ...userWithoutPrivateMetadata } = decoded - - if (roles) { - return { ...userWithoutPrivateMetadata, roles } - } - - return { ...userWithoutPrivateMetadata } + // Be careful to only return information that should be accessible on the web side. + return { id } } /** diff --git a/packages/auth-providers/clerk/setup/tsconfig.json b/packages/auth-providers/clerk/setup/tsconfig.json index abb54b70112a..5be9707b2f05 100644 --- a/packages/auth-providers/clerk/setup/tsconfig.json +++ b/packages/auth-providers/clerk/setup/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/clerk/web/README.md b/packages/auth-providers/clerk/web/README.md index 661208df586e..cba1dea094ac 100644 --- a/packages/auth-providers/clerk/web/README.md +++ b/packages/auth-providers/clerk/web/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/clerk/web/package.json b/packages/auth-providers/clerk/web/package.json index 7da3385d4b5f..61545a11ba96 100644 --- a/packages/auth-providers/clerk/web/package.json +++ b/packages/auth-providers/clerk/web/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-clerk-web", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,30 +14,30 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@redwoodjs/auth": "4.0.0", - "core-js": "3.29.1" + "@babel/runtime-corejs3": "7.23.6", + "@redwoodjs/auth": "6.0.7", + "core-js": "3.34.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@clerk/clerk-react": "4.14.1", - "@clerk/types": "3.33.0", - "@types/react": "18.0.31", - "jest": "29.5.0", - "react": "18.2.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@clerk/clerk-react": "4.28.3", + "@clerk/types": "3.60.0", + "@types/react": "18.2.37", + "jest": "29.7.0", + "react": "0.0.0-experimental-e5205658f-20230913", + "typescript": "5.3.3" }, "peerDependencies": { - "@clerk/clerk-react": "4.14.1" + "@clerk/clerk-react": "4.28.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/clerk/web/src/__tests__/clerk.test.tsx b/packages/auth-providers/clerk/web/src/__tests__/clerk.test.tsx index 2661955635c1..93d6faccbabf 100644 --- a/packages/auth-providers/clerk/web/src/__tests__/clerk.test.tsx +++ b/packages/auth-providers/clerk/web/src/__tests__/clerk.test.tsx @@ -1,4 +1,4 @@ -import { +import type { Clerk as ClerkClient, UserResource, EmailAddressResource, @@ -6,7 +6,7 @@ import { } from '@clerk/types' import { renderHook, act } from '@testing-library/react' -import { CurrentUser } from '@redwoodjs/auth' +import type { CurrentUser } from '@redwoodjs/auth' import { createAuth } from '../clerk' diff --git a/packages/auth-providers/clerk/web/src/clerk.tsx b/packages/auth-providers/clerk/web/src/clerk.tsx index 6ec0910ee9aa..f23ac13718d7 100644 --- a/packages/auth-providers/clerk/web/src/clerk.tsx +++ b/packages/auth-providers/clerk/web/src/clerk.tsx @@ -1,4 +1,4 @@ -import { +import type { SignInProps, SignUpProps, SignOutCallback, @@ -7,7 +7,8 @@ import { SignOutOptions, } from '@clerk/types' -import { CurrentUser, createAuthentication } from '@redwoodjs/auth' +import type { CurrentUser } from '@redwoodjs/auth' +import { createAuthentication } from '@redwoodjs/auth' type Clerk = ClerkClient | undefined | null diff --git a/packages/auth-providers/clerk/web/tsconfig.json b/packages/auth-providers/clerk/web/tsconfig.json index a925964c5e44..bf6fbe8951bd 100644 --- a/packages/auth-providers/clerk/web/tsconfig.json +++ b/packages/auth-providers/clerk/web/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/custom/setup/README.md b/packages/auth-providers/custom/setup/README.md index 661208df586e..cba1dea094ac 100644 --- a/packages/auth-providers/custom/setup/README.md +++ b/packages/auth-providers/custom/setup/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/custom/setup/package.json b/packages/auth-providers/custom/setup/package.json index 66e51a73ae7d..c8a8b4355607 100644 --- a/packages/auth-providers/custom/setup/package.json +++ b/packages/auth-providers/custom/setup/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-custom-setup", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,24 +14,24 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@redwoodjs/cli-helpers": "4.0.0", - "core-js": "3.29.1" + "@babel/runtime-corejs3": "7.23.6", + "@redwoodjs/cli-helpers": "6.0.7", + "core-js": "3.34.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@types/yargs": "17.0.24", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@types/yargs": "17.0.32", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/custom/setup/src/setup.ts b/packages/auth-providers/custom/setup/src/setup.ts index ae8a986db91b..6bc496a8388f 100644 --- a/packages/auth-providers/custom/setup/src/setup.ts +++ b/packages/auth-providers/custom/setup/src/setup.ts @@ -1,4 +1,4 @@ -import yargs from 'yargs' +import type yargs from 'yargs' import { standardAuthBuilder } from '@redwoodjs/cli-helpers' @@ -14,6 +14,6 @@ export interface Args { } export async function handler(options: Args) { - const { handler } = await import('./setupHandler') + const { handler } = await import('./setupHandler.js') return handler(options) } diff --git a/packages/auth-providers/custom/setup/src/setupHandler.ts b/packages/auth-providers/custom/setup/src/setupHandler.ts index 6c14308e4e1c..a055e0924662 100644 --- a/packages/auth-providers/custom/setup/src/setupHandler.ts +++ b/packages/auth-providers/custom/setup/src/setupHandler.ts @@ -6,7 +6,7 @@ import { standardAuthHandler, } from '@redwoodjs/cli-helpers' -import { Args } from './setup' +import type { Args } from './setup' const { version } = JSON.parse( fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8') diff --git a/packages/auth-providers/custom/setup/tsconfig.json b/packages/auth-providers/custom/setup/tsconfig.json index abb54b70112a..5be9707b2f05 100644 --- a/packages/auth-providers/custom/setup/tsconfig.json +++ b/packages/auth-providers/custom/setup/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/dbAuth/api/README.md b/packages/auth-providers/dbAuth/api/README.md index d27ea3626dac..10ba2ea0959a 100644 --- a/packages/auth-providers/dbAuth/api/README.md +++ b/packages/auth-providers/dbAuth/api/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/dbAuth/api/package.json b/packages/auth-providers/dbAuth/api/package.json index d54dd171103b..fca6ae6e73ac 100644 --- a/packages/auth-providers/dbAuth/api/package.json +++ b/packages/auth-providers/dbAuth/api/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-dbauth-api", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,31 +14,30 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", + "@babel/runtime-corejs3": "7.23.6", + "@redwoodjs/project-config": "6.0.7", "base64url": "3.0.1", - "core-js": "3.29.1", - "crypto-js": "4.1.1", + "core-js": "3.34.0", "md5": "2.3.0", - "uuid": "9.0.0" + "uuid": "9.0.1" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@redwoodjs/api": "4.0.0", - "@simplewebauthn/server": "7.2.0", - "@types/crypto-js": "4.1.1", - "@types/md5": "2.3.2", - "@types/uuid": "9.0.1", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@redwoodjs/api": "6.0.7", + "@simplewebauthn/server": "7.4.0", + "@types/md5": "2.3.5", + "@types/uuid": "9.0.7", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/dbAuth/api/src/DbAuthHandler.ts b/packages/auth-providers/dbAuth/api/src/DbAuthHandler.ts index c7cc1b8c0e46..6c28f1b066e2 100644 --- a/packages/auth-providers/dbAuth/api/src/DbAuthHandler.ts +++ b/packages/auth-providers/dbAuth/api/src/DbAuthHandler.ts @@ -1,11 +1,11 @@ import type { PrismaClient } from '@prisma/client' import type { - GenerateRegistrationOptionsOpts, GenerateAuthenticationOptionsOpts, - VerifyRegistrationResponseOpts, - VerifyAuthenticationResponseOpts, - VerifiedRegistrationResponse, + GenerateRegistrationOptionsOpts, VerifiedAuthenticationResponse, + VerifiedRegistrationResponse, + VerifyAuthenticationResponseOpts, + VerifyRegistrationResponseOpts, } from '@simplewebauthn/server' import type { AuthenticationResponseJSON, @@ -13,31 +13,31 @@ import type { } from '@simplewebauthn/typescript-types' import type { APIGatewayProxyEvent, Context as LambdaContext } from 'aws-lambda' import base64url from 'base64url' -import CryptoJS from 'crypto-js' import md5 from 'md5' import { v4 as uuidv4 } from 'uuid' -import { - CorsConfig, - CorsContext, - CorsHeaders, - createCorsContext, - normalizeRequest, -} from '@redwoodjs/api' +import type { CorsConfig, CorsContext, CorsHeaders } from '@redwoodjs/api' +import { createCorsContext, normalizeRequest } from '@redwoodjs/api' import * as DbAuthError from './errors' import { + cookieName, decryptSession, + encryptSession, extractCookie, getSession, hashPassword, + legacyHashPassword, + isLegacySession, + hashToken, webAuthnSession, + extractHashingOptions, } from './shared' type SetCookieHeader = { 'set-cookie': string } type CsrfTokenHeader = { 'csrf-token': string } -interface SignupFlowOptions { +interface SignupFlowOptions> { /** * Allow users to sign up. Defaults to true. * Needs to be explicitly set to false to disable the flow @@ -51,7 +51,7 @@ interface SignupFlowOptions { * were included in the object given to the `signUp()` function you got * from `useAuth()` */ - handler: (signupHandlerOptions: SignupHandlerOptions) => any + handler: (signupHandlerOptions: SignupHandlerOptions) => any /** * Validate the user-supplied password with whatever logic you want. Return @@ -67,15 +67,20 @@ interface SignupFlowOptions { usernameTaken?: string flowNotEnabled?: string } + + /** + * Allows the user to define if the UserCheck for their selected db provider should use case insensitive + */ + usernameMatch?: string } -interface ForgotPasswordFlowOptions> { +interface ForgotPasswordFlowOptions { /** * Allow users to request a new password via a call to forgotPassword. Defaults to true. * Needs to be explicitly set to false to disable the flow */ enabled?: boolean - handler: (user: TUser) => any + handler: (user: TUser, token: string) => any errors?: { usernameNotFound?: string usernameRequired?: string @@ -84,7 +89,7 @@ interface ForgotPasswordFlowOptions> { expires: number } -interface LoginFlowOptions> { +interface LoginFlowOptions { /** * Allow users to login. Defaults to true. * Needs to be explicitly set to false to disable the flow @@ -111,9 +116,14 @@ interface LoginFlowOptions> { * How long a user will remain logged in, in seconds */ expires: number + + /** + * Allows the user to define if the UserCheck for their selected db provider should use case insensitive + */ + usernameMatch?: string } -interface ResetPasswordFlowOptions> { +interface ResetPasswordFlowOptions { /** * Allow users to reset their password via a code from a call to forgotPassword. Defaults to true. * Needs to be explicitly set to false to disable the flow @@ -147,7 +157,12 @@ interface WebAuthnFlowOptions { } } -export interface DbAuthHandlerOptions> { +export type UserType = Record + +export interface DbAuthHandlerOptions< + TUser = UserType, + TUserAttributes = Record +> { /** * Provide prisma db client */ @@ -162,6 +177,12 @@ export interface DbAuthHandlerOptions> { * ie. if your Prisma model is named `UserCredential` this value would be `userCredential`, as in `db.userCredential` */ credentialModelAccessor?: keyof PrismaClient + /** + * The fields that are allowed to be returned from the user table when + * invoking handlers that return a user object (like forgotPassword and signup) + * Defaults to `id` and `email` if not set at all. + */ + allowedUserFields?: string[] /** * A map of what dbAuth calls a field to what your database calls it. * `id` is whatever column you use to uniquely identify a user (probably @@ -180,11 +201,31 @@ export interface DbAuthHandlerOptions> { * Object containing cookie config options */ cookie?: { + /** @deprecated set this option in `cookie.attributes` */ Path?: string + /** @deprecated set this option in `cookie.attributes` */ HttpOnly?: boolean + /** @deprecated set this option in `cookie.attributes` */ Secure?: boolean + /** @deprecated set this option in `cookie.attributes` */ SameSite?: string + /** @deprecated set this option in `cookie.attributes` */ Domain?: string + attributes?: { + Path?: string + HttpOnly?: boolean + Secure?: boolean + SameSite?: string + Domain?: string + } + /** + * The name of the cookie that dbAuth sets + * + * %port% will be replaced with the port the api server is running on. + * If you have multiple RW apps running on the same host, you'll need to + * make sure they all use unique cookie names + */ + name?: string } /** * Object containing forgot password options @@ -201,7 +242,7 @@ export interface DbAuthHandlerOptions> { /** * Object containing login options */ - signup: SignupFlowOptions | { enabled: false } + signup: SignupFlowOptions | { enabled: false } /** * Object containing WebAuthn options @@ -214,11 +255,11 @@ export interface DbAuthHandlerOptions> { cors?: CorsConfig } -interface SignupHandlerOptions { +export interface SignupHandlerOptions { username: string hashedPassword: string salt: string - userAttributes?: Record + userAttributes?: TUserAttributes } export type AuthMethodNames = @@ -246,18 +287,22 @@ interface DbAuthSession { id: TIdType } +const DEFAULT_ALLOWED_USER_FIELDS = ['id', 'email'] + export class DbAuthHandler< - TUser extends Record, - TIdType = any + TUser extends UserType, + TIdType = any, + TUserAttributes = Record > { event: APIGatewayProxyEvent context: LambdaContext - options: DbAuthHandlerOptions + options: DbAuthHandlerOptions cookie: string | undefined params: Params db: PrismaClient dbAccessor: any dbCredentialAccessor: any + allowedUserFields: string[] headerCsrfToken: string | undefined hasInvalidSession: boolean session: DbAuthSession | undefined @@ -314,8 +359,9 @@ export class DbAuthHandler< return ['usb', 'ble', 'nfc', 'internal'] } - // returns the set-cookie header to mark the cookie as expired ("deletes" the session) /** + * Returns the set-cookie header to mark the cookie as expired ("deletes" the session) + * * The header keys are case insensitive, but Fastify prefers these to be lowercase. * Therefore, we want to ensure that the headers are always lowercase and unique * for compliance with HTTP/2. @@ -325,7 +371,7 @@ export class DbAuthHandler< get _deleteSessionHeader() { return { 'set-cookie': [ - 'session=', + `${cookieName(this.options.cookie?.name)}=`, ...this._cookieAttributes({ expires: 'now' }), ].join(';'), } @@ -334,7 +380,7 @@ export class DbAuthHandler< constructor( event: APIGatewayProxyEvent, context: LambdaContext, - options: DbAuthHandlerOptions + options: DbAuthHandlerOptions ) { this.event = event this.context = context @@ -351,6 +397,8 @@ export class DbAuthHandler< : null this.headerCsrfToken = this.event.headers['csrf-token'] this.hasInvalidSession = false + this.allowedUserFields = + this.options.allowedUserFields || DEFAULT_ALLOWED_USER_FIELDS const sessionExpiresAt = new Date() sessionExpiresAt.setSeconds( @@ -374,7 +422,9 @@ export class DbAuthHandler< } try { - const [session, csrfToken] = decryptSession(getSession(this.cookie)) + const [session, csrfToken] = decryptSession( + getSession(this.cookie, this.options.cookie?.name) + ) this.session = session this.sessionCsrfToken = csrfToken } catch (e) { @@ -486,6 +536,9 @@ export class DbAuthHandler< const buffer = Buffer.from(token) token = buffer.toString('base64').replace('=', '').substring(0, 16) + // Store the token hash in the database so we can verify it later + const tokenHash = hashToken(token) + try { // set token and expires time user = await this.dbAccessor.update({ @@ -493,7 +546,7 @@ export class DbAuthHandler< [this.options.authFields.id]: user[this.options.authFields.id], }, data: { - [this.options.authFields.resetToken]: token, + [this.options.authFields.resetToken]: tokenHash, [this.options.authFields.resetTokenExpiresAt]: tokenExpires, }, }) @@ -504,20 +557,10 @@ export class DbAuthHandler< // call user-defined handler in their functions/auth.js const response = await ( this.options.forgotPassword as ForgotPasswordFlowOptions - ).handler(this._sanitizeUser(user)) - - // remove resetToken and resetTokenExpiresAt if in the body of the - // forgotPassword handler response - let responseObj = response - if (typeof response === 'object') { - responseObj = Object.assign(response, { - [this.options.authFields.resetToken]: undefined, - [this.options.authFields.resetTokenExpiresAt]: undefined, - }) - } + ).handler(this._sanitizeUser(user), token) return [ - response ? JSON.stringify(responseObj) : '', + response ? JSON.stringify(response) : '', { ...this._deleteSessionHeader, }, @@ -533,11 +576,15 @@ export class DbAuthHandler< async getToken() { try { const user = await this._getCurrentUser() + let headers = {} + + // if the session was encrypted with the old algorithm, re-encrypt it + // with the new one + if (isLegacySession(this.cookie)) { + headers = this._loginResponse(user)[1] + } - // need to return *something* for our existing Authorization header stuff - // to work, so return the user's ID in case we can use it for something - // in the future - return [user[this.options.authFields.id]] + return [user[this.options.authFields.id], headers] } catch (e: any) { if (e instanceof DbAuthError.NotLoggedInError) { return this._logoutResponse() @@ -600,12 +647,16 @@ export class DbAuthHandler< } let user = await this._findUserByToken(resetToken as string) - const [hashedPassword] = hashPassword(password, user.salt) + const [hashedPassword] = hashPassword(password, { + salt: user.salt, + }) + const [legacyHashedPassword] = legacyHashPassword(password, user.salt) if ( - !(this.options.resetPassword as ResetPasswordFlowOptions) + (!(this.options.resetPassword as ResetPasswordFlowOptions) .allowReusedPassword && - user.hashedPassword === hashedPassword + user.hashedPassword === hashedPassword) || + user.hashedPassword === legacyHashedPassword ) { throw new DbAuthError.ReusedPasswordError( ( @@ -1047,11 +1098,16 @@ export class DbAuthHandler< ].join(';') } - // removes sensitive fields from user before sending over the wire + // removes any fields not explicitly allowed to be sent to the client before + // sending a response over the wire _sanitizeUser(user: Record) { const sanitized = JSON.parse(JSON.stringify(user)) - delete sanitized[this.options.authFields.hashedPassword] - delete sanitized[this.options.authFields.salt] + + Object.keys(sanitized).forEach((key) => { + if (!this.allowedUserFields.includes(key)) { + delete sanitized[key] + } + }) return sanitized } @@ -1082,7 +1138,17 @@ export class DbAuthHandler< expires?: 'now' | string options?: DbAuthHandlerOptions['cookie'] }) { - const cookieOptions = { ...this.options.cookie, ...options } || { + // TODO: When we drop support for specifying cookie attributes directly on + // `options.cookie` we can get rid of all of this and just spread + // `this.options.cookie?.attributes` directly into `cookieOptions` below + const userCookieAttributes = this.options.cookie?.attributes + ? { ...this.options.cookie?.attributes } + : { ...this.options.cookie } + if (!this.options.cookie?.attributes) { + delete userCookieAttributes.name + } + + const cookieOptions = { ...userCookieAttributes, ...options } || { ...options, } const meta = Object.keys(cookieOptions) @@ -1108,11 +1174,6 @@ export class DbAuthHandler< return meta } - // encrypts a string with the SESSION_SECRET - _encrypt(data: string) { - return CryptoJS.AES.encrypt(data, process.env.SESSION_SECRET as string) - } - // returns the set-cookie header to be returned in the request (effectively // creates the session) _createSessionHeader( @@ -1120,9 +1181,9 @@ export class DbAuthHandler< csrfToken: string ): SetCookieHeader { const session = JSON.stringify(data) + ';' + csrfToken - const encrypted = this._encrypt(session) + const encrypted = encryptSession(session) const cookie = [ - `session=${encrypted.toString()}`, + `${cookieName(this.options.cookie?.name)}=${encrypted}`, ...this._cookieAttributes({ expires: this.sessionExpiresDate }), ].join(';') @@ -1145,9 +1206,11 @@ export class DbAuthHandler< (this.options.forgotPassword as ForgotPasswordFlowOptions).expires ) + const tokenHash = hashToken(token) + const user = await this.dbAccessor.findFirst({ where: { - [this.options.authFields.resetToken]: token, + [this.options.authFields.resetToken]: tokenHash, }, }) @@ -1209,11 +1272,16 @@ export class DbAuthHandler< ) } + const usernameMatchFlowOption = (this.options.login as LoginFlowOptions) + ?.usernameMatch + const findUniqueUserMatchCriteriaOptions = + this._getUserMatchCriteriaOptions(username, usernameMatchFlowOption) let user + try { // does user exist? - user = await this.dbAccessor.findUnique({ - where: { [this.options.authFields.username]: username }, + user = await this.dbAccessor.findFirst({ + where: findUniqueUserMatchCriteriaOptions, }) } catch (e) { throw new DbAuthError.GenericError() @@ -1226,19 +1294,56 @@ export class DbAuthHandler< ) } - // is password correct? - const [hashedPassword, _salt] = hashPassword( - password, - user[this.options.authFields.salt] + await this._verifyPassword(user, password) + return user + } + + // extracts scrypt strength options from hashed password (if present) and + // compares the hashed plain text password just submitted using those options + // with the one in the database. Falls back to the legacy CryptoJS algorihtm + // if no options are present. + async _verifyPassword(user: Record, password: string) { + const options = extractHashingOptions( + user[this.options.authFields.hashedPassword] as string ) - if (hashedPassword === user[this.options.authFields.hashedPassword]) { - return user + + if (Object.keys(options).length) { + // hashed using the node:crypto algorithm + const [hashedPassword] = hashPassword(password, { + salt: user[this.options.authFields.salt] as string, + options, + }) + + if (hashedPassword === user[this.options.authFields.hashedPassword]) { + return user + } } else { - throw new DbAuthError.IncorrectPasswordError( - username, - (this.options.login as LoginFlowOptions)?.errors?.incorrectPassword + // fallback to old CryptoJS hashing + const [legacyHashedPassword] = legacyHashPassword( + password, + user[this.options.authFields.salt] as string ) + + if ( + legacyHashedPassword === user[this.options.authFields.hashedPassword] + ) { + const [newHashedPassword] = hashPassword(password, { + salt: user[this.options.authFields.salt] as string, + }) + + // update user's hash to the new algorithm + await this.dbAccessor.update({ + where: { id: user.id }, + data: { [this.options.authFields.hashedPassword]: newHashedPassword }, + }) + return user + } } + + throw new DbAuthError.IncorrectPasswordError( + user[this.options.authFields.username] as string, + (this.options.login as LoginFlowOptions)?.errors?.incorrectPassword + ) } // gets the user from the database and returns only its ID @@ -1282,9 +1387,15 @@ export class DbAuthHandler< this._validateField('username', username) && this._validateField('password', password) ) { - const user = await this.dbAccessor.findUnique({ - where: { [this.options.authFields.username]: username }, + const usernameMatchFlowOption = (this.options.signup as SignupFlowOptions) + ?.usernameMatch + const findUniqueUserMatchCriteriaOptions = + this._getUserMatchCriteriaOptions(username, usernameMatchFlowOption) + + const user = await this.dbAccessor.findFirst({ + where: findUniqueUserMatchCriteriaOptions, }) + if (user) { throw new DbAuthError.DuplicateUsernameError( username, @@ -1292,8 +1403,7 @@ export class DbAuthHandler< ) } - // if we get here everything is good, call the app's signup handler and let - // them worry about scrubbing data and saving to the DB + // if we get here everything is good, call the app's signup handler const [hashedPassword, salt] = hashPassword(password) const newUser = await (this.options.signup as SignupFlowOptions).handler({ username, @@ -1323,10 +1433,10 @@ export class DbAuthHandler< return methodName } - // checks that a single field meets validation requirements and - // currently checks for presense only + // checks that a single field meets validation requirements + // currently checks for presence only _validateField(name: string, value: string | undefined): value is string { - // check for presense + // check for presence if (!value || value.trim() === '') { throw new DbAuthError.FieldRequiredError( name, @@ -1345,11 +1455,9 @@ export class DbAuthHandler< SetCookieHeader & CsrfTokenHeader, { statusCode: number } ] { - const sessionData = { id: user[this.options.authFields.id] } + const sessionData = this._sanitizeUser(user) - // TODO: this needs to go into graphql somewhere so that each request makes - // a new CSRF token and sets it in both the encrypted session and the - // csrf-token header + // TODO: this needs to go into graphql somewhere so that each request makes a new CSRF token and sets it in both the encrypted session and the csrf-token header const csrfToken = DbAuthHandler.CSRF_TOKEN return [ @@ -1411,4 +1519,23 @@ export class DbAuthHandler< }, } } + + _getUserMatchCriteriaOptions( + username: string, + usernameMatchFlowOption: string | undefined + ) { + // Each db provider has it owns rules for case insensitive comparison. + // We are checking if you have defined one for your db choice here + // https://www.prisma.io/docs/concepts/components/prisma-client/case-sensitivity + const findUniqueUserMatchCriteriaOptions = !usernameMatchFlowOption + ? { [this.options.authFields.username]: username } + : { + [this.options.authFields.username]: { + equals: username, + mode: usernameMatchFlowOption, + }, + } + + return findUniqueUserMatchCriteriaOptions + } } diff --git a/packages/auth-providers/dbAuth/api/src/__tests__/DbAuthHandler.test.js b/packages/auth-providers/dbAuth/api/src/__tests__/DbAuthHandler.test.js index 56623467d51f..3f7aa24971fe 100644 --- a/packages/auth-providers/dbAuth/api/src/__tests__/DbAuthHandler.test.js +++ b/packages/auth-providers/dbAuth/api/src/__tests__/DbAuthHandler.test.js @@ -1,7 +1,9 @@ -import CryptoJS from 'crypto-js' +import crypto from 'node:crypto' +import path from 'node:path' import { DbAuthHandler } from '../DbAuthHandler' import * as dbAuthError from '../errors' +import { hashToken } from '../shared' // mock prisma db client const DbMock = class { @@ -78,17 +80,31 @@ const db = new DbMock(['user', 'userCredential']) const UUID_REGEX = /\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b/ -const SET_SESSION_REGEX = /^session=[a-zA-Z0-9+=/]+;/ +const SET_SESSION_REGEX = /^session=[a-zA-Z0-9+=/]|[a-zA-Z0-9+=/]+;/ const UTC_DATE_REGEX = /\w{3}, \d{2} \w{3} \d{4} [\d:]{8} GMT/ const LOGOUT_COOKIE = 'session=;Expires=Thu, 01 Jan 1970 00:00:00 GMT' +const SESSION_SECRET = '540d03ebb00b441f8f7442cbc39958ad' +const FIXTURE_PATH = path.resolve( + __dirname, + '../../../../../../__fixtures__/example-todo-main' +) + +beforeAll(() => { + process.env.RWJS_CWD = FIXTURE_PATH +}) + +afterAll(() => { + delete process.env.RWJS_CWD +}) const createDbUser = async (attributes = {}) => { return await db.user.create({ data: { email: 'rob@redwoodjs.com', + // default hashedPassword is from `node:crypto` hashedPassword: - '0c2b24e20ee76a887eac1415cc2c175ff961e7a0f057cead74789c43399dd5ba', - salt: '2ef27f4073c603ba8b7807c6de6d6a89', + '230847bea5154b6c7d281d09593ad1be26fa03a93c04a73bcc2b608c073a8213|16384|8|1', + salt: 'ba8b7807c6de6d6a892ef27f4073c603', ...attributes, }, }) @@ -103,7 +119,16 @@ const expectLoggedInResponse = (response) => { } const encryptToCookie = (data) => { - return `session=${CryptoJS.AES.encrypt(data, process.env.SESSION_SECRET)}` + const iv = crypto.randomBytes(16) + const cipher = crypto.createCipheriv( + 'aes-256-cbc', + SESSION_SECRET.substring(0, 32), + iv + ) + let encryptedSession = cipher.update(data, 'utf-8', 'base64') + encryptedSession += cipher.final('base64') + + return `session=${encryptedSession}|${iv.toString('base64')}` } let event, context, options @@ -113,7 +138,7 @@ describe('dbAuth', () => { // hide deprecation warnings during test jest.spyOn(console, 'warn').mockImplementation(() => {}) // encryption key so results are consistent regardless of settings in .env - process.env.SESSION_SECRET = 'nREjs1HPS7cFia6tQHK70EWGtfhOgbqJQKsHQz3S' + process.env.SESSION_SECRET = SESSION_SECRET delete process.env.DBAUTH_COOKIE_DOMAIN event = { @@ -189,6 +214,9 @@ describe('dbAuth', () => { counter: 'counter', }, }, + cookie: { + name: 'session', + }, } }) @@ -562,7 +590,7 @@ describe('dbAuth', () => { event = { headers: { cookie: - 'session=U2FsdGVkX1/zRHVlEQhffsOufy7VLRAR6R4gb818vxblQQJFZI6W/T8uzxNUbQMx', + 'session=ko6iXKV11DSjb6kFJ4iwcf1FEqa5wPpbL1sdtKiV51Y=|cQaYkOPG/r3ILxWiFiz90w==', }, } const dbAuth = new DbAuthHandler(event, context, options) @@ -595,7 +623,7 @@ describe('dbAuth', () => { event.body = JSON.stringify({ method: 'logout' }) event.httpMethod = 'GET' event.headers.cookie = - 'session=U2FsdGVkX1/zRHVlEQhffsOufy7VLRAR6R4gb818vxblQQJFZI6W/T8uzxNUbQMx' + 'session=ko6iXKV11DSjb6kFJ4iwcf1FEqa5wPpbL1sdtKiV51Y=|cQaYkOPG/r3ILxWiFiz90w==' const dbAuth = new DbAuthHandler(event, context, options) const response = await dbAuth.invoke() @@ -606,7 +634,7 @@ describe('dbAuth', () => { event.body = JSON.stringify({ method: 'foobar' }) event.httpMethod = 'POST' event.headers.cookie = - 'session=U2FsdGVkX1/zRHVlEQhffsOufy7VLRAR6R4gb818vxblQQJFZI6W/T8uzxNUbQMx' + 'session=ko6iXKV11DSjb6kFJ4iwcf1FEqa5wPpbL1sdtKiV51Y=|cQaYkOPG/r3ILxWiFiz90w==' const dbAuth = new DbAuthHandler(event, context, options) const response = await dbAuth.invoke() @@ -617,7 +645,7 @@ describe('dbAuth', () => { event.body = JSON.stringify({ method: 'logout' }) event.httpMethod = 'POST' event.headers.cookie = - 'session=U2FsdGVkX1/zRHVlEQhffsOufy7VLRAR6R4gb818vxblQQJFZI6W/T8uzxNUbQMx' + 'session=ko6iXKV11DSjb6kFJ4iwcf1FEqa5wPpbL1sdtKiV51Y=|cQaYkOPG/r3ILxWiFiz90w==' const dbAuth = new DbAuthHandler(event, context, options) dbAuth.logout = jest.fn(() => { throw Error('Logout error') @@ -655,7 +683,7 @@ describe('dbAuth', () => { event.body = JSON.stringify({ method: 'logout' }) event.httpMethod = 'POST' event.headers.cookie = - 'session=U2FsdGVkX1/zRHVlEQhffsOufy7VLRAR6R4gb818vxblQQJFZI6W/T8uzxNUbQMx' + 'session=ko6iXKV11DSjb6kFJ4iwcf1FEqa5wPpbL1sdtKiV51Y=|cQaYkOPG/r3ILxWiFiz90w==' const dbAuth = new DbAuthHandler(event, context, options) dbAuth.logout = jest.fn(() => ['body', { foo: 'bar' }]) const response = await dbAuth.invoke() @@ -759,8 +787,8 @@ describe('dbAuth', () => { }) expect(resetUser.resetToken).not.toEqual(undefined) - // base64 characters only, except = - expect(resetUser.resetToken).toMatch(/^\w{16}$/) + // Should be a 64 character hex string for a 256 bit token hash (sha256) + expect(resetUser.resetToken).toMatch(/^\w{64}$/) expect(resetUser.resetTokenExpiresAt instanceof Date).toEqual(true) // response contains data returned from the handler @@ -790,12 +818,29 @@ describe('dbAuth', () => { event.body = JSON.stringify({ username: user.email, }) - options.forgotPassword.handler = (handlerUser) => { + options.forgotPassword.handler = (handlerUser, token) => { expect(handlerUser.id).toEqual(user.id) + expect(token).toMatch(/^[A-Za-z0-9/+]{16}$/) } const dbAuth = new DbAuthHandler(event, context, options) await dbAuth.forgotPassword() - expect.assertions(1) + expect.assertions(2) + }) + + it('invokes forgotPassword.handler() with the raw resetToken', async () => { + const user = await createDbUser() + event.body = JSON.stringify({ + username: user.email, + }) + options.forgotPassword.handler = (handlerUser, token) => { + // tokens should be the raw resetToken NOT the hash + // resetToken consists of 16 base64 characters + expect(handlerUser.resetToken).toBeUndefined() + expect(token).toMatch(/^[A-Za-z0-9/+]{16}$/) + } + const dbAuth = new DbAuthHandler(event, context, options) + await dbAuth.forgotPassword() + expect.assertions(2) }) it('removes the token from the forgotPassword response', async () => { @@ -868,6 +913,9 @@ describe('dbAuth', () => { expect.assertions(1) }) it('throws an error if username is not found', async () => { + delete options.signup.usernameMatch + delete options.login.usernameMatch + await createDbUser() event.body = JSON.stringify({ username: 'missing@redwoodjs.com', @@ -970,7 +1018,7 @@ describe('dbAuth', () => { const response = await dbAuth.login() - expect(response[0]).toEqual({ id: user.id }) + expect(response[0].id).toEqual(user.id) }) it('returns a CSRF token in the header', async () => { @@ -1010,6 +1058,58 @@ describe('dbAuth', () => { expectLoggedInResponse(response) }) + + it('login db check is called with insensitive string when user has provided one in LoginFlowOptions', async () => { + jest.clearAllMocks() + const spy = jest.spyOn(db.user, 'findFirst') + + options.signup.usernameMatch = 'insensitive' + options.login.usernameMatch = 'insensitive' + + await createDbUser() + event.body = JSON.stringify({ + username: 'rob@redwoodjs.com', + password: 'password', + }) + + const dbAuth = new DbAuthHandler(event, context, options) + + try { + await dbAuth.login() + } catch (e) { + expect(e).toBeInstanceOf(dbAuthError.UserNotFoundError) + } + + return expect(spy).toHaveBeenCalledWith({ + where: { + email: expect.objectContaining({ mode: 'insensitive' }), + }, + }) + }) + + it('login db check is not called with insensitive string when user has not provided one in LoginFlowOptions', async () => { + jest.clearAllMocks() + const spy = jest.spyOn(db.user, 'findFirst') + + delete options.signup.usernameMatch + delete options.login.usernameMatch + + await createDbUser() + event.body = JSON.stringify({ + username: 'rob@redwoodjs.com', + password: 'password', + }) + + const dbAuth = new DbAuthHandler(event, context, options) + + await dbAuth.login() + + return expect(spy).not.toHaveBeenCalledWith({ + where: { + email: expect.objectContaining({ mode: 'insensitive' }), + }, + }) + }) }) describe('logout', () => { @@ -1123,7 +1223,7 @@ describe('dbAuth', () => { tokenExpires.getSeconds() - options.forgotPassword.expires - 1 ) await createDbUser({ - resetToken: '1234', + resetToken: hashToken('1234'), resetTokenExpiresAt: tokenExpires, }) @@ -1144,7 +1244,7 @@ describe('dbAuth', () => { tokenExpires.getSeconds() - options.forgotPassword.expires - 1 ) const user = await createDbUser({ - resetToken: '1234', + resetToken: hashToken('1234'), resetTokenExpiresAt: tokenExpires, }) @@ -1172,7 +1272,7 @@ describe('dbAuth', () => { tokenExpires.getSeconds() - options.forgotPassword.expires + 1 ) await createDbUser({ - resetToken: '1234', + resetToken: hashToken('1234'), resetTokenExpiresAt: tokenExpires, }) @@ -1194,7 +1294,7 @@ describe('dbAuth', () => { tokenExpires.getSeconds() - options.forgotPassword.expires + 1 ) await createDbUser({ - resetToken: '1234', + resetToken: hashToken('1234'), resetTokenExpiresAt: tokenExpires, }) @@ -1214,7 +1314,7 @@ describe('dbAuth', () => { tokenExpires.getSeconds() - options.forgotPassword.expires + 1 ) const user = await createDbUser({ - resetToken: '1234', + resetToken: hashToken('1234'), resetTokenExpiresAt: tokenExpires, }) event.body = JSON.stringify({ @@ -1240,7 +1340,7 @@ describe('dbAuth', () => { tokenExpires.getSeconds() - options.forgotPassword.expires + 1 ) const user = await createDbUser({ - resetToken: '1234', + resetToken: hashToken('1234'), resetTokenExpiresAt: tokenExpires, }) event.body = JSON.stringify({ @@ -1265,7 +1365,7 @@ describe('dbAuth', () => { tokenExpires.getSeconds() - options.forgotPassword.expires + 1 ) const user = await createDbUser({ - resetToken: '1234', + resetToken: hashToken('1234'), resetTokenExpiresAt: tokenExpires, }) event.body = JSON.stringify({ @@ -1287,7 +1387,7 @@ describe('dbAuth', () => { tokenExpires.getSeconds() - options.forgotPassword.expires + 1 ) await createDbUser({ - resetToken: '1234', + resetToken: hashToken('1234'), resetTokenExpiresAt: tokenExpires, }) event.body = JSON.stringify({ @@ -1308,7 +1408,7 @@ describe('dbAuth', () => { tokenExpires.getSeconds() - options.forgotPassword.expires + 1 ) await createDbUser({ - resetToken: '1234', + resetToken: hashToken('1234'), resetTokenExpiresAt: tokenExpires, }) event.body = JSON.stringify({ @@ -1506,6 +1606,27 @@ describe('dbAuth', () => { expect(response[0]).toEqual('{"error":"User not found"}') }) + + it('re-encrypts the session cookie if using the legacy algorithm', async () => { + await createDbUser({ id: 7 }) + event = { + headers: { + // legacy session with { id: 7 } for userID + cookie: 'session=U2FsdGVkX1+s7seQJnVgGgInxuXm13l8VvzA3Mg2fYg=', + }, + } + process.env.SESSION_SECRET = + 'QKxN2vFSHAf94XYynK8LUALfDuDSdFowG6evfkFX8uszh4YZqhTiqEdshrhWbwbw' + + const dbAuth = new DbAuthHandler(event, context, options) + const [userId, headers] = await dbAuth.getToken() + + expect(userId).toEqual(7) + expect(headers['set-cookie']).toMatch(SET_SESSION_REGEX) + + // set session back to default + process.env.SESSION_SECRET = SESSION_SECRET + }) }) describe('When a developer has set GraphiQL headers to mock a session cookie', () => { @@ -1997,11 +2118,13 @@ describe('dbAuth', () => { { ...options, cookie: { - Path: '/', - HttpOnly: true, - SameSite: 'Strict', - Secure: true, - Domain: 'example.com', + attributes: { + Path: '/', + HttpOnly: true, + SameSite: 'Strict', + Secure: true, + Domain: 'example.com', + }, }, } ) @@ -2077,11 +2200,11 @@ describe('dbAuth', () => { `Expires=${dbAuth.sessionExpiresDate}` ) // can't really match on the session value since it will change on every render, - // due to CSRF token generation but we can check that it contains a only the - // characters that would be returned by the hash function + // due to CSRF token generation but we can check that it contains only the + // characters that would be returned by the encrypt function expect(headers['set-cookie']).toMatch(SET_SESSION_REGEX) // and we can check that it's a certain number of characters - expect(headers['set-cookie'].split(';')[0].length).toEqual(72) + expect(headers['set-cookie'].split(';')[0].length).toEqual(77) }) }) @@ -2244,6 +2367,38 @@ describe('dbAuth', () => { expect(user.id).toEqual(dbUser.id) }) + + it('returns the user if password is hashed with legacy algorithm', async () => { + const dbUser = await createDbUser({ + // CryptoJS hashed password + hashedPassword: + '0c2b24e20ee76a887eac1415cc2c175ff961e7a0f057cead74789c43399dd5ba', + salt: '2ef27f4073c603ba8b7807c6de6d6a89', + }) + const dbAuth = new DbAuthHandler(event, context, options) + const user = await dbAuth._verifyUser(dbUser.email, 'password') + + expect(user.id).toEqual(dbUser.id) + }) + + it('updates the user hashPassword to the new algorithm', async () => { + const dbUser = await createDbUser({ + // CryptoJS hashed password + hashedPassword: + '0c2b24e20ee76a887eac1415cc2c175ff961e7a0f057cead74789c43399dd5ba', + salt: '2ef27f4073c603ba8b7807c6de6d6a89', + }) + const dbAuth = new DbAuthHandler(event, context, options) + await dbAuth._verifyUser(dbUser.email, 'password') + const user = await db.user.findFirst({ where: { id: dbUser.id } }) + + // password now hashed by node:crypto + expect(user.hashedPassword).toEqual( + 'f20d69d478fa1afc85057384e21bd457a76b23b23e2a94f5bd982976f700a552|16384|8|1' + ) + // salt should remain the same + expect(user.salt).toEqual('2ef27f4073c603ba8b7807c6de6d6a89') + }) }) describe('_getCurrentUser()', () => { @@ -2340,6 +2495,55 @@ describe('dbAuth', () => { expect.assertions(2) }) + it('createUser db check is called with insensitive string when user has provided one in SignupFlowOptions', async () => { + const spy = jest.spyOn(db.user, 'findFirst') + options.signup.usernameMatch = 'insensitive' + + const dbUser = await createDbUser() + event.body = JSON.stringify({ + username: dbUser.email, + password: 'password', + }) + const dbAuth = new DbAuthHandler(event, context, options) + + await dbAuth._createUser() + expect(spy).toHaveBeenCalled() + return expect(spy).toHaveBeenCalledWith({ + where: { + email: expect.objectContaining({ mode: 'insensitive' }), + }, + }) + }) + + it('createUser db check is not called with insensitive string when user has not provided one in SignupFlowOptions', async () => { + jest.resetAllMocks() + jest.clearAllMocks() + + const defaultMessage = options.signup.errors.usernameTaken + const spy = jest.spyOn(db.user, 'findFirst') + delete options.signup.usernameMatch + + const dbUser = await createDbUser() + event.body = JSON.stringify({ + username: dbUser.email, + password: 'password', + }) + const dbAuth = new DbAuthHandler(event, context, options) + await dbAuth._createUser().catch((e) => { + expect(e).toBeInstanceOf(dbAuthError.DuplicateUsernameError) + expect(e.message).toEqual( + defaultMessage.replace(/\$\{username\}/, dbUser.email) + ) + }) + + expect(spy).toHaveBeenCalled() + return expect(spy).not.toHaveBeenCalledWith({ + where: { + email: expect.objectContaining({ mode: 'insensitive' }), + }, + }) + }) + it('throws a default error message if username is missing', async () => { const defaultMessage = options.signup.errors.fieldMissing delete options.signup.errors.fieldMissing @@ -2551,4 +2755,33 @@ describe('dbAuth', () => { expect(response.body).toEqual('{"error":"bad"}') }) }) + + describe('_sanitizeUser', () => { + it('removes all but the default fields [id, email] on user', () => { + const dbAuth = new DbAuthHandler(event, context, options) + const user = { + id: 1, + email: 'rob@redwoodjs.com', + password: 'secret', + } + + expect(dbAuth._sanitizeUser(user).id).toEqual(user.id) + expect(dbAuth._sanitizeUser(user).email).toEqual(user.email) + expect(dbAuth._sanitizeUser(user).secret).toBeUndefined() + }) + + it('removes any fields not explictly allowed in allowedUserFields', () => { + options.allowedUserFields = ['foo'] + const dbAuth = new DbAuthHandler(event, context, options) + const user = { + id: 1, + email: 'rob@redwoodjs.com', + foo: 'bar', + } + + expect(dbAuth._sanitizeUser(user).id).toBeUndefined() + expect(dbAuth._sanitizeUser(user).email).toBeUndefined() + expect(dbAuth._sanitizeUser(user).foo).toEqual('bar') + }) + }) }) diff --git a/packages/auth-providers/dbAuth/api/src/__tests__/shared.test.js b/packages/auth-providers/dbAuth/api/src/__tests__/shared.test.js deleted file mode 100644 index 2dfeae6a407e..000000000000 --- a/packages/auth-providers/dbAuth/api/src/__tests__/shared.test.js +++ /dev/null @@ -1,225 +0,0 @@ -import CryptoJS from 'crypto-js' - -import * as error from '../errors' -import { - extractCookie, - getSession, - hashPassword, - decryptSession, - dbAuthSession, - webAuthnSession, -} from '../shared' - -process.env.SESSION_SECRET = 'nREjs1HPS7cFia6tQHK70EWGtfhOgbqJQKsHQz3S' - -const encrypt = (data) => { - return CryptoJS.AES.encrypt(data, process.env.SESSION_SECRET).toString() -} - -describe('getSession()', () => { - it('returns null if no text', () => { - expect(getSession()).toEqual(null) - }) - - it('returns null if no session cookie', () => { - expect(getSession('foo=bar')).toEqual(null) - }) - - it('returns the value of the session cookie', () => { - expect(getSession('session=qwerty')).toEqual('qwerty') - }) - - it('returns the value of the session cookie when there are multiple cookies', () => { - expect(getSession('foo=bar;session=qwerty')).toEqual('qwerty') - expect(getSession('session=qwerty;foo=bar')).toEqual('qwerty') - }) - - it('returns the value of the session cookie when there are multiple cookies separated by spaces (iOS Safari)', () => { - expect(getSession('foo=bar; session=qwerty')).toEqual('qwerty') - expect(getSession('session=qwerty; foo=bar')).toEqual('qwerty') - }) -}) - -describe('decryptSession()', () => { - it('returns an empty array if no session', () => { - expect(decryptSession()).toEqual([]) - }) - - it('returns an empty array if session is empty', () => { - expect(decryptSession('')).toEqual([]) - expect(decryptSession(' ')).toEqual([]) - }) - - it('throws an error if decryption errors out', () => { - expect(() => decryptSession('session=qwerty')).toThrow( - error.SessionDecryptionError - ) - }) - - it('returns an array with contents of encrypted cookie parts', () => { - const first = { foo: 'bar' } - const second = 'abcd' - const text = encrypt(JSON.stringify(first) + ';' + second) - - expect(decryptSession(text)).toEqual([first, second]) - }) -}) - -describe('dbAuthSession()', () => { - it('returns null if no cookies', () => { - const event = { headers: {} } - - expect(dbAuthSession(event)).toEqual(null) - }) - - it('return session given event', () => { - const first = { foo: 'bar' } - const second = 'abcd' - const text = encrypt(JSON.stringify(first) + ';' + second) - const event = { - headers: { - cookie: `session=${text}`, - }, - } - - expect(dbAuthSession(event)).toEqual(first) - }) -}) - -describe('webAuthnSession', () => { - it('returns null if no cookies', () => { - expect(webAuthnSession({ headers: {} })).toEqual(null) - }) - - it('returns the webAuthn cookie data', () => { - const output = webAuthnSession({ - headers: { cookie: 'session=abcd1234;webAuthn=zyxw9876' }, - }) - - expect(output).toEqual('zyxw9876') - }) -}) - -describe('hashPassword', () => { - it('hashes a password with a given salt and returns both', () => { - const [hash, salt] = hashPassword( - 'password', - '2ef27f4073c603ba8b7807c6de6d6a89' - ) - - expect(hash).toEqual( - '0c2b24e20ee76a887eac1415cc2c175ff961e7a0f057cead74789c43399dd5ba' - ) - expect(salt).toEqual('2ef27f4073c603ba8b7807c6de6d6a89') - }) - - it('hashes a password with a generated salt if none provided', () => { - const [hash, salt] = hashPassword('password') - - expect(hash).toMatch(/^[a-f0-9]+$/) - expect(hash.length).toEqual(64) - expect(salt).toMatch(/^[a-f0-9]+$/) - expect(salt.length).toEqual(32) - }) - - describe('session cookie extraction', () => { - let event - - const encryptToCookie = (data) => { - return `session=${CryptoJS.AES.encrypt(data, process.env.SESSION_SECRET)}` - } - - beforeEach(() => { - event = { - queryStringParameters: {}, - path: '/.redwood/functions/auth', - headers: {}, - } - }) - - it('extracts from the event', () => { - const cookie = encryptToCookie( - JSON.stringify({ id: 9999999999 }) + ';' + 'token' - ) - - event = { - headers: { - cookie, - }, - } - - expect(extractCookie(event)).toEqual(cookie) - }) - - it('extract cookie handles non-JSON event body', () => { - event.body = '' - - expect(extractCookie(event)).toBeUndefined() - }) - - describe('when in development', () => { - const curNodeEnv = process.env.NODE_ENV - - beforeAll(() => { - // Session cookie from graphiQLHeaders only extracted in dev - process.env.NODE_ENV = 'development' - }) - - afterAll(() => { - process.env.NODE_ENV = curNodeEnv - event = {} - expect(process.env.NODE_ENV).toBe('test') - }) - - it('extract cookie handles non-JSON event body', () => { - event.body = '' - - expect(extractCookie(event)).toBeUndefined() - }) - - it('extracts GraphiQL cookie from the header extensions', () => { - const dbUserId = 42 - - const cookie = encryptToCookie(JSON.stringify({ id: dbUserId })) - event.body = JSON.stringify({ - extensions: { - headers: { - 'auth-provider': 'dbAuth', - cookie, - authorization: 'Bearer ' + dbUserId, - }, - }, - }) - - expect(extractCookie(event)).toEqual(cookie) - }) - - it('overwrites cookie with event header GraphiQL when in dev', () => { - const sessionCookie = encryptToCookie( - JSON.stringify({ id: 9999999999 }) + ';' + 'token' - ) - - event = { - headers: { - cookie: sessionCookie, - }, - } - - const dbUserId = 42 - - const cookie = encryptToCookie(JSON.stringify({ id: dbUserId })) - event.body = JSON.stringify({ - extensions: { - headers: { - 'auth-provider': 'dbAuth', - cookie, - authorization: 'Bearer ' + dbUserId, - }, - }, - }) - - expect(extractCookie(event)).toEqual(cookie) - }) - }) - }) -}) diff --git a/packages/auth-providers/dbAuth/api/src/__tests__/shared.test.ts b/packages/auth-providers/dbAuth/api/src/__tests__/shared.test.ts new file mode 100644 index 000000000000..67b1becccc60 --- /dev/null +++ b/packages/auth-providers/dbAuth/api/src/__tests__/shared.test.ts @@ -0,0 +1,383 @@ +import crypto from 'node:crypto' +import path from 'node:path' + +import type { APIGatewayProxyEvent } from 'aws-lambda' + +import * as error from '../errors' +import { + extractCookie, + getSession, + cookieName, + hashPassword, + isLegacySession, + legacyHashPassword, + decryptSession, + dbAuthSession, + webAuthnSession, + extractHashingOptions, +} from '../shared' + +const FIXTURE_PATH = path.resolve( + __dirname, + '../../../../../../__fixtures__/example-todo-main' +) +const SESSION_SECRET = '540d03ebb00b441f8f7442cbc39958ad' + +const encrypt = (data) => { + const iv = crypto.randomBytes(16) + const cipher = crypto.createCipheriv( + 'aes-256-cbc', + SESSION_SECRET.substring(0, 32), + iv + ) + let encryptedSession = cipher.update(data, 'utf-8', 'base64') + encryptedSession += cipher.final('base64') + + return `${encryptedSession}|${iv.toString('base64')}` +} + +function dummyEvent(cookie?: string) { + return { + headers: { + cookie, + }, + } as unknown as APIGatewayProxyEvent +} + +beforeAll(() => { + process.env.RWJS_CWD = FIXTURE_PATH +}) + +afterAll(() => { + delete process.env.RWJS_CWD +}) + +describe('getSession()', () => { + it('returns null if no text', () => { + expect(getSession(undefined, 'session')).toEqual(null) + }) + + it('returns null if no session cookie', () => { + expect(getSession('foo=bar', 'session')).toEqual(null) + }) + + it('returns the value of the session cookie', () => { + expect(getSession('session_8911=qwerty', 'session_%port%')).toEqual( + 'qwerty' + ) + }) + + it('returns the value of the session cookie when there are multiple cookies', () => { + expect(getSession('foo=bar;session=qwerty', 'session')).toEqual('qwerty') + expect(getSession('session=qwerty;foo=bar', 'session')).toEqual('qwerty') + }) + + it('returns the value of the session cookie when there are multiple cookies separated by spaces (iOS Safari)', () => { + expect(getSession('foo=bar; session=qwerty', 'session')).toEqual('qwerty') + expect(getSession('session=qwerty; foo=bar', 'session')).toEqual('qwerty') + }) +}) + +describe('cookieName()', () => { + it('returns the default cookie name', () => { + expect(cookieName(undefined)).toEqual('session') + }) + + it('allows you to pass a cookie name to use', () => { + expect(cookieName('my_cookie_name')).toEqual('my_cookie_name') + }) + + it('replaces %port% with a port number', () => { + expect(cookieName('session_%port%_my_app')).toEqual('session_8911_my_app') + }) +}) + +describe('isLegacySession()', () => { + it('returns `true` if the session cookie appears to be encrypted with CryptoJS', () => { + expect( + isLegacySession('U2FsdGVkX1+s7seQJnVgGgInxuXm13l8VvzA3Mg2fYg=') + ).toEqual(true) + }) + + it('returns `false` if the session cookie appears to be encrypted with node:crypto', () => { + expect( + isLegacySession( + 'ko6iXKV11DSjb6kFJ4iwcf1FEqa5wPpbL1sdtKiV51Y=|cQaYkOPG/r3ILxWiFiz90w==' + ) + ).toEqual(false) + }) +}) + +describe('decryptSession()', () => { + beforeEach(() => { + process.env.SESSION_SECRET = SESSION_SECRET + }) + + it('returns an empty array if no session', () => { + expect(decryptSession(null)).toEqual([]) + }) + + it('returns an empty array if session is empty', () => { + expect(decryptSession('')).toEqual([]) + expect(decryptSession(' ')).toEqual([]) + }) + + it('throws an error if decryption errors out', () => { + expect(() => decryptSession('session=qwerty')).toThrow( + error.SessionDecryptionError + ) + }) + + it('returns an array with contents of encrypted cookie parts', () => { + const first = { foo: 'bar' } + const second = 'abcd' + const text = encrypt(JSON.stringify(first) + ';' + second) + + expect(decryptSession(text)).toEqual([first, second]) + }) + + it('decrypts a session cookie that was created with the legacy CryptoJS algorithm', () => { + process.env.SESSION_SECRET = + 'QKxN2vFSHAf94XYynK8LUALfDuDSdFowG6evfkFX8uszh4YZqhTiqEdshrhWbwbw' + const [json] = decryptSession( + 'U2FsdGVkX1+s7seQJnVgGgInxuXm13l8VvzA3Mg2fYg=' + ) + + expect(json).toEqual({ id: 7 }) + }) +}) + +describe('dbAuthSession()', () => { + beforeEach(() => { + process.env.SESSION_SECRET = SESSION_SECRET + }) + + it('returns null if no cookies', () => { + expect(dbAuthSession(dummyEvent(), 'session_%port%')).toEqual(null) + }) + + it('return session given event', () => { + const first = { foo: 'bar' } + const second = 'abcd' + const text = encrypt(JSON.stringify(first) + ';' + second) + const event = dummyEvent(`session_8911=${text}`) + + expect(dbAuthSession(event, 'session_%port%')).toEqual(first) + }) +}) + +describe('webAuthnSession', () => { + it('returns null if no cookies', () => { + expect(webAuthnSession(dummyEvent())).toEqual(null) + }) + + it('returns the webAuthn cookie data', () => { + const output = webAuthnSession( + dummyEvent('session=abcd1234;webAuthn=zyxw9876') + ) + + expect(output).toEqual('zyxw9876') + }) +}) + +describe('hashPassword', () => { + it('hashes a password with a given salt and returns both', () => { + const [hash, salt] = hashPassword('password', { + salt: 'ba8b7807c6de6d6a892ef27f4073c603', + }) + + expect(hash).toEqual( + '230847bea5154b6c7d281d09593ad1be26fa03a93c04a73bcc2b608c073a8213|16384|8|1' + ) + expect(salt).toEqual('ba8b7807c6de6d6a892ef27f4073c603') + }) + + it('hashes a password with a generated salt if none provided', () => { + const [hash, salt] = hashPassword('password') + + expect(hash).toMatch(/^[a-f0-9]+|16384|8|1$/) + expect(hash.length).toEqual(74) + expect(salt).toMatch(/^[a-f0-9]+$/) + expect(salt.length).toEqual(64) + }) + + it('normalizes strings so utf-8 variants hash to the same output', () => { + const salt = crypto.randomBytes(32).toString('hex') + const [hash1] = hashPassword('\u0041\u006d\u00e9\u006c\u0069\u0065', { + salt, + }) // Amélie + const [hash2] = hashPassword('\u0041\u006d\u0065\u0301\u006c\u0069\u0065', { + salt, + }) // Amélie but separate e and accent codepoints + + expect(hash1).toEqual(hash2) + }) + + it('encodes the scrypt difficulty options into the hash', () => { + const [hash] = hashPassword('password', { + options: { cost: 8192, blockSize: 16, parallelization: 2 }, + }) + const [_hash, cost, blockSize, parallelization] = hash.split('|') + + expect(cost).toEqual('8192') + expect(blockSize).toEqual('16') + expect(parallelization).toEqual('2') + }) +}) + +describe('legacyHashPassword', () => { + it('hashes a password with CryptoJS given a salt and returns both', () => { + const [hash, salt] = legacyHashPassword( + 'password', + '2ef27f4073c603ba8b7807c6de6d6a89' + ) + + expect(hash).toEqual( + '0c2b24e20ee76a887eac1415cc2c175ff961e7a0f057cead74789c43399dd5ba' + ) + expect(salt).toEqual('2ef27f4073c603ba8b7807c6de6d6a89') + }) + + it('hashes a password with a generated salt if none provided', () => { + const [hash, salt] = legacyHashPassword('password') + + expect(hash).toMatch(/^[a-f0-9]+$/) + expect(hash.length).toEqual(64) + expect(salt).toMatch(/^[a-f0-9]+$/) + expect(salt.length).toEqual(64) + }) +}) + +describe('session cookie extraction', () => { + let event + + const encryptToCookie = (data) => { + return `session=${encrypt(data)}` + } + + beforeEach(() => { + event = { + queryStringParameters: {}, + path: '/.redwood/functions/auth', + headers: {}, + } + }) + + it('extracts from the event', () => { + const cookie = encryptToCookie( + JSON.stringify({ id: 9999999999 }) + ';' + 'token' + ) + + event = { + headers: { + cookie, + }, + } + + expect(extractCookie(event)).toEqual(cookie) + }) + + it('extract cookie handles non-JSON event body', () => { + event.body = '' + + expect(extractCookie(event)).toBeUndefined() + }) + + describe('when in development', () => { + const curNodeEnv = process.env.NODE_ENV + + beforeAll(() => { + // Session cookie from graphiQLHeaders only extracted in dev + process.env.NODE_ENV = 'development' + }) + + afterAll(() => { + process.env.NODE_ENV = curNodeEnv + event = {} + expect(process.env.NODE_ENV).toBe('test') + }) + + it('extract cookie handles non-JSON event body', () => { + event.body = '' + + expect(extractCookie(event)).toBeUndefined() + }) + + it('extracts GraphiQL cookie from the header extensions', () => { + const dbUserId = 42 + + const cookie = encryptToCookie(JSON.stringify({ id: dbUserId })) + event.body = JSON.stringify({ + extensions: { + headers: { + 'auth-provider': 'dbAuth', + cookie, + authorization: 'Bearer ' + dbUserId, + }, + }, + }) + + expect(extractCookie(event)).toEqual(cookie) + }) + + it('overwrites cookie with event header GraphiQL when in dev', () => { + const sessionCookie = encryptToCookie( + JSON.stringify({ id: 9999999999 }) + ';' + 'token' + ) + + event = { + headers: { + cookie: sessionCookie, + }, + } + + const dbUserId = 42 + + const cookie = encryptToCookie(JSON.stringify({ id: dbUserId })) + event.body = JSON.stringify({ + extensions: { + headers: { + 'auth-provider': 'dbAuth', + cookie, + authorization: 'Bearer ' + dbUserId, + }, + }, + }) + + expect(extractCookie(event)).toEqual(cookie) + }) + }) +}) + +describe('extractHashingOptions()', () => { + it('returns an empty object if no options', () => { + expect(extractHashingOptions('')).toEqual({}) + expect( + extractHashingOptions( + '0c2b24e20ee76a887eac1415cc2c175ff961e7a0f057cead74789c43399dd5ba' + ) + ).toEqual({}) + expect( + extractHashingOptions( + '0c2b24e20ee76a887eac1415cc2c175ff961e7a0f057cead74789c43399dd5ba|1' + ) + ).toEqual({}) + expect( + extractHashingOptions( + '0c2b24e20ee76a887eac1415cc2c175ff961e7a0f057cead74789c43399dd5ba|1|2' + ) + ).toEqual({}) + }) + + it('returns an object with scrypt options', () => { + expect( + extractHashingOptions( + '0c2b24e20ee76a887eac1415cc2c175ff961e7a0f057cead74789c43399dd5ba|16384|8|1' + ) + ).toEqual({ + cost: 16384, + blockSize: 8, + parallelization: 1, + }) + }) +}) diff --git a/packages/auth-providers/dbAuth/api/src/decoder.ts b/packages/auth-providers/dbAuth/api/src/decoder.ts index 5c6c873e96df..0657a2b622fa 100644 --- a/packages/auth-providers/dbAuth/api/src/decoder.ts +++ b/packages/auth-providers/dbAuth/api/src/decoder.ts @@ -1,9 +1,28 @@ import type { APIGatewayProxyEvent } from 'aws-lambda' -import { Decoder } from '@redwoodjs/api' +import type { Decoder } from '@redwoodjs/api' import { dbAuthSession } from './shared' +export const createAuthDecoder = (cookieNameOption: string): Decoder => { + return async (token, type, req) => { + if (type !== 'dbAuth') { + return null + } + + const session = dbAuthSession(req.event, cookieNameOption) + const authHeaderUserId = token + + if (session.id.toString() !== authHeaderUserId) { + console.error('Authorization header does not match decrypted user ID') + throw new Error('Authorization header does not match decrypted user ID') + } + + return session + } +} + +/** @deprecated use `createAuthDecoder` */ export const authDecoder: Decoder = async ( authHeaderValue: string, type: string, @@ -13,10 +32,14 @@ export const authDecoder: Decoder = async ( return null } - const session = dbAuthSession(req.event) + // Passing `undefined` as the second argument to `dbAuthSession` will make + // it fall back to the default cookie name `session`, making it backwards + // compatible with existing RW apps. + const session = dbAuthSession(req.event, undefined) const authHeaderUserId = authHeaderValue if (session.id.toString() !== authHeaderUserId) { + console.error('Authorization header does not match decrypted user ID') throw new Error('Authorization header does not match decrypted user ID') } diff --git a/packages/auth-providers/dbAuth/api/src/index.ts b/packages/auth-providers/dbAuth/api/src/index.ts index 129130bbef63..3ec37c609237 100644 --- a/packages/auth-providers/dbAuth/api/src/index.ts +++ b/packages/auth-providers/dbAuth/api/src/index.ts @@ -1,4 +1,4 @@ export * from './DbAuthHandler' export { PasswordValidationError } from './errors' export * from './shared' -export { authDecoder } from './decoder' +export { authDecoder, createAuthDecoder } from './decoder' diff --git a/packages/auth-providers/dbAuth/api/src/shared.ts b/packages/auth-providers/dbAuth/api/src/shared.ts index 1a005f4c4e59..78cf1db0de46 100644 --- a/packages/auth-providers/dbAuth/api/src/shared.ts +++ b/packages/auth-providers/dbAuth/api/src/shared.ts @@ -1,13 +1,45 @@ +import crypto from 'node:crypto' + import type { APIGatewayProxyEvent } from 'aws-lambda' -import CryptoJS from 'crypto-js' + +import { getConfig, getConfigPath } from '@redwoodjs/project-config' import * as DbAuthError from './errors' +type ScryptOptions = { + cost?: number + blockSize?: number + parallelization?: number + N?: number + r?: number + p?: number + maxmem?: number +} + +const DEFAULT_SCRYPT_OPTIONS: ScryptOptions = { + cost: 2 ** 14, + blockSize: 8, + parallelization: 1, +} + // Extracts the cookie from an event, handling lower and upper case header names. const eventHeadersCookie = (event: APIGatewayProxyEvent) => { return event.headers.cookie || event.headers.Cookie } +const getPort = () => { + let configPath + + try { + configPath = getConfigPath() + } catch { + // If this throws, we're in a serverless environment, and the `redwood.toml` file doesn't exist. + return 8911 + } + + return getConfig(configPath).api.port +} + // When in development environment, check for cookie in the request extension headers // if user has generated graphiql headers const eventGraphiQLHeadersCookie = (event: APIGatewayProxyEvent) => { @@ -27,23 +59,71 @@ const eventGraphiQLHeadersCookie = (event: APIGatewayProxyEvent) => { return } +// decrypts session text using old CryptoJS algorithm (using node:crypto library) +const legacyDecryptSession = (encryptedText: string) => { + const cypher = Buffer.from(encryptedText, 'base64') + const salt = cypher.slice(8, 16) + const password = Buffer.concat([ + Buffer.from(process.env.SESSION_SECRET as string, 'binary'), + salt, + ]) + const md5Hashes = [] + let digest = password + for (let i = 0; i < 3; i++) { + md5Hashes[i] = crypto.createHash('md5').update(digest).digest() + digest = Buffer.concat([md5Hashes[i], password]) + } + const key = Buffer.concat([md5Hashes[0], md5Hashes[1]]) + const iv = md5Hashes[2] + const contents = cypher.slice(16) + const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv) + + return decipher.update(contents) + decipher.final('utf-8') +} + // Extracts the session cookie from an event, handling both // development environment GraphiQL headers and production environment headers. export const extractCookie = (event: APIGatewayProxyEvent) => { return eventGraphiQLHeadersCookie(event) || eventHeadersCookie(event) } +// whether this encrypted session was made with the old CryptoJS algorithm +export const isLegacySession = (text: string | undefined) => { + if (!text) { + return false + } + + const [_encryptedText, iv] = text.split('|') + return !iv +} + // decrypts the session cookie and returns an array: [data, csrf] export const decryptSession = (text: string | null) => { if (!text || text.trim() === '') { return [] } + let decoded + // if cookie contains a pipe then it was encrypted using the `node:crypto` + // algorithm (first element is the ecrypted data, second is the initialization vector) + // otherwise fall back to using the older CryptoJS algorithm + const [encryptedText, iv] = text.split('|') + try { - const decoded = CryptoJS.AES.decrypt( - text, - process.env.SESSION_SECRET as string - ).toString(CryptoJS.enc.Utf8) + if (iv) { + // decrypt using the `node:crypto` algorithm + const decipher = crypto.createDecipheriv( + 'aes-256-cbc', + (process.env.SESSION_SECRET as string).substring(0, 32), + Buffer.from(iv, 'base64') + ) + decoded = + decipher.update(encryptedText, 'base64', 'utf-8') + + decipher.final('utf-8') + } else { + decoded = legacyDecryptSession(text) + } + const [data, csrf] = decoded.split(';') const json = JSON.parse(data) @@ -53,30 +133,50 @@ export const decryptSession = (text: string | null) => { } } +export const encryptSession = (dataString: string) => { + const iv = crypto.randomBytes(16) + const cipher = crypto.createCipheriv( + 'aes-256-cbc', + (process.env.SESSION_SECRET as string).substring(0, 32), + iv + ) + let encryptedData = cipher.update(dataString, 'utf-8', 'base64') + encryptedData += cipher.final('base64') + + return `${encryptedData}|${iv.toString('base64')}` +} + // returns the actual value of the session cookie -export const getSession = (text?: string) => { +export const getSession = ( + text: string | undefined, + cookieNameOption: string | undefined +) => { if (typeof text === 'undefined' || text === null) { return null } const cookies = text.split(';') - const sessionCookie = cookies.find((cook) => { - return cook.split('=')[0].trim() === 'session' + const sessionCookie = cookies.find((cookie) => { + return cookie.split('=')[0].trim() === cookieName(cookieNameOption) }) - if (!sessionCookie || sessionCookie === 'session=') { + if (!sessionCookie || sessionCookie === `${cookieName(cookieNameOption)}=`) { return null } - return sessionCookie.split('=')[1].trim() + return sessionCookie.replace(`${cookieName(cookieNameOption)}=`, '').trim() } // Convenience function to get session, decrypt, and return session data all -// at once. Accepts the `event` argument from a Lambda function call. -export const dbAuthSession = (event: APIGatewayProxyEvent) => { +// at once. Accepts the `event` argument from a Lambda function call and the +// name of the dbAuth session cookie +export const dbAuthSession = ( + event: APIGatewayProxyEvent, + cookieNameOption: string | undefined +) => { if (extractCookie(event)) { const [session, _csrfToken] = decryptSession( - getSession(extractCookie(event)) + getSession(extractCookie(event), cookieNameOption) ) return session } else { @@ -100,13 +200,59 @@ export const webAuthnSession = (event: APIGatewayProxyEvent) => { return webAuthnCookie.split('=')[1].trim() } +export const hashToken = (token: string) => { + return crypto.createHash('sha256').update(token).digest('hex') +} + // hashes a password using either the given `salt` argument, or creates a new // salt and hashes using that. Either way, returns an array with [hash, salt] -export const hashPassword = (text: string, salt?: string) => { - const useSalt = salt || CryptoJS.lib.WordArray.random(128 / 8).toString() +// normalizes the string in case it contains unicode characters: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize +// TODO: Add validation that the options are valid values for the scrypt algorithm +export const hashPassword = ( + text: string, + { + salt = crypto.randomBytes(32).toString('hex'), + options = DEFAULT_SCRYPT_OPTIONS, + }: { salt?: string; options?: ScryptOptions } = {} +) => { + const encryptedString = crypto + .scryptSync(text.normalize('NFC'), salt, 32, options) + .toString('hex') + const optionsToString = [ + options.cost, + options.blockSize, + options.parallelization, + ] + return [`${encryptedString}|${optionsToString.join('|')}`, salt] +} +// uses the old algorithm from CryptoJS: +// CryptoJS.PBKDF2(password, salt, { keySize: 8 }).toString() +export const legacyHashPassword = (text: string, salt?: string) => { + const useSalt = salt || crypto.randomBytes(32).toString('hex') return [ - CryptoJS.PBKDF2(text, useSalt, { keySize: 256 / 32 }).toString(), + crypto.pbkdf2Sync(text, useSalt, 1, 32, 'SHA1').toString('hex'), useSalt, ] } + +export const cookieName = (name: string | undefined) => { + const port = getPort() + const cookieName = name?.replace('%port%', '' + port) ?? 'session' + + return cookieName +} + +export const extractHashingOptions = (text: string): ScryptOptions => { + const [_hash, ...options] = text.split('|') + + if (options.length === 3) { + return { + cost: parseInt(options[0]), + blockSize: parseInt(options[1]), + parallelization: parseInt(options[2]), + } + } else { + return {} + } +} diff --git a/packages/auth-providers/dbAuth/api/tsconfig.json b/packages/auth-providers/dbAuth/api/tsconfig.json index a925964c5e44..de5a32a0ae66 100644 --- a/packages/auth-providers/dbAuth/api/tsconfig.json +++ b/packages/auth-providers/dbAuth/api/tsconfig.json @@ -4,9 +4,11 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], - "references": [{ "path": "../../../auth" }] + "references": [ + { "path": "../../../auth" }, + { "path": "../../../project-config" } + ] } diff --git a/packages/auth-providers/dbAuth/setup/README.md b/packages/auth-providers/dbAuth/setup/README.md index 661208df586e..cba1dea094ac 100644 --- a/packages/auth-providers/dbAuth/setup/README.md +++ b/packages/auth-providers/dbAuth/setup/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/dbAuth/setup/package.json b/packages/auth-providers/dbAuth/setup/package.json index 9934c0207a2c..2d9679fc8f4a 100644 --- a/packages/auth-providers/dbAuth/setup/package.json +++ b/packages/auth-providers/dbAuth/setup/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-dbauth-setup", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,30 +14,28 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src --passWithNoTests", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@redwoodjs/cli-helpers": "4.0.0", - "@simplewebauthn/browser": "7.2.0", - "core-js": "3.29.1", + "@babel/runtime-corejs3": "7.23.6", + "@redwoodjs/cli-helpers": "6.0.7", + "@simplewebauthn/browser": "7.4.0", + "core-js": "3.34.0", "prompts": "2.4.2", - "secure-random-password": "0.2.3", "terminal-link": "2.1.1" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@simplewebauthn/typescript-types": "7.0.0", - "@types/secure-random-password": "0.2.1", - "@types/yargs": "17.0.24", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@simplewebauthn/typescript-types": "7.4.0", + "@types/yargs": "17.0.32", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/dbAuth/setup/src/setup.ts b/packages/auth-providers/dbAuth/setup/src/setup.ts index d04c4e4b0594..b43454f10436 100644 --- a/packages/auth-providers/dbAuth/setup/src/setup.ts +++ b/packages/auth-providers/dbAuth/setup/src/setup.ts @@ -1,5 +1,5 @@ import terminalLink from 'terminal-link' -import yargs from 'yargs' +import type yargs from 'yargs' export const command = 'dbAuth' export const description = 'Set up auth for for dbAuth' @@ -32,6 +32,6 @@ export interface Args { } export const handler = async (options: Args) => { - const { handler } = await import('./setupHandler') + const { handler } = await import('./setupHandler.js') return handler(options) } diff --git a/packages/auth-providers/dbAuth/setup/src/setupData.ts b/packages/auth-providers/dbAuth/setup/src/setupData.ts index 284ee1548927..82bdf2d88dab 100644 --- a/packages/auth-providers/dbAuth/setup/src/setupData.ts +++ b/packages/auth-providers/dbAuth/setup/src/setupData.ts @@ -1,7 +1,6 @@ +import crypto from 'node:crypto' import path from 'path' -import password from 'secure-random-password' - import { getPaths, colors, addEnvVarTask } from '@redwoodjs/cli-helpers' export const libPath = getPaths().api.lib.replace(getPaths().base, '') @@ -10,10 +9,7 @@ export const functionsPath = getPaths().api.functions.replace( '' ) -const secret = password.randomPassword({ - length: 64, - characters: [password.lower, password.upper, password.digits], -}) +const secret = crypto.randomBytes(32).toString('base64') export const extraTask = addEnvVarTask( 'SESSION_SECRET', diff --git a/packages/auth-providers/dbAuth/setup/src/setupHandler.ts b/packages/auth-providers/dbAuth/setup/src/setupHandler.ts index a82226b32b82..813cb23f83bd 100644 --- a/packages/auth-providers/dbAuth/setup/src/setupHandler.ts +++ b/packages/auth-providers/dbAuth/setup/src/setupHandler.ts @@ -3,9 +3,9 @@ import path from 'path' import prompts from 'prompts' -import { standardAuthHandler } from '@redwoodjs/cli-helpers' +import { getGraphqlPath, standardAuthHandler } from '@redwoodjs/cli-helpers' -import { Args } from './setup' +import type { Args } from './setup' import { notes, extraTask } from './setupData' import { notes as webAuthnNotes, @@ -26,7 +26,7 @@ export async function handler({ webauthn, force: forceArg }: Args) { forceArg, provider: 'dbAuth', authDecoderImport: - "import { authDecoder } from '@redwoodjs/auth-dbauth-api'", + "import { createAuthDecoder } from '@redwoodjs/auth-dbauth-api'", webAuthn, webPackages: [ `@redwoodjs/auth-dbauth-web@${version}`, @@ -36,7 +36,10 @@ export async function handler({ webauthn, force: forceArg }: Args) { `@redwoodjs/auth-dbauth-api@${version}`, ...(webAuthn ? webAuthnApiPackages : []), ], - extraTask: webAuthn ? webAuthnExtraTask : extraTask, + extraTasks: [ + webAuthn ? webAuthnExtraTask : extraTask, + createAuthDecoderFunction, + ], notes: webAuthn ? webAuthnNotes : notes, }) } @@ -59,3 +62,33 @@ async function shouldIncludeWebAuthn(webauthn: boolean) { return webauthn } + +export const createAuthDecoderFunction = { + title: 'Create auth decoder function', + task: () => { + const graphqlPath = getGraphqlPath() + + if (!graphqlPath) { + throw new Error('Could not find your graphql file path') + } + + const content = fs.readFileSync(graphqlPath, 'utf-8') + + const newContent = content + .replace( + 'import { getCurrentUser } from', + 'import { cookieName, getCurrentUser } from' + ) + .replace( + 'export const handler = createGraphQLHandler({', + 'const authDecoder = createAuthDecoder(cookieName)\n\n' + + 'export const handler = createGraphQLHandler({' + ) + + if (!newContent.includes('import { cookieName')) { + throw new Error('Failed to import cookieName') + } + + fs.writeFileSync(graphqlPath, newContent) + }, +} diff --git a/packages/auth-providers/dbAuth/setup/src/templates/api/functions/auth.ts.template b/packages/auth-providers/dbAuth/setup/src/templates/api/functions/auth.ts.template index f8d8e2cd4e70..20fde6db8f29 100644 --- a/packages/auth-providers/dbAuth/setup/src/templates/api/functions/auth.ts.template +++ b/packages/auth-providers/dbAuth/setup/src/templates/api/functions/auth.ts.template @@ -1,7 +1,9 @@ import type { APIGatewayProxyEvent, Context } from 'aws-lambda' -import { DbAuthHandler, DbAuthHandlerOptions } from '@redwoodjs/auth-dbauth-api' +import { DbAuthHandler } from '@redwoodjs/auth-dbauth-api' +import type { DbAuthHandlerOptions, UserType } from '@redwoodjs/auth-dbauth-api' +import { cookieName } from 'src/lib/auth' import { db } from 'src/lib/db' export const handler = async ( @@ -17,11 +19,20 @@ export const handler = async ( // https://example.com/reset-password?resetToken=${user.resetToken} // // Whatever is returned from this function will be returned from - // the `forgotPassword()` function that is destructured from `useAuth()` + // the `forgotPassword()` function that is destructured from `useAuth()`. // You could use this return value to, for example, show the email // address in a toast message so the user will know it worked and where // to look for the email. - handler: (user) => { + // + // Note that this return value is sent to the client in *plain text* + // so don't include anything you wouldn't want prying eyes to see. The + // `user` here has been sanitized to only include the fields listed in + // `allowedUserFields` so it should be safe to return as-is. + handler: (user, _resetToken) => { + // TODO: Send user an email/message with a link to reset their password, + // including the `resetToken`. The URL should look something like: + // `http://localhost:8910/reset-password?resetToken=${resetToken}` + return user }, @@ -91,7 +102,14 @@ export const handler = async ( }, } - const signupOptions: DbAuthHandlerOptions['signup'] = { + interface UserAttributes { + name: string + } + + const signupOptions: DbAuthHandlerOptions< + UserType, + UserAttributes + >['signup'] = { // Whatever you want to happen to your data on new user signup. Redwood will // check for duplicate usernames before calling this handler. At a minimum // you need to save the `username`, `hashedPassword` and `salt` to your @@ -107,7 +125,12 @@ export const handler = async ( // // If this returns anything else, it will be returned by the // `signUp()` function in the form of: `{ message: 'String here' }`. - handler: ({ username, hashedPassword, salt, userAttributes }) => { + handler: ({ + username, + hashedPassword, + salt, + userAttributes: _userAttributes, + }) => { return db.user.create({ data: { email: username, @@ -152,17 +175,26 @@ export const handler = async ( resetTokenExpiresAt: 'resetTokenExpiresAt', }, + // A list of fields on your user object that are safe to return to the + // client when invoking a handler that returns a user (like forgotPassword + // and signup). This list should be as small as possible to be sure not to + // leak any sensitive information to the client. + allowedUserFields = ['id', 'email'], + // Specifies attributes on the cookie that dbAuth sets in order to remember // who is logged in. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies cookie: { - HttpOnly: true, - Path: '/', - SameSite: 'Strict', - Secure: process.env.NODE_ENV !== 'development', - - // If you need to allow other domains (besides the api side) access to - // the dbAuth session cookie: - // Domain: 'example.com', + attributes: { + HttpOnly: true, + Path: '/', + SameSite: 'Strict', + Secure: process.env.NODE_ENV !== 'development', + + // If you need to allow other domains (besides the api side) access to + // the dbAuth session cookie: + // Domain: 'example.com', + }, + name: cookieName, }, forgotPassword: forgotPasswordOptions, diff --git a/packages/auth-providers/dbAuth/setup/src/templates/api/functions/auth.webAuthn.ts.template b/packages/auth-providers/dbAuth/setup/src/templates/api/functions/auth.webAuthn.ts.template index 117a85726ec9..dd83f56d188a 100644 --- a/packages/auth-providers/dbAuth/setup/src/templates/api/functions/auth.webAuthn.ts.template +++ b/packages/auth-providers/dbAuth/setup/src/templates/api/functions/auth.webAuthn.ts.template @@ -1,7 +1,9 @@ import type { APIGatewayProxyEvent, Context } from 'aws-lambda' -import { DbAuthHandler, DbAuthHandlerOptions } from '@redwoodjs/auth-dbauth-api' +import { DbAuthHandler } from '@redwoodjs/auth-dbauth-api' +import type { DbAuthHandlerOptions, UserType } from '@redwoodjs/auth-dbauth-api' +import { cookieName } from 'src/lib/auth' import { db } from 'src/lib/db' export const handler = async ( @@ -91,7 +93,14 @@ export const handler = async ( }, } - const signupOptions: DbAuthHandlerOptions['signup'] = { + interface UserAttributes { + name: string + } + + const signupOptions: DbAuthHandlerOptions< + UserType, + UserAttributes + >['signup'] = { // Whatever you want to happen to your data on new user signup. Redwood will // check for duplicate usernames before calling this handler. At a minimum // you need to save the `username`, `hashedPassword` and `salt` to your @@ -107,7 +116,12 @@ export const handler = async ( // // If this returns anything else, it will be returned by the // `signUp()` function in the form of: `{ message: 'String here' }`. - handler: ({ username, hashedPassword, salt, userAttributes }) => { + handler: ({ + username, + hashedPassword, + salt, + userAttributes: _userAttributes, + }) => { return db.user.create({ data: { email: username, @@ -160,14 +174,17 @@ export const handler = async ( // Specifies attributes on the cookie that dbAuth sets in order to remember // who is logged in. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies cookie: { - HttpOnly: true, - Path: '/', - SameSite: 'Strict', - Secure: process.env.NODE_ENV !== 'development' ? true : false, - - // If you need to allow other domains (besides the api side) access to - // the dbAuth session cookie: - // Domain: 'example.com', + attributes: { + HttpOnly: true, + Path: '/', + SameSite: 'Strict', + Secure: process.env.NODE_ENV !== 'development' ? true : false, + + // If you need to allow other domains (besides the api side) access to + // the dbAuth session cookie: + // Domain: 'example.com', + }, + name: cookieName, }, forgotPassword: forgotPasswordOptions, diff --git a/packages/auth-providers/dbAuth/setup/src/templates/api/lib/auth.ts.template b/packages/auth-providers/dbAuth/setup/src/templates/api/lib/auth.ts.template index f318095f1aec..e212e348164f 100644 --- a/packages/auth-providers/dbAuth/setup/src/templates/api/lib/auth.ts.template +++ b/packages/auth-providers/dbAuth/setup/src/templates/api/lib/auth.ts.template @@ -3,6 +3,15 @@ import { AuthenticationError, ForbiddenError } from '@redwoodjs/graphql-server' import { db } from './db' +/** + * The name of the cookie that dbAuth sets + * + * %port% will be replaced with the port the api server is running on. + * If you have multiple RW apps running on the same host, you'll need to + * make sure they all use unique cookie names + */ +export const cookieName = 'session_%port%' + /** * The session object sent in as the first argument to getCurrentUser() will * have a single key `id` containing the unique ID of the logged in user diff --git a/packages/auth-providers/dbAuth/setup/tsconfig.json b/packages/auth-providers/dbAuth/setup/tsconfig.json index abb54b70112a..5be9707b2f05 100644 --- a/packages/auth-providers/dbAuth/setup/tsconfig.json +++ b/packages/auth-providers/dbAuth/setup/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/dbAuth/web/README.md b/packages/auth-providers/dbAuth/web/README.md index 661208df586e..cba1dea094ac 100644 --- a/packages/auth-providers/dbAuth/web/README.md +++ b/packages/auth-providers/dbAuth/web/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/dbAuth/web/package.json b/packages/auth-providers/dbAuth/web/package.json index c205e47c30f9..8e84d6804803 100644 --- a/packages/auth-providers/dbAuth/web/package.json +++ b/packages/auth-providers/dbAuth/web/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-dbauth-web", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -15,27 +15,27 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@redwoodjs/auth": "4.0.0", - "@simplewebauthn/browser": "7.2.0", - "core-js": "3.29.1" + "@babel/runtime-corejs3": "7.23.6", + "@redwoodjs/auth": "6.0.7", + "@simplewebauthn/browser": "7.4.0", + "core-js": "3.34.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@simplewebauthn/typescript-types": "7.0.0", - "@types/react": "18.0.31", - "jest": "29.5.0", - "react": "18.2.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@simplewebauthn/typescript-types": "7.4.0", + "@types/react": "18.2.37", + "jest": "29.7.0", + "react": "0.0.0-experimental-e5205658f-20230913", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/dbAuth/web/src/__tests__/dbAuth.test.ts b/packages/auth-providers/dbAuth/web/src/__tests__/dbAuth.test.ts index 9cb039ec243e..f6aa2f01b350 100644 --- a/packages/auth-providers/dbAuth/web/src/__tests__/dbAuth.test.ts +++ b/packages/auth-providers/dbAuth/web/src/__tests__/dbAuth.test.ts @@ -1,8 +1,9 @@ import { renderHook, act } from '@testing-library/react' -import { CurrentUser } from '@redwoodjs/auth' +import type { CurrentUser } from '@redwoodjs/auth' -import { createDbAuthClient, DbAuthClientArgs, createAuth } from '../dbAuth' +import type { DbAuthClientArgs } from '../dbAuth' +import { createDbAuthClient, createAuth } from '../dbAuth' globalThis.RWJS_API_URL = '/.redwood/functions' globalThis.RWJS_API_GRAPHQL_URL = '/.redwood/functions/graphql' diff --git a/packages/auth-providers/dbAuth/web/src/dbAuth.ts b/packages/auth-providers/dbAuth/web/src/dbAuth.ts index f778b8420ac3..4f53ebdd026f 100644 --- a/packages/auth-providers/dbAuth/web/src/dbAuth.ts +++ b/packages/auth-providers/dbAuth/web/src/dbAuth.ts @@ -1,6 +1,7 @@ -import { createAuthentication, CurrentUser } from '@redwoodjs/auth' +import type { CurrentUser } from '@redwoodjs/auth' +import { createAuthentication } from '@redwoodjs/auth' -import { WebAuthnClientType } from './webAuthn' +import type { WebAuthnClientType } from './webAuthn' export interface LoginAttributes { username: string @@ -92,11 +93,15 @@ export function createDbAuthClient({ .then((response) => response.text()) .then((tokenText) => { lastTokenCheckAt = new Date() - getTokenPromise = null cachedToken = tokenText.length === 0 ? null : tokenText - return cachedToken }) + .catch(() => { + return null + }) + .finally(() => { + getTokenPromise = null + }) return getTokenPromise } diff --git a/packages/auth-providers/dbAuth/web/tsconfig.json b/packages/auth-providers/dbAuth/web/tsconfig.json index 62646e80bdee..3221c1501034 100644 --- a/packages/auth-providers/dbAuth/web/tsconfig.json +++ b/packages/auth-providers/dbAuth/web/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src", "ambient.d.ts"], diff --git a/packages/auth-providers/firebase/api/README.md b/packages/auth-providers/firebase/api/README.md index d27ea3626dac..10ba2ea0959a 100644 --- a/packages/auth-providers/firebase/api/README.md +++ b/packages/auth-providers/firebase/api/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/firebase/api/package.json b/packages/auth-providers/firebase/api/package.json index f271e5cd8276..32166dd06ef0 100644 --- a/packages/auth-providers/firebase/api/package.json +++ b/packages/auth-providers/firebase/api/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-firebase-api", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,25 +14,25 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "core-js": "3.29.1", - "firebase-admin": "11.5.0" + "@babel/runtime-corejs3": "7.23.6", + "core-js": "3.34.0", + "firebase-admin": "11.11.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@redwoodjs/api": "4.0.0", - "@types/aws-lambda": "8.10.114", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@redwoodjs/api": "6.0.7", + "@types/aws-lambda": "8.10.126", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/firebase/api/src/decoder.ts b/packages/auth-providers/firebase/api/src/decoder.ts index 83ed62b6fbed..57a0001040c5 100644 --- a/packages/auth-providers/firebase/api/src/decoder.ts +++ b/packages/auth-providers/firebase/api/src/decoder.ts @@ -1,13 +1,33 @@ import admin from 'firebase-admin' +import type { FirebaseError } from 'firebase-admin' -import { Decoder } from '@redwoodjs/api' +import type { Decoder } from '@redwoodjs/api' +// Alternative third-party JWT verification process described here: +// https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library export const authDecoder: Decoder = async (token: string, type: string) => { if (type !== 'firebase') { return null } - return admin.auth().verifyIdToken(token) - // Alternative third-party JWT verification process described here: - // https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library + try { + return admin.auth().verifyIdToken(token) + } catch (error) { + const firebaseError = error as FirebaseError + + if (firebaseError.code === 'app/no-app') { + const message = [ + '', + '👉 Heads up', + '', + "The firebase app that the auth decoder is using wasn't initialized, which usually means that you have two different versions of firebase-admin.", + 'Make sure that you only have one version of firebase-admin: `yarn why firebase-admin`', + '', + ].join('\n') + + firebaseError.message = `${firebaseError.message}\n${message}` + } + + throw error + } } diff --git a/packages/auth-providers/firebase/api/tsconfig.json b/packages/auth-providers/firebase/api/tsconfig.json index bb9ec4a21fd1..992f3e8dfddf 100644 --- a/packages/auth-providers/firebase/api/tsconfig.json +++ b/packages/auth-providers/firebase/api/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/firebase/setup/README.md b/packages/auth-providers/firebase/setup/README.md index 661208df586e..cba1dea094ac 100644 --- a/packages/auth-providers/firebase/setup/README.md +++ b/packages/auth-providers/firebase/setup/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/firebase/setup/package.json b/packages/auth-providers/firebase/setup/package.json index 8c6e7905ef8e..7249e131cb6d 100644 --- a/packages/auth-providers/firebase/setup/package.json +++ b/packages/auth-providers/firebase/setup/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-firebase-setup", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,24 +14,24 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@redwoodjs/cli-helpers": "4.0.0", - "core-js": "3.29.1" + "@babel/runtime-corejs3": "7.23.6", + "@redwoodjs/cli-helpers": "6.0.7", + "core-js": "3.34.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@types/yargs": "17.0.24", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@types/yargs": "17.0.32", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/firebase/setup/src/setup.ts b/packages/auth-providers/firebase/setup/src/setup.ts index c5da280cfc23..c07768f6e1cc 100644 --- a/packages/auth-providers/firebase/setup/src/setup.ts +++ b/packages/auth-providers/firebase/setup/src/setup.ts @@ -1,4 +1,4 @@ -import yargs from 'yargs' +import type yargs from 'yargs' import { standardAuthBuilder } from '@redwoodjs/cli-helpers' @@ -14,6 +14,6 @@ export interface Args { } export async function handler(options: Args) { - const { handler } = await import('./setupHandler') + const { handler } = await import('./setupHandler.js') return handler(options) } diff --git a/packages/auth-providers/firebase/setup/src/setupHandler.ts b/packages/auth-providers/firebase/setup/src/setupHandler.ts index b35ac8cd61dc..e63cb42c1743 100644 --- a/packages/auth-providers/firebase/setup/src/setupHandler.ts +++ b/packages/auth-providers/firebase/setup/src/setupHandler.ts @@ -3,7 +3,7 @@ import path from 'path' import { standardAuthHandler } from '@redwoodjs/cli-helpers' -import { Args } from './setup' +import type { Args } from './setup' const { version } = JSON.parse( fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8') @@ -16,9 +16,10 @@ export async function handler({ force: forceArg }: Args) { provider: 'firebase', authDecoderImport: "import { authDecoder } from '@redwoodjs/auth-firebase-api'", - webPackages: ['firebase@^9', `@redwoodjs/auth-firebase-web@${version}`], + webPackages: ['firebase@^10', `@redwoodjs/auth-firebase-web@${version}`], apiPackages: [ - 'firebase-admin@^10', + // Note that the version of this package should be exactly the same as the version in `@redwoodjs/auth-firebase-api` . + 'firebase-admin@11.10.1', `@redwoodjs/auth-firebase-api@${version}`, ], notes: [ @@ -35,7 +36,7 @@ export async function handler({ force: forceArg }: Args) { '', '```toml title="redwood.toml"', 'includeEnvironmentVariables = [', - ' "FIREBASE_API_KEY"', + ' "FIREBASE_API_KEY",', ' "FIREBASE_AUTH_DOMAIN"', ']', '```', diff --git a/packages/auth-providers/firebase/setup/src/templates/api/lib/auth.ts.template b/packages/auth-providers/firebase/setup/src/templates/api/lib/auth.ts.template index d3f2a16c29e5..cc8200aeefe8 100644 --- a/packages/auth-providers/firebase/setup/src/templates/api/lib/auth.ts.template +++ b/packages/auth-providers/firebase/setup/src/templates/api/lib/auth.ts.template @@ -2,7 +2,7 @@ import admin from 'firebase-admin' import { AuthenticationError } from '@redwoodjs/graphql-server' -// eslint-disable-next-line @typescript-eslint/no-unused-vars +// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars const adminApp = admin.initializeApp({ projectId: process.env.FIREBASE_PROJECT_ID, }) @@ -25,9 +25,9 @@ const adminApp = admin.initializeApp({ */ export const getCurrentUser = async ( decoded, - /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + /* eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars */ { token, type }, - /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + /* eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars */ { event, context } ) => { return decoded diff --git a/packages/auth-providers/firebase/setup/tsconfig.json b/packages/auth-providers/firebase/setup/tsconfig.json index abb54b70112a..5be9707b2f05 100644 --- a/packages/auth-providers/firebase/setup/tsconfig.json +++ b/packages/auth-providers/firebase/setup/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/firebase/web/README.md b/packages/auth-providers/firebase/web/README.md index 661208df586e..cba1dea094ac 100644 --- a/packages/auth-providers/firebase/web/README.md +++ b/packages/auth-providers/firebase/web/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/firebase/web/package.json b/packages/auth-providers/firebase/web/package.json index 082ad7f20240..722cde65b5ad 100644 --- a/packages/auth-providers/firebase/web/package.json +++ b/packages/auth-providers/firebase/web/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-firebase-web", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,29 +14,29 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@redwoodjs/auth": "4.0.0", - "core-js": "3.29.1" + "@babel/runtime-corejs3": "7.23.6", + "@redwoodjs/auth": "6.0.7", + "core-js": "3.34.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@types/react": "18.0.31", - "firebase": "9.19.1", - "jest": "29.5.0", - "react": "18.2.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@types/react": "18.2.37", + "firebase": "10.6.0", + "jest": "29.7.0", + "react": "0.0.0-experimental-e5205658f-20230913", + "typescript": "5.3.3" }, "peerDependencies": { - "firebase": "9.19.1" + "firebase": "10.6.0" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/firebase/web/src/__tests__/firebase.test.tsx b/packages/auth-providers/firebase/web/src/__tests__/firebase.test.tsx index 69dc1478f21b..b1e9d4ed0a9a 100644 --- a/packages/auth-providers/firebase/web/src/__tests__/firebase.test.tsx +++ b/packages/auth-providers/firebase/web/src/__tests__/firebase.test.tsx @@ -1,10 +1,12 @@ import { renderHook, act } from '@testing-library/react' import type FirebaseAuthNamespace from 'firebase/auth' -import { User, OperationType, OAuthProvider, Auth } from 'firebase/auth' +import type { User, OAuthProvider, Auth } from 'firebase/auth' +import { OperationType } from 'firebase/auth' -import { CurrentUser } from '@redwoodjs/auth' +import type { CurrentUser } from '@redwoodjs/auth' -import { createAuth, FirebaseClient } from '../firebase' +import type { FirebaseClient } from '../firebase' +import { createAuth } from '../firebase' const user: User = { uid: 'unique_user_id', diff --git a/packages/auth-providers/firebase/web/src/firebase.ts b/packages/auth-providers/firebase/web/src/firebase.ts index dc498f8001ed..9081c523a9d5 100644 --- a/packages/auth-providers/firebase/web/src/firebase.ts +++ b/packages/auth-providers/firebase/web/src/firebase.ts @@ -2,7 +2,8 @@ import type { FirebaseApp } from 'firebase/app' import type { CustomParameters, OAuthProvider, User } from 'firebase/auth' import type FirebaseAuthNamespace from 'firebase/auth' -import { CurrentUser, createAuthentication } from '@redwoodjs/auth' +import type { CurrentUser } from '@redwoodjs/auth' +import { createAuthentication } from '@redwoodjs/auth' type FirebaseAuth = typeof FirebaseAuthNamespace diff --git a/packages/auth-providers/firebase/web/tsconfig.json b/packages/auth-providers/firebase/web/tsconfig.json index a925964c5e44..bf6fbe8951bd 100644 --- a/packages/auth-providers/firebase/web/tsconfig.json +++ b/packages/auth-providers/firebase/web/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/netlify/api/README.md b/packages/auth-providers/netlify/api/README.md index d27ea3626dac..10ba2ea0959a 100644 --- a/packages/auth-providers/netlify/api/README.md +++ b/packages/auth-providers/netlify/api/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/netlify/api/package.json b/packages/auth-providers/netlify/api/package.json index 37808ed82883..1c2a519c0fe8 100644 --- a/packages/auth-providers/netlify/api/package.json +++ b/packages/auth-providers/netlify/api/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-netlify-api", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,26 +14,26 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "core-js": "3.29.1", - "jsonwebtoken": "9.0.0" + "@babel/runtime-corejs3": "7.23.6", + "core-js": "3.34.0", + "jsonwebtoken": "9.0.2" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@redwoodjs/api": "4.0.0", - "@types/aws-lambda": "8.10.114", - "@types/jsonwebtoken": "9.0.1", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@redwoodjs/api": "6.0.7", + "@types/aws-lambda": "8.10.126", + "@types/jsonwebtoken": "9.0.5", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/netlify/api/src/decoder.ts b/packages/auth-providers/netlify/api/src/decoder.ts index 9a0ef516a22c..58dbf4d21226 100644 --- a/packages/auth-providers/netlify/api/src/decoder.ts +++ b/packages/auth-providers/netlify/api/src/decoder.ts @@ -1,7 +1,7 @@ import type { Context as LambdaContext, ClientContext } from 'aws-lambda' import jwt, { TokenExpiredError } from 'jsonwebtoken' -import { Decoder } from '@redwoodjs/api' +import type { Decoder } from '@redwoodjs/api' type NetlifyContext = ClientContext & { user?: Record diff --git a/packages/auth-providers/netlify/api/tsconfig.json b/packages/auth-providers/netlify/api/tsconfig.json index bb9ec4a21fd1..992f3e8dfddf 100644 --- a/packages/auth-providers/netlify/api/tsconfig.json +++ b/packages/auth-providers/netlify/api/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/netlify/setup/README.md b/packages/auth-providers/netlify/setup/README.md index 661208df586e..cba1dea094ac 100644 --- a/packages/auth-providers/netlify/setup/README.md +++ b/packages/auth-providers/netlify/setup/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/netlify/setup/package.json b/packages/auth-providers/netlify/setup/package.json index d45fd0b07ec6..cb97f572f656 100644 --- a/packages/auth-providers/netlify/setup/package.json +++ b/packages/auth-providers/netlify/setup/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-netlify-setup", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,24 +14,24 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@redwoodjs/cli-helpers": "4.0.0", - "core-js": "3.29.1" + "@babel/runtime-corejs3": "7.23.6", + "@redwoodjs/cli-helpers": "6.0.7", + "core-js": "3.34.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@types/yargs": "17.0.24", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@types/yargs": "17.0.32", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/netlify/setup/src/setup.ts b/packages/auth-providers/netlify/setup/src/setup.ts index 166b4f3ffa1c..957268923b51 100644 --- a/packages/auth-providers/netlify/setup/src/setup.ts +++ b/packages/auth-providers/netlify/setup/src/setup.ts @@ -1,4 +1,4 @@ -import yargs from 'yargs' +import type yargs from 'yargs' import { standardAuthBuilder } from '@redwoodjs/cli-helpers' @@ -14,6 +14,6 @@ export interface Args { } export async function handler(options: Args) { - const { handler } = await import('./setupHandler') + const { handler } = await import('./setupHandler.js') return handler(options) } diff --git a/packages/auth-providers/netlify/setup/src/setupHandler.ts b/packages/auth-providers/netlify/setup/src/setupHandler.ts index 34cfa489c52f..b857fd093cd2 100644 --- a/packages/auth-providers/netlify/setup/src/setupHandler.ts +++ b/packages/auth-providers/netlify/setup/src/setupHandler.ts @@ -3,7 +3,7 @@ import path from 'path' import { standardAuthHandler } from '@redwoodjs/cli-helpers' -import { Args } from './setup' +import type { Args } from './setup' const { version } = JSON.parse( fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8') diff --git a/packages/auth-providers/netlify/setup/tsconfig.json b/packages/auth-providers/netlify/setup/tsconfig.json index abb54b70112a..5be9707b2f05 100644 --- a/packages/auth-providers/netlify/setup/tsconfig.json +++ b/packages/auth-providers/netlify/setup/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/netlify/web/README.md b/packages/auth-providers/netlify/web/README.md index 661208df586e..cba1dea094ac 100644 --- a/packages/auth-providers/netlify/web/README.md +++ b/packages/auth-providers/netlify/web/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/netlify/web/package.json b/packages/auth-providers/netlify/web/package.json index f062a3565a7c..1267e54b42bb 100644 --- a/packages/auth-providers/netlify/web/package.json +++ b/packages/auth-providers/netlify/web/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-netlify-web", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,26 +14,26 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@redwoodjs/auth": "4.0.0", - "core-js": "3.29.1" + "@babel/runtime-corejs3": "7.23.6", + "@redwoodjs/auth": "6.0.7", + "core-js": "3.34.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@types/netlify-identity-widget": "1.9.3", - "@types/react": "18.0.31", - "jest": "29.5.0", - "react": "18.2.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@types/netlify-identity-widget": "1.9.6", + "@types/react": "18.2.37", + "jest": "29.7.0", + "react": "0.0.0-experimental-e5205658f-20230913", + "typescript": "5.3.3" }, "peerDependencies": { "netlify-identity-widget": "1.9.2" diff --git a/packages/auth-providers/netlify/web/src/__tests__/netlify.test.tsx b/packages/auth-providers/netlify/web/src/__tests__/netlify.test.tsx index c71507a3bed3..f6d3feeb905b 100644 --- a/packages/auth-providers/netlify/web/src/__tests__/netlify.test.tsx +++ b/packages/auth-providers/netlify/web/src/__tests__/netlify.test.tsx @@ -1,7 +1,7 @@ import { renderHook, act } from '@testing-library/react' -import * as NetlifyIdentityNS from 'netlify-identity-widget' +import type * as NetlifyIdentityNS from 'netlify-identity-widget' -import { CurrentUser } from '@redwoodjs/auth' +import type { CurrentUser } from '@redwoodjs/auth' import { createAuth } from '../netlify' diff --git a/packages/auth-providers/netlify/web/src/netlify.ts b/packages/auth-providers/netlify/web/src/netlify.ts index 3a0bde9bead1..580b758e5334 100644 --- a/packages/auth-providers/netlify/web/src/netlify.ts +++ b/packages/auth-providers/netlify/web/src/netlify.ts @@ -1,6 +1,7 @@ -import * as NetlifyIdentityNS from 'netlify-identity-widget' +import type * as NetlifyIdentityNS from 'netlify-identity-widget' -import { CurrentUser, createAuthentication } from '@redwoodjs/auth' +import type { CurrentUser } from '@redwoodjs/auth' +import { createAuthentication } from '@redwoodjs/auth' // TODO: // In the future, when this is a separate package, we can import the full thing diff --git a/packages/auth-providers/netlify/web/tsconfig.json b/packages/auth-providers/netlify/web/tsconfig.json index a925964c5e44..bf6fbe8951bd 100644 --- a/packages/auth-providers/netlify/web/tsconfig.json +++ b/packages/auth-providers/netlify/web/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/supabase/api/README.md b/packages/auth-providers/supabase/api/README.md index d27ea3626dac..10ba2ea0959a 100644 --- a/packages/auth-providers/supabase/api/README.md +++ b/packages/auth-providers/supabase/api/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/supabase/api/package.json b/packages/auth-providers/supabase/api/package.json index 6542d8fefcaf..32ea4aa1eece 100644 --- a/packages/auth-providers/supabase/api/package.json +++ b/packages/auth-providers/supabase/api/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-supabase-api", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,26 +14,26 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "core-js": "3.29.1", - "jsonwebtoken": "9.0.0" + "@babel/runtime-corejs3": "7.23.6", + "core-js": "3.34.0", + "jsonwebtoken": "9.0.2" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@redwoodjs/api": "4.0.0", - "@types/aws-lambda": "8.10.114", - "@types/jsonwebtoken": "9.0.1", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@redwoodjs/api": "6.0.7", + "@types/aws-lambda": "8.10.126", + "@types/jsonwebtoken": "9.0.5", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/supabase/api/src/decoder.ts b/packages/auth-providers/supabase/api/src/decoder.ts index 3c965d4d88ad..4c71dca75df4 100644 --- a/packages/auth-providers/supabase/api/src/decoder.ts +++ b/packages/auth-providers/supabase/api/src/decoder.ts @@ -1,6 +1,6 @@ import jwt from 'jsonwebtoken' -import { Decoder } from '@redwoodjs/api' +import type { Decoder } from '@redwoodjs/api' export const authDecoder: Decoder = async (token: string, type: string) => { if (type !== 'supabase') { diff --git a/packages/auth-providers/supabase/api/tsconfig.json b/packages/auth-providers/supabase/api/tsconfig.json index a925964c5e44..bf6fbe8951bd 100644 --- a/packages/auth-providers/supabase/api/tsconfig.json +++ b/packages/auth-providers/supabase/api/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/supabase/setup/README.md b/packages/auth-providers/supabase/setup/README.md index 661208df586e..cba1dea094ac 100644 --- a/packages/auth-providers/supabase/setup/README.md +++ b/packages/auth-providers/supabase/setup/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/supabase/setup/package.json b/packages/auth-providers/supabase/setup/package.json index 8c9ebe7000e9..f2e719a99668 100644 --- a/packages/auth-providers/supabase/setup/package.json +++ b/packages/auth-providers/supabase/setup/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-supabase-setup", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,24 +14,24 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src --passWithNoTests", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@redwoodjs/cli-helpers": "4.0.0", - "core-js": "3.29.1" + "@babel/runtime-corejs3": "7.23.6", + "@redwoodjs/cli-helpers": "6.0.7", + "core-js": "3.34.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@types/yargs": "17.0.24", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@types/yargs": "17.0.32", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/supabase/setup/src/setup.ts b/packages/auth-providers/supabase/setup/src/setup.ts index b87a895bf3c4..e6a2c22637b0 100644 --- a/packages/auth-providers/supabase/setup/src/setup.ts +++ b/packages/auth-providers/supabase/setup/src/setup.ts @@ -1,4 +1,4 @@ -import yargs from 'yargs' +import type yargs from 'yargs' import { standardAuthBuilder } from '@redwoodjs/cli-helpers' @@ -14,6 +14,6 @@ export interface Args { } export async function handler(options: Args) { - const { handler } = await import('./setupHandler') + const { handler } = await import('./setupHandler.js') return handler(options) } diff --git a/packages/auth-providers/supabase/setup/src/setupHandler.ts b/packages/auth-providers/supabase/setup/src/setupHandler.ts index 16430493850d..4a7b1d1396a6 100644 --- a/packages/auth-providers/supabase/setup/src/setupHandler.ts +++ b/packages/auth-providers/supabase/setup/src/setupHandler.ts @@ -3,7 +3,7 @@ import path from 'path' import { standardAuthHandler } from '@redwoodjs/cli-helpers' -import { Args } from './setup' +import type { Args } from './setup' const { version } = JSON.parse( fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8') @@ -34,8 +34,8 @@ export const handler = async ({ force: forceArg }: Args) => { '', '```toml title="redwood.toml"', 'includeEnvironmentVariables = [', - ' "SUPABASE_URL"', - ' "SUPABASE_KEY"', + ' "SUPABASE_URL",', + ' "SUPABASE_KEY",', ']', '```', '', diff --git a/packages/auth-providers/supabase/setup/src/templates/api/lib/auth.ts.template b/packages/auth-providers/supabase/setup/src/templates/api/lib/auth.ts.template index 08a3810c1266..589b5fad5af7 100644 --- a/packages/auth-providers/supabase/setup/src/templates/api/lib/auth.ts.template +++ b/packages/auth-providers/supabase/setup/src/templates/api/lib/auth.ts.template @@ -29,9 +29,9 @@ type RedwoodUser = Record & { roles?: string[] } */ export const getCurrentUser = async ( decoded, - /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + /* eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars */ { token, type }, - /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + /* eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars */ { event, context } ): Promise => { if (!decoded) { diff --git a/packages/auth-providers/supabase/setup/tsconfig.json b/packages/auth-providers/supabase/setup/tsconfig.json index abb54b70112a..5be9707b2f05 100644 --- a/packages/auth-providers/supabase/setup/tsconfig.json +++ b/packages/auth-providers/supabase/setup/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/supabase/web/package.json b/packages/auth-providers/supabase/web/package.json index f9fcd1679201..6d57ce6e4260 100644 --- a/packages/auth-providers/supabase/web/package.json +++ b/packages/auth-providers/supabase/web/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-supabase-web", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,28 +14,28 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "core-js": "3.29.1" + "@babel/runtime-corejs3": "7.23.6", + "core-js": "3.34.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@supabase/supabase-js": "2.13.1", - "@types/react": "18.0.31", - "jest": "29.5.0", - "react": "18.2.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@supabase/supabase-js": "2.39.0", + "@types/react": "18.2.37", + "jest": "29.7.0", + "react": "0.0.0-experimental-e5205658f-20230913", + "typescript": "5.3.3" }, "peerDependencies": { - "@supabase/supabase-js": "2.13.1" + "@supabase/supabase-js": "2.39.0" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/supabase/web/src/__tests__/supabase.test.tsx b/packages/auth-providers/supabase/web/src/__tests__/supabase.test.tsx index d45ff78107f9..a9d79a252cd7 100644 --- a/packages/auth-providers/supabase/web/src/__tests__/supabase.test.tsx +++ b/packages/auth-providers/supabase/web/src/__tests__/supabase.test.tsx @@ -1,4 +1,4 @@ -import { +import type { SupabaseClient, User, AuthResponse, @@ -11,11 +11,11 @@ import { SignInWithPasswordCredentials, SignUpWithPasswordCredentials, Session, - AuthError, } from '@supabase/supabase-js' +import { AuthError } from '@supabase/supabase-js' import { renderHook, act } from '@testing-library/react' -import { CurrentUser } from '@redwoodjs/auth' +import type { CurrentUser } from '@redwoodjs/auth' import { createAuth } from '../supabase' diff --git a/packages/auth-providers/supabase/web/src/supabase.ts b/packages/auth-providers/supabase/web/src/supabase.ts index c4abbdf3b5e9..8d4406124b06 100644 --- a/packages/auth-providers/supabase/web/src/supabase.ts +++ b/packages/auth-providers/supabase/web/src/supabase.ts @@ -12,7 +12,8 @@ import type { } from '@supabase/supabase-js' import { AuthError } from '@supabase/supabase-js' -import { CurrentUser, createAuthentication } from '@redwoodjs/auth' +import type { CurrentUser } from '@redwoodjs/auth' +import { createAuthentication } from '@redwoodjs/auth' export type SignInWithOAuthOptions = SignInWithOAuthCredentials & { authMethod: 'oauth' diff --git a/packages/auth-providers/supabase/web/tsconfig.json b/packages/auth-providers/supabase/web/tsconfig.json index a925964c5e44..bf6fbe8951bd 100644 --- a/packages/auth-providers/supabase/web/tsconfig.json +++ b/packages/auth-providers/supabase/web/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/supertokens/api/README.md b/packages/auth-providers/supertokens/api/README.md index d27ea3626dac..10ba2ea0959a 100644 --- a/packages/auth-providers/supertokens/api/README.md +++ b/packages/auth-providers/supertokens/api/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/supertokens/api/package.json b/packages/auth-providers/supertokens/api/package.json index 90ff7f3edcd3..c9004101d086 100644 --- a/packages/auth-providers/supertokens/api/package.json +++ b/packages/auth-providers/supertokens/api/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-supertokens-api", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,29 +14,29 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "core-js": "3.29.1", - "jsonwebtoken": "9.0.0", - "jwks-rsa": "3.0.1" + "@babel/runtime-corejs3": "7.23.6", + "core-js": "3.34.0", + "jsonwebtoken": "9.0.2", + "jwks-rsa": "3.1.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@redwoodjs/api": "4.0.0", - "@types/jsonwebtoken": "9.0.1", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@redwoodjs/api": "6.0.7", + "@types/jsonwebtoken": "9.0.5", + "jest": "29.7.0", + "typescript": "5.3.3" }, "peerDependencies": { - "supertokens-node": "13.3.0" + "supertokens-node": "15.2.1" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/supertokens/api/src/decoder.ts b/packages/auth-providers/supertokens/api/src/decoder.ts index 44f53bfe4274..400f81b9f7bb 100644 --- a/packages/auth-providers/supertokens/api/src/decoder.ts +++ b/packages/auth-providers/supertokens/api/src/decoder.ts @@ -1,7 +1,8 @@ import jwt from 'jsonwebtoken' -import jwksClient, { SigningKey } from 'jwks-rsa' +import type { SigningKey } from 'jwks-rsa' +import jwksClient from 'jwks-rsa' -import { Decoder } from '@redwoodjs/api' +import type { Decoder } from '@redwoodjs/api' export const authDecoder: Decoder = async (token: string, type: string) => { if (type !== 'supertokens') { diff --git a/packages/auth-providers/supertokens/api/tsconfig.json b/packages/auth-providers/supertokens/api/tsconfig.json index bb9ec4a21fd1..992f3e8dfddf 100644 --- a/packages/auth-providers/supertokens/api/tsconfig.json +++ b/packages/auth-providers/supertokens/api/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/supertokens/setup/README.md b/packages/auth-providers/supertokens/setup/README.md index 661208df586e..cba1dea094ac 100644 --- a/packages/auth-providers/supertokens/setup/README.md +++ b/packages/auth-providers/supertokens/setup/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/supertokens/setup/package.json b/packages/auth-providers/supertokens/setup/package.json index fde420e3f762..8518a9ef7cfe 100644 --- a/packages/auth-providers/supertokens/setup/package.json +++ b/packages/auth-providers/supertokens/setup/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-supertokens-setup", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,24 +14,24 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src --passWithNoTests", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@redwoodjs/cli-helpers": "4.0.0", - "core-js": "3.29.1" + "@babel/runtime-corejs3": "7.23.6", + "@redwoodjs/cli-helpers": "6.0.7", + "core-js": "3.34.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@types/yargs": "17.0.24", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@types/yargs": "17.0.32", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/supertokens/setup/src/__tests__/__snapshots__/setupHandler.test.ts.snap b/packages/auth-providers/supertokens/setup/src/__tests__/__snapshots__/setupHandler.test.ts.snap deleted file mode 100644 index d084b27f09fd..000000000000 --- a/packages/auth-providers/supertokens/setup/src/__tests__/__snapshots__/setupHandler.test.ts.snap +++ /dev/null @@ -1,55 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`extraTask 1`] = ` -"// In this file, all Page components from 'src/pages' are auto-imported. - -import SuperTokens from 'supertokens-auth-react' - -import { Router, Route } from '@redwoodjs/router' - -import { useAuth } from './auth' - -const Routes = () => { - if (SuperTokens.canHandleRoute()) { - return SuperTokens.getRoutingComponent() - } - - return ( - - - - - - ) -} - -export default Routes -" -`; - -exports[`extraTask already setup 1`] = ` -"// In this file, all Page components from 'src/pages' are auto-imported. - -import SuperTokens from 'supertokens-auth-react' - -import { Router, Route } from '@redwoodjs/router' - -import { useAuth } from './auth' - -const Routes = () => { - if (SuperTokens.canHandleRoute()) { - return SuperTokens.getRoutingComponent() - } - - return ( - - - - - - ) -} - -export default Routes -" -`; diff --git a/packages/auth-providers/supertokens/setup/src/__tests__/setupHandler.test.ts b/packages/auth-providers/supertokens/setup/src/__tests__/setupHandler.test.ts index 6904872c3fc1..6789d79d940c 100644 --- a/packages/auth-providers/supertokens/setup/src/__tests__/setupHandler.test.ts +++ b/packages/auth-providers/supertokens/setup/src/__tests__/setupHandler.test.ts @@ -31,13 +31,14 @@ const mockFS = fs as unknown as Omit, 'readdirSync'> & { import fs from 'fs' -import { extraTask } from '../setupHandler' - -test('extraTask', () => { - mockFS.__setMockFiles({ - 'Routes.tsx': - "// In this file, all Page components from 'src/pages' are auto-imported.\n" + - ` +import { addRoutingLogic } from '../setupHandler' + +describe('addRoutingLogic', () => { + it('modifies the Routes.{jsx,tsx} file', () => { + mockFS.__setMockFiles({ + 'Routes.tsx': + "// In this file, all Page components from 'src/pages' are auto-imported.\n" + + ` import { Router, Route } from '@redwoodjs/router' import { useAuth } from './auth' @@ -54,18 +55,43 @@ const Routes = () => { export default Routes `, - }) + }) - extraTask.task() + addRoutingLogic.task() - expect(mockFS.readFileSync('Routes.tsx')).toMatchSnapshot() -}) + expect(mockFS.readFileSync('Routes.tsx')).toMatchInlineSnapshot(` + "// In this file, all Page components from 'src/pages' are auto-imported. + + import { canHandleRoute, getRoutingComponent } from 'supertokens-auth-react/ui' + + import { Router, Route } from '@redwoodjs/router' + + import { useAuth, PreBuiltUI } from './auth' -test('extraTask already setup', () => { - mockFS.__setMockFiles({ - 'Routes.tsx': - "// In this file, all Page components from 'src/pages' are auto-imported.\n" + - ` + const Routes = () => { + if (canHandleRoute(PreBuiltUI)) { + return getRoutingComponent(PreBuiltUI) + } + + return ( + + + + + + ) + } + + export default Routes + " + `) + }) + + it('handles a Routes.{jsx,tsx} file with a legacy setup', () => { + mockFS.__setMockFiles({ + 'Routes.tsx': + "// In this file, all Page components from 'src/pages' are auto-imported.\n" + + ` import SuperTokens from 'supertokens-auth-react' import { Router, Route } from '@redwoodjs/router' @@ -88,9 +114,39 @@ const Routes = () => { export default Routes `, - }) + }) - extraTask.task() + addRoutingLogic.task() - expect(mockFS.readFileSync('Routes.tsx')).toMatchSnapshot() + expect(mockFS.readFileSync('Routes.tsx')).toMatchInlineSnapshot(` + "// In this file, all Page components from 'src/pages' are auto-imported. + + + + import { canHandleRoute, getRoutingComponent } from 'supertokens-auth-react/ui' + + import { Router, Route } from '@redwoodjs/router' + + import { useAuth, PreBuiltUI } from './auth' + + const Routes = () => { + if (canHandleRoute(PreBuiltUI)) { + return getRoutingComponent(PreBuiltUI) + } + + + + return ( + + + + + + ) + } + + export default Routes + " + `) + }) }) diff --git a/packages/auth-providers/supertokens/setup/src/setup.ts b/packages/auth-providers/supertokens/setup/src/setup.ts index a3bc2da8962f..49004f55aa0d 100644 --- a/packages/auth-providers/supertokens/setup/src/setup.ts +++ b/packages/auth-providers/supertokens/setup/src/setup.ts @@ -1,4 +1,4 @@ -import yargs from 'yargs' +import type yargs from 'yargs' import { standardAuthBuilder } from '@redwoodjs/cli-helpers' @@ -14,6 +14,6 @@ export interface Args { } export async function handler(options: Args) { - const { handler } = await import('./setupHandler') + const { handler } = await import('./setupHandler.js') return handler(options) } diff --git a/packages/auth-providers/supertokens/setup/src/setupHandler.ts b/packages/auth-providers/supertokens/setup/src/setupHandler.ts index f2bf1ada3ad6..d58fd648b8d6 100644 --- a/packages/auth-providers/supertokens/setup/src/setupHandler.ts +++ b/packages/auth-providers/supertokens/setup/src/setupHandler.ts @@ -3,32 +3,70 @@ import path from 'path' import { getPaths, standardAuthHandler } from '@redwoodjs/cli-helpers' -import { Args } from './setup' +import type { Args } from './setup' -// exported for testing -export const extraTask = { - title: `Adding SuperTokens routing component to Routes.{js,tsx}...`, +export async function handler({ force: forceArg }: Args) { + const { version } = JSON.parse( + fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8') + ) + + standardAuthHandler({ + basedir: __dirname, + forceArg, + provider: 'supertokens', + authDecoderImport: + "import { authDecoder } from '@redwoodjs/auth-supertokens-api'", + apiPackages: [ + `@redwoodjs/auth-supertokens-api@${version}`, + 'supertokens-node@^15', + ], + webPackages: [ + `@redwoodjs/auth-supertokens-web@${version}`, + 'supertokens-auth-react@~0.34.0', + 'supertokens-web-js@~0.7.0', + ], + extraTasks: [addRoutingLogic], + notes: [ + "We've implemented SuperToken's EmailPassword with Social / Enterprise (OAuth 2.0, SAML) login recipe,", + 'but feel free to switch to something that better fits your needs. See https://supertokens.com/docs/guides.', + '', + "To get things working, you'll need to add quite a few env vars to your .env file.", + 'See https://redwoodjs.com/docs/auth/supertokens for a full walkthrough.', + ], + }) +} + +// Exported for testing. +export const addRoutingLogic = { + title: `Adding SuperTokens routing logic to Routes.{jsx,tsx}...`, task: () => { - const webRoutesPath = getPaths().web.routes + const routesPath = getPaths().web.routes + + let content = fs.readFileSync(routesPath, 'utf-8') - let content = fs.readFileSync(webRoutesPath).toString() + // Remove the old setup if it's there. + content = content + .replace("import SuperTokens from 'supertokens-auth-react'", '') + .replace(/if \(SuperTokens.canHandleRoute\(\)\) {[^}]+}/, '') - if (!/\n\s*if \(SuperTokens.canHandleRoute\(\)\) \{/.test(content)) { - let hasImportedSuperTokens = false + if (!/\s*if\s*\(canHandleRoute\(PreBuiltUI\)\)\s*\{/.test(content)) { + let hasImportedSuperTokensFunctions = false content = content .split('\n') .reduce((acc, line) => { - // Add the SuperTokens import before the first import from @redwoodjs + // Add the SuperTokens import before the first import from a RedwoodJS package. if ( - !hasImportedSuperTokens && + !hasImportedSuperTokensFunctions && line.includes('import') && line.includes('@redwoodjs') ) { - acc.push("import SuperTokens from 'supertokens-auth-react'") + acc.push( + "import { canHandleRoute, getRoutingComponent } from 'supertokens-auth-react/ui'" + ) acc.push('') - hasImportedSuperTokens = true + hasImportedSuperTokensFunctions = true } acc.push(line) @@ -36,43 +74,20 @@ export const extraTask = { return acc }, []) .join('\n') - .replace( - /const Routes = \(\) => \{\n/, - 'const Routes = () => {\n' + - ' if (SuperTokens.canHandleRoute()) {\n' + - ' return SuperTokens.getRoutingComponent()\n' + - ' }\n\n' - ) + content = content.replace( + "import { useAuth } from './auth'", + "import { useAuth, PreBuiltUI } from './auth'" + ) - fs.writeFileSync(webRoutesPath, content) + content = content.replace( + /const Routes = \(\) => \{\n/, + 'const Routes = () => {\n' + + ' if (canHandleRoute(PreBuiltUI)) {\n' + + ' return getRoutingComponent(PreBuiltUI)\n' + + ' }\n\n' + ) + + fs.writeFileSync(routesPath, content) } }, } - -export async function handler({ force: forceArg }: Args) { - const { version } = JSON.parse( - fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8') - ) - - standardAuthHandler({ - basedir: __dirname, - forceArg, - provider: 'supertokens', - authDecoderImport: - "import { authDecoder } from '@redwoodjs/auth-supertokens-api'", - apiPackages: [ - `@redwoodjs/auth-supertokens-api@${version}`, - 'supertokens-node@^12', - ], - webPackages: [ - `@redwoodjs/auth-supertokens-web@${version}`, - 'supertokens-auth-react@^0.30', - 'supertokens-web-js@^0.4', - ], - extraTask, - notes: [ - "We've implemented some of SuperToken's recipes, but feel free", - 'to switch to something that better fits your needs. See https://supertokens.com/docs/guides.', - ], - }) -} diff --git a/packages/auth-providers/supertokens/setup/src/templates/api/lib/auth.ts.template b/packages/auth-providers/supertokens/setup/src/templates/api/lib/auth.ts.template index 08a3810c1266..589b5fad5af7 100644 --- a/packages/auth-providers/supertokens/setup/src/templates/api/lib/auth.ts.template +++ b/packages/auth-providers/supertokens/setup/src/templates/api/lib/auth.ts.template @@ -29,9 +29,9 @@ type RedwoodUser = Record & { roles?: string[] } */ export const getCurrentUser = async ( decoded, - /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + /* eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars */ { token, type }, - /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + /* eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars */ { event, context } ): Promise => { if (!decoded) { diff --git a/packages/auth-providers/supertokens/setup/src/templates/api/lib/supertokens.ts.template b/packages/auth-providers/supertokens/setup/src/templates/api/lib/supertokens.ts.template index c7f692841f88..474d4c4e06fb 100644 --- a/packages/auth-providers/supertokens/setup/src/templates/api/lib/supertokens.ts.template +++ b/packages/auth-providers/supertokens/setup/src/templates/api/lib/supertokens.ts.template @@ -1,23 +1,19 @@ import * as Session from 'supertokens-node/recipe/session' -import ThirdPartyEmailPassword, { - Google, - Github, - Apple, -} from 'supertokens-node/recipe/thirdpartyemailpassword' +import ThirdPartyEmailPassword from 'supertokens-node/recipe/thirdpartyemailpassword' import type { TypeInput } from 'supertokens-node/types' const websiteDomain = process.env.SUPERTOKENS_WEBSITE_DOMAIN || 'http://localhost:8910' const apiDomain = process.env.SUPERTOKENS_API_DOMAIN || websiteDomain -const apiGatewayPath = process.env.SUPERTOKENS_API_GATEWAY_PATH || '/.redwood/functions' - -const jwksIssuerUrl = {} +const apiGatewayPath = + process.env.SUPERTOKENS_API_GATEWAY_PATH || '/.redwood/functions' export const config: TypeInput = { + # The below options are ok here even if you're not running on top of AWS Lambda, since Redwood internally translates Fastify request/response objects to and from the AWS Lambda format. framework: 'awsLambda', isInServerlessEnv: true, appInfo: { - appName: 'SuperTokens RedwoodJS', + appName: process.env.SUPERTOKENS_APP_NAME, apiDomain, websiteDomain, apiGatewayPath, @@ -26,30 +22,54 @@ export const config: TypeInput = { }, supertokens: { connectionURI: process.env.SUPERTOKENS_CONNECTION_URI, + apiKey: process.env.SUPERTOKENS_API_KEY, }, recipeList: [ ThirdPartyEmailPassword.init({ providers: [ - Google({ - clientId: process.env.SUPERTOKENS_GOOGLE_CLIENT_ID, - clientSecret: process.env.SUPERTOKENS_GOOGLE_CLIENT_SECRET, - }), - Github({ - clientId: process.env.SUPERTOKENS_GITHUB_CLIENT_ID, - clientSecret: process.env.SUPERTOKENS_GITHUB_CLIENT_SECRET, - }), - Apple({ - clientId: process.env.SUPERTOKENS_APPLE_CLIENT_ID, - clientSecret: { - keyId: process.env.SUPERTOKENS_APPLE_SECRET_KEY_ID, - privateKey: process.env.SUPERTOKENS_APPLE_SECRET_PRIVATE_KEY, - teamId: process.env.SUPERTOKENS_APPLE_SECRET_TEAM_ID, + { + config: { + thirdPartyId: 'google', + clients: [ + { + clientId: process.env.SUPERTOKENS_GOOGLE_CLIENT_ID, + clientSecret: process.env.SUPERTOKENS_GOOGLE_CLIENT_SECRET, + }, + ], + }, + }, + { + config: { + thirdPartyId: 'github', + clients: [ + { + clientId: process.env.SUPERTOKENS_GITHUB_CLIENT_ID, + clientSecret: process.env.SUPERTOKENS_GITHUB_CLIENT_SECRET, + }, + ], + }, + }, + { + config: { + thirdPartyId: 'apple', + clients: [ + { + clientId: process.env.SUPERTOKENS_APPLE_CLIENT_ID, + additionalConfig: { + keyId: process.env.SUPERTOKENS_APPLE_SECRET_KEY_ID, + privateKey: process.env.SUPERTOKENS_APPLE_SECRET_PRIVATE_KEY, + teamId: process.env.SUPERTOKENS_APPLE_SECRET_TEAM_ID, + }, + }, + ], }, - }), + }, ], }), Session.init({ - jwt: { enable: true, ...jwksIssuerUrl }, + getTokenTransferMethod: () => { + return 'header' + }, }), ], } diff --git a/packages/auth-providers/supertokens/setup/src/templates/web/auth.tsx.template b/packages/auth-providers/supertokens/setup/src/templates/web/auth.tsx.template index 97b7efa403e2..8cd4dbb14a44 100644 --- a/packages/auth-providers/supertokens/setup/src/templates/web/auth.tsx.template +++ b/packages/auth-providers/supertokens/setup/src/templates/web/auth.tsx.template @@ -5,6 +5,7 @@ import ThirdPartyEmailPassword, { Google, Apple, } from 'supertokens-auth-react/recipe/thirdpartyemailpassword' +import { ThirdPartyEmailPasswordPreBuiltUI } from 'supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui' import { createAuth } from '@redwoodjs/auth-supertokens-web' import { isBrowser } from '@redwoodjs/prerender/browserUtils' @@ -20,10 +21,12 @@ const superTokensClient = { redirectToAuth: SuperTokens.redirectToAuth, } +export const PreBuiltUI = [ThirdPartyEmailPasswordPreBuiltUI] + isBrowser && SuperTokens.init({ appInfo: { - appName: 'SuperTokens RedwoodJS', + appName: process.env.SUPERTOKENS_APP_NAME, apiDomain, websiteDomain, apiGatewayPath, diff --git a/packages/auth-providers/supertokens/setup/tsconfig.json b/packages/auth-providers/supertokens/setup/tsconfig.json index abb54b70112a..5be9707b2f05 100644 --- a/packages/auth-providers/supertokens/setup/tsconfig.json +++ b/packages/auth-providers/supertokens/setup/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth-providers/supertokens/web/README.md b/packages/auth-providers/supertokens/web/README.md index 661208df586e..cba1dea094ac 100644 --- a/packages/auth-providers/supertokens/web/README.md +++ b/packages/auth-providers/supertokens/web/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth-providers/supertokens/web/package.json b/packages/auth-providers/supertokens/web/package.json index 77349e96d570..c157378f9f64 100644 --- a/packages/auth-providers/supertokens/web/package.json +++ b/packages/auth-providers/supertokens/web/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth-supertokens-web", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,29 +14,29 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@redwoodjs/auth": "4.0.0", - "core-js": "3.29.1" + "@babel/runtime-corejs3": "7.23.6", + "@redwoodjs/auth": "6.0.7", + "core-js": "3.34.0" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@types/react": "18.0.31", - "jest": "29.5.0", - "react": "18.2.0", - "supertokens-auth-react": "0.31.5", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@types/react": "18.2.37", + "jest": "29.7.0", + "react": "0.0.0-experimental-e5205658f-20230913", + "supertokens-auth-react": "0.34.0", + "typescript": "5.3.3" }, "peerDependencies": { - "supertokens-auth-react": "0.31.5" + "supertokens-auth-react": "0.34.0" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth-providers/supertokens/web/src/__tests__/supertokens.test.tsx b/packages/auth-providers/supertokens/web/src/__tests__/supertokens.test.tsx index a8f4de0da8bb..a740b41134e9 100644 --- a/packages/auth-providers/supertokens/web/src/__tests__/supertokens.test.tsx +++ b/packages/auth-providers/supertokens/web/src/__tests__/supertokens.test.tsx @@ -1,13 +1,13 @@ import { renderHook, act } from '@testing-library/react' -import { CurrentUser } from '@redwoodjs/auth' +import type { CurrentUser } from '@redwoodjs/auth' -import { - createAuth, +import type { SuperTokensUser, SessionRecipe, SuperTokensAuth, } from '../supertokens' +import { createAuth } from '../supertokens' const user: SuperTokensUser = { userId: 'unique_user_id', diff --git a/packages/auth-providers/supertokens/web/src/supertokens.ts b/packages/auth-providers/supertokens/web/src/supertokens.ts index fc8b655f8e36..e20c1e7904ff 100644 --- a/packages/auth-providers/supertokens/web/src/supertokens.ts +++ b/packages/auth-providers/supertokens/web/src/supertokens.ts @@ -1,6 +1,7 @@ import type SuperTokens from 'supertokens-auth-react' -import { createAuthentication, CurrentUser } from '@redwoodjs/auth' +import type { CurrentUser } from '@redwoodjs/auth' +import { createAuthentication } from '@redwoodjs/auth' export interface SuperTokensUser { userId: string @@ -11,6 +12,7 @@ export type SessionRecipe = { signOut: () => Promise doesSessionExist: () => Promise getAccessTokenPayloadSecurely: () => Promise + getAccessToken: () => Promise getUserId: () => Promise } @@ -47,10 +49,7 @@ function createAuthImplementation(superTokens: SuperTokensAuth) { }, getToken: async (): Promise => { if (await superTokens.sessionRecipe.doesSessionExist()) { - const accessTokenPayload = - await superTokens.sessionRecipe.getAccessTokenPayloadSecurely() - const jwtPropertyName = accessTokenPayload['_jwtPName'] - return accessTokenPayload[jwtPropertyName] + return superTokens.sessionRecipe.getAccessToken() } else { return null } diff --git a/packages/auth-providers/supertokens/web/tsconfig.json b/packages/auth-providers/supertokens/web/tsconfig.json index a925964c5e44..bf6fbe8951bd 100644 --- a/packages/auth-providers/supertokens/web/tsconfig.json +++ b/packages/auth-providers/supertokens/web/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/auth/README.md b/packages/auth/README.md index 661208df586e..cba1dea094ac 100644 --- a/packages/auth/README.md +++ b/packages/auth/README.md @@ -21,7 +21,7 @@ both auth service providers and RW apps we recommend you start looking in `authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx` has most of our implementation together with all the custom hooks it uses. Another file to be accustomed with is `AuthContext.ts`. The interface in there -has pretty god code comments, and is what will be exposed to RW apps. +has pretty good code comments, and is what will be exposed to RW apps. ## getCurrentUser diff --git a/packages/auth/package.json b/packages/auth/package.json index d26e992007de..0d0e31118523 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/auth", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,26 +14,26 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\"", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\"", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "core-js": "3.29.1", - "react": "18.2.0" + "@babel/runtime-corejs3": "7.23.6", + "core-js": "3.34.0", + "react": "0.0.0-experimental-e5205658f-20230913" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@testing-library/jest-dom": "5.16.5", - "@testing-library/react": "14.0.0", - "jest": "29.5.0", - "msw": "1.2.1", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@testing-library/jest-dom": "6.1.5", + "@testing-library/react": "14.1.2", + "jest": "29.7.0", + "msw": "1.3.2", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/auth/src/AuthProvider/AuthProvider.tsx b/packages/auth/src/AuthProvider/AuthProvider.tsx index 08fce5bddfce..b4a5d9a20d29 100644 --- a/packages/auth/src/AuthProvider/AuthProvider.tsx +++ b/packages/auth/src/AuthProvider/AuthProvider.tsx @@ -1,12 +1,11 @@ -import React, { ReactNode, useEffect, useState } from 'react' +import type { ReactNode } from 'react' +import React, { useEffect, useState } from 'react' -import { AuthContextInterface, CurrentUser } from '../AuthContext' +import type { AuthContextInterface, CurrentUser } from '../AuthContext' import type { AuthImplementation } from '../AuthImplementation' -import { - AuthProviderState, - defaultAuthProviderState, -} from './AuthProviderState' +import type { AuthProviderState } from './AuthProviderState' +import { defaultAuthProviderState } from './AuthProviderState' import { useCurrentUser } from './useCurrentUser' import { useForgotPassword } from './useForgotPassword' import { useHasRole } from './useHasRole' diff --git a/packages/auth/src/AuthProvider/AuthProviderState.ts b/packages/auth/src/AuthProvider/AuthProviderState.ts index c8ab8fd0d0ad..f9dbe07c02ff 100644 --- a/packages/auth/src/AuthProvider/AuthProviderState.ts +++ b/packages/auth/src/AuthProvider/AuthProviderState.ts @@ -1,4 +1,4 @@ -import { CurrentUser } from '../AuthContext' +import type { CurrentUser } from '../AuthContext' export type AuthProviderState = { loading: boolean diff --git a/packages/auth/src/AuthProvider/useHasRole.ts b/packages/auth/src/AuthProvider/useHasRole.ts index b78a90a472ed..bda4cbe0e3c3 100644 --- a/packages/auth/src/AuthProvider/useHasRole.ts +++ b/packages/auth/src/AuthProvider/useHasRole.ts @@ -1,6 +1,6 @@ import { useCallback } from 'react' -import { CurrentUser } from '../AuthContext' +import type { CurrentUser } from '../AuthContext' export const useHasRole = (currentUser: CurrentUser | null) => { /** diff --git a/packages/auth/src/AuthProvider/useLogIn.ts b/packages/auth/src/AuthProvider/useLogIn.ts index bab4eabb48b9..5ffbafb84442 100644 --- a/packages/auth/src/AuthProvider/useLogIn.ts +++ b/packages/auth/src/AuthProvider/useLogIn.ts @@ -2,11 +2,9 @@ import { useCallback } from 'react' import type { AuthImplementation } from '../AuthImplementation' -import { - AuthProviderState, - defaultAuthProviderState, -} from './AuthProviderState' -import { useCurrentUser } from './useCurrentUser' +import type { AuthProviderState } from './AuthProviderState' +import { defaultAuthProviderState } from './AuthProviderState' +import type { useCurrentUser } from './useCurrentUser' import { useReauthenticate } from './useReauthenticate' export const useLogIn = < diff --git a/packages/auth/src/AuthProvider/useLogOut.ts b/packages/auth/src/AuthProvider/useLogOut.ts index d2d71000e1d4..e11bb41ffa42 100644 --- a/packages/auth/src/AuthProvider/useLogOut.ts +++ b/packages/auth/src/AuthProvider/useLogOut.ts @@ -2,7 +2,7 @@ import { useCallback } from 'react' import type { AuthImplementation } from '../AuthImplementation' -import { AuthProviderState } from './AuthProviderState' +import type { AuthProviderState } from './AuthProviderState' export const useLogOut = < TUser, diff --git a/packages/auth/src/AuthProvider/useReauthenticate.ts b/packages/auth/src/AuthProvider/useReauthenticate.ts index 7b3371e27c4b..ffca5fe63384 100644 --- a/packages/auth/src/AuthProvider/useReauthenticate.ts +++ b/packages/auth/src/AuthProvider/useReauthenticate.ts @@ -1,9 +1,9 @@ import { useCallback } from 'react' -import { AuthImplementation } from '../AuthImplementation' +import type { AuthImplementation } from '../AuthImplementation' -import { AuthProviderState } from './AuthProviderState' -import { useCurrentUser } from './useCurrentUser' +import type { AuthProviderState } from './AuthProviderState' +import type { useCurrentUser } from './useCurrentUser' import { useToken } from './useToken' const notAuthenticatedState = { diff --git a/packages/auth/src/AuthProvider/useSignUp.ts b/packages/auth/src/AuthProvider/useSignUp.ts index bc5bfd04512a..e1d729e3a090 100644 --- a/packages/auth/src/AuthProvider/useSignUp.ts +++ b/packages/auth/src/AuthProvider/useSignUp.ts @@ -2,8 +2,8 @@ import { useCallback } from 'react' import type { AuthImplementation } from '../AuthImplementation' -import { AuthProviderState } from './AuthProviderState' -import { useCurrentUser } from './useCurrentUser' +import type { AuthProviderState } from './AuthProviderState' +import type { useCurrentUser } from './useCurrentUser' import { useReauthenticate } from './useReauthenticate' export const useSignUp = < diff --git a/packages/auth/src/__tests__/AuthProvider.test.tsx b/packages/auth/src/__tests__/AuthProvider.test.tsx index 24eadacd6ea7..ea0e601a3973 100644 --- a/packages/auth/src/__tests__/AuthProvider.test.tsx +++ b/packages/auth/src/__tests__/AuthProvider.test.tsx @@ -10,14 +10,12 @@ import { configure, } from '@testing-library/react' import { renderHook, act } from '@testing-library/react' -import '@testing-library/jest-dom/extend-expect' +import '@testing-library/jest-dom/jest-globals' import { graphql } from 'msw' import { setupServer } from 'msw/node' -import { - CustomTestAuthClient, - createCustomTestAuth, -} from './fixtures/customTestAuth' +import type { CustomTestAuthClient } from './fixtures/customTestAuth' +import { createCustomTestAuth } from './fixtures/customTestAuth' configure({ asyncUtilTimeout: 5_000, diff --git a/packages/auth/src/authFactory.ts b/packages/auth/src/authFactory.ts index b10b79891133..256ce56ae8ef 100644 --- a/packages/auth/src/authFactory.ts +++ b/packages/auth/src/authFactory.ts @@ -1,5 +1,6 @@ -import { createAuthContext, CurrentUser } from './AuthContext' -import { AuthImplementation } from './AuthImplementation' +import type { CurrentUser } from './AuthContext' +import { createAuthContext } from './AuthContext' +import type { AuthImplementation } from './AuthImplementation' import { createAuthProvider } from './AuthProvider/AuthProvider' import { createUseAuth } from './useAuth' diff --git a/packages/auth/tsconfig.json b/packages/auth/tsconfig.json index ba62a1160360..2ee6274e91d4 100644 --- a/packages/auth/tsconfig.json +++ b/packages/auth/tsconfig.json @@ -3,7 +3,6 @@ "compilerOptions": { "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist", }, "include": ["src", "ambient.d.ts"] diff --git a/packages/babel-config/.babelrc.js b/packages/babel-config/.babelrc.js new file mode 100644 index 000000000000..89125e955581 --- /dev/null +++ b/packages/babel-config/.babelrc.js @@ -0,0 +1,2 @@ +// Even though this package is built without Babel, we need this file for Jest. +module.exports = { extends: '../../babel.config.js' } diff --git a/packages/babel-config/README.md b/packages/babel-config/README.md new file mode 100644 index 000000000000..310ae444a608 --- /dev/null +++ b/packages/babel-config/README.md @@ -0,0 +1,3 @@ +# Redwood's Babel Config + +Redwood's Babel config. This package has been extracted out of `@redwoodjs/internal`. diff --git a/packages/babel-config/build.mjs b/packages/babel-config/build.mjs new file mode 100644 index 000000000000..7f99112baa8e --- /dev/null +++ b/packages/babel-config/build.mjs @@ -0,0 +1,25 @@ +import fs from 'node:fs/promises' + +import * as esbuild from 'esbuild' +import fg from 'fast-glob' + +const sourceFiles = await fg.glob(['./src/**/*.ts'], { + ignore: ['./src/**/__tests__'], +}) + +const result = await esbuild.build({ + entryPoints: sourceFiles, + outdir: 'dist', + + format: 'cjs', + platform: 'node', + target: ['node20'], + + logLevel: 'info', + + // For visualizing the bundle. + // See https://esbuild.github.io/api/#metafile and https://esbuild.github.io/analyze/. + metafile: true, +}) + +await fs.writeFile('meta.json', JSON.stringify(result.metafile, null, 2)) diff --git a/packages/babel-config/dependencyGraph.dist.svg b/packages/babel-config/dependencyGraph.dist.svg new file mode 100644 index 000000000000..bd812d389362 --- /dev/null +++ b/packages/babel-config/dependencyGraph.dist.svg @@ -0,0 +1,2345 @@ + + + + + + +dependency-cruiser output + + +cluster_node_modules + +node_modules + + +cluster_node_modules/@babel + +@babel + + +cluster_node_modules/@iarna + +@iarna + + +cluster_node_modules/@prisma + +@prisma + + +cluster_packages + +packages + + +cluster_packages/project-config + +project-config + + +cluster_packages/project-config/dist + +dist + + +cluster_packages/structure + +structure + + +cluster_packages/structure/dist + +dist + + +cluster_packages/structure/dist/x + +x + + +cluster_packages/structure/dist/model + +model + + +cluster_packages/structure/dist/model/util + +util + + +cluster_packages/babel-config + +babel-config + + +cluster_packages/babel-config/dist + +dist + + +cluster_packages/babel-config/dist/plugins + +plugins + + + +crypto + + +crypto + + + + + +fs + + +fs + + + + + +node_modules/@babel/core + + + + + +core + + + + + +node_modules/@babel/register + + + + + +register + + + + + +node_modules/@babel/runtime-corejs3 + + + + + +runtime-corejs3 + + + + + +node_modules/@iarna/toml + + + + + +toml + + + + + +node_modules/@prisma/internals + + + + + +internals + + + + + +node_modules/core-js + + + + + +core-js + + + + + +node_modules/deepmerge + + + + + +deepmerge + + + + + +node_modules/dotenv-defaults + + + + + +dotenv-defaults + + + + + +node_modules/fast-glob + + + + + +fast-glob + + + + + +node_modules/fs-extra + + + + + +fs-extra + + + + + +node_modules/graphql + + + + + +graphql + + + + + +node_modules/lazy-get-decorator + + + + + +lazy-get-decorator + + + + + +node_modules/line-column + + + + + +line-column + + + + + +node_modules/lodash + + + + + +lodash + + + + + +node_modules/lodash-decorators + + + + + +lodash-decorators + + + + + +node_modules/lru-cache + + + + + +lru-cache + + + + + +node_modules/string-env-interpolation + + + + + +string-env-interpolation + + + + + +node_modules/ts-morph + + + + + +ts-morph + + + + + +node_modules/vscode-languageserver + + + + + +vscode-languageserver + + + + + +node_modules/vscode-languageserver-types + + + + + +vscode-languageserver-types + + + + + +packages/babel-config/dist/api.d.ts + + +api.d.ts +100% + + + + + +packages/babel-config/dist/api.d.ts->node_modules/@babel/core + + + + + +packages/babel-config/dist/common.js + + +common.js +50% + + + + + +packages/babel-config/dist/api.d.ts->packages/babel-config/dist/common.js + + + + + +packages/babel-config/dist/common.js->fs + + + + + +packages/babel-config/dist/common.js->node_modules/@babel/register + + + + + +packages/project-config/dist/index.js + + +index.js +38% + + + + + +packages/babel-config/dist/common.js->packages/project-config/dist/index.js + + + + + +path + + +path + + + + + +packages/babel-config/dist/common.js->path + + + + + +packages/babel-config/package.json + + +package.json +0% + + + + + +packages/babel-config/dist/common.js->packages/babel-config/package.json + + + + + +packages/babel-config/dist/web.js + + +web.js +77% + + + + + +packages/babel-config/dist/common.js->packages/babel-config/dist/web.js + + + + + +no-circular + + + +packages/babel-config/dist/api.js + + +api.js +80% + + + + + +packages/babel-config/dist/api.js->fs + + + + + +packages/babel-config/dist/api.js->node_modules/@babel/core + + + + + +packages/babel-config/dist/api.js->packages/babel-config/dist/common.js + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-directory-named-import.js + + +babel-plugin-redwood-directory-named-import.js +50% + + + + + +packages/babel-config/dist/api.js->packages/babel-config/dist/plugins/babel-plugin-redwood-directory-named-import.js + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-import-dir.js + + +babel-plugin-redwood-import-dir.js +75% + + + + + +packages/babel-config/dist/api.js->packages/babel-config/dist/plugins/babel-plugin-redwood-import-dir.js + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-otel-wrapping.js + + +babel-plugin-redwood-otel-wrapping.js +67% + + + + + +packages/babel-config/dist/api.js->packages/babel-config/dist/plugins/babel-plugin-redwood-otel-wrapping.js + + + + + +packages/babel-config/dist/api.js->packages/project-config/dist/index.js + + + + + +packages/babel-config/dist/api.js->path + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-directory-named-import.js->packages/project-config/dist/index.js + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-directory-named-import.js->path + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-import-dir.js->node_modules/fast-glob + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-import-dir.js->packages/project-config/dist/index.js + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-import-dir.js->path + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-otel-wrapping.js->packages/project-config/dist/index.js + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-otel-wrapping.js->path + + + + + +packages/project-config/dist/index.js->fs + + + + + +packages/project-config/dist/index.js->node_modules/@iarna/toml + + + + + +packages/project-config/dist/index.js->node_modules/deepmerge + + + + + +packages/project-config/dist/index.js->node_modules/fast-glob + + + + + +packages/project-config/dist/index.js->node_modules/string-env-interpolation + + + + + +packages/project-config/dist/index.js->path + + + + + +packages/babel-config/dist/common.d.ts + + +common.d.ts +100% + + + + + +packages/babel-config/dist/common.d.ts->node_modules/@babel/core + + + + + +packages/babel-config/dist/web.js->fs + + + + + +packages/babel-config/dist/web.js->node_modules/@babel/core + + + + + +packages/babel-config/dist/web.js->packages/babel-config/dist/common.js + + + + + +no-circular + + + +packages/babel-config/dist/web.js->packages/babel-config/dist/plugins/babel-plugin-redwood-directory-named-import.js + + + + + +packages/babel-config/dist/web.js->packages/project-config/dist/index.js + + + + + +packages/babel-config/dist/web.js->path + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-cell.js + + +babel-plugin-redwood-cell.js +50% + + + + + +packages/babel-config/dist/web.js->packages/babel-config/dist/plugins/babel-plugin-redwood-cell.js + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-mock-cell-data.js + + +babel-plugin-redwood-mock-cell-data.js +75% + + + + + +packages/babel-config/dist/web.js->packages/babel-config/dist/plugins/babel-plugin-redwood-mock-cell-data.js + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-remove-dev-fatal-error-page.js + + +babel-plugin-redwood-remove-dev-fatal-error-page.js +0% + + + + + +packages/babel-config/dist/web.js->packages/babel-config/dist/plugins/babel-plugin-redwood-remove-dev-fatal-error-page.js + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-routes-auto-loader.js + + +babel-plugin-redwood-routes-auto-loader.js +67% + + + + + +packages/babel-config/dist/web.js->packages/babel-config/dist/plugins/babel-plugin-redwood-routes-auto-loader.js + + + + + +packages/babel-config/dist/index.d.ts + + +index.d.ts +100% + + + + + +packages/babel-config/dist/index.d.ts->packages/babel-config/dist/common.js + + + + + +packages/babel-config/dist/index.d.ts->packages/babel-config/dist/api.js + + + + + +packages/babel-config/dist/index.d.ts->packages/babel-config/dist/web.js + + + + + +packages/babel-config/dist/index.js + + +index.js +100% + + + + + +packages/babel-config/dist/index.js->packages/babel-config/dist/common.js + + + + + +packages/babel-config/dist/index.js->packages/babel-config/dist/api.js + + + + + +packages/babel-config/dist/index.js->packages/babel-config/dist/web.js + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-cell.d.ts + + +babel-plugin-redwood-cell.d.ts +100% + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-cell.d.ts->node_modules/@babel/core + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-cell.js->path + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-directory-named-import.d.ts + + +babel-plugin-redwood-directory-named-import.d.ts +100% + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-directory-named-import.d.ts->node_modules/@babel/core + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-import-dir.d.ts + + +babel-plugin-redwood-import-dir.d.ts +100% + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-import-dir.d.ts->node_modules/@babel/core + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-mock-cell-data.d.ts + + +babel-plugin-redwood-mock-cell-data.d.ts +100% + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-mock-cell-data.d.ts->node_modules/@babel/core + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-mock-cell-data.js->packages/project-config/dist/index.js + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-mock-cell-data.js->path + + + + + +packages/structure/dist/index.js + + +index.js +86% + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-mock-cell-data.js->packages/structure/dist/index.js + + + + + +packages/structure/dist/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/index.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/hosts.js + + +hosts.js +83% + + + + + +packages/structure/dist/index.js->packages/structure/dist/hosts.js + + + + + +packages/structure/dist/x/URL.js + + +URL.js +47% + + + + + +packages/structure/dist/index.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/index.js + + +index.js +67% + + + + + +packages/structure/dist/index.js->packages/structure/dist/model/index.js + + + + + +packages/structure/dist/x/vscode-languageserver-types.js + + +vscode-languageserver-types.js +58% + + + + + +packages/structure/dist/index.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-otel-wrapping.d.ts + + +babel-plugin-redwood-otel-wrapping.d.ts +100% + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-otel-wrapping.d.ts->node_modules/@babel/core + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-remove-dev-fatal-error-page.d.ts + + +babel-plugin-redwood-remove-dev-fatal-error-page.d.ts +100% + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-remove-dev-fatal-error-page.d.ts->node_modules/@babel/core + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-routes-auto-loader.d.ts + + +babel-plugin-redwood-routes-auto-loader.d.ts +100% + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-routes-auto-loader.d.ts->node_modules/@babel/core + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-routes-auto-loader.js->packages/project-config/dist/index.js + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-routes-auto-loader.js->path + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-src-alias.d.ts + + +babel-plugin-redwood-src-alias.d.ts +100% + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-src-alias.d.ts->node_modules/@babel/core + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-src-alias.js + + +babel-plugin-redwood-src-alias.js +100% + + + + + +packages/babel-config/dist/plugins/babel-plugin-redwood-src-alias.js->path + + + + + +packages/babel-config/dist/web.d.ts + + +web.d.ts +100% + + + + + +packages/babel-config/dist/web.d.ts->node_modules/@babel/core + + + + + +packages/babel-config/dist/web.d.ts->packages/babel-config/dist/common.js + + + + + +packages/structure/dist/errors.js + + +errors.js +20% + + + + + +packages/structure/dist/errors.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/hosts.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/hosts.js->node_modules/fast-glob + + + + + +packages/structure/dist/hosts.js->node_modules/fs-extra + + + + + +packages/structure/dist/hosts.js->packages/project-config/dist/index.js + + + + + +packages/structure/dist/x/decorators.js + + +decorators.js +17% + + + + + +packages/structure/dist/hosts.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/x/decorators.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/decorators.js->node_modules/lazy-get-decorator + + + + + +packages/structure/dist/x/decorators.js->node_modules/lodash-decorators + + + + + +packages/structure/dist/ide.js + + +ide.js +58% + + + + + +packages/structure/dist/ide.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/ide.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/ide.js->path + + + + + +packages/structure/dist/ide.js->packages/structure/dist/hosts.js + + + + + +packages/structure/dist/ide.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/x/Array.js + + +Array.js +40% + + + + + +packages/structure/dist/ide.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/x/path.js + + +path.js +67% + + + + + +packages/structure/dist/ide.js->packages/structure/dist/x/path.js + + + + + +packages/structure/dist/x/ts-morph.js + + +ts-morph.js +78% + + + + + +packages/structure/dist/ide.js->packages/structure/dist/x/ts-morph.js + + + + + +packages/structure/dist/ide.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/x/Array.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/path.js->fs + + + + + +packages/structure/dist/x/path.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/path.js->node_modules/core-js + + + + + +packages/structure/dist/x/path.js->path + + + + + +packages/structure/dist/x/ts-morph.js->crypto + + + + + +packages/structure/dist/x/ts-morph.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/ts-morph.js->node_modules/lodash + + + + + +packages/structure/dist/x/ts-morph.js->node_modules/lru-cache + + + + + +packages/structure/dist/x/ts-morph.js->node_modules/ts-morph + + + + + +packages/structure/dist/x/URL.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/URL.js->path + + + + + +packages/structure/dist/model/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWProject.js + + +RWProject.js +96% + + + + + +packages/structure/dist/model/index.js->packages/structure/dist/model/RWProject.js + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/core-js + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/line-column + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/lodash + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/ts-morph + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/RWCell.js + + +RWCell.js +92% + + + + + +packages/structure/dist/model/RWCell.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWCell.js->node_modules/graphql + + + + + +packages/structure/dist/model/RWCell.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWCell.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWCell.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWCell.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/model/RWComponent.js + + +RWComponent.js +82% + + + + + +packages/structure/dist/model/RWCell.js->packages/structure/dist/model/RWComponent.js + + + + + +packages/structure/dist/model/RWComponent.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWComponent.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWComponent.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWComponent.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWEnvHelper.js + + +RWEnvHelper.js +95% + + + + + +packages/structure/dist/model/RWEnvHelper.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWEnvHelper.js->node_modules/dotenv-defaults + + + + + +packages/structure/dist/model/RWEnvHelper.js->node_modules/fs-extra + + + + + +packages/structure/dist/model/RWEnvHelper.js->node_modules/lodash + + + + + +packages/structure/dist/model/RWEnvHelper.js->node_modules/vscode-languageserver + + + + + +packages/structure/dist/model/RWEnvHelper.js->path + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/x/prisma.js + + +prisma.js +89% + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/x/prisma.js + + + + + +packages/structure/dist/x/vscode.js + + +vscode.js +94% + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/x/vscode.js + + + + + +packages/structure/dist/model/util/process_env.js + + +process_env.js +92% + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/model/util/process_env.js + + + + + +packages/structure/dist/x/prisma.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/prisma.js->node_modules/core-js + + + + + +packages/structure/dist/x/prisma.js->node_modules/fs-extra + + + + + +packages/structure/dist/x/prisma.js->node_modules/vscode-languageserver + + + + + +packages/structure/dist/x/prisma.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/x/prisma.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/x/vscode.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/vscode.js->node_modules/core-js + + + + + +packages/structure/dist/x/vscode.js->node_modules/lodash + + + + + +packages/structure/dist/x/vscode.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/x/vscode.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/util/process_env.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/util/process_env.js->node_modules/fast-glob + + + + + +packages/structure/dist/model/util/process_env.js->node_modules/fs-extra + + + + + +packages/structure/dist/model/util/process_env.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/util/process_env.js->path + + + + + +packages/structure/dist/model/util/process_env.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/model/util/process_env.js->packages/structure/dist/x/ts-morph.js + + + + + +packages/structure/dist/model/RWFunction.js + + +RWFunction.js +67% + + + + + +packages/structure/dist/model/RWFunction.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWFunction.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWLayout.js + + +RWLayout.js +67% + + + + + +packages/structure/dist/model/RWLayout.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWLayout.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWPage.js + + +RWPage.js +93% + + + + + +packages/structure/dist/model/RWPage.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWPage.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWPage.js->path + + + + + +packages/structure/dist/model/RWPage.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWPage.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWProject.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWProject.js->node_modules/@prisma/internals + + + + + +packages/structure/dist/model/RWProject.js->packages/project-config/dist/index.js + + + + + +packages/structure/dist/model/RWProject.js->path + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/x/path.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWCell.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWComponent.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWEnvHelper.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWFunction.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWLayout.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWPage.js + + + + + +packages/structure/dist/model/RWRouter.js + + +RWRouter.js +94% + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWRouter.js + + + + + +packages/structure/dist/model/RWSDL.js + + +RWSDL.js +94% + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWSDL.js + + + + + +packages/structure/dist/model/RWService.js + + +RWService.js +92% + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWService.js + + + + + +packages/structure/dist/model/RWTOML.js + + +RWTOML.js +90% + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWTOML.js + + + + + +packages/structure/dist/model/RWRouter.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWRouter.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWRouter.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/errors.js + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/model/RWRoute.js + + +RWRoute.js +94% + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/model/RWRoute.js + + + + + +packages/structure/dist/model/RWSDL.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWSDL.js->node_modules/graphql + + + + + +packages/structure/dist/model/RWSDL.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWSDL.js->path + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/errors.js + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/model/RWSDLField.js + + +RWSDLField.js +92% + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/model/RWSDLField.js + + + + + +packages/structure/dist/model/RWService.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWService.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWService.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWService.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWService.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/model/RWService.js->packages/structure/dist/x/path.js + + + + + +packages/structure/dist/model/RWServiceFunction.js + + +RWServiceFunction.js +92% + + + + + +packages/structure/dist/model/RWService.js->packages/structure/dist/model/RWServiceFunction.js + + + + + +packages/structure/dist/model/RWTOML.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWTOML.js->node_modules/@iarna/toml + + + + + +packages/structure/dist/model/RWTOML.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWTOML.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWTOML.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWTOML.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/model/RWRoute.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWRoute.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWRoute.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWRoute.js->path + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/errors.js + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/util.js + + +util.js +83% + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/util.js + + + + + +packages/structure/dist/model/util/advanced_path_parser.js + + +advanced_path_parser.js +83% + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/model/util/advanced_path_parser.js + + + + + +packages/structure/dist/util.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/util/advanced_path_parser.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/util/advanced_path_parser.js->node_modules/core-js + + + + + +packages/structure/dist/model/RWSDLField.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWSDLField.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWSDLField.js->packages/structure/dist/errors.js + + + + + +packages/structure/dist/model/RWSDLField.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWSDLField.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWSDLField.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/RWSDLField.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/model/RWServiceFunction.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWServiceFunction.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWServiceFunction.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWServiceFunction.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWServiceFunction.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWServiceFunction.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/model/RWServiceFunction.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + diff --git a/packages/babel-config/dist.test.ts b/packages/babel-config/dist.test.ts new file mode 100644 index 000000000000..e9a2244cbb46 --- /dev/null +++ b/packages/babel-config/dist.test.ts @@ -0,0 +1,43 @@ +import path from 'path' + +const distPath = path.join(__dirname, 'dist') + +describe('dist', () => { + it('exports', async () => { + const mod = await import(path.join(distPath, 'index.js')) + + expect(mod).toMatchInlineSnapshot(` + { + "BABEL_PLUGIN_TRANSFORM_RUNTIME_OPTIONS": { + "corejs": { + "proposals": true, + "version": 3, + }, + "version": "7.23.5", + }, + "CORE_JS_VERSION": "3.33", + "RUNTIME_CORE_JS_VERSION": "7.23.5", + "TARGETS_NODE": "20.10", + "getApiSideBabelConfigPath": [Function], + "getApiSideBabelPlugins": [Function], + "getApiSideBabelPresets": [Function], + "getApiSideDefaultBabelConfig": [Function], + "getCommonPlugins": [Function], + "getPathsFromConfig": [Function], + "getRouteHookBabelPlugins": [Function], + "getWebSideBabelConfigPath": [Function], + "getWebSideBabelPlugins": [Function], + "getWebSideBabelPresets": [Function], + "getWebSideDefaultBabelConfig": [Function], + "getWebSideOverrides": [Function], + "parseTypeScriptConfigFiles": [Function], + "prebuildApiFile": [Function], + "prebuildWebFile": [Function], + "registerApiSideBabelHook": [Function], + "registerBabel": [Function], + "registerWebSideBabelHook": [Function], + "transformWithBabel": [Function], + } + `) + }) +}) diff --git a/packages/babel-config/jest.config.js b/packages/babel-config/jest.config.js new file mode 100644 index 000000000000..a191690e9a42 --- /dev/null +++ b/packages/babel-config/jest.config.js @@ -0,0 +1,3 @@ +module.exports = { + testPathIgnorePatterns: ['fixtures'], +} diff --git a/packages/babel-config/package.json b/packages/babel-config/package.json new file mode 100644 index 000000000000..3f80b5376868 --- /dev/null +++ b/packages/babel-config/package.json @@ -0,0 +1,54 @@ +{ + "name": "@redwoodjs/babel-config", + "version": "6.0.7", + "repository": { + "type": "git", + "url": "https://github.com/redwoodjs/redwood.git", + "directory": "packages/babel-config" + }, + "license": "MIT", + "exports": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "yarn node ./build.mjs && run build:types", + "build:types": "tsc --build --verbose", + "build:watch": "nodemon --watch src --ext \"js,ts,tsx\" --ignore dist --exec \"yarn build\"", + "prepublishOnly": "NODE_ENV=production yarn build", + "test": "jest src", + "test:watch": "run test --watch" + }, + "dependencies": { + "@babel/core": "^7.22.20", + "@babel/parser": "^7.22.16", + "@babel/plugin-transform-class-properties": "^7.22.5", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.11", + "@babel/plugin-transform-react-jsx": "^7.22.15", + "@babel/plugin-transform-runtime": "7.23.6", + "@babel/preset-env": "^7.22.20", + "@babel/preset-react": "^7.22.15", + "@babel/preset-typescript": "^7.22.15", + "@babel/register": "^7.22.15", + "@babel/runtime-corejs3": "7.23.6", + "@babel/traverse": "^7.22.20", + "@redwoodjs/project-config": "6.0.7", + "babel-plugin-auto-import": "1.1.0", + "babel-plugin-graphql-tag": "3.3.0", + "babel-plugin-module-resolver": "5.0.0", + "core-js": "3.34.0", + "fast-glob": "3.3.2", + "graphql": "16.8.1", + "typescript": "5.3.3" + }, + "devDependencies": { + "@types/babel-plugin-tester": "9.0.9", + "@types/babel__core": "7.20.4", + "babel-plugin-tester": "11.0.4", + "esbuild": "0.19.9", + "jest": "29.7.0" + }, + "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" +} diff --git a/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/package.json b/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/package.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/src/lib/autoImports.ts b/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/src/lib/autoImports.ts new file mode 100644 index 000000000000..679cc3e6751d --- /dev/null +++ b/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/src/lib/autoImports.ts @@ -0,0 +1,13 @@ +export const schema = gql` + type User { + id: Int! + email: String! + fullName: String! + roles: String + posts: [Post]! + } +` + +export const isAuthenticated = () => { + return !!context.currentUser +} diff --git a/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/src/lib/directoryGlobImports.ts b/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/src/lib/directoryGlobImports.ts new file mode 100644 index 000000000000..81617a48f9d9 --- /dev/null +++ b/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/src/lib/directoryGlobImports.ts @@ -0,0 +1,3 @@ +import directives from 'src/directives/**/*.{js,ts}' +import sdls from 'src/graphql/**/*.sdl.{js,ts}' +import services from 'src/services/**/*.{js,ts}' diff --git a/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/src/lib/polyfill.js b/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/src/lib/polyfill.js new file mode 100644 index 000000000000..6bcfead7daa1 --- /dev/null +++ b/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/src/lib/polyfill.js @@ -0,0 +1,137 @@ +// These examples have been taken from the core-js README, the proposal's README, or MDN. + +/** + * # ES + */ + +// ## Node.js 13 + +Math.hypot(3, 4) + +// ## Node.js 14 + +for (let [_, d, D] of '1111a2b3cccc'.matchAll(/(\d)(\D)/g)) { + console.log(d, D) +} + +// ## Node.js 15 + +const error1 = new TypeError('Error 1') +const error2 = new TypeError('Error 2') +const aggregate = new AggregateError([error1, error2], 'Collected errors') + +Promise.any([Promise.resolve(1), Promise.reject(2), Promise.resolve(3)]).then( + console.log +) +Promise.any([Promise.reject(1), Promise.reject(2), Promise.reject(3)]).catch( + ({ errors }) => console.log(errors) +) + +'Test abc test test abc test.'.replaceAll('abc', 'foo') + +const buffer = new ArrayBuffer(8) +const uint8 = new Uint8Array(buffer) +uint8.set([1, 2, 3], 3) + +/** + * # ES Next + */ + +// ## Pre-stage 0 + +Reflect.defineMetadata(metadataKey, metadataValue, target) +Reflect.deleteMetadata(metadataKey, target) +Reflect.getMetadata(metadataKey, target) +Reflect.getMetadataKeys(target) +Reflect.getOwnMetadata(metadataKey, target) +Reflect.getOwnMetadataKeys(target) +Reflect.hasMetadata(metadataKey, target) +Reflect.hasOwnMetadata(metadataKey, target) +Reflect.metadata(metadataKey, metadataValue) + +// Stage 1 +;[1, 2, 3].lastIndex +;[1, 2, 3].lastItem + +const array = [1, 2, 3] +array.lastItem = 4 + +new Array(1, 2, 3).lastIndex +new Array(1, 2, 3).lastItem + +const key = compositeKey({}) +const symbol = compositeSymbol({}) + +const m = new Map() +m.deleteAll() +const s = new Set() +s.addAll() +const wm = new WeakMap() +wm.deleteAll() +const ws = new WeakSet() +ws.addAll() + +Math.clamp(2, 1, 3) +Math.DEG_PER_RAD +Math.degrees(1) +Math.fscale(5, 1, 1, 2, 2) +Math.RAD_PER_DEG +Math.radians(360) +Math.scale(5, 1, 1, 2, 2) + +Math.signbit(NaN) + +Number.fromString('42') + +new Observable((observer) => { + observer.next('hello') + observer.next('world') + observer.complete() +}).subscribe({ + next(it) { + console.log(it) + }, + complete() { + console.log('!') + }, +}) +Symbol.observable + +for (let { codePoint, position } of 'qwe'.codePoints()) { + console.log(codePoint) + console.log(position) +} + +Symbol.patternMatch + +// Stage 2 + +Symbol.dispose + +/** + * Withdrawn ES Next + */ + +Math.iaddh(lo0, hi0, lo1, hi1) +Math.imulh(a, b) +Math.isubh(lo0, hi0, lo1, hi1) +Math.umulh(a, b) + +Promise.try(() => 42).then((it) => console.log(`Promise, resolved as ${it}`)) +Promise.try(() => { + throw 42 +}).catch((it) => console.log(`Promise, rejected as ${it}`)) + +'a𠮷b'.at(1) +'a𠮷b'.at(1).length + +/** + * Unstable + */ + +for (let x of Math.seededPRNG({ seed: 42 })) { + console.log(x) + if (x > 0.8) { + break + } +} diff --git a/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/src/lib/transform.js b/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/src/lib/transform.js new file mode 100644 index 000000000000..375b4bdd3b60 --- /dev/null +++ b/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/src/lib/transform.js @@ -0,0 +1,20 @@ +// Some of the examples here are from https://babeljs.io/docs/babel-plugin-transform-runtime/#core-js-aliasing. + +var sym = Symbol() + +var promise = Promise.resolve() + +var check = arr.includes('yeah!') + +console.log(arr[Symbol.iterator]()) + +Promise.allSettled() + +console.log([].includes('bazinga')) + +Promise.any() + +Object.hasOwn({ x: 2 }, 'x') + +var arr = [1, 2, 3] +arr.at(0) === 1 diff --git a/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/src/lib/typescript.ts b/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/src/lib/typescript.ts new file mode 100644 index 000000000000..00644680f066 --- /dev/null +++ b/packages/babel-config/src/__tests__/__fixtures__/redwood-app/api/src/lib/typescript.ts @@ -0,0 +1,4 @@ +// Disabling linting here so that we can test `prebuildApiFile`. +// +// eslint-disable-next-line prettier/prettier, @typescript-eslint/no-inferrable-types +const x: number = 0; diff --git a/packages/babel-config/src/__tests__/__fixtures__/redwood-app/redwood.toml b/packages/babel-config/src/__tests__/__fixtures__/redwood-app/redwood.toml new file mode 100644 index 000000000000..f68d0d5de4db --- /dev/null +++ b/packages/babel-config/src/__tests__/__fixtures__/redwood-app/redwood.toml @@ -0,0 +1,10 @@ +[web] + port = 8910 + apiProxyPath = "/api/functions" + +[api] + port = 8911 + [api.paths] + functions = './api/src/functions' + graphql = './api/src/graphql' + generated = './api/generated' diff --git a/packages/babel-config/src/__tests__/api.test.ts b/packages/babel-config/src/__tests__/api.test.ts new file mode 100644 index 000000000000..a3c7af764927 --- /dev/null +++ b/packages/babel-config/src/__tests__/api.test.ts @@ -0,0 +1,249 @@ +import { vol } from 'memfs' + +import { getPaths, ensurePosixPath } from '@redwoodjs/project-config' + +import { + getApiSideBabelConfigPath, + getApiSideBabelPlugins, + getApiSideBabelPresets, + TARGETS_NODE, +} from '../api' + +jest.mock('fs', () => require('memfs').fs) + +const redwoodProjectPath = '/redwood-app' +process.env.RWJS_CWD = redwoodProjectPath + +afterEach(() => { + vol.reset() +}) + +describe('api', () => { + test("TARGETS_NODE hasn't unintentionally changed", () => { + expect(TARGETS_NODE).toMatchInlineSnapshot(`"20.10"`) + }) + + describe('getApiSideBabelPresets', () => { + it('only includes `@babel/preset-typescript` by default', () => { + const apiSideBabelPresets = getApiSideBabelPresets() + expect(apiSideBabelPresets).toMatchInlineSnapshot(` + [ + [ + "@babel/preset-typescript", + { + "allExtensions": true, + "isTSX": true, + }, + "rwjs-babel-preset-typescript", + ], + ] + `) + }) + + it('can include `@babel/preset-env`', () => { + const apiSideBabelPresets = getApiSideBabelPresets({ presetEnv: true }) + expect(apiSideBabelPresets).toMatchInlineSnapshot(` + [ + [ + "@babel/preset-typescript", + { + "allExtensions": true, + "isTSX": true, + }, + "rwjs-babel-preset-typescript", + ], + [ + "@babel/preset-env", + { + "corejs": { + "proposals": true, + "version": "3.34", + }, + "exclude": [ + "@babel/plugin-transform-class-properties", + "@babel/plugin-transform-private-methods", + ], + "targets": { + "node": "20.10", + }, + "useBuiltIns": "usage", + }, + ], + ] + `) + }) + }) + + describe('getApiSideBabelConfigPath', () => { + it("gets babel.config.js if it's there", () => { + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + 'babel.config.js': '', + }, + }, + redwoodProjectPath + ) + + const apiSideBabelConfigPath = getApiSideBabelConfigPath() + expect(ensurePosixPath(apiSideBabelConfigPath)).toMatch( + '/redwood-app/api/babel.config.js' + ) + }) + + it("returns undefined if it's not there", () => { + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: {}, + }, + redwoodProjectPath + ) + + const apiSideBabelConfigPath = getApiSideBabelConfigPath() + expect(apiSideBabelConfigPath).toBeUndefined() + }) + }) + + describe('getApiSideBabelPlugins', () => { + it('returns babel plugins', () => { + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: {}, + }, + redwoodProjectPath + ) + + const apiSideBabelPlugins = getApiSideBabelPlugins() + expect(apiSideBabelPlugins).toHaveLength(10) + + const pluginNames = apiSideBabelPlugins.map(([name]) => name) + expect(pluginNames).toMatchInlineSnapshot(` + [ + "@babel/plugin-transform-class-properties", + "@babel/plugin-transform-private-methods", + "@babel/plugin-transform-private-property-in-object", + "@babel/plugin-transform-react-jsx", + "@babel/plugin-transform-runtime", + "babel-plugin-module-resolver", + [Function], + "babel-plugin-auto-import", + "babel-plugin-graphql-tag", + [Function], + ] + `) + + const pluginAliases = getPluginAliases(apiSideBabelPlugins) + expect(pluginAliases).toMatchInlineSnapshot(` + [ + "rwjs-api-module-resolver", + "rwjs-babel-directory-named-modules", + "rwjs-babel-auto-import", + "rwjs-babel-graphql-tag", + "rwjs-babel-glob-import-dir", + ] + `) + + expect(apiSideBabelPlugins).toContainEqual([ + '@babel/plugin-transform-class-properties', + { + loose: true, + }, + ]) + + expect(apiSideBabelPlugins).toContainEqual([ + '@babel/plugin-transform-private-methods', + { + loose: true, + }, + ]) + + expect(apiSideBabelPlugins).toContainEqual([ + '@babel/plugin-transform-private-property-in-object', + { + loose: true, + }, + ]) + + expect(apiSideBabelPlugins).toContainEqual([ + '@babel/plugin-transform-runtime', + { + corejs: { + proposals: true, + version: 3, + }, + version: '7.23.6', + }, + ]) + + expect(apiSideBabelPlugins).toContainEqual([ + '@babel/plugin-transform-react-jsx', + { + runtime: 'automatic', + }, + ]) + + const [_, babelPluginModuleResolverConfig] = apiSideBabelPlugins.find( + (plugin) => plugin[0] === 'babel-plugin-module-resolver' + ) + + expect(babelPluginModuleResolverConfig).toMatchObject({ + alias: { + src: './src', + }, + + cwd: 'packagejson', + loglevel: 'silent', // to silence the unnecessary warnings + }) + + expect(babelPluginModuleResolverConfig.root[0]).toMatch( + getPaths().api.base + ) + + expect(apiSideBabelPlugins).toContainEqual([ + 'babel-plugin-auto-import', + { + declarations: [ + { + default: 'gql', + path: 'graphql-tag', + }, + { + members: ['context'], + path: '@redwoodjs/context', + }, + ], + }, + 'rwjs-babel-auto-import', + ]) + }) + + it('can include openTelemetry', () => { + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: {}, + }, + redwoodProjectPath + ) + + const apiSideBabelPlugins = getApiSideBabelPlugins({ + openTelemetry: true, + }) + const pluginAliases = getPluginAliases(apiSideBabelPlugins) + expect(pluginAliases).toContain('rwjs-babel-otel-wrapping') + }) + }) +}) + +function getPluginAliases(plugins) { + return plugins.reduce((pluginAliases, plugin) => { + if (plugin.length !== 3) { + return pluginAliases + } + + return [...pluginAliases, plugin[2]] + }, []) +} diff --git a/packages/babel-config/src/__tests__/common.test.ts b/packages/babel-config/src/__tests__/common.test.ts new file mode 100644 index 000000000000..f5fa4f55eefc --- /dev/null +++ b/packages/babel-config/src/__tests__/common.test.ts @@ -0,0 +1,37 @@ +import { vol } from 'memfs' + +import { getCommonPlugins } from '../common' + +const redwoodProjectPath = '/redwood-app' +process.env.RWJS_CWD = redwoodProjectPath + +afterEach(() => { + vol.reset() +}) + +test("common plugins haven't changed unintentionally", () => { + const commonPlugins = getCommonPlugins() + + expect(commonPlugins).toMatchInlineSnapshot(` + [ + [ + "@babel/plugin-transform-class-properties", + { + "loose": true, + }, + ], + [ + "@babel/plugin-transform-private-methods", + { + "loose": true, + }, + ], + [ + "@babel/plugin-transform-private-property-in-object", + { + "loose": true, + }, + ], + ] + `) +}) diff --git a/packages/babel-config/src/__tests__/prebuildApiFile.test.ts b/packages/babel-config/src/__tests__/prebuildApiFile.test.ts new file mode 100644 index 000000000000..3bfadf7a4368 --- /dev/null +++ b/packages/babel-config/src/__tests__/prebuildApiFile.test.ts @@ -0,0 +1,571 @@ +import path from 'path' + +import compat from 'core-js-compat' + +import { getPaths, getConfig } from '@redwoodjs/project-config' + +import { + BABEL_PLUGIN_TRANSFORM_RUNTIME_OPTIONS, + getApiSideBabelPlugins, + prebuildApiFile, + TARGETS_NODE, +} from '../api' + +const RWJS_CWD = path.join(__dirname, '__fixtures__/redwood-app') +process.env.RWJS_CWD = RWJS_CWD + +let code + +describe('api prebuild ', () => { + describe('polyfills unsupported functionality', () => { + beforeAll(() => { + const apiFile = path.join(RWJS_CWD, 'api/src/lib/polyfill.js') + code = prebuildApiFileWrapper(apiFile) + }) + + describe('ES features', () => { + describe('Node.js 13', () => { + it('polyfills Math.hypot', () => { + expect(code).toContain( + `import _Math$hypot from "@babel/runtime-corejs3/core-js/math/hypot"` + ) + }) + }) + + describe('Node.js 14', () => { + it('polyfills String.matchAll', () => { + expect(code).toContain( + `import _matchAllInstanceProperty from "@babel/runtime-corejs3/core-js/instance/match-all"` + ) + }) + }) + + describe('Node.js 15', () => { + it('polyfills AggregateError', () => { + expect(code).toContain( + `import _AggregateError from "@babel/runtime-corejs3/core-js/aggregate-error"` + ) + }) + + // The reason we're testing it this way is that core-js polyfills the entire Promise built in. + // + // So this... + // + // ```js + // Promise.any([ + // Promise.resolve(1), + // Promise.reject(2), + // Promise.resolve(3), + // ]).then(console.log) + // + // ↓↓↓ + // + // import _Promise from "@babel/runtime-corejs3/core-js/promise"; + // + // _Promise.any([ + // _Promise.resolve(1), + // _Promise.reject(2), + // _Promise.resolve(3)]) + // .then(console.log); + // ``` + // + // Compared to the Reflect polyfills, which only polyfill the method used, + // just checking that the Promise polyfill is imported isn't enough: + // + // ```js + // Reflect.defineMetadata(metadataKey, metadataValue, target) + // + // ↓↓↓ + // + // import _Reflect$defineMetadata from "@babel/runtime-corejs3/core-js/reflect/define-metadata"; + + // _Reflect$defineMetadata(metadataKey, metadataValue, target); + // ``` + it('polyfills Promise.any', () => { + expect(code).toContain( + `import _Promise from "@babel/runtime-corejs3/core-js/promise"` + ) + const _Promise = require('@babel/runtime-corejs3/core-js/promise') + expect(_Promise).toHaveProperty('any') + }) + + it('polyfills String.replaceAll', () => { + expect(code).toContain( + `import _replaceAllInstanceProperty from "@babel/runtime-corejs3/core-js/instance/replace-all"` + ) + }) + }) + + describe('Node.js 17', () => { + // core-js-pure overrides this. See https://github.com/zloirock/core-js/blob/master/packages/core-js-pure/override/modules/es.typed-array.set.js. + it("doesn't polyfill TypedArray.set", () => { + expect(code).toContain( + [ + `const buffer = new ArrayBuffer(8);`, + `const uint8 = new Uint8Array(buffer);`, + `uint8.set([1, 2, 3], 3);`, + ].join('\n') + ) + }) + }) + }) + + describe('ES Next features', () => { + describe('Pre-stage 0 proposals', () => { + // Reflect metadata + // See https://github.com/zloirock/core-js#reflect-metadata + it('polyfills Reflect methods', () => { + expect(code).toContain( + `import _Reflect$defineMetadata from "@babel/runtime-corejs3/core-js/reflect/define-metadata"` + ) + expect(code).toContain( + `import _Reflect$deleteMetadata from "@babel/runtime-corejs3/core-js/reflect/delete-metadata"` + ) + expect(code).toContain( + `import _Reflect$getMetadata from "@babel/runtime-corejs3/core-js/reflect/get-metadata"` + ) + expect(code).toContain( + `import _Reflect$getMetadataKeys from "@babel/runtime-corejs3/core-js/reflect/get-metadata-keys"` + ) + expect(code).toContain( + `import _Reflect$getOwnMetadata from "@babel/runtime-corejs3/core-js/reflect/get-own-metadata"` + ) + expect(code).toContain( + `import _Reflect$getOwnMetadataKeys from "@babel/runtime-corejs3/core-js/reflect/get-own-metadata-keys"` + ) + expect(code).toContain( + `import _Reflect$hasMetadata from "@babel/runtime-corejs3/core-js/reflect/has-metadata"` + ) + expect(code).toContain( + `import _Reflect$hasOwnMetadata from "@babel/runtime-corejs3/core-js/reflect/has-own-metadata"` + ) + expect(code).toContain( + `import _Reflect$metadata from "@babel/runtime-corejs3/core-js/reflect/metadata"` + ) + }) + }) + + describe('Stage 1 proposals', () => { + // Getting last item from Array + // See https://github.com/zloirock/core-js#getting-last-item-from-array + // + // core-js-pure overrides these. See... + // - https://github.com/zloirock/core-js/blob/master/packages/core-js-pure/override/modules/esnext.array.last-index.js, + // - https://github.com/zloirock/core-js/blob/master/packages/core-js-pure/override/modules/esnext.array.last-item.js + it("doesn't polyfill Getting last item from Array", () => { + expect(code).toContain( + [ + `[1, 2, 3].lastIndex;`, + `[1, 2, 3].lastItem;`, + `const array = [1, 2, 3];`, + `array.lastItem = 4;`, + `new Array(1, 2, 3).lastIndex;`, + `new Array(1, 2, 3).lastItem;`, + ].join('\n') + ) + }) + + // compositeKey and compositeSymbol + // See https://github.com/zloirock/core-js#compositekey-and-compositesymbol + it('polyfills compositeKey and compositeSymbol', () => { + expect(code).toContain( + `import _compositeKey from "@babel/runtime-corejs3/core-js/composite-key"` + ) + expect(code).toContain( + `import _compositeSymbol from "@babel/runtime-corejs3/core-js/composite-symbol"` + ) + }) + + // New collections methods + // See https://github.com/zloirock/core-js#new-collections-methods + it('polyfills New collections methods', () => { + expect(code).toContain( + `import _Map from "@babel/runtime-corejs3/core-js/map"` + ) + // See the comments on Promise.any above for more of an explanation + // of why we're testing for properties. + const _Map = require('@babel/runtime-corejs3/core-js/map') + expect(_Map).toHaveProperty('deleteAll') + expect(_Map).toHaveProperty('every') + expect(_Map).toHaveProperty('filter') + expect(_Map).toHaveProperty('find') + expect(_Map).toHaveProperty('findKey') + expect(_Map).toHaveProperty('from') + expect(_Map).toHaveProperty('groupBy') + expect(_Map).toHaveProperty('includes') + expect(_Map).toHaveProperty('keyBy') + expect(_Map).toHaveProperty('keyOf') + expect(_Map).toHaveProperty('mapKeys') + expect(_Map).toHaveProperty('mapValues') + expect(_Map).toHaveProperty('merge') + expect(_Map).toHaveProperty('of') + expect(_Map).toHaveProperty('reduce') + expect(_Map).toHaveProperty('some') + expect(_Map).toHaveProperty('update') + + expect(code).toContain( + `import _Set from "@babel/runtime-corejs3/core-js/set"` + ) + const _Set = require('@babel/runtime-corejs3/core-js/set') + expect(_Set).toHaveProperty('addAll') + expect(_Set).toHaveProperty('deleteAll') + expect(_Set).toHaveProperty('difference') + expect(_Set).toHaveProperty('every') + expect(_Set).toHaveProperty('filter') + expect(_Set).toHaveProperty('find') + expect(_Set).toHaveProperty('from') + expect(_Set).toHaveProperty('intersection') + expect(_Set).toHaveProperty('isDisjointFrom') + expect(_Set).toHaveProperty('isSubsetOf') + expect(_Set).toHaveProperty('isSupersetOf') + expect(_Set).toHaveProperty('join') + expect(_Set).toHaveProperty('map') + expect(_Set).toHaveProperty('of') + expect(_Set).toHaveProperty('reduce') + expect(_Set).toHaveProperty('some') + expect(_Set).toHaveProperty('symmetricDifference') + expect(_Set).toHaveProperty('union') + + expect(code).toContain( + `import _WeakMap from "@babel/runtime-corejs3/core-js/weak-map"` + ) + const _WeakMap = require('@babel/runtime-corejs3/core-js/weak-map') + expect(_WeakMap).toHaveProperty('deleteAll') + expect(_WeakMap).toHaveProperty('from') + expect(_WeakMap).toHaveProperty('of') + + expect(code).toContain( + `import _WeakSet from "@babel/runtime-corejs3/core-js/weak-set"` + ) + const _WeakSet = require('@babel/runtime-corejs3/core-js/weak-set') + expect(_WeakSet).toHaveProperty('addAll') + expect(_WeakSet).toHaveProperty('deleteAll') + expect(_WeakSet).toHaveProperty('from') + expect(_WeakSet).toHaveProperty('of') + }) + + // Math extensions + // See https://github.com/zloirock/core-js#math-extensions + it('polyfills Math extensions', () => { + expect(code).toContain( + `import _Math$clamp from "@babel/runtime-corejs3/core-js/math/clamp"` + ) + expect(code).toContain( + `import _Math$DEG_PER_RAD from "@babel/runtime-corejs3/core-js/math/deg-per-rad"` + ) + expect(code).toContain( + `import _Math$degrees from "@babel/runtime-corejs3/core-js/math/degrees"` + ) + expect(code).toContain( + `import _Math$fscale from "@babel/runtime-corejs3/core-js/math/fscale"` + ) + expect(code).toContain( + `import _Math$RAD_PER_DEG from "@babel/runtime-corejs3/core-js/math/rad-per-deg"` + ) + expect(code).toContain( + `import _Math$radians from "@babel/runtime-corejs3/core-js/math/radians"` + ) + expect(code).toContain( + `import _Math$scale from "@babel/runtime-corejs3/core-js/math/scale"` + ) + }) + + // Math.signbit + // See https://github.com/zloirock/core-js#mathsignbit + it('polyfills Math.signbit', () => { + expect(code).toContain( + `import _Math$signbit from "@babel/runtime-corejs3/core-js/math/signbit"` + ) + }) + + // Number.fromString + // See https://github.com/zloirock/core-js#numberfromstring + it('polyfills Number.fromString', () => { + expect(code).toContain( + `import _Number$fromString from "@babel/runtime-corejs3/core-js/number/from-string"` + ) + }) + + // Observable + // See https://github.com/zloirock/core-js#observable + it('polyfills Observable', () => { + expect(code).toContain( + `import _Observable from "@babel/runtime-corejs3/core-js/observable"` + ) + expect(code).toContain( + `import _Symbol$observable from "@babel/runtime-corejs3/core-js/symbol/observable"` + ) + }) + + // String.prototype.codePoints + // See https://github.com/zloirock/core-js#stringprototypecodepoints + it('polyfills String.prototype.codePoints', () => { + expect(code).toContain( + `import _codePointsInstanceProperty from "@babel/runtime-corejs3/core-js/instance/code-points"` + ) + }) + + // Symbol.matcher for pattern matching + // See https://github.com/zloirock/core-js#symbolmatcher-for-pattern-matching + // This one's been renamed to Symbol.matcher since core-js v3.0.0. But Symbol.patternMatch still works + it('polyfills Symbol.matcher', () => { + expect(code).toContain( + `import _Symbol$patternMatch from "@babel/runtime-corejs3/core-js/symbol/pattern-match"` + ) + }) + }) + + describe('Stage 2 proposals', () => { + // Symbol.{ asyncDispose, dispose } for using statement + // See https://github.com/zloirock/core-js#symbol-asyncdispose-dispose--for-using-statement + it('polyfills Symbol.{ asyncDispose, dispose } for using statement', () => { + expect(code).toContain( + `import _Symbol$dispose from "@babel/runtime-corejs3/core-js/symbol/dispose"` + ) + }) + }) + }) + + describe('Withdrawn proposals (will be removed in core-js 4)', () => { + // Efficient 64 bit arithmetic + // See https://github.com/zloirock/core-js#efficient-64-bit-arithmetic + it('polyfills efficient 64 bit arithmetic', () => { + expect(code).toContain( + `import _Math$iaddh from "@babel/runtime-corejs3/core-js/math/iaddh"` + ) + expect(code).toContain( + `import _Math$imulh from "@babel/runtime-corejs3/core-js/math/imulh"` + ) + expect(code).toContain( + `import _Math$isubh from "@babel/runtime-corejs3/core-js/math/isubh"` + ) + expect(code).toContain( + `import _Math$umulh from "@babel/runtime-corejs3/core-js/math/umulh"` + ) + }) + + // Promise.try + // See https://github.com/zloirock/core-js#promisetry + it('polyfills Promise.try', () => { + const _Promise = require('@babel/runtime-corejs3/core-js/promise') + expect(_Promise).toHaveProperty('try') + }) + + // String#at + // See https://github.com/zloirock/core-js#stringat + it('polyfills String#at', () => { + expect(code).toContain( + `import _atInstanceProperty from "@babel/runtime-corejs3/core-js/instance/at"` + ) + }) + }) + + describe('Unstable (will be removed in core-js 4)', () => { + // Seeded pseudo-random numbers + // See https://github.com/zloirock/core-js#seeded-pseudo-random-numbers + it('polyfills Seeded pseudo-random numbers', () => { + expect(code).toContain( + `import _Math$seededPRNG from "@babel/runtime-corejs3/core-js/math/seeded-prng"` + ) + }) + }) + + it('includes source maps', () => { + const sourceMaps = code.split('\n').pop() + + const sourceMapsMatcher = + '//# sourceMappingURL=data:application/json;charset=utf-8;base64,' + + expect(sourceMaps).toMatch(sourceMapsMatcher) + + const [_, base64EncodedFile] = sourceMaps.split(sourceMapsMatcher) + + const { sources } = JSON.parse( + Buffer.from(base64EncodedFile, 'base64').toString('utf-8') + ) + + expect(sources).toMatchInlineSnapshot(` + [ + "../../../../../api/src/lib/polyfill.js", + ] + `) + }) + }) + + describe('uses core-js3 aliasing', () => { + beforeAll(() => { + const apiFile = path.join(RWJS_CWD, 'api/src/lib/transform.js') + code = prebuildApiFileWrapper(apiFile) + }) + + it('works', () => { + // See https://babeljs.io/docs/en/babel-plugin-transform-runtime#core-js-aliasing + // This is because we configure the transform runtime plugin corejs + + // Polyfill for Symbol + expect(code).toContain( + `import _Symbol from "@babel/runtime-corejs3/core-js/symbol"` + ) + + // Polyfill for Promise + expect(code).toContain( + `import _Promise from "@babel/runtime-corejs3/core-js/promise"` + ) + + // Polyfill for .includes + expect(code).toContain( + 'import _includesInstanceProperty from "@babel/runtime-corejs3/core-js/instance/includes"' + ) + + // Polyfill for .iterator + expect(code).toContain( + `import _getIterator from "@babel/runtime-corejs3/core-js/get-iterator"` + ) + }) + }) + + describe('typescript', () => { + beforeAll(() => { + const apiFile = path.join(RWJS_CWD, 'api/src/lib/typescript.ts') + code = prebuildApiFileWrapper(apiFile) + }) + + it('transpiles ts to js', () => { + expect(code).toContain('const x = 0') + expect(code).not.toContain('const x: number = 0') + }) + }) + + describe('auto imports', () => { + beforeAll(() => { + const apiFile = path.join(RWJS_CWD, 'api/src/lib/autoImports.ts') + code = prebuildApiFileWrapper(apiFile) + }) + + it('auto imports', () => { + expect(code).toContain('import { context } from "@redwoodjs/context"') + expect(code).toContain('import gql from "graphql-tag"') + }) + }) + + test('core-js polyfill list', () => { + const { list } = compat({ + targets: { node: TARGETS_NODE }, + version: BABEL_PLUGIN_TRANSFORM_RUNTIME_OPTIONS.corejs.version, + }) + + /** + * Redwood targets Node.js 12, but that doesn't factor into what gets polyfilled + * because Redwood uses the plugin-transform-runtime polyfill strategy. + * + * Also, plugin-transform-runtime is pinned to core-js v3.0.0, + * so the list of available polyfill is a little outdated. + * Some "ES Next" polyfills have landed in v12+ Node.js versions. + */ + expect(list).toMatchInlineSnapshot(` + [ + "esnext.array.last-index", + "esnext.array.last-item", + "esnext.composite-key", + "esnext.composite-symbol", + "esnext.map.delete-all", + "esnext.map.every", + "esnext.map.filter", + "esnext.map.find", + "esnext.map.find-key", + "esnext.map.from", + "esnext.map.group-by", + "esnext.map.includes", + "esnext.map.key-by", + "esnext.map.key-of", + "esnext.map.map-keys", + "esnext.map.map-values", + "esnext.map.merge", + "esnext.map.of", + "esnext.map.reduce", + "esnext.map.some", + "esnext.map.update", + "esnext.math.clamp", + "esnext.math.deg-per-rad", + "esnext.math.degrees", + "esnext.math.fscale", + "esnext.math.iaddh", + "esnext.math.imulh", + "esnext.math.isubh", + "esnext.math.rad-per-deg", + "esnext.math.radians", + "esnext.math.scale", + "esnext.math.seeded-prng", + "esnext.math.signbit", + "esnext.math.umulh", + "esnext.number.from-string", + "esnext.observable", + "esnext.promise.try", + "esnext.reflect.define-metadata", + "esnext.reflect.delete-metadata", + "esnext.reflect.get-metadata", + "esnext.reflect.get-metadata-keys", + "esnext.reflect.get-own-metadata", + "esnext.reflect.get-own-metadata-keys", + "esnext.reflect.has-metadata", + "esnext.reflect.has-own-metadata", + "esnext.reflect.metadata", + "esnext.set.add-all", + "esnext.set.delete-all", + "esnext.set.difference", + "esnext.set.every", + "esnext.set.filter", + "esnext.set.find", + "esnext.set.from", + "esnext.set.intersection", + "esnext.set.is-disjoint-from", + "esnext.set.is-subset-of", + "esnext.set.is-superset-of", + "esnext.set.join", + "esnext.set.map", + "esnext.set.of", + "esnext.set.reduce", + "esnext.set.some", + "esnext.set.symmetric-difference", + "esnext.set.union", + "esnext.string.at", + "esnext.string.code-points", + "esnext.symbol.observable", + "esnext.symbol.pattern-match", + "esnext.weak-map.delete-all", + "esnext.weak-map.from", + "esnext.weak-map.of", + "esnext.weak-set.add-all", + "esnext.weak-set.delete-all", + "esnext.weak-set.from", + "esnext.weak-set.of", + ] + `) + }) +}) + +/** + * A copy of prebuildApiFiles from packages/internal/src/build/api.ts + * This will be re-architected, but doing so now would introduce breaking changes. + */ +export const prebuildApiFileWrapper = (srcFile: string) => { + const redwoodProjectPaths = getPaths() + + const plugins = getApiSideBabelPlugins({ + openTelemetry: getConfig().experimental.opentelemetry.enabled, + }) + + const relativePathFromSrc = path.relative(redwoodProjectPaths.base, srcFile) + + const dstPath = path + .join(redwoodProjectPaths.generated.prebuild, relativePathFromSrc) + .replace(/\.(ts)$/, '.js') + + const result = prebuildApiFile(srcFile, dstPath, plugins) + + if (!result?.code) { + throw new Error(`Couldn't prebuild ${srcFile}`) + } + + return result.code +} diff --git a/packages/babel-config/src/__tests__/tsconfigParsing.test.ts b/packages/babel-config/src/__tests__/tsconfigParsing.test.ts new file mode 100644 index 000000000000..3ca80f58fa43 --- /dev/null +++ b/packages/babel-config/src/__tests__/tsconfigParsing.test.ts @@ -0,0 +1,237 @@ +import { vol } from 'memfs' + +import { ensurePosixPath } from '@redwoodjs/project-config' + +import { + getPathsFromTypeScriptConfig, + parseTypeScriptConfigFiles, +} from '../common' + +jest.mock('fs', () => require('memfs').fs) + +const redwoodProjectPath = '/redwood-app' +process.env.RWJS_CWD = redwoodProjectPath + +afterEach(() => { + vol.reset() +}) + +describe('TypeScript config file parsing', () => { + it("returns `null` if it can't find TypeScript config files", () => { + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: {}, + web: {}, + }, + redwoodProjectPath + ) + + const typeScriptConfig = parseTypeScriptConfigFiles() + expect(typeScriptConfig).toHaveProperty('api', null) + expect(typeScriptConfig).toHaveProperty('web', null) + }) + + it('finds and parses tsconfig.json files', () => { + const apiTSConfig = '{"compilerOptions": {"noEmit": true}}' + const webTSConfig = '{"compilerOptions": {"allowJs": true}}' + + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + 'tsconfig.json': apiTSConfig, + }, + web: { + 'tsconfig.json': webTSConfig, + }, + }, + redwoodProjectPath + ) + + const typeScriptConfig = parseTypeScriptConfigFiles() + expect(typeScriptConfig.api).toMatchObject(JSON.parse(apiTSConfig)) + expect(typeScriptConfig.web).toMatchObject(JSON.parse(webTSConfig)) + }) + + it('finds and parses jsconfig.json files', () => { + const apiJSConfig = '{"compilerOptions": {"noEmit": true}}' + const webJSConfig = '{"compilerOptions": {"allowJs": true}}' + + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + 'jsconfig.json': apiJSConfig, + }, + web: { + 'jsconfig.json': webJSConfig, + }, + }, + redwoodProjectPath + ) + + const typeScriptConfig = parseTypeScriptConfigFiles() + expect(typeScriptConfig.api).toMatchObject(JSON.parse(apiJSConfig)) + expect(typeScriptConfig.web).toMatchObject(JSON.parse(webJSConfig)) + }) + + it('handles invalid JSON', () => { + const apiTSConfig = + '{"compilerOptions": {"noEmit": true,"allowJs": true,"esModuleInterop": true,"target": "esnext","module": "esnext","moduleResolution": "node","baseUrl": "./","rootDirs": ["./src","../.redwood/types/mirror/api/src"],"paths": {"src/*": ["./src/*","../.redwood/types/mirror/api/src/*"],"types/*": ["./types/*", "../types/*"],"@redwoodjs/testing": ["../node_modules/@redwoodjs/testing/api"]},"typeRoots": ["../node_modules/@types","./node_modules/@types"],"types": ["jest"],},"include": ["src","../.redwood/types/includes/all-*","../.redwood/types/includes/api-*","../types"]}' + const webTSConfig = + '{"compilerOptions": {"noEmit": true,"allowJs": true,"esModuleInterop": true,"target": "esnext","module": "esnext","moduleResolution": "node","baseUrl": "./","rootDirs": ["./src","../.redwood/types/mirror/web/src","../api/src","../.redwood/types/mirror/api/src"],"paths": {"src/*": ["./src/*","../.redwood/types/mirror/web/src/*","../api/src/*","../.redwood/types/mirror/api/src/*"],"$api/*": [ "../api/*" ],"types/*": ["./types/*", "../types/*"],"@redwoodjs/testing": ["../node_modules/@redwoodjs/testing/web"]},"typeRoots": ["../node_modules/@types", "./node_modules/@types"],"types": ["jest", "@testing-library/jest-dom"],"jsx": "preserve",},"include": ["src","../.redwood/types/includes/all-*","../.redwood/types/includes/web-*","../types","./types"]}' + + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + 'tsconfig.json': apiTSConfig, + }, + web: { + 'tsconfig.json': webTSConfig, + }, + }, + redwoodProjectPath + ) + + expect(parseTypeScriptConfigFiles).not.toThrow() + }) +}) + +describe('getPathsFromTypeScriptConfig', () => { + const FAKE_API_ROOT = + process.platform === 'win32' ? '/d/redwood-app/api' : '/redwood-app/api' + const FAKE_WEB_ROOT = + process.platform === 'win32' ? '/d/redwood-app/web' : '/redwood-app/web' + + it("returns an empty object if there's no TypeScript config files", () => { + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: {}, + web: {}, + }, + redwoodProjectPath + ) + + const typeScriptConfig = parseTypeScriptConfigFiles() + + const apiPaths = getPathsFromTypeScriptConfig( + typeScriptConfig.api, + FAKE_API_ROOT + ) + expect(apiPaths).toMatchObject({}) + + const webPaths = getPathsFromTypeScriptConfig( + typeScriptConfig.web, + FAKE_WEB_ROOT + ) + expect(webPaths).toMatchObject({}) + }) + + it("returns an empty object if there's no compilerOptions, baseUrl, or paths", () => { + const apiTSConfig = '{}' + const webTSConfig = '{"compilerOptions":{"allowJs": true}}' + + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + 'tsconfig.json': apiTSConfig, + }, + web: { + 'tsconfig.json': webTSConfig, + }, + }, + redwoodProjectPath + ) + + const typeScriptConfig = parseTypeScriptConfigFiles() + + const apiPaths = getPathsFromTypeScriptConfig( + typeScriptConfig.api, + FAKE_API_ROOT + ) + expect(apiPaths).toMatchInlineSnapshot(`{}`) + + const webPaths = getPathsFromTypeScriptConfig( + typeScriptConfig.web, + FAKE_WEB_ROOT + ) + expect(webPaths).toMatchInlineSnapshot(`{}`) + }) + + it('excludes "src/*", "$api/*", "types/*", and "@redwoodjs/testing"', () => { + const apiTSConfig = + '{"compilerOptions":{"baseUrl":"./","paths":{"src/*":["./src/*","../.redwood/types/mirror/api/src/*"],"types/*":["./types/*","../types/*"],"@redwoodjs/testing":["../node_modules/@redwoodjs/testing/api"]}}}' + const webTSConfig = + '{"compilerOptions":{"baseUrl":"./","paths":{"src/*":["./src/*","../.redwood/types/mirror/web/src/*"],"$api/*":[ "../api/*" ],"types/*":["./types/*", "../types/*"],"@redwoodjs/testing":["../node_modules/@redwoodjs/testing/web"]}}}' + + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + 'tsconfig.json': apiTSConfig, + }, + web: { + 'tsconfig.json': webTSConfig, + }, + }, + redwoodProjectPath + ) + + const typeScriptConfig = parseTypeScriptConfigFiles() + + const apiPaths = getPathsFromTypeScriptConfig( + typeScriptConfig.api, + FAKE_API_ROOT + ) + expect(apiPaths).toMatchInlineSnapshot(`{}`) + + const webPaths = getPathsFromTypeScriptConfig( + typeScriptConfig.web, + FAKE_WEB_ROOT + ) + expect(webPaths).toMatchInlineSnapshot(`{}`) + }) + + it('gets and formats paths', () => { + const apiTSConfig = + '{"compilerOptions":{"baseUrl":"./","paths":{"@services/*":["./src/services/*"]}}}' + const webTSConfig = + '{"compilerOptions":{"baseUrl":"./","paths":{"@ui/*":["./src/ui/*"]}}}' + + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + 'tsconfig.json': apiTSConfig, + }, + web: { + 'tsconfig.json': webTSConfig, + }, + }, + redwoodProjectPath + ) + + const typeScriptConfig = parseTypeScriptConfigFiles() + + const apiPaths = getPathsFromTypeScriptConfig( + typeScriptConfig.api, + FAKE_API_ROOT + ) + + expect(ensurePosixPath(apiPaths['@services'])).toEqual( + ensurePosixPath(`${FAKE_API_ROOT}/src/services`) + ) + + const webPaths = getPathsFromTypeScriptConfig( + typeScriptConfig.web, + FAKE_WEB_ROOT + ) + expect(ensurePosixPath(webPaths['@ui'])).toEqual( + ensurePosixPath(`${FAKE_WEB_ROOT}/src/ui`) + ) + }) +}) diff --git a/packages/babel-config/src/api.ts b/packages/babel-config/src/api.ts new file mode 100644 index 000000000000..0e61d8f0cc53 --- /dev/null +++ b/packages/babel-config/src/api.ts @@ -0,0 +1,234 @@ +import fs from 'fs' +import path from 'path' + +import { transform } from '@babel/core' +import type { PluginItem, TransformOptions } from '@babel/core' + +import { getPaths } from '@redwoodjs/project-config' + +import type { RegisterHookOptions } from './common' +import { + registerBabel, + CORE_JS_VERSION, + RUNTIME_CORE_JS_VERSION, + getCommonPlugins, + parseTypeScriptConfigFiles, + getPathsFromTypeScriptConfig, +} from './common' + +export const TARGETS_NODE = '20.10' + +export const getApiSideBabelPresets = ( + { presetEnv } = { presetEnv: false } +) => { + return [ + [ + '@babel/preset-typescript', + { + isTSX: true, + allExtensions: true, + }, + 'rwjs-babel-preset-typescript', + ], + // Preset-env is required when we are not doing the transpilation with esbuild + presetEnv && [ + '@babel/preset-env', + { + targets: { + node: TARGETS_NODE, + }, + useBuiltIns: 'usage', + corejs: { + version: CORE_JS_VERSION, + // List of supported proposals: https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md#ecmascript-proposals + proposals: true, + }, + exclude: [ + // Remove class-properties from preset-env, and include separately with loose + // https://github.com/webpack/webpack/issues/9708 + '@babel/plugin-transform-class-properties', + '@babel/plugin-transform-private-methods', + ], + }, + ], + ].filter(Boolean) as TransformOptions['presets'] +} + +export const BABEL_PLUGIN_TRANSFORM_RUNTIME_OPTIONS = { + // See https://babeljs.io/docs/babel-plugin-transform-runtime/#corejs + // and https://babeljs.io/docs/en/babel-plugin-transform-runtime/#core-js-aliasing. + // + // This results in over polyfilling. + corejs: { version: 3, proposals: true }, + + // See https://babeljs.io/docs/en/babel-plugin-transform-runtime/#version. + version: RUNTIME_CORE_JS_VERSION, +} + +export const getApiSideBabelPlugins = ( + { openTelemetry } = { + openTelemetry: false, + } +) => { + // Plugin shape: [ ["Target", "Options", "name"] ], + // a custom "name" is supplied so that user's do not accidentally overwrite + // Redwood's own plugins when they specify their own. + const tsConfig = parseTypeScriptConfigFiles() + + const plugins: TransformOptions['plugins'] = [ + ...getCommonPlugins(), + // Needed to support `/** @jsxImportSource custom-jsx-library */` + // comments in JSX files + ['@babel/plugin-transform-react-jsx', { runtime: 'automatic' }], + ['@babel/plugin-transform-runtime', BABEL_PLUGIN_TRANSFORM_RUNTIME_OPTIONS], + [ + 'babel-plugin-module-resolver', + { + alias: { + src: './src', + // adds the paths from [ts|js]config.json to the module resolver + ...getPathsFromTypeScriptConfig(tsConfig.api, getPaths().api.base), + }, + root: [getPaths().api.base], + cwd: 'packagejson', + loglevel: 'silent', // to silence the unnecessary warnings + }, + 'rwjs-api-module-resolver', + ], + [ + require('./plugins/babel-plugin-redwood-directory-named-import').default, + undefined, + 'rwjs-babel-directory-named-modules', + ], + [ + 'babel-plugin-auto-import', + { + declarations: [ + { + // import gql from 'graphql-tag' + default: 'gql', + path: 'graphql-tag', + }, + { + // import { context } from '@redwoodjs/context' + members: ['context'], + path: '@redwoodjs/context', + }, + ], + }, + 'rwjs-babel-auto-import', + ], + // FIXME: `graphql-tag` is not working: https://github.com/redwoodjs/redwood/pull/3193 + ['babel-plugin-graphql-tag', undefined, 'rwjs-babel-graphql-tag'], + [ + require('./plugins/babel-plugin-redwood-import-dir').default, + undefined, + 'rwjs-babel-glob-import-dir', + ], + openTelemetry && [ + require('./plugins/babel-plugin-redwood-otel-wrapping').default, + undefined, + 'rwjs-babel-otel-wrapping', + ], + ].filter(Boolean) as PluginItem[] + + return plugins +} + +export const getApiSideBabelConfigPath = () => { + const p = path.join(getPaths().api.base, 'babel.config.js') + if (fs.existsSync(p)) { + return p + } else { + return undefined + } +} + +export const getApiSideBabelOverrides = () => { + const overrides = [ + // Apply context wrapping to all functions + { + // match */api/src/functions/*.js|ts + test: /.+api(?:[\\|/])src(?:[\\|/])functions(?:[\\|/]).+.(?:js|ts)$/, + plugins: [ + require('./plugins/babel-plugin-redwood-context-wrapping').default, + ], + }, + ].filter(Boolean) + return overrides as TransformOptions[] +} + +export const getApiSideDefaultBabelConfig = () => { + return { + presets: getApiSideBabelPresets(), + plugins: getApiSideBabelPlugins(), + overrides: getApiSideBabelOverrides(), + extends: getApiSideBabelConfigPath(), + babelrc: false, + ignore: ['node_modules'], + } +} + +// Used in cli commands that need to use es6, lib and services +export const registerApiSideBabelHook = ({ + plugins = [], + ...rest +}: RegisterHookOptions = {}) => { + const defaultOptions = getApiSideDefaultBabelConfig() + + registerBabel({ + ...defaultOptions, + presets: getApiSideBabelPresets({ + presetEnv: true, + }), + extensions: ['.js', '.ts', '.jsx', '.tsx'], + plugins: [...defaultOptions.plugins, ...plugins], + cache: false, + ...rest, + }) +} + +export const transformWithBabel = ( + srcPath: string, + plugins: TransformOptions['plugins'] +) => { + const code = fs.readFileSync(srcPath, 'utf-8') + const defaultOptions = getApiSideDefaultBabelConfig() + + const result = transform(code, { + ...defaultOptions, + cwd: getPaths().api.base, + filename: srcPath, + // we need inline sourcemaps at this level + // because this file will eventually be fed to esbuild + // when esbuild finds an inline sourcemap, it tries to "combine" it + // so the final sourcemap (the one that esbuild generates) combines both mappings + sourceMaps: 'inline', + plugins, + }) + return result +} + +// TODO (STREAMING) I changed the prebuildApiFile function in https://github.com/redwoodjs/redwood/pull/7672/files +// but we had to revert. For this branch temporarily, I'm going to add a new function +// This is used in building routeHooks +export const transformWithBabel = ( + srcPath: string, + plugins: TransformOptions['plugins'] +) => { + const code = fs.readFileSync(srcPath, 'utf-8') + const defaultOptions = getApiSideDefaultBabelConfig() + + const result = transform(code, { + ...defaultOptions, + cwd: getPaths().api.base, + filename: srcPath, + // we need inline sourcemaps at this level + // because this file will eventually be fed to esbuild + // when esbuild finds an inline sourcemap, it tries to "combine" it + // so the final sourcemap (the one that esbuild generates) combines both mappings + sourceMaps: 'inline', + plugins, + }) + return result +} diff --git a/packages/babel-config/src/common.ts b/packages/babel-config/src/common.ts new file mode 100644 index 000000000000..b05f18a42a16 --- /dev/null +++ b/packages/babel-config/src/common.ts @@ -0,0 +1,173 @@ +import fs from 'fs' +import path from 'path' + +import type { TransformOptions, PluginItem } from '@babel/core' +import { parseConfigFileTextToJson } from 'typescript' + +import { getPaths } from '@redwoodjs/project-config' + +import { getWebSideBabelPlugins } from './web' + +const pkgJson = require('../package.json') + +export interface RegisterHookOptions { + /** + * Be careful: plugins are a nested array e.g. [[plug1, x, x], [plug2, y, y]]. + * These are in addition to the default RW plugins + */ + plugins?: PluginItem[] + overrides?: TransformOptions['overrides'] +} + +interface BabelRegisterOptions extends TransformOptions { + extensions?: string[] + cache?: boolean +} + +/** NOTE: + * We do this so we still get types, but don't import babel/register + * Importing babel/register in typescript (instead of requiring) has dire consequences.. + + Lets say we use the import syntax: import babelRequireHook from '@babel/register' + - if your import in a JS file (like we used to in the cli project) - not a problem, and it would only invoke the register function when you called babelRequireHook + - if you import in a TS file, the transpile process modifies it when we build the framework - + so it will invoke it once as soon as you import, and another time when you use babelRequireHook... + BUTTT!!! you won't notice it if your project is TS because by default it ignore .ts and .tsx files, but if its a JS project, it would try to transpile twice + * + * + * +**/ +export const registerBabel = (options: BabelRegisterOptions) => { + require('@babel/register')(options) +} + +export const CORE_JS_VERSION = pkgJson.dependencies['core-js'] + .split('.') + .slice(0, 2) + .join('.') // Produces: 3.12, instead of 3.12.1 + +if (!CORE_JS_VERSION) { + throw new Error( + 'RedwoodJS Project Babel: Could not determine core-js version.' + ) +} + +export const RUNTIME_CORE_JS_VERSION = + pkgJson.dependencies['@babel/runtime-corejs3'] + +if (!RUNTIME_CORE_JS_VERSION) { + throw new Error( + 'RedwoodJS Project Babel: Could not determine core-js runtime version' + ) +} + +export const getCommonPlugins = () => { + return [ + ['@babel/plugin-transform-class-properties', { loose: true }], + // Note: The private method loose mode configuration setting must be the + // same as @babel/plugin-proposal class-properties. + // (https://babeljs.io/docs/en/babel-plugin-proposal-private-methods#loose) + ['@babel/plugin-transform-private-methods', { loose: true }], + ['@babel/plugin-transform-private-property-in-object', { loose: true }], + ] +} + +// TODO (STREAMING) double check this, think about it more carefully please! +// It's related to yarn workspaces to be or not to be +export const getRouteHookBabelPlugins = () => { + return [ + ...getWebSideBabelPlugins({ + forVite: true, + }), + [ + 'babel-plugin-module-resolver', + { + alias: { + 'api/src': './src', + }, + root: [getPaths().api.base], + cwd: 'packagejson', + loglevel: 'silent', // to silence the unnecessary warnings + }, + 'rwjs-api-module-resolver', + ], + ] +} + +/** + * Finds, reads and parses the [ts|js]config.json file + * @returns The config object + */ +export const parseTypeScriptConfigFiles = () => { + const rwPaths = getPaths() + + const parseConfigFile = (basePath: string) => { + let configPath = path.join(basePath, 'tsconfig.json') + if (!fs.existsSync(configPath)) { + configPath = path.join(basePath, 'jsconfig.json') + if (!fs.existsSync(configPath)) { + return null + } + } + return parseConfigFileTextToJson( + configPath, + fs.readFileSync(configPath, 'utf-8') + ) + } + const apiConfig = parseConfigFile(rwPaths.api.base) + const webConfig = parseConfigFile(rwPaths.web.base) + + return { + api: apiConfig?.config ?? null, + web: webConfig?.config ?? null, + } +} + +type CompilerOptionsForPaths = { + compilerOptions: { baseUrl: string; paths: string } +} +/** + * Extracts and formats the paths from the [ts|js]config.json file + * @param config The config object + * @param rootDir {string} Where the jsconfig/tsconfig is loaded from + * @returns {Record} The paths object + */ +export const getPathsFromTypeScriptConfig = ( + config: CompilerOptionsForPaths, + rootDir: string +): Record => { + if (!config) { + return {} + } + + if (!config.compilerOptions?.baseUrl || !config.compilerOptions?.paths) { + return {} + } + + const { baseUrl, paths } = config.compilerOptions + + // Convert it to absolute path - on windows the baseUrl is already absolute + const absoluteBase = path.isAbsolute(baseUrl) + ? baseUrl + : path.join(rootDir, baseUrl) + + const pathsObj: Record = {} + for (const [key, value] of Object.entries(paths)) { + // exclude the default paths that are included in the tsconfig.json file + // "src/*" + // "$api/*" + // "types/*" + // "@redwoodjs/testing" + if (key.match(/src\/|\$api\/\*|types\/\*|\@redwoodjs\/.*/g)) { + continue + } + const aliasKey = key.replace('/*', '') + const aliasValue = path.join( + absoluteBase, + (value as string)[0].replace('/*', '') + ) + + pathsObj[aliasKey] = aliasValue + } + return pathsObj +} diff --git a/packages/babel-config/src/index.ts b/packages/babel-config/src/index.ts new file mode 100644 index 000000000000..0c4d675cb117 --- /dev/null +++ b/packages/babel-config/src/index.ts @@ -0,0 +1,37 @@ +// The excess of exports here is for backwards compatibility +// since `@redwoodjs/internal` exported everything from babel/api, web, and common. +// See https://github.com/redwoodjs/redwood/blob/44b4a9023ac3a14b5f56b071052bdf49c410bb8b/packages/internal/src/index.ts#L13-L16. + +export { + BABEL_PLUGIN_TRANSFORM_RUNTIME_OPTIONS, + TARGETS_NODE, + getApiSideBabelConfigPath, + getApiSideBabelPlugins, + getApiSideBabelPresets, + getApiSideDefaultBabelConfig, + prebuildApiFile, + registerApiSideBabelHook, + transformWithBabel, +} from './api' + +export { + getWebSideBabelConfigPath, + getWebSideBabelPlugins, + getWebSideBabelPresets, + getWebSideDefaultBabelConfig, + getWebSideOverrides, + prebuildWebFile, + registerWebSideBabelHook, +} from './web' + +export type { Flags } from './web' + +export { + CORE_JS_VERSION, + RUNTIME_CORE_JS_VERSION, + getCommonPlugins, + getPathsFromTypeScriptConfig as getPathsFromConfig, + getRouteHookBabelPlugins, + parseTypeScriptConfigFiles, + registerBabel, +} from './common' diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/cell/cell-with-commented-exports/code.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/cell/cell-with-commented-exports/code.js similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/cell/cell-with-commented-exports/code.js rename to packages/babel-config/src/plugins/__tests__/__fixtures__/cell/cell-with-commented-exports/code.js diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/cell/cell-with-commented-exports/output.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/cell/cell-with-commented-exports/output.js similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/cell/cell-with-commented-exports/output.js rename to packages/babel-config/src/plugins/__tests__/__fixtures__/cell/cell-with-commented-exports/output.js diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/cell/cell-with-default-and-other-named-export/code.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/cell/cell-with-default-and-other-named-export/code.js similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/cell/cell-with-default-and-other-named-export/code.js rename to packages/babel-config/src/plugins/__tests__/__fixtures__/cell/cell-with-default-and-other-named-export/code.js diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/cell/cell-with-default-and-other-named-export/output.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/cell/cell-with-default-and-other-named-export/output.js similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/cell/cell-with-default-and-other-named-export/output.js rename to packages/babel-config/src/plugins/__tests__/__fixtures__/cell/cell-with-default-and-other-named-export/output.js diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/cell/cell-with-default-export/code.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/cell/cell-with-default-export/code.js similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/cell/cell-with-default-export/code.js rename to packages/babel-config/src/plugins/__tests__/__fixtures__/cell/cell-with-default-export/code.js diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/cell/cell-with-default-export/output.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/cell/cell-with-default-export/output.js similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/cell/cell-with-default-export/output.js rename to packages/babel-config/src/plugins/__tests__/__fixtures__/cell/cell-with-default-export/output.js diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/cell/cell-with-required-exports/code.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/cell/cell-with-required-exports/code.js similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/cell/cell-with-required-exports/code.js rename to packages/babel-config/src/plugins/__tests__/__fixtures__/cell/cell-with-required-exports/code.js diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/cell/cell-with-required-exports/output.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/cell/cell-with-required-exports/output.js similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/cell/cell-with-required-exports/output.js rename to packages/babel-config/src/plugins/__tests__/__fixtures__/cell/cell-with-required-exports/output.js diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/auth/code.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/auth/code.js new file mode 100644 index 000000000000..c95c23b252fe --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/auth/code.js @@ -0,0 +1,173 @@ +import { DbAuthHandler, DbAuthHandlerOptions } from '@redwoodjs/auth-dbauth-api' + +import { db } from 'src/lib/db' + +export const handler = async ( + event, + context +) => { + const forgotPasswordOptions = { + // handler() is invoked after verifying that a user was found with the given + // username. This is where you can send the user an email with a link to + // reset their password. With the default dbAuth routes and field names, the + // URL to reset the password will be: + // + // https://example.com/reset-password?resetToken=${user.resetToken} + // + // Whatever is returned from this function will be returned from + // the `forgotPassword()` function that is destructured from `useAuth()` + // You could use this return value to, for example, show the email + // address in a toast message so the user will know it worked and where + // to look for the email. + handler: (user) => { + return user + }, + + // How long the resetToken is valid for, in seconds (default is 24 hours) + expires: 60 * 60 * 24, + + errors: { + // for security reasons you may want to be vague here rather than expose + // the fact that the email address wasn't found (prevents fishing for + // valid email addresses) + usernameNotFound: 'Username not found', + // if the user somehow gets around client validation + usernameRequired: 'Username is required', + }, + } + + const loginOptions = { + // handler() is called after finding the user that matches the + // username/password provided at login, but before actually considering them + // logged in. The `user` argument will be the user in the database that + // matched the username/password. + // + // If you want to allow this user to log in simply return the user. + // + // If you want to prevent someone logging in for another reason (maybe they + // didn't validate their email yet), throw an error and it will be returned + // by the `logIn()` function from `useAuth()` in the form of: + // `{ message: 'Error message' }` + handler: (user) => { + return user + }, + + errors: { + usernameOrPasswordMissing: 'Both username and password are required', + usernameNotFound: 'Username ${username} not found', + // For security reasons you may want to make this the same as the + // usernameNotFound error so that a malicious user can't use the error + // to narrow down if it's the username or password that's incorrect + incorrectPassword: 'Incorrect password for ${username}', + }, + + // How long a user will remain logged in, in seconds + expires: 60 * 60 * 24 * 365 * 10, + } + + const resetPasswordOptions = { + // handler() is invoked after the password has been successfully updated in + // the database. Returning anything truthy will automatically log the user + // in. Return `false` otherwise, and in the Reset Password page redirect the + // user to the login page. + handler: (_user) => { + return true + }, + + // If `false` then the new password MUST be different from the current one + allowReusedPassword: true, + + errors: { + // the resetToken is valid, but expired + resetTokenExpired: 'resetToken is expired', + // no user was found with the given resetToken + resetTokenInvalid: 'resetToken is invalid', + // the resetToken was not present in the URL + resetTokenRequired: 'resetToken is required', + // new password is the same as the old password (apparently they did not forget it) + reusedPassword: 'Must choose a new password', + }, + } + + const signupOptions = { + // Whatever you want to happen to your data on new user signup. Redwood will + // check for duplicate usernames before calling this handler. At a minimum + // you need to save the `username`, `hashedPassword` and `salt` to your + // user table. `userAttributes` contains any additional object members that + // were included in the object given to the `signUp()` function you got + // from `useAuth()`. + // + // If you want the user to be immediately logged in, return the user that + // was created. + // + // If this handler throws an error, it will be returned by the `signUp()` + // function in the form of: `{ error: 'Error message' }`. + // + // If this returns anything else, it will be returned by the + // `signUp()` function in the form of: `{ message: 'String here' }`. + handler: ({ username, hashedPassword, salt, userAttributes }) => { + return db.user.create({ + data: { + email: username, + hashedPassword: hashedPassword, + salt: salt, + fullName: userAttributes['full-name'], + }, + }) + }, + + // Include any format checks for password here. Return `true` if the + // password is valid, otherwise throw a `PasswordValidationError`. + // Import the error along with `DbAuthHandler` from `@redwoodjs/api` above. + passwordValidation: (_password) => { + return true + }, + + errors: { + // `field` will be either "username" or "password" + fieldMissing: '${field} is required', + usernameTaken: 'Username `${username}` already in use', + }, + } + + const authHandler = new DbAuthHandler(event, context, { + // Provide prisma db client + db: db, + + // The name of the property you'd call on `db` to access your user table. + // i.e. if your Prisma model is named `User` this value would be `user`, as in `db.user` + authModelAccessor: 'user', + + // A map of what dbAuth calls a field to what your database calls it. + // `id` is whatever column you use to uniquely identify a user (probably + // something like `id` or `userId` or even `email`) + authFields: { + id: 'id', + username: 'email', + hashedPassword: 'hashedPassword', + salt: 'salt', + resetToken: 'resetToken', + resetTokenExpiresAt: 'resetTokenExpiresAt', + }, + + // Specifies attributes on the cookie that dbAuth sets in order to remember + // who is logged in. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies + cookie: { + HttpOnly: true, + Path: '/', + SameSite: 'Strict', + Secure: process.env.NODE_ENV !== 'development', + + // If you need to allow other domains (besides the api side) access to + // the dbAuth session cookie: + // Domain: 'example.com', + }, + + forgotPassword: forgotPasswordOptions, + login: loginOptions, + resetPassword: resetPasswordOptions, + signup: signupOptions, + }) + + return await authHandler.invoke() +} diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/auth/output.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/auth/output.js new file mode 100644 index 000000000000..871120a0843e --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/auth/output.js @@ -0,0 +1,165 @@ +import { DbAuthHandler, DbAuthHandlerOptions } from '@redwoodjs/auth-dbauth-api' +import { db } from 'src/lib/db' +import { getAsyncStoreInstance as __rw_getAsyncStoreInstance } from '@redwoodjs/context/dist/store' +const __rw_handler = async (event, context) => { + const forgotPasswordOptions = { + // handler() is invoked after verifying that a user was found with the given + // username. This is where you can send the user an email with a link to + // reset their password. With the default dbAuth routes and field names, the + // URL to reset the password will be: + // + // https://example.com/reset-password?resetToken=${user.resetToken} + // + // Whatever is returned from this function will be returned from + // the `forgotPassword()` function that is destructured from `useAuth()` + // You could use this return value to, for example, show the email + // address in a toast message so the user will know it worked and where + // to look for the email. + handler: (user) => { + return user + }, + // How long the resetToken is valid for, in seconds (default is 24 hours) + expires: 60 * 60 * 24, + errors: { + // for security reasons you may want to be vague here rather than expose + // the fact that the email address wasn't found (prevents fishing for + // valid email addresses) + usernameNotFound: 'Username not found', + // if the user somehow gets around client validation + usernameRequired: 'Username is required', + }, + } + const loginOptions = { + // handler() is called after finding the user that matches the + // username/password provided at login, but before actually considering them + // logged in. The `user` argument will be the user in the database that + // matched the username/password. + // + // If you want to allow this user to log in simply return the user. + // + // If you want to prevent someone logging in for another reason (maybe they + // didn't validate their email yet), throw an error and it will be returned + // by the `logIn()` function from `useAuth()` in the form of: + // `{ message: 'Error message' }` + handler: (user) => { + return user + }, + errors: { + usernameOrPasswordMissing: 'Both username and password are required', + usernameNotFound: 'Username ${username} not found', + // For security reasons you may want to make this the same as the + // usernameNotFound error so that a malicious user can't use the error + // to narrow down if it's the username or password that's incorrect + incorrectPassword: 'Incorrect password for ${username}', + }, + // How long a user will remain logged in, in seconds + expires: 60 * 60 * 24 * 365 * 10, + } + const resetPasswordOptions = { + // handler() is invoked after the password has been successfully updated in + // the database. Returning anything truthy will automatically log the user + // in. Return `false` otherwise, and in the Reset Password page redirect the + // user to the login page. + handler: (_user) => { + return true + }, + // If `false` then the new password MUST be different from the current one + allowReusedPassword: true, + errors: { + // the resetToken is valid, but expired + resetTokenExpired: 'resetToken is expired', + // no user was found with the given resetToken + resetTokenInvalid: 'resetToken is invalid', + // the resetToken was not present in the URL + resetTokenRequired: 'resetToken is required', + // new password is the same as the old password (apparently they did not forget it) + reusedPassword: 'Must choose a new password', + }, + } + const signupOptions = { + // Whatever you want to happen to your data on new user signup. Redwood will + // check for duplicate usernames before calling this handler. At a minimum + // you need to save the `username`, `hashedPassword` and `salt` to your + // user table. `userAttributes` contains any additional object members that + // were included in the object given to the `signUp()` function you got + // from `useAuth()`. + // + // If you want the user to be immediately logged in, return the user that + // was created. + // + // If this handler throws an error, it will be returned by the `signUp()` + // function in the form of: `{ error: 'Error message' }`. + // + // If this returns anything else, it will be returned by the + // `signUp()` function in the form of: `{ message: 'String here' }`. + handler: ({ username, hashedPassword, salt, userAttributes }) => { + return db.user.create({ + data: { + email: username, + hashedPassword: hashedPassword, + salt: salt, + fullName: userAttributes['full-name'], + }, + }) + }, + // Include any format checks for password here. Return `true` if the + // password is valid, otherwise throw a `PasswordValidationError`. + // Import the error along with `DbAuthHandler` from `@redwoodjs/api` above. + passwordValidation: (_password) => { + return true + }, + errors: { + // `field` will be either "username" or "password" + fieldMissing: '${field} is required', + usernameTaken: 'Username `${username}` already in use', + }, + } + const authHandler = new DbAuthHandler(event, context, { + // Provide prisma db client + db: db, + // The name of the property you'd call on `db` to access your user table. + // i.e. if your Prisma model is named `User` this value would be `user`, as in `db.user` + authModelAccessor: 'user', + // A map of what dbAuth calls a field to what your database calls it. + // `id` is whatever column you use to uniquely identify a user (probably + // something like `id` or `userId` or even `email`) + authFields: { + id: 'id', + username: 'email', + hashedPassword: 'hashedPassword', + salt: 'salt', + resetToken: 'resetToken', + resetTokenExpiresAt: 'resetTokenExpiresAt', + }, + // Specifies attributes on the cookie that dbAuth sets in order to remember + // who is logged in. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies + cookie: { + HttpOnly: true, + Path: '/', + SameSite: 'Strict', + Secure: process.env.NODE_ENV !== 'development', + + // If you need to allow other domains (besides the api side) access to + // the dbAuth session cookie: + // Domain: 'example.com', + }, + forgotPassword: forgotPasswordOptions, + login: loginOptions, + resetPassword: resetPasswordOptions, + signup: signupOptions, + }) + return await authHandler.invoke() +} +export const handler = async (__rw_event, __rw__context) => { + // The store will be undefined if no context isolation has been performed yet + const __rw_contextStore = __rw_getAsyncStoreInstance().getStore() + if (__rw_contextStore === undefined) { + return __rw_getAsyncStoreInstance().run( + new Map(), + __rw_handler, + __rw_event, + __rw__context + ) + } + return __rw_handler(__rw_event, __rw__context) +} \ No newline at end of file diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/custom/code.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/custom/code.js new file mode 100644 index 000000000000..b487854d6633 --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/custom/code.js @@ -0,0 +1,31 @@ +import { logger } from 'src/lib/logger' + +/** + * The handler function is your code that processes http request events. + * You can use return and throw to send a response or error, respectively. + * + * Important: When deployed, a custom serverless function is an open API endpoint and + * is your responsibility to secure appropriately. + * + * @see {@link https://redwoodjs.com/docs/serverless-functions#security-considerations|Serverless Function Considerations} + * in the RedwoodJS documentation for more information. + * + * @typedef { import('aws-lambda').APIGatewayEvent } APIGatewayEvent + * @typedef { import('aws-lambda').Context } Context + * @param { APIGatewayEvent } event - an object which contains information from the invoker. + * @param { Context } context - contains information about the invocation, + * function, and execution environment. + */ +export const handler = async (event, _context) => { + logger.info(`${event.httpMethod} ${event.path}: custom function`) + + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + data: 'custom function', + }), + } +} diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/custom/output.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/custom/output.js new file mode 100644 index 000000000000..636deea78910 --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/custom/output.js @@ -0,0 +1,44 @@ +import { logger } from 'src/lib/logger' + +/** + * The handler function is your code that processes http request events. + * You can use return and throw to send a response or error, respectively. + * + * Important: When deployed, a custom serverless function is an open API endpoint and + * is your responsibility to secure appropriately. + * + * @see {@link https://redwoodjs.com/docs/serverless-functions#security-considerations|Serverless Function Considerations} + * in the RedwoodJS documentation for more information. + * + * @typedef { import('aws-lambda').APIGatewayEvent } APIGatewayEvent + * @typedef { import('aws-lambda').Context } Context + * @param { APIGatewayEvent } event - an object which contains information from the invoker. + * @param { Context } context - contains information about the invocation, + * function, and execution environment. + */ +import { getAsyncStoreInstance as __rw_getAsyncStoreInstance } from '@redwoodjs/context/dist/store' +const __rw_handler = async (event, _context) => { + logger.info(`${event.httpMethod} ${event.path}: custom function`) + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + data: 'custom function', + }), + } +} +export const handler = async (__rw_event, __rw__context) => { + // The store will be undefined if no context isolation has been performed yet + const __rw_contextStore = __rw_getAsyncStoreInstance().getStore() + if (__rw_contextStore === undefined) { + return __rw_getAsyncStoreInstance().run( + new Map(), + __rw_handler, + __rw_event, + __rw__context + ) + } + return __rw_handler(__rw_event, __rw__context) +} \ No newline at end of file diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/graphql/code.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/graphql/code.js new file mode 100644 index 000000000000..5d8db6ab8f2a --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/graphql/code.js @@ -0,0 +1,23 @@ +import { authDecoder } from '@redwoodjs/auth-dbauth-api' +import { createGraphQLHandler } from '@redwoodjs/graphql-server' + +import directives from 'src/directives/**/*.{js,ts}' +import sdls from 'src/graphql/**/*.sdl.{js,ts}' +import services from 'src/services/**/*.{js,ts}' + +import { getCurrentUser } from 'src/lib/auth' +import { db } from 'src/lib/db' +import { logger } from 'src/lib/logger' + +export const handler = createGraphQLHandler({ + authDecoder, + getCurrentUser, + loggerConfig: { logger, options: {} }, + directives, + sdls, + services, + onException: () => { + // Disconnect from your database with an unhandled exception. + db.$disconnect() + }, +}) diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/graphql/output.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/graphql/output.js new file mode 100644 index 000000000000..ba3386d5b18f --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/context-wrapping/graphql/output.js @@ -0,0 +1,37 @@ +import { authDecoder } from '@redwoodjs/auth-dbauth-api' +import { createGraphQLHandler } from '@redwoodjs/graphql-server' +import directives from 'src/directives/**/*.{js,ts}' +import sdls from 'src/graphql/**/*.sdl.{js,ts}' +import services from 'src/services/**/*.{js,ts}' +import { getCurrentUser } from 'src/lib/auth' +import { db } from 'src/lib/db' +import { logger } from 'src/lib/logger' +import { getAsyncStoreInstance as __rw_getAsyncStoreInstance } from '@redwoodjs/context/dist/store' +const __rw_handler = createGraphQLHandler({ + authDecoder, + getCurrentUser, + loggerConfig: { + logger, + options: {}, + }, + directives, + sdls, + services, + onException: () => { + // Disconnect from your database with an unhandled exception. + db.$disconnect() + }, +}) +export const handler = (__rw_event, __rw__context) => { + // The store will be undefined if no context isolation has been performed yet + const __rw_contextStore = __rw_getAsyncStoreInstance().getStore() + if (__rw_contextStore === undefined) { + return __rw_getAsyncStoreInstance().run( + new Map(), + __rw_handler, + __rw_event, + __rw__context + ) + } + return __rw_handler(__rw_event, __rw__context) +} \ No newline at end of file diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/dev-fatal-error-page/test/code.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/dev-fatal-error-page/test/code.js new file mode 100644 index 000000000000..72b696602ac3 --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/dev-fatal-error-page/test/code.js @@ -0,0 +1,22 @@ +// This page will be rendered when an error makes it all the way to the top of the +// application without being handled by a Javascript catch statement or React error +// boundary. +// +// You can modify this page as you wish, but it is important to keep things simple to +// avoid the possibility that it will cause its own error. If it does, Redwood will +// still render a generic error page, but your users will prefer something a bit more +// thoughtful :) + +// This import will be automatically removed when building for production +import { DevFatalErrorPage } from '@redwoodjs/web/dist/components/DevFatalErrorPage' + +export default DevFatalErrorPage || + (() => ( +
+
+

+ Something went wrong +

+
+
+ )) diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/dev-fatal-error-page/test/output.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/dev-fatal-error-page/test/output.js new file mode 100644 index 000000000000..fc9c3c482b24 --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/dev-fatal-error-page/test/output.js @@ -0,0 +1,29 @@ +// This page will be rendered when an error makes it all the way to the top of the +// application without being handled by a Javascript catch statement or React error +// boundary. +// +// You can modify this page as you wish, but it is important to keep things simple to +// avoid the possibility that it will cause its own error. If it does, Redwood will +// still render a generic error page, but your users will prefer something a bit more +// thoughtful :) +// This import will be automatically removed when building for production +const DevFatalErrorPage = undefined +export default DevFatalErrorPage || + (() => + /*#__PURE__*/ React.createElement( + 'main', + null, + /*#__PURE__*/ React.createElement( + 'section', + null, + /*#__PURE__*/ React.createElement( + 'h1', + null, + /*#__PURE__*/ React.createElement( + 'span', + null, + 'Something went wrong' + ) + ) + ) + )) diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/directory-named-imports/JSX/JSX.jsx b/packages/babel-config/src/plugins/__tests__/__fixtures__/directory-named-imports/JSX/JSX.jsx new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/directory-named-imports/Module/Module.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/directory-named-imports/Module/Module.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/directory-named-imports/TS/TS.ts b/packages/babel-config/src/plugins/__tests__/__fixtures__/directory-named-imports/TS/TS.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/directory-named-imports/TSWithIndex/TSWithIndex.ts b/packages/babel-config/src/plugins/__tests__/__fixtures__/directory-named-imports/TSWithIndex/TSWithIndex.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/directory-named-imports/TSWithIndex/index.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/directory-named-imports/TSWithIndex/index.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/directory-named-imports/TSX/TSX.tsx b/packages/babel-config/src/plugins/__tests__/__fixtures__/directory-named-imports/TSX/TSX.tsx new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/directory-named-imports/indexModule/index.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/directory-named-imports/indexModule/index.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/directory-named-imports/indexModule/indexModule.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/directory-named-imports/indexModule/indexModule.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/import-dir/__fixtures__/a.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/__fixtures__/a.js similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/import-dir/__fixtures__/a.js rename to packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/__fixtures__/a.js diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/import-dir/__fixtures__/a.test.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/__fixtures__/a.test.js similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/import-dir/__fixtures__/a.test.js rename to packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/__fixtures__/a.test.js diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/import-dir/__fixtures__/b.ts b/packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/__fixtures__/b.ts similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/import-dir/__fixtures__/b.ts rename to packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/__fixtures__/b.ts diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/import-dir/__fixtures__/c.sdl.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/__fixtures__/c.sdl.js similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/import-dir/__fixtures__/c.sdl.js rename to packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/__fixtures__/c.sdl.js diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/import-dir/__fixtures__/nested/d.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/__fixtures__/nested/d.js similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/import-dir/__fixtures__/nested/d.js rename to packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/__fixtures__/nested/d.js diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/__fixtures__/nested/d.scenarios.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/__fixtures__/nested/d.scenarios.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/__fixtures__/types.d.ts b/packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/__fixtures__/types.d.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/import-dir-default/code.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/import-dir-default/code.js new file mode 100644 index 000000000000..e8409924624c --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/import-dir-default/code.js @@ -0,0 +1 @@ +import services from '../__fixtures__/**/*.{js,ts}' diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/import-dir/import-dir-default/output.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/import-dir-default/output.js similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/import-dir/import-dir-default/output.js rename to packages/babel-config/src/plugins/__tests__/__fixtures__/import-dir/import-dir-default/output.js diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/mock-cell-data/output_NumTodosCell.mock.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/mock-cell-data/output_NumTodosCell.mock.js similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/mock-cell-data/output_NumTodosCell.mock.js rename to packages/babel-config/src/plugins/__tests__/__fixtures__/mock-cell-data/output_NumTodosCell.mock.js diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/mock-cell-data/output_NumTodosTwoCell.mock.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/mock-cell-data/output_NumTodosTwoCell.mock.js similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/mock-cell-data/output_NumTodosTwoCell.mock.js rename to packages/babel-config/src/plugins/__tests__/__fixtures__/mock-cell-data/output_NumTodosTwoCell.mock.js diff --git a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/mock-cell-data/output_TodoListCell.mock.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/mock-cell-data/output_TodoListCell.mock.js similarity index 99% rename from packages/internal/src/build/babelPlugins/__tests__/__fixtures__/mock-cell-data/output_TodoListCell.mock.js rename to packages/babel-config/src/plugins/__tests__/__fixtures__/mock-cell-data/output_TodoListCell.mock.js index 60a3a0e1cc3f..aa875d687bb4 100644 --- a/packages/internal/src/build/babelPlugins/__tests__/__fixtures__/mock-cell-data/output_TodoListCell.mock.js +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/mock-cell-data/output_TodoListCell.mock.js @@ -1,4 +1,3 @@ - import { afterQuery } from './TodoListCell.tsx' export const standard = () => afterQuery( diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/directive-skipAuth/code.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/directive-skipAuth/code.js new file mode 100644 index 000000000000..e85b94ae8b89 --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/directive-skipAuth/code.js @@ -0,0 +1,16 @@ +import gql from 'graphql-tag' + +import { createValidatorDirective } from '@redwoodjs/graphql-server' + +export const schema = gql` + """ + Use to skip authentication checks and allow public access. + """ + directive @skipAuth on FIELD_DEFINITION +` + +const skipAuth = createValidatorDirective(schema, () => { + return +}) + +export default skipAuth diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/directive-skipAuth/output.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/directive-skipAuth/output.js new file mode 100644 index 000000000000..40d4c728bfbd --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/directive-skipAuth/output.js @@ -0,0 +1,13 @@ +import { trace as RW_OTEL_WRAPPER_TRACE } from '@opentelemetry/api' +import gql from 'graphql-tag' +import { createValidatorDirective } from '@redwoodjs/graphql-server' +export const schema = gql` + """ + Use to skip authentication checks and allow public access. + """ + directive @skipAuth on FIELD_DEFINITION +` +const skipAuth = createValidatorDirective(schema, () => { + return +}) +export default skipAuth \ No newline at end of file diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/function-auth/code.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/function-auth/code.js new file mode 100644 index 000000000000..b3494cb4d877 --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/function-auth/code.js @@ -0,0 +1,102 @@ +import { DbAuthHandler, DbAuthHandlerOptions } from '@redwoodjs/auth-dbauth-api' + +import { db } from 'src/lib/db' + +export const handler = async ( + event, + context +) => { + const forgotPasswordOptions = { + handler: (user) => { + return user + }, + + expires: 60 * 60 * 24, + + errors: { + usernameNotFound: 'Username not found', + usernameRequired: 'Username is required', + }, + } + + const loginOptions = { + handler: (user) => { + return user + }, + + errors: { + usernameOrPasswordMissing: 'Both username and password are required', + usernameNotFound: 'Username ${username} not found', + incorrectPassword: 'Incorrect password for ${username}', + }, + + expires: 60 * 60 * 24 * 365 * 10, + } + + const resetPasswordOptions = { + handler: (_user) => { + return true + }, + + allowReusedPassword: true, + + errors: { + resetTokenExpired: 'resetToken is expired', + resetTokenInvalid: 'resetToken is invalid', + resetTokenRequired: 'resetToken is required', + reusedPassword: 'Must choose a new password', + }, + } + + const signupOptions = { + handler: ({ username, hashedPassword, salt, userAttributes }) => { + return db.user.create({ + data: { + email: username, + hashedPassword: hashedPassword, + salt: salt, + fullName: userAttributes['full-name'], + }, + }) + }, + + passwordValidation: (_password) => { + return true + }, + + errors: { + fieldMissing: '${field} is required', + usernameTaken: 'Username `${username}` already in use', + }, + } + + const authHandler = new DbAuthHandler(event, context, { + db: db, + + authModelAccessor: 'user', + + authFields: { + id: 'id', + username: 'email', + hashedPassword: 'hashedPassword', + salt: 'salt', + resetToken: 'resetToken', + resetTokenExpiresAt: 'resetTokenExpiresAt', + }, + + cookie: { + HttpOnly: true, + Path: '/', + SameSite: 'Strict', + Secure: process.env.NODE_ENV !== 'development', + + }, + + forgotPassword: forgotPasswordOptions, + login: loginOptions, + resetPassword: resetPasswordOptions, + signup: signupOptions, + }) + + return await authHandler.invoke() +} diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/function-auth/output.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/function-auth/output.js new file mode 100644 index 000000000000..c2e265b1563b --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/function-auth/output.js @@ -0,0 +1,105 @@ +import { trace as RW_OTEL_WRAPPER_TRACE } from '@opentelemetry/api' +import { DbAuthHandler, DbAuthHandlerOptions } from '@redwoodjs/auth-dbauth-api' +import { db } from 'src/lib/db' +export const handler = async (event, context) => { + const __handler = async (event, context) => { + const forgotPasswordOptions = { + handler: (user) => { + return user + }, + expires: 60 * 60 * 24, + errors: { + usernameNotFound: 'Username not found', + usernameRequired: 'Username is required', + }, + } + const loginOptions = { + handler: (user) => { + return user + }, + errors: { + usernameOrPasswordMissing: 'Both username and password are required', + usernameNotFound: 'Username ${username} not found', + incorrectPassword: 'Incorrect password for ${username}', + }, + expires: 60 * 60 * 24 * 365 * 10, + } + const resetPasswordOptions = { + handler: (_user) => { + return true + }, + allowReusedPassword: true, + errors: { + resetTokenExpired: 'resetToken is expired', + resetTokenInvalid: 'resetToken is invalid', + resetTokenRequired: 'resetToken is required', + reusedPassword: 'Must choose a new password', + }, + } + const signupOptions = { + handler: ({ username, hashedPassword, salt, userAttributes }) => { + return db.user.create({ + data: { + email: username, + hashedPassword: hashedPassword, + salt: salt, + fullName: userAttributes['full-name'], + }, + }) + }, + passwordValidation: (_password) => { + return true + }, + errors: { + fieldMissing: '${field} is required', + usernameTaken: 'Username `${username}` already in use', + }, + } + const authHandler = new DbAuthHandler(event, context, { + db: db, + authModelAccessor: 'user', + authFields: { + id: 'id', + username: 'email', + hashedPassword: 'hashedPassword', + salt: 'salt', + resetToken: 'resetToken', + resetTokenExpiresAt: 'resetTokenExpiresAt', + }, + cookie: { + HttpOnly: true, + Path: '/', + SameSite: 'Strict', + Secure: process.env.NODE_ENV !== 'development', + }, + forgotPassword: forgotPasswordOptions, + login: loginOptions, + resetPassword: resetPasswordOptions, + signup: signupOptions, + }) + return await authHandler.invoke() + } + const RW_OTEL_WRAPPER_TRACER = RW_OTEL_WRAPPER_TRACE.getTracer('redwoodjs') + const RW_OTEL_WRAPPER_RESULT = await RW_OTEL_WRAPPER_TRACER.startActiveSpan( + 'redwoodjs:api:__MOCKED_API_FOLDER__:handler', + async (span) => { + span.setAttribute('code.function', 'handler') + span.setAttribute('code.filepath', '__MOCKED_FILENAME__') + try { + const RW_OTEL_WRAPPER_INNER_RESULT = await __handler(event, context) + span.end() + return RW_OTEL_WRAPPER_INNER_RESULT + } catch (error) { + span.recordException(error) + span.setStatus({ + code: 2, + message: + error?.message?.split('\n')[0] ?? error?.toString()?.split('\n')[0], + }) + span.end() + throw error + } + } + ) + return RW_OTEL_WRAPPER_RESULT +} \ No newline at end of file diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/function-graphql/code.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/function-graphql/code.js new file mode 100644 index 000000000000..5d8db6ab8f2a --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/function-graphql/code.js @@ -0,0 +1,23 @@ +import { authDecoder } from '@redwoodjs/auth-dbauth-api' +import { createGraphQLHandler } from '@redwoodjs/graphql-server' + +import directives from 'src/directives/**/*.{js,ts}' +import sdls from 'src/graphql/**/*.sdl.{js,ts}' +import services from 'src/services/**/*.{js,ts}' + +import { getCurrentUser } from 'src/lib/auth' +import { db } from 'src/lib/db' +import { logger } from 'src/lib/logger' + +export const handler = createGraphQLHandler({ + authDecoder, + getCurrentUser, + loggerConfig: { logger, options: {} }, + directives, + sdls, + services, + onException: () => { + // Disconnect from your database with an unhandled exception. + db.$disconnect() + }, +}) diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/function-graphql/output.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/function-graphql/output.js new file mode 100644 index 000000000000..873ed281570b --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/function-graphql/output.js @@ -0,0 +1,24 @@ +import { trace as RW_OTEL_WRAPPER_TRACE } from '@opentelemetry/api' +import { authDecoder } from '@redwoodjs/auth-dbauth-api' +import { createGraphQLHandler } from '@redwoodjs/graphql-server' +import directives from 'src/directives/**/*.{js,ts}' +import sdls from 'src/graphql/**/*.sdl.{js,ts}' +import services from 'src/services/**/*.{js,ts}' +import { getCurrentUser } from 'src/lib/auth' +import { db } from 'src/lib/db' +import { logger } from 'src/lib/logger' +export const handler = createGraphQLHandler({ + authDecoder, + getCurrentUser, + loggerConfig: { + logger, + options: {}, + }, + directives, + sdls, + services, + onException: () => { + // Disconnect from your database with an unhandled exception. + db.$disconnect() + }, +}) \ No newline at end of file diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/lib-auth/code.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/lib-auth/code.js new file mode 100644 index 000000000000..a268c008e449 --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/lib-auth/code.js @@ -0,0 +1,61 @@ +import { AuthenticationError, ForbiddenError } from '@redwoodjs/graphql-server' + +import { db } from './db' + +export const getCurrentUser = async (session) => { + if (!session || typeof session.id !== 'number') { + throw new Error('Invalid session') + } + + return await db.user.findUnique({ + where: { id: session.id }, + select: { id: true, roles: true, email: true }, + }) +} + +export const isAuthenticated = () => { + return !!context.currentUser +} + +export const hasRole = (roles) => { + if (!isAuthenticated()) { + return false + } + + const currentUserRoles = context.currentUser?.roles + + if (typeof roles === 'string') { + if (typeof currentUserRoles === 'string') { + // roles to check is a string, currentUser.roles is a string + return currentUserRoles === roles + } else if (Array.isArray(currentUserRoles)) { + // roles to check is a string, currentUser.roles is an array + return currentUserRoles?.some((allowedRole) => roles === allowedRole) + } + } + + if (Array.isArray(roles)) { + if (Array.isArray(currentUserRoles)) { + // roles to check is an array, currentUser.roles is an array + return currentUserRoles?.some((allowedRole) => + roles.includes(allowedRole) + ) + } else if (typeof currentUserRoles === 'string') { + // roles to check is an array, currentUser.roles is a string + return roles.some((allowedRole) => currentUserRoles === allowedRole) + } + } + + // roles not found + return false +} + +export const requireAuth = ({ roles } = {}) => { + if (!isAuthenticated()) { + throw new AuthenticationError("You don't have permission to do that.") + } + + if (roles && !hasRole(roles)) { + throw new ForbiddenError("You don't have access to do that.") + } +} diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/lib-auth/output.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/lib-auth/output.js new file mode 100644 index 000000000000..893341755924 --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/lib-auth/output.js @@ -0,0 +1,160 @@ +import { trace as RW_OTEL_WRAPPER_TRACE } from '@opentelemetry/api' +import { AuthenticationError, ForbiddenError } from '@redwoodjs/graphql-server' +import { db } from './db' +export const getCurrentUser = async (session) => { + const __getCurrentUser = async (session) => { + if (!session || typeof session.id !== 'number') { + throw new Error('Invalid session') + } + return await db.user.findUnique({ + where: { + id: session.id, + }, + select: { + id: true, + roles: true, + email: true, + }, + }) + } + const RW_OTEL_WRAPPER_TRACER = RW_OTEL_WRAPPER_TRACE.getTracer('redwoodjs') + const RW_OTEL_WRAPPER_RESULT = await RW_OTEL_WRAPPER_TRACER.startActiveSpan( + 'redwoodjs:api:__MOCKED_API_FOLDER__:getCurrentUser', + async (span) => { + span.setAttribute('code.function', 'getCurrentUser') + span.setAttribute('code.filepath', '__MOCKED_FILENAME__') + try { + const RW_OTEL_WRAPPER_INNER_RESULT = await __getCurrentUser(session) + span.end() + return RW_OTEL_WRAPPER_INNER_RESULT + } catch (error) { + span.recordException(error) + span.setStatus({ + code: 2, + message: + error?.message?.split('\n')[0] ?? error?.toString()?.split('\n')[0], + }) + span.end() + throw error + } + } + ) + return RW_OTEL_WRAPPER_RESULT +} +export const isAuthenticated = () => { + const __isAuthenticated = () => { + return !!context.currentUser + } + const RW_OTEL_WRAPPER_TRACER = RW_OTEL_WRAPPER_TRACE.getTracer('redwoodjs') + const RW_OTEL_WRAPPER_RESULT = RW_OTEL_WRAPPER_TRACER.startActiveSpan( + 'redwoodjs:api:__MOCKED_API_FOLDER__:isAuthenticated', + (span) => { + span.setAttribute('code.function', 'isAuthenticated') + span.setAttribute('code.filepath', '__MOCKED_FILENAME__') + try { + const RW_OTEL_WRAPPER_INNER_RESULT = __isAuthenticated() + span.end() + return RW_OTEL_WRAPPER_INNER_RESULT + } catch (error) { + span.recordException(error) + span.setStatus({ + code: 2, + message: + error?.message?.split('\n')[0] ?? error?.toString()?.split('\n')[0], + }) + span.end() + throw error + } + } + ) + return RW_OTEL_WRAPPER_RESULT +} +export const hasRole = (roles) => { + const __hasRole = (roles) => { + if (!isAuthenticated()) { + return false + } + const currentUserRoles = context.currentUser?.roles + if (typeof roles === 'string') { + if (typeof currentUserRoles === 'string') { + // roles to check is a string, currentUser.roles is a string + return currentUserRoles === roles + } else if (Array.isArray(currentUserRoles)) { + // roles to check is a string, currentUser.roles is an array + return currentUserRoles?.some((allowedRole) => roles === allowedRole) + } + } + if (Array.isArray(roles)) { + if (Array.isArray(currentUserRoles)) { + // roles to check is an array, currentUser.roles is an array + return currentUserRoles?.some((allowedRole) => + roles.includes(allowedRole) + ) + } else if (typeof currentUserRoles === 'string') { + // roles to check is an array, currentUser.roles is a string + return roles.some((allowedRole) => currentUserRoles === allowedRole) + } + } + + // roles not found + return false + } + const RW_OTEL_WRAPPER_TRACER = RW_OTEL_WRAPPER_TRACE.getTracer('redwoodjs') + const RW_OTEL_WRAPPER_RESULT = RW_OTEL_WRAPPER_TRACER.startActiveSpan( + 'redwoodjs:api:__MOCKED_API_FOLDER__:hasRole', + (span) => { + span.setAttribute('code.function', 'hasRole') + span.setAttribute('code.filepath', '__MOCKED_FILENAME__') + try { + const RW_OTEL_WRAPPER_INNER_RESULT = __hasRole(roles) + span.end() + return RW_OTEL_WRAPPER_INNER_RESULT + } catch (error) { + span.recordException(error) + span.setStatus({ + code: 2, + message: + error?.message?.split('\n')[0] ?? error?.toString()?.split('\n')[0], + }) + span.end() + throw error + } + } + ) + return RW_OTEL_WRAPPER_RESULT +} +export const requireAuth = ({ roles } = {}) => { + const __requireAuth = ({ roles } = {}) => { + if (!isAuthenticated()) { + throw new AuthenticationError("You don't have permission to do that.") + } + if (roles && !hasRole(roles)) { + throw new ForbiddenError("You don't have access to do that.") + } + } + const RW_OTEL_WRAPPER_TRACER = RW_OTEL_WRAPPER_TRACE.getTracer('redwoodjs') + const RW_OTEL_WRAPPER_RESULT = RW_OTEL_WRAPPER_TRACER.startActiveSpan( + 'redwoodjs:api:__MOCKED_API_FOLDER__:requireAuth', + (span) => { + span.setAttribute('code.function', 'requireAuth') + span.setAttribute('code.filepath', '__MOCKED_FILENAME__') + try { + const RW_OTEL_WRAPPER_INNER_RESULT = __requireAuth({ + roles, + }) + span.end() + return RW_OTEL_WRAPPER_INNER_RESULT + } catch (error) { + span.recordException(error) + span.setStatus({ + code: 2, + message: + error?.message?.split('\n')[0] ?? error?.toString()?.split('\n')[0], + }) + span.end() + throw error + } + } + ) + return RW_OTEL_WRAPPER_RESULT +} \ No newline at end of file diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/lib-db/code.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/lib-db/code.js new file mode 100644 index 000000000000..63a4c976ab1c --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/lib-db/code.js @@ -0,0 +1,18 @@ +import { PrismaClient } from '@prisma/client' + +import { emitLogLevels, handlePrismaLogging } from '@redwoodjs/api/logger' + +import { logger } from './logger' + +/* + * Instance of the Prisma Client + */ +export const db = new PrismaClient({ + log: emitLogLevels(['info', 'warn', 'error']), +}) + +handlePrismaLogging({ + db, + logger, + logLevels: ['info', 'warn', 'error'], +}) diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/lib-db/output.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/lib-db/output.js new file mode 100644 index 000000000000..e70d8a743d2e --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/lib-db/output.js @@ -0,0 +1,16 @@ +import { trace as RW_OTEL_WRAPPER_TRACE } from '@opentelemetry/api' +import { PrismaClient } from '@prisma/client' +import { emitLogLevels, handlePrismaLogging } from '@redwoodjs/api/logger' +import { logger } from './logger' + +/* + * Instance of the Prisma Client + */ +export const db = new PrismaClient({ + log: emitLogLevels(['info', 'warn', 'error']), +}) +handlePrismaLogging({ + db, + logger, + logLevels: ['info', 'warn', 'error'], +}) \ No newline at end of file diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-basic/code.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-basic/code.js new file mode 100644 index 000000000000..6bdecbe8123b --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-basic/code.js @@ -0,0 +1,35 @@ +import { db } from 'src/lib/db' + +export const contacts = () => { + return db.contact.findMany() +} + +export const contact = ({ id }) => { + return db.contact.findUnique({ + where: { id }, + }) +} + +export const createContact = ({ + input, +}) => { + return db.contact.create({ + data: input, + }) +} + +export const updateContact = ({ + id, + input, +}) => { + return db.contact.update({ + data: input, + where: { id }, + }) +} + +export const deleteContact = ({ id }) => { + return db.contact.delete({ + where: { id }, + }) +} diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-basic/output.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-basic/output.js new file mode 100644 index 000000000000..bede484d4d4b --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-basic/output.js @@ -0,0 +1,166 @@ +import { trace as RW_OTEL_WRAPPER_TRACE } from '@opentelemetry/api' +import { db } from 'src/lib/db' +export const contacts = () => { + const __contacts = () => { + return db.contact.findMany() + } + const RW_OTEL_WRAPPER_TRACER = RW_OTEL_WRAPPER_TRACE.getTracer('redwoodjs') + const RW_OTEL_WRAPPER_RESULT = RW_OTEL_WRAPPER_TRACER.startActiveSpan( + 'redwoodjs:api:__MOCKED_API_FOLDER__:contacts', + (span) => { + span.setAttribute('code.function', 'contacts') + span.setAttribute('code.filepath', '__MOCKED_FILENAME__') + try { + const RW_OTEL_WRAPPER_INNER_RESULT = __contacts() + span.end() + return RW_OTEL_WRAPPER_INNER_RESULT + } catch (error) { + span.recordException(error) + span.setStatus({ + code: 2, + message: + error?.message?.split('\n')[0] ?? error?.toString()?.split('\n')[0], + }) + span.end() + throw error + } + } + ) + return RW_OTEL_WRAPPER_RESULT +} +export const contact = ({ id }) => { + const __contact = ({ id }) => { + return db.contact.findUnique({ + where: { + id, + }, + }) + } + const RW_OTEL_WRAPPER_TRACER = RW_OTEL_WRAPPER_TRACE.getTracer('redwoodjs') + const RW_OTEL_WRAPPER_RESULT = RW_OTEL_WRAPPER_TRACER.startActiveSpan( + 'redwoodjs:api:__MOCKED_API_FOLDER__:contact', + (span) => { + span.setAttribute('code.function', 'contact') + span.setAttribute('code.filepath', '__MOCKED_FILENAME__') + try { + const RW_OTEL_WRAPPER_INNER_RESULT = __contact({ + id, + }) + span.end() + return RW_OTEL_WRAPPER_INNER_RESULT + } catch (error) { + span.recordException(error) + span.setStatus({ + code: 2, + message: + error?.message?.split('\n')[0] ?? error?.toString()?.split('\n')[0], + }) + span.end() + throw error + } + } + ) + return RW_OTEL_WRAPPER_RESULT +} +export const createContact = ({ input }) => { + const __createContact = ({ input }) => { + return db.contact.create({ + data: input, + }) + } + const RW_OTEL_WRAPPER_TRACER = RW_OTEL_WRAPPER_TRACE.getTracer('redwoodjs') + const RW_OTEL_WRAPPER_RESULT = RW_OTEL_WRAPPER_TRACER.startActiveSpan( + 'redwoodjs:api:__MOCKED_API_FOLDER__:createContact', + (span) => { + span.setAttribute('code.function', 'createContact') + span.setAttribute('code.filepath', '__MOCKED_FILENAME__') + try { + const RW_OTEL_WRAPPER_INNER_RESULT = __createContact({ + input, + }) + span.end() + return RW_OTEL_WRAPPER_INNER_RESULT + } catch (error) { + span.recordException(error) + span.setStatus({ + code: 2, + message: + error?.message?.split('\n')[0] ?? error?.toString()?.split('\n')[0], + }) + span.end() + throw error + } + } + ) + return RW_OTEL_WRAPPER_RESULT +} +export const updateContact = ({ id, input }) => { + const __updateContact = ({ id, input }) => { + return db.contact.update({ + data: input, + where: { + id, + }, + }) + } + const RW_OTEL_WRAPPER_TRACER = RW_OTEL_WRAPPER_TRACE.getTracer('redwoodjs') + const RW_OTEL_WRAPPER_RESULT = RW_OTEL_WRAPPER_TRACER.startActiveSpan( + 'redwoodjs:api:__MOCKED_API_FOLDER__:updateContact', + (span) => { + span.setAttribute('code.function', 'updateContact') + span.setAttribute('code.filepath', '__MOCKED_FILENAME__') + try { + const RW_OTEL_WRAPPER_INNER_RESULT = __updateContact({ + id, + input, + }) + span.end() + return RW_OTEL_WRAPPER_INNER_RESULT + } catch (error) { + span.recordException(error) + span.setStatus({ + code: 2, + message: + error?.message?.split('\n')[0] ?? error?.toString()?.split('\n')[0], + }) + span.end() + throw error + } + } + ) + return RW_OTEL_WRAPPER_RESULT +} +export const deleteContact = ({ id }) => { + const __deleteContact = ({ id }) => { + return db.contact.delete({ + where: { + id, + }, + }) + } + const RW_OTEL_WRAPPER_TRACER = RW_OTEL_WRAPPER_TRACE.getTracer('redwoodjs') + const RW_OTEL_WRAPPER_RESULT = RW_OTEL_WRAPPER_TRACER.startActiveSpan( + 'redwoodjs:api:__MOCKED_API_FOLDER__:deleteContact', + (span) => { + span.setAttribute('code.function', 'deleteContact') + span.setAttribute('code.filepath', '__MOCKED_FILENAME__') + try { + const RW_OTEL_WRAPPER_INNER_RESULT = __deleteContact({ + id, + }) + span.end() + return RW_OTEL_WRAPPER_INNER_RESULT + } catch (error) { + span.recordException(error) + span.setStatus({ + code: 2, + message: + error?.message?.split('\n')[0] ?? error?.toString()?.split('\n')[0], + }) + span.end() + throw error + } + } + ) + return RW_OTEL_WRAPPER_RESULT +} \ No newline at end of file diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-custom/code.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-custom/code.js new file mode 100644 index 000000000000..5d01ea5c350f --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-custom/code.js @@ -0,0 +1,25 @@ +// This example function has default values in the function signature +export const withDefaultValues = async ({ + id, + process = true, + output = [], + backup = () => ('backup'), +}) => { + if (process) { + output.push(backup()) + } + return `${id}: ${output.join('\t')}` +} + +// This example function has a different default value definition in the function signature +export const withDefaultValuesTwo = async (args = { + id, + process: true, + output: [], + backup: () => ('backup'), +}) => { + if (args.process) { + args.output.push(args.backup()) + } + return `${args.id}: ${args.output.join('\t')}` +} diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-custom/output.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-custom/output.js new file mode 100644 index 000000000000..fdf834704bb9 --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-custom/output.js @@ -0,0 +1,95 @@ +import { trace as RW_OTEL_WRAPPER_TRACE } from '@opentelemetry/api' +// This example function has default values in the function signature +export const withDefaultValues = async ({ + id, + process = true, + output = [], + backup = () => 'backup', +}) => { + const __withDefaultValues = async ({ + id, + process = true, + output = [], + backup = () => 'backup', + }) => { + if (process) { + output.push(backup()) + } + return `${id}: ${output.join('\t')}` + } + const RW_OTEL_WRAPPER_TRACER = RW_OTEL_WRAPPER_TRACE.getTracer('redwoodjs') + const RW_OTEL_WRAPPER_RESULT = await RW_OTEL_WRAPPER_TRACER.startActiveSpan( + 'redwoodjs:api:__MOCKED_API_FOLDER__:withDefaultValues', + async (span) => { + span.setAttribute('code.function', 'withDefaultValues') + span.setAttribute('code.filepath', '__MOCKED_FILENAME__') + try { + const RW_OTEL_WRAPPER_INNER_RESULT = await __withDefaultValues({ + id, + process: process, + output: output, + backup: backup, + }) + span.end() + return RW_OTEL_WRAPPER_INNER_RESULT + } catch (error) { + span.recordException(error) + span.setStatus({ + code: 2, + message: + error?.message?.split('\n')[0] ?? error?.toString()?.split('\n')[0], + }) + span.end() + throw error + } + } + ) + return RW_OTEL_WRAPPER_RESULT +} + +// This example function has a different default value definition in the function signature +export const withDefaultValuesTwo = async ( + args = { + id, + process: true, + output: [], + backup: () => 'backup', + } +) => { + const __withDefaultValuesTwo = async ( + args = { + id, + process: true, + output: [], + backup: () => 'backup', + } + ) => { + if (args.process) { + args.output.push(args.backup()) + } + return `${args.id}: ${args.output.join('\t')}` + } + const RW_OTEL_WRAPPER_TRACER = RW_OTEL_WRAPPER_TRACE.getTracer('redwoodjs') + const RW_OTEL_WRAPPER_RESULT = await RW_OTEL_WRAPPER_TRACER.startActiveSpan( + 'redwoodjs:api:__MOCKED_API_FOLDER__:withDefaultValuesTwo', + async (span) => { + span.setAttribute('code.function', 'withDefaultValuesTwo') + span.setAttribute('code.filepath', '__MOCKED_FILENAME__') + try { + const RW_OTEL_WRAPPER_INNER_RESULT = await __withDefaultValuesTwo(args) + span.end() + return RW_OTEL_WRAPPER_INNER_RESULT + } catch (error) { + span.recordException(error) + span.setStatus({ + code: 2, + message: + error?.message?.split('\n')[0] ?? error?.toString()?.split('\n')[0], + }) + span.end() + throw error + } + } + ) + return RW_OTEL_WRAPPER_RESULT +} \ No newline at end of file diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-instrumented/code.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-instrumented/code.js new file mode 100644 index 000000000000..210393e41988 --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-instrumented/code.js @@ -0,0 +1,22 @@ +import opentelemetry from '@opentelemetry/api' + +import { db } from 'src/lib/db' + +export const updateContact = ({ + id, + input, +} = { + id: 1, + input: { + name: 'R. Edwoods', + }, + }) => { + return opentelemetry.trace.getTracer('service').startActiveSpan('updateContact', async (span) => { + const data = await db.contact.update({ + data: input, + where: { id }, + }) + span.end() + return data + }) +} diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-instrumented/output.js b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-instrumented/output.js new file mode 100644 index 000000000000..cf325a44c74b --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/otel-wrapping/service-instrumented/output.js @@ -0,0 +1,59 @@ +import { trace as RW_OTEL_WRAPPER_TRACE } from '@opentelemetry/api' +import opentelemetry from '@opentelemetry/api' +import { db } from 'src/lib/db' +export const updateContact = ( + { id, input } = { + id: 1, + input: { + name: 'R. Edwoods', + }, + } +) => { + const __updateContact = ( + { id, input } = { + id: 1, + input: { + name: 'R. Edwoods', + }, + } + ) => { + return opentelemetry.trace + .getTracer('service') + .startActiveSpan('updateContact', async (span) => { + const data = await db.contact.update({ + data: input, + where: { + id, + }, + }) + span.end() + return data + }) + } + const RW_OTEL_WRAPPER_TRACER = RW_OTEL_WRAPPER_TRACE.getTracer('redwoodjs') + const RW_OTEL_WRAPPER_RESULT = RW_OTEL_WRAPPER_TRACER.startActiveSpan( + 'redwoodjs:api:__MOCKED_API_FOLDER__:updateContact', + (span) => { + span.setAttribute('code.function', 'updateContact') + span.setAttribute('code.filepath', '__MOCKED_FILENAME__') + try { + const RW_OTEL_WRAPPER_INNER_RESULT = __updateContact({ + id, + input, + }) + span.end() + return RW_OTEL_WRAPPER_INNER_RESULT + } catch (error) { + span.recordException(error) + span.setStatus({ + code: 2, + message: + error?.message?.split('\n')[0] ?? error?.toString()?.split('\n')[0], + }) + span.end() + throw error + } + } + ) + return RW_OTEL_WRAPPER_RESULT +} \ No newline at end of file diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/route-auto-loader/failure/redwood.toml b/packages/babel-config/src/plugins/__tests__/__fixtures__/route-auto-loader/failure/redwood.toml new file mode 100644 index 000000000000..286d9315b1ba --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/route-auto-loader/failure/redwood.toml @@ -0,0 +1,10 @@ +[web] + port = 8910 + apiProxyPath = "/api/functions" + +[api] + port = 8911 + [api.paths] + functions = './api/src/functions' + graphql = './api/src/graphql' + generated = './api/generated' diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/route-auto-loader/failure/web/src/Routes.tsx b/packages/babel-config/src/plugins/__tests__/__fixtures__/route-auto-loader/failure/web/src/Routes.tsx new file mode 100644 index 000000000000..6e95d14b53c8 --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/route-auto-loader/failure/web/src/Routes.tsx @@ -0,0 +1,11 @@ +import { Router, Route } from '@redwoodjs/router' + +const Routes = () => { + return ( + + + + ) +} + +export default Routes diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/route-auto-loader/failure/web/src/pages/HomePage/HomePage.tsx b/packages/babel-config/src/plugins/__tests__/__fixtures__/route-auto-loader/failure/web/src/pages/HomePage/HomePage.tsx new file mode 100644 index 000000000000..c184a81e8e9b --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/route-auto-loader/failure/web/src/pages/HomePage/HomePage.tsx @@ -0,0 +1,9 @@ +const HomePage = () => { + return ( +
+

HomePage

+
+ ) +} + +export default HomePage diff --git a/packages/babel-config/src/plugins/__tests__/__fixtures__/route-auto-loader/failure/web/src/pages/HomePage/useHomePage.tsx b/packages/babel-config/src/plugins/__tests__/__fixtures__/route-auto-loader/failure/web/src/pages/HomePage/useHomePage.tsx new file mode 100644 index 000000000000..e93ada786af0 --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/__fixtures__/route-auto-loader/failure/web/src/pages/HomePage/useHomePage.tsx @@ -0,0 +1,5 @@ +const useHomePage = () => { + return 'useHomePage' +} + +export default useHomePage diff --git a/packages/internal/src/build/babelPlugins/__tests__/babel-plugin-redwood-cell.test.ts b/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-cell.test.ts similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/babel-plugin-redwood-cell.test.ts rename to packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-cell.test.ts diff --git a/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-context-wrapping.test.ts b/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-context-wrapping.test.ts new file mode 100644 index 000000000000..25eecfd9be96 --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-context-wrapping.test.ts @@ -0,0 +1,11 @@ +import path from 'path' + +import pluginTester from 'babel-plugin-tester' + +import redwoodOtelWrappingPlugin from '../babel-plugin-redwood-context-wrapping' + +pluginTester({ + plugin: redwoodOtelWrappingPlugin, + pluginName: 'babel-plugin-redwood-context-wrapping', + fixtures: path.join(__dirname, '__fixtures__/context-wrapping'), +}) diff --git a/packages/internal/src/build/babelPlugins/__tests__/babel-plugin-redwood-directory-named-imports.ts b/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-directory-named-imports.test.ts similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/babel-plugin-redwood-directory-named-imports.ts rename to packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-directory-named-imports.test.ts diff --git a/packages/internal/src/build/babelPlugins/__tests__/babel-plugin-redwood-import-dir.test.ts b/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-import-dir.test.ts similarity index 100% rename from packages/internal/src/build/babelPlugins/__tests__/babel-plugin-redwood-import-dir.test.ts rename to packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-import-dir.test.ts diff --git a/packages/internal/src/build/babelPlugins/__tests__/babel-plugin-redwood-mock-cell-data.test.ts b/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-mock-cell-data.test.ts similarity index 89% rename from packages/internal/src/build/babelPlugins/__tests__/babel-plugin-redwood-mock-cell-data.test.ts rename to packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-mock-cell-data.test.ts index 3ff9149fbcca..1a63e132f863 100644 --- a/packages/internal/src/build/babelPlugins/__tests__/babel-plugin-redwood-mock-cell-data.test.ts +++ b/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-mock-cell-data.test.ts @@ -5,7 +5,8 @@ import pluginTester from 'babel-plugin-tester' import plugin from '../babel-plugin-redwood-mock-cell-data' describe('babel plugin redwood mock cell data', () => { - const __fixtures__ = path.resolve(__dirname, '../../../../../../__fixtures__') + const __fixtures__ = path.resolve(__dirname, '../../../../../__fixtures__') + process.env.RWJS_CWD = path.join(__fixtures__, 'example-todo-main') pluginTester({ plugin, diff --git a/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-otel-wrapping.test.ts b/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-otel-wrapping.test.ts new file mode 100644 index 000000000000..37ec1d2d5394 --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-otel-wrapping.test.ts @@ -0,0 +1,19 @@ +import path from 'path' + +import pluginTester from 'babel-plugin-tester' + +import redwoodOtelWrappingPlugin from '../babel-plugin-redwood-otel-wrapping' + +jest.mock('@redwoodjs/project-config', () => { + return { + getBaseDirFromFile: () => { + return '' + }, + } +}) + +pluginTester({ + plugin: redwoodOtelWrappingPlugin, + pluginName: 'babel-plugin-redwood-otel-wrapping', + fixtures: path.join(__dirname, '__fixtures__/otel-wrapping'), +}) diff --git a/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-remove-dev-fatal-error-page.test.ts b/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-remove-dev-fatal-error-page.test.ts new file mode 100644 index 000000000000..f07176f13563 --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-remove-dev-fatal-error-page.test.ts @@ -0,0 +1,16 @@ +import path from 'path' + +import pluginTester from 'babel-plugin-tester' + +import plugin from '../babel-plugin-redwood-remove-dev-fatal-error-page' + +describe('babel plugin redwood remove dev fatal error page', () => { + pluginTester({ + plugin, + pluginName: 'babel-plugin-redwood-remove-dev-fatal-error-page', + fixtures: path.join(__dirname, '__fixtures__/dev-fatal-error-page'), + babelOptions: { + presets: ['@babel/preset-react'], + }, + }) +}) diff --git a/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-routes-auto-loader.test.ts b/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-routes-auto-loader.test.ts new file mode 100644 index 000000000000..9c27d7c93800 --- /dev/null +++ b/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-routes-auto-loader.test.ts @@ -0,0 +1,70 @@ +import fs from 'fs' +import path from 'path' + +import * as babel from '@babel/core' + +import { getPaths } from '@redwoodjs/project-config' + +import babelRoutesAutoLoader from '../babel-plugin-redwood-routes-auto-loader' + +const transform = (filename: string) => { + const code = fs.readFileSync(filename, 'utf-8') + return babel.transform(code, { + filename, + presets: ['@babel/preset-react'], + plugins: [babelRoutesAutoLoader], + }) +} + +describe('mulitiple files ending in Page.{js,jsx,ts,tsx}', () => { + const FAILURE_FIXTURE_PATH = path.resolve( + __dirname, + './__fixtures__/route-auto-loader/failure' + ) + + beforeAll(() => { + process.env.RWJS_CWD = FAILURE_FIXTURE_PATH + }) + + afterAll(() => { + delete process.env.RWJS_CWD + }) + + test('Fails with appropriate message', () => { + expect(() => { + transform(getPaths().web.routes) + }).toThrowError( + "Unable to find only a single file ending in 'Page.{js,jsx,ts,tsx}' in the follow page directories: 'HomePage" + ) + }) +}) + +describe('page auto loader correctly imports pages', () => { + const FIXTURE_PATH = path.resolve( + __dirname, + '../../../../../__fixtures__/example-todo-main/' + ) + + let result: babel.BabelFileResult | null + + beforeAll(() => { + process.env.RWJS_CWD = FIXTURE_PATH + result = transform(getPaths().web.routes) + }) + + afterAll(() => { + delete process.env.RWJS_CWD + }) + + test('Pages get both a LazyComponent and a prerenderLoader', () => { + expect(result?.code).toContain(`const HomePage = { + name: "HomePage", + prerenderLoader: name => __webpack_require__(require.resolveWeak("./pages/HomePage/HomePage")), + LazyComponent: lazy(() => import( /* webpackChunkName: "HomePage" */"./pages/HomePage/HomePage")) +`) + }) + + test('Already imported pages are left alone.', () => { + expect(result?.code).toContain(`import FooPage from 'src/pages/FooPage'`) + }) +}) diff --git a/packages/internal/src/build/babelPlugins/__tests__/babel-plugin-redwood-src-alias.test.ts b/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-src-alias.test.ts similarity index 98% rename from packages/internal/src/build/babelPlugins/__tests__/babel-plugin-redwood-src-alias.test.ts rename to packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-src-alias.test.ts index fd027e8d8dad..7323831e49ca 100644 --- a/packages/internal/src/build/babelPlugins/__tests__/babel-plugin-redwood-src-alias.test.ts +++ b/packages/babel-config/src/plugins/__tests__/babel-plugin-redwood-src-alias.test.ts @@ -6,7 +6,7 @@ import plugin from '../babel-plugin-redwood-src-alias' const FIXTURE_PATH = path.resolve( __dirname, - '../../../../../../__fixtures__/empty-project' + '../../../../../__fixtures__/empty-project' ) describe('babel plugin redwood import dir - graphql function', () => { diff --git a/packages/internal/src/build/babelPlugins/babel-plugin-redwood-cell.ts b/packages/babel-config/src/plugins/babel-plugin-redwood-cell.ts similarity index 97% rename from packages/internal/src/build/babelPlugins/babel-plugin-redwood-cell.ts rename to packages/babel-config/src/plugins/babel-plugin-redwood-cell.ts index 96a6f6ac66d3..09cbbe7fbc10 100644 --- a/packages/internal/src/build/babelPlugins/babel-plugin-redwood-cell.ts +++ b/packages/babel-config/src/plugins/babel-plugin-redwood-cell.ts @@ -82,7 +82,7 @@ export default function ({ types: t }: { types: typeof types }): PluginObj { ) // Insert at the bottom of the file: - // + export default createCell({ QUERY?, Loading?, Succes?, Failure?, Empty?, beforeQuery?, isEmpty, afterQuery?, displayName? }) + // + export default createCell({ QUERY?, Loading?, Success?, Failure?, Empty?, beforeQuery?, isEmpty, afterQuery?, displayName? }) path.node.body.push( t.exportDefaultDeclaration( t.callExpression(t.identifier('createCell'), [ diff --git a/packages/babel-config/src/plugins/babel-plugin-redwood-context-wrapping.ts b/packages/babel-config/src/plugins/babel-plugin-redwood-context-wrapping.ts new file mode 100644 index 000000000000..9da33bc3ba77 --- /dev/null +++ b/packages/babel-config/src/plugins/babel-plugin-redwood-context-wrapping.ts @@ -0,0 +1,126 @@ +import type { PluginObj, types } from '@babel/core' + +// This wraps user API functions to ensure context isolation has been performed. This should already +// be done at the request level but in serverless environments like Netlify we need to do +// this at the function level as a safeguard. + +function generateWrappedHandler(t: typeof types, isAsync: boolean) { + const contextStoreVariableDeclaration = t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier('__rw_contextStore'), + t.callExpression( + t.memberExpression( + t.callExpression(t.identifier('__rw_getAsyncStoreInstance'), []), + t.identifier('getStore') + ), + [] + ) + ), + ]) + t.addComment( + contextStoreVariableDeclaration, + 'leading', + ' The store will be undefined if no context isolation has been performed yet', + true + ) + return t.arrowFunctionExpression( + [t.identifier('__rw_event'), t.identifier('__rw__context')], + t.blockStatement([ + contextStoreVariableDeclaration, + t.ifStatement( + t.binaryExpression( + '===', + t.identifier('__rw_contextStore'), + t.identifier('undefined') + ), + t.blockStatement([ + t.returnStatement( + t.callExpression( + t.memberExpression( + t.callExpression( + t.identifier('__rw_getAsyncStoreInstance'), + [] + ), + t.identifier('run') + ), + [ + t.newExpression(t.identifier('Map'), []), + t.identifier('__rw_handler'), + t.identifier('__rw_event'), + t.identifier('__rw__context'), + ] + ) + ), + ]) + ), + t.returnStatement( + t.callExpression(t.identifier('__rw_handler'), [ + t.identifier('__rw_event'), + t.identifier('__rw__context'), + ]) + ), + ]), + isAsync + ) +} + +export default function ({ types: t }: { types: typeof types }): PluginObj { + return { + name: 'babel-plugin-redwood-context-wrapping', + visitor: { + ExportNamedDeclaration(path, _state) { + // Confirm we're at the "handler" export + const declaration = path.node.declaration + if (!t.isVariableDeclaration(declaration)) { + return + } + const identifier = declaration.declarations[0].id + if (!t.isIdentifier(identifier)) { + return + } + if (identifier.name !== 'handler') { + return + } + + // Import the context package + const parentNode = path.parentPath.node + if (!t.isProgram(parentNode)) { + // This should be unreachable + return + } + path.insertBefore( + // import { getAsyncStoreInstance as __rw_getAsyncStoreInstance } from '@redwoodjs/context/dist/store' + t.importDeclaration( + [ + t.importSpecifier( + t.identifier('__rw_getAsyncStoreInstance'), + t.identifier('getAsyncStoreInstance') + ), + ], + t.stringLiteral('@redwoodjs/context/dist/store') + ) + ) + + // Copy the original handler function to a new renamed function + path.insertBefore( + t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier('__rw_handler'), + declaration.declarations[0].init + ), + ]) + ) + + // Attempt to determine if we should mark the handler as async + let isAsync = false + const originalInit = declaration.declarations[0].init + if (t.isFunction(originalInit)) { + isAsync = originalInit.async + } + + // Update the original handler to check the context status and call the renamed function + declaration.declarations[0].init = generateWrappedHandler(t, isAsync) + }, + }, + } +} diff --git a/packages/internal/src/build/babelPlugins/babel-plugin-redwood-directory-named-import.ts b/packages/babel-config/src/plugins/babel-plugin-redwood-directory-named-import.ts similarity index 100% rename from packages/internal/src/build/babelPlugins/babel-plugin-redwood-directory-named-import.ts rename to packages/babel-config/src/plugins/babel-plugin-redwood-directory-named-import.ts diff --git a/packages/internal/src/build/babelPlugins/babel-plugin-redwood-import-dir.ts b/packages/babel-config/src/plugins/babel-plugin-redwood-import-dir.ts similarity index 100% rename from packages/internal/src/build/babelPlugins/babel-plugin-redwood-import-dir.ts rename to packages/babel-config/src/plugins/babel-plugin-redwood-import-dir.ts diff --git a/packages/babel-config/src/plugins/babel-plugin-redwood-mock-cell-data.ts b/packages/babel-config/src/plugins/babel-plugin-redwood-mock-cell-data.ts new file mode 100644 index 000000000000..861154c8e83c --- /dev/null +++ b/packages/babel-config/src/plugins/babel-plugin-redwood-mock-cell-data.ts @@ -0,0 +1,285 @@ +import fs from 'fs' +import path from 'path' + +import type { types } from '@babel/core' +import type { PluginObj } from '@babel/core' +import { parse as babelParse } from '@babel/parser' +import type { ParserPlugin } from '@babel/parser' +import traverse from '@babel/traverse' +import fg from 'fast-glob' +import { parse as graphqlParse } from 'graphql' + +export default function ({ types: t }: { types: typeof types }): PluginObj { + let nodesToRemove: any[] = [] + let nodesToInsert: any[] = [] + + // export const standard = ${ex} + const createExportStandard = ( + ex: types.CallExpression | types.ArrowFunctionExpression + ) => + t.exportNamedDeclaration( + t.variableDeclaration('const', [ + t.variableDeclarator(t.identifier('standard'), ex), + ]) + ) + + return { + name: 'babel-plugin-redwood-mock-cell-data', + + visitor: { + Program: { + enter() { + nodesToRemove = [] + nodesToInsert = [] + }, + exit(p) { + for (const n of nodesToRemove) { + n.remove() + } + // Insert at the top of the file + p.node.body.unshift(...nodesToInsert) + }, + }, + ExportNamedDeclaration(p, state: { file?: any }) { + // This converts a standard export into a "mockGraphQLQuery" by automatically: + // Determining the query operation name for `QUERY` and, + // wrapping the exported data in `afterQuery` + // + // Rules: + // 1. Must be a *.mock.[ts,js] file. + // 2. That has a named export called "standard". + // 3. That are adjacent to a Cell. + // 4. The Cell has a operation name for the QUERY export. + + const d = p.node.declaration + + let mockFunction + + // Only auto-mock the standard export + + switch (d?.type) { + case 'VariableDeclaration': + // If its an arrow function + // or export standard = function() + { + const standardMockExport = d.declarations[0] + const id = standardMockExport.id as types.Identifier + const exportName = id?.name + + if (exportName !== 'standard') { + return + } + + const mockFunctionMaybe = standardMockExport?.init + if (!mockFunctionMaybe) { + return + } + + // If they're not exporting a function, blow up + if ( + mockFunctionMaybe.type !== 'ArrowFunctionExpression' && + mockFunctionMaybe.type !== 'FunctionExpression' + ) { + throw new Error( + `\n \n Mock Error: You must export your standard mock as a function \n \n` + ) + } + + mockFunction = mockFunctionMaybe + } + break + + case 'FunctionDeclaration': + { + const exportName = d.id?.name + + if (exportName !== 'standard') { + return + } + + // if its a normal function export e.g. export function standard() + // convert the named FunctionDeclaration to an arrow func i.e. (..args)=>{//originalbody here} + mockFunction = t.arrowFunctionExpression(d.params, d.body) + } + break + + default: + // If it isn't a mock function called standard, ignore it + return + } + + // Find the model of the Cell that is in the same directory. + const dirname = path.dirname(state.file.opts.filename) + const cellName = path.basename(dirname) + + const [cellPath] = fg.sync(`${cellName}.{js,jsx,ts,tsx}`, { + cwd: dirname, + absolute: true, + ignore: ['node_modules'], + }) + + if (!cellPath) { + return + } + + const cellMetadata = getCellMetadata(cellPath) + + if (cellMetadata.hasDefaultExport || !cellMetadata.hasQueryExport) { + return + } + + // mockGraphQLQuery(, ) + const mockGraphQLCall = t.callExpression( + t.identifier('mockGraphQLQuery'), + [t.stringLiteral(cellMetadata.operationName), mockFunction] + ) + + // Delete original "export const standard" + nodesToRemove = [...nodesToRemove, p] + + // + import { afterQuery } from './${cellFileName}' + // + export const standard = () => afterQuery(...) + if (cellMetadata.hasAfterQueryExport) { + const importAfterQuery = t.importDeclaration( + [ + t.importSpecifier( + t.identifier('afterQuery'), + t.identifier('afterQuery') + ), + ], + t.stringLiteral(`./${path.basename(cellPath)}`) + ) + + nodesToInsert = [ + ...nodesToInsert, + importAfterQuery, + createExportStandard( + t.arrowFunctionExpression( + [], + t.callExpression(t.identifier('afterQuery'), [ + t.callExpression(mockGraphQLCall, []), + ]) + ) + ), + ] + } else { + // + export const standard = mo + nodesToInsert = [ + ...nodesToInsert, + createExportStandard(mockGraphQLCall), + ] + } + }, + }, + } +} + +export const getCellMetadata = (p: string) => { + const ast = getCellAst(p) + + let hasDefaultExport = false + const namedExports: NamedExports[] = [] + let operation + + traverse(ast, { + ExportDefaultDeclaration() { + hasDefaultExport = true + return + }, + ExportNamedDeclaration(path) { + // Re-exports from other modules + // Eg: export { a, b } from './module' + const specifiers = path.node?.specifiers + + if (specifiers.length) { + for (const s of specifiers) { + const id = s.exported as types.Identifier + namedExports.push({ + name: id.name, + type: 're-export', + }) + } + return + } + + const declaration = path.node.declaration + + if (!declaration) { + return + } + + if (declaration.type === 'VariableDeclaration') { + const id = declaration.declarations[0].id as types.Identifier + + namedExports.push({ + name: id.name as string, + type: 'variable', + }) + } else if (declaration.type === 'FunctionDeclaration') { + namedExports.push({ + name: declaration?.id?.name as string, + type: 'function', + }) + } else if (declaration.type === 'ClassDeclaration') { + namedExports.push({ + name: declaration?.id?.name as string, + type: 'class', + }) + } + }, + TaggedTemplateExpression(path) { + // @ts-expect-error wip + if (path.parent?.id?.name !== 'QUERY') { + return + } + + operation = path.node.quasi.quasis[0].value.raw + }, + }) + + const hasQueryExport = namedExports.find(({ name }) => name === 'QUERY') + const hasAfterQueryExport = namedExports.find( + ({ name }) => name === 'afterQuery' + ) + + let operationName = '' + + if (operation) { + const document = graphqlParse(operation) + + for (const definition of document.definitions) { + if (definition.kind === 'OperationDefinition' && definition.name?.value) { + operationName = definition.name.value + } + } + } + + return { + hasDefaultExport, + namedExports, + hasQueryExport, + hasAfterQueryExport, + operationName, + } +} + +function getCellAst(filePath: string): types.Node { + const code = fs.readFileSync(filePath, 'utf-8') + const plugins = ['typescript', 'jsx'].filter(Boolean) as ParserPlugin[] + + try { + return babelParse(code, { + sourceType: 'module', + plugins, + }) + } catch (e: any) { + console.error(`Error parsing: ${filePath}`) + console.error(e) + throw new Error(e?.message) // we throw, so typescript doesn't complain about returning + } +} + +interface NamedExports { + name: string + type: 're-export' | 'variable' | 'function' | 'class' +} diff --git a/packages/babel-config/src/plugins/babel-plugin-redwood-otel-wrapping.ts b/packages/babel-config/src/plugins/babel-plugin-redwood-otel-wrapping.ts new file mode 100644 index 000000000000..7b1ab73644aa --- /dev/null +++ b/packages/babel-config/src/plugins/babel-plugin-redwood-otel-wrapping.ts @@ -0,0 +1,363 @@ +import * as nodejsPath from 'path' + +import type { NodePath, PluginObj, PluginPass, types } from '@babel/core' + +import { getBaseDirFromFile } from '@redwoodjs/project-config' + +// This wraps user code within opentelemetry spans to provide automatic tracing in your redwood API. + +function addOpenTelemetryImport( + path: NodePath, + t: typeof types +) { + // We need to have access to the `trace` from `@opentelemetry/api` in order to add the + // automatic instrumentation. We will import it and alias it to something highly specific + // to avoid any potential naming conflicts with user code. + path.node.body.unshift( + t.importDeclaration( + [ + t.importSpecifier( + t.identifier('RW_OTEL_WRAPPER_TRACE'), + t.identifier('trace') + ), + ], + t.stringLiteral('@opentelemetry/api') + ) + ) +} + +function getRedwoodPaths(state: PluginPass): { + filename: string | null | undefined + apiFolder: string +} { + // NOTE: Unable to get 'babel-plugin-tester' to mock the filename so we have specific + // testing logic here. Not ideal but it works for now. + if (process.env.NODE_ENV === 'test') { + return { + filename: '__MOCKED_FILENAME__', + apiFolder: '__MOCKED_API_FOLDER__', + } + } + + const filename = state.file.opts.filename + const filenameOffset = filename + ? getBaseDirFromFile(filename).length + 9 // 9 is the length of '/api/src/' + : 0 + const apiFolder = filename + ? filename.substring( + filenameOffset, + filename.substring(filenameOffset).indexOf(nodejsPath.sep) + + filenameOffset + ) + : '?' + + return { + filename, + apiFolder, + } +} + +function wrapExportNamedDeclaration( + path: NodePath, + state: PluginPass, + t: typeof types +) { + const declaration = path.node.declaration + const declarationIsSupported = + declaration != null && + declaration.type === 'VariableDeclaration' && + declaration.declarations[0].init?.type === 'ArrowFunctionExpression' + if (!declarationIsSupported) { + return + } + + const originalFunction = declaration.declarations[0] + .init as types.ArrowFunctionExpression + if (!originalFunction) { + return + } + + const originalFunctionName = + declaration.declarations[0].id.type === 'Identifier' + ? declaration.declarations[0].id.name + : '?' + const wrappedFunctionName = `__${ + originalFunctionName === '?' + ? 'RW_OTEL_WRAPPER_UNKNOWN_FUNCTION' + : originalFunctionName + }` + + const originalFunctionArgumentsWithoutDefaults: ( + | types.ArgumentPlaceholder + | types.JSXNamespacedName + | types.SpreadElement + | types.Expression + )[] = [] + for (const param of originalFunction.params) { + if (param.type === 'Identifier') { + originalFunctionArgumentsWithoutDefaults.push(param) + continue + } + + if (param.type === 'ObjectPattern') { + const objectProperties = param.properties.filter( + (p) => p.type === 'ObjectProperty' + ) as types.ObjectProperty[] + originalFunctionArgumentsWithoutDefaults.push( + t.objectExpression( + objectProperties.map((p) => { + if (p.value.type === 'AssignmentPattern') { + return t.objectProperty(p.key, p.value.left) + } + return p + }) + ) + ) + + continue + } + + if (param.type === 'AssignmentPattern') { + if (param.left.type === 'Identifier') { + originalFunctionArgumentsWithoutDefaults.push(param.left) + } else if (param.left.type === 'ObjectPattern') { + const objectProperties = param.left.properties.filter( + (p) => p.type === 'ObjectProperty' + ) as types.ObjectProperty[] + originalFunctionArgumentsWithoutDefaults.push( + t.objectExpression( + objectProperties.map((p) => { + if (p.value.type === 'AssignmentPattern') { + return t.objectProperty(p.key, p.value.left) + } + return p + }) + ) + ) + } else { + // TODO: Implement others, bail out for now + return + } + } + + if (param.type === 'ArrayPattern' || param.type === 'RestElement') { + // TODO: Implement, bail out for now + return + } + } + + const { filename, apiFolder } = getRedwoodPaths(state) + + const activeSpanBlock = t.callExpression( + t.memberExpression( + t.identifier('RW_OTEL_WRAPPER_TRACER'), + t.identifier('startActiveSpan') + ), + [ + t.stringLiteral(`redwoodjs:api:${apiFolder}:${originalFunctionName}`), + t.arrowFunctionExpression( + [t.identifier('span')], + t.blockStatement([ + t.expressionStatement( + t.callExpression( + t.memberExpression( + t.identifier('span'), + t.identifier('setAttribute') + ), + [ + t.stringLiteral('code.function'), + t.stringLiteral(originalFunctionName), + ] + ) + ), + t.expressionStatement( + t.callExpression( + t.memberExpression( + t.identifier('span'), + t.identifier('setAttribute') + ), + [ + t.stringLiteral('code.filepath'), + t.stringLiteral(filename || '?'), + ] + ) + ), + t.tryStatement( + t.blockStatement([ + t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier('RW_OTEL_WRAPPER_INNER_RESULT'), + originalFunction.async + ? t.awaitExpression( + t.callExpression( + t.identifier(wrappedFunctionName), + originalFunctionArgumentsWithoutDefaults + ) + ) + : t.callExpression( + t.identifier(wrappedFunctionName), + originalFunctionArgumentsWithoutDefaults + ) + ), + ]), + t.expressionStatement( + t.callExpression( + t.memberExpression(t.identifier('span'), t.identifier('end')), + [] + ) + ), + t.returnStatement(t.identifier('RW_OTEL_WRAPPER_INNER_RESULT')), + ]), + t.catchClause( + t.identifier('error'), + t.blockStatement([ + t.expressionStatement( + t.callExpression( + t.memberExpression( + t.identifier('span'), + t.identifier('recordException') + ), + [t.identifier('error')] + ) + ), + t.expressionStatement( + t.callExpression( + t.memberExpression( + t.identifier('span'), + t.identifier('setStatus') + ), + [ + t.objectExpression([ + t.objectProperty( + t.identifier('code'), + t.numericLiteral(2) + ), + t.objectProperty( + t.identifier('message'), + t.logicalExpression( + '??', + t.optionalMemberExpression( + t.optionalCallExpression( + t.optionalMemberExpression( + t.optionalMemberExpression( + t.identifier('error'), + t.identifier('message'), + false, + true + ), + t.identifier('split'), + false, + true + ), + [t.stringLiteral('\n')], + false + ), + t.numericLiteral(0), + true, + false + ), + t.optionalMemberExpression( + t.optionalCallExpression( + t.optionalMemberExpression( + t.optionalCallExpression( + t.optionalMemberExpression( + t.identifier('error'), + t.identifier('toString'), + false, + true + ), + [], + false + ), + t.identifier('split'), + false, + true + ), + [t.stringLiteral('\n')], + false + ), + t.numericLiteral(0), + true, + false + ) + ) + ), + ]), + ] + ) + ), + t.expressionStatement( + t.callExpression( + t.memberExpression( + t.identifier('span'), + t.identifier('end') + ), + [] + ) + ), + t.throwStatement(t.identifier('error')), + ]) + ) + ), + ]), + originalFunction.async + ), + ] + ) + + const wrapper = t.arrowFunctionExpression( + originalFunction.params, + t.blockStatement( + [ + t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier(wrappedFunctionName), + originalFunction + ), + ]), + t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier('RW_OTEL_WRAPPER_TRACER'), + t.callExpression( + t.memberExpression( + t.identifier('RW_OTEL_WRAPPER_TRACE'), + t.identifier('getTracer') + ), + [t.stringLiteral('redwoodjs')] + ) + ), + ]), + t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier('RW_OTEL_WRAPPER_RESULT'), + originalFunction.async + ? t.awaitExpression(activeSpanBlock) + : activeSpanBlock + ), + ]), + t.returnStatement(t.identifier('RW_OTEL_WRAPPER_RESULT')), + ], + originalFunction.body.type === 'BlockStatement' + ? originalFunction.body.directives + : undefined + ), + originalFunction.async + ) + + // Replace the original function with the wrapped version + declaration.declarations[0].init = wrapper +} + +export default function ({ types: t }: { types: typeof types }): PluginObj { + return { + name: 'babel-plugin-redwood-otel-wrapping', + visitor: { + Program(path) { + addOpenTelemetryImport(path, t) + }, + ExportNamedDeclaration(path, state) { + wrapExportNamedDeclaration(path, state, t) + }, + }, + } +} diff --git a/packages/babel-config/src/plugins/babel-plugin-redwood-remove-dev-fatal-error-page.ts b/packages/babel-config/src/plugins/babel-plugin-redwood-remove-dev-fatal-error-page.ts new file mode 100644 index 000000000000..12108e7b671a --- /dev/null +++ b/packages/babel-config/src/plugins/babel-plugin-redwood-remove-dev-fatal-error-page.ts @@ -0,0 +1,31 @@ +import type { PluginObj, types } from '@babel/core' + +// This replaces +// import { DevFatalErrorPage } from '@redwoodjs/web/dist/components/DevFatalErrorPage' +// with +// const DevFatalErrorPage = undefined + +export default function ({ types: t }: { types: typeof types }): PluginObj { + return { + name: 'babel-plugin-redwood-remove-dev-fatal-error-page', + visitor: { + ImportDeclaration(path) { + // import { DevFatalErrorPage } from '@redwoodjs/web/dist/components/DevFatalErrorPage' + if ( + path.node.source.value === + '@redwoodjs/web/dist/components/DevFatalErrorPage' + ) { + // const DevFatalErrorPage = undefined + const variableDeclaration = t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier('DevFatalErrorPage'), + t.identifier('undefined') + ), + ]) + + path.replaceWith(variableDeclaration) + } + }, + }, + } +} diff --git a/packages/babel-config/src/plugins/babel-plugin-redwood-routes-auto-loader.ts b/packages/babel-config/src/plugins/babel-plugin-redwood-routes-auto-loader.ts new file mode 100644 index 000000000000..4b2d0405afab --- /dev/null +++ b/packages/babel-config/src/plugins/babel-plugin-redwood-routes-auto-loader.ts @@ -0,0 +1,267 @@ +import path from 'path' + +import type { PluginObj, types } from '@babel/core' + +import type { PagesDependency } from '@redwoodjs/project-config' +import { + importStatementPath, + processPagesDir, + getPaths, + ensurePosixPath, + getConfig, +} from '@redwoodjs/project-config' + +interface PluginOptions { + prerender?: boolean + vite?: boolean +} + +/** + * When running from the CLI: Babel-plugin-module-resolver will convert + * For dev/build/prerender (forJest == false): 'src/pages/ExamplePage' -> './pages/ExamplePage' + * For test (forJest == true): 'src/pages/ExamplePage' -> '/Users/blah/pathToProject/web/src/pages/ExamplePage' + */ +const getPathRelativeToSrc = (maybeAbsolutePath: string) => { + // If the path is already relative + if (!path.isAbsolute(maybeAbsolutePath)) { + return maybeAbsolutePath + } + + return `./${path.relative(getPaths().web.src, maybeAbsolutePath)}` +} + +const withRelativeImports = (page: PagesDependency) => { + return { + ...page, + relativeImport: ensurePosixPath(getPathRelativeToSrc(page.importPath)), + } +} + +export default function ( + { types: t }: { types: typeof types }, + { prerender = false, vite = false }: PluginOptions +): PluginObj { + // @NOTE: This var gets mutated inside the visitors + let pages = processPagesDir().map(withRelativeImports) + + // Currently processPagesDir() can return duplicate entries when there are multiple files + // ending in Page in the individual page directories. This will cause an error upstream. + // Here we check for duplicates and throw a more helpful error message. + const duplicatePageImportNames = new Set() + const sortedPageImportNames = pages.map((page) => page.importName).sort() + for (let i = 0; i < sortedPageImportNames.length - 1; i++) { + if (sortedPageImportNames[i + 1] === sortedPageImportNames[i]) { + duplicatePageImportNames.add(sortedPageImportNames[i]) + } + } + if (duplicatePageImportNames.size > 0) { + throw new Error( + `Unable to find only a single file ending in 'Page.{js,jsx,ts,tsx}' in the follow page directories: ${Array.from( + duplicatePageImportNames + ) + .map((name) => `'${name}'`) + .join(', ')}` + ) + } + + if (getConfig().experimental?.rsc?.enabled) { + // TODO (RSC): Enable auto-loader for RSC + return { + name: 'babel-plugin-redwood-routes-auto-loader', + visitor: {}, + } + } + + return { + name: 'babel-plugin-redwood-routes-auto-loader', + visitor: { + // Remove any pages that have been explicitly imported in the Routes file, + // because when one is present, the user is requesting that the module be + // included in the main bundle. + ImportDeclaration(p) { + if (pages.length === 0) { + return + } + + const userImportRelativePath = getPathRelativeToSrc( + importStatementPath(p.node.source?.value) + ) + + const defaultSpecifier = p.node.specifiers.filter((specifiers) => + t.isImportDefaultSpecifier(specifiers) + )[0] + + // Remove Page imports in prerender mode (see babel-preset) + // The removed imports will be replaced further down in this file + // with declarations like these: + // const HomePage = { + // name: "HomePage", + // loader: () => import("./pages/HomePage/HomePage") + // prerenderLoader: () => require("./pages/HomePage/HomePage") + // }; + // This is to make sure that all the imported "Page modules" are normal + // imports and not asynchronous ones. + // Note that jest in a user's project does not enter this block, but our tests do + if (prerender) { + // Match import paths, const name could be different + + const pageThatUserImported = pages.find((page) => { + return ( + page.relativeImport === ensurePosixPath(userImportRelativePath) + ) + }) + + if (pageThatUserImported) { + // Update the import name, with the user's import name + // So that the JSX name stays consistent + pageThatUserImported.importName = defaultSpecifier.local.name + + // Remove the default import for the page and leave all the others + p.node.specifiers = p.node.specifiers.filter( + (specifier) => !t.isImportDefaultSpecifier(specifier) + ) + } + + return + } + + if (userImportRelativePath && defaultSpecifier) { + // Remove the page from pages list, if it is already explicitly imported, so that we don't add loaders for these pages. + // We use the path & defaultSpecifier because the const name could be anything + pages = pages.filter( + (page) => + !(page.relativeImport === ensurePosixPath(userImportRelativePath)) + ) + } + }, + Program: { + enter() { + pages = processPagesDir().map(withRelativeImports) + }, + exit(p) { + if (pages.length === 0) { + return + } + const nodes = [] + + // Add "import {lazy} from 'react'" + nodes.unshift( + t.importDeclaration( + [t.importSpecifier(t.identifier('lazy'), t.identifier('lazy'))], + t.stringLiteral('react') + ) + ) + + // Prepend all imports to the top of the file + for (const { importName, relativeImport } of pages) { + // const = { + // name: , + // prerenderLoader: (name) => prerenderLoaderImpl + // LazyComponent: lazy(() => import(/* webpackChunkName: "..." */ ) + // } + + /** + * Real example + * const LoginPage = { + * name: "LoginPage", + * prerenderLoader: () => __webpack_require__(require.resolveWeak("./pages/LoginPage/LoginPage")), */ + // LazyComponent: lazy(() => import("/* webpackChunkName: "LoginPage" *//pages/LoginPage/LoginPage.tsx")) + /* + * } + */ + + const importArgument = t.stringLiteral(relativeImport) + + importArgument.leadingComments = [ + { + type: 'CommentBlock', + value: ` webpackChunkName: "${importName}" `, + }, + ] + + nodes.push( + t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier(importName), + t.objectExpression([ + t.objectProperty( + t.identifier('name'), + t.stringLiteral(importName) + ), + // prerenderLoader for ssr/prerender and first load of + // prerendered pages in browser (csr) + // prerenderLoader: (name) => { prerenderLoaderImpl } + t.objectProperty( + t.identifier('prerenderLoader'), + t.arrowFunctionExpression( + [t.identifier('name')], + prerenderLoaderImpl(prerender, vite, relativeImport, t) + ) + ), + t.objectProperty( + t.identifier('LazyComponent'), + t.callExpression(t.identifier('lazy'), [ + t.arrowFunctionExpression( + [], + t.callExpression(t.identifier('import'), [ + importArgument, + ]) + ), + ]) + ), + ]) + ), + ]) + ) + } + + // Insert at the top of the file + p.node.body.unshift(...nodes) + }, + }, + }, + } +} + +function prerenderLoaderImpl( + prerender: boolean, + vite: boolean, + relativeImport: string, + t: typeof types +) { + if (prerender) { + // This works for both vite and webpack + return t.callExpression(t.identifier('require'), [ + t.stringLiteral(relativeImport), + ]) + } + + // This code will be output when building the web side (i.e. not when + // prerendering) + // active-route-loader will use this code for auto-imported pages, for the + // first load of a prerendered page + // Manually imported pages will be bundled in the main bundle and will be + // loaded by the code in `normalizePage` in util.ts + let implForBuild + if (vite) { + implForBuild = t.objectExpression([ + t.objectProperty( + t.identifier('default'), + t.memberExpression( + t.identifier('globalThis.__REDWOOD__PRERENDER_PAGES'), + t.identifier('name'), + true + ) + ), + ]) + } else { + // Use __webpack_require__ otherwise all pages will be bundled + implForBuild = t.callExpression(t.identifier('__webpack_require__'), [ + t.callExpression(t.identifier('require.resolveWeak'), [ + t.stringLiteral(relativeImport), + ]), + ]) + } + + return implForBuild +} diff --git a/packages/internal/src/build/babelPlugins/babel-plugin-redwood-src-alias.ts b/packages/babel-config/src/plugins/babel-plugin-redwood-src-alias.ts similarity index 100% rename from packages/internal/src/build/babelPlugins/babel-plugin-redwood-src-alias.ts rename to packages/babel-config/src/plugins/babel-plugin-redwood-src-alias.ts diff --git a/packages/babel-config/src/web.ts b/packages/babel-config/src/web.ts new file mode 100644 index 000000000000..df4b3efe51e1 --- /dev/null +++ b/packages/babel-config/src/web.ts @@ -0,0 +1,255 @@ +import fs from 'fs' +import path from 'path' + +import * as babel from '@babel/core' +import type { TransformOptions } from '@babel/core' + +import { getConfig, getPaths } from '@redwoodjs/project-config' + +import type { RegisterHookOptions } from './common' +import { + CORE_JS_VERSION, + getCommonPlugins, + registerBabel, + parseTypeScriptConfigFiles, + getPathsFromTypeScriptConfig, +} from './common' + +export const getWebSideBabelPlugins = ( + { forJest, forVite }: Flags = { forJest: false, forVite: false } +) => { + // Need the project config to know if trusted graphql documents is being used and decide to use + // the gql tag import or the trusted document gql function generated by code gen client preset + const config = getConfig() + + const useTrustedDocumentsGqlTag = config.graphql.trustedDocuments + + const rwjsPaths = getPaths() + // Get the TS configs in the api and web sides as an object + const tsConfigs = parseTypeScriptConfigFiles() + + // Vite does not need these plugins + const commonPlugins = forVite ? [] : getCommonPlugins() + const plugins = [ + ...commonPlugins, + // === Import path handling + [ + 'babel-plugin-module-resolver', + { + alias: { + src: + // Jest monorepo and multi project runner is not correctly determining + // the `cwd`: https://github.com/facebook/jest/issues/7359 + forJest ? rwjsPaths.web.src : './src', + // adds the paths from [ts|js]config.json to the module resolver + ...getPathsFromTypeScriptConfig(tsConfigs.web, rwjsPaths.web.base), + $api: rwjsPaths.api.base, + }, + root: [rwjsPaths.web.base], + cwd: 'packagejson', + loglevel: 'silent', // to silence the unnecessary warnings + }, + 'rwjs-module-resolver', + ], + [ + require('./plugins/babel-plugin-redwood-directory-named-import').default, + undefined, + 'rwjs-directory-named-modules', + ], + + // === Auto imports, and transforms + [ + 'babel-plugin-auto-import', + { + declarations: [ + { + // import { React } from 'react' + default: 'React', + path: 'react', + }, + // A project can turn on trusted graphql documents + // If projects do not use trusted documents (default) + // it auto-imports the gql tag from graphql-tag + !useTrustedDocumentsGqlTag && { + // import gql from 'graphql-tag' + default: 'gql', + path: 'graphql-tag', + }, + // if projects use trusted documents + // then it auto-imports the gql function from the generated codegen client preset + useTrustedDocumentsGqlTag && { + // import { gql } from 'src/graphql/gql' + members: ['gql'], + path: `web/src/graphql/gql`, + }, + ].filter(Boolean), + }, + 'rwjs-web-auto-import', + ], + ['babel-plugin-graphql-tag', undefined, 'rwjs-babel-graphql-tag'], + process.env.NODE_ENV !== 'development' && [ + require('./plugins/babel-plugin-redwood-remove-dev-fatal-error-page') + .default, + undefined, + 'rwjs-remove-dev-fatal-error-page', + ], + ].filter(Boolean) as TransformOptions[] + + return plugins +} + +export const getWebSideOverrides = ( + { prerender, forVite }: Flags = { + prerender: false, + forVite: false, + } +) => { + const overrides = [ + { + test: /.+Cell.(js|tsx|jsx)$/, + plugins: [require('./plugins/babel-plugin-redwood-cell').default], + }, + // Automatically import files in `./web/src/pages/*` in to + // the `./web/src/Routes.[ts|jsx]` file. + { + test: /Routes.(js|tsx|jsx)$/, + plugins: [ + [ + require('./plugins/babel-plugin-redwood-routes-auto-loader').default, + { + prerender, + vite: forVite, + }, + ], + ], + }, + // ** Files ending in `Cell.mock.[js,ts]` ** + // Automatically determine keys for saving and retrieving mock data. + // Only required for storybook and jest + process.env.NODE_ENV !== 'production' && { + test: /.+Cell.mock.(js|ts)$/, + plugins: [ + require('./plugins/babel-plugin-redwood-mock-cell-data').default, + ], + }, + ].filter(Boolean) + + return overrides as TransformOptions[] +} + +export const getWebSideBabelPresets = (options: Flags) => { + if (options.forVite) { + return [] + } + + let reactPresetConfig: babel.PluginItem = { runtime: 'automatic' } + + // This is a special case, where @babel/preset-react needs config + // And using extends doesn't work + if (getWebSideBabelConfigPath()) { + const userProjectConfig = require(getWebSideBabelConfigPath() as string) + + userProjectConfig.presets?.forEach( + (preset: TransformOptions['presets']) => { + // If it isn't a preset with special config ignore it + if (!Array.isArray(preset)) { + return + } + + const [presetName, presetConfig] = preset + if (presetName === '@babel/preset-react') { + reactPresetConfig = presetConfig + } + } + ) + } + return [ + ['@babel/preset-react', reactPresetConfig], + [ + '@babel/preset-env', + { + // the targets are set in /web/package.json + useBuiltIns: 'usage', + corejs: { + version: CORE_JS_VERSION, + proposals: true, + }, + exclude: [ + // Remove class-properties from preset-env, and include separately + // https://github.com/webpack/webpack/issues/9708 + '@babel/plugin-transform-class-properties', + '@babel/plugin-transform-private-methods', + ], + }, + 'rwjs-babel-preset-env', + ], + ['@babel/preset-typescript', undefined, 'rwjs-babel-preset-typescript'], + ] +} + +export const getWebSideBabelConfigPath = () => { + const customBabelConfig = path.join(getPaths().web.base, 'babel.config.js') + if (fs.existsSync(customBabelConfig)) { + return customBabelConfig + } else { + return undefined + } +} + +// These flags toggle on/off certain features +export interface Flags { + forJest?: boolean // will change the alias for module-resolver plugin + prerender?: boolean // changes what babel-plugin-redwood-routes-auto-loader does + forVite?: boolean +} + +export const getWebSideDefaultBabelConfig = (options: Flags = {}) => { + // NOTE: + // Even though we specify the config file, babel will still search for .babelrc + // and merge them because we have specified the filename property, unless babelrc = false + + return { + presets: getWebSideBabelPresets(options), + plugins: getWebSideBabelPlugins(options), + overrides: getWebSideOverrides(options), + extends: getWebSideBabelConfigPath(), + babelrc: false, + ignore: ['node_modules'], + } +} + +// Used in prerender only currently +export const registerWebSideBabelHook = ({ + forVite = false, + plugins = [], + overrides = [], +}: RegisterHookOptions & { forVite?: boolean } = {}) => { + const defaultOptions = getWebSideDefaultBabelConfig() + registerBabel({ + ...defaultOptions, + root: getPaths().base, + extensions: ['.js', '.ts', '.tsx', '.jsx'], + plugins: [...defaultOptions.plugins, ...plugins], + cache: false, + // We only register for prerender currently + // Static importing pages makes sense + overrides: [ + ...getWebSideOverrides({ prerender: true, forVite }), + ...overrides, + ], + }) +} + +// @MARK +// Currently only used in testing +export const prebuildWebFile = (srcPath: string, flags: Flags = {}) => { + const code = fs.readFileSync(srcPath, 'utf-8') + const defaultOptions = getWebSideDefaultBabelConfig(flags) + + const result = babel.transform(code, { + ...defaultOptions, + cwd: getPaths().web.base, + filename: srcPath, + }) + return result +} diff --git a/packages/babel-config/tsconfig.json b/packages/babel-config/tsconfig.json new file mode 100644 index 000000000000..91b48264c7cf --- /dev/null +++ b/packages/babel-config/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.compilerOption.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "src", + "outDir": "dist", + }, + "include": ["src"], + "references": [ + { "path": "../project-config" } + ] +} diff --git a/packages/cli-helpers/package.json b/packages/cli-helpers/package.json index 90f8d6e7bc38..117a16719b81 100644 --- a/packages/cli-helpers/package.json +++ b/packages/cli-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/cli-helpers", - "version": "4.0.0", + "version": "6.0.7", "repository": { "type": "git", "url": "https://github.com/redwoodjs/redwood.git", @@ -14,35 +14,38 @@ ], "scripts": { "build": "yarn build:js && yarn build:types", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\"", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\"", "build:types": "tsc --build --verbose", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx\" --ignore dist --exec \"yarn build\"", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx\" --ignore dist --exec \"yarn build\"", "prepublishOnly": "NODE_ENV=production yarn build", "test": "jest src", "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/core": "7.21.3", - "@babel/runtime-corejs3": "7.21.0", - "@redwoodjs/project-config": "4.0.0", - "@redwoodjs/telemetry": "4.0.0", + "@babel/core": "^7.22.20", + "@babel/runtime-corejs3": "7.23.6", + "@iarna/toml": "2.2.5", + "@opentelemetry/api": "1.7.0", + "@redwoodjs/project-config": "6.0.7", + "@redwoodjs/telemetry": "6.0.7", "chalk": "4.1.2", - "core-js": "3.29.1", + "core-js": "3.34.0", + "dotenv": "16.3.1", "execa": "5.1.1", - "listr2": "5.0.8", - "lodash.memoize": "4.1.2", + "listr2": "6.6.1", + "lodash": "4.17.21", "pascalcase": "1.0.0", - "prettier": "2.8.7", + "prettier": "2.8.8", "prompts": "2.4.2", "terminal-link": "2.1.1" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@types/lodash.memoize": "4.1.7", - "@types/pascalcase": "1.0.1", - "@types/yargs": "17.0.24", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@types/lodash": "4.14.201", + "@types/pascalcase": "1.0.3", + "@types/yargs": "17.0.32", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/cli-helpers/src/auth/__tests__/__snapshots__/authTasks.test.ts.snap b/packages/cli-helpers/src/auth/__tests__/__snapshots__/authTasks.test.ts.snap index 489251a9888e..585825351020 100644 --- a/packages/cli-helpers/src/auth/__tests__/__snapshots__/authTasks.test.ts.snap +++ b/packages/cli-helpers/src/auth/__tests__/__snapshots__/authTasks.test.ts.snap @@ -166,7 +166,7 @@ export default App " `; -exports[`authTasks Should update App.{js,tsx}, Routes.{js,tsx} and add auth.ts (Auth0) 1`] = ` +exports[`authTasks Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Auth0) 1`] = ` "import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web' import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' @@ -193,7 +193,7 @@ export default App " `; -exports[`authTasks Should update App.{js,tsx}, Routes.{js,tsx} and add auth.ts (Auth0) 2`] = ` +exports[`authTasks Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Auth0) 2`] = ` "import { Auth0Client } from '@auth0/auth0-spa-js' import { createAuth } from '@redwoodjs/auth-auth0-web' @@ -221,7 +221,7 @@ export const { AuthProvider, useAuth } = createAuth(auth0) " `; -exports[`authTasks Should update App.{js,tsx}, Routes.{js,tsx} and add auth.ts (Auth0) 3`] = ` +exports[`authTasks Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Auth0) 3`] = ` "// In this file, all Page components from 'src/pages\` are auto-imported. Nested // directories are supported, and should be uppercase. Each subdirectory will be // prepended onto the component name. @@ -247,7 +247,7 @@ export default Routes " `; -exports[`authTasks Should update App.{js,tsx}, Routes.{js,tsx} and add auth.ts (Clerk) 1`] = ` +exports[`authTasks Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Clerk) 1`] = ` "import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web' import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' @@ -274,7 +274,7 @@ export default App " `; -exports[`authTasks Should update App.{js,tsx}, Routes.{js,tsx} and add auth.ts (Clerk) 2`] = ` +exports[`authTasks Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Clerk) 2`] = ` "import React, { useEffect } from 'react' import { ClerkLoaded, ClerkProvider, useUser } from '@clerk/clerk-react' @@ -327,7 +327,7 @@ export const AuthProvider = ({ children }: Props) => { " `; -exports[`authTasks Should update App.{js,tsx}, Routes.{js,tsx} and add auth.ts (Clerk) 3`] = ` +exports[`authTasks Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Clerk) 3`] = ` "// In this file, all Page components from 'src/pages\` are auto-imported. Nested // directories are supported, and should be uppercase. Each subdirectory will be // prepended onto the component name. diff --git a/packages/cli-helpers/src/auth/__tests__/authTasks.test.ts b/packages/cli-helpers/src/auth/__tests__/authTasks.test.ts index a01bb3e4dc51..70f6b77603e8 100644 --- a/packages/cli-helpers/src/auth/__tests__/authTasks.test.ts +++ b/packages/cli-helpers/src/auth/__tests__/authTasks.test.ts @@ -22,8 +22,8 @@ jest.mock('../../lib/paths', () => { const path = require('path') const actualPaths = jest.requireActual('../../lib/paths') const basedir = '/mock/setup/path' - const app = mockIsTypeScriptProject ? 'App.tsx' : 'App.js' - const routes = mockIsTypeScriptProject ? 'Routes.tsx' : 'Routes.js' + const app = mockIsTypeScriptProject ? 'App.tsx' : 'App.jsx' + const routes = mockIsTypeScriptProject ? 'Routes.tsx' : 'Routes.jsx' return { resolveFile: actualPaths.resolveFile, @@ -67,12 +67,12 @@ import fs from 'fs' import path from 'path' import { getPaths } from '../../lib/paths' +import type { AuthGeneratorCtx } from '../authTasks' import { addApiConfig, addConfigToWebApp, addConfigToRoutes, createWebAuth, - AuthGeneratorCtx, hasAuthProvider, removeAuthProvider, } from '../authTasks' @@ -117,7 +117,7 @@ beforeEach(() => { }) describe('authTasks', () => { - it('Should update App.{js,tsx}, Routes.{js,tsx} and add auth.ts (Auth0)', () => { + it('Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Auth0)', () => { const templatePath = path.join( getPaths().base, platformPath('/templates/web/auth.ts.template') @@ -144,7 +144,7 @@ describe('authTasks', () => { expect(fs.readFileSync(getPaths().web.routes)).toMatchSnapshot() }) - it('Should update App.{js,tsx}, Routes.{js,tsx} and add auth.ts (Clerk)', () => { + it('Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Clerk)', () => { const templatePath = path.join( getPaths().base, platformPath('/templates/web/auth.tsx.template') diff --git a/packages/cli-helpers/src/auth/__tests__/fixtures/dbAuthSetup/templates/api/functions/auth.ts.template b/packages/cli-helpers/src/auth/__tests__/fixtures/dbAuthSetup/templates/api/functions/auth.ts.template index ca34d653d811..8fd16f7f5f34 100644 --- a/packages/cli-helpers/src/auth/__tests__/fixtures/dbAuthSetup/templates/api/functions/auth.ts.template +++ b/packages/cli-helpers/src/auth/__tests__/fixtures/dbAuthSetup/templates/api/functions/auth.ts.template @@ -1,7 +1,9 @@ import type { APIGatewayProxyEvent, Context } from 'aws-lambda' -import { DbAuthHandler, DbAuthHandlerOptions } from '@redwoodjs/auth-providers-api/dist/dbAuth' +import { DbAuthHandler } from '@redwoodjs/auth-dbauth-api' +import type { DbAuthHandlerOptions, UserType } from '@redwoodjs/auth-dbauth-api' +import { cookieName } from 'src/lib/auth' import { db } from 'src/lib/db' export const handler = async ( @@ -91,7 +93,14 @@ export const handler = async ( }, } - const signupOptions: DbAuthHandlerOptions['signup'] = { + interface UserAttributes { + name: string + } + + const signupOptions: DbAuthHandlerOptions< + UserType, + UserAttributes + >['signup'] = { // Whatever you want to happen to your data on new user signup. Redwood will // check for duplicate usernames before calling this handler. At a minimum // you need to save the `username`, `hashedPassword` and `salt` to your @@ -107,7 +116,12 @@ export const handler = async ( // // If this returns anything else, it will be returned by the // `signUp()` function in the form of: `{ message: 'String here' }`. - handler: ({ username, hashedPassword, salt, userAttributes }) => { + handler: ({ + username, + hashedPassword, + salt, + userAttributes: _userAttributes, + }) => { return db.user.create({ data: { email: username, @@ -155,14 +169,17 @@ export const handler = async ( // Specifies attributes on the cookie that dbAuth sets in order to remember // who is logged in. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies cookie: { - HttpOnly: true, - Path: '/', - SameSite: 'Strict', - Secure: process.env.NODE_ENV !== 'development', - - // If you need to allow other domains (besides the api side) access to - // the dbAuth session cookie: - // Domain: 'example.com', + attributes: { + HttpOnly: true, + Path: '/', + SameSite: 'Strict', + Secure: process.env.NODE_ENV !== 'development' ? true : false, + + // If you need to allow other domains (besides the api side) access to + // the dbAuth session cookie: + // Domain: 'example.com', + }, + name: cookieName, }, forgotPassword: forgotPasswordOptions, diff --git a/packages/cli-helpers/src/auth/__tests__/fixtures/dbAuthSetup/templates/api/functions/auth.webAuthn.ts.template b/packages/cli-helpers/src/auth/__tests__/fixtures/dbAuthSetup/templates/api/functions/auth.webAuthn.ts.template index dbbc6413c21f..aff737384bed 100644 --- a/packages/cli-helpers/src/auth/__tests__/fixtures/dbAuthSetup/templates/api/functions/auth.webAuthn.ts.template +++ b/packages/cli-helpers/src/auth/__tests__/fixtures/dbAuthSetup/templates/api/functions/auth.webAuthn.ts.template @@ -1,7 +1,9 @@ import type { APIGatewayProxyEvent, Context } from 'aws-lambda' -import { DbAuthHandler, DbAuthHandlerOptions } from '@redwoodjs/auth-providers-api/dist/dbAuth' +import { DbAuthHandler } from '@redwoodjs/auth-providers-api/dist/dbAuth' +import type { DbAuthHandlerOptions, UserType } from '@redwoodjs/auth-dbauth-api' +import { cookieName } from 'src/lib/auth' import { db } from 'src/lib/db' export const handler = async ( @@ -91,7 +93,14 @@ export const handler = async ( }, } - const signupOptions = { + interface UserAttributes { + name: string + } + + const signupOptions: DbAuthHandlerOptions< + UserType, + UserAttributes + >['signup'] = { // Whatever you want to happen to your data on new user signup. Redwood will // check for duplicate usernames before calling this handler. At a minimum // you need to save the `username`, `hashedPassword` and `salt` to your @@ -107,7 +116,12 @@ export const handler = async ( // // If this returns anything else, it will be returned by the // `signUp()` function in the form of: `{ message: 'String here' }`. - handler: ({ username, hashedPassword, salt, userAttributes }) => { + handler: ({ + username, + hashedPassword, + salt, + userAttributes: _userAttributes, + }) => { return db.user.create({ data: { email: username, @@ -160,14 +174,17 @@ export const handler = async ( // Specifies attributes on the cookie that dbAuth sets in order to remember // who is logged in. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies cookie: { - HttpOnly: true, - Path: '/', - SameSite: 'Strict', - Secure: process.env.NODE_ENV !== 'development' ? true : false, - - // If you need to allow other domains (besides the api side) access to - // the dbAuth session cookie: - // Domain: 'example.com', + attributes: { + HttpOnly: true, + Path: '/', + SameSite: 'Strict', + Secure: process.env.NODE_ENV !== 'development' ? true : false, + + // If you need to allow other domains (besides the api side) access to + // the dbAuth session cookie: + // Domain: 'example.com', + }, + name: cookieName, }, forgotPassword: forgotPasswordOptions, diff --git a/packages/cli-helpers/src/auth/__tests__/fixtures/dbAuthSetup/templates/api/lib/auth.ts.template b/packages/cli-helpers/src/auth/__tests__/fixtures/dbAuthSetup/templates/api/lib/auth.ts.template index 1626963161a1..3d8956d920e0 100644 --- a/packages/cli-helpers/src/auth/__tests__/fixtures/dbAuthSetup/templates/api/lib/auth.ts.template +++ b/packages/cli-helpers/src/auth/__tests__/fixtures/dbAuthSetup/templates/api/lib/auth.ts.template @@ -3,6 +3,15 @@ import { AuthenticationError, ForbiddenError } from '@redwoodjs/graphql-server' import { db } from './db' +/** + * The name of the cookie that dbAuth sets + * + * %port% will be replaced with the port the api server is running on. + * If you have multiple RW apps running on the same host, you'll need to + * make sure they all use unique cookie names + */ +export const cookieName = 'session_%port%' + /** * The session object sent in as the first argument to getCurrentUser() will * have a single key `id` containing the unique ID of the logged in user diff --git a/packages/cli-helpers/src/auth/__tests__/fixtures/supertokensSetup/templates/api/lib/auth.ts.template b/packages/cli-helpers/src/auth/__tests__/fixtures/supertokensSetup/templates/api/lib/auth.ts.template index 08a3810c1266..589b5fad5af7 100644 --- a/packages/cli-helpers/src/auth/__tests__/fixtures/supertokensSetup/templates/api/lib/auth.ts.template +++ b/packages/cli-helpers/src/auth/__tests__/fixtures/supertokensSetup/templates/api/lib/auth.ts.template @@ -29,9 +29,9 @@ type RedwoodUser = Record & { roles?: string[] } */ export const getCurrentUser = async ( decoded, - /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + /* eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars */ { token, type }, - /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + /* eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars */ { event, context } ): Promise => { if (!decoded) { diff --git a/packages/cli-helpers/src/auth/__tests__/fixtures/supertokensSetup/templates/api/lib/supertokens.ts.template b/packages/cli-helpers/src/auth/__tests__/fixtures/supertokensSetup/templates/api/lib/supertokens.ts.template index 9d8727c3841d..4b0089a4a94e 100644 --- a/packages/cli-helpers/src/auth/__tests__/fixtures/supertokensSetup/templates/api/lib/supertokens.ts.template +++ b/packages/cli-helpers/src/auth/__tests__/fixtures/supertokensSetup/templates/api/lib/supertokens.ts.template @@ -26,6 +26,7 @@ export const config = { }, supertokens: { connectionURI: process.env.SUPERTOKENS_CONNECTION_URI, + apiKey: process.env.SUPERTOKENS_API_KEY, }, recipeList: [ ThirdPartyEmailPassword.init({ diff --git a/packages/cli-helpers/src/auth/authFiles.ts b/packages/cli-helpers/src/auth/authFiles.ts index d53fc2e890c9..85bd5f812075 100644 --- a/packages/cli-helpers/src/auth/authFiles.ts +++ b/packages/cli-helpers/src/auth/authFiles.ts @@ -41,13 +41,12 @@ export const apiSideFiles = ({ basedir, webAuthn }: FilesArgs) => { ) }) .map((fileName) => { - // remove "template" from the end, and change from {ts,tsx} to js for + // remove "template" from the end, and change from {ts,tsx} to {js,jsx} for // JavaScript projects - const fileNameParts = fileName.split('.') - const outputFileName = [ - ...fileNameParts.slice(0, -2), - isTypeScriptProject() ? fileNameParts.at(-2) : 'js', - ].join('.') + let outputFileName = fileName.replace(/\.template$/, '') + if (!isTypeScriptProject()) { + outputFileName = outputFileName.replace(/\.ts(x?)$/, '.js$1') + } if (!webAuthn) { return { templateFileName: fileName, outputFileName } diff --git a/packages/cli-helpers/src/auth/authTasks.ts b/packages/cli-helpers/src/auth/authTasks.ts index 14a1942c6fd0..69d19239cb73 100644 --- a/packages/cli-helpers/src/auth/authTasks.ts +++ b/packages/cli-helpers/src/auth/authTasks.ts @@ -1,11 +1,14 @@ import fs from 'fs' import path from 'path' -import { ListrRenderer, ListrTask, ListrTaskWrapper } from 'listr2' +import type { ListrRenderer, ListrTask, ListrTaskWrapper } from 'listr2' -import { ExistingFiles, transformTSToJS, writeFilesTask } from '../lib' +import { resolveFile } from '@redwoodjs/project-config' + +import type { ExistingFiles } from '../lib' +import { transformTSToJS, writeFilesTask } from '../lib' import { colors } from '../lib/colors' -import { getPaths, resolveFile } from '../lib/paths' +import { getPaths } from '../lib/paths' import { getGraphqlPath, graphFunctionDoesExist, @@ -219,7 +222,7 @@ export const removeAuthProvider = (content: string) => { .join('\n') } -/** returns the content of App.{js,tsx} with added */ +/** returns the content of App.{jsx,tsx} with added */ const addAuthProviderToApp = (content: string, setupMode: AuthSetupMode) => { if (setupMode === 'FORCE' || setupMode === 'REPLACE') { content = removeAuthProvider(content) @@ -230,7 +233,7 @@ const addAuthProviderToApp = (content: string, setupMode: AuthSetupMode) => { ) if (!match) { - throw new Error('Could not find in App.{js,tsx}') + throw new Error('Could not find in App.{jsx,tsx}') } // If Auth.tsx already contains exactly what we're trying to add there's no @@ -286,19 +289,19 @@ const addUseAuthHook = (componentName: string, content: string) => { } /** - * Actually inserts the required config lines into App.{js,tsx} + * Actually inserts the required config lines into App.{jsx,tsx} * Exported for testing */ export const addConfigToWebApp = < Renderer extends typeof ListrRenderer >(): ListrTask => { return { - title: 'Updating web/src/App.{js,tsx}', + title: 'Updating web/src/App.{jsx,tsx}', task: (ctx, task) => { const webAppPath = getWebAppPath() if (!fs.existsSync(webAppPath)) { - const ext = isTypeScriptProject() ? 'tsx' : 'js' + const ext = isTypeScriptProject() ? 'tsx' : 'jsx' throw new Error(`Could not find root App.${ext}`) } @@ -351,8 +354,11 @@ export const createWebAuth = (basedir: string, webAuthn: boolean) => { const isTSProject = isTypeScriptProject() - // ext will be tsx, ts or js - const ext = isTypeScriptProject() ? templateExtension : 'js' + // ext will be tsx, ts or jsx, js + let ext = templateExtension + if (!isTypeScriptProject()) { + ext = ext?.replace('ts', 'js') + } return { title: `Creating web/src/auth.${ext}`, diff --git a/packages/cli-helpers/src/auth/setupHelpers.ts b/packages/cli-helpers/src/auth/setupHelpers.ts index 52b14ed399b9..5c572a7c6427 100644 --- a/packages/cli-helpers/src/auth/setupHelpers.ts +++ b/packages/cli-helpers/src/auth/setupHelpers.ts @@ -1,6 +1,7 @@ -import { Listr, ListrTask } from 'listr2' +import type { ListrTask } from 'listr2' +import { Listr } from 'listr2' import terminalLink from 'terminal-link' -import yargs from 'yargs' +import type yargs from 'yargs' import { errorTelemetry } from '@redwoodjs/telemetry' @@ -11,11 +12,11 @@ import { installPackages, } from '../lib/installHelpers' +import type { AuthGeneratorCtx } from './authTasks' import { addAuthConfigToGqlApi, addConfigToRoutes, addConfigToWebApp, - AuthGeneratorCtx, setAuthSetupMode, createWebAuth, generateAuthApiFiles, @@ -51,7 +52,7 @@ interface Args { webAuthn?: boolean webPackages?: string[] apiPackages?: string[] - extraTask?: ListrTask + extraTasks?: ListrTask[] notes?: string[] verbose?: boolean } @@ -64,8 +65,8 @@ function truthy(value: T): value is Truthy { } /** - * basedir assumes that you must have a templates folder in that directory. - * See folder structure of auth providers in packages/auth-providers//setup/src + * basedir assumes that you must have a templates folder in that directory. + * See folder structure of auth providers in packages/auth-providers//setup/src */ export const standardAuthHandler = async ({ basedir, @@ -75,7 +76,7 @@ export const standardAuthHandler = async ({ webAuthn = false, webPackages = [], apiPackages = [], - extraTask, + extraTasks, notes, verbose, }: Args) => { @@ -97,7 +98,7 @@ export const standardAuthHandler = async ({ webPackages.length && addWebPackages(webPackages), apiPackages.length && addApiPackages(apiPackages), (webPackages.length || apiPackages.length) && installPackages, - extraTask, + ...(extraTasks || []), notes && { title: 'One more thing...', task: (ctx: AuthGeneratorCtx) => { @@ -132,7 +133,7 @@ export const standardAuthHandler = async ({ }, ].filter(truthy), { - rendererOptions: { collapse: false }, + rendererOptions: { collapseSubtasks: false }, renderer: verbose ? 'verbose' : 'default', ctx: { setupMode: 'UNKNOWN', diff --git a/packages/cli-helpers/src/index.ts b/packages/cli-helpers/src/index.ts index edda963714ba..04e8c7a96987 100644 --- a/packages/cli-helpers/src/index.ts +++ b/packages/cli-helpers/src/index.ts @@ -5,6 +5,9 @@ export * from './lib' export * from './lib/colors' export * from './lib/paths' export * from './lib/project' +export * from './lib/version' export * from './auth/setupHelpers' export * from './lib/installHelpers' + +export * from './telemetry/index' diff --git a/packages/cli-helpers/src/lib/__tests__/__snapshots__/project.test.ts.snap b/packages/cli-helpers/src/lib/__tests__/__snapshots__/project.test.ts.snap new file mode 100644 index 000000000000..6da535eaf61e --- /dev/null +++ b/packages/cli-helpers/src/lib/__tests__/__snapshots__/project.test.ts.snap @@ -0,0 +1,149 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`addEnvVar addEnvVar adds environment variables as part of a setup task should add a comment that the existing environment variable value was not changed, but include its new value as a comment 1`] = ` +"EXISTING_VAR=value +# CommentedVar=123 + +# Note: The existing environment variable EXISTING_VAR was not overwritten. Uncomment to use its new value. +# Updated existing variable Comment +# EXISTING_VAR = new_value +" +`; + +exports[`addEnvVar addEnvVar adds environment variables as part of a setup task should add a new environment variable when it does not exist 1`] = ` +"EXISTING_VAR = value +# CommentedVar = 123 + +# New Variable Comment +NEW_VAR = new_value +" +`; + +exports[`addEnvVar addEnvVar adds environment variables as part of a setup task should add a new environment variable when it does not exist when existing envars have no spacing 1`] = ` +"EXISTING_VAR=value +# CommentedVar = 123 + +# New Variable Comment +NEW_VAR = new_value +" +`; + +exports[`addEnvVar addEnvVar adds environment variables as part of a setup task should handle existing environment variables and new value with quoted values by not updating the original value 1`] = ` +"EXISTING_VAR = "value" +# CommentedVar = 123 + +# Note: The existing environment variable EXISTING_VAR was not overwritten. Uncomment to use its new value. +# New Variable Comment +# EXISTING_VAR = new_value +" +`; + +exports[`addEnvVar addEnvVar adds environment variables as part of a setup task should handle existing environment variables with quoted values 1`] = ` +"EXISTING_VAR = "value" +# CommentedVar = 123 +" +`; + +exports[`addEnvVar addEnvVar adds environment variables as part of a setup task should handle existing environment variables with quoted values and no spacing 1`] = ` +"EXISTING_VAR="value" +# CommentedVar=123 +" +`; + +exports[`updateTomlConfig updateTomlConfig configures a new CLI plugin adds package but keeps autoInstall false 1`] = ` +"[web] +title = "Redwood App" +port = 8_910 +apiUrl = "/.redwood/functions" +includeEnvironmentVariables = [ ] + +[api] +port = 8_911 + +[experimental.cli] +autoInstall = false + +[[experimental.cli.plugins]] +package = "@example/test-package-when-autoInstall-false" +enabled = true +" +`; + +exports[`updateTomlConfig updateTomlConfig configures a new CLI plugin adds when experimental cli has some plugins configured 1`] = ` +"[web] +title = "Redwood App" +port = 8_910 +apiUrl = "/.redwood/functions" +includeEnvironmentVariables = [ ] + +[api] +port = 8_911 + +[experimental.cli] +autoInstall = true + + [[experimental.cli.plugins]] + package = "@existing-example/some-package-when-cli-has-some-packages-configured" + +[[experimental.cli.plugins]] +package = "@example/test-package-name" +enabled = true +" +`; + +exports[`updateTomlConfig updateTomlConfig configures a new CLI plugin adds when experimental cli is not configured 1`] = ` +"[web] +title = "Redwood App" +port = 8_910 +apiUrl = "/.redwood/functions" +includeEnvironmentVariables = [ ] + +[api] +port = 8_911 + +[experimental.cli] +autoInstall = true + + [[experimental.cli.plugins]] + package = "@example/test-package-when-cli-not-configured" + enabled = true +" +`; + +exports[`updateTomlConfig updateTomlConfig configures a new CLI plugin adds when experimental cli is setup but has no plugins configured 1`] = ` +"[web] +title = "Redwood App" +port = 8_910 +apiUrl = "/.redwood/functions" +includeEnvironmentVariables = [ ] + +[api] +port = 8_911 + +[experimental.cli] +autoInstall = true + +[[experimental.cli.plugins]] +package = "@example/test-package-when-no-plugins-configured" +enabled = true +" +`; + +exports[`updateTomlConfig updateTomlConfig configures a new CLI plugin does not add duplicate place when experimental cli has that plugin configured 1`] = ` +"[web] +title = "Redwood App" +port = 8_910 +apiUrl = "/.redwood/functions" +includeEnvironmentVariables = [ ] + +[api] +port = 8_911 + +[experimental.cli] +autoInstall = true + + [[experimental.cli.plugins]] + package = "@existing-example/some-package-name-already-exists" + +" +`; diff --git a/packages/cli-helpers/src/lib/__tests__/project.test.ts b/packages/cli-helpers/src/lib/__tests__/project.test.ts new file mode 100644 index 000000000000..3aef810bc9bc --- /dev/null +++ b/packages/cli-helpers/src/lib/__tests__/project.test.ts @@ -0,0 +1,214 @@ +import fs from 'fs' + +import toml from '@iarna/toml' + +import { updateTomlConfig, addEnvVar } from '../project' // Replace with the correct path to your module + +jest.mock('fs') + +const defaultRedwoodToml = { + web: { + title: 'Redwood App', + port: 8910, + apiUrl: '/.redwood/functions', + includeEnvironmentVariables: [], + }, + api: { + port: 8911, + }, +} + +const getRedwoodToml = () => { + return defaultRedwoodToml +} + +jest.mock('@redwoodjs/project-config', () => { + return { + getPaths: () => { + return { + generated: { + base: '.redwood', + }, + base: '', + } + }, + getConfigPath: () => { + return '.redwood.toml' + }, + getConfig: () => { + return getRedwoodToml() + }, + } +}) + +describe('addEnvVar', () => { + let envFileContent = '' + + describe('addEnvVar adds environment variables as part of a setup task', () => { + beforeEach(() => { + jest.spyOn(fs, 'existsSync').mockImplementation(() => { + return true + }) + + jest.spyOn(fs, 'readFileSync').mockImplementation(() => { + return envFileContent + }) + + jest.spyOn(fs, 'writeFileSync').mockImplementation((envPath, envFile) => { + expect(envPath).toContain('.env') + return envFile + }) + }) + + afterEach(() => { + jest.restoreAllMocks() + envFileContent = '' + }) + + it('should add a new environment variable when it does not exist', () => { + envFileContent = 'EXISTING_VAR = value\n# CommentedVar = 123\n' + const file = addEnvVar('NEW_VAR', 'new_value', 'New Variable Comment') + + expect(file).toMatchSnapshot() + }) + + it('should add a new environment variable when it does not exist when existing envars have no spacing', () => { + envFileContent = 'EXISTING_VAR=value\n# CommentedVar = 123\n' + const file = addEnvVar('NEW_VAR', 'new_value', 'New Variable Comment') + + expect(file).toMatchSnapshot() + }) + + it('should add a comment that the existing environment variable value was not changed, but include its new value as a comment', () => { + envFileContent = 'EXISTING_VAR=value\n# CommentedVar=123\n' + const file = addEnvVar( + 'EXISTING_VAR', + 'new_value', + 'Updated existing variable Comment' + ) + + expect(file).toMatchSnapshot() + }) + + it('should handle existing environment variables with quoted values', () => { + envFileContent = `EXISTING_VAR = "value"\n# CommentedVar = 123\n` + const file = addEnvVar('EXISTING_VAR', 'value', 'New Variable Comment') + + expect(file).toMatchSnapshot() + }) + + it('should handle existing environment variables with quoted values and no spacing', () => { + envFileContent = `EXISTING_VAR="value"\n# CommentedVar=123\n` + const file = addEnvVar('EXISTING_VAR', 'value', 'New Variable Comment') + + expect(file).toMatchSnapshot() + }) + + it('should handle existing environment variables and new value with quoted values by not updating the original value', () => { + envFileContent = `EXISTING_VAR = "value"\n# CommentedVar = 123\n` + const file = addEnvVar( + 'EXISTING_VAR', + 'new_value', + 'New Variable Comment' + ) + + expect(file).toMatchSnapshot() + }) + }) +}) + +describe('updateTomlConfig', () => { + describe('updateTomlConfig configures a new CLI plugin', () => { + beforeEach(() => { + jest.spyOn(fs, 'existsSync').mockImplementation(() => { + return true + }) + + jest.spyOn(fs, 'readFileSync').mockImplementation(() => { + return toml.stringify(defaultRedwoodToml) + }) + + jest + .spyOn(fs, 'writeFileSync') + .mockImplementation((tomlPath, tomlFile) => { + expect(tomlPath).toContain('redwood.toml') + return tomlFile + }) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('adds when experimental cli is not configured', () => { + const file = updateTomlConfig( + '@example/test-package-when-cli-not-configured' + ) + expect(file).toMatchSnapshot() + }) + + it('adds when experimental cli has some plugins configured', () => { + defaultRedwoodToml['experimental'] = { + cli: { + autoInstall: true, + plugins: [ + { + package: + '@existing-example/some-package-when-cli-has-some-packages-configured', + }, + ], + }, + } + + const file = updateTomlConfig('@example/test-package-name') + expect(file).toMatchSnapshot() + }) + + it('adds when experimental cli is setup but has no plugins configured', () => { + defaultRedwoodToml['experimental'] = { + cli: { + autoInstall: true, + }, + } + + const file = updateTomlConfig( + '@example/test-package-when-no-plugins-configured' + ) + + expect(file).toMatchSnapshot() + }) + + it('adds package but keeps autoInstall false', () => { + defaultRedwoodToml['experimental'] = { + cli: { + autoInstall: false, + }, + } + + const file = updateTomlConfig( + '@example/test-package-when-autoInstall-false' + ) + + expect(file).toMatchSnapshot() + }) + + it('does not add duplicate place when experimental cli has that plugin configured', () => { + defaultRedwoodToml['experimental'] = { + cli: { + autoInstall: true, + plugins: [ + { + package: '@existing-example/some-package-name-already-exists', + }, + ], + }, + } + + const file = updateTomlConfig( + '@existing-example/some-package-name-already-exists' + ) + + expect(file).toMatchSnapshot() + }) + }) +}) diff --git a/packages/cli-helpers/src/lib/__tests__/version.test.ts b/packages/cli-helpers/src/lib/__tests__/version.test.ts new file mode 100644 index 000000000000..cbcd8bb43e68 --- /dev/null +++ b/packages/cli-helpers/src/lib/__tests__/version.test.ts @@ -0,0 +1,360 @@ +jest.mock('@redwoodjs/project-config', () => { + return { + getPaths: () => { + return { + base: '', + } + }, + } +}) +jest.mock('fs') + +import fs from 'fs' + +import { getCompatibilityData } from '../version' + +const EXAMPLE_PACKUMENT = { + _id: '@scope/package-name', + _rev: 'a1b2c3a1b2c3a1b2c3a1b2c3a1b2c3a1b2c3', + name: '@scope/package-name', + 'dist-tags': { + latest: '0.0.3', + }, + versions: { + '0.0.1': { + name: '@scope/package-name', + version: '0.0.1', + main: 'index.js', + scripts: { + test: 'echo "Error: no test specified" && exit 1', + }, + author: '', + license: 'ISC', + description: '', + dependencies: { + 'some-package': '1.2.3', + }, + _id: '@scope/package-name@0.0.1', + _nodeVersion: '18.16.0', + _npmVersion: '9.5.1', + dist: { + integrity: 'sha512-somehashvalue', + shasum: 'somehashvalue', + tarball: 'someurl', + fileCount: 8, + unpackedSize: 1024, + signatures: [ + { + keyid: 'SHA256:somehashvalue', + sig: 'somehashvalue', + }, + ], + }, + _npmUser: { + name: 'someuser', + email: 'someemail', + }, + directories: {}, + maintainers: [ + { + name: 'someuser', + email: 'someemail', + }, + ], + _npmOperationalInternal: { + host: 'somes3', + tmp: 'sometmp', + }, + _hasShrinkwrap: false, + }, + '0.0.2': { + name: '@scope/package-name', + version: '0.0.2', + main: 'index.js', + scripts: { + test: 'echo "Error: no test specified" && exit 1', + }, + author: '', + license: 'ISC', + description: '', + dependencies: { + 'some-package': '1.2.3', + }, + engines: { + redwoodjs: '^5.1.0', + }, + _id: '@scope/package-name@0.0.2', + _nodeVersion: '20.2.0', + _npmVersion: '9.6.6', + dist: { + integrity: 'sha512-somehashvalue', + shasum: 'somehashvalue', + tarball: 'someurl', + fileCount: 8, + unpackedSize: 1024, + signatures: [ + { + keyid: 'SHA256:somehashvalue', + sig: 'somehashvalue', + }, + ], + }, + _npmUser: { + name: 'someuser', + email: 'someemail', + }, + directories: {}, + maintainers: [ + { + name: 'someuser', + email: 'someemail', + }, + ], + _npmOperationalInternal: { + host: 'somes3', + tmp: 'sometmp', + }, + _hasShrinkwrap: false, + }, + '0.0.3': { + name: '@scope/package-name', + version: '0.0.3', + main: 'index.js', + scripts: { + test: 'echo "Error: no test specified" && exit 1', + }, + author: '', + license: 'ISC', + description: '', + dependencies: { + 'some-package': '1.2.3', + }, + engines: { + redwoodjs: '^6.0.0', + }, + _id: '@scope/package-name@0.0.3', + _nodeVersion: '20.2.0', + _npmVersion: '9.6.6', + dist: { + integrity: 'sha512-somehashvalue', + shasum: 'somehashvalue', + tarball: 'someurl', + fileCount: 8, + unpackedSize: 1024, + signatures: [ + { + keyid: 'SHA256:somehashvalue', + sig: 'somehashvalue', + }, + ], + }, + _npmUser: { + name: 'someuser', + email: 'someemail', + }, + directories: {}, + maintainers: [ + { + name: 'someuser', + email: 'someemail', + }, + ], + _npmOperationalInternal: { + host: 'somes3', + tmp: 'sometmp', + }, + _hasShrinkwrap: false, + }, + }, + time: { + created: '2023-05-10T12:10:52.090Z', + '0.0.1': '2023-05-10T12:10:52.344Z', + '0.0.2': '2023-07-15T19:45:25.905Z', + }, + maintainers: [ + { + name: 'someuser', + email: 'someemail', + }, + ], + license: 'ISC', + readme: 'ERROR: No README data found!', + readmeFilename: '', + author: { + name: 'someuser', + }, +} + +describe('version compatibility detection', () => { + beforeEach(() => { + jest.spyOn(global, 'fetch').mockImplementation(() => { + return { + json: () => { + return EXAMPLE_PACKUMENT + }, + } as any + }) + + jest.spyOn(fs, 'readFileSync').mockImplementation(() => { + return JSON.stringify({ + devDependencies: { + '@redwoodjs/core': '^6.0.0', + }, + }) + }) + }) + + test('throws for some fetch related error', async () => { + // Mock the fetch function to throw an error + jest.spyOn(global, 'fetch').mockImplementation(() => { + throw new Error('Some fetch related error') + }) + await expect( + getCompatibilityData('some-package', 'latest') + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Some fetch related error"`) + + // Mock the json parsing to throw an error + jest.spyOn(global, 'fetch').mockImplementation(() => { + return { + json: () => { + throw new Error('Some json parsing error') + }, + } as any + }) + + await expect( + getCompatibilityData('some-package', 'latest') + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Some json parsing error"`) + }) + + test('throws for some packument related error', async () => { + jest.spyOn(global, 'fetch').mockImplementation(() => { + return { + json: () => { + return { + error: 'Some packument related error', + } + }, + } as any + }) + + await expect( + getCompatibilityData('some-package', 'latest') + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Some packument related error"` + ) + }) + + test('throws if preferred version is not found', async () => { + await expect( + getCompatibilityData('@scope/package-name', '0.0.4') + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"The package '@scope/package-name' does not have a version '0.0.4'"` + ) + }) + + test('throws if preferred tag is not found', async () => { + await expect( + getCompatibilityData('@scope/package-name', 'next') + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"The package '@scope/package-name' does not have a tag 'next'"` + ) + }) + + test('throws if no latest version could be found', async () => { + jest.spyOn(global, 'fetch').mockImplementation(() => { + return { + json: () => { + return { + ...EXAMPLE_PACKUMENT, + 'dist-tags': {}, + } + }, + } as any + }) + + await expect( + getCompatibilityData('@scope/package-name', 'latest') + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"The package '@scope/package-name' does not have a tag 'latest'"` + ) + }) + + test('returns the preferred version if it is compatible', async () => { + expect(await getCompatibilityData('@scope/package-name', '0.0.3')).toEqual({ + preferred: { + tag: 'latest', + version: '0.0.3', + }, + compatible: { + tag: 'latest', + version: '0.0.3', + }, + }) + }) + + test('returns the latest compatible version if the preferred version is not compatible', async () => { + expect(await getCompatibilityData('@scope/package-name', '0.0.2')).toEqual({ + preferred: { + tag: undefined, + version: '0.0.2', + }, + compatible: { + tag: 'latest', + version: '0.0.3', + }, + }) + }) + + test('returns the latest compatible version when given a tag', async () => { + expect(await getCompatibilityData('@scope/package-name', 'latest')).toEqual( + { + preferred: { + tag: 'latest', + version: '0.0.3', + }, + compatible: { + tag: 'latest', + version: '0.0.3', + }, + } + ) + + jest.spyOn(fs, 'readFileSync').mockImplementation(() => { + return JSON.stringify({ + devDependencies: { + '@redwoodjs/core': '5.2.0', + }, + }) + }) + + expect(await getCompatibilityData('@scope/package-name', 'latest')).toEqual( + { + preferred: { + tag: 'latest', + version: '0.0.3', + }, + compatible: { + tag: undefined, + version: '0.0.2', + }, + } + ) + }) + + test('throws if no compatible version could be found', async () => { + jest.spyOn(fs, 'readFileSync').mockImplementation(() => { + return JSON.stringify({ + devDependencies: { + '@redwoodjs/core': '7.0.0', + }, + }) + }) + + expect( + getCompatibilityData('@scope/package-name', 'latest') + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"No compatible version of '@scope/package-name' was found"` + ) + }) +}) diff --git a/packages/cli-helpers/src/lib/index.ts b/packages/cli-helpers/src/lib/index.ts index a3fbcd45971e..30861f18062c 100644 --- a/packages/cli-helpers/src/lib/index.ts +++ b/packages/cli-helpers/src/lib/index.ts @@ -2,12 +2,12 @@ import fs from 'fs' import path from 'path' import * as babel from '@babel/core' -import { - Listr, +import type { ListrTaskWrapper, ListrRenderer, ListrGetRendererClassFromValue, } from 'listr2' +import { Listr } from 'listr2' import { format } from 'prettier' import { colors } from './colors' @@ -46,7 +46,7 @@ export const transformTSToJS = (filename: string, content: string) => { process.exit(1) } - return prettify(filename.replace(/\.tsx?$/, '.js'), babelFileResult.code) + return prettify(filename.replace(/\.ts(x)?$/, '.js$1'), babelFileResult.code) } /** diff --git a/packages/cli-helpers/src/lib/installHelpers.ts b/packages/cli-helpers/src/lib/installHelpers.ts index 2a8a1decf713..c8016953dee1 100644 --- a/packages/cli-helpers/src/lib/installHelpers.ts +++ b/packages/cli-helpers/src/lib/installHelpers.ts @@ -1,23 +1,34 @@ import execa from 'execa' +import { getPaths } from './paths' + export const addWebPackages = (webPackages: string[]) => ({ title: 'Adding required web packages...', task: async () => { - const args = ['workspace', 'web', 'add', ...webPackages] - await execa('yarn', args) + await execa('yarn', ['add', ...webPackages], { cwd: getPaths().web.base }) }, }) export const addApiPackages = (apiPackages: string[]) => ({ title: 'Adding required api packages...', task: async () => { - await execa('yarn', ['workspace', 'api', 'add', ...apiPackages]) + await execa('yarn', ['add', ...apiPackages], { cwd: getPaths().api.base }) }, }) +export const addRootPackages = (packages: string[], devDependency = false) => { + const addMode = devDependency ? ['add', '-D'] : ['add'] + return { + title: 'Installing packages...', + task: async () => { + await execa('yarn', [...addMode, ...packages], { cwd: getPaths().base }) + }, + } +} + export const installPackages = { title: 'Installing packages...', task: async () => { - await execa('yarn', ['install']) + await execa('yarn', ['install'], { cwd: getPaths().base }) }, } diff --git a/packages/cli-helpers/src/lib/paths.ts b/packages/cli-helpers/src/lib/paths.ts index a1c847ab1997..1f026d6ebc5c 100644 --- a/packages/cli-helpers/src/lib/paths.ts +++ b/packages/cli-helpers/src/lib/paths.ts @@ -1,9 +1,4 @@ -import memoize from 'lodash.memoize' - -import { - getPaths as getRedwoodPaths, - resolveFile as internalResolveFile, -} from '@redwoodjs/project-config' +import { getPaths as _getPaths } from '@redwoodjs/project-config' import { colors } from './colors' @@ -15,9 +10,9 @@ function isErrorWithMessage(e: any): e is { message: string } { * This wraps the core version of getPaths into something that catches the exception * and displays a helpful error message. */ -const _getPaths = () => { +export function getPaths() { try { - return getRedwoodPaths() + return _getPaths() } catch (e) { if (isErrorWithMessage(e)) { console.error(colors.error(e.message)) @@ -26,6 +21,3 @@ const _getPaths = () => { process.exit(1) } } - -export const getPaths = memoize(_getPaths) -export const resolveFile = internalResolveFile diff --git a/packages/cli-helpers/src/lib/project.ts b/packages/cli-helpers/src/lib/project.ts index bdcbb1d0e052..7755671f2aad 100644 --- a/packages/cli-helpers/src/lib/project.ts +++ b/packages/cli-helpers/src/lib/project.ts @@ -1,8 +1,19 @@ import fs from 'fs' import path from 'path' +import type { JsonMap } from '@iarna/toml' +import toml from '@iarna/toml' +import dotenv from 'dotenv' + +import { + findUp, + getConfigPath, + getConfig, + resolveFile, +} from '@redwoodjs/project-config' + import { colors } from './colors' -import { getPaths, resolveFile } from './paths' +import { getPaths } from './paths' export const getGraphqlPath = () => { return resolveFile(path.join(getPaths().api.functions, 'graphql')) @@ -31,19 +42,150 @@ export const getInstalledRedwoodVersion = () => { } } +/** + * Updates the project's redwood.toml file to include the specified packages plugin + * + * Uses toml parsing to determine if the plugin is already included in the file and + * only adds it if it is not. + * + * Writes the updated config to the file system by appending strings, not stringify-ing the toml. + */ +export const updateTomlConfig = (packageName: string) => { + const redwoodTomlPath = getConfigPath() + const originalTomlContent = fs.readFileSync(redwoodTomlPath, 'utf-8') + + let tomlToAppend = {} as JsonMap + + const config = getConfig(redwoodTomlPath) + + const cliSection = config.experimental?.cli + + if (!cliSection) { + tomlToAppend = { + experimental: { + cli: { + autoInstall: true, + plugins: [{ package: packageName, enabled: true }], + }, + }, + } + } else if (cliSection.plugins) { + const packageExists = cliSection.plugins.some( + (plugin) => plugin.package === packageName + ) + + if (!packageExists) { + tomlToAppend = { + experimental: { + cli: { + plugins: [{ package: packageName, enabled: true }], + }, + }, + } + } + } else { + tomlToAppend = { + experimental: { + cli: { + plugins: [{ package: packageName, enabled: true }], + }, + }, + } + } + + const newConfig = originalTomlContent + '\n' + toml.stringify(tomlToAppend) + + return fs.writeFileSync(redwoodTomlPath, newConfig, 'utf-8') +} + +export const updateTomlConfigTask = (packageName: string) => { + return { + title: `Updating redwood.toml to configure ${packageName} ...`, + task: () => { + updateTomlConfig(packageName) + }, + } +} + export const addEnvVarTask = (name: string, value: string, comment: string) => { return { title: `Adding ${name} var to .env...`, task: () => { - const envPath = path.join(getPaths().base, '.env') - const content = [comment && `# ${comment}`, `${name}=${value}`, ''].flat() - let envFile = '' + addEnvVar(name, value, comment) + }, + } +} - if (fs.existsSync(envPath)) { - envFile = fs.readFileSync(envPath).toString() + '\n' - } +export const addEnvVar = (name: string, value: string, comment: string) => { + const envPath = path.join(getPaths().base, '.env') + let envFile = '' + const newEnvironmentVariable = [ + comment && `# ${comment}`, + `${name} = ${value}`, + '', + ] + .flat() + .join('\n') - fs.writeFileSync(envPath, envFile + content.join('\n')) - }, + if (fs.existsSync(envPath)) { + envFile = fs.readFileSync(envPath).toString() + const existingEnvVars = dotenv.parse(envFile) + + if (existingEnvVars[name] && existingEnvVars[name] === value) { + return envFile + } + + if (existingEnvVars[name]) { + const p = [ + `# Note: The existing environment variable ${name} was not overwritten. Uncomment to use its new value.`, + comment && `# ${comment}`, + `# ${name} = ${value}`, + '', + ] + .flat() + .join('\n') + envFile += '\n' + p + } else { + envFile += '\n' + newEnvironmentVariable + } + } else { + envFile = newEnvironmentVariable + } + + return fs.writeFileSync(envPath, envFile) +} + +/** + * This sets the `RWJS_CWD` env var to the redwood project directory. This is typically required for internal + * redwood packages to work correctly. For example, `@redwoodjs/project-config` uses this when reading config + * or paths. + * + * @param cwd Explicitly set the redwood cwd. If not set, we'll try to determine it automatically. You likely + * only want to set this based on some specific input, like a CLI flag. + */ +export const setRedwoodCWD = (cwd?: string) => { + // Get the existing `cwd` from the `RWJS_CWD` env var, if it exists. + cwd ??= process.env.RWJS_CWD + + if (cwd) { + // `cwd` was specifically passed in or the `RWJS_CWD` env var. In this case, + // we don't want to find up for a `redwood.toml` file. The `redwood.toml` should just be in that directory. + if (!fs.existsSync(path.join(cwd, 'redwood.toml'))) { + throw new Error(`Couldn't find a "redwood.toml" file in ${cwd}`) + } + } else { + // `cwd` wasn't set. Odds are they're in a Redwood project, + // but they could be in ./api or ./web, so we have to find up to be sure. + const redwoodTOMLPath = findUp('redwood.toml', process.cwd()) + if (!redwoodTOMLPath) { + throw new Error( + `Couldn't find up a "redwood.toml" file from ${process.cwd()}` + ) + } + if (redwoodTOMLPath) { + cwd = path.dirname(redwoodTOMLPath) + } } + + process.env.RWJS_CWD = cwd } diff --git a/packages/cli-helpers/src/lib/version.ts b/packages/cli-helpers/src/lib/version.ts new file mode 100644 index 000000000000..a0ec4fec0ed2 --- /dev/null +++ b/packages/cli-helpers/src/lib/version.ts @@ -0,0 +1,116 @@ +import fs from 'fs' +import path from 'path' + +import semver from 'semver' + +import { getPaths } from '@redwoodjs/project-config' + +function getCorrespondingTag( + version: string, + distTags: Record +) { + return Object.entries(distTags).find(([_, v]) => v === version)?.[0] +} + +// NOTE: This only considers the package's engines.redwoodjs field and does not consider the package's dependencies, +// devDependencies, or peerDependencies. +/** + * Check if the package at the given version is compatible with the current version of the user's RedwoodJS project. This is + * determined by checking if the package's engines.redwoodjs field intersects with the user's RedwoodJS version. + * + * If the preferred version is not compatible, the latest compatible version will be returned if one exists. + */ +export async function getCompatibilityData( + packageName: string, + preferredVersionOrTag: string +) { + // Get the project's version of RedwoodJS from the root package.json's @redwoodjs/core dev dependency + const projectPackageJson = JSON.parse( + fs.readFileSync(path.join(getPaths().base, 'package.json'), { + encoding: 'utf8', + }) + ) + const projectRedwoodVersion = + projectPackageJson.devDependencies['@redwoodjs/core'] + + // Parse the version, we'll assume it's a tag if it's not a valid semver version + const semverVersion = semver.parse(preferredVersionOrTag) + const isUsingTag = semverVersion === null + + // Get the package information from NPM registry + // Valid package names are URL safe so we can just slot it right in here + const res = await fetch(`https://registry.npmjs.org/${packageName}`) + const packument = await res.json() + + // Check if there was an error fetching the package's information + if (packument.error !== undefined) { + throw new Error(packument.error) + } + + // Check if the package has the requested version/tag + if (isUsingTag) { + if (packument['dist-tags'][preferredVersionOrTag] === undefined) { + throw new Error( + `The package '${packageName}' does not have a tag '${preferredVersionOrTag}'` + ) + } + } else { + if (packument.versions[preferredVersionOrTag] === undefined) { + throw new Error( + `The package '${packageName}' does not have a version '${preferredVersionOrTag}'` + ) + } + } + + // Determine the version to try to use, defaulting to the latest published version of the package + const preferredVersion: string = isUsingTag + ? packument['dist-tags'][preferredVersionOrTag] + : preferredVersionOrTag + + // Extract the package's redwoodjs engine specification for the preferred version + const packageRedwoodSpecification = + packument.versions[preferredVersion].engines?.redwoodjs + + // We have to use the semver.intersects function because the package's redwoodjs engine could be a range + if ( + packageRedwoodSpecification !== undefined && + semver.intersects(projectRedwoodVersion, packageRedwoodSpecification) + ) { + const tag = getCorrespondingTag(preferredVersion, packument['dist-tags']) + return { + preferred: { + tag, + version: preferredVersion, + }, + compatible: { + tag, + version: preferredVersion, + }, + } + } + + // Look in the pacument for the latest version that is compatible with the current version of RedwoodJS + const versions = semver.sort(Object.keys(packument.versions)) + for (let i = versions.length - 1; i >= 0; i--) { + const redwoodVersionRequired = + packument.versions[versions[i]].engines?.redwoodjs + if (redwoodVersionRequired === undefined) { + continue + } + if (semver.intersects(projectRedwoodVersion, redwoodVersionRequired)) { + return { + preferred: { + tag: getCorrespondingTag(preferredVersion, packument['dist-tags']), + version: preferredVersion, + }, + compatible: { + tag: getCorrespondingTag(versions[i], packument['dist-tags']), + version: versions[i], + }, + } + } + } + + // No compatible version was found + throw new Error(`No compatible version of '${packageName}' was found`) +} diff --git a/packages/cli-helpers/src/telemetry/index.ts b/packages/cli-helpers/src/telemetry/index.ts new file mode 100644 index 000000000000..6e32cd251887 --- /dev/null +++ b/packages/cli-helpers/src/telemetry/index.ts @@ -0,0 +1,48 @@ +import type { AttributeValue, Span } from '@opentelemetry/api' +import opentelemetry, { SpanStatusCode } from '@opentelemetry/api' + +type TelemetryAttributes = { + [key: string]: AttributeValue +} + +/** + * Safely records attributes to the opentelemetry span + * + * @param attributes An object of key-value pairs to be individually recorded as attributes + * @param span An optional span to record the attributes to. If not provided, the current active span will be used + */ +export function recordTelemetryAttributes( + attributes: TelemetryAttributes, + span?: Span +) { + const spanToRecord = span ?? opentelemetry.trace.getActiveSpan() + if (spanToRecord === undefined) { + return + } + for (const [key, value] of Object.entries(attributes)) { + spanToRecord.setAttribute(key, value) + } +} + +/** + * Safely records an error to the opentelemetry span + * + * @param error An error to record to the span + * @param span An optional span to record the error to. If not provided, the current active span will be used + */ +export function recordTelemetryError(error: any, span?: Span) { + const spanToRecord = span ?? opentelemetry.trace.getActiveSpan() + if (spanToRecord === undefined) { + return + } + const message = error?.message ?? error?.toString() ?? 'Unknown error' + + // Some errors had the full stack trace in the message, so we only want the first line + const firstLineOfError = message.split('\n')[0] + + spanToRecord.setStatus({ + code: SpanStatusCode.ERROR, + message: firstLineOfError, + }) + spanToRecord.recordException(error ?? new Error(firstLineOfError)) +} diff --git a/packages/cli-helpers/tsconfig.json b/packages/cli-helpers/tsconfig.json index e26199e0de52..5e7f7fd919f2 100644 --- a/packages/cli-helpers/tsconfig.json +++ b/packages/cli-helpers/tsconfig.json @@ -4,7 +4,6 @@ "strict": true, "baseUrl": ".", "rootDir": "src", - "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo", "outDir": "dist" }, "include": ["src"], diff --git a/packages/cli-packages/dataMigrate/.babelrc.js b/packages/cli-packages/dataMigrate/.babelrc.js new file mode 100644 index 000000000000..bceadae91039 --- /dev/null +++ b/packages/cli-packages/dataMigrate/.babelrc.js @@ -0,0 +1,2 @@ +// Even though this package is built without Babel, we need this file for Jest. +module.exports = { extends: '../../../babel.config.js' } diff --git a/packages/cli-packages/dataMigrate/README.md b/packages/cli-packages/dataMigrate/README.md new file mode 100644 index 000000000000..d997043f51b6 --- /dev/null +++ b/packages/cli-packages/dataMigrate/README.md @@ -0,0 +1,14 @@ +# CLI Packages - Data Migrate + +Redwood's data migrations as a standalone CLI command. +The utility here is mainly for Docker. + +``` +npx @redwoodjs/cli-data-migrate --help +``` + +Run data migrations: + +``` +npx @redwoodjs/cli-data-migrate +``` diff --git a/packages/cli-packages/dataMigrate/build.mjs b/packages/cli-packages/dataMigrate/build.mjs new file mode 100644 index 000000000000..02fe92e4bb11 --- /dev/null +++ b/packages/cli-packages/dataMigrate/build.mjs @@ -0,0 +1,57 @@ +import fs from 'node:fs/promises' + +import * as esbuild from 'esbuild' +import fg from 'fast-glob' + +// ─── Package ───────────────────────────────────────────────────────────────── +// +// Types don't need to be transformed by esbuild, and the bin is bundled later. + +const sourceFiles = await fg.glob(['./src/**/*.ts'], { + ignore: ['./src/__tests__', './src/types.ts', './src/bin.ts'], +}) + +let result = await esbuild.build({ + entryPoints: sourceFiles, + outdir: 'dist', + + format: 'cjs', + platform: 'node', + target: ['node20'], + + logLevel: 'info', + + // For visualizing the bundle. + // See https://esbuild.github.io/api/#metafile and https://esbuild.github.io/analyze/. + metafile: true, +}) + +await fs.writeFile('meta.json', JSON.stringify(result.metafile, null, 2)) + +// ─── Bin ───────────────────────────────────────────────────────────────────── +// +// We build the bin differently because it doesn't have to asynchronously import the handler. + +result = await esbuild.build({ + entryPoints: ['./src/bin.ts'], + outdir: 'dist', + + banner: { + js: '#!/usr/bin/env node', + }, + + bundle: true, + minify: true, + + platform: 'node', + target: ['node20'], + packages: 'external', + + logLevel: 'info', + + // For visualizing the bundle. + // See https://esbuild.github.io/api/#metafile and https://esbuild.github.io/analyze/. + metafile: true, +}) + +await fs.writeFile('meta.bins.json', JSON.stringify(result.metafile, null, 2)) diff --git a/packages/cli-packages/dataMigrate/dependencyGraph.dist.svg b/packages/cli-packages/dataMigrate/dependencyGraph.dist.svg new file mode 100644 index 000000000000..8a0eeabf2e6b --- /dev/null +++ b/packages/cli-packages/dataMigrate/dependencyGraph.dist.svg @@ -0,0 +1,3302 @@ + + + + + + +dependency-cruiser output + + +cluster_node_modules + +node_modules + + +cluster_node_modules/@babel + +@babel + + +cluster_node_modules/@iarna + +@iarna + + +cluster_node_modules/@opentelemetry + +@opentelemetry + + +cluster_node_modules/@prisma + +@prisma + + +cluster_packages + +packages + + +cluster_packages/telemetry + +telemetry + + +cluster_packages/telemetry/dist + +dist + + +cluster_packages/internal + +internal + + +cluster_packages/internal/dist + +dist + + +cluster_packages/internal/dist/build + +build + + +cluster_packages/internal/dist/build/babel + +babel + + +cluster_packages/internal/dist/build/babelPlugins + +babelPlugins + + +cluster_packages/project-config + +project-config + + +cluster_packages/project-config/dist + +dist + + +cluster_packages/structure + +structure + + +cluster_packages/structure/dist + +dist + + +cluster_packages/structure/dist/x + +x + + +cluster_packages/structure/dist/model + +model + + +cluster_packages/structure/dist/model/util + +util + + +cluster_packages/cli-helpers + +cli-helpers + + +cluster_packages/cli-helpers/dist + +dist + + +cluster_packages/cli-helpers/dist/telemetry + +telemetry + + +cluster_packages/cli-helpers/dist/auth + +auth + + +cluster_packages/cli-helpers/dist/lib + +lib + + +cluster_packages/cli-packages + +cli-packages + + +cluster_packages/cli-packages/dataMigrate + +dataMigrate + + +cluster_packages/cli-packages/dataMigrate/dist + +dist + + +cluster_packages/cli-packages/dataMigrate/dist/lib + +lib + + +cluster_packages/cli-packages/dataMigrate/dist/commands + +commands + + + +child_process + + +child_process + + + + + +crypto + + +crypto + + + + + +fs + + +fs + + + + + +node_modules/@babel/core + + + + + +core + + + + + +node_modules/@babel/register + + + + + +register + + + + + +node_modules/@babel/runtime-corejs3 + + + + + +runtime-corejs3 + + + + + +node_modules/@iarna/toml + + + + + +toml + + + + + +node_modules/@opentelemetry/api + + + + + +api + + + + + +node_modules/@prisma/internals + + + + + +internals + + + + + +node_modules/chalk + + + + + +chalk + + + + + +node_modules/core-js + + + + + +core-js + + + + + +node_modules/deepmerge + + + + + +deepmerge + + + + + +node_modules/dotenv-defaults + + + + + +dotenv-defaults + + + + + +node_modules/execa + + + + + +execa + + + + + +node_modules/fast-glob + + + + + +fast-glob + + + + + +node_modules/fs-extra + + + + + +fs-extra + + + + + +node_modules/graphql + + + + + +graphql + + + + + +node_modules/lazy-get-decorator + + + + + +lazy-get-decorator + + + + + +node_modules/line-column + + + + + +line-column + + + + + +node_modules/listr2 + + + + + +listr2 + + + + + +node_modules/lodash + + + + + +lodash + + + + + +node_modules/lodash-decorators + + + + + +lodash-decorators + + + + + +node_modules/lru-cache + + + + + +lru-cache + + + + + +node_modules/pascalcase + + + + + +pascalcase + + + + + +node_modules/prettier + + + + + +prettier + + + + + +node_modules/string-env-interpolation + + + + + +string-env-interpolation + + + + + +node_modules/terminal-link + + + + + +terminal-link + + + + + +node_modules/ts-morph + + + + + +ts-morph + + + + + +node_modules/typescript + + + + + +typescript + + + + + +node_modules/vscode-languageserver + + + + + +vscode-languageserver + + + + + +node_modules/vscode-languageserver-types + + + + + +vscode-languageserver-types + + + + + +node_modules/yargs + + + + + +yargs + + + + + +os + + +os + + + + + +packages/cli-helpers/dist/auth/authFiles.js + + +authFiles.js +94% + + + + + +packages/cli-helpers/dist/auth/authFiles.js->fs + + + + + +packages/cli-helpers/dist/auth/authFiles.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/auth/authFiles.js->node_modules/pascalcase + + + + + +packages/cli-helpers/dist/lib/index.js + + +index.js +80% + + + + + +packages/cli-helpers/dist/auth/authFiles.js->packages/cli-helpers/dist/lib/index.js + + + + + +packages/cli-helpers/dist/lib/paths.js + + +paths.js +33% + + + + + +packages/cli-helpers/dist/auth/authFiles.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/lib/project.js + + +project.js +75% + + + + + +packages/cli-helpers/dist/auth/authFiles.js->packages/cli-helpers/dist/lib/project.js + + + + + +path + + +path + + + + + +packages/cli-helpers/dist/auth/authFiles.js->path + + + + + +packages/cli-helpers/dist/lib/index.js->fs + + + + + +packages/cli-helpers/dist/lib/index.js->node_modules/@babel/core + + + + + +packages/cli-helpers/dist/lib/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/index.js->node_modules/listr2 + + + + + +packages/cli-helpers/dist/lib/index.js->node_modules/prettier + + + + + +packages/cli-helpers/dist/lib/index.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/lib/index.js->path + + + + + +packages/cli-helpers/dist/lib/colors.js + + +colors.js +33% + + + + + +packages/cli-helpers/dist/lib/index.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/lib/paths.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/paths.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/project-config/dist/index.js + + +index.js +25% + + + + + +packages/cli-helpers/dist/lib/paths.js->packages/project-config/dist/index.js + + + + + +packages/cli-helpers/dist/lib/project.js->fs + + + + + +packages/cli-helpers/dist/lib/project.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/project.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/lib/project.js->path + + + + + +packages/cli-helpers/dist/lib/project.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/lib/project.js->packages/project-config/dist/index.js + + + + + +packages/cli-helpers/package.json + + +package.json +0% + + + + + +packages/cli-helpers/dist/lib/project.js->packages/cli-helpers/package.json + + + + + +packages/cli-helpers/dist/auth/authTasks.js + + +authTasks.js +95% + + + + + +packages/cli-helpers/dist/auth/authTasks.js->fs + + + + + +packages/cli-helpers/dist/auth/authTasks.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/auth/authTasks.js->node_modules/core-js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/auth/authFiles.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/lib/index.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/lib/project.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->path + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/project-config/dist/index.js + + + + + +packages/cli-helpers/dist/lib/colors.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/colors.js->node_modules/chalk + + + + + +packages/project-config/dist/index.js->fs + + + + + +packages/project-config/dist/index.js->node_modules/@iarna/toml + + + + + +packages/project-config/dist/index.js->node_modules/deepmerge + + + + + +packages/project-config/dist/index.js->node_modules/fast-glob + + + + + +packages/project-config/dist/index.js->node_modules/string-env-interpolation + + + + + +packages/project-config/dist/index.js->path + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js + + +setupHelpers.js +91% + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->node_modules/core-js + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->node_modules/listr2 + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->node_modules/terminal-link + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->packages/cli-helpers/dist/auth/authTasks.js + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/lib/installHelpers.js + + +installHelpers.js +67% + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->packages/cli-helpers/dist/lib/installHelpers.js + + + + + +packages/telemetry/dist/index.js + + +index.js +50% + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->packages/telemetry/dist/index.js + + + + + +packages/cli-helpers/dist/lib/installHelpers.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/installHelpers.js->node_modules/execa + + + + + +packages/cli-helpers/dist/lib/installHelpers.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/telemetry/dist/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/telemetry/dist/telemetry.js + + +telemetry.js +88% + + + + + +packages/telemetry/dist/index.js->packages/telemetry/dist/telemetry.js + + + + + +packages/cli-helpers/dist/index.js + + +index.js +77% + + + + + +packages/cli-helpers/dist/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/index.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/project.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/auth/setupHelpers.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/installHelpers.js + + + + + +packages/cli-helpers/dist/telemetry/index.js + + +index.js +83% + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/telemetry/index.js + + + + + +packages/cli-helpers/dist/telemetry/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/telemetry/index.js->node_modules/@opentelemetry/api + + + + + +packages/cli-packages/dataMigrate/dist/bin.d.ts + + +bin.d.ts +0% + + + + + +packages/cli-packages/dataMigrate/dist/bin.js + + +bin.js +100% + + + + + +packages/cli-packages/dataMigrate/dist/bin.js->fs + + + + + +packages/cli-packages/dataMigrate/dist/bin.js->node_modules/chalk + + + + + +packages/cli-packages/dataMigrate/dist/bin.js->node_modules/listr2 + + + + + +packages/cli-packages/dataMigrate/dist/bin.js->node_modules/terminal-link + + + + + +packages/cli-packages/dataMigrate/dist/bin.js->node_modules/yargs + + + + + +packages/cli-packages/dataMigrate/dist/bin.js->path + + + + + +packages/cli-packages/dataMigrate/dist/bin.js->packages/project-config/dist/index.js + + + + + +packages/cli-packages/dataMigrate/dist/bin.js->packages/telemetry/dist/index.js + + + + + +packages/cli-packages/dataMigrate/dist/bin.js->packages/cli-helpers/dist/index.js + + + + + +no-non-package-json + + + +packages/internal/dist/build/babel/api.js + + +api.js +85% + + + + + +packages/cli-packages/dataMigrate/dist/bin.js->packages/internal/dist/build/babel/api.js + + + + + +packages/internal/dist/build/babel/api.js->fs + + + + + +packages/internal/dist/build/babel/api.js->node_modules/@babel/core + + + + + +packages/internal/dist/build/babel/api.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babel/api.js->path + + + + + +packages/internal/dist/build/babel/api.js->packages/project-config/dist/index.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-directory-named-import.js + + +babel-plugin-redwood-directory-named-import.js +67% + + + + + +packages/internal/dist/build/babel/api.js->packages/internal/dist/build/babelPlugins/babel-plugin-redwood-directory-named-import.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-import-dir.js + + +babel-plugin-redwood-import-dir.js +89% + + + + + +packages/internal/dist/build/babel/api.js->packages/internal/dist/build/babelPlugins/babel-plugin-redwood-import-dir.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-otel-wrapping.js + + +babel-plugin-redwood-otel-wrapping.js +90% + + + + + +packages/internal/dist/build/babel/api.js->packages/internal/dist/build/babelPlugins/babel-plugin-redwood-otel-wrapping.js + + + + + +packages/internal/dist/build/babel/common.js + + +common.js +85% + + + + + +packages/internal/dist/build/babel/api.js->packages/internal/dist/build/babel/common.js + + + + + +packages/cli-packages/dataMigrate/dist/commands/install.d.ts + + +install.d.ts +100% + + + + + +packages/cli-packages/dataMigrate/dist/commands/install.d.ts->node_modules/yargs + + + + + +packages/cli-packages/dataMigrate/dist/commands/install.js + + +install.js +75% + + + + + +packages/cli-packages/dataMigrate/dist/commands/install.js->node_modules/terminal-link + + + + + +packages/cli-packages/dataMigrate/dist/commands/install.js->packages/cli-helpers/dist/index.js + + + + + +no-non-package-json + + + +packages/cli-packages/dataMigrate/dist/commands/installHandler.js + + +installHandler.js +88% + + + + + +packages/cli-packages/dataMigrate/dist/commands/install.js->packages/cli-packages/dataMigrate/dist/commands/installHandler.js + + + + + +packages/cli-packages/dataMigrate/dist/commands/installHandler.js->node_modules/execa + + + + + +packages/cli-packages/dataMigrate/dist/commands/installHandler.js->node_modules/fs-extra + + + + + +packages/cli-packages/dataMigrate/dist/commands/installHandler.js->node_modules/listr2 + + + + + +packages/cli-packages/dataMigrate/dist/commands/installHandler.js->path + + + + + +packages/cli-packages/dataMigrate/dist/commands/installHandler.js->packages/project-config/dist/index.js + + + + + +packages/cli-packages/dataMigrate/dist/commands/installHandler.js->packages/telemetry/dist/index.js + + + + + +packages/cli-packages/dataMigrate/dist/lib/colors.js + + +colors.js +33% + + + + + +packages/cli-packages/dataMigrate/dist/commands/installHandler.js->packages/cli-packages/dataMigrate/dist/lib/colors.js + + + + + +packages/cli-packages/dataMigrate/dist/commands/installHandler.d.ts + + +installHandler.d.ts +0% + + + + + +packages/cli-packages/dataMigrate/dist/lib/colors.js->node_modules/chalk + + + + + +packages/cli-packages/dataMigrate/dist/commands/up.d.ts + + +up.d.ts +100% + + + + + +packages/cli-packages/dataMigrate/dist/commands/up.d.ts->node_modules/yargs + + + + + +packages/cli-packages/dataMigrate/dist/types.d.ts + + +types.d.ts +0% + + + + + +packages/cli-packages/dataMigrate/dist/commands/up.d.ts->packages/cli-packages/dataMigrate/dist/types.d.ts + + + + + +packages/cli-packages/dataMigrate/dist/commands/up.js + + +up.js +67% + + + + + +packages/cli-packages/dataMigrate/dist/commands/up.js->node_modules/terminal-link + + + + + +packages/cli-packages/dataMigrate/dist/commands/up.js->packages/project-config/dist/index.js + + + + + +packages/cli-packages/dataMigrate/dist/commands/up.js->packages/cli-helpers/dist/index.js + + + + + +no-non-package-json + + + +packages/cli-packages/dataMigrate/dist/commands/upHandler.js + + +upHandler.js +88% + + + + + +packages/cli-packages/dataMigrate/dist/commands/up.js->packages/cli-packages/dataMigrate/dist/commands/upHandler.js + + + + + +packages/cli-packages/dataMigrate/dist/commands/upHandler.js->fs + + + + + +packages/cli-packages/dataMigrate/dist/commands/upHandler.js->node_modules/listr2 + + + + + +packages/cli-packages/dataMigrate/dist/commands/upHandler.js->path + + + + + +packages/cli-packages/dataMigrate/dist/commands/upHandler.js->packages/project-config/dist/index.js + + + + + +packages/cli-packages/dataMigrate/dist/commands/upHandler.js->packages/telemetry/dist/index.js + + + + + +packages/cli-packages/dataMigrate/dist/commands/upHandler.js->packages/internal/dist/build/babel/api.js + + + + + +packages/cli-packages/dataMigrate/dist/commands/upHandler.js->packages/cli-packages/dataMigrate/dist/lib/colors.js + + + + + +packages/cli-packages/dataMigrate/dist/commands/upHandler.d.ts + + +upHandler.d.ts +100% + + + + + +packages/cli-packages/dataMigrate/dist/commands/upHandler.d.ts->packages/cli-packages/dataMigrate/dist/types.d.ts + + + + + +packages/cli-packages/dataMigrate/dist/index.d.ts + + +index.d.ts +100% + + + + + +packages/cli-packages/dataMigrate/dist/index.d.ts->packages/cli-packages/dataMigrate/dist/commands/up.js + + + + + +packages/cli-packages/dataMigrate/dist/index.js + + +index.js +100% + + + + + +packages/cli-packages/dataMigrate/dist/index.js->packages/cli-packages/dataMigrate/dist/commands/install.js + + + + + +packages/cli-packages/dataMigrate/dist/index.js->packages/cli-packages/dataMigrate/dist/commands/up.js + + + + + +packages/cli-packages/dataMigrate/dist/lib/colors.d.ts + + +colors.d.ts +100% + + + + + +packages/cli-packages/dataMigrate/dist/lib/colors.d.ts->node_modules/chalk + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-directory-named-import.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-directory-named-import.js->path + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-directory-named-import.js->packages/project-config/dist/index.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-import-dir.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-import-dir.js->node_modules/core-js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-import-dir.js->node_modules/fast-glob + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-import-dir.js->path + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-import-dir.js->packages/project-config/dist/index.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-otel-wrapping.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-otel-wrapping.js->node_modules/core-js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-otel-wrapping.js->path + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-otel-wrapping.js->packages/project-config/dist/index.js + + + + + +packages/internal/dist/build/babel/common.js->fs + + + + + +packages/internal/dist/build/babel/common.js->node_modules/@babel/register + + + + + +packages/internal/dist/build/babel/common.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babel/common.js->node_modules/typescript + + + + + +packages/internal/dist/build/babel/common.js->path + + + + + +packages/internal/dist/build/babel/common.js->packages/project-config/dist/index.js + + + + + +packages/internal/package.json + + +package.json +0% + + + + + +packages/internal/dist/build/babel/common.js->packages/internal/package.json + + + + + +packages/internal/dist/build/babel/web.js + + +web.js +94% + + + + + +packages/internal/dist/build/babel/common.js->packages/internal/dist/build/babel/web.js + + + + + +no-circular + + + +packages/internal/dist/build/babel/web.js->fs + + + + + +packages/internal/dist/build/babel/web.js->node_modules/@babel/core + + + + + +packages/internal/dist/build/babel/web.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babel/web.js->path + + + + + +packages/internal/dist/build/babel/web.js->packages/project-config/dist/index.js + + + + + +packages/internal/dist/build/babel/web.js->packages/internal/dist/build/babelPlugins/babel-plugin-redwood-directory-named-import.js + + + + + +packages/internal/dist/build/babel/web.js->packages/internal/dist/build/babel/common.js + + + + + +no-circular + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-cell.js + + +babel-plugin-redwood-cell.js +86% + + + + + +packages/internal/dist/build/babel/web.js->packages/internal/dist/build/babelPlugins/babel-plugin-redwood-cell.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-mock-cell-data.js + + +babel-plugin-redwood-mock-cell-data.js +88% + + + + + +packages/internal/dist/build/babel/web.js->packages/internal/dist/build/babelPlugins/babel-plugin-redwood-mock-cell-data.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-remove-dev-fatal-error-page.js + + +babel-plugin-redwood-remove-dev-fatal-error-page.js +50% + + + + + +packages/internal/dist/build/babel/web.js->packages/internal/dist/build/babelPlugins/babel-plugin-redwood-remove-dev-fatal-error-page.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-routes-auto-loader.js + + +babel-plugin-redwood-routes-auto-loader.js +89% + + + + + +packages/internal/dist/build/babel/web.js->packages/internal/dist/build/babelPlugins/babel-plugin-redwood-routes-auto-loader.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-cell.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-cell.js->node_modules/core-js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-cell.js->path + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-mock-cell-data.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-mock-cell-data.js->path + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-mock-cell-data.js->packages/project-config/dist/index.js + + + + + +packages/structure/dist/index.js + + +index.js +86% + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-mock-cell-data.js->packages/structure/dist/index.js + + + + + +no-non-package-json + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-remove-dev-fatal-error-page.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-routes-auto-loader.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-routes-auto-loader.js->node_modules/core-js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-routes-auto-loader.js->path + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-routes-auto-loader.js->packages/project-config/dist/index.js + + + + + +packages/structure/dist/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/index.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/hosts.js + + +hosts.js +83% + + + + + +packages/structure/dist/index.js->packages/structure/dist/hosts.js + + + + + +packages/structure/dist/x/URL.js + + +URL.js +47% + + + + + +packages/structure/dist/index.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/index.js + + +index.js +67% + + + + + +packages/structure/dist/index.js->packages/structure/dist/model/index.js + + + + + +packages/structure/dist/x/vscode-languageserver-types.js + + +vscode-languageserver-types.js +58% + + + + + +packages/structure/dist/index.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/errors.js + + +errors.js +20% + + + + + +packages/structure/dist/errors.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/hosts.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/hosts.js->node_modules/fast-glob + + + + + +packages/structure/dist/hosts.js->node_modules/fs-extra + + + + + +packages/structure/dist/hosts.js->packages/project-config/dist/index.js + + + + + +packages/structure/dist/x/decorators.js + + +decorators.js +17% + + + + + +packages/structure/dist/hosts.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/x/decorators.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/decorators.js->node_modules/lazy-get-decorator + + + + + +packages/structure/dist/x/decorators.js->node_modules/lodash-decorators + + + + + +packages/structure/dist/ide.js + + +ide.js +58% + + + + + +packages/structure/dist/ide.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/ide.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/ide.js->path + + + + + +packages/structure/dist/ide.js->packages/structure/dist/hosts.js + + + + + +packages/structure/dist/ide.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/x/Array.js + + +Array.js +40% + + + + + +packages/structure/dist/ide.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/x/path.js + + +path.js +67% + + + + + +packages/structure/dist/ide.js->packages/structure/dist/x/path.js + + + + + +packages/structure/dist/x/ts-morph.js + + +ts-morph.js +78% + + + + + +packages/structure/dist/ide.js->packages/structure/dist/x/ts-morph.js + + + + + +packages/structure/dist/ide.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/x/Array.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/path.js->fs + + + + + +packages/structure/dist/x/path.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/path.js->node_modules/core-js + + + + + +packages/structure/dist/x/path.js->path + + + + + +packages/structure/dist/x/ts-morph.js->crypto + + + + + +packages/structure/dist/x/ts-morph.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/ts-morph.js->node_modules/lodash + + + + + +packages/structure/dist/x/ts-morph.js->node_modules/lru-cache + + + + + +packages/structure/dist/x/ts-morph.js->node_modules/ts-morph + + + + + +packages/structure/dist/x/URL.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/URL.js->path + + + + + +packages/structure/dist/model/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWProject.js + + +RWProject.js +96% + + + + + +packages/structure/dist/model/index.js->packages/structure/dist/model/RWProject.js + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/core-js + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/line-column + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/lodash + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/ts-morph + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/RWCell.js + + +RWCell.js +92% + + + + + +packages/structure/dist/model/RWCell.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWCell.js->node_modules/graphql + + + + + +packages/structure/dist/model/RWCell.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWCell.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWCell.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWCell.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/model/RWComponent.js + + +RWComponent.js +82% + + + + + +packages/structure/dist/model/RWCell.js->packages/structure/dist/model/RWComponent.js + + + + + +packages/structure/dist/model/RWComponent.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWComponent.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWComponent.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWComponent.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWEnvHelper.js + + +RWEnvHelper.js +95% + + + + + +packages/structure/dist/model/RWEnvHelper.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWEnvHelper.js->node_modules/dotenv-defaults + + + + + +packages/structure/dist/model/RWEnvHelper.js->node_modules/fs-extra + + + + + +packages/structure/dist/model/RWEnvHelper.js->node_modules/lodash + + + + + +packages/structure/dist/model/RWEnvHelper.js->node_modules/vscode-languageserver + + + + + +packages/structure/dist/model/RWEnvHelper.js->path + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/x/prisma.js + + +prisma.js +89% + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/x/prisma.js + + + + + +packages/structure/dist/x/vscode.js + + +vscode.js +94% + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/x/vscode.js + + + + + +packages/structure/dist/model/util/process_env.js + + +process_env.js +92% + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/model/util/process_env.js + + + + + +packages/structure/dist/x/prisma.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/prisma.js->node_modules/core-js + + + + + +packages/structure/dist/x/prisma.js->node_modules/fs-extra + + + + + +packages/structure/dist/x/prisma.js->node_modules/vscode-languageserver + + + + + +packages/structure/dist/x/prisma.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/x/prisma.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/x/vscode.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/vscode.js->node_modules/core-js + + + + + +packages/structure/dist/x/vscode.js->node_modules/lodash + + + + + +packages/structure/dist/x/vscode.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/x/vscode.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/util/process_env.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/util/process_env.js->node_modules/fast-glob + + + + + +packages/structure/dist/model/util/process_env.js->node_modules/fs-extra + + + + + +packages/structure/dist/model/util/process_env.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/util/process_env.js->path + + + + + +packages/structure/dist/model/util/process_env.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/model/util/process_env.js->packages/structure/dist/x/ts-morph.js + + + + + +packages/structure/dist/model/RWFunction.js + + +RWFunction.js +67% + + + + + +packages/structure/dist/model/RWFunction.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWFunction.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWLayout.js + + +RWLayout.js +67% + + + + + +packages/structure/dist/model/RWLayout.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWLayout.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWPage.js + + +RWPage.js +93% + + + + + +packages/structure/dist/model/RWPage.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWPage.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWPage.js->path + + + + + +packages/structure/dist/model/RWPage.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWPage.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWProject.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWProject.js->node_modules/@prisma/internals + + + + + +packages/structure/dist/model/RWProject.js->path + + + + + +packages/structure/dist/model/RWProject.js->packages/project-config/dist/index.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/x/path.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWCell.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWComponent.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWEnvHelper.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWFunction.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWLayout.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWPage.js + + + + + +packages/structure/dist/model/RWRouter.js + + +RWRouter.js +94% + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWRouter.js + + + + + +packages/structure/dist/model/RWSDL.js + + +RWSDL.js +94% + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWSDL.js + + + + + +packages/structure/dist/model/RWService.js + + +RWService.js +92% + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWService.js + + + + + +packages/structure/dist/model/RWTOML.js + + +RWTOML.js +90% + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWTOML.js + + + + + +packages/structure/dist/model/RWRouter.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWRouter.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWRouter.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/errors.js + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/model/RWRoute.js + + +RWRoute.js +94% + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/model/RWRoute.js + + + + + +packages/structure/dist/model/RWSDL.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWSDL.js->node_modules/graphql + + + + + +packages/structure/dist/model/RWSDL.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWSDL.js->path + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/errors.js + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/model/RWSDLField.js + + +RWSDLField.js +92% + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/model/RWSDLField.js + + + + + +packages/structure/dist/model/RWService.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWService.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWService.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWService.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWService.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/model/RWService.js->packages/structure/dist/x/path.js + + + + + +packages/structure/dist/model/RWServiceFunction.js + + +RWServiceFunction.js +92% + + + + + +packages/structure/dist/model/RWService.js->packages/structure/dist/model/RWServiceFunction.js + + + + + +packages/structure/dist/model/RWTOML.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWTOML.js->node_modules/@iarna/toml + + + + + +packages/structure/dist/model/RWTOML.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWTOML.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWTOML.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWTOML.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/model/RWRoute.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWRoute.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWRoute.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWRoute.js->path + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/errors.js + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/util.js + + +util.js +83% + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/util.js + + + + + +packages/structure/dist/model/util/advanced_path_parser.js + + +advanced_path_parser.js +83% + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/model/util/advanced_path_parser.js + + + + + +packages/structure/dist/util.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/util/advanced_path_parser.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/util/advanced_path_parser.js->node_modules/core-js + + + + + +packages/structure/dist/model/RWSDLField.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWSDLField.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWSDLField.js->packages/structure/dist/errors.js + + + + + +packages/structure/dist/model/RWSDLField.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWSDLField.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWSDLField.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/RWSDLField.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/model/RWServiceFunction.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWServiceFunction.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWServiceFunction.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWServiceFunction.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWServiceFunction.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWServiceFunction.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/model/RWServiceFunction.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/telemetry/dist/telemetry.js->child_process + + + + + +packages/telemetry/dist/telemetry.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/telemetry/dist/telemetry.js->os + + + + + +packages/telemetry/dist/telemetry.js->path + + + + + +packages/telemetry/dist/telemetry.js->packages/project-config/dist/index.js + + + + + diff --git a/packages/cli-packages/dataMigrate/dependencyGraph.src.svg b/packages/cli-packages/dataMigrate/dependencyGraph.src.svg new file mode 100644 index 000000000000..20febf33dbaf --- /dev/null +++ b/packages/cli-packages/dataMigrate/dependencyGraph.src.svg @@ -0,0 +1,3192 @@ + + + + + + +dependency-cruiser output + + +cluster_node_modules + +node_modules + + +cluster_node_modules/@babel + +@babel + + +cluster_node_modules/@iarna + +@iarna + + +cluster_node_modules/@opentelemetry + +@opentelemetry + + +cluster_node_modules/@prisma + +@prisma + + +cluster_packages + +packages + + +cluster_packages/telemetry + +telemetry + + +cluster_packages/telemetry/dist + +dist + + +cluster_packages/project-config + +project-config + + +cluster_packages/project-config/dist + +dist + + +cluster_packages/structure + +structure + + +cluster_packages/structure/dist + +dist + + +cluster_packages/structure/dist/x + +x + + +cluster_packages/structure/dist/model + +model + + +cluster_packages/structure/dist/model/util + +util + + +cluster_packages/cli-packages + +cli-packages + + +cluster_packages/cli-packages/dataMigrate + +dataMigrate + + +cluster_packages/cli-packages/dataMigrate/src + +src + + +cluster_packages/cli-packages/dataMigrate/src/commands + +commands + + +cluster_packages/cli-packages/dataMigrate/src/lib + +lib + + +cluster_packages/internal + +internal + + +cluster_packages/internal/dist + +dist + + +cluster_packages/internal/dist/build + +build + + +cluster_packages/internal/dist/build/babel + +babel + + +cluster_packages/internal/dist/build/babelPlugins + +babelPlugins + + +cluster_packages/cli-helpers + +cli-helpers + + +cluster_packages/cli-helpers/dist + +dist + + +cluster_packages/cli-helpers/dist/telemetry + +telemetry + + +cluster_packages/cli-helpers/dist/auth + +auth + + +cluster_packages/cli-helpers/dist/lib + +lib + + + +child_process + + +child_process + + + + + +crypto + + +crypto + + + + + +fs + + +fs + + + + + +node_modules/@babel/core + + + + + +core + + + + + +node_modules/@babel/register + + + + + +register + + + + + +node_modules/@babel/runtime-corejs3 + + + + + +runtime-corejs3 + + + + + +node_modules/@iarna/toml + + + + + +toml + + + + + +node_modules/@opentelemetry/api + + + + + +api + + + + + +node_modules/@prisma/client + + + + + +client + + + + + +node_modules/@prisma/internals + + + + + +internals + + + + + +node_modules/chalk + + + + + +chalk + + + + + +node_modules/core-js + + + + + +core-js + + + + + +node_modules/deepmerge + + + + + +deepmerge + + + + + +node_modules/dotenv-defaults + + + + + +dotenv-defaults + + + + + +node_modules/execa + + + + + +execa + + + + + +node_modules/fast-glob + + + + + +fast-glob + + + + + +node_modules/fs-extra + + + + + +fs-extra + + + + + +node_modules/graphql + + + + + +graphql + + + + + +node_modules/lazy-get-decorator + + + + + +lazy-get-decorator + + + + + +node_modules/line-column + + + + + +line-column + + + + + +node_modules/listr2 + + + + + +listr2 + + + + + +node_modules/lodash + + + + + +lodash + + + + + +node_modules/lodash-decorators + + + + + +lodash-decorators + + + + + +node_modules/lru-cache + + + + + +lru-cache + + + + + +node_modules/pascalcase + + + + + +pascalcase + + + + + +node_modules/prettier + + + + + +prettier + + + + + +node_modules/string-env-interpolation + + + + + +string-env-interpolation + + + + + +node_modules/terminal-link + + + + + +terminal-link + + + + + +node_modules/ts-morph + + + + + +ts-morph + + + + + +node_modules/typescript + + + + + +typescript + + + + + +node_modules/vscode-languageserver + + + + + +vscode-languageserver + + + + + +node_modules/vscode-languageserver-types + + + + + +vscode-languageserver-types + + + + + +node_modules/yargs + + + + + +yargs + + + + + +os + + +os + + + + + +packages/cli-helpers/dist/auth/authFiles.js + + +authFiles.js +94% + + + + + +packages/cli-helpers/dist/auth/authFiles.js->fs + + + + + +packages/cli-helpers/dist/auth/authFiles.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/auth/authFiles.js->node_modules/pascalcase + + + + + +packages/cli-helpers/dist/lib/index.js + + +index.js +80% + + + + + +packages/cli-helpers/dist/auth/authFiles.js->packages/cli-helpers/dist/lib/index.js + + + + + +packages/cli-helpers/dist/lib/paths.js + + +paths.js +33% + + + + + +packages/cli-helpers/dist/auth/authFiles.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/lib/project.js + + +project.js +75% + + + + + +packages/cli-helpers/dist/auth/authFiles.js->packages/cli-helpers/dist/lib/project.js + + + + + +path + + +path + + + + + +packages/cli-helpers/dist/auth/authFiles.js->path + + + + + +packages/cli-helpers/dist/lib/index.js->fs + + + + + +packages/cli-helpers/dist/lib/index.js->node_modules/@babel/core + + + + + +packages/cli-helpers/dist/lib/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/index.js->node_modules/listr2 + + + + + +packages/cli-helpers/dist/lib/index.js->node_modules/prettier + + + + + +packages/cli-helpers/dist/lib/index.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/lib/index.js->path + + + + + +packages/cli-helpers/dist/lib/colors.js + + +colors.js +33% + + + + + +packages/cli-helpers/dist/lib/index.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/lib/paths.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/paths.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/project-config/dist/index.js + + +index.js +26% + + + + + +packages/cli-helpers/dist/lib/paths.js->packages/project-config/dist/index.js + + + + + +packages/cli-helpers/dist/lib/project.js->fs + + + + + +packages/cli-helpers/dist/lib/project.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/project.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/lib/project.js->path + + + + + +packages/cli-helpers/dist/lib/project.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/lib/project.js->packages/project-config/dist/index.js + + + + + +packages/cli-helpers/package.json + + +package.json +0% + + + + + +packages/cli-helpers/dist/lib/project.js->packages/cli-helpers/package.json + + + + + +packages/cli-helpers/dist/auth/authTasks.js + + +authTasks.js +95% + + + + + +packages/cli-helpers/dist/auth/authTasks.js->fs + + + + + +packages/cli-helpers/dist/auth/authTasks.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/auth/authTasks.js->node_modules/core-js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/auth/authFiles.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/lib/index.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/lib/project.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->path + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/project-config/dist/index.js + + + + + +packages/cli-helpers/dist/lib/colors.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/colors.js->node_modules/chalk + + + + + +packages/project-config/dist/index.js->fs + + + + + +packages/project-config/dist/index.js->node_modules/@iarna/toml + + + + + +packages/project-config/dist/index.js->node_modules/deepmerge + + + + + +packages/project-config/dist/index.js->node_modules/fast-glob + + + + + +packages/project-config/dist/index.js->node_modules/string-env-interpolation + + + + + +packages/project-config/dist/index.js->path + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js + + +setupHelpers.js +91% + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->node_modules/core-js + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->node_modules/listr2 + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->node_modules/terminal-link + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->packages/cli-helpers/dist/auth/authTasks.js + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/lib/installHelpers.js + + +installHelpers.js +67% + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->packages/cli-helpers/dist/lib/installHelpers.js + + + + + +packages/telemetry/dist/index.js + + +index.js +57% + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->packages/telemetry/dist/index.js + + + + + +packages/cli-helpers/dist/lib/installHelpers.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/installHelpers.js->node_modules/execa + + + + + +packages/cli-helpers/dist/lib/installHelpers.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/telemetry/dist/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/telemetry/dist/telemetry.js + + +telemetry.js +88% + + + + + +packages/telemetry/dist/index.js->packages/telemetry/dist/telemetry.js + + + + + +packages/cli-helpers/dist/index.js + + +index.js +83% + + + + + +packages/cli-helpers/dist/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/index.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/project.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/auth/setupHelpers.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/installHelpers.js + + + + + +packages/cli-helpers/dist/telemetry/index.js + + +index.js +83% + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/telemetry/index.js + + + + + +packages/cli-helpers/dist/telemetry/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/telemetry/index.js->node_modules/@opentelemetry/api + + + + + +packages/cli-packages/dataMigrate/src/bin.ts + + +bin.ts +100% + + + + + +packages/cli-packages/dataMigrate/src/bin.ts->node_modules/yargs + + + + + +packages/cli-packages/dataMigrate/src/commands/up.ts + + +up.ts +75% + + + + + +packages/cli-packages/dataMigrate/src/bin.ts->packages/cli-packages/dataMigrate/src/commands/up.ts + + + + + +packages/cli-packages/dataMigrate/src/commands/upHandler.ts + + +upHandler.ts +82% + + + + + +packages/cli-packages/dataMigrate/src/bin.ts->packages/cli-packages/dataMigrate/src/commands/upHandler.ts + + + + + +packages/cli-packages/dataMigrate/src/commands/up.ts->node_modules/terminal-link + + + + + +packages/cli-packages/dataMigrate/src/commands/up.ts->node_modules/yargs + + + + + +packages/cli-packages/dataMigrate/src/commands/up.ts->packages/project-config/dist/index.js + + + + + +packages/cli-packages/dataMigrate/src/commands/up.ts->packages/cli-helpers/dist/index.js + + + + + +no-non-package-json + + + +packages/cli-packages/dataMigrate/src/commands/up.ts->packages/cli-packages/dataMigrate/src/commands/upHandler.ts + + + + + +packages/cli-packages/dataMigrate/src/types.ts + + +types.ts +0% + + + + + +packages/cli-packages/dataMigrate/src/commands/up.ts->packages/cli-packages/dataMigrate/src/types.ts + + + + + +packages/cli-packages/dataMigrate/src/commands/upHandler.ts->fs + + + + + +packages/cli-packages/dataMigrate/src/commands/upHandler.ts->node_modules/@prisma/client + + + + + +packages/cli-packages/dataMigrate/src/commands/upHandler.ts->node_modules/listr2 + + + + + +packages/cli-packages/dataMigrate/src/commands/upHandler.ts->path + + + + + +packages/cli-packages/dataMigrate/src/commands/upHandler.ts->packages/project-config/dist/index.js + + + + + +packages/cli-packages/dataMigrate/src/commands/upHandler.ts->packages/telemetry/dist/index.js + + + + + +packages/cli-packages/dataMigrate/src/lib/colors.ts + + +colors.ts +33% + + + + + +packages/cli-packages/dataMigrate/src/commands/upHandler.ts->packages/cli-packages/dataMigrate/src/lib/colors.ts + + + + + +packages/cli-packages/dataMigrate/src/commands/upHandler.ts->packages/cli-packages/dataMigrate/src/types.ts + + + + + +packages/internal/dist/build/babel/api.js + + +api.js +92% + + + + + +packages/cli-packages/dataMigrate/src/commands/upHandler.ts->packages/internal/dist/build/babel/api.js + + + + + +packages/cli-packages/dataMigrate/src/commands/install.ts + + +install.ts +80% + + + + + +packages/cli-packages/dataMigrate/src/commands/install.ts->node_modules/terminal-link + + + + + +packages/cli-packages/dataMigrate/src/commands/install.ts->node_modules/yargs + + + + + +packages/cli-packages/dataMigrate/src/commands/install.ts->packages/cli-helpers/dist/index.js + + + + + +no-non-package-json + + + +packages/cli-packages/dataMigrate/src/commands/installHandler.ts + + +installHandler.ts +88% + + + + + +packages/cli-packages/dataMigrate/src/commands/install.ts->packages/cli-packages/dataMigrate/src/commands/installHandler.ts + + + + + +packages/cli-packages/dataMigrate/src/commands/installHandler.ts->node_modules/execa + + + + + +packages/cli-packages/dataMigrate/src/commands/installHandler.ts->node_modules/fs-extra + + + + + +packages/cli-packages/dataMigrate/src/commands/installHandler.ts->node_modules/listr2 + + + + + +packages/cli-packages/dataMigrate/src/commands/installHandler.ts->path + + + + + +packages/cli-packages/dataMigrate/src/commands/installHandler.ts->packages/project-config/dist/index.js + + + + + +packages/cli-packages/dataMigrate/src/commands/installHandler.ts->packages/telemetry/dist/index.js + + + + + +packages/cli-packages/dataMigrate/src/commands/installHandler.ts->packages/cli-packages/dataMigrate/src/lib/colors.ts + + + + + +packages/cli-packages/dataMigrate/src/lib/colors.ts->node_modules/chalk + + + + + +packages/internal/dist/build/babel/api.js->fs + + + + + +packages/internal/dist/build/babel/api.js->node_modules/@babel/core + + + + + +packages/internal/dist/build/babel/api.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babel/api.js->path + + + + + +packages/internal/dist/build/babel/api.js->packages/project-config/dist/index.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-directory-named-import.js + + +babel-plugin-redwood-directory-named-import.js +67% + + + + + +packages/internal/dist/build/babel/api.js->packages/internal/dist/build/babelPlugins/babel-plugin-redwood-directory-named-import.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-import-dir.js + + +babel-plugin-redwood-import-dir.js +89% + + + + + +packages/internal/dist/build/babel/api.js->packages/internal/dist/build/babelPlugins/babel-plugin-redwood-import-dir.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-otel-wrapping.js + + +babel-plugin-redwood-otel-wrapping.js +90% + + + + + +packages/internal/dist/build/babel/api.js->packages/internal/dist/build/babelPlugins/babel-plugin-redwood-otel-wrapping.js + + + + + +packages/internal/dist/build/babel/common.js + + +common.js +85% + + + + + +packages/internal/dist/build/babel/api.js->packages/internal/dist/build/babel/common.js + + + + + +packages/cli-packages/dataMigrate/src/index.ts + + +index.ts +100% + + + + + +packages/cli-packages/dataMigrate/src/index.ts->packages/cli-packages/dataMigrate/src/commands/up.ts + + + + + +packages/cli-packages/dataMigrate/src/index.ts->packages/cli-packages/dataMigrate/src/commands/install.ts + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-directory-named-import.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-directory-named-import.js->path + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-directory-named-import.js->packages/project-config/dist/index.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-import-dir.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-import-dir.js->node_modules/core-js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-import-dir.js->node_modules/fast-glob + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-import-dir.js->path + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-import-dir.js->packages/project-config/dist/index.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-otel-wrapping.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-otel-wrapping.js->node_modules/core-js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-otel-wrapping.js->path + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-otel-wrapping.js->packages/project-config/dist/index.js + + + + + +packages/internal/dist/build/babel/common.js->fs + + + + + +packages/internal/dist/build/babel/common.js->node_modules/@babel/register + + + + + +packages/internal/dist/build/babel/common.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babel/common.js->node_modules/typescript + + + + + +packages/internal/dist/build/babel/common.js->path + + + + + +packages/internal/dist/build/babel/common.js->packages/project-config/dist/index.js + + + + + +packages/internal/package.json + + +package.json +0% + + + + + +packages/internal/dist/build/babel/common.js->packages/internal/package.json + + + + + +packages/internal/dist/build/babel/web.js + + +web.js +94% + + + + + +packages/internal/dist/build/babel/common.js->packages/internal/dist/build/babel/web.js + + + + + +no-circular + + + +packages/internal/dist/build/babel/web.js->fs + + + + + +packages/internal/dist/build/babel/web.js->node_modules/@babel/core + + + + + +packages/internal/dist/build/babel/web.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babel/web.js->path + + + + + +packages/internal/dist/build/babel/web.js->packages/project-config/dist/index.js + + + + + +packages/internal/dist/build/babel/web.js->packages/internal/dist/build/babelPlugins/babel-plugin-redwood-directory-named-import.js + + + + + +packages/internal/dist/build/babel/web.js->packages/internal/dist/build/babel/common.js + + + + + +no-circular + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-cell.js + + +babel-plugin-redwood-cell.js +86% + + + + + +packages/internal/dist/build/babel/web.js->packages/internal/dist/build/babelPlugins/babel-plugin-redwood-cell.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-mock-cell-data.js + + +babel-plugin-redwood-mock-cell-data.js +88% + + + + + +packages/internal/dist/build/babel/web.js->packages/internal/dist/build/babelPlugins/babel-plugin-redwood-mock-cell-data.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-remove-dev-fatal-error-page.js + + +babel-plugin-redwood-remove-dev-fatal-error-page.js +50% + + + + + +packages/internal/dist/build/babel/web.js->packages/internal/dist/build/babelPlugins/babel-plugin-redwood-remove-dev-fatal-error-page.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-routes-auto-loader.js + + +babel-plugin-redwood-routes-auto-loader.js +89% + + + + + +packages/internal/dist/build/babel/web.js->packages/internal/dist/build/babelPlugins/babel-plugin-redwood-routes-auto-loader.js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-cell.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-cell.js->node_modules/core-js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-cell.js->path + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-mock-cell-data.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-mock-cell-data.js->path + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-mock-cell-data.js->packages/project-config/dist/index.js + + + + + +packages/structure/dist/index.js + + +index.js +86% + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-mock-cell-data.js->packages/structure/dist/index.js + + + + + +no-non-package-json + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-remove-dev-fatal-error-page.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-routes-auto-loader.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-routes-auto-loader.js->node_modules/core-js + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-routes-auto-loader.js->path + + + + + +packages/internal/dist/build/babelPlugins/babel-plugin-redwood-routes-auto-loader.js->packages/project-config/dist/index.js + + + + + +packages/structure/dist/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/index.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/hosts.js + + +hosts.js +83% + + + + + +packages/structure/dist/index.js->packages/structure/dist/hosts.js + + + + + +packages/structure/dist/x/URL.js + + +URL.js +47% + + + + + +packages/structure/dist/index.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/index.js + + +index.js +67% + + + + + +packages/structure/dist/index.js->packages/structure/dist/model/index.js + + + + + +packages/structure/dist/x/vscode-languageserver-types.js + + +vscode-languageserver-types.js +58% + + + + + +packages/structure/dist/index.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/errors.js + + +errors.js +20% + + + + + +packages/structure/dist/errors.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/hosts.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/hosts.js->node_modules/fast-glob + + + + + +packages/structure/dist/hosts.js->node_modules/fs-extra + + + + + +packages/structure/dist/hosts.js->packages/project-config/dist/index.js + + + + + +packages/structure/dist/x/decorators.js + + +decorators.js +17% + + + + + +packages/structure/dist/hosts.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/x/decorators.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/decorators.js->node_modules/lazy-get-decorator + + + + + +packages/structure/dist/x/decorators.js->node_modules/lodash-decorators + + + + + +packages/structure/dist/ide.js + + +ide.js +58% + + + + + +packages/structure/dist/ide.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/ide.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/ide.js->path + + + + + +packages/structure/dist/ide.js->packages/structure/dist/hosts.js + + + + + +packages/structure/dist/ide.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/x/Array.js + + +Array.js +40% + + + + + +packages/structure/dist/ide.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/x/path.js + + +path.js +67% + + + + + +packages/structure/dist/ide.js->packages/structure/dist/x/path.js + + + + + +packages/structure/dist/x/ts-morph.js + + +ts-morph.js +78% + + + + + +packages/structure/dist/ide.js->packages/structure/dist/x/ts-morph.js + + + + + +packages/structure/dist/ide.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/x/Array.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/path.js->fs + + + + + +packages/structure/dist/x/path.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/path.js->node_modules/core-js + + + + + +packages/structure/dist/x/path.js->path + + + + + +packages/structure/dist/x/ts-morph.js->crypto + + + + + +packages/structure/dist/x/ts-morph.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/ts-morph.js->node_modules/lodash + + + + + +packages/structure/dist/x/ts-morph.js->node_modules/lru-cache + + + + + +packages/structure/dist/x/ts-morph.js->node_modules/ts-morph + + + + + +packages/structure/dist/x/URL.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/URL.js->path + + + + + +packages/structure/dist/model/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWProject.js + + +RWProject.js +96% + + + + + +packages/structure/dist/model/index.js->packages/structure/dist/model/RWProject.js + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/core-js + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/line-column + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/lodash + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/ts-morph + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/x/vscode-languageserver-types.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/RWCell.js + + +RWCell.js +92% + + + + + +packages/structure/dist/model/RWCell.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWCell.js->node_modules/graphql + + + + + +packages/structure/dist/model/RWCell.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWCell.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWCell.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWCell.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/model/RWComponent.js + + +RWComponent.js +82% + + + + + +packages/structure/dist/model/RWCell.js->packages/structure/dist/model/RWComponent.js + + + + + +packages/structure/dist/model/RWComponent.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWComponent.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWComponent.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWComponent.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWEnvHelper.js + + +RWEnvHelper.js +95% + + + + + +packages/structure/dist/model/RWEnvHelper.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWEnvHelper.js->node_modules/dotenv-defaults + + + + + +packages/structure/dist/model/RWEnvHelper.js->node_modules/fs-extra + + + + + +packages/structure/dist/model/RWEnvHelper.js->node_modules/lodash + + + + + +packages/structure/dist/model/RWEnvHelper.js->node_modules/vscode-languageserver + + + + + +packages/structure/dist/model/RWEnvHelper.js->path + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/x/prisma.js + + +prisma.js +89% + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/x/prisma.js + + + + + +packages/structure/dist/x/vscode.js + + +vscode.js +94% + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/x/vscode.js + + + + + +packages/structure/dist/model/util/process_env.js + + +process_env.js +92% + + + + + +packages/structure/dist/model/RWEnvHelper.js->packages/structure/dist/model/util/process_env.js + + + + + +packages/structure/dist/x/prisma.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/prisma.js->node_modules/core-js + + + + + +packages/structure/dist/x/prisma.js->node_modules/fs-extra + + + + + +packages/structure/dist/x/prisma.js->node_modules/vscode-languageserver + + + + + +packages/structure/dist/x/prisma.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/x/prisma.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/x/vscode.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/x/vscode.js->node_modules/core-js + + + + + +packages/structure/dist/x/vscode.js->node_modules/lodash + + + + + +packages/structure/dist/x/vscode.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/x/vscode.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/util/process_env.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/util/process_env.js->node_modules/fast-glob + + + + + +packages/structure/dist/model/util/process_env.js->node_modules/fs-extra + + + + + +packages/structure/dist/model/util/process_env.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/util/process_env.js->path + + + + + +packages/structure/dist/model/util/process_env.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/model/util/process_env.js->packages/structure/dist/x/ts-morph.js + + + + + +packages/structure/dist/model/RWFunction.js + + +RWFunction.js +67% + + + + + +packages/structure/dist/model/RWFunction.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWFunction.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWLayout.js + + +RWLayout.js +67% + + + + + +packages/structure/dist/model/RWLayout.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWLayout.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWPage.js + + +RWPage.js +93% + + + + + +packages/structure/dist/model/RWPage.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWPage.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWPage.js->path + + + + + +packages/structure/dist/model/RWPage.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWPage.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWProject.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWProject.js->node_modules/@prisma/internals + + + + + +packages/structure/dist/model/RWProject.js->path + + + + + +packages/structure/dist/model/RWProject.js->packages/project-config/dist/index.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/x/path.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWCell.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWComponent.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWEnvHelper.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWFunction.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWLayout.js + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWPage.js + + + + + +packages/structure/dist/model/RWRouter.js + + +RWRouter.js +94% + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWRouter.js + + + + + +packages/structure/dist/model/RWSDL.js + + +RWSDL.js +94% + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWSDL.js + + + + + +packages/structure/dist/model/RWService.js + + +RWService.js +92% + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWService.js + + + + + +packages/structure/dist/model/RWTOML.js + + +RWTOML.js +90% + + + + + +packages/structure/dist/model/RWProject.js->packages/structure/dist/model/RWTOML.js + + + + + +packages/structure/dist/model/RWRouter.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWRouter.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWRouter.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/errors.js + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/model/RWRoute.js + + +RWRoute.js +94% + + + + + +packages/structure/dist/model/RWRouter.js->packages/structure/dist/model/RWRoute.js + + + + + +packages/structure/dist/model/RWSDL.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWSDL.js->node_modules/graphql + + + + + +packages/structure/dist/model/RWSDL.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWSDL.js->path + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/errors.js + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/model/RWSDLField.js + + +RWSDLField.js +92% + + + + + +packages/structure/dist/model/RWSDL.js->packages/structure/dist/model/RWSDLField.js + + + + + +packages/structure/dist/model/RWService.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWService.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWService.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWService.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWService.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/model/RWService.js->packages/structure/dist/x/path.js + + + + + +packages/structure/dist/model/RWServiceFunction.js + + +RWServiceFunction.js +92% + + + + + +packages/structure/dist/model/RWService.js->packages/structure/dist/model/RWServiceFunction.js + + + + + +packages/structure/dist/model/RWTOML.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWTOML.js->node_modules/@iarna/toml + + + + + +packages/structure/dist/model/RWTOML.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWTOML.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWTOML.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWTOML.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/model/RWRoute.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWRoute.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWRoute.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWRoute.js->path + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/errors.js + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/util.js + + +util.js +83% + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/util.js + + + + + +packages/structure/dist/model/util/advanced_path_parser.js + + +advanced_path_parser.js +83% + + + + + +packages/structure/dist/model/RWRoute.js->packages/structure/dist/model/util/advanced_path_parser.js + + + + + +packages/structure/dist/util.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/util/advanced_path_parser.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/util/advanced_path_parser.js->node_modules/core-js + + + + + +packages/structure/dist/model/RWSDLField.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWSDLField.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWSDLField.js->packages/structure/dist/errors.js + + + + + +packages/structure/dist/model/RWSDLField.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWSDLField.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWSDLField.js->packages/structure/dist/x/URL.js + + + + + +packages/structure/dist/model/RWSDLField.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/structure/dist/model/RWServiceFunction.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/structure/dist/model/RWServiceFunction.js->node_modules/ts-morph + + + + + +packages/structure/dist/model/RWServiceFunction.js->node_modules/vscode-languageserver-types + + + + + +packages/structure/dist/model/RWServiceFunction.js->packages/structure/dist/x/decorators.js + + + + + +packages/structure/dist/model/RWServiceFunction.js->packages/structure/dist/ide.js + + + + + +packages/structure/dist/model/RWServiceFunction.js->packages/structure/dist/x/Array.js + + + + + +packages/structure/dist/model/RWServiceFunction.js->packages/structure/dist/x/vscode-languageserver-types.js + + + + + +packages/telemetry/dist/telemetry.js->child_process + + + + + +packages/telemetry/dist/telemetry.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/telemetry/dist/telemetry.js->os + + + + + +packages/telemetry/dist/telemetry.js->path + + + + + +packages/telemetry/dist/telemetry.js->packages/project-config/dist/index.js + + + + + diff --git a/packages/cli-packages/dataMigrate/dist.test.ts b/packages/cli-packages/dataMigrate/dist.test.ts new file mode 100644 index 000000000000..10a98623da00 --- /dev/null +++ b/packages/cli-packages/dataMigrate/dist.test.ts @@ -0,0 +1,30 @@ +import fs from 'fs' +import path from 'path' + +const distPath = path.join(__dirname, 'dist') + +describe('dist', () => { + it("shouldn't have tests", () => { + expect(fs.existsSync(path.join(distPath, '__tests__'))).toBe(false) + }) + + it("shouldn't have the types file", () => { + expect(fs.existsSync(path.join(distPath, 'types.ts'))).toBe(false) + expect(fs.existsSync(path.join(distPath, 'types.js'))).toBe(false) + }) + + it('should export commands', async () => { + const mod = await import(path.join(distPath, 'index.js')) + expect(mod).toHaveProperty('commands') + }) + + describe('bin', () => { + it('starts with shebang', () => { + const binFileContent = fs.readFileSync( + path.join(distPath, 'bin.js'), + 'utf-8' + ) + binFileContent.startsWith('#!/usr/bin/env node') + }) + }) +}) diff --git a/packages/cli-packages/dataMigrate/e2eTest.mjs b/packages/cli-packages/dataMigrate/e2eTest.mjs new file mode 100644 index 000000000000..bc3d3103eb94 --- /dev/null +++ b/packages/cli-packages/dataMigrate/e2eTest.mjs @@ -0,0 +1,123 @@ +/* eslint-env node */ + +// This test mostly provides an easy way to run the code in this package on a project. +// We'll see about automating it in CI and making the monolithic CI action lighter. + +import assert from 'node:assert/strict' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +import chalk from 'chalk' +import execa from 'execa' +import yargs from 'yargs/yargs' + +import { commands } from './dist/index.js' + +const distPath = fileURLToPath(new URL('./dist', import.meta.url)) +const binPath = path.join(distPath, 'bin.js') + +const command = `node ${binPath}` + +// ─── Constants ─────────────────────────────────────────────────────────────── + +const expectedBinHelp = `\ +data-migrate + +Run any outstanding Data Migrations against the database + +Options: + --help Show help [boolean] + --version Show version number [boolean] + --import-db-client-from-dist, Import the db client from dist + --db-from-dist [boolean] [default: false] + -d, --dist-path Path to the api dist directory + [string] [default: "/Users/dom/projects/redwood/test-project/api/dist"] + +Also see the Redwood CLI Reference +(​https://redwoodjs.com/docs/cli-commands#datamigrate-up​)` + +const expectedBinNoPendingDataMigrations = `\ + +No pending data migrations run, already up-to-date. +` + +const expectedInstallHelp = `\ +e2eTest.mjs install + +Add the RW_DataMigration model to your schema + +Options: + --help Show help [boolean] + --version Show version number [boolean] + +Also see the Redwood CLI Reference +(​https://redwoodjs.com/docs/cli-commands#datamigrate-install​)` + +// ─── Tests ─────────────────────────────────────────────────────────────────── + +const testProjectPath = + process.env.REDWOOD_TEST_PROJECT_PATH ?? process.env.PROJECT_PATH + +// Handle there being no test project to run against. +if (testProjectPath === undefined) { + console.error( + [ + chalk.red('Error: No test project to run against.'), + "If you haven't generated a test project, do so first via...", + '', + ' yarn build:test-project --link ', + '', + `Then set the ${chalk.magenta( + 'REDWOOD_TEST_PROJECT_PATH' + )} env var to the path of your test project and run this script again.`, + ].join('\n') + ) + process.exit(1) +} + +process.chdir(testProjectPath) + +let stdout + +// ─── Bin Help ──────────────────────────────────────────────────────────────── +console.log('Running bin help test') +stdout = (await execa.command(`${command} --help`)).stdout +assert.equal(stdout, expectedBinHelp) + +// ─── Bin No Pending Data Migrations ────────────────────────────────────────── +await execa.command('yarn rw prisma generate') + +console.log('Running bin no pending data migrations test') +stdout = (await execa.command(command)).stdout +assert.equal(stdout, expectedBinNoPendingDataMigrations) + +// ─── Install Help ──────────────────────────────────────────────────────────── +console.log('Running install help test') +const installCommand = commands.find(({ command }) => command === 'install') +const parser = yargs().command(installCommand) + +stdout = await new Promise((resolve) => { + parser.parse(['install', '--help'], (err, argv, stdout) => { + resolve(stdout) + }) +}) + +assert.equal(stdout, expectedInstallHelp) + +// ─── Install ───────────────────────────────────────────────────────────────── +await parser.parse(['install']) + +// check that install worked... + +// ─── Bin ───────────────────────────────────────────────────────────────────── +await execa.command('yarn rw prisma migrate dev --name test', { + stdio: 'inherit', +}) + +await execa.command('yarn rw g data-migration test', { + stdio: 'inherit', +}) + +await execa.command(command) + +// check that up worked... diff --git a/packages/cli-packages/dataMigrate/package.json b/packages/cli-packages/dataMigrate/package.json new file mode 100644 index 000000000000..11f631d3edfd --- /dev/null +++ b/packages/cli-packages/dataMigrate/package.json @@ -0,0 +1,48 @@ +{ + "name": "@redwoodjs/cli-data-migrate", + "version": "6.0.7", + "repository": { + "type": "git", + "url": "https://github.com/redwoodjs/redwood.git", + "directory": "packages/cli-packages/storybook" + }, + "license": "MIT", + "bin": { + "up": "./dist/bin.js" + }, + "exports": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "yarn node ./build.mjs && yarn build:types", + "build:types": "tsc --build --verbose", + "prepublishOnly": "NODE_ENV=production yarn build", + "test": "yarn test:unit && yarn test:dist", + "test:unit": "yarn jest src", + "test:dist": "yarn jest ./dist.test.ts" + }, + "dependencies": { + "@redwoodjs/babel-config": "6.0.7", + "@redwoodjs/project-config": "6.0.7", + "chalk": "4.1.2", + "dotenv-defaults": "5.0.2", + "execa": "5.1.1", + "fs-extra": "11.2.0", + "listr2": "6.6.1", + "terminal-link": "2.1.1", + "yargs": "17.7.2" + }, + "devDependencies": { + "@prisma/client": "5.7.0", + "@types/fs-extra": "11.0.4", + "@types/yargs": "17.0.32", + "esbuild": "0.19.9", + "fast-glob": "3.3.2", + "jest": "29.7.0", + "memfs": "4.6.0", + "typescript": "5.3.3" + }, + "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" +} diff --git a/packages/cli-packages/dataMigrate/src/__tests__/install.test.ts b/packages/cli-packages/dataMigrate/src/__tests__/install.test.ts new file mode 100644 index 000000000000..e7eb5165d42c --- /dev/null +++ b/packages/cli-packages/dataMigrate/src/__tests__/install.test.ts @@ -0,0 +1,40 @@ +import * as installCommand from '../commands/install' +import { handler as dataMigrateInstallHandler } from '../commands/installHandler.js' + +jest.mock( + '../commands/installHandler.js', + () => ({ + handler: jest.fn(), + }), + { virtual: true } +) + +describe('install', () => { + it('exports `command`, `description`, `builder`, and `handler`', () => { + for (const property of ['command', 'builder', 'description', 'handler']) { + expect(installCommand).toHaveProperty(property) + } + }) + + it("`command` and `description` haven't unintentionally changed", () => { + expect(installCommand.command).toMatchInlineSnapshot(`"install"`) + expect(installCommand.description).toMatchInlineSnapshot( + `"Add the RW_DataMigration model to your schema"` + ) + }) + + it('`builder` has an epilogue', () => { + const yargs = { epilogue: jest.fn() } + // @ts-expect-error this is a test file; epilogue is the only thing `builder` calls right now + installCommand.builder(yargs) + expect(yargs.epilogue).toBeCalledWith( + // eslint-disable-next-line no-irregular-whitespace + 'Also see the Redwood CLI Reference (​https://redwoodjs.com/docs/cli-commands#datamigrate-install​)' + ) + }) + + it('`handler` proxies to `./installHandler.js`', async () => { + await installCommand.handler() + expect(dataMigrateInstallHandler).toHaveBeenCalled() + }) +}) diff --git a/packages/cli-packages/dataMigrate/src/__tests__/installHandler.test.ts b/packages/cli-packages/dataMigrate/src/__tests__/installHandler.test.ts new file mode 100644 index 000000000000..089c4ce984a2 --- /dev/null +++ b/packages/cli-packages/dataMigrate/src/__tests__/installHandler.test.ts @@ -0,0 +1,76 @@ +import fs from 'fs' + +import execa from 'execa' +import { vol } from 'memfs' + +import { getPaths } from '@redwoodjs/project-config' + +import { + handler, + RW_DATA_MIGRATION_MODEL, + createDatabaseMigrationCommand, + notes, +} from '../commands/installHandler' + +jest.mock('fs', () => require('memfs').fs) + +jest.mock('execa', () => { + return { + command: jest.fn(() => { + return { + stdout: 42, + } + }), + } +}) + +describe('installHandler', () => { + it("the RW_DATA_MIGRATION_MODEL hasn't unintentionally changed", () => { + expect(RW_DATA_MIGRATION_MODEL).toMatchInlineSnapshot(` + "model RW_DataMigration { + version String @id + name String + startedAt DateTime + finishedAt DateTime + }" + `) + }) + + it("the `createDatabaseMigrationCommand` hasn't unintentionally changed", () => { + expect(createDatabaseMigrationCommand).toMatchInlineSnapshot( + `"yarn rw prisma migrate dev --name create_data_migrations --create-only"` + ) + }) + + it('adds a data migrations directory, model, and migration', async () => { + const redwoodProjectPath = '/redwood-app' + process.env.RWJS_CWD = redwoodProjectPath + + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + db: { + 'schema.prisma': '', + }, + }, + }, + redwoodProjectPath + ) + + console.log = jest.fn() + + await handler() + + const dataMigrationsPath = getPaths().api.dataMigrations + + expect(fs.readdirSync(dataMigrationsPath)).toEqual(['.keep']) + expect(fs.readFileSync(getPaths().api.dbSchema, 'utf-8')).toMatch( + RW_DATA_MIGRATION_MODEL + ) + expect(execa.command).toHaveBeenCalledWith(createDatabaseMigrationCommand, { + cwd: getPaths().base, + }) + expect(console.log).toHaveBeenCalledWith(notes) + }) +}) diff --git a/packages/cli-packages/dataMigrate/src/__tests__/up.test.ts b/packages/cli-packages/dataMigrate/src/__tests__/up.test.ts new file mode 100644 index 000000000000..06ad05baa883 --- /dev/null +++ b/packages/cli-packages/dataMigrate/src/__tests__/up.test.ts @@ -0,0 +1,52 @@ +import { vol } from 'memfs' +import yargs from 'yargs/yargs' + +import { getPaths } from '@redwoodjs/project-config' + +import * as upCommand from '../commands/up' +import { handler as dataMigrateUpHandler } from '../commands/upHandler.js' + +jest.mock('fs', () => require('memfs').fs) +jest.mock( + '../commands/upHandler.js', + () => ({ + handler: jest.fn(), + }), + { virtual: true } +) + +describe('up', () => { + it('exports `command`, `description`, `builder`, and `handler`', () => { + expect(upCommand).toHaveProperty('command', 'up') + expect(upCommand).toHaveProperty( + 'description', + 'Run any outstanding Data Migrations against the database' + ) + expect(upCommand).toHaveProperty('builder') + expect(upCommand).toHaveProperty('handler') + }) + + it('`builder` configures two options with defaults', () => { + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + dist: {}, + }, + }, + '/redwood-app' + ) + + process.env.RWJS_CWD = '/redwood-app' + + const { argv } = upCommand.builder(yargs) + + expect(argv).toHaveProperty('import-db-client-from-dist', false) + expect(argv).toHaveProperty('dist-path', getPaths().api.dist) + }) + + it('`handler` proxies to `./upHandler.js`', async () => { + await upCommand.handler({}) + expect(dataMigrateUpHandler).toHaveBeenCalled() + }) +}) diff --git a/packages/cli-packages/dataMigrate/src/__tests__/upHandler.test.ts b/packages/cli-packages/dataMigrate/src/__tests__/upHandler.test.ts new file mode 100644 index 000000000000..2fbf0f457bbc --- /dev/null +++ b/packages/cli-packages/dataMigrate/src/__tests__/upHandler.test.ts @@ -0,0 +1,262 @@ +import { vol } from 'memfs' + +import { getPaths } from '@redwoodjs/project-config' + +import { handler, NO_PENDING_MIGRATIONS_MESSAGE } from '../commands/upHandler' + +// ─── Mocks ─────────────────────────────────────────────────────────────────── + +const redwoodProjectPath = '/redwood-app' + +jest.mock('fs', () => require('memfs').fs) + +const mockDataMigrations: { current: any[] } = { current: [] } + +jest.mock( + '/redwood-app/api/dist/lib/db.js', + () => { + return { + db: { + rW_DataMigration: { + create(dataMigration) { + mockDataMigrations.current.push(dataMigration) + }, + findMany() { + return mockDataMigrations.current + }, + }, + $disconnect: () => {}, + }, + } + }, + { virtual: true } +) + +jest.mock( + `\\redwood-app\\api\\dist\\lib\\db.js`, + () => { + return { + db: { + rW_DataMigration: { + create(dataMigration) { + mockDataMigrations.current.push(dataMigration) + }, + findMany() { + return mockDataMigrations.current + }, + }, + $disconnect: () => {}, + }, + } + }, + { virtual: true } +) + +jest.mock( + '/redwood-app/api/db/dataMigrations/20230822075442-wip.ts', + () => { + return { default: () => {} } + }, + { + virtual: true, + } +) + +jest.mock( + '\\redwood-app\\api\\db\\dataMigrations\\20230822075442-wip.ts', + () => { + return { default: () => {} } + }, + { + virtual: true, + } +) + +jest.mock( + '/redwood-app/api/db/dataMigrations/20230822075443-wip.ts', + () => { + return { + default: () => { + throw new Error('oops') + }, + } + }, + { + virtual: true, + } +) + +jest.mock( + '\\redwood-app\\api\\db\\dataMigrations\\20230822075443-wip.ts', + () => { + return { + default: () => { + throw new Error('oops') + }, + } + }, + { + virtual: true, + } +) + +jest.mock( + '/redwood-app/api/db/dataMigrations/20230822075444-wip.ts', + () => { + return { default: () => {} } + }, + { + virtual: true, + } +) + +jest.mock( + '\\redwood-app\\api\\db\\dataMigrations\\20230822075444-wip.ts', + () => { + return { default: () => {} } + }, + { + virtual: true, + } +) + +const RWJS_CWD = process.env.RWJS_CWD + +beforeAll(() => { + process.env.RWJS_CWD = redwoodProjectPath +}) + +afterEach(() => { + vol.reset() + mockDataMigrations.current = [] +}) + +afterAll(() => { + process.env.RWJS_CWD = RWJS_CWD +}) + +const ranDataMigration = { + version: '20230822075441', + name: '20230822075441-wip.ts', + startedAt: '2023-08-22T07:55:16.292Z', + finishedAt: '2023-08-22T07:55:16.292Z', +} + +// ─── Tests ─────────────────────────────────────────────────────────────────── + +describe('upHandler', () => { + it("noops if there's no data migrations directory", async () => { + console.info = jest.fn() + + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + dist: { + lib: { + 'db.js': '', + }, + }, + db: { + // No dataMigrations dir: + // + // dataMigrations: { + // [ranDataMigration.name]: '', + // }, + }, + }, + }, + redwoodProjectPath + ) + + await handler({ + importDbClientFromDist: true, + distPath: getPaths().api.dist, + }) + + expect(console.info.mock.calls[0][0]).toMatch(NO_PENDING_MIGRATIONS_MESSAGE) + }) + + it("noops if there's no pending migrations", async () => { + mockDataMigrations.current = [ranDataMigration] + + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + dist: { + lib: { + 'db.js': '', + }, + }, + db: { + dataMigrations: { + [ranDataMigration.name]: '', + }, + }, + }, + }, + redwoodProjectPath + ) + + console.info = jest.fn() + + await handler({ + importDbClientFromDist: true, + distPath: getPaths().api.dist, + }) + + expect(console.info.mock.calls[0][0]).toMatch(NO_PENDING_MIGRATIONS_MESSAGE) + }) + + it('runs pending migrations', async () => { + console.info = jest.fn() + console.error = jest.fn() + console.warn = jest.fn() + + mockDataMigrations.current = [ + { + version: '20230822075441', + name: '20230822075441-wip.ts', + startedAt: '2023-08-22T07:55:16.292Z', + finishedAt: '2023-08-22T07:55:16.292Z', + }, + ] + + vol.fromNestedJSON( + { + 'redwood.toml': '', + api: { + dist: { + lib: { + 'db.js': '', + }, + }, + db: { + dataMigrations: { + '20230822075442-wip.ts': '', + '20230822075443-wip.ts': '', + '20230822075444-wip.ts': '', + }, + }, + }, + }, + redwoodProjectPath + ) + + await handler({ + importDbClientFromDist: true, + distPath: getPaths().api.dist, + }) + + expect(console.info.mock.calls[0][0]).toMatch( + '1 data migration(s) completed successfully.' + ) + expect(console.error.mock.calls[1][0]).toMatch( + '1 data migration(s) exited with errors.' + ) + expect(console.warn.mock.calls[0][0]).toMatch( + '1 data migration(s) skipped due to previous error' + ) + }) +}) diff --git a/packages/cli-packages/dataMigrate/src/bin.ts b/packages/cli-packages/dataMigrate/src/bin.ts new file mode 100644 index 000000000000..8de413f619c9 --- /dev/null +++ b/packages/cli-packages/dataMigrate/src/bin.ts @@ -0,0 +1,23 @@ +import path from 'path' + +// @ts-expect-error not sure; other packages use this and don't provide the types +import { config } from 'dotenv-defaults' +import { hideBin } from 'yargs/helpers' +import yargs from 'yargs/yargs' + +import { getPaths } from '@redwoodjs/project-config' + +import { description, builder } from './commands/up' +import { handler } from './commands/upHandler' + +config({ + path: path.join(getPaths().base, '.env'), + defaults: path.join(getPaths().base, '.env.defaults'), + multiline: true, +}) + +yargs(hideBin(process.argv)) + .scriptName('data-migrate') + // @ts-expect-error not sure; this is a valid signature + .command('$0', description, builder, handler) + .parse() diff --git a/packages/cli-packages/dataMigrate/src/commands/install.ts b/packages/cli-packages/dataMigrate/src/commands/install.ts new file mode 100644 index 000000000000..001245a3406d --- /dev/null +++ b/packages/cli-packages/dataMigrate/src/commands/install.ts @@ -0,0 +1,21 @@ +import terminalLink from 'terminal-link' +import type { Argv } from 'yargs' + +export const command = 'install' +export const description = 'Add the RW_DataMigration model to your schema' + +export function builder(yargs: Argv): Argv { + return yargs.epilogue( + `Also see the ${terminalLink( + 'Redwood CLI Reference', + 'https://redwoodjs.com/docs/cli-commands#datamigrate-install' + )}` + ) +} + +export async function handler(): Promise { + const { handler: dataMigrateInstallHandler } = await import( + './installHandler.js' + ) + await dataMigrateInstallHandler() +} diff --git a/packages/cli-packages/dataMigrate/src/commands/installHandler.ts b/packages/cli-packages/dataMigrate/src/commands/installHandler.ts new file mode 100644 index 000000000000..5bdda934716a --- /dev/null +++ b/packages/cli-packages/dataMigrate/src/commands/installHandler.ts @@ -0,0 +1,81 @@ +import path from 'path' + +import execa from 'execa' +import fs from 'fs-extra' +import { Listr } from 'listr2' + +import { getPaths } from '@redwoodjs/project-config' + +import c from '../lib/colors' + +export async function handler() { + const redwoodProjectPaths = getPaths() + + const tasks = new Listr( + [ + { + title: 'Creating the dataMigrations directory...', + task() { + fs.outputFileSync( + path.join(redwoodProjectPaths.api.dataMigrations, '.keep'), + '' + ) + }, + }, + { + title: 'Adding the RW_DataMigration model to schema.prisma...', + task() { + const dbSchemaFilePath = redwoodProjectPaths.api.dbSchema + const dbSchemaFileContent = fs.readFileSync(dbSchemaFilePath, 'utf-8') + + fs.writeFileSync( + dbSchemaFilePath, + [dbSchemaFileContent.trim(), '', RW_DATA_MIGRATION_MODEL, ''].join( + '\n' + ) + ) + }, + }, + { + title: 'Creating the database migration...', + task() { + return execa.command(createDatabaseMigrationCommand, { + cwd: redwoodProjectPaths.base, + }).stdout + }, + }, + ], + // Not sure why, but the renderer here really matters to Jest, and 'verbose' + // is the only one I've found to work. So we set it just during testing. + { + renderer: process.env.NODE_ENV === 'test' ? 'verbose' : 'default', + } + ) + + try { + await tasks.run() + console.log(notes) + } catch (e) { + process.exitCode = 1 + console.error(c.error((e as Error).message)) + } +} + +export const RW_DATA_MIGRATION_MODEL = `\ +model RW_DataMigration { + version String @id + name String + startedAt DateTime + finishedAt DateTime +}` + +export const createDatabaseMigrationCommand = + 'yarn rw prisma migrate dev --name create_data_migrations --create-only' + +export const notes = [ + '', + c.warning("Don't forget to apply the migration when you're ready:"), + '', + ` ${c.bold('yarn rw prisma migrate dev')}`, + '', +].join('\n') diff --git a/packages/cli-packages/dataMigrate/src/commands/up.ts b/packages/cli-packages/dataMigrate/src/commands/up.ts new file mode 100644 index 000000000000..314082e0eb52 --- /dev/null +++ b/packages/cli-packages/dataMigrate/src/commands/up.ts @@ -0,0 +1,37 @@ +import terminalLink from 'terminal-link' +import type { Argv } from 'yargs' + +import { getPaths } from '@redwoodjs/project-config' + +import type { DataMigrateUpOptions } from '../types' + +export const command = 'up' +export const description = + 'Run any outstanding Data Migrations against the database' + +export function builder(yargs: Argv): Argv { + return yargs + .option('import-db-client-from-dist', { + type: 'boolean', + alias: ['db-from-dist'], + description: 'Import the db client from dist', + default: false, + }) + .option('dist-path', { + type: 'string', + alias: 'd', + description: 'Path to the api dist directory', + default: getPaths().api.dist, + }) + .epilogue( + `Also see the ${terminalLink( + 'Redwood CLI Reference', + 'https://redwoodjs.com/docs/cli-commands#datamigrate-up' + )}` + ) +} + +export async function handler(options: DataMigrateUpOptions): Promise { + const { handler: dataMigrateUpHandler } = await import('./upHandler.js') + await dataMigrateUpHandler(options) +} diff --git a/packages/cli-packages/dataMigrate/src/commands/upHandler.ts b/packages/cli-packages/dataMigrate/src/commands/upHandler.ts new file mode 100644 index 000000000000..732648ec4257 --- /dev/null +++ b/packages/cli-packages/dataMigrate/src/commands/upHandler.ts @@ -0,0 +1,239 @@ +import fs from 'fs' +import path from 'path' + +import type { PrismaClient } from '@prisma/client' +import { Listr } from 'listr2' + +import { registerApiSideBabelHook } from '@redwoodjs/babel-config' +import { getPaths } from '@redwoodjs/project-config' + +import c from '../lib/colors' +import type { DataMigrateUpOptions, DataMigration } from '../types' + +export async function handler({ + importDbClientFromDist, + distPath, +}: DataMigrateUpOptions) { + let db: any + let requireHookRegistered = false + + if (importDbClientFromDist) { + if (!fs.existsSync(distPath)) { + console.warn( + `Can't find api dist at ${distPath}. You may need to build first: yarn rw build api` + ) + process.exitCode = 1 + return + } + + const distLibDbPath = path.join(distPath, 'lib', 'db.js') + + if (!fs.existsSync(distLibDbPath)) { + console.error( + `Can't find db.js at ${distLibDbPath}. Redwood expects the db.js file to be in the ${path.join( + distPath, + 'lib' + )} directory` + ) + process.exitCode = 1 + return + } + + db = (await import(distLibDbPath)).db + } else { + registerApiSideBabelHook() + requireHookRegistered = true + + db = require(path.join(getPaths().api.lib, 'db')).db + } + + const pendingDataMigrations = await getPendingDataMigrations(db) + + if (!pendingDataMigrations.length) { + console.info(c.green(`\n${NO_PENDING_MIGRATIONS_MESSAGE}\n`)) + process.exitCode = 0 + return + } + + const counters = { run: 0, skipped: 0, error: 0 } + + const dataMigrationTasks = pendingDataMigrations.map((dataMigration) => { + const dataMigrationName = path.basename(dataMigration.path, '.js') + + return { + title: dataMigrationName, + skip() { + if (counters.error > 0) { + counters.skipped++ + return true + } else { + return false + } + }, + async task() { + if (!requireHookRegistered) { + registerApiSideBabelHook() + } + + try { + const { startedAt, finishedAt } = await runDataMigration( + db, + dataMigration.path + ) + counters.run++ + await recordDataMigration(db, { + version: dataMigration.version, + name: dataMigrationName, + startedAt, + finishedAt, + }) + } catch (e) { + counters.error++ + console.error( + c.error(`Error in data migration: ${(e as Error).message}`) + ) + } + }, + } + }) + + const tasks = new Listr(dataMigrationTasks, { + renderer: 'verbose', + }) + + try { + await tasks.run() + await db.$disconnect() + + console.log() + reportDataMigrations(counters) + console.log() + + if (counters.error) { + process.exitCode = 1 + } + } catch (e) { + process.exitCode = 1 + await db.$disconnect() + + console.log() + reportDataMigrations(counters) + console.log() + } +} + +/** + * Return the list of migrations that haven't run against the database yet + */ +async function getPendingDataMigrations(db: PrismaClient) { + const dataMigrationsPath = getPaths().api.dataMigrations + + if (!fs.existsSync(dataMigrationsPath)) { + return [] + } + + const dataMigrations = fs + .readdirSync(dataMigrationsPath) + // There may be a `.keep` file in the data migrations directory. + .filter((dataMigrationFileName) => + ['js', '.ts'].some((extension) => + dataMigrationFileName.endsWith(extension) + ) + ) + .map((dataMigrationFileName) => { + const [version] = dataMigrationFileName.split('-') + + return { + version, + path: path.join(dataMigrationsPath, dataMigrationFileName), + } + }) + + const ranDataMigrations: DataMigration[] = await db.rW_DataMigration.findMany( + { + orderBy: { version: 'asc' }, + } + ) + const ranDataMigrationVersions = ranDataMigrations.map((dataMigration) => + dataMigration.version.toString() + ) + + const pendingDataMigrations = dataMigrations + .filter(({ version }) => { + return !ranDataMigrationVersions.includes(version) + }) + .sort(sortDataMigrationsByVersion) + + return pendingDataMigrations +} + +/** + * Sorts migrations by date, oldest first + */ +function sortDataMigrationsByVersion( + dataMigrationA: { version: string }, + dataMigrationB: { version: string } +) { + const aVersion = parseInt(dataMigrationA.version) + const bVersion = parseInt(dataMigrationB.version) + + if (aVersion > bVersion) { + return 1 + } + if (aVersion < bVersion) { + return -1 + } + return 0 +} + +async function runDataMigration(db: PrismaClient, dataMigrationPath: string) { + const dataMigration = require(dataMigrationPath) + + const startedAt = new Date() + await dataMigration.default({ db }) + const finishedAt = new Date() + + return { startedAt, finishedAt } +} + +export const NO_PENDING_MIGRATIONS_MESSAGE = + 'No pending data migrations run, already up-to-date.' + +/** + * Adds data for completed migrations to the DB + */ +async function recordDataMigration( + db: PrismaClient, + { version, name, startedAt, finishedAt }: DataMigration +) { + await db.rW_DataMigration.create({ + data: { version, name, startedAt, finishedAt }, + }) +} + +/** + * Output run status to the console + */ +function reportDataMigrations(counters: { + run: number + skipped: number + error: number +}) { + if (counters.run) { + console.info( + c.green(`${counters.run} data migration(s) completed successfully.`) + ) + } + if (counters.error) { + console.error( + c.error(`${counters.error} data migration(s) exited with errors.`) + ) + } + if (counters.skipped) { + console.warn( + c.warning( + `${counters.skipped} data migration(s) skipped due to previous error.` + ) + ) + } +} diff --git a/packages/cli-packages/dataMigrate/src/index.ts b/packages/cli-packages/dataMigrate/src/index.ts new file mode 100644 index 000000000000..b01fcdc32622 --- /dev/null +++ b/packages/cli-packages/dataMigrate/src/index.ts @@ -0,0 +1,41 @@ +import terminalLink from 'terminal-link' +import type { Argv } from 'yargs' + +import { + command as installCommand, + description as installDescription, + builder as installBuilder, + handler as installHandler, +} from './commands/install' +import { + command as upCommand, + description as upDescription, + builder as upBuilder, + handler as upHandler, +} from './commands/up' + +const command = 'data-migrate ' +const aliases = ['dataMigrate', 'dm'] +const description = 'Migrate the data in your database' + +function builder(yargs: Argv) { + yargs + .command(installCommand, installDescription, installBuilder, installHandler) + // @ts-expect-error not sure; this is a valid signature + .command(upCommand, upDescription, upBuilder, upHandler) + .epilogue( + `Also see the ${terminalLink( + 'Redwood CLI Reference', + 'https://redwoodjs.com/docs/cli-commands#datamigrate' + )}` + ) +} + +export const commands = [ + { + command, + aliases, + description, + builder, + }, +] diff --git a/packages/cli-packages/dataMigrate/src/lib/colors.ts b/packages/cli-packages/dataMigrate/src/lib/colors.ts new file mode 100644 index 000000000000..b32f9980d5c3 --- /dev/null +++ b/packages/cli-packages/dataMigrate/src/lib/colors.ts @@ -0,0 +1,11 @@ +// This file will eventually be deduplicated across the framework +// when we take the time to make architectural changes. + +import chalk from 'chalk' + +export default { + error: chalk.bold.red, + warning: chalk.keyword('orange'), + green: chalk.green, + bold: chalk.bold, +} diff --git a/packages/cli-packages/dataMigrate/src/types.ts b/packages/cli-packages/dataMigrate/src/types.ts new file mode 100644 index 000000000000..e0da431119a3 --- /dev/null +++ b/packages/cli-packages/dataMigrate/src/types.ts @@ -0,0 +1,11 @@ +export type DataMigrateUpOptions = { + importDbClientFromDist: boolean + distPath: string +} + +export type DataMigration = { + version: string + name: string + startedAt: Date + finishedAt: Date +} diff --git a/packages/cli-packages/dataMigrate/tsconfig.json b/packages/cli-packages/dataMigrate/tsconfig.json new file mode 100644 index 000000000000..96510ef2be8c --- /dev/null +++ b/packages/cli-packages/dataMigrate/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../../tsconfig.compilerOption.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src"], + "references": [ + { "path": "../../babel-config" }, + { "path": "../../project-config" } + ] +} diff --git a/packages/cli-packages/storybook/README.md b/packages/cli-packages/storybook/README.md new file mode 100644 index 000000000000..6c3b4e496888 --- /dev/null +++ b/packages/cli-packages/storybook/README.md @@ -0,0 +1,21 @@ +# CLI Packages - Storybook + +**WIP**: This package is the first example of extracting a command from `@redwoodjs/cli` into it's own CLI plugin package. + + + + + +## Dependency graphs + +### src + +![src](./dependencyGraph.src.svg) + +### dist + +![dist](./dependencyGraph.dist.svg) diff --git a/packages/cli-packages/storybook/build.mjs b/packages/cli-packages/storybook/build.mjs new file mode 100644 index 000000000000..f173e7ab9024 --- /dev/null +++ b/packages/cli-packages/storybook/build.mjs @@ -0,0 +1,25 @@ +import fs from 'node:fs' + +import * as esbuild from 'esbuild' +import fg from 'fast-glob' + +// Get source files +const sourceFiles = fg.sync(['./src/**/*.ts']) + +// Build general source files +const result = await esbuild.build({ + entryPoints: sourceFiles, + outdir: 'dist', + + format: 'cjs', + platform: 'node', + target: ['node20'], + + logLevel: 'info', + + // For visualizing dist. + // See https://esbuild.github.io/api/#metafile and https://esbuild.github.io/analyze/. + metafile: true, +}) + +fs.writeFileSync('meta.json', JSON.stringify(result.metafile, null, 2)) diff --git a/packages/cli-packages/storybook/dependencyGraph.dist.svg b/packages/cli-packages/storybook/dependencyGraph.dist.svg new file mode 100644 index 000000000000..4caa2cb553d4 --- /dev/null +++ b/packages/cli-packages/storybook/dependencyGraph.dist.svg @@ -0,0 +1,1080 @@ + + + + + + +dependency-cruiser output + + +cluster_node_modules + +node_modules + + +cluster_node_modules/@babel + +@babel + + +cluster_node_modules/@iarna + +@iarna + + +cluster_node_modules/@opentelemetry + +@opentelemetry + + +cluster_packages + +packages + + +cluster_packages/project-config + +project-config + + +cluster_packages/project-config/dist + +dist + + +cluster_packages/telemetry + +telemetry + + +cluster_packages/telemetry/dist + +dist + + +cluster_packages/cli-helpers + +cli-helpers + + +cluster_packages/cli-helpers/dist + +dist + + +cluster_packages/cli-helpers/dist/telemetry + +telemetry + + +cluster_packages/cli-helpers/dist/auth + +auth + + +cluster_packages/cli-helpers/dist/lib + +lib + + +cluster_packages/cli-packages + +cli-packages + + +cluster_packages/cli-packages/storybook + +storybook + + +cluster_packages/cli-packages/storybook/dist + +dist + + +cluster_packages/cli-packages/storybook/dist/commands + +commands + + +cluster_packages/cli-packages/storybook/dist/lib + +lib + + + +child_process + + +child_process + + + + + +fs + + +fs + + + + + +node_modules/@babel/core + + + + + +core + + + + + +node_modules/@babel/runtime-corejs3 + + + + + +runtime-corejs3 + + + + + +node_modules/@iarna/toml + + + + + +toml + + + + + +node_modules/@opentelemetry/api + + + + + +api + + + + + +node_modules/chalk + + + + + +chalk + + + + + +node_modules/core-js + + + + + +core-js + + + + + +node_modules/deepmerge + + + + + +deepmerge + + + + + +node_modules/execa + + + + + +execa + + + + + +node_modules/fast-glob + + + + + +fast-glob + + + + + +node_modules/listr2 + + + + + +listr2 + + + + + +node_modules/pascalcase + + + + + +pascalcase + + + + + +node_modules/prettier + + + + + +prettier + + + + + +node_modules/string-env-interpolation + + + + + +string-env-interpolation + + + + + +node_modules/terminal-link + + + + + +terminal-link + + + + + +node_modules/yargs + + + + + +yargs + + + + + +os + + +os + + + + + +packages/cli-helpers/dist/auth/authFiles.js + + +authFiles.js +94% + + + + + +packages/cli-helpers/dist/auth/authFiles.js->fs + + + + + +packages/cli-helpers/dist/auth/authFiles.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/auth/authFiles.js->node_modules/pascalcase + + + + + +packages/cli-helpers/dist/lib/index.js + + +index.js +80% + + + + + +packages/cli-helpers/dist/auth/authFiles.js->packages/cli-helpers/dist/lib/index.js + + + + + +packages/cli-helpers/dist/lib/paths.js + + +paths.js +33% + + + + + +packages/cli-helpers/dist/auth/authFiles.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/lib/project.js + + +project.js +75% + + + + + +packages/cli-helpers/dist/auth/authFiles.js->packages/cli-helpers/dist/lib/project.js + + + + + +path + + +path + + + + + +packages/cli-helpers/dist/auth/authFiles.js->path + + + + + +packages/cli-helpers/dist/lib/index.js->fs + + + + + +packages/cli-helpers/dist/lib/index.js->node_modules/@babel/core + + + + + +packages/cli-helpers/dist/lib/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/index.js->node_modules/listr2 + + + + + +packages/cli-helpers/dist/lib/index.js->node_modules/prettier + + + + + +packages/cli-helpers/dist/lib/index.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/lib/index.js->path + + + + + +packages/cli-helpers/dist/lib/colors.js + + +colors.js +33% + + + + + +packages/cli-helpers/dist/lib/index.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/lib/paths.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/paths.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/project-config/dist/index.js + + +index.js +55% + + + + + +packages/cli-helpers/dist/lib/paths.js->packages/project-config/dist/index.js + + + + + +packages/cli-helpers/dist/lib/project.js->fs + + + + + +packages/cli-helpers/dist/lib/project.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/project.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/lib/project.js->path + + + + + +packages/cli-helpers/dist/lib/project.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/lib/project.js->packages/project-config/dist/index.js + + + + + +packages/cli-helpers/package.json + + +package.json +0% + + + + + +packages/cli-helpers/dist/lib/project.js->packages/cli-helpers/package.json + + + + + +packages/cli-helpers/dist/auth/authTasks.js + + +authTasks.js +95% + + + + + +packages/cli-helpers/dist/auth/authTasks.js->fs + + + + + +packages/cli-helpers/dist/auth/authTasks.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/auth/authTasks.js->node_modules/core-js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/auth/authFiles.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/lib/index.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/lib/project.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->path + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/project-config/dist/index.js + + + + + +packages/cli-helpers/dist/lib/colors.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/colors.js->node_modules/chalk + + + + + +packages/project-config/dist/index.js->fs + + + + + +packages/project-config/dist/index.js->node_modules/@iarna/toml + + + + + +packages/project-config/dist/index.js->node_modules/deepmerge + + + + + +packages/project-config/dist/index.js->node_modules/fast-glob + + + + + +packages/project-config/dist/index.js->node_modules/string-env-interpolation + + + + + +packages/project-config/dist/index.js->path + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js + + +setupHelpers.js +91% + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->node_modules/core-js + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->node_modules/listr2 + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->node_modules/terminal-link + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->packages/cli-helpers/dist/auth/authTasks.js + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/lib/installHelpers.js + + +installHelpers.js +67% + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->packages/cli-helpers/dist/lib/installHelpers.js + + + + + +packages/telemetry/dist/index.js + + +index.js +67% + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->packages/telemetry/dist/index.js + + + + + +packages/cli-helpers/dist/lib/installHelpers.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/installHelpers.js->node_modules/execa + + + + + +packages/cli-helpers/dist/lib/installHelpers.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/telemetry/dist/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/telemetry/dist/telemetry.js + + +telemetry.js +88% + + + + + +packages/telemetry/dist/index.js->packages/telemetry/dist/telemetry.js + + + + + +packages/cli-helpers/dist/index.js + + +index.js +91% + + + + + +packages/cli-helpers/dist/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/index.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/project.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/auth/setupHelpers.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/installHelpers.js + + + + + +packages/cli-helpers/dist/telemetry/index.js + + +index.js +83% + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/telemetry/index.js + + + + + +packages/cli-helpers/dist/telemetry/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/telemetry/index.js->node_modules/@opentelemetry/api + + + + + +packages/cli-packages/storybook/dist/commands/storybook.d.ts + + +storybook.d.ts +100% + + + + + +packages/cli-packages/storybook/dist/commands/storybook.d.ts->node_modules/yargs + + + + + +packages/cli-packages/storybook/dist/types.js + + +types.js +0% + + + + + +packages/cli-packages/storybook/dist/commands/storybook.d.ts->packages/cli-packages/storybook/dist/types.js + + + + + +packages/cli-packages/storybook/dist/commands/storybook.js + + +storybook.js +60% + + + + + +packages/cli-packages/storybook/dist/commands/storybook.js->node_modules/terminal-link + + + + + +packages/cli-packages/storybook/dist/commands/storybook.js->packages/cli-helpers/dist/index.js + + + + + +packages/cli-packages/storybook/dist/commands/storybookHandler.js + + +storybookHandler.js +83% + + + + + +packages/cli-packages/storybook/dist/commands/storybook.js->packages/cli-packages/storybook/dist/commands/storybookHandler.js + + + + + +packages/cli-packages/storybook/dist/commands/storybookHandler.js->node_modules/execa + + + + + +packages/cli-packages/storybook/dist/commands/storybookHandler.js->path + + + + + +packages/cli-packages/storybook/dist/commands/storybookHandler.js->packages/project-config/dist/index.js + + + + + +packages/cli-packages/storybook/dist/commands/storybookHandler.js->packages/telemetry/dist/index.js + + + + + +packages/cli-packages/storybook/dist/lib/colors.js + + +colors.js +50% + + + + + +packages/cli-packages/storybook/dist/commands/storybookHandler.js->packages/cli-packages/storybook/dist/lib/colors.js + + + + + +packages/cli-packages/storybook/dist/commands/storybookHandler.d.ts + + +storybookHandler.d.ts +100% + + + + + +packages/cli-packages/storybook/dist/commands/storybookHandler.d.ts->packages/cli-packages/storybook/dist/types.js + + + + + +packages/cli-packages/storybook/dist/lib/colors.js->node_modules/chalk + + + + + +packages/cli-packages/storybook/dist/index.d.ts + + +index.d.ts +100% + + + + + +packages/cli-packages/storybook/dist/index.d.ts->packages/cli-packages/storybook/dist/commands/storybook.js + + + + + +packages/cli-packages/storybook/dist/index.js + + +index.js +100% + + + + + +packages/cli-packages/storybook/dist/index.js->packages/cli-packages/storybook/dist/commands/storybook.js + + + + + +packages/cli-packages/storybook/dist/lib/colors.d.ts + + +colors.d.ts +100% + + + + + +packages/cli-packages/storybook/dist/lib/colors.d.ts->node_modules/chalk + + + + + +packages/cli-packages/storybook/dist/types.d.ts + + +types.d.ts +0% + + + + + +packages/telemetry/dist/telemetry.js->child_process + + + + + +packages/telemetry/dist/telemetry.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/telemetry/dist/telemetry.js->os + + + + + +packages/telemetry/dist/telemetry.js->path + + + + + +packages/telemetry/dist/telemetry.js->packages/project-config/dist/index.js + + + + + diff --git a/packages/cli-packages/storybook/dependencyGraph.src.svg b/packages/cli-packages/storybook/dependencyGraph.src.svg new file mode 100644 index 000000000000..903d3717fba0 --- /dev/null +++ b/packages/cli-packages/storybook/dependencyGraph.src.svg @@ -0,0 +1,1018 @@ + + + + + + +dependency-cruiser output + + +cluster_node_modules + +node_modules + + +cluster_node_modules/@babel + +@babel + + +cluster_node_modules/@iarna + +@iarna + + +cluster_node_modules/@opentelemetry + +@opentelemetry + + +cluster_packages + +packages + + +cluster_packages/cli-helpers + +cli-helpers + + +cluster_packages/cli-helpers/dist + +dist + + +cluster_packages/cli-helpers/dist/auth + +auth + + +cluster_packages/cli-helpers/dist/lib + +lib + + +cluster_packages/cli-helpers/dist/telemetry + +telemetry + + +cluster_packages/cli-packages + +cli-packages + + +cluster_packages/cli-packages/storybook + +storybook + + +cluster_packages/cli-packages/storybook/src + +src + + +cluster_packages/cli-packages/storybook/src/commands + +commands + + +cluster_packages/cli-packages/storybook/src/lib + +lib + + +cluster_packages/project-config + +project-config + + +cluster_packages/project-config/dist + +dist + + +cluster_packages/telemetry + +telemetry + + +cluster_packages/telemetry/dist + +dist + + + +child_process + + +child_process + + + + + +fs + + +fs + + + + + +node_modules/@babel/core + + + + + +core + + + + + +node_modules/@babel/runtime-corejs3 + + + + + +runtime-corejs3 + + + + + +node_modules/@iarna/toml + + + + + +toml + + + + + +node_modules/@opentelemetry/api + + + + + +api + + + + + +node_modules/chalk + + + + + +chalk + + + + + +node_modules/core-js + + + + + +core-js + + + + + +node_modules/deepmerge + + + + + +deepmerge + + + + + +node_modules/execa + + + + + +execa + + + + + +node_modules/fast-glob + + + + + +fast-glob + + + + + +node_modules/listr2 + + + + + +listr2 + + + + + +node_modules/pascalcase + + + + + +pascalcase + + + + + +node_modules/prettier + + + + + +prettier + + + + + +node_modules/string-env-interpolation + + + + + +string-env-interpolation + + + + + +node_modules/terminal-link + + + + + +terminal-link + + + + + +node_modules/yargs + + + + + +yargs + + + + + +os + + +os + + + + + +packages/cli-helpers/dist/auth/authFiles.js + + +authFiles.js +94% + + + + + +packages/cli-helpers/dist/auth/authFiles.js->fs + + + + + +packages/cli-helpers/dist/auth/authFiles.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/auth/authFiles.js->node_modules/pascalcase + + + + + +packages/cli-helpers/dist/lib/index.js + + +index.js +80% + + + + + +packages/cli-helpers/dist/auth/authFiles.js->packages/cli-helpers/dist/lib/index.js + + + + + +packages/cli-helpers/dist/lib/paths.js + + +paths.js +33% + + + + + +packages/cli-helpers/dist/auth/authFiles.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/lib/project.js + + +project.js +75% + + + + + +packages/cli-helpers/dist/auth/authFiles.js->packages/cli-helpers/dist/lib/project.js + + + + + +path + + +path + + + + + +packages/cli-helpers/dist/auth/authFiles.js->path + + + + + +packages/cli-helpers/dist/lib/index.js->fs + + + + + +packages/cli-helpers/dist/lib/index.js->node_modules/@babel/core + + + + + +packages/cli-helpers/dist/lib/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/index.js->node_modules/listr2 + + + + + +packages/cli-helpers/dist/lib/index.js->node_modules/prettier + + + + + +packages/cli-helpers/dist/lib/index.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/lib/index.js->path + + + + + +packages/cli-helpers/dist/lib/colors.js + + +colors.js +33% + + + + + +packages/cli-helpers/dist/lib/index.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/lib/paths.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/paths.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/project-config/dist/index.js + + +index.js +55% + + + + + +packages/cli-helpers/dist/lib/paths.js->packages/project-config/dist/index.js + + + + + +packages/cli-helpers/dist/lib/project.js->fs + + + + + +packages/cli-helpers/dist/lib/project.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/project.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/lib/project.js->path + + + + + +packages/cli-helpers/dist/lib/project.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/lib/project.js->packages/project-config/dist/index.js + + + + + +packages/cli-helpers/package.json + + +package.json +0% + + + + + +packages/cli-helpers/dist/lib/project.js->packages/cli-helpers/package.json + + + + + +packages/cli-helpers/dist/auth/authTasks.js + + +authTasks.js +95% + + + + + +packages/cli-helpers/dist/auth/authTasks.js->fs + + + + + +packages/cli-helpers/dist/auth/authTasks.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/auth/authTasks.js->node_modules/core-js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/auth/authFiles.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/lib/index.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/lib/project.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->path + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/auth/authTasks.js->packages/project-config/dist/index.js + + + + + +packages/cli-helpers/dist/lib/colors.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/colors.js->node_modules/chalk + + + + + +packages/project-config/dist/index.js->fs + + + + + +packages/project-config/dist/index.js->node_modules/@iarna/toml + + + + + +packages/project-config/dist/index.js->node_modules/deepmerge + + + + + +packages/project-config/dist/index.js->node_modules/fast-glob + + + + + +packages/project-config/dist/index.js->node_modules/string-env-interpolation + + + + + +packages/project-config/dist/index.js->path + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js + + +setupHelpers.js +91% + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->node_modules/core-js + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->node_modules/listr2 + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->node_modules/terminal-link + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->packages/cli-helpers/dist/auth/authTasks.js + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/lib/installHelpers.js + + +installHelpers.js +67% + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->packages/cli-helpers/dist/lib/installHelpers.js + + + + + +packages/telemetry/dist/index.js + + +index.js +67% + + + + + +packages/cli-helpers/dist/auth/setupHelpers.js->packages/telemetry/dist/index.js + + + + + +packages/cli-helpers/dist/lib/installHelpers.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/lib/installHelpers.js->node_modules/execa + + + + + +packages/cli-helpers/dist/lib/installHelpers.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/telemetry/dist/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/telemetry/dist/telemetry.js + + +telemetry.js +88% + + + + + +packages/telemetry/dist/index.js->packages/telemetry/dist/telemetry.js + + + + + +packages/cli-helpers/dist/index.js + + +index.js +91% + + + + + +packages/cli-helpers/dist/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/index.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/paths.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/project.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/colors.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/auth/setupHelpers.js + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/lib/installHelpers.js + + + + + +packages/cli-helpers/dist/telemetry/index.js + + +index.js +83% + + + + + +packages/cli-helpers/dist/index.js->packages/cli-helpers/dist/telemetry/index.js + + + + + +packages/cli-helpers/dist/telemetry/index.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/cli-helpers/dist/telemetry/index.js->node_modules/@opentelemetry/api + + + + + +packages/cli-packages/storybook/src/commands/storybook.ts + + +storybook.ts +83% + + + + + +packages/cli-packages/storybook/src/commands/storybook.ts->node_modules/terminal-link + + + + + +packages/cli-packages/storybook/src/commands/storybook.ts->node_modules/yargs + + + + + +packages/cli-packages/storybook/src/commands/storybook.ts->packages/cli-helpers/dist/index.js + + + + + +packages/cli-packages/storybook/src/types.ts + + +types.ts +0% + + + + + +packages/cli-packages/storybook/src/commands/storybook.ts->packages/cli-packages/storybook/src/types.ts + + + + + +packages/cli-packages/storybook/src/commands/storybookHandler.ts + + +storybookHandler.ts +86% + + + + + +packages/cli-packages/storybook/src/commands/storybook.ts->packages/cli-packages/storybook/src/commands/storybookHandler.ts + + + + + +packages/cli-packages/storybook/src/commands/storybookHandler.ts->node_modules/execa + + + + + +packages/cli-packages/storybook/src/commands/storybookHandler.ts->path + + + + + +packages/cli-packages/storybook/src/commands/storybookHandler.ts->packages/project-config/dist/index.js + + + + + +packages/cli-packages/storybook/src/commands/storybookHandler.ts->packages/telemetry/dist/index.js + + + + + +packages/cli-packages/storybook/src/commands/storybookHandler.ts->packages/cli-packages/storybook/src/types.ts + + + + + +packages/cli-packages/storybook/src/lib/colors.ts + + +colors.ts +50% + + + + + +packages/cli-packages/storybook/src/commands/storybookHandler.ts->packages/cli-packages/storybook/src/lib/colors.ts + + + + + +packages/cli-packages/storybook/src/lib/colors.ts->node_modules/chalk + + + + + +packages/cli-packages/storybook/src/index.ts + + +index.ts +100% + + + + + +packages/cli-packages/storybook/src/index.ts->packages/cli-packages/storybook/src/commands/storybook.ts + + + + + +packages/telemetry/dist/telemetry.js->child_process + + + + + +packages/telemetry/dist/telemetry.js->node_modules/@babel/runtime-corejs3 + + + + + +packages/telemetry/dist/telemetry.js->os + + + + + +packages/telemetry/dist/telemetry.js->path + + + + + +packages/telemetry/dist/telemetry.js->packages/project-config/dist/index.js + + + + + diff --git a/packages/cli-packages/storybook/package.json b/packages/cli-packages/storybook/package.json new file mode 100644 index 000000000000..42bdb0b98acf --- /dev/null +++ b/packages/cli-packages/storybook/package.json @@ -0,0 +1,48 @@ +{ + "name": "@redwoodjs/cli-storybook", + "version": "6.0.7", + "repository": { + "type": "git", + "url": "https://github.com/redwoodjs/redwood.git", + "directory": "packages/cli-packages/storybook" + }, + "license": "MIT", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "yarn node ./build.mjs && yarn build:types", + "build:types": "tsc --build --verbose", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx\" --ignore dist --exec \"yarn build\"", + "prepublishOnly": "NODE_ENV=production yarn build" + }, + "jest": { + "testPathIgnorePatterns": [ + "/dist/" + ] + }, + "dependencies": { + "@redwoodjs/cli-helpers": "6.0.7", + "@redwoodjs/project-config": "6.0.7", + "@redwoodjs/telemetry": "6.0.7", + "@storybook/addon-a11y": "7.6.4", + "@storybook/addon-docs": "7.6.4", + "@storybook/addon-essentials": "7.6.4", + "@storybook/react-webpack5": "7.6.4", + "chalk": "4.1.2", + "execa": "5.1.1", + "storybook": "7.6.4", + "terminal-link": "2.1.1", + "yargs": "17.7.2" + }, + "devDependencies": { + "@types/yargs": "17.0.32", + "esbuild": "0.19.9", + "fast-glob": "3.3.2", + "jest": "29.7.0", + "typescript": "5.3.3" + }, + "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" +} diff --git a/packages/cli-packages/storybook/src/commands/storybook.ts b/packages/cli-packages/storybook/src/commands/storybook.ts new file mode 100644 index 000000000000..50a6285ccb68 --- /dev/null +++ b/packages/cli-packages/storybook/src/commands/storybook.ts @@ -0,0 +1,82 @@ +import terminalLink from 'terminal-link' +import type { Argv } from 'yargs' + +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + +import type { StorybookYargsOptions } from '../types' + +export const command = 'storybook' +export const aliases = ['sb'] +export const description = + 'Launch Storybook: a tool for building UI components and pages in isolation' + +export const defaultOptions: StorybookYargsOptions = { + open: true, + build: false, + ci: false, + port: 7910, + buildDirectory: 'public/storybook', + smokeTest: false, +} + +export function builder( + yargs: Argv +): Argv { + return yargs + .option('build', { + describe: 'Build Storybook', + type: 'boolean', + default: defaultOptions.build, + }) + .option('build-directory', { + describe: 'Directory in web/ to store static files', + type: 'string', + default: defaultOptions.buildDirectory, + }) + .option('ci', { + describe: 'Start server in CI mode, with no interactive prompts', + type: 'boolean', + default: defaultOptions.ci, + }) + .option('open', { + describe: 'Open storybook in your browser on start', + type: 'boolean', + default: defaultOptions.open, + }) + .option('port', { + describe: 'Which port to run storybook on', + type: 'number', + default: defaultOptions.port, + }) + .option('smoke-test', { + describe: + "CI mode plus smoke-test (skip prompts; don't open browser; exit after successful start)", + type: 'boolean', + default: defaultOptions.smokeTest, + }) + + .epilogue( + `Also see the ${terminalLink( + 'Redwood CLI Reference', + 'https://redwoodjs.com/docs/cli-commands#storybook' + )}` + ) +} + +export async function handler(options: StorybookYargsOptions): Promise { + // NOTE: We should provide some visual output before the import to increase + // the perceived performance of the command as there will be delay while we + // load the handler. + recordTelemetryAttributes({ + command: 'storybook', + build: options.build, + ci: options.ci, + open: options.open, + smokeTest: options.smokeTest, + }) + // @ts-expect-error - Custom workaround for storybook telemetry + process.emit('shutdown-telemetry') + + const { handler: storybookHandler } = await import('./storybookHandler.js') + await storybookHandler(options) +} diff --git a/packages/cli-packages/storybook/src/commands/storybookHandler.ts b/packages/cli-packages/storybook/src/commands/storybookHandler.ts new file mode 100644 index 000000000000..9433cb62080a --- /dev/null +++ b/packages/cli-packages/storybook/src/commands/storybookHandler.ts @@ -0,0 +1,107 @@ +import fs from 'node:fs' +import path from 'node:path' + +import type { ExecaError } from 'execa' +import execa from 'execa' + +import { getPaths } from '@redwoodjs/project-config' +// Allow import of untyped package +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import { errorTelemetry } from '@redwoodjs/telemetry' + +import c from '../lib/colors' +import type { StorybookYargsOptions } from '../types' + +export async function handler({ + build, + buildDirectory, + ci, + open, + port, + smokeTest, +}: StorybookYargsOptions) { + // We add a stub file to type generation because users don't have Storybook + // installed when they first start a project. We need to remove the file once + // they install Storybook so that the real types come through. + fs.rmSync( + path.join(getPaths().generated.types.includes, 'web-storybook.d.ts'), + { force: true } + ) + + // Check for conflicting options + if (build && smokeTest) { + throw new Error('Can not provide both "--build" and "--smoke-test"') + } + + if (build && open) { + console.warn( + c.warning( + 'Warning: --open option has no effect when running Storybook build' + ) + ) + } + + const cwd = getPaths().web.base + const staticAssetsFolder = path.join(cwd, 'public') + const execaOptions: Partial = { + stdio: 'inherit', + shell: true, + cwd, + } + + // Create the `MockServiceWorker.js` file. See https://mswjs.io/docs/cli/init. + await execa.command( + `yarn msw init "${staticAssetsFolder}" --no-save`, + execaOptions + ) + + const storybookConfigPath = path.dirname( + require.resolve('@redwoodjs/testing/config/storybook/main.js') + ) + + let command = '' + const flags = [ + `--config-dir "${storybookConfigPath}"`, + '--webpack-stats-json', + ] + + if (build) { + command = `yarn storybook build ${[ + ...flags, + `--output-dir "${buildDirectory}"`, + ] + .filter(Boolean) + .join(' ')}` + } else if (smokeTest) { + command = `yarn storybook dev ${[ + ...flags, + `--port ${port}`, + `--smoke-test`, + `--ci`, + `--no-version-updates`, + ] + .filter(Boolean) + .join(' ')}` + } else { + command = `yarn storybook dev ${[ + ...flags, + `--port ${port}`, + `--no-version-updates`, + ci && '--ci', + !open && `--no-open`, + ] + .filter(Boolean) + .join(' ')}` + } + + try { + await execa.command(command, execaOptions) + } catch (e) { + if ((e as ExecaError).signal !== 'SIGINT') { + console.log(c.error((e as Error).message)) + errorTelemetry(process.argv, (e as Error).message) + } + process.exit((e as ExecaError).exitCode ?? 1) + } +} diff --git a/packages/cli-packages/storybook/src/index.ts b/packages/cli-packages/storybook/src/index.ts new file mode 100644 index 000000000000..3706aa9c55f5 --- /dev/null +++ b/packages/cli-packages/storybook/src/index.ts @@ -0,0 +1,17 @@ +import { + command, + aliases, + description, + builder, + handler, +} from './commands/storybook' + +export const commands = [ + { + command, + aliases, + description, + builder, + handler, + }, +] diff --git a/packages/cli-packages/storybook/src/lib/colors.ts b/packages/cli-packages/storybook/src/lib/colors.ts new file mode 100644 index 000000000000..e6abe0edcdbf --- /dev/null +++ b/packages/cli-packages/storybook/src/lib/colors.ts @@ -0,0 +1,21 @@ +import chalk from 'chalk' + +/** + * To keep a consistent color/style palette between cli packages, such as + * \@redwood/cli and \@redwood/create-redwood-app, please keep them compatible + * with one and another. We'll might split up and refactor these into a + * separate package when there is a strong motivation behind it. + * + * Current files: + * + * - packages/cli/src/lib/colors.js (this file) + * - packages/create-redwood-app/src/create-redwood-app.js + */ +export default { + error: chalk.bold.red, + warning: chalk.keyword('orange'), + green: chalk.green, + info: chalk.grey, + bold: chalk.bold, + underline: chalk.underline, +} diff --git a/packages/cli-packages/storybook/src/types.ts b/packages/cli-packages/storybook/src/types.ts new file mode 100644 index 000000000000..505798eabbe4 --- /dev/null +++ b/packages/cli-packages/storybook/src/types.ts @@ -0,0 +1,8 @@ +export interface StorybookYargsOptions { + open: boolean + build: boolean + ci: boolean + port: number + buildDirectory: string + smokeTest: boolean +} diff --git a/packages/cli-packages/storybook/tsconfig.json b/packages/cli-packages/storybook/tsconfig.json new file mode 100644 index 000000000000..9625d97d662f --- /dev/null +++ b/packages/cli-packages/storybook/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig.compilerOption.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src"], +} diff --git a/packages/cli/README.md b/packages/cli/README.md index e3f53d5f7827..7659bf11f13c 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -111,6 +111,10 @@ cd packages/cli yarn dev ``` +> **Note:** If you are implementing a command and wish to run via `yarn dev ` be sure to `yarn build:clean-dist` beforehand to run the latest version. + +> **Important:** If your command alters the `example-todo-main` project (adds a package, modifies redwood.toml, etc) be sure not to commit and push those changes as part of your PR. + ### Best Practices There's a few best practices we follow that you should be aware of: @@ -560,7 +564,7 @@ We'll use the [Netlify Identity](https://github.com/redwoodjs/redwood/blob/main/ ```javascript // ./src/commands/setup/auth/providers/netlify.js -// the lines that need to be added to App.{js,tsx} +// the lines that need to be added to App.{jsx,tsx} export const config = { imports: [ `import netlifyIdentity from 'netlify-identity-widget'`, diff --git a/packages/cli/__mocks__/fs.js b/packages/cli/__mocks__/fs.js index 99fec09d82ed..c7bf119b4118 100644 --- a/packages/cli/__mocks__/fs.js +++ b/packages/cli/__mocks__/fs.js @@ -46,7 +46,11 @@ fs.__getMockFiles = () => { fs.readFileSync = (path) => { // In prisma v4.3.0, prisma format uses a Wasm module. See https://github.com/prisma/prisma/releases/tag/4.3.0. // We shouldn't mock this, so we'll use the real fs.readFileSync. - if (path.includes('prisma_fmt_build_bg.wasm')) { + // Prisma v5.0.0 seems to have added the schema_build Wasm module. + if ( + path.includes('prisma_fmt_build_bg.wasm') || + path.includes('prisma_schema_build_bg.wasm') + ) { return jest.requireActual('fs').readFileSync(path) } diff --git a/packages/cli/package.json b/packages/cli/package.json index b142355e5509..b309dc09267c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@redwoodjs/cli", - "version": "4.0.0", + "version": "6.0.7", "description": "The Redwood Command Line", "repository": { "type": "git", @@ -19,8 +19,8 @@ "scripts": { "build": "yarn build:js", "build:clean-dist": "rimraf 'dist/**/*/__tests__' --glob", - "build:js": "babel src -d dist --extensions \".js,.ts,.tsx\" --copy-files --no-copy-ignored && yarn build:clean-dist", - "build:watch": "nodemon --watch src --ext \"js,ts,tsx,template\" --ignore dist --exec \"yarn build && yarn fix:permissions\"", + "build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored && yarn build:clean-dist", + "build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build && yarn fix:permissions\"", "dev": "RWJS_CWD=../../__fixtures__/example-todo-main node dist/index.js", "fix:permissions": "chmod +x dist/index.js dist/rwfw.js", "prepublishOnly": "yarn build", @@ -28,53 +28,65 @@ "test:watch": "yarn test --watch" }, "dependencies": { - "@babel/runtime-corejs3": "7.21.0", - "@prisma/internals": "4.12.0", - "@redwoodjs/api-server": "4.0.0", - "@redwoodjs/cli-helpers": "4.0.0", - "@redwoodjs/internal": "4.0.0", - "@redwoodjs/prerender": "4.0.0", - "@redwoodjs/project-config": "4.0.0", - "@redwoodjs/structure": "4.0.0", - "@redwoodjs/telemetry": "4.0.0", - "@types/secure-random-password": "0.2.1", + "@babel/runtime-corejs3": "7.23.6", + "@iarna/toml": "2.2.5", + "@opentelemetry/api": "1.7.0", + "@opentelemetry/core": "1.18.1", + "@opentelemetry/exporter-trace-otlp-http": "0.45.1", + "@opentelemetry/resources": "1.18.1", + "@opentelemetry/sdk-trace-node": "1.18.1", + "@opentelemetry/semantic-conventions": "1.18.1", + "@prisma/internals": "5.7.0", + "@redwoodjs/api-server": "6.0.7", + "@redwoodjs/cli-helpers": "6.0.7", + "@redwoodjs/fastify": "6.0.7", + "@redwoodjs/internal": "6.0.7", + "@redwoodjs/prerender": "6.0.7", + "@redwoodjs/project-config": "6.0.7", + "@redwoodjs/structure": "6.0.7", + "@redwoodjs/telemetry": "6.0.7", + "archiver": "6.0.1", "boxen": "5.1.2", "camelcase": "6.3.0", "chalk": "4.1.2", - "concurrently": "8.0.1", + "ci-info": "4.0.0", + "concurrently": "8.2.2", "configstore": "3.1.5", - "core-js": "3.29.1", + "core-js": "3.34.0", "cross-env": "7.0.3", - "crypto-js": "4.1.1", - "decamelize": "5.0.0", + "decamelize": "5.0.1", "dotenv-defaults": "5.0.2", - "envinfo": "7.8.1", + "enquirer": "2.4.1", + "envinfo": "7.11.0", "execa": "5.1.1", - "fast-glob": "3.2.12", - "findup-sync": "5.0.0", - "fs-extra": "11.1.1", + "fast-glob": "3.3.2", + "fs-extra": "11.2.0", + "humanize-string": "2.1.0", "latest-version": "5.1.0", - "listr2": "5.0.8", + "listr2": "6.6.1", "lodash": "4.17.21", "param-case": "3.0.4", "pascalcase": "1.0.0", "pluralize": "8.0.0", "portfinder": "1.0.32", - "prettier": "2.8.7", - "prisma": "4.12.0", + "prettier": "2.8.8", + "prisma": "5.7.0", "prompts": "2.4.2", - "rimraf": "4.4.1", - "secure-random-password": "0.2.3", + "rimraf": "5.0.5", + "semver": "7.5.4", + "string-env-interpolation": "1.0.1", + "systeminformation": "5.21.20", "terminal-link": "2.1.1", "title-case": "3.0.3", - "yargs": "17.7.1" + "uuid": "9.0.1", + "yargs": "17.7.2" }, "devDependencies": { - "@babel/cli": "7.21.0", - "@babel/core": "7.21.3", - "@types/crypto-js": "4.1.1", - "jest": "29.5.0", - "typescript": "5.0.3" + "@babel/cli": "7.23.4", + "@babel/core": "^7.22.20", + "@types/archiver": "^6", + "jest": "29.7.0", + "typescript": "5.3.3" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" } diff --git a/packages/cli/src/__tests__/__snapshots__/plugin.test.js.snap b/packages/cli/src/__tests__/__snapshots__/plugin.test.js.snap new file mode 100644 index 000000000000..b72bcb8b9f18 --- /dev/null +++ b/packages/cli/src/__tests__/__snapshots__/plugin.test.js.snap @@ -0,0 +1,602 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`plugin loading correct loading for @redwoodjs namespace help ('') 1`] = ` +[ + [ + "@redwoodjs/cli-some-package", + { + "some-command": { + "aliases": [ + "sc", + "someCommand", + ], + "description": "Some example command", + }, + }, + ], + [ + "@bluewoodjs/cli-some-package", + { + "third-party": { + "aliases": [ + "tp", + "thirdParty", + ], + "description": "Some third party command", + }, + }, + ], + [ + "@redwoodjs/cli-some-package-not-in-cache", + { + "some-other-command": { + "aliases": [ + "soc", + "someOtherCommand", + ], + "description": "Some example other command", + }, + }, + ], +] +`; + +exports[`plugin loading correct loading for @redwoodjs namespace help ('--help') 1`] = ` +[ + [ + "@redwoodjs/cli-some-package", + { + "some-command": { + "aliases": [ + "sc", + "someCommand", + ], + "description": "Some example command", + }, + }, + ], + [ + "@bluewoodjs/cli-some-package", + { + "third-party": { + "aliases": [ + "tp", + "thirdParty", + ], + "description": "Some third party command", + }, + }, + ], + [ + "@redwoodjs/cli-some-package-not-in-cache", + { + "some-other-command": { + "aliases": [ + "soc", + "someOtherCommand", + ], + "description": "Some example other command", + }, + }, + ], +] +`; + +exports[`plugin loading correct loading for @redwoodjs namespace help ('-h') 1`] = ` +[ + [ + "@redwoodjs/cli-some-package", + { + "some-command": { + "aliases": [ + "sc", + "someCommand", + ], + "description": "Some example command", + }, + }, + ], + [ + "@bluewoodjs/cli-some-package", + { + "third-party": { + "aliases": [ + "tp", + "thirdParty", + ], + "description": "Some third party command", + }, + }, + ], + [ + "@redwoodjs/cli-some-package-not-in-cache", + { + "some-other-command": { + "aliases": [ + "soc", + "someOtherCommand", + ], + "description": "Some example other command", + }, + }, + ], +] +`; + +exports[`plugin loading correct loading for known redwood command (with cache) 1`] = ` +[ + [ + "@redwoodjs/cli-some-package", + { + "some-command": { + "aliases": [ + "sc", + "someCommand", + ], + "description": "Some example command", + }, + }, + ], + [ + "@bluewoodjs/cli-some-package", + { + "third-party": { + "aliases": [ + "tp", + "thirdParty", + ], + "description": "Some third party command", + }, + }, + ], +] +`; + +exports[`plugin loading correct loading for known redwood command (without cache) 1`] = ` +[ + [ + "@redwoodjs/cli-some-package", + { + "some-command": { + "aliases": [ + "sc", + "someCommand", + ], + "description": "Some example command", + }, + }, + ], + [ + "@redwoodjs/cli-some-package-not-in-cache", + { + "some-other-command": { + "aliases": [ + "soc", + "someOtherCommand", + ], + "description": "Some example other command", + }, + }, + ], +] +`; + +exports[`plugin loading correct loading for known third party command (with cache) 1`] = ` +[ + [ + "@redwoodjs/cli-some-package", + { + "some-command": { + "aliases": [ + "sc", + "someCommand", + ], + "description": "Some example command", + }, + }, + ], + [ + "@bluewoodjs/cli-some-package", + { + "third-party": { + "aliases": [ + "tp", + "thirdParty", + ], + "description": "Some third party command", + }, + }, + ], +] +`; + +exports[`plugin loading correct loading for known third party command (without cache) 1`] = ` +[ + [ + "@bluewoodjs/cli-some-package", + { + "third-party": { + "aliases": [ + "tp", + "thirdParty", + ], + "description": "Some third party command", + }, + }, + ], + [ + "@bluewoodjs/cli-some-package-second-example", + { + "third-party-other": { + "aliases": [ + "tpo", + "thirdPartyOther", + ], + "description": "Some other third party command", + }, + }, + ], +] +`; + +exports[`plugin loading correct loading for root help ('') 1`] = ` +[ + [ + "@redwoodjs/cli-some-package", + { + "some-command": { + "aliases": [ + "sc", + "someCommand", + ], + "description": "Some example command", + }, + }, + ], + [ + "@bluewoodjs/cli-some-package", + { + "third-party": { + "aliases": [ + "tp", + "thirdParty", + ], + "description": "Some third party command", + }, + }, + ], + [ + "@redwoodjs/cli-some-package-not-in-cache", + { + "some-other-command": { + "aliases": [ + "soc", + "someOtherCommand", + ], + "description": "Some example other command", + }, + }, + ], +] +`; + +exports[`plugin loading correct loading for root help ('--help') 1`] = ` +[ + [ + "@redwoodjs/cli-some-package", + { + "some-command": { + "aliases": [ + "sc", + "someCommand", + ], + "description": "Some example command", + }, + }, + ], + [ + "@bluewoodjs/cli-some-package", + { + "third-party": { + "aliases": [ + "tp", + "thirdParty", + ], + "description": "Some third party command", + }, + }, + ], + [ + "@redwoodjs/cli-some-package-not-in-cache", + { + "some-other-command": { + "aliases": [ + "soc", + "someOtherCommand", + ], + "description": "Some example other command", + }, + }, + ], +] +`; + +exports[`plugin loading correct loading for root help ('-h') 1`] = ` +[ + [ + "@redwoodjs/cli-some-package", + { + "some-command": { + "aliases": [ + "sc", + "someCommand", + ], + "description": "Some example command", + }, + }, + ], + [ + "@bluewoodjs/cli-some-package", + { + "third-party": { + "aliases": [ + "tp", + "thirdParty", + ], + "description": "Some third party command", + }, + }, + ], + [ + "@redwoodjs/cli-some-package-not-in-cache", + { + "some-other-command": { + "aliases": [ + "soc", + "someOtherCommand", + ], + "description": "Some example other command", + }, + }, + ], +] +`; + +exports[`plugin loading correct loading for third party namespace help ('') 1`] = ` +[ + [ + "@redwoodjs/cli-some-package", + { + "some-command": { + "aliases": [ + "sc", + "someCommand", + ], + "description": "Some example command", + }, + }, + ], + [ + "@bluewoodjs/cli-some-package", + { + "third-party": { + "aliases": [ + "tp", + "thirdParty", + ], + "description": "Some third party command", + }, + }, + ], +] +`; + +exports[`plugin loading correct loading for third party namespace help ('--help') 1`] = ` +[ + [ + "@redwoodjs/cli-some-package", + { + "some-command": { + "aliases": [ + "sc", + "someCommand", + ], + "description": "Some example command", + }, + }, + ], + [ + "@bluewoodjs/cli-some-package", + { + "third-party": { + "aliases": [ + "tp", + "thirdParty", + ], + "description": "Some third party command", + }, + }, + ], +] +`; + +exports[`plugin loading correct loading for third party namespace help ('-h') 1`] = ` +[ + [ + "@redwoodjs/cli-some-package", + { + "some-command": { + "aliases": [ + "sc", + "someCommand", + ], + "description": "Some example command", + }, + }, + ], + [ + "@bluewoodjs/cli-some-package", + { + "third-party": { + "aliases": [ + "tp", + "thirdParty", + ], + "description": "Some third party command", + }, + }, + ], +] +`; + +exports[`plugin loading correct loading for unknown namespace (no command) 1`] = ` +[ + [ + "@redwoodjs/cli-some-package", + { + "some-command": { + "aliases": [ + "sc", + "someCommand", + ], + "description": "Some example command", + }, + }, + ], + [ + "@bluewoodjs/cli-some-package", + { + "third-party": { + "aliases": [ + "tp", + "thirdParty", + ], + "description": "Some third party command", + }, + }, + ], + [ + "@redwoodjs/cli-some-package-not-in-cache", + { + "some-other-command": { + "aliases": [ + "soc", + "someOtherCommand", + ], + "description": "Some example other command", + }, + }, + ], +] +`; + +exports[`plugin loading correct loading for unknown namespace (with command) 1`] = ` +[ + [ + "@redwoodjs/cli-some-package", + { + "some-command": { + "aliases": [ + "sc", + "someCommand", + ], + "description": "Some example command", + }, + }, + ], + [ + "@bluewoodjs/cli-some-package", + { + "third-party": { + "aliases": [ + "tp", + "thirdParty", + ], + "description": "Some third party command", + }, + }, + ], + [ + "@redwoodjs/cli-some-package-not-in-cache", + { + "some-other-command": { + "aliases": [ + "soc", + "someOtherCommand", + ], + "description": "Some example other command", + }, + }, + ], +] +`; + +exports[`plugin loading correct loading for unknown redwood command 1`] = ` +[ + [ + "@redwoodjs/cli-some-package", + { + "some-command": { + "aliases": [ + "sc", + "someCommand", + ], + "description": "Some example command", + }, + }, + ], + [ + "@bluewoodjs/cli-some-package", + { + "third-party": { + "aliases": [ + "tp", + "thirdParty", + ], + "description": "Some third party command", + }, + }, + ], + [ + "@redwoodjs/cli-some-package-not-in-cache", + { + "some-other-command": { + "aliases": [ + "soc", + "someOtherCommand", + ], + "description": "Some example other command", + }, + }, + ], +] +`; + +exports[`plugin loading correct loading for unknown third party command 1`] = ` +[ + [ + "@redwoodjs/cli-some-package", + { + "some-command": { + "aliases": [ + "sc", + "someCommand", + ], + "description": "Some example command", + }, + }, + ], + [ + "@bluewoodjs/cli-some-package", + { + "third-party": { + "aliases": [ + "tp", + "thirdParty", + ], + "description": "Some third party command", + }, + }, + ], +] +`; diff --git a/packages/cli/src/__tests__/fs.test.js b/packages/cli/src/__tests__/fs.test.js index 1352240e62a9..ad793de93484 100644 --- a/packages/cli/src/__tests__/fs.test.js +++ b/packages/cli/src/__tests__/fs.test.js @@ -1,8 +1,9 @@ jest.mock('fs') -import fs from 'fs' import path from 'path' +import fs from 'fs-extra' + const INITIAL_FS = { file_a: 'content_a', [path.join('fake_dir', 'mock_dir', 'made_up_file')]: 'made_up_content', diff --git a/packages/cli/src/__tests__/plugin.test.js b/packages/cli/src/__tests__/plugin.test.js new file mode 100644 index 000000000000..692d098634af --- /dev/null +++ b/packages/cli/src/__tests__/plugin.test.js @@ -0,0 +1,1315 @@ +import fs from 'fs-extra' +import yargs from 'yargs' +import { hideBin } from 'yargs/helpers' + +import { getConfig, getPaths } from '@redwoodjs/project-config' + +import * as pluginLib from '../lib/plugin' +import { loadPlugins } from '../plugin' + +jest.mock('fs') +jest.mock('@redwoodjs/project-config', () => { + return { + getPaths: jest.fn(), + getConfig: jest.fn(), + } +}) +jest.mock('../lib/packages', () => { + return { + installModule: jest.fn(), + isModuleInstalled: jest.fn().mockReturnValue(true), + } +}) + +function getMockYargsInstance() { + return yargs(hideBin(process.argv)) + .scriptName('rw') + .command({ + command: 'built-in', + description: 'Some builtin command', + aliases: ['bi', 'builtIn'], + }) + .exitProcess(false) +} + +describe('command information caching', () => { + beforeEach(() => { + getPaths.mockReturnValue({ + generated: { + base: '', + }, + }) + }) + + test('returns the correct cache when no local cache exists', () => { + const cache = pluginLib.loadCommadCache() + expect(cache).toEqual({ + ...pluginLib.PLUGIN_CACHE_DEFAULT, + _builtin: pluginLib.PLUGIN_CACHE_BUILTIN, + }) + }) + + test('returns the correct cache when a local cache exists', () => { + const anExistingDefaultCacheEntryKey = Object.keys( + pluginLib.PLUGIN_CACHE_DEFAULT + )[0] + const anExistingDefaultCacheEntry = { + [anExistingDefaultCacheEntryKey]: { + ...pluginLib.PLUGIN_CACHE_DEFAULT[anExistingDefaultCacheEntryKey], + description: + 'Mutated description which should be reverted back to default', + }, + } + const exampleCacheEntry = { + '@redwoodjs/cli-some-package': { + 'some-command': { + aliases: ['sc', 'someCommand'], + description: 'Some example command', + }, + }, + } + fs.__setMockFiles({ + ['commandCache.json']: JSON.stringify({ + ...exampleCacheEntry, + ...anExistingDefaultCacheEntry, + }), + }) + + const cache = pluginLib.loadCommadCache() + expect(cache).toEqual({ + ...pluginLib.PLUGIN_CACHE_DEFAULT, + ...exampleCacheEntry, + _builtin: pluginLib.PLUGIN_CACHE_BUILTIN, + }) + }) +}) + +describe('plugin loading', () => { + beforeAll(() => { + jest.spyOn(console, 'log').mockImplementation(() => {}) + }) + + beforeEach(() => { + getPaths.mockReturnValue({ + generated: { + base: '', + }, + }) + + jest.spyOn(pluginLib, 'loadCommadCache') + jest.spyOn(pluginLib, 'loadPluginPackage') + jest.spyOn(pluginLib, 'checkPluginListAndWarn') + jest.spyOn(pluginLib, 'saveCommandCache') + }) + + afterEach(() => { + pluginLib.loadCommadCache.mockRestore() + pluginLib.checkPluginListAndWarn.mockRestore() + pluginLib.loadPluginPackage.mockRestore() + pluginLib.saveCommandCache.mockRestore() + + console.log.mockClear() + }) + + afterAll(() => { + console.log.mockRestore() + }) + + test('no plugins are loaded for --version at the root level', async () => { + const originalArgv = process.argv + process.argv = ['node', 'rw', '--version'] + + const yargsInstance = getMockYargsInstance() + await loadPlugins(yargsInstance) + + expect(pluginLib.loadCommadCache).toHaveBeenCalledTimes(0) + expect(pluginLib.checkPluginListAndWarn).toHaveBeenCalledTimes(0) + expect(pluginLib.loadPluginPackage).toHaveBeenCalledTimes(0) + expect(pluginLib.saveCommandCache).toHaveBeenCalledTimes(0) + + process.argv = originalArgv + }) + + test('no plugins are loaded if it is a built-in command', async () => { + const originalArgv = process.argv + process.argv = ['node', 'rw', pluginLib.PLUGIN_CACHE_BUILTIN[0]] + + getConfig.mockReturnValue({ + experimental: { + cli: { + plugins: [], + autoInstall: true, + }, + }, + }) + + const yargsInstance = getMockYargsInstance() + await loadPlugins(yargsInstance) + + expect(pluginLib.loadCommadCache).toHaveBeenCalledTimes(1) + expect(pluginLib.checkPluginListAndWarn).toHaveBeenCalledTimes(0) + expect(pluginLib.loadPluginPackage).toHaveBeenCalledTimes(0) + expect(pluginLib.saveCommandCache).toHaveBeenCalledTimes(0) + + getConfig.mockRestore() + process.argv = originalArgv + }) + + test.each([['--help'], ['-h'], ['']])( + `correct loading for root help ('%s')`, + async (command) => { + const originalArgv = process.argv + process.argv = ['node', 'rw', command] + + getConfig.mockReturnValue({ + experimental: { + cli: { + plugins: [ + { + package: '@redwoodjs/cli-some-package', + }, + { + package: '@redwoodjs/cli-some-package-not-in-cache', + }, + { + package: '@bluewoodjs/cli-some-package', + }, + ], + autoInstall: true, + }, + }, + }) + jest.mock( + '@redwoodjs/cli-some-package-not-in-cache', + () => { + return { + commands: [ + { + command: 'some-other-command', + description: 'Some example other command', + aliases: ['soc', 'someOtherCommand'], + }, + ], + } + }, + { virtual: true } + ) + fs.__setMockFiles({ + ['commandCache.json']: JSON.stringify({ + '@redwoodjs/cli-some-package': { + 'some-command': { + aliases: ['sc', 'someCommand'], + description: 'Some example command', + }, + }, + '@bluewoodjs/cli-some-package': { + 'third-party': { + aliases: ['tp', 'thirdParty'], + description: 'Some third party command', + }, + }, + }), + }) + + const yargsInstance = getMockYargsInstance() + await loadPlugins(yargsInstance) + + expect(pluginLib.loadCommadCache).toHaveBeenCalledTimes(1) + expect(pluginLib.checkPluginListAndWarn).toHaveBeenCalledTimes(1) + + // Should have loaded the package when it was not in the cache + expect(pluginLib.loadPluginPackage).toHaveBeenCalledTimes(1) + expect(pluginLib.loadPluginPackage).toHaveBeenCalledWith( + '@redwoodjs/cli-some-package-not-in-cache', + undefined, + true + ) + + // Should have saved the cache with the new package + expect(pluginLib.saveCommandCache).toHaveBeenCalledTimes(1) + const knownPlugins = + getConfig.mock.results[0].value.experimental.cli.plugins.map( + (plugin) => plugin.package + ) + const saveCommandCacheArg = Object.entries( + pluginLib.saveCommandCache.mock.calls[0][0] + ).filter(([key]) => knownPlugins.includes(key)) + expect(saveCommandCacheArg).toMatchSnapshot() + + // Rudimentary check that the help output contains the correct commands + const helpOutput = await yargsInstance.getHelp() + expect(helpOutput).toContain('rw built-in') + expect(helpOutput).toContain('Some builtin command') + expect(helpOutput).toContain('[aliases: bi, builtIn]') + expect(helpOutput).toContain('rw some-command') + expect(helpOutput).toContain('Some example command') + expect(helpOutput).toContain('[aliases: sc, someCommand]') + expect(helpOutput).toContain('rw some-other-command') + expect(helpOutput).toContain('Some example other command') + expect(helpOutput).toContain('[aliases: soc, someOtherCommand]') + expect(helpOutput).toContain('rw @bluewoodjs ') + expect(helpOutput).toContain('Commands from @bluewoodjs') + + getConfig.mockRestore() + process.argv = originalArgv + } + ) + + test.each([['--help'], ['-h'], ['']])( + `correct loading for @redwoodjs namespace help ('%s')`, + async (command) => { + const originalArgv = process.argv + process.argv = ['node', 'rw', '@redwoodjs', command] + + getConfig.mockReturnValue({ + experimental: { + cli: { + plugins: [ + { + package: '@redwoodjs/cli-some-package', + }, + { + package: '@redwoodjs/cli-some-package-not-in-cache', + }, + { + package: '@bluewoodjs/cli-some-package', + }, + ], + autoInstall: true, + }, + }, + }) + jest.mock( + '@redwoodjs/cli-some-package-not-in-cache', + () => { + return { + commands: [ + { + command: 'some-other-command', + description: 'Some example other command', + aliases: ['soc', 'someOtherCommand'], + }, + ], + } + }, + { virtual: true } + ) + fs.__setMockFiles({ + ['commandCache.json']: JSON.stringify({ + '@redwoodjs/cli-some-package': { + 'some-command': { + aliases: ['sc', 'someCommand'], + description: 'Some example command', + }, + }, + '@bluewoodjs/cli-some-package': { + 'third-party': { + aliases: ['tp', 'thirdParty'], + description: 'Some third party command', + }, + }, + }), + }) + + const yargsInstance = getMockYargsInstance() + await loadPlugins(yargsInstance) + + expect(pluginLib.loadCommadCache).toHaveBeenCalledTimes(1) + expect(pluginLib.checkPluginListAndWarn).toHaveBeenCalledTimes(1) + + // Should have loaded the package when it was not in the cache + expect(pluginLib.loadPluginPackage).toHaveBeenCalledTimes(1) + expect(pluginLib.loadPluginPackage).toHaveBeenCalledWith( + '@redwoodjs/cli-some-package-not-in-cache', + undefined, + true + ) + + // Should have saved the cache with the new package + expect(pluginLib.saveCommandCache).toHaveBeenCalledTimes(1) + const knownPlugins = + getConfig.mock.results[0].value.experimental.cli.plugins.map( + (plugin) => plugin.package + ) + const saveCommandCacheArg = Object.entries( + pluginLib.saveCommandCache.mock.calls[0][0] + ).filter(([key]) => knownPlugins.includes(key)) + expect(saveCommandCacheArg).toMatchSnapshot() + + // Rudimentary check that the help output contains the correct commands + const helpOutput = await yargsInstance.getHelp() + expect(helpOutput).toContain('rw built-in') + expect(helpOutput).toContain('Some builtin command') + expect(helpOutput).toContain('[aliases: bi, builtIn]') + expect(helpOutput).toContain('rw some-command') + expect(helpOutput).toContain('Some example command') + expect(helpOutput).toContain('[aliases: sc, someCommand]') + expect(helpOutput).toContain('rw some-other-command') + expect(helpOutput).toContain('Some example other command') + expect(helpOutput).toContain('[aliases: soc, someOtherCommand]') + expect(helpOutput).not.toContain('rw @bluewoodjs ') + expect(helpOutput).not.toContain('Commands from @bluewoodjs') + + getConfig.mockRestore() + process.argv = originalArgv + } + ) + + test.each([['--help'], ['-h'], ['']])( + `correct loading for third party namespace help ('%s')`, + async (command) => { + const originalArgv = process.argv + process.argv = ['node', 'rw', '@bluewoodjs', command] + + getConfig.mockReturnValue({ + experimental: { + cli: { + plugins: [ + { + package: '@redwoodjs/cli-some-package', + }, + { + package: '@redwoodjs/cli-some-package-not-in-cache', + }, + { + package: '@bluewoodjs/cli-some-package', + }, + ], + autoInstall: true, + }, + }, + }) + jest.mock( + '@redwoodjs/cli-some-package-not-in-cache', + () => { + return { + commands: [ + { + command: 'some-other-command', + description: 'Some example other command', + aliases: ['soc', 'someOtherCommand'], + }, + ], + } + }, + { virtual: true } + ) + fs.__setMockFiles({ + ['commandCache.json']: JSON.stringify({ + '@redwoodjs/cli-some-package': { + 'some-command': { + aliases: ['sc', 'someCommand'], + description: 'Some example command', + }, + }, + '@bluewoodjs/cli-some-package': { + 'third-party': { + aliases: ['tp', 'thirdParty'], + description: 'Some third party command', + }, + }, + }), + }) + + const yargsInstance = getMockYargsInstance() + await loadPlugins(yargsInstance) + + expect(pluginLib.loadCommadCache).toHaveBeenCalledTimes(1) + expect(pluginLib.checkPluginListAndWarn).toHaveBeenCalledTimes(1) + + // Should have NOT loaded the package when it was not in the cache + // because it is not in the correct namespace + expect(pluginLib.loadPluginPackage).toHaveBeenCalledTimes(0) + + // Should have saved the cache with the new package + expect(pluginLib.saveCommandCache).toHaveBeenCalledTimes(1) + const knownPlugins = + getConfig.mock.results[0].value.experimental.cli.plugins.map( + (plugin) => plugin.package + ) + const saveCommandCacheArg = Object.entries( + pluginLib.saveCommandCache.mock.calls[0][0] + ).filter(([key]) => knownPlugins.includes(key)) + expect(saveCommandCacheArg).toMatchSnapshot() + + // Rudimentary check that the help output contains the correct commands + const helpOutput = await yargsInstance.getHelp() + expect(helpOutput).toContain('rw @bluewoodjs ') + expect(helpOutput).toContain('Commands from @bluewoodjs') + expect(helpOutput).toContain('rw @bluewoodjs third-party') + expect(helpOutput).toContain('Some third party command') + expect(helpOutput).toContain('[aliases: tp, thirdParty]') + + getConfig.mockRestore() + process.argv = originalArgv + } + ) + + test('correct loading for unknown namespace (no command)', async () => { + const originalArgv = process.argv + process.argv = ['node', 'rw', '@greenwoodjs'] + + getConfig.mockReturnValue({ + experimental: { + cli: { + plugins: [ + { + package: '@redwoodjs/cli-some-package', + }, + { + package: '@redwoodjs/cli-some-package-not-in-cache', + }, + { + package: '@bluewoodjs/cli-some-package', + }, + ], + autoInstall: true, + }, + }, + }) + jest.mock( + '@redwoodjs/cli-some-package-not-in-cache', + () => { + return { + commands: [ + { + command: 'some-other-command', + description: 'Some example other command', + aliases: ['soc', 'someOtherCommand'], + }, + ], + } + }, + { virtual: true } + ) + fs.__setMockFiles({ + ['commandCache.json']: JSON.stringify({ + '@redwoodjs/cli-some-package': { + 'some-command': { + aliases: ['sc', 'someCommand'], + description: 'Some example command', + }, + }, + '@bluewoodjs/cli-some-package': { + 'third-party': { + aliases: ['tp', 'thirdParty'], + description: 'Some third party command', + }, + }, + }), + }) + + const yargsInstance = getMockYargsInstance() + await loadPlugins(yargsInstance) + + expect(pluginLib.loadCommadCache).toHaveBeenCalledTimes(1) + expect(pluginLib.checkPluginListAndWarn).toHaveBeenCalledTimes(1) + + // Should have loaded the package when it was not in the cache + expect(pluginLib.loadPluginPackage).toHaveBeenCalledTimes(1) + expect(pluginLib.loadPluginPackage).toHaveBeenCalledWith( + '@redwoodjs/cli-some-package-not-in-cache', + undefined, + true + ) + + // Should have saved the cache with the new package + expect(pluginLib.saveCommandCache).toHaveBeenCalledTimes(1) + const knownPlugins = + getConfig.mock.results[0].value.experimental.cli.plugins.map( + (plugin) => plugin.package + ) + const saveCommandCacheArg = Object.entries( + pluginLib.saveCommandCache.mock.calls[0][0] + ).filter(([key]) => knownPlugins.includes(key)) + expect(saveCommandCacheArg).toMatchSnapshot() + + // Rudimentary check that the help output contains the correct commands + const helpOutput = await yargsInstance.getHelp() + expect(helpOutput).toContain('rw built-in') + expect(helpOutput).toContain('Some builtin command') + expect(helpOutput).toContain('[aliases: bi, builtIn]') + expect(helpOutput).toContain('rw some-command') + expect(helpOutput).toContain('Some example command') + expect(helpOutput).toContain('[aliases: sc, someCommand]') + expect(helpOutput).toContain('rw some-other-command') + expect(helpOutput).toContain('Some example other command') + expect(helpOutput).toContain('[aliases: soc, someOtherCommand]') + expect(helpOutput).toContain('rw @bluewoodjs ') + expect(helpOutput).toContain('Commands from @bluewoodjs') + + getConfig.mockRestore() + process.argv = originalArgv + }) + test('correct loading for unknown namespace (with command)', async () => { + const originalArgv = process.argv + process.argv = ['node', 'rw', '@greenwoodjs', 'anything'] + + getConfig.mockReturnValue({ + experimental: { + cli: { + plugins: [ + { + package: '@redwoodjs/cli-some-package', + }, + { + package: '@redwoodjs/cli-some-package-not-in-cache', + }, + { + package: '@bluewoodjs/cli-some-package', + }, + ], + autoInstall: true, + }, + }, + }) + jest.mock( + '@redwoodjs/cli-some-package-not-in-cache', + () => { + return { + commands: [ + { + command: 'some-other-command', + description: 'Some example other command', + aliases: ['soc', 'someOtherCommand'], + }, + ], + } + }, + { virtual: true } + ) + fs.__setMockFiles({ + ['commandCache.json']: JSON.stringify({ + '@redwoodjs/cli-some-package': { + 'some-command': { + aliases: ['sc', 'someCommand'], + description: 'Some example command', + }, + }, + '@bluewoodjs/cli-some-package': { + 'third-party': { + aliases: ['tp', 'thirdParty'], + description: 'Some third party command', + }, + }, + }), + }) + + const yargsInstance = getMockYargsInstance() + await loadPlugins(yargsInstance) + + expect(pluginLib.loadCommadCache).toHaveBeenCalledTimes(1) + expect(pluginLib.checkPluginListAndWarn).toHaveBeenCalledTimes(1) + + // Should have loaded the package when it was not in the cache + expect(pluginLib.loadPluginPackage).toHaveBeenCalledTimes(1) + expect(pluginLib.loadPluginPackage).toHaveBeenCalledWith( + '@redwoodjs/cli-some-package-not-in-cache', + undefined, + true + ) + + // Should have saved the cache with the new package + expect(pluginLib.saveCommandCache).toHaveBeenCalledTimes(1) + const knownPlugins = + getConfig.mock.results[0].value.experimental.cli.plugins.map( + (plugin) => plugin.package + ) + const saveCommandCacheArg = Object.entries( + pluginLib.saveCommandCache.mock.calls[0][0] + ).filter(([key]) => knownPlugins.includes(key)) + expect(saveCommandCacheArg).toMatchSnapshot() + + // Rudimentary check that the help output contains the correct commands + const helpOutput = await yargsInstance.getHelp() + expect(helpOutput).toContain('rw built-in') + expect(helpOutput).toContain('Some builtin command') + expect(helpOutput).toContain('[aliases: bi, builtIn]') + expect(helpOutput).toContain('rw some-command') + expect(helpOutput).toContain('Some example command') + expect(helpOutput).toContain('[aliases: sc, someCommand]') + expect(helpOutput).toContain('rw some-other-command') + expect(helpOutput).toContain('Some example other command') + expect(helpOutput).toContain('[aliases: soc, someOtherCommand]') + expect(helpOutput).toContain('rw @bluewoodjs ') + expect(helpOutput).toContain('Commands from @bluewoodjs') + + getConfig.mockRestore() + process.argv = originalArgv + }) + + test('correct loading for known redwood command (with cache)', async () => { + const originalArgv = process.argv + process.argv = ['node', 'rw', 'someCommand'] + + getConfig.mockReturnValue({ + experimental: { + cli: { + plugins: [ + { + package: '@redwoodjs/cli-some-package', + }, + { + package: '@redwoodjs/cli-some-package-not-in-cache', + }, + { + package: '@bluewoodjs/cli-some-package', + }, + ], + autoInstall: true, + }, + }, + }) + jest.mock( + '@redwoodjs/cli-some-package-not-in-cache', + () => { + return { + commands: [ + { + command: 'some-other-command', + description: 'Some example other command', + aliases: ['soc', 'someOtherCommand'], + }, + ], + } + }, + { virtual: true } + ) + fs.__setMockFiles({ + ['commandCache.json']: JSON.stringify({ + '@redwoodjs/cli-some-package': { + 'some-command': { + aliases: ['sc', 'someCommand'], + description: 'Some example command', + }, + }, + '@bluewoodjs/cli-some-package': { + 'third-party': { + aliases: ['tp', 'thirdParty'], + description: 'Some third party command', + }, + }, + }), + }) + + pluginLib.loadPluginPackage.mockImplementation((packageName) => { + if (packageName === '@redwoodjs/cli-some-package') { + return { + commands: [ + { + command: 'some-command', + description: 'Some example command', + aliases: ['sc', 'someCommand'], + builder: () => {}, + handler: () => { + console.log('MARKER') + }, + }, + ], + } + } + throw new Error(`Unexpected behaviour: loading ${packageName}`) + }) + + const yargsInstance = getMockYargsInstance() + await loadPlugins(yargsInstance) + + expect(pluginLib.loadCommadCache).toHaveBeenCalledTimes(1) + expect(pluginLib.checkPluginListAndWarn).toHaveBeenCalledTimes(1) + + // Should have loaded the package - only the one we need + expect(pluginLib.loadPluginPackage).toHaveBeenCalledTimes(1) + expect(pluginLib.loadPluginPackage).toHaveBeenCalledWith( + '@redwoodjs/cli-some-package', + undefined, + true + ) + + // Should have saved the cache with the new package + expect(pluginLib.saveCommandCache).toHaveBeenCalledTimes(1) + const knownPlugins = + getConfig.mock.results[0].value.experimental.cli.plugins.map( + (plugin) => plugin.package + ) + const saveCommandCacheArg = Object.entries( + pluginLib.saveCommandCache.mock.calls[0][0] + ).filter(([key]) => knownPlugins.includes(key)) + expect(saveCommandCacheArg).toMatchSnapshot() + + // Rudimentary check that the right handler was invoked + await yargsInstance.parse() + expect(console.log).toHaveBeenCalledWith('MARKER') + + getConfig.mockRestore() + process.argv = originalArgv + }) + test('correct loading for known redwood command (without cache)', async () => { + const originalArgv = process.argv + process.argv = ['node', 'rw', 'someCommand'] + + getConfig.mockReturnValue({ + experimental: { + cli: { + plugins: [ + { + package: '@redwoodjs/cli-some-package', + }, + { + package: '@redwoodjs/cli-some-package-not-in-cache', + }, + { + package: '@bluewoodjs/cli-some-package', + }, + ], + autoInstall: true, + }, + }, + }) + jest.mock( + '@redwoodjs/cli-some-package-not-in-cache', + () => { + return { + commands: [ + { + command: 'some-other-command', + description: 'Some example other command', + aliases: ['soc', 'someOtherCommand'], + }, + ], + } + }, + { virtual: true } + ) + jest.mock( + '@redwoodjs/cli-some-package', + () => { + return { + commands: [ + { + command: 'some-command', + description: 'Some example command', + aliases: ['sc', 'someCommand'], + }, + ], + } + }, + { virtual: true } + ) + fs.__setMockFiles({ + ['commandCache.json']: JSON.stringify({}), + }) + + pluginLib.loadPluginPackage.mockImplementation((packageName) => { + if (packageName === '@redwoodjs/cli-some-package') { + return { + commands: [ + { + command: 'some-command', + description: 'Some example command', + aliases: ['sc', 'someCommand'], + builder: () => {}, + handler: () => { + console.log('MARKER SOME') + }, + }, + ], + } + } + if (packageName === '@redwoodjs/cli-some-package-not-in-cache') { + return { + commands: [ + { + command: 'some-other-command', + description: 'Some example other command', + aliases: ['soc', 'someOtherCommand'], + builder: () => {}, + handler: () => { + console.log('MARKER SOME OTHER') + }, + }, + ], + } + } + throw new Error(`Unexpected behaviour: loading ${packageName}`) + }) + + const yargsInstance = getMockYargsInstance() + await loadPlugins(yargsInstance) + + expect(pluginLib.loadCommadCache).toHaveBeenCalledTimes(1) + expect(pluginLib.checkPluginListAndWarn).toHaveBeenCalledTimes(1) + + // Should have loaded the package - all in the namespace + expect(pluginLib.loadPluginPackage).toHaveBeenCalledTimes(2) + expect(pluginLib.loadPluginPackage).toHaveBeenCalledWith( + '@redwoodjs/cli-some-package', + undefined, + true + ) + expect(pluginLib.loadPluginPackage).toHaveBeenCalledWith( + '@redwoodjs/cli-some-package-not-in-cache', + undefined, + true + ) + + // Should have saved the cache with the new package + expect(pluginLib.saveCommandCache).toHaveBeenCalledTimes(1) + const knownPlugins = + getConfig.mock.results[0].value.experimental.cli.plugins.map( + (plugin) => plugin.package + ) + const saveCommandCacheArg = Object.entries( + pluginLib.saveCommandCache.mock.calls[0][0] + ).filter(([key]) => knownPlugins.includes(key)) + expect(saveCommandCacheArg).toMatchSnapshot() + + // Rudimentary check that the right handler was invoked + await yargsInstance.parse() + expect(console.log).toHaveBeenCalledWith('MARKER SOME') + expect(console.log).not.toHaveBeenCalledWith('MARKER SOME OTHER') + + getConfig.mockRestore() + process.argv = originalArgv + }) + test('correct loading for unknown redwood command', async () => { + const originalArgv = process.argv + process.argv = ['node', 'rw', 'unknownCommand'] + + getConfig.mockReturnValue({ + experimental: { + cli: { + plugins: [ + { + package: '@redwoodjs/cli-some-package', + }, + { + package: '@redwoodjs/cli-some-package-not-in-cache', + }, + { + package: '@bluewoodjs/cli-some-package', + }, + ], + autoInstall: true, + }, + }, + }) + jest.mock( + '@redwoodjs/cli-some-package-not-in-cache', + () => { + return { + commands: [ + { + command: 'some-other-command', + description: 'Some example other command', + aliases: ['soc', 'someOtherCommand'], + }, + ], + } + }, + { virtual: true } + ) + fs.__setMockFiles({ + ['commandCache.json']: JSON.stringify({ + '@redwoodjs/cli-some-package': { + 'some-command': { + aliases: ['sc', 'someCommand'], + description: 'Some example command', + }, + }, + '@bluewoodjs/cli-some-package': { + 'third-party': { + aliases: ['tp', 'thirdParty'], + description: 'Some third party command', + }, + }, + }), + }) + + pluginLib.loadPluginPackage.mockImplementation((packageName) => { + if (packageName === '@redwoodjs/cli-some-package') { + return { + commands: [ + { + command: 'some-command', + description: 'Some example command', + aliases: ['sc', 'someCommand'], + builder: () => {}, + handler: () => { + console.log('MARKER SOME') + }, + }, + ], + } + } + if (packageName === '@redwoodjs/cli-some-package-not-in-cache') { + return { + commands: [ + { + command: 'some-other-command', + description: 'Some example other command', + aliases: ['soc', 'someOtherCommand'], + builder: () => {}, + handler: () => { + console.log('MARKER SOME OTHER') + }, + }, + ], + } + } + throw new Error(`Unexpected behaviour: loading ${packageName}`) + }) + + const yargsInstance = getMockYargsInstance() + await loadPlugins(yargsInstance) + + expect(pluginLib.loadCommadCache).toHaveBeenCalledTimes(1) + expect(pluginLib.checkPluginListAndWarn).toHaveBeenCalledTimes(1) + + // Should have loaded the package that we couldn't rule out from the cache + expect(pluginLib.loadPluginPackage).toHaveBeenCalledTimes(1) + expect(pluginLib.loadPluginPackage).toHaveBeenCalledWith( + '@redwoodjs/cli-some-package-not-in-cache', + undefined, + true + ) + + // Should have saved the cache with the new package + expect(pluginLib.saveCommandCache).toHaveBeenCalledTimes(1) + const knownPlugins = + getConfig.mock.results[0].value.experimental.cli.plugins.map( + (plugin) => plugin.package + ) + const saveCommandCacheArg = Object.entries( + pluginLib.saveCommandCache.mock.calls[0][0] + ).filter(([key]) => knownPlugins.includes(key)) + expect(saveCommandCacheArg).toMatchSnapshot() + + // Rudimentary check that the help output contains the correct commands + const helpOutput = await yargsInstance.getHelp() + expect(helpOutput).toContain('rw built-in') + expect(helpOutput).toContain('Some builtin command') + expect(helpOutput).toContain('[aliases: bi, builtIn]') + expect(helpOutput).toContain('rw some-command') + expect(helpOutput).toContain('Some example command') + expect(helpOutput).toContain('[aliases: sc, someCommand]') + expect(helpOutput).toContain('rw some-other-command') + expect(helpOutput).toContain('Some example other command') + expect(helpOutput).toContain('[aliases: soc, someOtherCommand]') + expect(helpOutput).toContain('rw @bluewoodjs ') + expect(helpOutput).toContain('Commands from @bluewoodjs') + + // Rudimentary check that the right handler was invoked + await yargsInstance.parse() + expect(console.log).not.toHaveBeenCalledWith('MARKER SOME') + expect(console.log).not.toHaveBeenCalledWith('MARKER SOME OTHER') + + getConfig.mockRestore() + process.argv = originalArgv + }) + + test('correct loading for known third party command (with cache)', async () => { + const originalArgv = process.argv + process.argv = ['node', 'rw', '@bluewoodjs', 'tp'] + + getConfig.mockReturnValue({ + experimental: { + cli: { + plugins: [ + { + package: '@redwoodjs/cli-some-package', + }, + { + package: '@redwoodjs/cli-some-package-not-in-cache', + }, + { + package: '@bluewoodjs/cli-some-package', + }, + ], + autoInstall: true, + }, + }, + }) + jest.mock( + '@redwoodjs/cli-some-package-not-in-cache', + () => { + return { + commands: [ + { + command: 'some-other-command', + description: 'Some example other command', + aliases: ['soc', 'someOtherCommand'], + }, + ], + } + }, + { virtual: true } + ) + fs.__setMockFiles({ + ['commandCache.json']: JSON.stringify({ + '@redwoodjs/cli-some-package': { + 'some-command': { + aliases: ['sc', 'someCommand'], + description: 'Some example command', + }, + }, + '@bluewoodjs/cli-some-package': { + 'third-party': { + aliases: ['tp', 'thirdParty'], + description: 'Some third party command', + }, + }, + }), + }) + + pluginLib.loadPluginPackage.mockImplementation((packageName) => { + if (packageName === '@bluewoodjs/cli-some-package') { + return { + commands: [ + { + command: 'third-party', + description: 'Some third party command', + aliases: ['tp', 'thirdParty'], + builder: () => {}, + handler: () => { + console.log('MARKER') + }, + }, + ], + } + } + throw new Error(`Unexpected behaviour: loading ${packageName}`) + }) + + const yargsInstance = getMockYargsInstance() + await loadPlugins(yargsInstance) + + expect(pluginLib.loadCommadCache).toHaveBeenCalledTimes(1) + expect(pluginLib.checkPluginListAndWarn).toHaveBeenCalledTimes(1) + + // Should have loaded the package - only the one we need + expect(pluginLib.loadPluginPackage).toHaveBeenCalledTimes(1) + expect(pluginLib.loadPluginPackage).toHaveBeenCalledWith( + '@bluewoodjs/cli-some-package', + undefined, + true + ) + + // Should have saved the cache with the new package + expect(pluginLib.saveCommandCache).toHaveBeenCalledTimes(1) + const knownPlugins = + getConfig.mock.results[0].value.experimental.cli.plugins.map( + (plugin) => plugin.package + ) + const saveCommandCacheArg = Object.entries( + pluginLib.saveCommandCache.mock.calls[0][0] + ).filter(([key]) => knownPlugins.includes(key)) + expect(saveCommandCacheArg).toMatchSnapshot() + + // Rudimentary check that the right handler was invoked + await yargsInstance.parse() + expect(console.log).toHaveBeenCalledWith('MARKER') + + getConfig.mockRestore() + process.argv = originalArgv + }) + test('correct loading for known third party command (without cache)', async () => { + const originalArgv = process.argv + process.argv = ['node', 'rw', '@bluewoodjs', 'tpo'] + + getConfig.mockReturnValue({ + experimental: { + cli: { + plugins: [ + { + package: '@redwoodjs/cli-some-package', + }, + { + package: '@redwoodjs/cli-some-package-not-in-cache', + }, + { + package: '@bluewoodjs/cli-some-package', + }, + { + package: '@bluewoodjs/cli-some-package-second-example', + }, + ], + autoInstall: true, + }, + }, + }) + fs.__setMockFiles({ + ['commandCache.json']: JSON.stringify({}), + }) + + pluginLib.loadPluginPackage.mockImplementation((packageName) => { + if (packageName === '@bluewoodjs/cli-some-package') { + return { + commands: [ + { + command: 'third-party', + description: 'Some third party command', + aliases: ['tp', 'thirdParty'], + builder: () => {}, + handler: () => { + console.log('MARKER TP') + }, + }, + ], + } + } + if (packageName === '@bluewoodjs/cli-some-package-second-example') { + return { + commands: [ + { + command: 'third-party-other', + description: 'Some other third party command', + aliases: ['tpo', 'thirdPartyOther'], + builder: () => {}, + handler: () => { + console.log('MARKER TPO') + }, + }, + ], + } + } + throw new Error(`Unexpected behaviour: loading ${packageName}`) + }) + + const yargsInstance = getMockYargsInstance() + await loadPlugins(yargsInstance) + + expect(pluginLib.loadCommadCache).toHaveBeenCalledTimes(1) + expect(pluginLib.checkPluginListAndWarn).toHaveBeenCalledTimes(1) + + // Should have loaded the package - only the one we need + expect(pluginLib.loadPluginPackage).toHaveBeenCalledTimes(2) + expect(pluginLib.loadPluginPackage).toHaveBeenCalledWith( + '@bluewoodjs/cli-some-package', + undefined, + true + ) + expect(pluginLib.loadPluginPackage).toHaveBeenCalledWith( + '@bluewoodjs/cli-some-package-second-example', + undefined, + true + ) + + // Should have saved the cache with the new package + expect(pluginLib.saveCommandCache).toHaveBeenCalledTimes(1) + const knownPlugins = + getConfig.mock.results[0].value.experimental.cli.plugins.map( + (plugin) => plugin.package + ) + const saveCommandCacheArg = Object.entries( + pluginLib.saveCommandCache.mock.calls[0][0] + ).filter(([key]) => knownPlugins.includes(key)) + expect(saveCommandCacheArg).toMatchSnapshot() + + // Rudimentary check that the right handler was invoked + await yargsInstance.parse() + expect(console.log).not.toHaveBeenCalledWith('MARKER TP') + expect(console.log).toHaveBeenCalledWith('MARKER TPO') + + getConfig.mockRestore() + process.argv = originalArgv + }) + test('correct loading for unknown third party command', async () => { + const originalArgv = process.argv + process.argv = ['node', 'rw', '@bluewoodjs', 'unknownCommand'] + + getConfig.mockReturnValue({ + experimental: { + cli: { + plugins: [ + { + package: '@redwoodjs/cli-some-package', + }, + { + package: '@redwoodjs/cli-some-package-not-in-cache', + }, + { + package: '@bluewoodjs/cli-some-package', + }, + ], + autoInstall: true, + }, + }, + }) + fs.__setMockFiles({ + ['commandCache.json']: JSON.stringify({ + '@redwoodjs/cli-some-package': { + 'some-command': { + aliases: ['sc', 'someCommand'], + description: 'Some example command', + }, + }, + '@bluewoodjs/cli-some-package': { + 'third-party': { + aliases: ['tp', 'thirdParty'], + description: 'Some third party command', + }, + }, + }), + }) + + pluginLib.loadPluginPackage.mockImplementation((packageName) => { + if (packageName === '@bluewoodjs/cli-some-package') { + return { + commands: [ + { + command: 'third-party', + description: 'Some third party command', + aliases: ['tp', 'thirdParty'], + builder: () => {}, + handler: () => { + console.log('MARKER SOME') + }, + }, + ], + } + } + throw new Error(`Unexpected behaviour: loading ${packageName}`) + }) + + const yargsInstance = getMockYargsInstance() + await loadPlugins(yargsInstance) + + expect(pluginLib.loadCommadCache).toHaveBeenCalledTimes(1) + expect(pluginLib.checkPluginListAndWarn).toHaveBeenCalledTimes(1) + + // Should have loaded the package that we couldn't rule out from the cache + expect(pluginLib.loadPluginPackage).toHaveBeenCalledTimes(0) + + // Should have saved the cache with the new package + expect(pluginLib.saveCommandCache).toHaveBeenCalledTimes(1) + const knownPlugins = + getConfig.mock.results[0].value.experimental.cli.plugins.map( + (plugin) => plugin.package + ) + const saveCommandCacheArg = Object.entries( + pluginLib.saveCommandCache.mock.calls[0][0] + ).filter(([key]) => knownPlugins.includes(key)) + expect(saveCommandCacheArg).toMatchSnapshot() + + // Rudimentary check that the help output contains the correct commands + const helpOutput = await yargsInstance.getHelp() + expect(helpOutput).not.toContain('rw built-in') + expect(helpOutput).not.toContain('Some builtin command') + expect(helpOutput).not.toContain('[aliases: bi, builtIn]') + expect(helpOutput).not.toContain('rw some-command') + expect(helpOutput).not.toContain('Some example command') + expect(helpOutput).not.toContain('[aliases: sc, someCommand]') + expect(helpOutput).not.toContain('rw some-other-command') + expect(helpOutput).not.toContain('Some example other command') + expect(helpOutput).not.toContain('[aliases: soc, someOtherCommand]') + expect(helpOutput).toContain('rw @bluewoodjs ') + expect(helpOutput).toContain('Commands from @bluewoodjs') + + // Rudimentary check that the right handler was invoked + await yargsInstance.parse() + expect(console.log).not.toHaveBeenCalledWith('MARKER') + + getConfig.mockRestore() + process.argv = originalArgv + }) +}) diff --git a/packages/cli/src/commands/__tests__/dev.test.js b/packages/cli/src/commands/__tests__/dev.test.js index f7ab0d7f80e2..7de47c38266a 100644 --- a/packages/cli/src/commands/__tests__/dev.test.js +++ b/packages/cli/src/commands/__tests__/dev.test.js @@ -81,6 +81,50 @@ describe('yarn rw dev', () => { port: 8911, debugPort: 18911, }, + experimental: { + streamingSsr: { + enabled: false, + }, + }, + }) + + await handler({ + side: ['api', 'web'], + }) + + expect(generatePrismaClient).toHaveBeenCalledTimes(1) + const concurrentlyArgs = concurrently.mock.lastCall[0] + + const webCommand = find(concurrentlyArgs, { name: 'web' }) + const apiCommand = find(concurrentlyArgs, { name: 'api' }) + const generateCommand = find(concurrentlyArgs, { name: 'gen' }) + + // Uses absolute path, so not doing a snapshot + expect(webCommand.command).toContain( + 'yarn cross-env NODE_ENV=development rw-vite-dev' + ) + + expect(apiCommand.command).toMatchInlineSnapshot( + `"yarn cross-env NODE_ENV=development NODE_OPTIONS="--enable-source-maps" yarn nodemon --quiet --watch "/mocked/project/redwood.toml" --exec "yarn rw-api-server-watch --port 8911 --debug-port 18911 | rw-log-formatter""` + ) + + expect(generateCommand.command).toEqual('yarn rw-gen-watch') + }) + + it('Should run api and FE dev server, when streaming experimental flag enabled', async () => { + getConfig.mockReturnValue({ + web: { + port: 8910, + }, + api: { + port: 8911, + debugPort: 18911, + }, + experimental: { + streamingSsr: { + enabled: true, // <-- enable SSR/Streaming + }, + }, }) await handler({ @@ -96,11 +140,11 @@ describe('yarn rw dev', () => { // Uses absolute path, so not doing a snapshot expect(webCommand.command).toContain( - 'yarn cross-env NODE_ENV=development RWJS_WATCH_NODE_MODULES= webpack serve' + 'yarn cross-env NODE_ENV=development rw-dev-fe' ) expect(apiCommand.command).toMatchInlineSnapshot( - `"yarn cross-env NODE_ENV=development NODE_OPTIONS=--enable-source-maps yarn nodemon --quiet --watch "/mocked/project/redwood.toml" --exec "yarn rw-api-server-watch --port 8911 --debug-port 18911 | rw-log-formatter""` + `"yarn cross-env NODE_ENV=development NODE_OPTIONS="--enable-source-maps" yarn nodemon --quiet --watch "/mocked/project/redwood.toml" --exec "yarn rw-api-server-watch --port 8911 --debug-port 18911 | rw-log-formatter""` ) expect(generateCommand.command).toEqual('yarn rw-gen-watch') @@ -115,6 +159,11 @@ describe('yarn rw dev', () => { port: 8911, debugPort: 505050, }, + experimental: { + streamingSsr: { + enabled: false, + }, + }, }) await handler({ @@ -140,6 +189,11 @@ describe('yarn rw dev', () => { port: 8911, debugPort: false, }, + experimental: { + streamingSsr: { + enabled: false, + }, + }, }) await handler({ @@ -162,6 +216,11 @@ describe('yarn rw dev', () => { api: { port: 8911, }, + experimental: { + streamingSsr: { + enabled: false, + }, + }, }) await handler({ diff --git a/packages/cli/src/commands/__tests__/serve.test.js b/packages/cli/src/commands/__tests__/serve.test.js index 5cae4f42896d..f45e60380c43 100644 --- a/packages/cli/src/commands/__tests__/serve.test.js +++ b/packages/cli/src/commands/__tests__/serve.test.js @@ -6,9 +6,11 @@ jest.mock('@redwoodjs/project-config', () => { getPaths: () => { return { api: { + base: '/mocked/project/api', dist: '/mocked/project/api/dist', }, web: { + base: '/mocked/project/web', dist: '/mocked/project/web/dist', }, } @@ -24,28 +26,41 @@ jest.mock('@redwoodjs/project-config', () => { jest.mock('fs', () => { return { ...jest.requireActual('fs'), - existsSync: () => true, + existsSync: (p) => { + // Don't detect the experimental server file, can't use path.sep here so the replaceAll is used + if (p.replaceAll('\\', '/') === '/mocked/project/api/dist/server.js') { + return false + } + return true + }, } }) -jest.mock('@redwoodjs/api-server', () => { +jest.mock('../serveApiHandler', () => { return { - ...jest.requireActual('@redwoodjs/api-server'), + ...jest.requireActual('../serveApiHandler'), apiServerHandler: jest.fn(), - webServerHandler: jest.fn(), + } +}) +jest.mock('../serveBothHandler', () => { + return { + ...jest.requireActual('../serveBothHandler'), bothServerHandler: jest.fn(), } }) - +jest.mock('execa', () => + jest.fn((cmd, params) => ({ + cmd, + params, + })) +) + +import execa from 'execa' import yargs from 'yargs' -import { - apiServerHandler, - bothServerHandler, - webServerHandler, -} from '@redwoodjs/api-server' - import { builder } from '../serve' +import { apiServerHandler } from '../serveApiHandler' +import { bothServerHandler } from '../serveBothHandler' describe('yarn rw serve', () => { afterEach(() => { @@ -87,12 +102,18 @@ describe('yarn rw serve', () => { 'serve web --port 9898 --socket abc --apiHost https://myapi.redwood/api' ) - expect(webServerHandler).toHaveBeenCalledWith( - expect.objectContaining({ - port: 9898, - socket: 'abc', - apiHost: 'https://myapi.redwood/api', - }) + expect(execa).toHaveBeenCalledWith( + 'yarn', + expect.arrayContaining([ + 'rw-web-server', + '--port', + 9898, + '--socket', + 'abc', + '--api-host', + 'https://myapi.redwood/api', + ]), + expect.anything() ) }) diff --git a/packages/cli/src/commands/build.js b/packages/cli/src/commands/build.js index 7669c6187a9a..d38568f6ba96 100644 --- a/packages/cli/src/commands/build.js +++ b/packages/cli/src/commands/build.js @@ -1,7 +1,9 @@ import terminalLink from 'terminal-link' +import c from '../lib/colors' +import { exitWithError } from '../lib/exit' import { sides } from '../lib/project' -import checkForBabelConfig from '../middleware/checkForBabelConfig' +import { checkNodeVersion } from '../middleware/checkNodeVersion' export const command = 'build [side..]' export const description = 'Build for production' @@ -47,7 +49,18 @@ export const builder = (yargs) => { default: false, description: 'Measure build performance', }) - .middleware(checkForBabelConfig) + .middleware(() => { + const check = checkNodeVersion() + + if (check.ok) { + return + } + + exitWithError(undefined, { + message: `${c.error('Error')}: ${check.message}`, + includeEpilogue: false, + }) + }) .epilogue( `Also see the ${terminalLink( 'Redwood CLI Reference', @@ -57,6 +70,6 @@ export const builder = (yargs) => { } export const handler = async (options) => { - const { handler } = await import('./buildHandler') + const { handler } = await import('./buildHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/buildHandler.js b/packages/cli/src/commands/buildHandler.js index bf891e805edb..8a47f2b842c1 100644 --- a/packages/cli/src/commands/buildHandler.js +++ b/packages/cli/src/commands/buildHandler.js @@ -1,19 +1,18 @@ -import fs from 'fs' import path from 'path' import execa from 'execa' +import fs from 'fs-extra' import { Listr } from 'listr2' -import rimraf from 'rimraf' +import { rimraf } from 'rimraf' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { buildApi } from '@redwoodjs/internal/dist/build/api' -import { buildWeb } from '@redwoodjs/internal/dist/build/web' import { loadAndValidateSdls } from '@redwoodjs/internal/dist/validateSchema' import { detectPrerenderRoutes } from '@redwoodjs/prerender/detection' -import { timedTelemetry, errorTelemetry } from '@redwoodjs/telemetry' +import { timedTelemetry } from '@redwoodjs/telemetry' import { getPaths, getConfig } from '../lib' -import c from '../lib/colors' import { generatePrismaCommand } from '../lib/generatePrismaClient' export const handler = async ({ @@ -24,6 +23,15 @@ export const handler = async ({ prisma = true, prerender, }) => { + recordTelemetryAttributes({ + command: 'build', + side: JSON.stringify(side), + verbose, + performance, + stats, + prisma, + prerender, + }) const rwjsPaths = getPaths() if (performance) { @@ -91,15 +99,34 @@ export const handler = async ({ task: () => { return rimraf(rwjsPaths.web.dist) }, - enabled: getConfig().web.bundler !== 'vite', + enabled: getConfig().web.bundler === 'webpack', }, side.includes('web') && { title: 'Building Web...', task: async () => { - if (getConfig().web.bundler === 'vite') { - await buildWeb({ - verbose, - }) + if (getConfig().web.bundler !== 'webpack') { + // @NOTE: we're using the vite build command here, instead of the + // buildWeb function directly because we want the process.cwd to be + // the web directory, not the root of the project. + // This is important for postcss/tailwind to work correctly + // Having a separate binary lets us contain the change of cwd to that + // process only. If we changed cwd here, or in the buildWeb function, + // it could affect other things that run in parallel while building. + // We don't have any parallel tasks right now, but someone might add + // one in the future as a performance optimization. + await execa( + `node ${require.resolve( + '@redwoodjs/vite/bins/rw-vite-build.mjs' + )} --webDir="${rwjsPaths.web.base}" --verbose=${verbose}`, + { + stdio: verbose ? 'inherit' : 'pipe', + shell: true, + // `cwd` is needed for yarn to find the rw-vite-build binary + // It won't change process.cwd for anything else here, in this + // process + cwd: rwjsPaths.web.base, + } + ) } else { await execa( `yarn cross-env NODE_ENV=production webpack --config ${require.resolve( @@ -113,14 +140,17 @@ export const handler = async ({ ) } - console.log('Creating 200.html...') + // Streaming SSR does not use the index.html file. + if (!getConfig().experimental?.streamingSsr?.enabled) { + console.log('Creating 200.html...') - const indexHtmlPath = path.join(getPaths().web.dist, 'index.html') + const indexHtmlPath = path.join(getPaths().web.dist, 'index.html') - fs.copyFileSync( - indexHtmlPath, - path.join(getPaths().web.dist, '200.html') - ) + fs.copyFileSync( + indexHtmlPath, + path.join(getPaths().web.dist, '200.html') + ) + } }, }, ].filter(Boolean) @@ -151,18 +181,12 @@ export const handler = async ({ renderer: verbose && 'verbose', }) - try { - await timedTelemetry(process.argv, { type: 'build' }, async () => { - await jobs.run() + await timedTelemetry(process.argv, { type: 'build' }, async () => { + await jobs.run() - if (side.includes('web') && prerender) { - // This step is outside Listr so that it prints clearer, complete messages - await triggerPrerender() - } - }) - } catch (e) { - console.log(c.error(e.message)) - errorTelemetry(process.argv, e.message) - process.exit(1) - } + if (side.includes('web') && prerender) { + // This step is outside Listr so that it prints clearer, complete messages + await triggerPrerender() + } + }) } diff --git a/packages/cli/src/commands/check.js b/packages/cli/src/commands/check.js index 6fdf5896a3f5..f11abcbeec0e 100644 --- a/packages/cli/src/commands/check.js +++ b/packages/cli/src/commands/check.js @@ -1,3 +1,5 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { getPaths } from '../lib' import c from '../lib/colors' @@ -6,10 +8,30 @@ export const aliases = ['diagnostics'] export const description = 'Get structural diagnostics for a Redwood project (experimental)' -export const handler = async () => { - const { printDiagnostics, DiagnosticSeverity } = await import( - '@redwoodjs/structure' - ) +export const handler = () => { + recordTelemetryAttributes({ + command: 'check', + }) + // Deep dive + // + // It seems like we have to use `require` here instead of `await import` + // because of how Babel builds the `DiagnosticSeverity` export in `@redwoodjs/structure`: + // + // ```js + // _Object$defineProperty(exports, "DiagnosticSeverity", { + // enumerable: true, + // get: function () { + // return _vscodeLanguageserverTypes.DiagnosticSeverity; + // } + // }); + // ``` + // + // I'm not sure why, but with `await import`, `DiagnosticSeverity` is `undefined` + // so it seems like `await import` doesn't execute the getter function. + const { + printDiagnostics, + DiagnosticSeverity, + } = require('@redwoodjs/structure') printDiagnostics(getPaths().base, { getSeverityLabel: (severity) => { diff --git a/packages/cli/src/commands/console.js b/packages/cli/src/commands/console.js index 119acd0dbe75..0d8b66c4f952 100644 --- a/packages/cli/src/commands/console.js +++ b/packages/cli/src/commands/console.js @@ -3,6 +3,6 @@ export const aliases = ['c'] export const description = 'Launch an interactive Redwood shell (experimental)' export const handler = async (options) => { - const { handler } = await import('./consoleHandler') + const { handler } = await import('./consoleHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/consoleHandler.js b/packages/cli/src/commands/consoleHandler.js index 3aa33008eaa9..038e9d2f3c08 100644 --- a/packages/cli/src/commands/consoleHandler.js +++ b/packages/cli/src/commands/consoleHandler.js @@ -1,8 +1,10 @@ -import fs from 'fs' import path from 'path' import repl from 'repl' -import { registerApiSideBabelHook } from '@redwoodjs/internal/dist/build/babel/api' +import fs from 'fs-extra' + +import { registerApiSideBabelHook } from '@redwoodjs/babel-config' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { getPaths } from '../lib' @@ -10,6 +12,8 @@ const paths = getPaths() const loadPrismaClient = (replContext) => { const { db } = require(path.join(paths.api.lib, 'db')) + // workaround for Prisma issue: https://github.com/prisma/prisma/issues/18292 + db[Symbol.for('nodejs.util.inspect.custom')] = 'PrismaClient' replContext.db = db } @@ -35,6 +39,10 @@ const loadConsoleHistory = async (r) => { } export const handler = () => { + recordTelemetryAttributes({ + command: 'console', + }) + // Transpile on the fly registerApiSideBabelHook({ plugins: [ diff --git a/packages/cli/src/commands/data-migrate.js b/packages/cli/src/commands/data-migrate.js deleted file mode 100644 index 6d35ae664c47..000000000000 --- a/packages/cli/src/commands/data-migrate.js +++ /dev/null @@ -1,15 +0,0 @@ -export const command = 'data-migrate ' -export const aliases = ['dm', 'dataMigrate'] -export const description = 'Migrate the data in your database' -import terminalLink from 'terminal-link' - -export const builder = (yargs) => - yargs - .commandDir('./dataMigrate') - .demandCommand() - .epilogue( - `Also see the ${terminalLink( - 'Redwood CLI Reference', - 'https://redwoodjs.com/docs/cli-commands#datamigrate' - )}` - ) diff --git a/packages/cli/src/commands/dataMigrate/install.js b/packages/cli/src/commands/dataMigrate/install.js deleted file mode 100644 index 93ce796c9b8a..000000000000 --- a/packages/cli/src/commands/dataMigrate/install.js +++ /dev/null @@ -1,99 +0,0 @@ -import path from 'path' - -import execa from 'execa' -import fs from 'fs-extra' -import { Listr } from 'listr2' -import terminalLink from 'terminal-link' - -import { errorTelemetry } from '@redwoodjs/telemetry' - -import { getPaths } from '../../lib' -import c from '../../lib/colors' - -const MODEL = `model RW_DataMigration { - version String @id - name String - startedAt DateTime - finishedAt DateTime -}` - -const POST_INSTALL_INSTRUCTIONS = `${c.warning( - "Don't forget to apply your migration when ready:" -)} - - ${c.bold('yarn rw prisma migrate dev')} -` - -// Creates dataMigrations directory -const createPath = () => { - return fs.outputFileSync( - path.join(getPaths().api.dataMigrations, '.keep'), - '' - ) -} - -// Appends RW_DataMigration model to schema.prisma -const appendModel = () => { - const schemaPath = getPaths().api.dbSchema - const schema = fs.readFileSync(schemaPath).toString() - const newSchema = `${schema}\n${MODEL}\n` - - return fs.writeFileSync(schemaPath, newSchema) -} - -// Create a new migration -const save = async () => { - return await execa( - 'yarn rw', - ['prisma migrate dev', '--name create_data_migrations', '--create-only'], - { - cwd: getPaths().api.base, - shell: true, - } - ) -} - -export const command = 'install' -export const description = 'Add the RW_DataMigration model to your schema' -export const builder = (yargs) => { - yargs.epilogue( - `Also see the ${terminalLink( - 'Redwood CLI Reference', - 'https://redwoodjs.com/docs/cli-commands#install' - )}` - ) -} - -export const handler = async () => { - const tasks = new Listr( - [ - { - title: `Creating dataMigrations directory...`, - task: createPath, - }, - { - title: 'Adding RW_DataMigration model to schema.prisma...', - task: await appendModel, - }, - { - title: 'Create db migration...', - task: await save, - }, - { - title: 'One more thing...', - task: (_ctx, task) => { - task.title = `Next steps:\n ${POST_INSTALL_INSTRUCTIONS}` - }, - }, - ], - { rendererOptions: { collapse: false }, exitOnError: true } - ) - - try { - await tasks.run() - } catch (e) { - errorTelemetry(process.argv, e.message) - console.error(c.error(e.message)) - process.exit(e?.exitCode || 1) - } -} diff --git a/packages/cli/src/commands/dataMigrate/up.js b/packages/cli/src/commands/dataMigrate/up.js deleted file mode 100644 index 05b1d0f7c9b4..000000000000 --- a/packages/cli/src/commands/dataMigrate/up.js +++ /dev/null @@ -1,179 +0,0 @@ -import fs from 'fs' -import path from 'path' - -import { Listr } from 'listr2' -import terminalLink from 'terminal-link' - -import { registerApiSideBabelHook } from '@redwoodjs/internal/dist/build/babel/api' -import { errorTelemetry } from '@redwoodjs/telemetry' - -import { getPaths } from '../../lib' -import c from '../../lib/colors' - -// sorts migrations by date, oldest first -const sortMigrations = (migrations) => { - return migrations.sort((a, b) => { - const aVersion = parseInt(Object.keys(a)[0]) - const bVersion = parseInt(Object.keys(b)[0]) - - if (aVersion > bVersion) { - return 1 - } - if (aVersion < bVersion) { - return -1 - } - return 0 - }) -} - -const SUPPORTED_EXTENSIONS = ['.js', '.ts'] - -// Return the list of migrations that haven't run against the database yet -const getMigrations = async (db) => { - const basePath = path.join(getPaths().api.dataMigrations) - - if (!fs.existsSync(basePath)) { - return [] - } - - // gets all migrations present in the app - const files = fs - .readdirSync(basePath) - .filter((m) => SUPPORTED_EXTENSIONS.includes(path.extname(m))) - .map((m) => { - return { - [m.split('-')[0]]: path.join(basePath, m), - } - }) - - // gets all migration versions that have already run against the database - const ranMigrations = await db.rW_DataMigration.findMany({ - orderBy: { version: 'asc' }, - }) - const ranVersions = ranMigrations.map((migration) => - migration.version.toString() - ) - - const unrunMigrations = files.filter((migration) => { - return !ranVersions.includes(Object.keys(migration)[0]) - }) - - return sortMigrations(unrunMigrations) -} - -// adds data for completed migrations to the DB -const record = async (db, { version, name, startedAt, finishedAt }) => { - await db.rW_DataMigration.create({ - data: { version, name, startedAt, finishedAt }, - }) -} - -// output run status to the console -const report = (counters) => { - console.log('') - if (counters.run) { - console.info( - c.green(`${counters.run} data migration(s) completed successfully.`) - ) - } - if (counters.error) { - console.error( - c.error(`${counters.error} data migration(s) exited with errors.`) - ) - } - if (counters.skipped) { - console.warn( - c.warning( - `${counters.skipped} data migration(s) skipped due to previous error.` - ) - ) - } - console.log('') -} - -const runScript = async (db, scriptPath) => { - const script = await import(scriptPath) - const startedAt = new Date() - await script.default({ db }) - const finishedAt = new Date() - - return { startedAt, finishedAt } -} - -export const command = 'up' -export const description = - 'Run any outstanding Data Migrations against the database' -export const builder = (yargs) => { - yargs.epilogue( - `Also see the ${terminalLink( - 'Redwood CLI Reference', - 'https://redwoodjs.com/docs/cli-commands#datamigrate-up' - )}` - ) -} - -export const handler = async () => { - // Import babel settings so we can write es6 scripts - registerApiSideBabelHook() - - const { db } = require(path.join(getPaths().api.lib, 'db')) - - const migrations = await getMigrations(db) - - // exit immediately if there aren't any migrations to run - if (!migrations.length) { - console.info(c.green('\nNo data migrations run, already up-to-date.\n')) - process.exit(0) - } - - const counters = { run: 0, skipped: 0, error: 0 } - const migrationTasks = migrations.map((migration) => { - const version = Object.keys(migration)[0] - const migrationPath = Object.values(migration)[0] - const migrationName = path.basename(migrationPath, '.js') - - return { - title: migrationName, - skip: () => { - if (counters.error > 0) { - counters.skipped++ - return true - } - }, - task: async () => { - try { - const { startedAt, finishedAt } = await runScript(db, migrationPath) - counters.run++ - await record(db, { - version, - name: migrationName, - startedAt, - finishedAt, - }) - } catch (e) { - counters.error++ - console.error(c.error(`Error in data migration: ${e.message}`)) - } - }, - } - }) - - const tasks = new Listr(migrationTasks, { - rendererOptions: { collapse: false }, - renderer: 'verbose', - }) - - try { - await tasks.run() - await db.$disconnect() - report(counters) - if (counters.error) { - process.exit(1) - } - } catch (e) { - await db.$disconnect() - report(counters) - errorTelemetry(process.argv, e.message) - process.exit(e?.exitCode || 1) - } -} diff --git a/packages/cli/src/commands/deploy/__tests__/baremetal.test.js b/packages/cli/src/commands/deploy/__tests__/baremetal.test.js index 149089f83323..3a076da64799 100644 --- a/packages/cli/src/commands/deploy/__tests__/baremetal.test.js +++ b/packages/cli/src/commands/deploy/__tests__/baremetal.test.js @@ -299,6 +299,33 @@ describe('parseConfig', () => { }) expect(envLifecycle.after).toEqual({}) }) + + it('interpolates environment variables correctly', () => { + process.env.TEST_VAR_HOST = 'staging.server.com' + process.env.TEST_VAR_REPO = 'git://staging.github.com' + const { + envConfig: { servers }, + } = baremetal.parseConfig( + { environment: 'production' }, + ` + [[production.servers]] + host = '\${TEST_VAR_HOST:server.com}' + repo = '\${TEST_VAR_REPO:git://github.com}' + path = '\${TEST_VAR_PATH:/var/www/app}' + privateKeyPath = '/Users/me/.ssh/id_rsa' + ` + ) + const server = servers[0] + expect(server.host).toEqual('staging.server.com') + expect(server.repo).toEqual('git://staging.github.com') + // Default value should work + expect(server.path).toEqual('/var/www/app') + // No substitution should work + expect(server.privateKeyPath).toEqual('/Users/me/.ssh/id_rsa') + + delete process.env.TEST_VAR_HOST + delete process.env.TEST_VAR_REPO + }) }) describe('commandWithLifecycleEvents', () => { diff --git a/packages/cli/src/commands/deploy/__tests__/nftPack.test.js b/packages/cli/src/commands/deploy/__tests__/nftPack.test.js index 62e859a49c6a..3099422cf930 100644 --- a/packages/cli/src/commands/deploy/__tests__/nftPack.test.js +++ b/packages/cli/src/commands/deploy/__tests__/nftPack.test.js @@ -1,6 +1,11 @@ +import path from 'path' + +import fs from 'fs-extra' + +import { buildApi } from '@redwoodjs/internal/dist/build/api' import { findApiDistFunctions } from '@redwoodjs/internal/dist/files' -import nftPacker from '../packing/nft' +import * as nftPacker from '../packing/nft' jest.mock('@redwoodjs/internal/dist/files', () => { return { diff --git a/packages/cli/src/commands/deploy/baremetal.js b/packages/cli/src/commands/deploy/baremetal.js index 7c6d03593024..52f3945a2b18 100644 --- a/packages/cli/src/commands/deploy/baremetal.js +++ b/packages/cli/src/commands/deploy/baremetal.js @@ -1,12 +1,15 @@ -import fs from 'fs' import path from 'path' import toml from '@iarna/toml' import boxen from 'boxen' +import fs from 'fs-extra' import { Listr } from 'listr2' +import { env as envInterpolation } from 'string-env-interpolation' import terminalLink from 'terminal-link' import { titleCase } from 'title-case' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { getPaths } from '../../lib' import c from '../../lib/colors' @@ -112,6 +115,12 @@ export const builder = (yargs) => { help: 'Rollback [count] number of releases', }) + yargs.option('verbose', { + describe: 'Verbose mode, for debugging purposes', + default: false, + type: 'boolean', + }) + // TODO: Allow option to pass --sides and only deploy select sides instead of all, always yargs.epilogue( @@ -122,8 +131,8 @@ export const builder = (yargs) => { ) } -// Executes a single command via SSH connection. Displays an error and will -// exit() with the same code returned from the SSH command. +// Executes a single command via SSH connection. Throws an error and sets +// the exit code with the same code returned from the SSH command. const sshExec = async (ssh, path, command, args) => { let sshCommand = command @@ -136,18 +145,11 @@ const sshExec = async (ssh, path, command, args) => { }) if (result.code !== 0) { - console.error(c.error(`\nDeploy failed!`)) - console.error( - c.error(`Error while running command \`${command} ${args.join(' ')}\`:`) - ) - console.error( - boxen(result.stderr, { - padding: { top: 0, bottom: 0, right: 1, left: 1 }, - margin: 0, - borderColor: 'red', - }) + const error = new Error( + `Error while running command \`${command} ${args.join(' ')}\`` ) - process.exit(result.code) + error.exitCode = result.code + throw error } return result @@ -588,14 +590,15 @@ const mergeLifecycleEvents = (lifecycle, other) => { return lifecycleCopy } -export const parseConfig = (yargs, configToml) => { +export const parseConfig = (yargs, rawConfigToml) => { + const configToml = envInterpolation(rawConfigToml) const config = toml.parse(configToml) let envConfig const emptyLifecycle = {} verifyConfig(config, yargs) - // start with an emtpy set of hooks, { before: {}, after: {} } + // start with an empty set of hooks, { before: {}, after: {} } for (const hook of LIFECYCLE_HOOKS) { emptyLifecycle[hook] = {} } @@ -676,6 +679,20 @@ export const commands = (yargs, ssh) => { } export const handler = async (yargs) => { + recordTelemetryAttributes({ + command: 'deploy baremetal', + firstRun: yargs.firstRun, + update: yargs.update, + install: yargs.install, + migrate: yargs.migrate, + build: yargs.build, + restart: yargs.restart, + cleanup: yargs.cleanup, + maintenance: yargs.maintenance, + rollback: yargs.rollback, + verbose: yargs.verbose, + }) + const { NodeSSH } = require('node-ssh') const ssh = new NodeSSH() diff --git a/packages/cli/src/commands/deploy/edgio.js b/packages/cli/src/commands/deploy/edgio.js index a446f63c8758..9cfc80f793fc 100644 --- a/packages/cli/src/commands/deploy/edgio.js +++ b/packages/cli/src/commands/deploy/edgio.js @@ -2,9 +2,10 @@ import path from 'path' import execa from 'execa' import fs from 'fs-extra' -import omit from 'lodash/omit' +import { omit } from 'lodash' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { getPaths } from '@redwoodjs/project-config' import c from '../../lib/colors' @@ -45,6 +46,14 @@ const execaOptions = { } export const handler = async (args) => { + recordTelemetryAttributes({ + command: 'deploy edgio', + skipInit: args.skipInit, + build: args.build, + prisma: args.prisma, + dataMigrate: args.dataMigrate, + }) + const { builder: edgioBuilder } = require('@edgio/cli/commands/deploy') const cwd = path.join(getPaths().base) diff --git a/packages/cli/src/commands/deploy/flightcontrol.js b/packages/cli/src/commands/deploy/flightcontrol.js index 0380c926e07c..1274773bc17a 100644 --- a/packages/cli/src/commands/deploy/flightcontrol.js +++ b/packages/cli/src/commands/deploy/flightcontrol.js @@ -3,10 +3,11 @@ import path from 'path' import execa from 'execa' import terminalLink from 'terminal-link' -import { apiServerHandler } from '@redwoodjs/api-server' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { getConfig } from '@redwoodjs/project-config' import { getPaths } from '../../lib' +import { apiServerHandler } from '../serveApiHandler' export const command = 'flightcontrol ' export const alias = 'fc' @@ -44,6 +45,13 @@ export const builder = (yargs) => { } export const handler = async ({ side, serve, prisma, dm: dataMigrate }) => { + recordTelemetryAttributes({ + command: 'deploy flightcontrol', + side, + prisma, + dataMigrate, + serve, + }) const rwjsPaths = getPaths() const execaConfig = { diff --git a/packages/cli/src/commands/deploy/netlify.js b/packages/cli/src/commands/deploy/netlify.js index 0eaeed87ba3c..0f9309d892eb 100644 --- a/packages/cli/src/commands/deploy/netlify.js +++ b/packages/cli/src/commands/deploy/netlify.js @@ -1,3 +1,5 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { deployBuilder, deployHandler } from './helpers/helpers' export const command = 'netlify [...commands]' @@ -5,4 +7,12 @@ export const description = 'Build command for Netlify deploy' export const builder = (yargs) => deployBuilder(yargs) -export const handler = deployHandler +export const handler = (yargs) => { + recordTelemetryAttributes({ + command: 'deploy netlify', + build: yargs.build, + prisma: yargs.prisma, + dataMigrate: yargs.dataMigrate, + }) + deployHandler(yargs) +} diff --git a/packages/cli/src/commands/deploy/packing/nft.js b/packages/cli/src/commands/deploy/packing/nft.js index 2dca4bf5ab7e..7c9cb979be5a 100644 --- a/packages/cli/src/commands/deploy/packing/nft.js +++ b/packages/cli/src/commands/deploy/packing/nft.js @@ -9,7 +9,7 @@ import { ensurePosixPath, getPaths } from '@redwoodjs/project-config' const ZIPBALL_DIR = './api/dist/zipball' -function zipDirectory(source, out) { +export function zipDirectory(source, out) { const archive = archiver('zip', { zlib: { level: 5 } }) const stream = fse.createWriteStream(out) @@ -25,7 +25,7 @@ function zipDirectory(source, out) { } // returns a tuple of [filePath, fileContent] -function generateEntryFile(functionAbsolutePath, name) { +export function generateEntryFile(functionAbsolutePath, name) { const relativeImport = ensurePosixPath( path.relative(getPaths().base, functionAbsolutePath) ) @@ -35,7 +35,7 @@ function generateEntryFile(functionAbsolutePath, name) { ] } -async function packageSingleFunction(functionFile) { +export async function packageSingleFunction(functionFile) { const { name: functionName } = path.parse(functionFile) const { fileList: functionDependencyFileList } = await nodeFileTrace([ @@ -67,18 +67,7 @@ async function packageSingleFunction(functionFile) { return } -function nftPack() { +export function nftPack() { const filesToBePacked = findApiDistFunctions() return Promise.all(filesToBePacked.map(exports.packageSingleFunction)) } - -// We do this, so we can spy the functions in the test -// It didn't make sense to separate into different files -const exports = { - nftPack, - packageSingleFunction, - generateEntryFile, - zipDirectory, -} - -export default exports diff --git a/packages/cli/src/commands/deploy/render.js b/packages/cli/src/commands/deploy/render.js index 39178ffbd1f8..1faf8a23eeea 100644 --- a/packages/cli/src/commands/deploy/render.js +++ b/packages/cli/src/commands/deploy/render.js @@ -3,10 +3,11 @@ import path from 'path' import execa from 'execa' import terminalLink from 'terminal-link' -import { apiServerHandler } from '@redwoodjs/api-server' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { getConfig } from '@redwoodjs/project-config' import { getPaths } from '../../lib' +import { apiServerHandler } from '../serveApiHandler' export const command = 'render ' export const description = 'Build, Migrate, and Serve command for Render deploy' @@ -44,6 +45,13 @@ if (process.argv.slice(2).includes('api')) { } export const handler = async ({ side, prisma, dm: dataMigrate }) => { + recordTelemetryAttributes({ + command: 'deploy render', + side, + prisma, + dataMigrate, + }) + const rwjsPaths = getPaths() const execaConfig = { diff --git a/packages/cli/src/commands/deploy/serverless.js b/packages/cli/src/commands/deploy/serverless.js new file mode 100644 index 000000000000..c3fc24ab0c77 --- /dev/null +++ b/packages/cli/src/commands/deploy/serverless.js @@ -0,0 +1,292 @@ +import path from 'path' + +import boxen from 'boxen' +import chalk from 'chalk' +import { config } from 'dotenv-defaults' +import execa from 'execa' +import fs from 'fs-extra' +import { Listr } from 'listr2' +import prompts from 'prompts' +import terminalLink from 'terminal-link' + +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + +import { getPaths } from '../../lib' +import c from '../../lib/colors' + +export const command = 'serverless' +export const aliases = ['aws serverless', 'sls'] +export const description = 'Deploy to AWS via the serverless framework' + +export const builder = (yargs) => { + yargs.option('stage', { + describe: + 'serverless stage pass through param: https://www.serverless.com/blog/stages-and-environments', + default: 'production', + type: 'string', + }) + + yargs.option('sides', { + describe: 'which Side(s) to deploy', + choices: ['api', 'web'], + default: ['api', 'web'], + alias: 'side', + type: 'array', + }) + + yargs.option('verbose', { + describe: 'verbosity of logs', + default: true, + type: 'boolean', + }) + + yargs.option('pack-only', { + describe: 'Only build and pack, and dont push code up using serverless', + default: false, + type: 'boolean', + }) + + yargs.option('first-run', { + describe: + 'Set this flag the first time you deploy, to configure your API URL on the webside', + default: false, + type: 'boolean', + }) + + yargs.epilogue( + `Also see the ${terminalLink( + 'Redwood CLI Reference', + 'https://redwoodjs.com/docs/cli-commands#deploy' + )}\n` + ) +} + +export const preRequisites = () => [ + { + title: 'Checking if Serverless framework is installed...', + command: ['yarn serverless', ['--version']], + errorMessage: [ + 'Looks like Serverless is not installed.', + 'Please run yarn add -W --dev serverless.', + ], + }, +] + +export const buildCommands = ({ sides }) => { + return [ + { + title: `Building ${sides.join(' & ')}...`, + command: ['yarn', ['rw', 'build', ...sides]], + }, + { + title: 'Packing Functions...', + enabled: () => sides.includes('api'), + task: async () => { + // Dynamically import this function + // because its dependencies are only installed when `rw setup deploy serverless` is run + const { nftPack } = await import('./packing/nft.js') + + await nftPack() + }, + }, + ] +} + +export const deployCommands = ({ stage, sides, firstRun, packOnly }) => { + const slsStage = stage ? ['--stage', stage] : [] + + return sides.map((side) => { + return { + title: `Deploying ${side}....`, + task: async () => { + await execa('yarn', ['serverless', 'deploy', ...slsStage], { + cwd: path.join(getPaths().base, side), + shell: true, + stdio: 'inherit', + cleanup: true, + }) + }, + skip: () => { + if (firstRun && side === 'web') { + return 'Skipping web deploy, until environment configured' + } + + if (packOnly) { + return 'Finishing early due to --pack-only flag. Your Redwood project is packaged and ready to deploy' + } + }, + } + }) +} + +const loadDotEnvForStage = (dotEnvPath) => { + // Make sure we use the correct .env based on the stage + config({ + path: dotEnvPath, + defaults: path.join(getPaths().base, '.env.defaults'), + encoding: 'utf8', + }) +} + +export const handler = async (yargs) => { + recordTelemetryAttributes({ + command: 'deploy serverless', + sides: JSON.stringify(yargs.sides), + verbose: yargs.verbose, + packOnly: yargs.packOnly, + firstRun: yargs.firstRun, + }) + + const rwjsPaths = getPaths() + const dotEnvPath = path.join(rwjsPaths.base, `.env.${yargs.stage}`) + + // Make sure .env.staging, .env.production, etc are loaded based on the --stage flag + loadDotEnvForStage(dotEnvPath) + + const tasks = new Listr( + [ + ...preRequisites(yargs).map(mapCommandsToListr), + ...buildCommands(yargs).map(mapCommandsToListr), + ...deployCommands(yargs).map(mapCommandsToListr), + ], + { + exitOnError: true, + renderer: yargs.verbose && 'verbose', + } + ) + try { + await tasks.run() + + if (yargs.firstRun) { + const SETUP_MARKER = chalk.bgBlue(chalk.black('First Setup ')) + console.log() + + console.log(SETUP_MARKER, c.green('Starting first setup wizard...')) + + const { stdout: slsInfo } = await execa( + `yarn serverless info --verbose --stage=${yargs.stage}`, + { + shell: true, + cwd: getPaths().api.base, + } + ) + + const deployedApiUrl = slsInfo.match(/HttpApiUrl: (https:\/\/.*)/)[1] + + console.log() + console.log(SETUP_MARKER, `Found ${c.green(deployedApiUrl)}`) + console.log() + + const { addDotEnv } = await prompts({ + type: 'confirm', + name: 'addDotEnv', + message: `Add API_URL to your .env.${yargs.stage}? This will be used if you deploy the web side from your machine`, + }) + + if (addDotEnv) { + fs.writeFileSync(dotEnvPath, `API_URL=${deployedApiUrl}`) + + // Reload dotenv, after adding the new file + loadDotEnvForStage(dotEnvPath) + } + + if (yargs.sides.includes('web')) { + console.log() + console.log(SETUP_MARKER, 'Deploying web side with updated API_URL') + + console.log( + SETUP_MARKER, + 'First deploys can take a good few minutes...' + ) + console.log() + + const webDeployTasks = new Listr( + [ + // Rebuild web with the new API_URL + ...buildCommands({ ...yargs, sides: ['web'], firstRun: false }).map( + mapCommandsToListr + ), + ...deployCommands({ + ...yargs, + sides: ['web'], + firstRun: false, + }).map(mapCommandsToListr), + ], + { + exitOnError: true, + renderer: yargs.verbose && 'verbose', + } + ) + + // Deploy the web side now that the API_URL has been configured + await webDeployTasks.run() + + const { stdout: slsInfo } = await execa( + `yarn serverless info --verbose --stage=${yargs.stage}`, + { + shell: true, + cwd: getPaths().web.base, + } + ) + + const deployedWebUrl = slsInfo.match(/url: (https:\/\/.*)/)[1] + + const message = [ + c.bold('Successful first deploy!'), + '', + `View your deployed site at: ${c.green(deployedWebUrl)}`, + '', + 'You can use serverless.com CI/CD by connecting/creating an app', + 'To do this run `yarn serverless` on each of the sides, and connect your account', + '', + 'Find more information in our docs:', + c.underline('https://redwoodjs.com/docs/deploy#serverless'), + ] + + console.log( + boxen(message.join('\n'), { + padding: { top: 0, bottom: 0, right: 1, left: 1 }, + margin: 1, + borderColor: 'gray', + }) + ) + } + } + } catch (e) { + console.error(c.error(e.message)) + process.exit(e?.exitCode || 1) + } +} + +const mapCommandsToListr = ({ + title, + command, + task, + cwd, + errorMessage, + skip, + enabled, +}) => { + return { + title, + task: task + ? task + : async () => { + try { + const executingCommand = execa(...command, { + cwd: cwd || getPaths().base, + shell: true, + }) + executingCommand.stdout.pipe(process.stdout) + await executingCommand + } catch (error) { + if (errorMessage) { + error.message = error.message + '\n' + errorMessage.join(' ') + } + throw error + } + }, + skip, + enabled, + } +} diff --git a/packages/cli/src/commands/deploy/vercel.js b/packages/cli/src/commands/deploy/vercel.js index 2616c9779637..675d22a69950 100644 --- a/packages/cli/src/commands/deploy/vercel.js +++ b/packages/cli/src/commands/deploy/vercel.js @@ -1,3 +1,5 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { deployBuilder, deployHandler } from './helpers/helpers' export const command = 'vercel [...commands]' @@ -5,4 +7,12 @@ export const description = 'Build command for Vercel deploy' export const builder = (yargs) => deployBuilder(yargs) -export const handler = deployHandler +export const handler = (yargs) => { + recordTelemetryAttributes({ + command: 'deploy vercel', + build: yargs.build, + prisma: yargs.prisma, + dataMigrate: yargs.dataMigrate, + }) + deployHandler(yargs) +} diff --git a/packages/cli/src/commands/destroy/cell/__tests__/cell.test.js b/packages/cli/src/commands/destroy/cell/__tests__/cell.test.js index 4ac811b8072c..2cde53ce55a2 100644 --- a/packages/cli/src/commands/destroy/cell/__tests__/cell.test.js +++ b/packages/cli/src/commands/destroy/cell/__tests__/cell.test.js @@ -16,7 +16,7 @@ jest.mock('@redwoodjs/structure', () => { } }) -import fs from 'fs' +import fs from 'fs-extra' import '../../../../lib/test' diff --git a/packages/cli/src/commands/destroy/component/__tests__/component.test.js b/packages/cli/src/commands/destroy/component/__tests__/component.test.js index cccc803db4c6..95d2013ca909 100644 --- a/packages/cli/src/commands/destroy/component/__tests__/component.test.js +++ b/packages/cli/src/commands/destroy/component/__tests__/component.test.js @@ -7,7 +7,7 @@ jest.mock('../../../../lib', () => { } }) -import fs from 'fs' +import fs from 'fs-extra' import '../../../../lib/test' diff --git a/packages/cli/src/commands/destroy/directive/__tests__/directive.test.js b/packages/cli/src/commands/destroy/directive/__tests__/directive.test.js index 3078a88ab229..b1b4552b5f1e 100644 --- a/packages/cli/src/commands/destroy/directive/__tests__/directive.test.js +++ b/packages/cli/src/commands/destroy/directive/__tests__/directive.test.js @@ -8,7 +8,7 @@ jest.mock('../../../../lib', () => { } }) -import fs from 'fs' +import fs from 'fs-extra' import '../../../../lib/test' diff --git a/packages/cli/src/commands/destroy/function/__tests__/function.test.js b/packages/cli/src/commands/destroy/function/__tests__/function.test.js index d72a403de010..fa302c5935a0 100644 --- a/packages/cli/src/commands/destroy/function/__tests__/function.test.js +++ b/packages/cli/src/commands/destroy/function/__tests__/function.test.js @@ -7,7 +7,7 @@ jest.mock('../../../../lib', () => { } }) -import fs from 'fs' +import fs from 'fs-extra' import '../../../../lib/test' diff --git a/packages/cli/src/commands/destroy/graphiql/graphiql.js b/packages/cli/src/commands/destroy/graphiql/graphiql.js index 997bfa40f69c..98314a02af4f 100644 --- a/packages/cli/src/commands/destroy/graphiql/graphiql.js +++ b/packages/cli/src/commands/destroy/graphiql/graphiql.js @@ -1,5 +1,7 @@ import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { existsAnyExtensionSync, deleteFile, @@ -33,6 +35,9 @@ export const command = 'graphiql' export const description = 'Destroy graphiql header' export const handler = () => { + recordTelemetryAttributes({ + command: 'destory graphiql', + }) const path = getOutputPath() const tasks = new Listr( [ @@ -46,7 +51,7 @@ export const handler = () => { task: removeGraphiqlFromGraphqlHandler, }, ], - { rendererOptions: { collapse: false }, exitOnError: true } + { rendererOptions: { collapseSubtasks: false }, exitOnError: true } ) try { tasks.run() diff --git a/packages/cli/src/commands/destroy/helpers.js b/packages/cli/src/commands/destroy/helpers.js index f7c4f128a937..b92b49602368 100644 --- a/packages/cli/src/commands/destroy/helpers.js +++ b/packages/cli/src/commands/destroy/helpers.js @@ -1,7 +1,8 @@ import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { deleteFilesTask } from '../../lib' -import c from '../../lib/colors' const tasks = ({ componentName, filesFn, name }) => new Listr( @@ -14,7 +15,7 @@ const tasks = ({ componentName, filesFn, name }) => }, }, ], - { rendererOptions: { collapse: false }, exitOnError: true } + { rendererOptions: { collapseSubtasks: false }, exitOnError: true } ) export const createYargsForComponentDestroy = ({ @@ -32,12 +33,11 @@ export const createYargsForComponentDestroy = ({ }) }, handler: async (options) => { - try { - options = await preTasksFn({ ...options, isDestroyer: true }) - await tasks({ componentName, filesFn, name: options.name }).run() - } catch (e) { - console.log(c.error(e.message)) - } + recordTelemetryAttributes({ + command: `destory ${componentName}`, + }) + options = await preTasksFn({ ...options, isDestroyer: true }) + await tasks({ componentName, filesFn, name: options.name }).run() }, tasks, } diff --git a/packages/cli/src/commands/destroy/layout/__tests__/layout.test.js b/packages/cli/src/commands/destroy/layout/__tests__/layout.test.js index f0d970bc1d7f..751e54ceac00 100644 --- a/packages/cli/src/commands/destroy/layout/__tests__/layout.test.js +++ b/packages/cli/src/commands/destroy/layout/__tests__/layout.test.js @@ -7,7 +7,7 @@ jest.mock('../../../../lib', () => { } }) -import fs from 'fs' +import fs from 'fs-extra' import '../../../../lib/test' diff --git a/packages/cli/src/commands/destroy/page/__tests__/page.test.js b/packages/cli/src/commands/destroy/page/__tests__/page.test.js index d6182d32a45a..6e2fd1e6b752 100644 --- a/packages/cli/src/commands/destroy/page/__tests__/page.test.js +++ b/packages/cli/src/commands/destroy/page/__tests__/page.test.js @@ -7,7 +7,7 @@ jest.mock('../../../../lib', () => { } }) -import fs from 'fs' +import fs from 'fs-extra' import '../../../../lib/test' diff --git a/packages/cli/src/commands/destroy/page/page.js b/packages/cli/src/commands/destroy/page/page.js index 1701fd404999..d4de305c849e 100644 --- a/packages/cli/src/commands/destroy/page/page.js +++ b/packages/cli/src/commands/destroy/page/page.js @@ -1,6 +1,8 @@ import camelcase from 'camelcase' import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { deleteFilesTask, removeRoutesFromRouterTask } from '../../../lib' import c from '../../../lib/colors' import { pathName } from '../../generate/helpers' @@ -44,10 +46,13 @@ export const tasks = ({ name, path }) => task: async () => removeRoutesFromRouterTask([camelcase(name)]), }, ], - { rendererOptions: { collapse: false }, exitOnError: true } + { rendererOptions: { collapseSubtasks: false }, exitOnError: true } ) export const handler = async ({ name, path }) => { + recordTelemetryAttributes({ + command: 'destory page', + }) const t = tasks({ name, path }) try { await t.run() diff --git a/packages/cli/src/commands/destroy/scaffold/__tests__/scaffold.test.js b/packages/cli/src/commands/destroy/scaffold/__tests__/scaffold.test.js index 1baba32c507c..61ae251c0742 100644 --- a/packages/cli/src/commands/destroy/scaffold/__tests__/scaffold.test.js +++ b/packages/cli/src/commands/destroy/scaffold/__tests__/scaffold.test.js @@ -1,8 +1,9 @@ globalThis.__dirname = __dirname -import fs from 'fs' import path from 'path' +import fs from 'fs-extra' + import '../../../../lib/test' import { getPaths, getDefaultArgs } from '../../../../lib' diff --git a/packages/cli/src/commands/destroy/scaffold/__tests__/scaffoldNoNest.test.js b/packages/cli/src/commands/destroy/scaffold/__tests__/scaffoldNoNest.test.js index ee69c8702f59..09af6b5d0c6b 100644 --- a/packages/cli/src/commands/destroy/scaffold/__tests__/scaffoldNoNest.test.js +++ b/packages/cli/src/commands/destroy/scaffold/__tests__/scaffoldNoNest.test.js @@ -1,8 +1,9 @@ globalThis.__dirname = __dirname -import fs from 'fs' import path from 'path' +import fs from 'fs-extra' + import '../../../../lib/test' import { getPaths, getDefaultArgs } from '../../../../lib' diff --git a/packages/cli/src/commands/destroy/scaffold/scaffold.js b/packages/cli/src/commands/destroy/scaffold/scaffold.js index 83c4b7e38a80..7b6ef8194fcf 100644 --- a/packages/cli/src/commands/destroy/scaffold/scaffold.js +++ b/packages/cli/src/commands/destroy/scaffold/scaffold.js @@ -1,6 +1,8 @@ import { Listr } from 'listr2' import pascalcase from 'pascalcase' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { deleteFilesTask, getPaths, @@ -33,7 +35,7 @@ const removeSetImport = () => { const routesPath = getPaths().web.routes const routesContent = readFile(routesPath).toString() if (routesContent.match(' { ) writeFile(routesPath, newRoutesContent, { overwriteExisting: true }) - return 'Removed Set import in Routes.{js,tsx}' + return 'Removed Set import in Routes.{jsx,tsx}' } const removeLayoutImport = ({ model: name, path: scaffoldPath = '' }) => { @@ -67,7 +69,7 @@ const removeLayoutImport = ({ model: name, path: scaffoldPath = '' }) => { writeFile(routesPath, newRoutesContent, { overwriteExisting: true }) - return 'Removed layout import from Routes.{js,tsx}' + return 'Removed layout import from Routes.{jsx,tsx}' } export const builder = (yargs) => { @@ -89,6 +91,7 @@ export const tasks = ({ model, path, tests, nestScaffoldByModel }) => tests, nestScaffoldByModel, }) + return deleteFilesTask(f) }, }, @@ -106,10 +109,13 @@ export const tasks = ({ model, path, tests, nestScaffoldByModel }) => task: () => removeLayoutImport({ model, path }), }, ], - { rendererOptions: { collapse: false }, exitOnError: true } + { rendererOptions: { collapseSubtasks: false }, exitOnError: true } ) export const handler = async ({ model: modelArg }) => { + recordTelemetryAttributes({ + command: 'destory scaffold', + }) const { model, path } = splitPathAndModel(modelArg) try { const { name } = await verifyModelName({ name: model, isDestroyer: true }) diff --git a/packages/cli/src/commands/destroy/sdl/__tests__/sdl.test.js b/packages/cli/src/commands/destroy/sdl/__tests__/sdl.test.js index edd57eea089b..b1577c9a0370 100644 --- a/packages/cli/src/commands/destroy/sdl/__tests__/sdl.test.js +++ b/packages/cli/src/commands/destroy/sdl/__tests__/sdl.test.js @@ -1,6 +1,6 @@ globalThis.__dirname = __dirname -import fs from 'fs' +import fs from 'fs-extra' import '../../../../lib/test' @@ -26,13 +26,13 @@ jest.mock('../../../../lib/schemaHelpers', () => { } }) -describe('rw destory sdl', () => { +describe('rw destroy sdl', () => { afterEach(() => { fs.__setMockFiles({}) jest.spyOn(fs, 'unlinkSync').mockClear() }) - describe('for javascipt files', () => { + describe('for javascript files', () => { beforeEach(async () => { fs.__setMockFiles( await files({ ...getDefaultArgs(builder), name: 'Post' }) diff --git a/packages/cli/src/commands/destroy/sdl/sdl.js b/packages/cli/src/commands/destroy/sdl/sdl.js index f4755737948e..e6b2c26d96ad 100644 --- a/packages/cli/src/commands/destroy/sdl/sdl.js +++ b/packages/cli/src/commands/destroy/sdl/sdl.js @@ -1,5 +1,7 @@ import { Listr } from 'listr2' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + import { deleteFilesTask } from '../../../lib' import c from '../../../lib/colors' import { verifyModelName } from '../../../lib/schemaHelpers' @@ -27,10 +29,13 @@ export const tasks = ({ model }) => }, }, ], - { rendererOptions: { collapse: false }, exitOnError: true } + { rendererOptions: { collapseSubtasks: false }, exitOnError: true } ) export const handler = async ({ model }) => { + recordTelemetryAttributes({ + command: 'destory sdl', + }) try { const { name } = await verifyModelName({ name: model, isDestroyer: true }) await tasks({ model: name }).run() diff --git a/packages/cli/src/commands/destroy/service/__tests__/service.test.js b/packages/cli/src/commands/destroy/service/__tests__/service.test.js index 02b7ce99e108..6973bb303a96 100644 --- a/packages/cli/src/commands/destroy/service/__tests__/service.test.js +++ b/packages/cli/src/commands/destroy/service/__tests__/service.test.js @@ -1,5 +1,5 @@ globalThis.__dirname = __dirname -import fs from 'fs' +import fs from 'fs-extra' import '../../../../lib/test' @@ -25,7 +25,7 @@ jest.mock('../../../../lib/schemaHelpers', () => { } }) -describe('rw destory service', () => { +describe('rw destroy service', () => { beforeEach(() => { jest.spyOn(console, 'info').mockImplementation(() => {}) jest.spyOn(console, 'log').mockImplementation(() => {}) diff --git a/packages/cli/src/commands/dev.js b/packages/cli/src/commands/dev.js index 3c86f4272707..de9b2946bb22 100644 --- a/packages/cli/src/commands/dev.js +++ b/packages/cli/src/commands/dev.js @@ -1,9 +1,11 @@ import terminalLink from 'terminal-link' -import checkForBabelConfig from '../middleware/checkForBabelConfig' +import c from '../lib/colors' +import { checkNodeVersion } from '../middleware/checkNodeVersion' export const command = 'dev [side..]' export const description = 'Start development servers for api, and web' + export const builder = (yargs) => { yargs .positional('side', { @@ -32,7 +34,15 @@ export const builder = (yargs) => { description: 'Port on which to expose API server debugger. If you supply the flag with no value it defaults to 18911.', }) - .middleware(checkForBabelConfig) + .middleware(() => { + const check = checkNodeVersion() + + if (check.ok) { + return + } + + console.warn(`${c.warning('Warning')}: ${check.message}\n`) + }) .epilogue( `Also see the ${terminalLink( 'Redwood CLI Reference', @@ -42,6 +52,6 @@ export const builder = (yargs) => { } export const handler = async (options) => { - const { handler } = await import('./devHandler') + const { handler } = await import('./devHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/devHandler.js b/packages/cli/src/commands/devHandler.js index 918f3a2afa95..e4ed83085460 100644 --- a/packages/cli/src/commands/devHandler.js +++ b/packages/cli/src/commands/devHandler.js @@ -1,14 +1,16 @@ -import fs from 'fs' import { argv } from 'process' import concurrently from 'concurrently' +import fs from 'fs-extra' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { shutdownPort } from '@redwoodjs/internal/dist/dev' import { getConfig, getConfigPath } from '@redwoodjs/project-config' import { errorTelemetry } from '@redwoodjs/telemetry' import { getPaths } from '../lib' import c from '../lib/colors' +import { exitWithError } from '../lib/exit' import { generatePrismaClient } from '../lib/generatePrismaClient' import { getFreePort } from '../lib/ports' @@ -21,6 +23,13 @@ export const handler = async ({ watchNodeModules = process.env.RWJS_WATCH_NODE_MODULES === '1', apiDebugPort, }) => { + recordTelemetryAttributes({ + command: 'dev', + side: JSON.stringify(side), + generate, + watchNodeModules, + }) + const rwjsPaths = getPaths() // Starting values of ports from config (redwood.toml) @@ -37,8 +46,9 @@ export const handler = async ({ if (side.includes('api')) { apiAvailablePort = await getFreePort(apiPreferredPort) if (apiAvailablePort === -1) { - console.error(`Error could not determine a free port for the api server`) - process.exit(1) + exitWithError(undefined, { + message: `Could not determine a free port for the api server`, + }) } apiPortChangeNeeded = apiAvailablePort !== apiPreferredPort } @@ -50,16 +60,16 @@ export const handler = async ({ ...forward.matchAll(/\-\-port(\=|\s)(?[^\s]*)/g), ] if (forwardedPortMatches.length) { - webPreferredPort = forwardedPortMatches.pop().groups.port + webPreferredPort = parseInt(forwardedPortMatches.pop().groups.port) } - webAvailablePort = await getFreePort(webPreferredPort, [ apiPreferredPort, apiAvailablePort, ]) if (webAvailablePort === -1) { - console.error(`Error could not determine a free port for the web server`) - process.exit(1) + exitWithError(undefined, { + message: `Could not determine a free port for the web server`, + }) } webPortChangeNeeded = webAvailablePort !== webPreferredPort } @@ -73,11 +83,10 @@ export const handler = async ({ message += webPortChangeNeeded ? ` - Web to use port ${webAvailablePort} instead of your currently configured ${webPreferredPort}\n` : `` - console.error(message) - console.error( - `Cannot run the development server until your configured ports are changed or become available.` - ) - process.exit(1) + message += `\nCannot run the development server until your configured ports are changed or become available.` + exitWithError(undefined, { + message, + }) } if (side.includes('api')) { @@ -139,18 +148,37 @@ export const handler = async ({ const redwoodConfigPath = getConfigPath() - const webCommand = - getConfig().web.bundler === 'vite' // @NOTE: can't use enums, not TS - ? `yarn cross-env NODE_ENV=development rw-vite-dev` - : `yarn cross-env NODE_ENV=development RWJS_WATCH_NODE_MODULES=${ - watchNodeModules ? '1' : '' - } webpack serve --config "${webpackDevConfig}" ${forward}` + const streamingSsrEnabled = getConfig().experimental.streamingSsr?.enabled + + // @TODO (Streaming) Lot of temporary feature flags for started dev server. + // Written this way to make it easier to read + + // 1. default: Vite (SPA) + let webCommand = `yarn cross-env NODE_ENV=development rw-vite-dev ${forward}` + + // 2. Vite with SSR + if (streamingSsrEnabled) { + webCommand = `yarn cross-env NODE_ENV=development rw-dev-fe ${forward}` + } + + // 3. Webpack (SPA): we will remove this override after v7 + if (getConfig().web.bundler === 'webpack') { + if (streamingSsrEnabled) { + throw new Error( + 'Webpack does not support SSR. Please switch your bundler to Vite in redwood.toml first' + ) + } else { + webCommand = `yarn cross-env NODE_ENV=development RWJS_WATCH_NODE_MODULES=${ + watchNodeModules ? '1' : '' + } webpack serve --config "${webpackDevConfig}" ${forward}` + } + } /** @type {Record} */ const jobs = { api: { name: 'api', - command: `yarn cross-env NODE_ENV=development NODE_OPTIONS=--enable-source-maps yarn nodemon --quiet --watch "${redwoodConfigPath}" --exec "yarn rw-api-server-watch --port ${apiAvailablePort} ${getApiDebugFlag()} | rw-log-formatter"`, + command: `yarn cross-env NODE_ENV=development NODE_OPTIONS="${getDevNodeOptions()}" yarn nodemon --quiet --watch "${redwoodConfigPath}" --exec "yarn rw-api-server-watch --port ${apiAvailablePort} ${getApiDebugFlag()} | rw-log-formatter"`, prefixColor: 'cyan', runWhen: () => fs.existsSync(rwjsPaths.api.src), }, @@ -190,8 +218,28 @@ export const handler = async ({ process.argv, `Error concurrently starting sides: ${e.message}` ) - console.error(c.error(e.message)) - process.exit(1) + exitWithError(e) } }) } + +/** + * Gets the value of the `NODE_OPTIONS` env var from `process.env`, appending `--enable-source-maps` if it's not already there. + * See https://nodejs.org/api/cli.html#node_optionsoptions. + * + * @returns {string} + */ +export function getDevNodeOptions() { + const { NODE_OPTIONS } = process.env + const enableSourceMapsOption = '--enable-source-maps' + + if (!NODE_OPTIONS) { + return enableSourceMapsOption + } + + if (NODE_OPTIONS.includes(enableSourceMapsOption)) { + return NODE_OPTIONS + } + + return `${NODE_OPTIONS} ${enableSourceMapsOption}` +} diff --git a/packages/cli/src/commands/exec.js b/packages/cli/src/commands/exec.js index 2eefa62a2191..c171ed9974b6 100644 --- a/packages/cli/src/commands/exec.js +++ b/packages/cli/src/commands/exec.js @@ -29,6 +29,6 @@ export const builder = (yargs) => { } export const handler = async (options) => { - const { handler } = await import('./execHandler') + const { handler } = await import('./execHandler.js') return handler(options) } diff --git a/packages/cli/src/commands/execHandler.js b/packages/cli/src/commands/execHandler.js index 4a6f0936e487..09b0511499f2 100644 --- a/packages/cli/src/commands/execHandler.js +++ b/packages/cli/src/commands/execHandler.js @@ -1,9 +1,14 @@ import path from 'path' +import { context } from '@opentelemetry/api' +import { suppressTracing } from '@opentelemetry/core' import { Listr } from 'listr2' -import { registerApiSideBabelHook } from '@redwoodjs/internal/dist/build/babel/api' -import { getWebSideDefaultBabelConfig } from '@redwoodjs/internal/dist/build/babel/web' +import { + getWebSideDefaultBabelConfig, + registerApiSideBabelHook, +} from '@redwoodjs/babel-config' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' import { findScripts } from '@redwoodjs/internal/dist/files' import { getPaths } from '../lib' @@ -21,6 +26,12 @@ const printAvailableScriptsToConsole = () => { } export const handler = async (args) => { + recordTelemetryAttributes({ + command: 'exec', + prisma: args.prisma, + list: args.list, + }) + const { name, prisma, list, ...scriptArgs } = args if (list || !name) { printAvailableScriptsToConsole() @@ -123,14 +134,12 @@ export const handler = async (args) => { ] const tasks = new Listr(scriptTasks, { - rendererOptions: { collapse: false }, + rendererOptions: { collapseSubtasks: false }, renderer: 'verbose', }) - try { + // Prevent user project telemetry from within the script from being recorded + await context.with(suppressTracing(context.active()), async () => { await tasks.run() - } catch (e) { - console.error(c.error(`The script exited with errors.`)) - process.exit(e?.exitCode || 1) - } + }) } diff --git a/packages/cli/src/commands/experimental.js b/packages/cli/src/commands/experimental.js new file mode 100644 index 000000000000..32e235b31282 --- /dev/null +++ b/packages/cli/src/commands/experimental.js @@ -0,0 +1,29 @@ +import terminalLink from 'terminal-link' + +import detectRwVersion from '../middleware/detectProjectRwVersion' + +export const command = 'experimental ' +export const aliases = ['exp'] +export const description = 'Run or setup experimental features' + +export const builder = (yargs) => + yargs + .commandDir('./experimental', { + recurse: true, + // @NOTE This regex will ignore all commands nested more than two + // levels deep. + // e.g. /setup/hi.js & setup/hi/hi.js are picked up, but + // setup/hi/hello/bazinga.js will be ignored + // The [/\\] bit is for supporting both windows and unix style paths + // Also take care to not trip up on paths that have "setup" earlier + // in the path by eagerly matching in the start of the regexp + exclude: /.*[/\\]experimental[/\\].*[/\\].*[/\\]/, + }) + .demandCommand() + .middleware(detectRwVersion) + .epilogue( + `Also see the ${terminalLink( + 'Redwood CLI Reference', + 'https://redwoodjs.com/docs/cli-commands#experimental' + )}` + ) diff --git a/packages/cli/src/commands/experimental/__tests__/setupDocker.test.js b/packages/cli/src/commands/experimental/__tests__/setupDocker.test.js new file mode 100644 index 000000000000..5a1803bad42a --- /dev/null +++ b/packages/cli/src/commands/experimental/__tests__/setupDocker.test.js @@ -0,0 +1,52 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + +import { command, description, builder, handler } from '../setupDocker' + +jest.mock('../setupDockerHandler.js') + +jest.mock('@redwoodjs/cli-helpers', () => { + return { + recordTelemetryAttributes: jest.fn(), + } +}) + +describe('setupDocker', () => { + test("command didn't change unintentionally", () => { + expect(command).toMatchInlineSnapshot(`"setup-docker"`) + }) + + test("description didn't change unintentionally", () => { + expect(description).toMatchInlineSnapshot( + `"Setup the experimental Dockerfile"` + ) + }) + + test('builder configures command options force and verbose ', () => { + const yargs = { + option: jest.fn(() => yargs), + epilogue: jest.fn(() => yargs), + } + + builder(yargs) + + expect(yargs.option.mock.calls[0][0]).toMatchInlineSnapshot(`"force"`) + expect(yargs.option.mock.calls[0][1]).toMatchInlineSnapshot(` + { + "alias": "f", + "default": false, + "description": "Overwrite existing configuration", + "type": "boolean", + } + `) + }) + + test('the handler calls recordTelemetryAttributes', async () => { + await handler({}) + + expect(recordTelemetryAttributes).toHaveBeenCalledWith({ + command: 'experimental setup-docker', + force: undefined, + verbose: undefined, + }) + }) +}) diff --git a/packages/cli/src/commands/experimental/setupDocker.js b/packages/cli/src/commands/experimental/setupDocker.js new file mode 100644 index 000000000000..024dfd4fb786 --- /dev/null +++ b/packages/cli/src/commands/experimental/setupDocker.js @@ -0,0 +1,31 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + +import { getEpilogue } from './util' + +export const EXPERIMENTAL_TOPIC_ID = null + +export const command = 'setup-docker' + +export const description = 'Setup the experimental Dockerfile' + +export function builder(yargs) { + yargs + .option('force', { + alias: 'f', + default: false, + description: 'Overwrite existing configuration', + type: 'boolean', + }) + .epilogue(getEpilogue(command, description, EXPERIMENTAL_TOPIC_ID, true)) +} + +export async function handler(options) { + recordTelemetryAttributes({ + command: 'experimental setup-docker', + force: options.force, + verbose: options.verbose, + }) + + const { handler } = await import('./setupDockerHandler.js') + return handler(options) +} diff --git a/packages/cli/src/commands/experimental/setupDockerHandler.js b/packages/cli/src/commands/experimental/setupDockerHandler.js new file mode 100644 index 000000000000..c4f9170aea67 --- /dev/null +++ b/packages/cli/src/commands/experimental/setupDockerHandler.js @@ -0,0 +1,347 @@ +import path from 'path' + +import execa from 'execa' +import fs from 'fs-extra' +import { Listr } from 'listr2' + +import { writeFile } from '@redwoodjs/cli-helpers' +import { getConfig, getConfigPath, getPaths } from '@redwoodjs/project-config' +import { errorTelemetry } from '@redwoodjs/telemetry' + +import c from '../../lib/colors' + +export async function handler({ force }) { + const TEMPLATE_DIR = path.join(__dirname, 'templates', 'docker') + + let dockerfileTemplateContent = fs.readFileSync( + path.resolve(TEMPLATE_DIR, 'Dockerfile'), + 'utf-8' + ) + const dockerComposeDevTemplateContent = fs.readFileSync( + path.resolve(TEMPLATE_DIR, 'docker-compose.dev.yml'), + 'utf-8' + ) + const dockerComposeProdTemplateContent = fs.readFileSync( + path.resolve(TEMPLATE_DIR, 'docker-compose.prod.yml'), + 'utf-8' + ) + const dockerignoreTemplateContent = fs.readFileSync( + path.resolve(TEMPLATE_DIR, 'dockerignore'), + 'utf-8' + ) + + const dockerfilePath = path.join(getPaths().base, 'Dockerfile') + const dockerComposeDevFilePath = path.join( + getPaths().base, + 'docker-compose.dev.yml' + ) + const dockerComposeProdFilePath = path.join( + getPaths().base, + 'docker-compose.prod.yml' + ) + const dockerignoreFilePath = path.join(getPaths().base, '.dockerignore') + + const tasks = new Listr( + [ + { + title: 'Confirmation', + task: async (_ctx, task) => { + const confirmation = await task.prompt({ + type: 'Confirm', + message: 'The Dockerfile is experimental. Continue?', + }) + + if (!confirmation) { + throw new Error('User aborted') + } + }, + skip: force, + }, + + { + title: 'Adding the official yarn workspace-tools plugin...', + task: async (_ctx, task) => { + const { stdout } = await execa.command('yarn plugin runtime --json', { + cwd: getPaths().base, + }) + + const hasWorkspaceToolsPlugin = stdout + .trim() + .split('\n') + .map(JSON.parse) + .some(({ name }) => name === '@yarnpkg/plugin-workspace-tools') + + if (hasWorkspaceToolsPlugin) { + task.skip( + 'The official yarn workspace-tools plugin is already installed' + ) + return + } + + return execa.command('yarn plugin import workspace-tools', { + cwd: getPaths().base, + }).stdout + }, + }, + + { + title: 'Adding @redwoodjs/api-server and @redwoodjs/web-server...', + task: async (_ctx, task) => { + const apiServerPackageName = '@redwoodjs/api-server' + const { dependencies: apiDependencies } = fs.readJSONSync( + path.join(getPaths().api.base, 'package.json') + ) + const hasApiServerPackage = + Object.keys(apiDependencies).includes(apiServerPackageName) + + const webServerPackageName = '@redwoodjs/web-server' + const { dependencies: webDependencies } = fs.readJSONSync( + path.join(getPaths().web.base, 'package.json') + ) + const hasWebServerPackage = + Object.keys(webDependencies).includes(webServerPackageName) + + if (hasApiServerPackage && hasWebServerPackage) { + task.skip( + `${apiServerPackageName} and ${webServerPackageName} are already installed` + ) + return + } + + if (!hasApiServerPackage) { + const apiServerPackageVersion = + await getVersionOfRedwoodPackageToInstall(apiServerPackageName) + + await execa.command( + `yarn workspace api add ${apiServerPackageName}@${apiServerPackageVersion}`, + { + cwd: getPaths().base, + } + ) + } + + if (!hasWebServerPackage) { + const webServerPackageVersion = + await getVersionOfRedwoodPackageToInstall(webServerPackageName) + + await execa.command( + `yarn workspace web add ${webServerPackageName}@${webServerPackageVersion}`, + { + cwd: getPaths().base, + } + ) + } + + return execa.command(`yarn dedupe`, { + cwd: getPaths().base, + }).stdout + }, + }, + + { + title: 'Adding the experimental Dockerfile and compose files...', + task: (_ctx, task) => { + const shouldSkip = [ + dockerfilePath, + dockerComposeDevFilePath, + dockerComposeProdFilePath, + dockerignoreFilePath, + ].every(fs.existsSync) + + if (!force && shouldSkip) { + task.skip('The Dockerfile and compose files already exist') + return + } + + const config = getConfig() + const { includeEnvironmentVariables } = config.web + + if (includeEnvironmentVariables.length) { + const webBuildWithPrerenderStageDelimeter = + 'FROM api_build as web_build_with_prerender\n' + const webBuildStageDelimeter = 'FROM base as web_build\n' + + const [ + beforeWebBuildWithPrerenderStageDelimeter, + afterWebBuildWithPrerenderStageDelimeter, + ] = dockerfileTemplateContent.split( + webBuildWithPrerenderStageDelimeter + ) + + const [beforeWebBuildStageDelimeter, afterWebBuildStageDelimeter] = + afterWebBuildWithPrerenderStageDelimeter.split( + webBuildStageDelimeter + ) + + dockerfileTemplateContent = [ + beforeWebBuildWithPrerenderStageDelimeter.trim(), + webBuildWithPrerenderStageDelimeter, + ...includeEnvironmentVariables.map((envVar) => `ARG ${envVar}`), + '', + beforeWebBuildStageDelimeter.trim(), + webBuildStageDelimeter, + ...includeEnvironmentVariables.map((envVar) => `ARG ${envVar}`), + afterWebBuildStageDelimeter, + ].join('\n') + } + + writeFile( + dockerfilePath, + dockerfileTemplateContent, + { + existingFiles: force ? 'OVERWRITE' : 'SKIP', + }, + task + ) + writeFile( + dockerComposeDevFilePath, + dockerComposeDevTemplateContent, + { + existingFiles: force ? 'OVERWRITE' : 'SKIP', + }, + task + ) + writeFile( + dockerComposeProdFilePath, + dockerComposeProdTemplateContent, + { existingFiles: force ? 'OVERWRITE' : 'SKIP' }, + task + ) + writeFile( + dockerignoreFilePath, + dockerignoreTemplateContent, + { + existingFiles: force ? 'OVERWRITE' : 'SKIP', + }, + task + ) + }, + }, + + { + title: 'Adding postgres to .gitignore...', + task: (_ctx, task) => { + const gitignoreFilePath = path.join(getPaths().base, '.gitignore') + const gitignoreFileContent = fs.readFileSync( + gitignoreFilePath, + 'utf-8' + ) + + if (gitignoreFileContent.includes('postgres')) { + task.skip('postgres is already ignored by git') + return + } + + writeFile( + gitignoreFilePath, + gitignoreFileContent.concat('\npostgres\n'), + { existingFiles: 'OVERWRITE' } + ) + }, + }, + + { + title: 'Adding config to redwood.toml...', + task: (_ctx, task) => { + const redwoodTomlPath = getConfigPath() + let configContent = fs.readFileSync(redwoodTomlPath, 'utf-8') + + const browserOpenRegExp = /open\s*=\s*true/ + + const hasOpenSetToTrue = browserOpenRegExp.test(configContent) + const hasExperimentalDockerfileConfig = configContent.includes( + '[experimental.dockerfile]' + ) + + if (!hasOpenSetToTrue && hasExperimentalDockerfileConfig) { + task.skip( + `The [experimental.dockerfile] config block already exists in your 'redwood.toml' file` + ) + return + } + + if (hasOpenSetToTrue) { + configContent = configContent.replace( + /open\s*=\s*true/, + 'open = false' + ) + } + + if (!hasExperimentalDockerfileConfig) { + configContent = configContent.concat( + `\n[experimental.dockerfile]\n\tenabled = true\n` + ) + } + + // using string replace here to preserve comments and formatting. + writeFile(redwoodTomlPath, configContent, { + existingFiles: 'OVERWRITE', + }) + }, + }, + ], + + { + renderer: process.env.NODE_ENV === 'test' ? 'verbose' : 'default', + } + ) + + try { + await tasks.run() + + console.log( + [ + '', + "We've written four files:", + '', + '- ./Dockerfile', + '- ./.dockerignore', + '- ./docker-compose.dev.yml', + '- ./docker-compose.prod.yml', + '', + 'To start the docker compose dev:', + '', + ' docker compose -f docker-compose.dev.yml up ', + '', + 'Then, connect to the container and migrate your database:', + '', + ' docker compose -f ./docker-compose.dev.yml run --rm -it console /bin/bash', + ' root@...:/home/node/app# yarn rw prisma migrate dev', + '', + "Lastly, ensure you have Docker. If you don't, see https://docs.docker.com/desktop/", + '', + "There's a lot in the Dockerfile and there's a reason for every line.", + 'Be sure to check out the docs: https://redwoodjs.com/docs/docker', + ].join('\n') + ) + } catch (e) { + errorTelemetry(process.argv, e.message) + console.error(c.error(e.message)) + process.exit(e?.exitCode || 1) + } +} + +export async function getVersionOfRedwoodPackageToInstall(module) { + const packageJsonPath = require.resolve('@redwoodjs/cli/package.json', { + paths: [getPaths().base], + }) + let { version } = fs.readJSONSync(packageJsonPath) + + const packumentP = await fetch(`https://registry.npmjs.org/${module}`) + const packument = await packumentP.json() + + // If the version includes a plus, like '4.0.0-rc.428+dd79f1726' + // (all @canary, @next, and @rc packages do), get rid of everything after the plus. + if (version.includes('+')) { + version = version.split('+')[0] + } + + const versionIsPublished = Object.keys(packument.versions).includes(version) + + // Fallback to canary. This is most likely because it's a new package + if (!versionIsPublished) { + version = 'canary' + } + + return version +} diff --git a/packages/cli/src/commands/experimental/setupInngest.js b/packages/cli/src/commands/experimental/setupInngest.js new file mode 100644 index 000000000000..1c0f41199a6a --- /dev/null +++ b/packages/cli/src/commands/experimental/setupInngest.js @@ -0,0 +1,30 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + +import { getEpilogue } from './util' + +export const command = 'setup-inngest' + +export const description = + 'Setup Inngest for background, scheduled, delayed, multi-step, and fan-out jobs' + +export const EXPERIMENTAL_TOPIC_ID = 4866 + +export const builder = (yargs) => { + yargs + .option('force', { + alias: 'f', + default: false, + description: 'Overwrite existing configuration', + type: 'boolean', + }) + .epilogue(getEpilogue(command, description, EXPERIMENTAL_TOPIC_ID, true)) +} + +export const handler = async (options) => { + recordTelemetryAttributes({ + command: 'experimental setup-inngest', + force: options.force, + }) + const { handler } = await import('./setupInngestHandler.js') + return handler(options) +} diff --git a/packages/cli/src/commands/experimental/setupInngestHandler.js b/packages/cli/src/commands/experimental/setupInngestHandler.js new file mode 100644 index 000000000000..f9f26fc02c61 --- /dev/null +++ b/packages/cli/src/commands/experimental/setupInngestHandler.js @@ -0,0 +1,50 @@ +import execa from 'execa' +import { Listr } from 'listr2' + +import { errorTelemetry } from '@redwoodjs/telemetry' + +import { getPaths } from '../../lib' +import c from '../../lib/colors' + +import { command, description, EXPERIMENTAL_TOPIC_ID } from './setupInngest' +import { printTaskEpilogue } from './util' + +export const handler = async ({ force }) => { + const tasks = new Listr([ + { + title: `Adding Inngest setup packages for RedwoodJS ...`, + task: async () => { + await execa('yarn', ['add', '-D', 'inngest-setup-redwoodjs'], { + cwd: getPaths().base, + }) + }, + }, + { + task: async () => { + const pluginCommands = ['inngest-setup-redwoodjs', 'plugin'] + + if (force) { + pluginCommands.push('--force') + } + + await execa('yarn', [...pluginCommands], { + stdout: 'inherit', + cwd: getPaths().base, + }) + }, + }, + { + task: () => { + printTaskEpilogue(command, description, EXPERIMENTAL_TOPIC_ID) + }, + }, + ]) + + try { + await tasks.run() + } catch (e) { + errorTelemetry(process.argv, e.message) + console.error(c.error(e.message)) + process.exit(e?.exitCode || 1) + } +} diff --git a/packages/cli/src/commands/experimental/setupOpentelemetry.js b/packages/cli/src/commands/experimental/setupOpentelemetry.js new file mode 100644 index 000000000000..3c32e9457dfb --- /dev/null +++ b/packages/cli/src/commands/experimental/setupOpentelemetry.js @@ -0,0 +1,36 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + +import { getEpilogue } from './util' + +export const command = 'setup-opentelemetry' + +export const description = 'Setup OpenTelemetry within the API side' + +export const EXPERIMENTAL_TOPIC_ID = 4772 + +export const builder = (yargs) => { + yargs + .option('force', { + alias: 'f', + default: false, + description: 'Overwrite existing configuration', + type: 'boolean', + }) + .option('verbose', { + alias: 'v', + default: false, + description: 'Print more logs', + type: 'boolean', + }) + .epilogue(getEpilogue(command, description, EXPERIMENTAL_TOPIC_ID, true)) +} + +export const handler = async (options) => { + recordTelemetryAttributes({ + command: 'experimental setup-opentelemetry', + force: options.force, + verbose: options.verbose, + }) + const { handler } = await import('./setupOpentelemetryHandler.js') + return handler(options) +} diff --git a/packages/cli/src/commands/experimental/setupOpentelemetryHandler.js b/packages/cli/src/commands/experimental/setupOpentelemetryHandler.js new file mode 100644 index 000000000000..c0c75a5924cc --- /dev/null +++ b/packages/cli/src/commands/experimental/setupOpentelemetryHandler.js @@ -0,0 +1,227 @@ +import path from 'path' + +import execa from 'execa' +import fs from 'fs-extra' +import { Listr } from 'listr2' + +import { addApiPackages } from '@redwoodjs/cli-helpers' +import { getConfigPath, resolveFile } from '@redwoodjs/project-config' +import { errorTelemetry } from '@redwoodjs/telemetry' + +import { getPaths, transformTSToJS, writeFile } from '../../lib' +import c from '../../lib/colors' +import { isTypeScriptProject } from '../../lib/project' + +import { + command, + description, + EXPERIMENTAL_TOPIC_ID, +} from './setupOpentelemetry' +import { printTaskEpilogue } from './util' + +export const handler = async ({ force, verbose }) => { + const ts = isTypeScriptProject() + + // Used in multiple tasks + const opentelemetryScriptPath = `${getPaths().api.src}/opentelemetry.${ + ts ? 'ts' : 'js' + }` + + // TODO: Consider extracting these from the templates? Consider version pinning? + const opentelemetryPackages = [ + '@opentelemetry/api', + '@opentelemetry/instrumentation', + '@opentelemetry/exporter-trace-otlp-http', + '@opentelemetry/resources', + '@opentelemetry/sdk-node', + '@opentelemetry/semantic-conventions', + '@opentelemetry/instrumentation-http', + '@opentelemetry/instrumentation-fastify', + '@prisma/instrumentation', + ] + + const opentelemetryTasks = [ + { + title: `Adding OpenTelemetry setup files...`, + task: () => { + const setupTemplateContent = fs.readFileSync( + path.resolve(__dirname, 'templates', 'opentelemetry.ts.template'), + 'utf-8' + ) + const setupScriptContent = ts + ? setupTemplateContent + : transformTSToJS(opentelemetryScriptPath, setupTemplateContent) + + return [ + writeFile(opentelemetryScriptPath, setupScriptContent, { + overwriteExisting: force, + }), + ] + }, + }, + { + title: 'Adding config to redwood.toml...', + task: (_ctx, task) => { + const redwoodTomlPath = getConfigPath() + const configContent = fs.readFileSync(redwoodTomlPath, 'utf-8') + if (!configContent.includes('[experimental.opentelemetry]')) { + // Use string replace to preserve comments and formatting + writeFile( + redwoodTomlPath, + configContent.concat( + `\n[experimental.opentelemetry]\n\tenabled = true\n\twrapApi = true\n\tapiSdk = "${opentelemetryScriptPath}"` + ), + { + overwriteExisting: true, // redwood.toml always exists + } + ) + } else { + task.skip( + `The [experimental.opentelemetry] config block already exists in your 'redwood.toml' file.` + ) + } + }, + }, + { + title: 'Notice: GraphQL function update...', + enabled: () => { + return fs.existsSync( + resolveFile(path.join(getPaths().api.functions, 'graphql')) + ) + }, + task: (_ctx, task) => { + task.output = [ + "Please add the following to your 'createGraphQLHandler' function options to enable OTel for your graphql", + 'openTelemetryOptions: {', + ' resolvers: true,', + ' result: true,', + ' variables: true,', + '}', + '', + `Which can found at ${c.info( + path.join(getPaths().api.functions, 'graphql') + )}`, + ].join('\n') + }, + options: { persistentOutput: true }, + }, + { + title: 'Notice: GraphQL function update (server file)...', + enabled: () => { + return fs.existsSync( + resolveFile(path.join(getPaths().api.src, 'server')) + ) + }, + task: (_ctx, task) => { + task.output = [ + "Please add the following to your 'redwoodFastifyGraphQLServer' plugin options to enable OTel for your graphql", + 'openTelemetryOptions: {', + ' resolvers: true,', + ' result: true,', + ' variables: true,', + '}', + '', + `Which can found at ${c.info( + path.join(getPaths().api.src, 'server') + )}`, + ].join('\n') + }, + options: { persistentOutput: true }, + }, + addApiPackages(opentelemetryPackages), + ] + + const prismaTasks = [ + { + title: 'Setup Prisma OpenTelemetry...', + task: (_ctx, task) => { + const schemaPath = path.join(getPaths().api.db, 'schema.prisma') // TODO: schema file is already in getPaths()? + const schemaContent = fs.readFileSync(schemaPath, { + encoding: 'utf-8', + flag: 'r', + }) + + const clientConfig = schemaContent + .slice( + schemaContent.indexOf('generator client') + + 'generator client'.length, + schemaContent.indexOf( + '}', + schemaContent.indexOf('generator client') + ) + 1 + ) + .trim() + + const previewLineExists = clientConfig.includes('previewFeatures') + let newSchemaContents = schemaContent + if (previewLineExists) { + task.skip( + 'Please add "tracing" to your previewFeatures in prisma.schema' + ) + } else { + const newClientConfig = clientConfig.trim().split('\n') + newClientConfig.splice( + newClientConfig.length - 1, + 0, + 'previewFeatures = ["tracing"]' + ) + newSchemaContents = newSchemaContents.replace( + clientConfig, + newClientConfig.join('\n') + ) + } + + return writeFile(schemaPath, newSchemaContents, { + overwriteExisting: true, // We'll likely always already have this file in the project + }) + }, + }, + { + title: 'Regenerate the Prisma client...', + task: (_ctx, _task) => { + return execa(`yarn rw prisma generate`, { + stdio: 'inherit', + shell: true, + cwd: getPaths().web.base, + }) + }, + }, + ] + + const tasks = new Listr( + [ + { + title: 'Confirmation', + task: async (_ctx, task) => { + const confirmation = await task.prompt({ + type: 'Confirm', + message: 'OpenTelemetry support is experimental. Continue?', + }) + + if (!confirmation) { + throw new Error('User aborted') + } + }, + }, + ...opentelemetryTasks, + ...prismaTasks, + { + task: () => { + printTaskEpilogue(command, description, EXPERIMENTAL_TOPIC_ID) + }, + }, + ], + { + rendererOptions: { collapseSubtasks: false, persistentOutput: true }, + renderer: verbose ? 'verbose' : 'default', + } + ) + + try { + await tasks.run() + } catch (e) { + errorTelemetry(process.argv, e.message) + console.error(c.error(e.message)) + process.exit(e?.exitCode || 1) + } +} diff --git a/packages/cli/src/commands/experimental/setupRsc.js b/packages/cli/src/commands/experimental/setupRsc.js new file mode 100644 index 000000000000..9ad8ca5aca6b --- /dev/null +++ b/packages/cli/src/commands/experimental/setupRsc.js @@ -0,0 +1,29 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + +import { getEpilogue } from './util' + +export const command = 'setup-rsc' + +export const description = 'Enable React Server Components (RSC)' + +export const EXPERIMENTAL_TOPIC_ID = 5081 + +export const builder = (yargs) => { + yargs + .option('force', { + alias: 'f', + default: false, + description: 'Overwrite existing configuration', + type: 'boolean', + }) + .epilogue(getEpilogue(command, description, EXPERIMENTAL_TOPIC_ID, true)) +} + +export const handler = async (options) => { + recordTelemetryAttributes({ + command: ['experimental', command].join(' '), + force: options.force, + }) + const { handler } = await import('./setupRscHandler.js') + return handler(options) +} diff --git a/packages/cli/src/commands/experimental/setupRscHandler.js b/packages/cli/src/commands/experimental/setupRscHandler.js new file mode 100644 index 000000000000..8a8f5fad08b1 --- /dev/null +++ b/packages/cli/src/commands/experimental/setupRscHandler.js @@ -0,0 +1,363 @@ +import path from 'path' + +import fs from 'fs-extra' +import { Listr } from 'listr2' + +import { prettify } from '@redwoodjs/cli-helpers' +import { getConfig, getConfigPath } from '@redwoodjs/project-config' +import { errorTelemetry } from '@redwoodjs/telemetry' + +import { getPaths, writeFile } from '../../lib' +import c from '../../lib/colors' +import { isTypeScriptProject } from '../../lib/project' + +import { command, description, EXPERIMENTAL_TOPIC_ID } from './setupRsc' +import { printTaskEpilogue } from './util' + +export const handler = async ({ force, verbose }) => { + const rwPaths = getPaths() + const redwoodTomlPath = getConfigPath() + const configContent = fs.readFileSync(redwoodTomlPath, 'utf-8') + + const tasks = new Listr( + [ + { + title: 'Check prerequisites', + task: () => { + if (!rwPaths.web.entryClient || !rwPaths.web.viteConfig) { + throw new Error('Vite needs to be setup before you can enable RSCs') + } + + if (!getConfig().experimental?.streamingSsr?.enabled) { + throw new Error( + 'The Streaming SSR experimental feature must be enabled before you can enable RSCs' + ) + } + + if (!isTypeScriptProject()) { + throw new Error( + 'RSCs are only supported in TypeScript projects at this time' + ) + } + }, + }, + { + title: 'Adding config to redwood.toml...', + task: (_ctx, task) => { + if (!configContent.includes('[experimental.rsc]')) { + writeFile( + redwoodTomlPath, + configContent.concat('\n[experimental.rsc]\n enabled = true\n'), + { + overwriteExisting: true, // redwood.toml always exists + } + ) + } else { + if (force) { + task.output = 'Overwriting config in redwood.toml' + + writeFile( + redwoodTomlPath, + configContent.replace( + // Enable if it's currently disabled + '\n[experimental.rsc]\n enabled = false\n', + '\n[experimental.rsc]\n enabled = true\n' + ), + { + overwriteExisting: true, // redwood.toml always exists + } + ) + } else { + task.skip( + 'The [experimental.rsc] config block already exists in your `redwood.toml` file.' + ) + } + } + }, + options: { persistentOutput: true }, + }, + { + title: 'Adding entries.ts...', + task: async () => { + const entriesTemplate = fs.readFileSync( + path.resolve(__dirname, 'templates', 'rsc', 'entries.ts.template'), + 'utf-8' + ) + + // Can't use rwPaths.web.entries because it's not created yet + writeFile(path.join(rwPaths.web.src, 'entries.ts'), entriesTemplate, { + overwriteExisting: force, + }) + }, + }, + { + title: 'Adding Pages...', + task: async () => { + const homePageTemplate = fs.readFileSync( + path.resolve( + __dirname, + 'templates', + 'rsc', + 'HomePage.tsx.template' + ), + 'utf-8' + ) + const homePagePath = path.join( + rwPaths.web.pages, + 'HomePage', + 'HomePage.tsx' + ) + + writeFile(homePagePath, homePageTemplate, { + overwriteExisting: force, + }) + + const aboutPageTemplate = fs.readFileSync( + path.resolve( + __dirname, + 'templates', + 'rsc', + 'AboutPage.tsx.template' + ), + 'utf-8' + ) + const aboutPagePath = path.join( + rwPaths.web.pages, + 'AboutPage', + 'AboutPage.tsx' + ) + + writeFile(aboutPagePath, aboutPageTemplate, { + overwriteExisting: force, + }) + }, + }, + { + title: 'Adding Counter.tsx...', + task: async () => { + const counterTemplate = fs.readFileSync( + path.resolve(__dirname, 'templates', 'rsc', 'Counter.tsx.template'), + 'utf-8' + ) + const counterPath = path.join( + rwPaths.web.components, + 'Counter', + 'Counter.tsx' + ) + + writeFile(counterPath, counterTemplate, { + overwriteExisting: force, + }) + }, + }, + { + title: 'Adding AboutCounter.tsx...', + task: async () => { + const counterTemplate = fs.readFileSync( + path.resolve( + __dirname, + 'templates', + 'rsc', + 'AboutCounter.tsx.template' + ), + 'utf-8' + ) + const counterPath = path.join( + rwPaths.web.components, + 'Counter', + 'AboutCounter.tsx' + ) + + writeFile(counterPath, counterTemplate, { + overwriteExisting: force, + }) + }, + }, + { + title: 'Adding CSS files...', + task: async () => { + const files = [ + { + template: 'Counter.css.template', + path: ['components', 'Counter', 'Counter.css'], + }, + { + template: 'Counter.module.css.template', + path: ['components', 'Counter', 'Counter.module.css'], + }, + { + template: 'HomePage.css.template', + path: ['pages', 'HomePage', 'HomePage.css'], + }, + { + template: 'HomePage.module.css.template', + path: ['pages', 'HomePage', 'HomePage.module.css'], + }, + { + template: 'AboutPage.css.template', + path: ['pages', 'AboutPage', 'AboutPage.css'], + }, + ] + + files.forEach((file) => { + const template = fs.readFileSync( + path.resolve(__dirname, 'templates', 'rsc', file.template), + 'utf-8' + ) + const filePath = path.join(rwPaths.web.src, ...file.path) + + writeFile(filePath, template, { + overwriteExisting: force, + }) + }) + }, + }, + { + title: 'Adding Layout...', + task: async () => { + const layoutTemplate = fs.readFileSync( + path.resolve( + __dirname, + 'templates', + 'rsc', + 'NavigationLayout.tsx.template' + ), + 'utf-8' + ) + const layoutPath = path.join( + rwPaths.web.layouts, + 'NavigationLayout', + 'NavigationLayout.tsx' + ) + + writeFile(layoutPath, layoutTemplate, { overwriteExisting: force }) + + const cssTemplate = fs.readFileSync( + path.resolve( + __dirname, + 'templates', + 'rsc', + 'NavigationLayout.css.template' + ), + 'utf-8' + ) + const cssPath = path.join( + rwPaths.web.layouts, + 'NavigationLayout', + 'NavigationLayout.css' + ) + + writeFile(cssPath, cssTemplate, { overwriteExisting: force }) + }, + }, + { + title: 'Updating index.html...', + task: async () => { + let indexHtml = fs.readFileSync(rwPaths.web.html, 'utf-8') + + if ( + /\n\s*' + ) + + writeFile(rwPaths.web.html, indexHtml, { + overwriteExisting: true, + }) + }, + }, + { + title: 'Overwriting index.css...', + task: async () => { + const template = fs.readFileSync( + path.resolve(__dirname, 'templates', 'rsc', 'index.css.template'), + 'utf-8' + ) + const filePath = path.join(rwPaths.web.src, 'index.css') + + writeFile(filePath, template, { + overwriteExisting: true, + }) + }, + }, + { + title: 'Overwrite App.tsx...', + task: async () => { + const appTemplate = fs.readFileSync( + path.resolve(__dirname, 'templates', 'rsc', 'App.tsx.template'), + 'utf-8' + ) + + const appPath = + rwPaths.web.app ?? path.join(rwPaths.web.src, 'App.tsx') + + writeFile(appPath, appTemplate, { + overwriteExisting: true, + }) + }, + }, + { + title: 'Add React experimental types', + task: async () => { + const tsconfigPath = path.join(rwPaths.web.base, 'tsconfig.json') + const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, 'utf-8')) + + if (tsconfig.compilerOptions.types.includes('react/experimental')) { + return + } + + tsconfig.compilerOptions.types.push('react/experimental') + + writeFile( + tsconfigPath, + prettify('tsconfig.json', JSON.stringify(tsconfig, null, 2)), + { + overwriteExisting: true, + } + ) + }, + }, + // TODO (RSC): Remove this once we have a better way to handle routes. + // This is a total hack right now + { + title: 'Overwriting routes...', + task: async () => { + const routesTemplate = fs.readFileSync( + path.resolve(__dirname, 'templates', 'rsc', 'Routes.tsx.template'), + 'utf-8' + ) + + writeFile(rwPaths.web.routes, routesTemplate, { + overwriteExisting: true, + }) + }, + }, + { + task: () => { + printTaskEpilogue(command, description, EXPERIMENTAL_TOPIC_ID) + }, + }, + ], + { + rendererOptions: { collapseSubtasks: false, persistentOutput: true }, + renderer: verbose ? 'verbose' : 'default', + } + ) + + try { + await tasks.run() + } catch (e) { + errorTelemetry(process.argv, e.message) + console.error(c.error(e.message)) + process.exit(e?.exitCode || 1) + } +} diff --git a/packages/cli/src/commands/experimental/setupSentry.js b/packages/cli/src/commands/experimental/setupSentry.js new file mode 100644 index 000000000000..740dc67cbf2e --- /dev/null +++ b/packages/cli/src/commands/experimental/setupSentry.js @@ -0,0 +1,29 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + +import { getEpilogue } from './util' + +export const command = 'setup-sentry' + +export const description = 'Setup Sentry error and performance tracking' + +export const EXPERIMENTAL_TOPIC_ID = 4880 + +export const builder = (yargs) => { + yargs + .option('force', { + alias: 'f', + default: false, + description: 'Overwrite existing sentry.js config files', + type: 'boolean', + }) + .epilogue(getEpilogue(command, description, EXPERIMENTAL_TOPIC_ID, true)) +} + +export const handler = async (options) => { + recordTelemetryAttributes({ + command: 'experimental setup-sentry', + force: options.force, + }) + const { handler } = await import('./setupSentryHandler.js') + return handler(options) +} diff --git a/packages/cli/src/commands/experimental/setupSentryHandler.js b/packages/cli/src/commands/experimental/setupSentryHandler.js new file mode 100644 index 000000000000..a249a75a4188 --- /dev/null +++ b/packages/cli/src/commands/experimental/setupSentryHandler.js @@ -0,0 +1,196 @@ +import path from 'path' + +import fs from 'fs-extra' +import { Listr } from 'listr2' + +import { + addApiPackages, + addEnvVarTask, + addWebPackages, + colors as c, + getPaths, + isTypeScriptProject, + prettify, + writeFilesTask, +} from '@redwoodjs/cli-helpers' +import { getConfigPath } from '@redwoodjs/project-config' +import { errorTelemetry } from '@redwoodjs/telemetry' + +import { writeFile } from '../../lib' + +const PATHS = getPaths() + +export const handler = async ({ force }) => { + const extension = isTypeScriptProject ? 'ts' : 'js' + + const notes = [] + + const tasks = new Listr([ + addApiPackages(['@envelop/sentry@5', '@sentry/node@7']), + addWebPackages(['@sentry/react@7', '@sentry/browser@7']), + addEnvVarTask( + 'SENTRY_DSN', + '', + 'https://docs.sentry.io/product/sentry-basics/dsn-explainer/' + ), + { + title: 'Setting up Sentry on the API and web sides', + task: () => + writeFilesTask( + { + [path.join(PATHS.api.lib, `sentry.${extension}`)]: fs + .readFileSync( + path.join(__dirname, 'templates/sentryApi.ts.template') + ) + .toString(), + [path.join(PATHS.web.src, 'lib', `sentry.${extension}`)]: fs + .readFileSync( + path.join(__dirname, 'templates/sentryWeb.ts.template') + ) + .toString(), + }, + { existingFiles: force ? 'OVERWRITE' : 'SKIP' } + ), + }, + { + title: 'Implementing the Envelop plugin', + task: (ctx) => { + const graphqlHandlerPath = path.join( + PATHS.api.functions, + `graphql.${extension}` + ) + + const contentLines = fs + .readFileSync(graphqlHandlerPath) + .toString() + .split('\n') + + const handlerIndex = contentLines.findLastIndex((line) => + /^export const handler = createGraphQLHandler\({/.test(line) + ) + + const pluginsIndex = contentLines.findLastIndex((line) => + /extraPlugins:/.test(line) + ) + + if (handlerIndex === -1 || pluginsIndex !== -1) { + ctx.addEnvelopPluginSkipped = true + return + } + + contentLines.splice( + handlerIndex, + 1, + "import 'src/lib/sentry'", + '', + 'export const handler = createGraphQLHandler({', + 'extraPlugins: [useSentry({', + 'includeRawResult: true,', + 'includeResolverArgs: true,', + 'includeExecuteVariables: true,', + '})],' + ) + + contentLines.splice(0, 0, "import { useSentry } from '@envelop/sentry'") + + fs.writeFileSync( + graphqlHandlerPath, + prettify('graphql.ts', contentLines.join('\n')) + ) + }, + }, + { + title: "Replacing Redwood's Error boundary", + task: () => { + const contentLines = fs + .readFileSync(PATHS.web.app) + .toString() + .split('\n') + + const webImportIndex = contentLines.findLastIndex((line) => + /^import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs\/web'$/.test( + line + ) + ) + contentLines.splice( + webImportIndex, + 1, + "import { RedwoodProvider } from '@redwoodjs/web'" + ) + + const boundaryOpenIndex = contentLines.findLastIndex((line) => + //.test(line) + ) + contentLines.splice( + boundaryOpenIndex, + 1, + '' + ) + + const boundaryCloseIndex = contentLines.findLastIndex((line) => + /<\/FatalErrorBoundary>/.test(line) + ) + contentLines.splice(boundaryCloseIndex, 1, '') + + contentLines.splice(0, 0, "import Sentry from 'src/lib/sentry'") + + fs.writeFileSync( + PATHS.web.app, + prettify('App.tsx', contentLines.join('\n')) + ) + }, + }, + { + title: 'Adding config to redwood.toml...', + task: (_ctx, task) => { + const redwoodTomlPath = getConfigPath() + const configContent = fs.readFileSync(redwoodTomlPath, 'utf-8') + if (!configContent.includes('[experimental.sentry]')) { + // Use string replace to preserve comments and formatting + writeFile( + redwoodTomlPath, + configContent.concat(`\n[experimental.sentry]\n\tenabled = true\n`), + { + overwriteExisting: true, // redwood.toml always exists + } + ) + } else { + task.skip( + `The [experimental.sentry] config block already exists in your 'redwood.toml' file.` + ) + } + }, + }, + { + title: 'One more thing...', + task: (ctx) => { + notes.push( + c.green( + 'You will need to add `SENTRY_DSN` to `includeEnvironmentVariables` in redwood.toml.' + ) + ) + + if (ctx.addEnvelopPluginSkipped) { + notes.push( + `${c.underline( + 'Make sure you implement the Sentry Envelop plugin:' + )} https://redwoodjs.com/docs/cli-commands#sentry-envelop-plugin` + ) + } else { + notes.push( + "Check out RedwoodJS forums' for more: https://community.redwoodjs.com/t/sentry-error-and-performance-monitoring-experimental/4880" + ) + } + }, + }, + ]) + + try { + await tasks.run() + console.log(notes.join('\n')) + } catch (e) { + errorTelemetry(process.argv, e.message) + console.error(c.error(e.message)) + process.exit(e?.exitCode || 1) + } +} diff --git a/packages/cli/src/commands/experimental/setupServerFile.js b/packages/cli/src/commands/experimental/setupServerFile.js new file mode 100644 index 000000000000..b842d3797263 --- /dev/null +++ b/packages/cli/src/commands/experimental/setupServerFile.js @@ -0,0 +1,36 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + +import { getEpilogue } from './util' + +export const EXPERIMENTAL_TOPIC_ID = 4851 + +export const command = 'setup-server-file' + +export const description = 'Setup the experimental server file' + +export function builder(yargs) { + yargs + .option('force', { + alias: 'f', + default: false, + description: 'Overwrite existing configuration', + type: 'boolean', + }) + .option('verbose', { + alias: 'v', + default: false, + description: 'Print more logs', + type: 'boolean', + }) + .epilogue(getEpilogue(command, description, EXPERIMENTAL_TOPIC_ID, true)) +} + +export async function handler(options) { + recordTelemetryAttributes({ + command: 'experimental setup-server-file', + force: options.force, + verbose: options.verbose, + }) + const { handler } = await import('./setupServerFileHandler.js') + return handler(options) +} diff --git a/packages/cli/src/commands/experimental/setupServerFileHandler.js b/packages/cli/src/commands/experimental/setupServerFileHandler.js new file mode 100644 index 000000000000..b6140dae8971 --- /dev/null +++ b/packages/cli/src/commands/experimental/setupServerFileHandler.js @@ -0,0 +1,119 @@ +import path from 'path' + +import fs from 'fs-extra' +import { Listr } from 'listr2' + +import { addApiPackages } from '@redwoodjs/cli-helpers' +import { getConfigPath } from '@redwoodjs/project-config' +import { errorTelemetry } from '@redwoodjs/telemetry' + +import { getPaths, transformTSToJS, writeFile } from '../../lib' +import c from '../../lib/colors' +import { isTypeScriptProject } from '../../lib/project' + +import { command, description, EXPERIMENTAL_TOPIC_ID } from './setupServerFile' +import { printTaskEpilogue } from './util' + +const { version } = JSON.parse( + fs.readFileSync(path.resolve(__dirname, '../../../package.json'), 'utf-8') +) + +export const setupServerFileTasks = (force = false) => { + const redwoodPaths = getPaths() + const ts = isTypeScriptProject() + + const serverFilePath = path.join( + redwoodPaths.api.src, + `server.${isTypeScriptProject() ? 'ts' : 'js'}` + ) + + return [ + { + title: 'Adding the experimental server files...', + task: () => { + const serverFileTemplateContent = fs.readFileSync( + path.resolve(__dirname, 'templates', 'server.ts.template'), + 'utf-8' + ) + + const setupScriptContent = ts + ? serverFileTemplateContent + : transformTSToJS(serverFilePath, serverFileTemplateContent) + + return [ + writeFile(serverFilePath, setupScriptContent, { + overwriteExisting: force, + }), + ] + }, + }, + { + title: 'Adding config to redwood.toml...', + task: (_ctx, task) => { + // + const redwoodTomlPath = getConfigPath() + const configContent = fs.readFileSync(redwoodTomlPath, 'utf-8') + if (!configContent.includes('[experimental.serverFile]')) { + // Use string replace to preserve comments and formatting + writeFile( + redwoodTomlPath, + configContent.concat( + `\n[experimental.serverFile]\n\tenabled = true\n` + ), + { + overwriteExisting: true, // redwood.toml always exists + } + ) + } else { + task.skip( + `The [experimental.serverFile] config block already exists in your 'redwood.toml' file.` + ) + } + }, + }, + addApiPackages([ + 'fastify', + 'chalk@4.1.2', + `@redwoodjs/fastify@${version}`, + `@redwoodjs/project-config@${version}`, + ]), + ] +} + +export async function handler({ force, verbose }) { + const tasks = new Listr( + [ + { + title: 'Confirmation', + task: async (_ctx, task) => { + const confirmation = await task.prompt({ + type: 'Confirm', + message: 'The server file is experimental. Continue?', + }) + + if (!confirmation) { + throw new Error('User aborted') + } + }, + }, + ...setupServerFileTasks(force), + { + task: () => { + printTaskEpilogue(command, description, EXPERIMENTAL_TOPIC_ID) + }, + }, + ], + { + rendererOptions: { collapseSubtasks: false, persistentOutput: true }, + renderer: verbose ? 'verbose' : 'default', + } + ) + + try { + await tasks.run() + } catch (e) { + errorTelemetry(process.argv, e.message) + console.error(c.error(e.message)) + process.exit(e?.exitCode || 1) + } +} diff --git a/packages/cli/src/commands/experimental/setupStreamingSsr.js b/packages/cli/src/commands/experimental/setupStreamingSsr.js new file mode 100644 index 000000000000..38b8ff203244 --- /dev/null +++ b/packages/cli/src/commands/experimental/setupStreamingSsr.js @@ -0,0 +1,30 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + +import { getEpilogue } from './util' + +export const command = 'setup-streaming-ssr' + +export const description = + 'Enable React Streaming and Server Side Rendering (SSR)' + +export const EXPERIMENTAL_TOPIC_ID = 5052 + +export const builder = (yargs) => { + yargs + .option('force', { + alias: 'f', + default: false, + description: 'Overwrite existing configuration', + type: 'boolean', + }) + .epilogue(getEpilogue(command, description, EXPERIMENTAL_TOPIC_ID, true)) +} + +export const handler = async (options) => { + recordTelemetryAttributes({ + command: ['experimental', command].join(' '), + force: options.force, + }) + const { handler } = await import('./setupStreamingSsrHandler.js') + return handler(options) +} diff --git a/packages/cli/src/commands/experimental/setupStreamingSsrHandler.js b/packages/cli/src/commands/experimental/setupStreamingSsrHandler.js new file mode 100644 index 000000000000..28f8e658f982 --- /dev/null +++ b/packages/cli/src/commands/experimental/setupStreamingSsrHandler.js @@ -0,0 +1,184 @@ +import path from 'path' + +import fs from 'fs-extra' +import { Listr } from 'listr2' + +import { addWebPackages } from '@redwoodjs/cli-helpers' +import { getConfigPath } from '@redwoodjs/project-config' +import { errorTelemetry } from '@redwoodjs/telemetry' + +import { getPaths, transformTSToJS, writeFile } from '../../lib' +import c from '../../lib/colors' +import { isTypeScriptProject } from '../../lib/project' + +import { + command, + description, + EXPERIMENTAL_TOPIC_ID, +} from './setupStreamingSsr' +import { printTaskEpilogue } from './util' + +export const handler = async ({ force, verbose }) => { + const rwPaths = getPaths() + const redwoodTomlPath = getConfigPath() + const configContent = fs.readFileSync(redwoodTomlPath, 'utf-8') + const ts = isTypeScriptProject() + const ext = path.extname(rwPaths.web.entryClient || '') + + const tasks = new Listr( + [ + { + title: 'Check prerequisites', + task: () => { + if (!rwPaths.web.entryClient || !rwPaths.web.viteConfig) { + throw new Error( + 'Vite needs to be setup before you can enable Streaming SSR' + ) + } + }, + }, + { + title: 'Adding config to redwood.toml...', + task: (_ctx, task) => { + if (!configContent.includes('[experimental.streamingSsr]')) { + writeFile( + redwoodTomlPath, + configContent.concat( + `\n[experimental.streamingSsr]\n enabled = true\n` + ), + { + overwriteExisting: true, // redwood.toml always exists + } + ) + } else { + if (force) { + task.output = 'Overwriting config in redwood.toml' + + writeFile( + redwoodTomlPath, + configContent.replace( + // Enable if it's currently disabled + `\n[experimental.streamingSsr]\n enabled = false\n`, + `\n[experimental.streamingSsr]\n enabled = true\n` + ), + { + overwriteExisting: true, // redwood.toml always exists + } + ) + } else { + task.skip( + `The [experimental.streamingSsr] config block already exists in your 'redwood.toml' file.` + ) + } + } + }, + options: { persistentOutput: true }, + }, + { + title: `Adding entry.client${ext}...`, + task: async (_ctx, task) => { + const entryClientTemplate = fs.readFileSync( + path.resolve( + __dirname, + 'templates', + 'streamingSsr', + 'entry.client.tsx.template' + ), + 'utf-8' + ) + let entryClientPath = rwPaths.web.entryClient + const entryClientContent = ts + ? entryClientTemplate + : transformTSToJS(entryClientPath, entryClientTemplate) + + let overwriteExisting = force + + if (!force) { + overwriteExisting = await task.prompt({ + type: 'Confirm', + message: `Overwrite ${entryClientPath}?`, + }) + + if (!overwriteExisting) { + entryClientPath = entryClientPath.replace(ext, `.new${ext}`) + task.output = + `File will be written to ${entryClientPath}\n` + + `You'll manually need to merge it with your existing entry.client${ext} file.` + } + } + + writeFile(entryClientPath, entryClientContent, { overwriteExisting }) + }, + options: { persistentOutput: true }, + }, + { + title: `Adding entry.server${ext}...`, + task: async () => { + const entryServerTemplate = fs.readFileSync( + path.resolve( + __dirname, + 'templates', + 'streamingSsr', + 'entry.server.tsx.template' + ), + 'utf-8' + ) + // Can't use rwPaths.web.entryServer because it might not be not created yet + const entryServerPath = path.join( + rwPaths.web.src, + `entry.server${ext}` + ) + const entryServerContent = ts + ? entryServerTemplate + : transformTSToJS(entryServerPath, entryServerTemplate) + + writeFile(entryServerPath, entryServerContent, { + overwriteExisting: force, + }) + }, + }, + { + title: `Adding Document${ext}...`, + task: async () => { + const documentTemplate = fs.readFileSync( + path.resolve( + __dirname, + 'templates', + 'streamingSsr', + 'Document.tsx.template' + ), + 'utf-8' + ) + const documentPath = path.join(rwPaths.web.src, `Document${ext}`) + const documentContent = ts + ? documentTemplate + : transformTSToJS(documentPath, documentTemplate) + + writeFile(documentPath, documentContent, { + overwriteExisting: force, + }) + }, + }, + addWebPackages([ + '@apollo/experimental-nextjs-app-support@0.0.0-commit-b8a73fe', + ]), + { + task: () => { + printTaskEpilogue(command, description, EXPERIMENTAL_TOPIC_ID) + }, + }, + ], + { + rendererOptions: { collapseSubtasks: false, persistentOutput: true }, + renderer: verbose ? 'verbose' : 'default', + } + ) + + try { + await tasks.run() + } catch (e) { + errorTelemetry(process.argv, e.message) + console.error(c.error(e.message)) + process.exit(e?.exitCode || 1) + } +} diff --git a/packages/cli/src/commands/experimental/studio.js b/packages/cli/src/commands/experimental/studio.js new file mode 100644 index 000000000000..644a350644b0 --- /dev/null +++ b/packages/cli/src/commands/experimental/studio.js @@ -0,0 +1,26 @@ +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + +import { getEpilogue } from './util' + +export const command = 'studio' +export const description = 'Run the Redwood development studio' + +export const EXPERIMENTAL_TOPIC_ID = 4771 + +export function builder(yargs) { + yargs + .option('open', { + default: true, + description: 'Open the studio in your browser', + }) + .epilogue(getEpilogue(command, description, EXPERIMENTAL_TOPIC_ID, true)) +} + +export async function handler(options) { + recordTelemetryAttributes({ + command: 'experimental studio', + open: options.open, + }) + const { handler } = await import('./studioHandler.js') + return handler(options) +} diff --git a/packages/cli/src/commands/experimental/studioHandler.js b/packages/cli/src/commands/experimental/studioHandler.js new file mode 100644 index 000000000000..4ca9d7000d2b --- /dev/null +++ b/packages/cli/src/commands/experimental/studioHandler.js @@ -0,0 +1,50 @@ +import fs from 'fs-extra' + +import { getConfigPath } from '@redwoodjs/project-config' + +import { writeFile } from '../../lib' +import { isModuleInstalled, installRedwoodModule } from '../../lib/packages' + +import { command, description, EXPERIMENTAL_TOPIC_ID } from './studio' +import { printTaskEpilogue } from './util' + +export const handler = async (options) => { + printTaskEpilogue(command, description, EXPERIMENTAL_TOPIC_ID) + try { + // Check the module is installed + if (!isModuleInstalled('@redwoodjs/studio')) { + console.log( + 'The studio package is not installed, installing it for you, this may take a moment...' + ) + await installRedwoodModule('@redwoodjs/studio') + console.log('Studio package installed successfully.') + + console.log('Adding config to redwood.toml...') + const redwoodTomlPath = getConfigPath() + const configContent = fs.readFileSync(redwoodTomlPath, 'utf-8') + + if (!configContent.includes('[experimental.studio]')) { + // Use string replace to preserve comments and formatting + writeFile( + redwoodTomlPath, + configContent.concat(`\n[experimental.studio]\n enabled = true\n`), + { + overwriteExisting: true, // redwood.toml always exists + } + ) + } else { + console.log( + `The [experimental.studio] config block already exists in your 'redwood.toml' file.` + ) + } + } + + // Import studio and start it + const { start } = await import('@redwoodjs/studio') + await start({ open: options.open }) + } catch (e) { + console.log('Cannot start the development studio') + console.log(e) + process.exit(1) + } +} diff --git a/packages/cli/src/commands/experimental/templates/docker/Dockerfile b/packages/cli/src/commands/experimental/templates/docker/Dockerfile new file mode 100644 index 000000000000..0fe3539046aa --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/docker/Dockerfile @@ -0,0 +1,146 @@ +# base +# ------------------------------------------------ +FROM node:20-bookworm-slim as base + +RUN corepack enable + +# We tried to make the Dockerfile as lean as possible. In some cases, that means we excluded a dependency your project needs. +# By far the most common is Python. If you're running into build errors because `python3` isn't available, +# uncomment the line below here and in other stages as necessary: +RUN apt-get update && apt-get install -y \ + openssl \ + # python3 make gcc \ + && rm -rf /var/lib/apt/lists/* + +USER node +WORKDIR /home/node/app + +COPY --chown=node:node .yarnrc.yml . +COPY --chown=node:node package.json . +COPY --chown=node:node api/package.json api/ +COPY --chown=node:node web/package.json web/ +COPY --chown=node:node yarn.lock . + +RUN mkdir -p /home/node/.yarn/berry/index + +RUN --mount=type=cache,target=/home/node/.yarn/berry/cache,uid=1000 \ + --mount=type=cache,target=/home/node/.cache,uid=1000 \ + CI=1 yarn install + +COPY --chown=node:node redwood.toml . +COPY --chown=node:node graphql.config.js . +COPY --chown=node:node .env.defaults .env.defaults + +# api build +# ------------------------------------------------ +FROM base as api_build + +# If your api side build relies on build-time environment variables, +# specify them here as ARGs. (But don't put secrets in your Dockerfile!) +# +# ARG MY_BUILD_TIME_ENV_VAR + +COPY --chown=node:node api api +RUN yarn redwood build api + +# web prerender build +# ------------------------------------------------ +FROM api_build as web_build_with_prerender + +COPY --chown=node:node web web +RUN yarn redwood build web + +# web build +# ------------------------------------------------ +FROM base as web_build + +COPY --chown=node:node web web +RUN yarn redwood build web --no-prerender + +# serve api +# ------------------------------------------------ +FROM node:20-bookworm-slim as api_serve + +RUN corepack enable + +RUN apt-get update && apt-get install -y \ + openssl \ + # python3 make gcc \ + && rm -rf /var/lib/apt/lists/* + +USER node +WORKDIR /home/node/app + +COPY --chown=node:node .yarnrc.yml . +COPY --chown=node:node package.json . +COPY --chown=node:node api/package.json api/ +COPY --chown=node:node yarn.lock . + +RUN mkdir -p /home/node/.yarn/berry/index + +RUN --mount=type=cache,target=/home/node/.yarn/berry/cache,uid=1000 \ + --mount=type=cache,target=/home/node/.cache,uid=1000 \ + CI=1 yarn workspaces focus api --production + +COPY --chown=node:node redwood.toml . +COPY --chown=node:node graphql.config.js . +COPY --chown=node:node .env.defaults .env.defaults + +COPY --chown=node:node --from=api_build /home/node/app/api/dist /home/node/app/api/dist +COPY --chown=node:node --from=api_build /home/node/app/api/db /home/node/app/api/db +COPY --chown=node:node --from=api_build /home/node/app/node_modules/.prisma /home/node/app/node_modules/.prisma + +ENV NODE_ENV=production + +CMD [ "node_modules/.bin/rw-server", "api", "--load-env-files" ] + +# serve web +# ------------------------------------------------ +FROM node:20-bookworm-slim as web_serve + +RUN corepack enable + +USER node +WORKDIR /home/node/app + +COPY --chown=node:node .yarnrc.yml . +COPY --chown=node:node package.json . +COPY --chown=node:node web/package.json web/ +COPY --chown=node:node yarn.lock . + +RUN mkdir -p /home/node/.yarn/berry/index + +RUN --mount=type=cache,target=/home/node/.yarn/berry/cache,uid=1000 \ + --mount=type=cache,target=/home/node/.cache,uid=1000 \ + CI=1 yarn workspaces focus web --production + +COPY --chown=node:node redwood.toml . +COPY --chown=node:node graphql.config.js . +COPY --chown=node:node .env.defaults .env.defaults + +COPY --chown=node:node --from=web_build /home/node/app/web/dist /home/node/app/web/dist + +ENV NODE_ENV=production \ + API_HOST=http://api:8911 + +# We use the shell form here for variable expansion. +CMD "node_modules/.bin/rw-web-server" "--apiHost" "$API_HOST" + +# console +# ------------------------------------------------ +FROM base as console + +# To add more packages: +# +# ``` +# USER root +# +# RUN apt-get update && apt-get install -y \ +# curl +# +# USER node +# ``` + +COPY --chown=node:node api api +COPY --chown=node:node web web +COPY --chown=node:node scripts scripts diff --git a/packages/cli/src/commands/experimental/templates/docker/docker-compose.dev.yml b/packages/cli/src/commands/experimental/templates/docker/docker-compose.dev.yml new file mode 100644 index 000000000000..d5ae4c4f07cd --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/docker/docker-compose.dev.yml @@ -0,0 +1,59 @@ +version: "3.8" + +services: + redwood: + build: + context: . + dockerfile: ./Dockerfile + target: base + command: yarn rw dev + volumes: + - .:/home/node/app + - node_modules:/home/node/app/node_modules + ports: + - "8910:8910" + depends_on: + - db + environment: + - DATABASE_URL=postgresql://redwood:redwood@db:5432/redwood + - TEST_DATABASE_URL=postgresql://redwood:redwood@db:5432/redwood_test + - SESSION_SECRET=super_secret_session_key_change_me_in_production_please + - CI= + - NODE_ENV=development + + db: + image: postgres:16-bookworm + environment: + POSTGRES_USER: redwood + POSTGRES_PASSWORD: redwood + POSTGRES_DB: redwood + ports: + - "5432:5432" + volumes: + - postgres:/var/lib/postgresql/data + + # After starting with `docker compose -f ./docker-compose.dev.yml up`, + # use the console to run commands in the container: + # + # ``` + # docker compose -f ./docker-compose.dev.yml run --rm -it console /bin/bash + # root@...:/home/node/app# yarn rw prisma migrate dev + # ``` + console: + user: root + build: + context: . + dockerfile: ./Dockerfile + target: console + tmpfs: + - /tmp + command: "true" + environment: + - DATABASE_URL=postgresql://redwood:redwood@db:5432/redwood + - TEST_DATABASE_URL=postgresql://redwood:redwood@db:5432/redwood_test + depends_on: + - db + +volumes: + node_modules: + postgres: diff --git a/packages/cli/src/commands/experimental/templates/docker/docker-compose.prod.yml b/packages/cli/src/commands/experimental/templates/docker/docker-compose.prod.yml new file mode 100644 index 000000000000..a921f7603051 --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/docker/docker-compose.prod.yml @@ -0,0 +1,60 @@ +version: "3.8" + +services: + api: + build: + context: . + dockerfile: ./Dockerfile + target: api_serve + ports: + - "8911:8911" + depends_on: + - db + environment: + - DATABASE_URL=postgresql://redwood:redwood@db:5432/redwood + - TEST_DATABASE_URL=postgresql://redwood:redwood@db:5432/redwood_test + - SESSION_SECRET=super_secret_session_key_change_me_in_production_please + + web: + build: + context: . + dockerfile: ./Dockerfile + target: web_serve + ports: + - "8910:8910" + depends_on: + - api + environment: + - API_HOST=http://api:8911 + + db: + image: postgres:16-bookworm + environment: + POSTGRES_USER: redwood + POSTGRES_PASSWORD: redwood + POSTGRES_DB: redwood + ports: + - "5432:5432" + volumes: + - ./postgres:/var/lib/postgresql/data + + # After starting with `docker compose -f ./docker-compose.prod.yml up`, + # use the console to run commands in the container: + # + # ``` + # docker compose -f ./docker-compose.prod.yml run --rm -it console /bin/bash + # ``` + console: + user: root + build: + context: . + dockerfile: ./Dockerfile + target: console + tmpfs: + - /tmp + command: "true" + environment: + - DATABASE_URL=postgresql://redwood:redwood@db:5432/redwood + - TEST_DATABASE_URL=postgresql://redwood:redwood@db:5432/redwood_test + depends_on: + - db diff --git a/packages/cli/src/commands/experimental/templates/docker/dockerignore b/packages/cli/src/commands/experimental/templates/docker/dockerignore new file mode 100644 index 000000000000..f1d225cca910 --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/docker/dockerignore @@ -0,0 +1,18 @@ +**/node_modules +**/dist +.redwood + +.env + +README.md +LICENSE + +.git +.gitignore + +.vscode +.editorconfig + +Dockerfile +docker-compose* +.dockerignore diff --git a/packages/cli/src/commands/experimental/templates/opentelemetry.ts.template b/packages/cli/src/commands/experimental/templates/opentelemetry.ts.template new file mode 100644 index 000000000000..d2a53589e70b --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/opentelemetry.ts.template @@ -0,0 +1,58 @@ +const { diag, DiagConsoleLogger, DiagLogLevel } = require('@opentelemetry/api') +const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http') +const { registerInstrumentations } = require('@opentelemetry/instrumentation') +const { + FastifyInstrumentation, +} = require('@opentelemetry/instrumentation-fastify') +const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http') +const { Resource } = require('@opentelemetry/resources') +const { + NodeTracerProvider, + SimpleSpanProcessor, +} = require('@opentelemetry/sdk-trace-node') +const { + SemanticResourceAttributes, +} = require('@opentelemetry/semantic-conventions') +const { PrismaInstrumentation } = require ('@prisma/instrumentation') + +const { getConfig } from '@redwoodjs/project-config' + +// You may wish to set this to DiagLogLevel.DEBUG when you need to debug opentelemetry itself +diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO) + +const resource = Resource.default().merge( + new Resource({ + [SemanticResourceAttributes.SERVICE_NAME]: 'redwood-app', + [SemanticResourceAttributes.SERVICE_VERSION]: '0.0.0', + }) +) + +const studioPort = getConfig().experimental.studio.basePort +const exporter = new OTLPTraceExporter({ + // Update this URL to point to where your OTLP compatible collector is listening + // The redwood development studio (`yarn rw exp studio`) can collect your + // telemetry at `http://127.0.0.1:/v1/traces` (default PORT is 4318) + url: `http://127.0.0.1:${studioPort}/v1/traces`, +}) + +// You may wish to switch to BatchSpanProcessor in production as it is the recommended choice for performance reasons +const processor = new SimpleSpanProcessor(exporter) + +const provider = new NodeTracerProvider({ + resource: resource, +}) +provider.addSpanProcessor(processor) + +// Optionally register instrumentation libraries here +registerInstrumentations({ + tracerProvider: provider, + instrumentations: [ + new HttpInstrumentation(), + new FastifyInstrumentation(), + new PrismaInstrumentation({ + middleware: true, + }) + ], +}) + +provider.register() diff --git a/packages/cli/src/commands/experimental/templates/rsc/AboutCounter.tsx.template b/packages/cli/src/commands/experimental/templates/rsc/AboutCounter.tsx.template new file mode 100644 index 000000000000..c86915e87f8b --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/rsc/AboutCounter.tsx.template @@ -0,0 +1,20 @@ +'use client' + +import React from 'react' + +// @ts-expect-error no types +import styles from './Counter.module.css' +import './Counter.css' + +export const AboutCounter = () => { + const [count, setCount] = React.useState(0) + + return ( +
+

Count: {count}

+ +

This is a client component.

+

RSC on client: {globalThis.RWJS_EXP_RSC ? 'enabled' : 'disabled'}

+
+ ) +} diff --git a/packages/cli/src/commands/experimental/templates/rsc/AboutPage.css.template b/packages/cli/src/commands/experimental/templates/rsc/AboutPage.css.template new file mode 100644 index 000000000000..995b3bbde1e0 --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/rsc/AboutPage.css.template @@ -0,0 +1,2 @@ +.about-page { +} diff --git a/packages/cli/src/commands/experimental/templates/rsc/AboutPage.tsx.template b/packages/cli/src/commands/experimental/templates/rsc/AboutPage.tsx.template new file mode 100644 index 000000000000..2706e12e63db --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/rsc/AboutPage.tsx.template @@ -0,0 +1,27 @@ +import { Assets } from '@redwoodjs/vite/assets' +import { ProdRwRscServerGlobal } from '@redwoodjs/vite/rwRscGlobal' + +import { AboutCounter } from '../../components/Counter/AboutCounter' + +import './AboutPage.css' + +// TODO (RSC) Something like this will probably be needed +// const RwRscGlobal = import.meta.env.PROD ? ProdRwRscServerGlobal : DevRwRscServerGlobal; + +globalThis.rwRscGlobal = new ProdRwRscServerGlobal() + +const AboutPage = () => { + return ( +
+ {/* TODO (RSC) should be part of the router later */} + +
+

About Redwood

+ +

RSC on server: {globalThis.RWJS_EXP_RSC ? 'enabled' : 'disabled'}

+
+
+ ) +} + +export default AboutPage diff --git a/packages/cli/src/commands/experimental/templates/rsc/App.tsx.template b/packages/cli/src/commands/experimental/templates/rsc/App.tsx.template new file mode 100644 index 000000000000..27cb83121ff7 --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/rsc/App.tsx.template @@ -0,0 +1,19 @@ +import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web' +import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' + +import FatalErrorPage from './pages/FatalErrorPage/FatalErrorPage' +import Routes from './Routes' + +import './index.css' + +const App = () => ( + + + + + + + +) + +export default App diff --git a/packages/cli/src/commands/experimental/templates/rsc/Counter.css.template b/packages/cli/src/commands/experimental/templates/rsc/Counter.css.template new file mode 100644 index 000000000000..4cbd74d7d5b6 --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/rsc/Counter.css.template @@ -0,0 +1,7 @@ +/** + * This should affect all h3 elements on the page, both server components and + * client components. This is just standard CSS stuff + */ +h3 { + color: orange; +} diff --git a/packages/cli/src/commands/experimental/templates/rsc/Counter.module.css.template b/packages/cli/src/commands/experimental/templates/rsc/Counter.module.css.template new file mode 100644 index 000000000000..736b0da8688c --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/rsc/Counter.module.css.template @@ -0,0 +1,3 @@ +.header { + font-style: italic; +} diff --git a/packages/cli/src/commands/experimental/templates/rsc/Counter.tsx.template b/packages/cli/src/commands/experimental/templates/rsc/Counter.tsx.template new file mode 100644 index 000000000000..a6e38c4059b5 --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/rsc/Counter.tsx.template @@ -0,0 +1,19 @@ +'use client' + +import React from 'react' + +// @ts-expect-error no types +import styles from './Counter.module.css' +import './Counter.css' + +export const Counter = () => { + const [count, setCount] = React.useState(0) + + return ( +
+

Count: {count}

+ +

This is a client component.

+
+ ) +} diff --git a/packages/cli/src/commands/experimental/templates/rsc/HomePage.css.template b/packages/cli/src/commands/experimental/templates/rsc/HomePage.css.template new file mode 100644 index 000000000000..9be6b50cd8f7 --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/rsc/HomePage.css.template @@ -0,0 +1,2 @@ +.home-page { +} diff --git a/packages/cli/src/commands/experimental/templates/rsc/HomePage.module.css.template b/packages/cli/src/commands/experimental/templates/rsc/HomePage.module.css.template new file mode 100644 index 000000000000..29212ea8142d --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/rsc/HomePage.module.css.template @@ -0,0 +1,3 @@ +.title { + color: green; +} diff --git a/packages/cli/src/commands/experimental/templates/rsc/HomePage.tsx.template b/packages/cli/src/commands/experimental/templates/rsc/HomePage.tsx.template new file mode 100644 index 000000000000..ad75dbcfa24e --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/rsc/HomePage.tsx.template @@ -0,0 +1,29 @@ +import { Assets } from '@redwoodjs/vite/assets' +import { ProdRwRscServerGlobal } from '@redwoodjs/vite/rwRscGlobal' + +import { Counter } from '../../components/Counter/Counter' +// @ts-expect-error no types +import styles from './HomePage.module.css' + +import './HomePage.css' + +// TODO (RSC) Something like this will probably be needed +// const RwRscGlobal = import.meta.env.PROD ? ProdRwRscServerGlobal : DevRwRscServerGlobal; + +globalThis.rwRscGlobal = new ProdRwRscServerGlobal() + +const HomePage = ({ name = 'Anonymous' }) => { + return ( +
+ {/* TODO (RSC) should be part of the router later */} + +
+

Hello {name}!!

+

This is a server component.

+ +
+
+ ) +} + +export default HomePage diff --git a/packages/cli/src/commands/experimental/templates/rsc/NavigationLayout.css.template b/packages/cli/src/commands/experimental/templates/rsc/NavigationLayout.css.template new file mode 100644 index 000000000000..a3e7be665c31 --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/rsc/NavigationLayout.css.template @@ -0,0 +1,32 @@ +.navigation-layout { + & nav { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + background-color: color-mix(in srgb, yellow 50%, transparent); + border-bottom: 2px dashed color-mix(in srgb, yellow 90%, black); + } + + & ul { + list-style: none; + display: flex; + margin: 0; + padding: 0; + } + + & li { + margin-right: 10px; + } + + & a { + text-decoration: none; + color: #333; + padding: 5px; + border-bottom: 2px solid transparent; + } + + & a:hover { + border-bottom: 2px solid #333; + } +} diff --git a/packages/cli/src/commands/experimental/templates/rsc/NavigationLayout.tsx.template b/packages/cli/src/commands/experimental/templates/rsc/NavigationLayout.tsx.template new file mode 100644 index 000000000000..4f13e197309a --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/rsc/NavigationLayout.tsx.template @@ -0,0 +1,27 @@ +import { Link, routes } from '@redwoodjs/router' + +import './NavigationLayout.css' + +type NavigationLayoutProps = { + children?: React.ReactNode +} + +const NavigationLayout = ({ children }: NavigationLayoutProps) => { + return ( +
+ +
{children}
+
+ ) +} + +export default NavigationLayout diff --git a/packages/cli/src/commands/experimental/templates/rsc/Routes.tsx.template b/packages/cli/src/commands/experimental/templates/rsc/Routes.tsx.template new file mode 100644 index 000000000000..89a1df33eef0 --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/rsc/Routes.tsx.template @@ -0,0 +1,31 @@ +// In this file, all Page components from 'src/pages` are auto-imported. Nested +// directories are supported, and should be uppercase. Each subdirectory will be +// prepended onto the component name. +// +// Examples: +// +// 'src/pages/HomePage/HomePage.js' -> HomePage +// 'src/pages/Admin/BooksPage/BooksPage.js' -> AdminBooksPage + +import { Router, Route, Set } from '@redwoodjs/router' +import { serve } from '@redwoodjs/vite/client' + +import NavigationLayout from './layouts/NavigationLayout/NavigationLayout' +import NotFoundPage from './pages/NotFoundPage/NotFoundPage' + +const AboutPage = serve('AboutPage') +const HomePage = serve('HomePage') + +const Routes = () => { + return ( + + + + + + + + ) +} + +export default Routes diff --git a/packages/cli/src/commands/experimental/templates/rsc/entries.ts.template b/packages/cli/src/commands/experimental/templates/rsc/entries.ts.template new file mode 100644 index 000000000000..6259057e245b --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/rsc/entries.ts.template @@ -0,0 +1,15 @@ +import { defineEntries } from '@redwoodjs/vite/entries' + +export default defineEntries( + // getEntry + async (id) => { + switch (id) { + case 'AboutPage': + return import('./pages/AboutPage/AboutPage') + case 'HomePage': + return import('./pages/HomePage/HomePage') + default: + return null + } + } +) diff --git a/packages/cli/src/commands/experimental/templates/rsc/index.css.template b/packages/cli/src/commands/experimental/templates/rsc/index.css.template new file mode 100644 index 000000000000..57c14ee231a9 --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/rsc/index.css.template @@ -0,0 +1,4 @@ +html, body { + margin: 0; + padding: 0; +} diff --git a/packages/cli/src/commands/experimental/templates/sentryApi.ts.template b/packages/cli/src/commands/experimental/templates/sentryApi.ts.template new file mode 100644 index 000000000000..dcb781fcb611 --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/sentryApi.ts.template @@ -0,0 +1,15 @@ +import * as Sentry from '@sentry/node' + +import { db as client } from 'src/lib/db' + +Sentry.init({ + dsn: process.env.SENTRY_DSN, + environment: process.env.NODE_ENV, + integrations: [ + new Sentry.Integrations.Prisma({ client }), + new Sentry.Integrations.Http({ tracing: true }), + ], + tracesSampleRate: 1.0, +}) + +export default Sentry diff --git a/packages/cli/src/commands/experimental/templates/sentryWeb.ts.template b/packages/cli/src/commands/experimental/templates/sentryWeb.ts.template new file mode 100644 index 000000000000..1bf3051a226e --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/sentryWeb.ts.template @@ -0,0 +1,31 @@ +import * as Sentry from '@sentry/react' + +let dsn = '' +let environment = 'development' + +if (typeof process === 'undefined' || !process.env?.SENTRY_DSN) { + console.error( + 'Missing SENTRY_DSN environment variable. Did you forget to add it to ' + + 'your redwood.toml file in `includeEnvironmentVariables`?' + ) + console.info(`Copy this into your redwood.toml file:`) + console.info(` + includeEnvironmentVariables = [ + "SENTRY_DSN" + ] + + `) + console.error('Sentry is disabled for now') +} else { + dsn = process.env.SENTRY_DSN + environment = process.env.NODE_ENV +} + +Sentry.init({ + dsn, + environment, + integrations: [new Sentry.BrowserTracing()], + tracesSampleRate: 1.0, +}) + +export default Sentry diff --git a/packages/cli/src/commands/experimental/templates/server.ts.template b/packages/cli/src/commands/experimental/templates/server.ts.template new file mode 100644 index 000000000000..fedba89afd12 --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/server.ts.template @@ -0,0 +1,113 @@ +import { parseArgs } from 'node:util' +import path from 'path' + +import chalk from 'chalk' +import { config } from 'dotenv-defaults' +import Fastify from 'fastify' + +import { + coerceRootPath, + redwoodFastifyWeb, + redwoodFastifyAPI, + redwoodFastifyGraphQLServer, + DEFAULT_REDWOOD_FASTIFY_CONFIG, +} from '@redwoodjs/fastify' +import { getPaths, getConfig } from '@redwoodjs/project-config' + +import directives from 'src/directives/**/*.{js,ts}' +import sdls from 'src/graphql/**/*.sdl.{js,ts}' +import services from 'src/services/**/*.{js,ts}' + +// Import if using RedwoodJS authentication +// import { authDecoder } from '@redwoodjs/' +// import { getCurrentUser } from 'src/lib/auth' + +import { logger } from 'src/lib/logger' + +// Import if using RedwoodJS Realtime via `yarn rw exp setup-realtime` +// import { realtime } from 'src/lib/realtime' + +async function serve() { + // Parse server file args + const { values: args } = parseArgs({ + options: { + ['enable-web']: { + type: 'boolean', + default: false, + }, + }, + }) + const { ['enable-web']: enableWeb } = args + + // Load .env files + const redwoodProjectPaths = getPaths() + const redwoodConfig = getConfig() + + const apiRootPath = enableWeb ? coerceRootPath(redwoodConfig.web.apiUrl) : '' + const port = enableWeb ? redwoodConfig.web.port : redwoodConfig.api.port + + const tsServer = Date.now() + + config({ + path: path.join(redwoodProjectPaths.base, '.env'), + defaults: path.join(redwoodProjectPaths.base, '.env.defaults'), + multiline: true, + }) + + console.log(chalk.italic.dim('Starting API and Web Servers...')) + + // Configure Fastify + const fastify = Fastify({ + ...DEFAULT_REDWOOD_FASTIFY_CONFIG, + }) + + if (enableWeb) { + await fastify.register(redwoodFastifyWeb) + } + + await fastify.register(redwoodFastifyAPI, { + redwood: { + apiRootPath, + }, + }) + + await fastify.register(redwoodFastifyGraphQLServer, { + // If authenticating, be sure to import and add in + // authDecoder, + // getCurrentUser, + loggerConfig: { + logger: logger, + }, + graphiQLEndpoint: enableWeb ? '/.redwood/functions/graphql' : '/graphql', + sdls, + services, + directives, + allowIntrospection: true, + allowGraphiQL: true, + // Configure if using RedwoodJS Realtime + // realtime, + }) + + // Start + fastify.listen({ port }) + + fastify.ready(() => { + console.log(chalk.italic.dim('Took ' + (Date.now() - tsServer) + ' ms')) + const on = chalk.magenta(`http://localhost:${port}${apiRootPath}`) + if (enableWeb) { + const webServer = chalk.green(`http://localhost:${port}`) + console.log(`Web server started on ${webServer}`) + } + const apiServer = chalk.magenta(`http://localhost:${port}`) + console.log(`API serving from ${apiServer}`) + console.log(`API listening on ${on}`) + const graphqlEnd = chalk.magenta(`${apiRootPath}graphql`) + console.log(`GraphQL function endpoint at ${graphqlEnd}`) + }) + + process.on('exit', () => { + fastify.close() + }) +} + +serve() diff --git a/packages/cli/src/commands/experimental/templates/streamingSsr/Document.tsx.template b/packages/cli/src/commands/experimental/templates/streamingSsr/Document.tsx.template new file mode 100644 index 000000000000..f989f217529b --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/streamingSsr/Document.tsx.template @@ -0,0 +1,27 @@ +import React from 'react' + +import { Css, Meta } from '@redwoodjs/web' +import type { TagDescriptor } from '@redwoodjs/web' + +interface DocumentProps { + children: React.ReactNode + css: string[] // array of css import strings + meta?: TagDescriptor[] +} + +export const Document: React.FC = ({ children, css, meta }) => { + return ( + + + + + + + + + +
{children}
+ + + ) +} diff --git a/packages/cli/src/commands/experimental/templates/streamingSsr/entry.client.tsx.template b/packages/cli/src/commands/experimental/templates/streamingSsr/entry.client.tsx.template new file mode 100644 index 000000000000..38020749d89d --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/streamingSsr/entry.client.tsx.template @@ -0,0 +1,28 @@ +import { hydrateRoot, createRoot } from 'react-dom/client' + +import App from './App' +import { Document } from './Document' + +/** + * When `#redwood-app` isn't empty then it's very likely that you're using + * prerendering. So React attaches event listeners to the existing markup + * rather than replacing it. + * https://reactjs.org/docs/react-dom-client.html#hydrateroot + */ +const redwoodAppElement = document.getElementById('redwood-app') + +if (redwoodAppElement.children?.length > 0) { + hydrateRoot( + document, + + + + ) +} else { + const root = createRoot(document) + root.render( + + + + ) +} diff --git a/packages/cli/src/commands/experimental/templates/streamingSsr/entry.server.tsx.template b/packages/cli/src/commands/experimental/templates/streamingSsr/entry.server.tsx.template new file mode 100644 index 000000000000..a52b268b771d --- /dev/null +++ b/packages/cli/src/commands/experimental/templates/streamingSsr/entry.server.tsx.template @@ -0,0 +1,15 @@ +import App from './App' +import { Document } from './Document' + +interface Props { + css: string[] + meta?: any[] +} + +export const ServerEntry: React.FC = ({ css, meta }) => { + return ( + + + + ) +} diff --git a/packages/cli/src/commands/experimental/util.js b/packages/cli/src/commands/experimental/util.js new file mode 100644 index 000000000000..d55b130e787a --- /dev/null +++ b/packages/cli/src/commands/experimental/util.js @@ -0,0 +1,82 @@ +import path from 'path' + +import chalk from 'chalk' +import fs from 'fs-extra' +import terminalLink from 'terminal-link' + +import { getPaths } from '../../lib' +import { isTypeScriptProject } from '../../lib/project' + +const link = (topicId, isTerminal = false) => { + const communityLink = `https://community.redwoodjs.com/t/${topicId}` + if (isTerminal) { + return terminalLink(communityLink, communityLink) + } else { + return communityLink + } +} + +export const getEpilogue = ( + command, + description, + topicId, + isTerminal = false +) => + `This is an experimental feature to: ${description}.\n\nPlease find documentation and links to provide feedback for ${command} at:\n -> ${link( + topicId, + isTerminal + )}` + +export const printTaskEpilogue = (command, description, topicId) => { + console.log( + `${chalk.hex('#ff845e')( + `------------------------------------------------------------------\n 🧪 ${chalk.green( + 'Experimental Feature' + )} 🧪\n------------------------------------------------------------------` + )}` + ) + console.log(getEpilogue(command, description, topicId, false)) + + console.log( + `${chalk.hex('#ff845e')( + '------------------------------------------------------------------' + )}\n` + ) +} + +export const serverFileExists = () => { + const serverFilePath = path.join( + getPaths().api.src, + `server.${isTypeScriptProject() ? 'ts' : 'js'}` + ) + + return fs.existsSync(serverFilePath) +} + +export const isServerFileSetup = () => { + if (!serverFileExists) { + throw new Error( + 'RedwoodJS Realtime requires a serverful environment. Please run `yarn rw exp setup-server-file` first.' + ) + } + + return true +} + +export const realtimeExists = () => { + const realtimePath = path.join( + getPaths().api.lib, + `realtime.${isTypeScriptProject() ? 'ts' : 'js'}` + ) + return fs.existsSync(realtimePath) +} + +export const isRealtimeSetup = () => { + if (!realtimeExists) { + throw new Error( + 'Adding realtime events requires that RedwoodJS Realtime be setup. Please run `yarn setup realtime` first.' + ) + } + + return true +} diff --git a/packages/cli/src/commands/generate.js b/packages/cli/src/commands/generate.js index c20d7ff9e43b..d42c8a5f6f4d 100644 --- a/packages/cli/src/commands/generate.js +++ b/packages/cli/src/commands/generate.js @@ -1,6 +1,8 @@ import execa from 'execa' import terminalLink from 'terminal-link' +import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers' + export const command = 'generate ' export const aliases = ['g'] export const description = 'Generate boilerplate code and type definitions' @@ -8,7 +10,16 @@ export const description = 'Generate boilerplate code and type definitions' export const builder = (yargs) => yargs .command('types', 'Generate supplementary code', {}, () => { - execa.sync('yarn rw-gen', { shell: true, stdio: 'inherit' }) + recordTelemetryAttributes({ + command: 'generate types', + }) + try { + execa.sync('yarn rw-gen', { shell: true, stdio: 'inherit' }) + } catch (error) { + // rw-gen is responsible for logging its own errors but we need to + // make sure we exit with a non-zero exit code + process.exitCode = error.exitCode ?? 1 + } }) .commandDir('./generate', { recurse: true, diff --git a/packages/cli/src/commands/generate/__tests__/helpers.test.js b/packages/cli/src/commands/generate/__tests__/helpers.test.js index 35bf51fb02cd..1714901af7f0 100644 --- a/packages/cli/src/commands/generate/__tests__/helpers.test.js +++ b/packages/cli/src/commands/generate/__tests__/helpers.test.js @@ -1,6 +1,7 @@ -import fs from 'fs' import path from 'path' +import fs from 'fs-extra' + // Setup test mocks globalThis.__dirname = __dirname import '../../../lib/test' @@ -9,12 +10,12 @@ import * as helpers from '../helpers' import * as page from '../page/page' const PAGE_TEMPLATE_OUTPUT = `import { Link, routes } from '@redwoodjs/router' -import { MetaTags } from '@redwoodjs/web' +import { Metadata } from '@redwoodjs/web' const FooBarPage = () => { return ( <> - +

FooBarPage

diff --git a/packages/cli/src/commands/generate/cell/__tests__/__snapshots__/cell.test.js.snap b/packages/cli/src/commands/generate/cell/__tests__/__snapshots__/cell.test.js.snap index c02af91a8e36..bce33bd25656 100644 --- a/packages/cli/src/commands/generate/cell/__tests__/__snapshots__/cell.test.js.snap +++ b/packages/cli/src/commands/generate/cell/__tests__/__snapshots__/cell.test.js.snap @@ -89,23 +89,36 @@ exports[`Kebab case words creates a cell stories with a kebabCase word name 1`] "import { Loading, Empty, Failure, Success } from './UserProfileCell' import { standard } from './UserProfileCell.mock' -export const loading = () => { - return Loading ? : <> +const meta = { + title: 'Cells/UserProfileCell', + tags: ['autodocs'], } -export const empty = () => { - return Empty ? : <> +export default meta + +export const loading = { + render: () => { + return Loading ? : <> + }, } -export const failure = (args) => { - return Failure ? : <> +export const empty = { + render: () => { + return Empty ? : <> + }, } -export const success = (args) => { - return Success ? : <> +export const failure = { + render: (args) => { + return Failure ? : <> + }, } -export default { title: 'Cells/UserProfileCell' } +export const success = { + render: (args) => { + return Success ? : <> + }, +} " `; @@ -191,23 +204,36 @@ exports[`Multiword files creates a cell stories with a multi word name 1`] = ` "import { Loading, Empty, Failure, Success } from './UserProfileCell' import { standard } from './UserProfileCell.mock' -export const loading = () => { - return Loading ? : <> +const meta = { + title: 'Cells/UserProfileCell', + tags: ['autodocs'], } -export const empty = () => { - return Empty ? : <> +export default meta + +export const loading = { + render: () => { + return Loading ? : <> + }, } -export const failure = (args) => { - return Failure ? : <> +export const empty = { + render: () => { + return Empty ? : <> + }, } -export const success = (args) => { - return Success ? : <> +export const failure = { + render: (args) => { + return Failure ? : <> + }, } -export default { title: 'Cells/UserProfileCell' } +export const success = { + render: (args) => { + return Success ? : <> + }, +} " `; @@ -293,23 +319,36 @@ exports[`Single word files creates a cell stories with a single word name 1`] = "import { Loading, Empty, Failure, Success } from './UserCell' import { standard } from './UserCell.mock' -export const loading = () => { - return Loading ? : <> +const meta = { + title: 'Cells/UserCell', + tags: ['autodocs'], } -export const empty = () => { - return Empty ? : <> +export default meta + +export const loading = { + render: () => { + return Loading ? : <> + }, } -export const failure = (args) => { - return Failure ? : <> +export const empty = { + render: () => { + return Empty ? : <> + }, } -export const success = (args) => { - return Success ? : <> +export const failure = { + render: (args) => { + return Failure ? : <> + }, } -export default { title: 'Cells/UserCell' } +export const success = { + render: (args) => { + return Success ? : <> + }, +} " `; @@ -395,23 +434,36 @@ exports[`Snake case words creates a cell stories with a snakeCase word name 1`] "import { Loading, Empty, Failure, Success } from './UserProfileCell' import { standard } from './UserProfileCell.mock' -export const loading = () => { - return Loading ? : <> +const meta = { + title: 'Cells/UserProfileCell', + tags: ['autodocs'], } -export const empty = () => { - return Empty ? : <> +export default meta + +export const loading = { + render: () => { + return Loading ? : <> + }, } -export const failure = (args) => { - return Failure ? : <> +export const empty = { + render: () => { + return Empty ? : <> + }, } -export const success = (args) => { - return Success ? : <> +export const failure = { + render: (args) => { + return Failure ? : <> + }, } -export default { title: 'Cells/UserProfileCell' } +export const success = { + render: (args) => { + return Success ? : <> + }, +} " `; @@ -462,6 +514,7 @@ describe('UserProfileCell', () => { exports[`TypeScript: generates list cells if list flag passed in 1`] = ` "import type { FindBazingaQuery, FindBazingaQueryVariables } from 'types/graphql' + import type { CellSuccessProps, CellFailureProps } from '@redwoodjs/web' export const QUERY = gql\` @@ -536,28 +589,41 @@ describe('BazingaCell', () => { `; exports[`TypeScript: generates list cells if list flag passed in 3`] = ` -"import type { ComponentStory } from '@storybook/react' +"import type { Meta, StoryObj } from '@storybook/react' import { Loading, Empty, Failure, Success } from './BazingaCell' import { standard } from './BazingaCell.mock' -export const loading = () => { - return Loading ? : <> +const meta: Meta = { + title: 'Cells/BazingaCell', + tags: ['autodocs'], } -export const empty = () => { - return Empty ? : <> +export default meta + +export const loading: StoryObj = { + render: () => { + return Loading ? : <> + }, } -export const failure: ComponentStory = (args) => { - return Failure ? : <> +export const empty: StoryObj = { + render: () => { + return Empty ? : <> + }, } -export const success: ComponentStory = (args) => { - return Success ? : <> +export const failure: StoryObj = { + render: (args) => { + return Failure ? : <> + }, } -export default { title: 'Cells/BazingaCell' } +export const success: StoryObj = { + render: (args) => { + return Success ? : <> + }, +} " `; @@ -573,6 +639,7 @@ export const standard = (/* vars, { ctx, req } */) => ({ exports[`TypeScript: generates list cells if name is plural 1`] = ` "import type { MembersQuery } from 'types/graphql' + import type { CellSuccessProps, CellFailureProps } from '@redwoodjs/web' export const QUERY = gql\` @@ -640,23 +707,36 @@ exports[`camelCase words creates a cell stories with a camelCase word name 1`] = "import { Loading, Empty, Failure, Success } from './UserProfileCell' import { standard } from './UserProfileCell.mock' -export const loading = () => { - return Loading ? : <> +const meta = { + title: 'Cells/UserProfileCell', + tags: ['autodocs'], } -export const empty = () => { - return Empty ? : <> +export default meta + +export const loading = { + render: () => { + return Loading ? : <> + }, } -export const failure = (args) => { - return Failure ? : <> +export const empty = { + render: () => { + return Empty ? : <> + }, } -export const success = (args) => { - return Success ? : <> +export const failure = { + render: (args) => { + return Failure ? : <> + }, } -export default { title: 'Cells/UserProfileCell' } +export const success = { + render: (args) => { + return Success ? : <> + }, +} " `; @@ -777,23 +857,36 @@ exports[`generates a cell with a string primary id key 3`] = ` "import { Loading, Empty, Failure, Success } from './AddressCell' import { standard } from './AddressCell.mock' -export const loading = () => { - return Loading ? : <> +const meta = { + title: 'Cells/AddressCell', + tags: ['autodocs'], } -export const empty = () => { - return Empty ? : <> +export default meta + +export const loading = { + render: () => { + return Loading ? : <> + }, } -export const failure = (args) => { - return Failure ? : <> +export const empty = { + render: () => { + return Empty ? : <> + }, } -export const success = (args) => { - return Success ? : <> +export const failure = { + render: (args) => { + return Failure ? : <> + }, } -export default { title: 'Cells/AddressCell' } +export const success = { + render: (args) => { + return Success ? : <> + }, +} " `; @@ -885,23 +978,36 @@ exports[`generates list a cell with a string primary id keys 3`] = ` "import { Loading, Empty, Failure, Success } from './AddressesCell' import { standard } from './AddressesCell.mock' -export const loading = () => { - return Loading ? : <> +const meta = { + title: 'Cells/AddressesCell', + tags: ['autodocs'], } -export const empty = () => { - return Empty ? : <> +export default meta + +export const loading = { + render: () => { + return Loading ? : <> + }, } -export const failure = (args) => { - return Failure ? : <> +export const empty = { + render: () => { + return Empty ? : <> + }, } -export const success = (args) => { - return Success ? : <> +export const failure = { + render: (args) => { + return Failure ? : <> + }, } -export default { title: 'Cells/AddressesCell' } +export const success = { + render: (args) => { + return Success ? : <> + }, +} " `; @@ -991,23 +1097,36 @@ exports[`generates list cells if list flag passed in 3`] = ` "import { Loading, Empty, Failure, Success } from './MembersCell' import { standard } from './MembersCell.mock' -export const loading = () => { - return Loading ? : <> +const meta = { + title: 'Cells/MembersCell', + tags: ['autodocs'], } -export const empty = () => { - return Empty ? : <> +export default meta + +export const loading = { + render: () => { + return Loading ? : <> + }, } -export const failure = (args) => { - return Failure ? : <> +export const empty = { + render: () => { + return Empty ? : <> + }, } -export const success = (args) => { - return Success ? : <> +export const failure = { + render: (args) => { + return Failure ? : <> + }, } -export default { title: 'Cells/MembersCell' } +export const success = { + render: (args) => { + return Success ? : <> + }, +} " `; diff --git a/packages/cli/src/commands/generate/cell/__tests__/cell.test.js b/packages/cli/src/commands/generate/cell/__tests__/cell.test.js index 2d72c8c06fc9..a52684c7280e 100644 --- a/packages/cli/src/commands/generate/cell/__tests__/cell.test.js +++ b/packages/cli/src/commands/generate/cell/__tests__/cell.test.js @@ -33,7 +33,7 @@ describe('Single word files', () => { expect( singleWordFiles[ path.normalize( - '/path/to/project/web/src/components/UserCell/UserCell.js' + '/path/to/project/web/src/components/UserCell/UserCell.jsx' ) ] ).toMatchSnapshot() @@ -43,7 +43,7 @@ describe('Single word files', () => { expect( singleWordFiles[ path.normalize( - '/path/to/project/web/src/components/UserCell/UserCell.test.js' + '/path/to/project/web/src/components/UserCell/UserCell.test.jsx' ) ] ).toMatchSnapshot() @@ -53,7 +53,7 @@ describe('Single word files', () => { expect( singleWordFiles[ path.normalize( - '/path/to/project/web/src/components/UserCell/UserCell.stories.js' + '/path/to/project/web/src/components/UserCell/UserCell.stories.jsx' ) ] ).toMatchSnapshot() @@ -82,7 +82,7 @@ test('trims Cell from end of name', async () => { const cellCode = files[ path.normalize( - '/path/to/project/web/src/components/BazingaCell/BazingaCell.js' + '/path/to/project/web/src/components/BazingaCell/BazingaCell.jsx' ) ] @@ -109,7 +109,7 @@ describe('Multiword files', () => { expect( multiWordFiles[ path.normalize( - '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.js' + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.jsx' ) ] ).toMatchSnapshot() @@ -119,7 +119,7 @@ describe('Multiword files', () => { expect( multiWordFiles[ path.normalize( - '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.test.js' + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.test.jsx' ) ] ).toMatchSnapshot() @@ -129,7 +129,7 @@ describe('Multiword files', () => { expect( multiWordFiles[ path.normalize( - '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.stories.js' + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.stories.jsx' ) ] ).toMatchSnapshot() @@ -163,7 +163,7 @@ describe('Snake case words', () => { expect( snakeCaseWordFiles[ path.normalize( - '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.js' + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.jsx' ) ] ).toMatchSnapshot() @@ -173,7 +173,7 @@ describe('Snake case words', () => { expect( snakeCaseWordFiles[ path.normalize( - '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.test.js' + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.test.jsx' ) ] ).toMatchSnapshot() @@ -183,7 +183,7 @@ describe('Snake case words', () => { expect( snakeCaseWordFiles[ path.normalize( - '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.stories.js' + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.stories.jsx' ) ] ).toMatchSnapshot() @@ -216,7 +216,7 @@ describe('Kebab case words', () => { expect( kebabCaseWordFiles[ path.normalize( - '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.js' + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.jsx' ) ] ).toMatchSnapshot() @@ -226,7 +226,7 @@ describe('Kebab case words', () => { expect( kebabCaseWordFiles[ path.normalize( - '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.test.js' + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.test.jsx' ) ] ).toMatchSnapshot() @@ -236,7 +236,7 @@ describe('Kebab case words', () => { expect( kebabCaseWordFiles[ path.normalize( - '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.stories.js' + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.stories.jsx' ) ] ).toMatchSnapshot() @@ -270,7 +270,7 @@ describe('camelCase words', () => { expect( camelCaseWordFiles[ path.normalize( - '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.js' + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.jsx' ) ] ).toMatchSnapshot() @@ -280,7 +280,7 @@ describe('camelCase words', () => { expect( camelCaseWordFiles[ path.normalize( - '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.test.js' + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.test.jsx' ) ] ).toMatchSnapshot() @@ -290,7 +290,7 @@ describe('camelCase words', () => { expect( camelCaseWordFiles[ path.normalize( - '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.stories.js' + '/path/to/project/web/src/components/UserProfileCell/UserProfileCell.stories.jsx' ) ] ).toMatchSnapshot() @@ -320,9 +320,9 @@ test("doesn't include test file when --tests is set to false", async () => { '/path/to/project/web/src/components/UserCell/UserCell.mock.js' ), path.normalize( - '/path/to/project/web/src/components/UserCell/UserCell.stories.js' + '/path/to/project/web/src/components/UserCell/UserCell.stories.jsx' ), - path.normalize('/path/to/project/web/src/components/UserCell/UserCell.js'), + path.normalize('/path/to/project/web/src/components/UserCell/UserCell.jsx'), ]) }) @@ -339,9 +339,9 @@ test("doesn't include storybook file when --stories is set to false", async () = '/path/to/project/web/src/components/UserCell/UserCell.mock.js' ), path.normalize( - '/path/to/project/web/src/components/UserCell/UserCell.test.js' + '/path/to/project/web/src/components/UserCell/UserCell.test.jsx' ), - path.normalize('/path/to/project/web/src/components/UserCell/UserCell.js'), + path.normalize('/path/to/project/web/src/components/UserCell/UserCell.jsx'), ]) }) @@ -354,7 +354,7 @@ test("doesn't include storybook and test files when --stories and --tests is set }) expect(Object.keys(withoutTestAndStoryFiles)).toEqual([ - path.normalize('/path/to/project/web/src/components/UserCell/UserCell.js'), + path.normalize('/path/to/project/web/src/components/UserCell/UserCell.jsx'), ]) }) @@ -367,15 +367,15 @@ test('generates list cells if list flag passed in', async () => { }) const CELL_PATH = path.normalize( - '/path/to/project/web/src/components/MembersCell/MembersCell.js' + '/path/to/project/web/src/components/MembersCell/MembersCell.jsx' ) const TEST_PATH = path.normalize( - '/path/to/project/web/src/components/MembersCell/MembersCell.test.js' + '/path/to/project/web/src/components/MembersCell/MembersCell.test.jsx' ) const STORY_PATH = path.normalize( - '/path/to/project/web/src/components/MembersCell/MembersCell.stories.js' + '/path/to/project/web/src/components/MembersCell/MembersCell.stories.jsx' ) const MOCK_PATH = path.normalize( @@ -405,15 +405,15 @@ test('generates list cells if name is plural', async () => { }) const CELL_PATH = path.normalize( - '/path/to/project/web/src/components/MembersCell/MembersCell.js' + '/path/to/project/web/src/components/MembersCell/MembersCell.jsx' ) const TEST_PATH = path.normalize( - '/path/to/project/web/src/components/MembersCell/MembersCell.test.js' + '/path/to/project/web/src/components/MembersCell/MembersCell.test.jsx' ) const STORY_PATH = path.normalize( - '/path/to/project/web/src/components/MembersCell/MembersCell.stories.js' + '/path/to/project/web/src/components/MembersCell/MembersCell.stories.jsx' ) const MOCK_PATH = path.normalize( @@ -516,15 +516,15 @@ test('"equipment" with list flag', async () => { }) const CELL_PATH = path.normalize( - '/path/to/project/web/src/components/EquipmentListCell/EquipmentListCell.js' + '/path/to/project/web/src/components/EquipmentListCell/EquipmentListCell.jsx' ) const TEST_PATH = path.normalize( - '/path/to/project/web/src/components/EquipmentListCell/EquipmentListCell.test.js' + '/path/to/project/web/src/components/EquipmentListCell/EquipmentListCell.test.jsx' ) const STORY_PATH = path.normalize( - '/path/to/project/web/src/components/EquipmentListCell/EquipmentListCell.stories.js' + '/path/to/project/web/src/components/EquipmentListCell/EquipmentListCell.stories.jsx' ) const MOCK_PATH = path.normalize( @@ -552,15 +552,15 @@ test('"equipment" withOUT list flag should find equipment by id', async () => { }) const CELL_PATH = path.normalize( - '/path/to/project/web/src/components/EquipmentCell/EquipmentCell.js' + '/path/to/project/web/src/components/EquipmentCell/EquipmentCell.jsx' ) const TEST_PATH = path.normalize( - '/path/to/project/web/src/components/EquipmentCell/EquipmentCell.test.js' + '/path/to/project/web/src/components/EquipmentCell/EquipmentCell.test.jsx' ) const STORY_PATH = path.normalize( - '/path/to/project/web/src/components/EquipmentCell/EquipmentCell.stories.js' + '/path/to/project/web/src/components/EquipmentCell/EquipmentCell.stories.jsx' ) const MOCK_PATH = path.normalize( @@ -588,15 +588,15 @@ test('generates a cell with a string primary id key', async () => { }) const CELL_PATH = path.normalize( - '/path/to/project/web/src/components/AddressCell/AddressCell.js' + '/path/to/project/web/src/components/AddressCell/AddressCell.jsx' ) const TEST_PATH = path.normalize( - '/path/to/project/web/src/components/AddressCell/AddressCell.test.js' + '/path/to/project/web/src/components/AddressCell/AddressCell.test.jsx' ) const STORY_PATH = path.normalize( - '/path/to/project/web/src/components/AddressCell/AddressCell.stories.js' + '/path/to/project/web/src/components/AddressCell/AddressCell.stories.jsx' ) const MOCK_PATH = path.normalize( @@ -627,15 +627,15 @@ test('generates list a cell with a string primary id keys', async () => { }) const CELL_PATH = path.normalize( - '/path/to/project/web/src/components/AddressesCell/AddressesCell.js' + '/path/to/project/web/src/components/AddressesCell/AddressesCell.jsx' ) const TEST_PATH = path.normalize( - '/path/to/project/web/src/components/AddressesCell/AddressesCell.test.js' + '/path/to/project/web/src/components/AddressesCell/AddressesCell.test.jsx' ) const STORY_PATH = path.normalize( - '/path/to/project/web/src/components/AddressesCell/AddressesCell.stories.js' + '/path/to/project/web/src/components/AddressesCell/AddressesCell.stories.jsx' ) const MOCK_PATH = path.normalize( @@ -667,7 +667,7 @@ describe('Custom query names', () => { }) const CELL_PATH = path.normalize( - '/path/to/project/web/src/components/CluesCell/CluesCell.js' + '/path/to/project/web/src/components/CluesCell/CluesCell.jsx' ) expect(generatedFiles[CELL_PATH]).toContain('query FindBluesClues {') diff --git a/packages/cli/src/commands/generate/cell/cell.js b/packages/cli/src/commands/generate/cell/cell.js index 8ad18e8c97eb..00342139444a 100644 --- a/packages/cli/src/commands/generate/cell/cell.js +++ b/packages/cli/src/commands/generate/cell/cell.js @@ -25,11 +25,7 @@ import { const COMPONENT_SUFFIX = 'Cell' const REDWOOD_WEB_PATH_NAME = 'components' -export const files = async ({ - name, - typescript: generateTypescript, - ...options -}) => { +export const files = async ({ name, typescript, ...options }) => { let cellName = removeGeneratorName(name, 'cell') let idType, mockIdValues = [42, 43, 44], @@ -78,10 +74,11 @@ export const files = async ({ }) } + const extension = typescript ? '.tsx' : '.jsx' const cellFile = templateForComponentFile({ name: cellName, suffix: COMPONENT_SUFFIX, - extension: generateTypescript ? '.tsx' : '.js', + extension, webPathSection: REDWOOD_WEB_PATH_NAME, generator: 'cell', templatePath: `cell${templateNameSuffix}.tsx.template`, @@ -94,7 +91,7 @@ export const files = async ({ const testFile = templateForComponentFile({ name: cellName, suffix: COMPONENT_SUFFIX, - extension: generateTypescript ? '.test.tsx' : '.test.js', + extension: `.test${extension}`, webPathSection: REDWOOD_WEB_PATH_NAME, generator: 'cell', templatePath: 'test.js.template', @@ -103,16 +100,16 @@ export const files = async ({ const storiesFile = templateForComponentFile({ name: cellName, suffix: COMPONENT_SUFFIX, - extension: generateTypescript ? '.stories.tsx' : '.stories.js', + extension: `.stories${extension}`, webPathSection: REDWOOD_WEB_PATH_NAME, generator: 'cell', - templatePath: 'stories.js.template', + templatePath: 'stories.tsx.template', }) const mockFile = templateForComponentFile({ name: cellName, suffix: COMPONENT_SUFFIX, - extension: generateTypescript ? '.mock.ts' : '.mock.js', + extension: typescript ? '.mock.ts' : '.mock.js', webPathSection: REDWOOD_WEB_PATH_NAME, generator: 'cell', templatePath: `mock${templateNameSuffix}.js.template`, @@ -141,9 +138,7 @@ export const files = async ({ // "path/to/fileB": "<<