diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da4afbb4..2a53ea50 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,6 +128,9 @@ jobs: - name: Verify CLI bundle exists run: test -f dist/Cli.mjs + - name: Verify package runtime outputs + run: npm run test:package + - name: Download MacOS x86 binary uses: actions/download-artifact@v4 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bf78a79..0d011afd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +## 1.3.3 + +- Publish both ESM (`.mjs`) and CommonJS (`.js`) runtime artifacts so generated + consumers can load ResGraph after production-only dependency installation. + ## 1.3.2 - Support `@gql.authorize.byAncestor` defaults on interface types and fields, diff --git a/cli/Bindings.js b/cli/Bindings.js new file mode 100644 index 00000000..978a65bd --- /dev/null +++ b/cli/Bindings.js @@ -0,0 +1,12 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +'use strict'; + + +let Watcher = {}; + +let Chokidar = { + Watcher: Watcher +}; + +exports.Chokidar = Chokidar; +/* No side effect */ diff --git a/cli/Cli.js b/cli/Cli.js new file mode 100644 index 00000000..7c6f1a0d --- /dev/null +++ b/cli/Cli.js @@ -0,0 +1,453 @@ +#!/usr/bin/env node +// Generated by ReScript, PLEASE EDIT WITH CARE +'use strict'; + +let Lsp = require("./Lsp.js"); +let Utils = require("./Utils.js"); +let Stdlib = require("@rescript/runtime/lib/js/Stdlib.js"); +let Process = require("process"); +let Stdlib_Exn = require("@rescript/runtime/lib/js/Stdlib_Exn.js"); +let InitProject = require("./InitProject.js"); +let Stdlib_List = require("@rescript/runtime/lib/js/Stdlib_List.js"); +let ErrorPrinter = require("./ErrorPrinter.js"); +let Stdlib_Array = require("@rescript/runtime/lib/js/Stdlib_Array.js"); +let Stdlib_Option = require("@rescript/runtime/lib/js/Stdlib_Option.js"); +let Nodeperf_hooks = require("node:perf_hooks"); +let GeneratedArtifacts = require("./GeneratedArtifacts.js"); +let Primitive_exceptions = require("@rescript/runtime/lib/js/Primitive_exceptions.js"); + +let args = Stdlib_Array.keepSome(process.argv.slice(2)); + +let argsList = Stdlib_List.fromArray(args); + +function printBuildTime(schema, buildDuration, showSchemaName) { + let prefix = showSchemaName ? `[` + schema.name + `] ` : ""; + console.log(prefix + `Build succeeded in ` + (buildDuration / 1000).toFixed(2) + ` seconds.`); +} + +function printAuthorizationBaselineWarning(authorization) { + let baselinePath = Stdlib_Option.flatMap(authorization, authorization => authorization.baselinePath); + if (baselinePath !== undefined) { + console.warn(`⚠️ Authorization baseline active: fields listed in ` + baselinePath + ` are not protected by required authorization coverage.`); + return; + } +} + +let helpText = ` +**ResGraph v0.1.0 CLI** +This is the CLI of ResGraph. All configuration is read from \`resgraph.json\`. +Available commands: + +init | Validate the project configuration. +build [schema] | Build all schemas, or one named schema. +authorization baseline [schema] | Create or update a schema's authorization baseline. +watch [schema] | Watch all schemas, or one named schema. +tools | Show available ResGraph tools. +help | Show this help message. +`; + +let toolsHelpText = ` +Tools commands: + +find-definition [--schema ] [--json] +`; + +function parseFindDefinitionArgs(args) { + let _remaining = args; + let _state = { + target: undefined, + jsonOutput: false, + schemaName: undefined + }; + while (true) { + let state = _state; + let remaining = _remaining; + if (remaining === 0) { + return Stdlib_Option.map(state.target, target => ({ + target: target, + jsonOutput: state.jsonOutput, + schemaName: state.schemaName + })); + } + let target = remaining.hd; + switch (target) { + case "--json" : + if (!state.jsonOutput) { + _state = { + target: state.target, + jsonOutput: true, + schemaName: state.schemaName + }; + _remaining = remaining.tl; + continue; + } + break; + case "--schema" : + let match = remaining.tl; + if (match !== 0 && Stdlib_Option.isNone(state.schemaName)) { + _state = { + target: state.target, + jsonOutput: state.jsonOutput, + schemaName: match.hd + }; + _remaining = match.tl; + continue; + } + break; + } + if (!(Stdlib_Option.isNone(state.target) && !target.startsWith("--"))) { + return; + } + _state = { + target: target, + jsonOutput: state.jsonOutput, + schemaName: state.schemaName + }; + _remaining = remaining.tl; + continue; + }; +} + +function readConfig() { + let msg = Utils.readConfigFromCwd(); + if (msg.TAG === "Ok") { + return msg._0; + } else { + return Stdlib.panic(msg._0); + } +} + +function validateConfig(config) { + let issues = []; + InitProject.validateConfig(config, issues, Process.cwd()); + if (issues.length !== 0) { + InitProject.printProjectIssues(issues); + Process.exit(1); + return; + } +} + +function selectSchemas(config, schemaName) { + if (schemaName === undefined) { + return config.schemas; + } + let schema = Utils.findSchema(config, schemaName); + if (schema !== undefined) { + return [schema]; + } else { + console.error(`Unknown ResGraph schema "` + schemaName + `".`); + Process.exit(1); + return []; + } +} + +function printFindDefinition(target, jsonOutput, schemaName) { + let config = readConfig(); + validateConfig(config); + let schema = schemaName !== undefined ? Utils.findSchema(config, schemaName) : Utils.defaultSchema(config); + if (schema !== undefined) { + let stateName = schema.stateName; + let match = Utils.callPrivateCli({ + TAG: "FindDefinition", + filePath: schema.projectRoot, + definitionHint: target, + stateName: stateName + }); + if (typeof match === "object" && match.status === "FindDefinition") { + let item = match.item; + if (item !== undefined) { + if (match.error === undefined) { + if (jsonOutput) { + console.log(Utils.stringifyFindDefinitionJson(item)); + } else { + console.log(Utils.formatFindDefinitionText(item)); + } + return; + } + } else { + let error = match.error; + if (error !== undefined) { + if (jsonOutput) { + console.log(Utils.stringifyFindDefinitionError(error)); + } else { + console.error(error); + } + Process.exit(1); + return; + } + } + } + console.error("Unexpected response from ResGraph tools command."); + Process.exit(1); + return; + } + console.error("Could not select a ResGraph schema."); + Process.exit(1); +} + +function buildSchemas(config, schemas) { + let showSchemaName = !config.legacy || schemas.length > 1; + let hadError = { + contents: false + }; + schemas.forEach(schema => { + let timeStart = Nodeperf_hooks.performance.now(); + try { + let match = Utils.callPrivateCli({ + TAG: "GenerateSchema", + _0: schema + }); + if (typeof match !== "object") { + return; + } + switch (match.status) { + case "Success" : + printBuildTime(schema, Nodeperf_hooks.performance.now() - timeStart, showSchemaName); + return printAuthorizationBaselineWarning(schema.authorization); + case "Error" : + if (showSchemaName) { + console.error(`[` + schema.name + `] Schema generation failed.`); + } + ErrorPrinter.printErrors(match.errors); + hadError.contents = true; + return; + default: + return; + } + } catch (raw_error) { + let error = Primitive_exceptions.internalToException(raw_error); + if (error.RE_EXN_ID === Stdlib_Exn.$$Error) { + console.error(`[` + schema.name + `] Generator process failed.`); + console.error(error._1); + hadError.contents = true; + } else { + console.error(`[` + schema.name + `] Generator process failed.`); + hadError.contents = true; + } + return; + } + }); + if (hadError.contents) { + Process.exit(1); + return; + } +} + +function generateAuthorizationBaseline(schemaName) { + let config = readConfig(); + validateConfig(config); + let schema = schemaName !== undefined ? Utils.findSchema(config, schemaName) : Utils.defaultSchema(config); + let schema$1 = Stdlib_Option.getOrThrow(schema, "Could not select a ResGraph schema."); + let authorization = schema$1.authorization; + if (authorization !== undefined) { + let baselinePath = authorization.baselinePath; + if (baselinePath !== undefined) { + let newrecord = {...authorization}; + newrecord.mode = "Baseline"; + let newrecord$1 = {...schema$1}; + newrecord$1.authorization = newrecord; + GeneratedArtifacts.sync(config, [newrecord$1], Process.cwd()); + let match = Utils.callPrivateCli({ + TAG: "GenerateSchema", + _0: newrecord$1 + }); + if (typeof match === "object") { + switch (match.status) { + case "Success" : + console.log(`Authorization baseline written to ` + baselinePath + `.`); + return printAuthorizationBaselineWarning(newrecord); + case "Error" : + ErrorPrinter.printErrors(match.errors); + Process.exit(1); + return; + } + } + console.error("Unexpected response while creating the authorization baseline."); + Process.exit(1); + return; + } + console.error("Set `authorization.baselinePath` in resgraph.json before creating a baseline."); + Process.exit(1); + return; + } + console.error(`Required authorization must be configured for schema "` + schema$1.name + `" before creating a baseline.`); + Process.exit(1); +} + +try { + let exit = 0; + if (argsList !== 0) { + switch (argsList.hd) { + case "authorization" : + let match = argsList.tl; + if (match !== 0 && match.hd === "baseline") { + let match$1 = match.tl; + if (match$1 !== 0) { + if (match$1.tl !== 0) { + exit = 1; + } else { + generateAuthorizationBaseline(match$1.hd); + } + } else { + generateAuthorizationBaseline(undefined); + } + } else { + exit = 1; + } + break; + case "build" : + let match$2 = argsList.tl; + if (match$2 !== 0) { + if (match$2.tl !== 0) { + exit = 1; + } else { + let config = readConfig(); + validateConfig(config); + let schemas = selectSchemas(config, match$2.hd); + GeneratedArtifacts.sync(config, schemas, Process.cwd()); + buildSchemas(config, schemas); + } + } else { + let config$1 = readConfig(); + validateConfig(config$1); + let schemas$1 = selectSchemas(config$1, undefined); + GeneratedArtifacts.sync(config$1, schemas$1, Process.cwd()); + buildSchemas(config$1, schemas$1); + } + break; + case "help" : + if (argsList.tl !== 0) { + exit = 1; + } else { + console.log(helpText); + } + break; + case "init" : + if (argsList.tl !== 0) { + exit = 1; + } else { + let issues = InitProject.validateProject(Process.cwd()); + if (issues.length !== 0) { + InitProject.printProjectIssues(issues); + Process.exit(1); + } else { + console.log("✅ Project already set up correctly."); + } + } + break; + case "lsp" : + let match$3 = argsList.tl; + if (match$3 !== 0 && match$3.tl === 0) { + Lsp.start("Stdio", match$3.hd); + } else { + exit = 1; + } + break; + case "tools" : + let match$4 = argsList.tl; + if (match$4 !== 0 && match$4.hd === "find-definition") { + let match$5 = parseFindDefinitionArgs(match$4.tl); + if (match$5 !== undefined) { + printFindDefinition(match$5.target, match$5.jsonOutput, match$5.schemaName); + } else { + console.error("Invalid tools arguments."); + console.log(toolsHelpText); + Process.exit(1); + } + } else { + exit = 1; + } + break; + case "watch" : + let match$6 = argsList.tl; + exit = match$6 !== 0 && match$6.tl !== 0 ? 1 : 2; + break; + default: + exit = 1; + } + } else { + exit = 1; + } + switch (exit) { + case 1 : + console.log("Invalid command: " + Stdlib_List.toArray(argsList).join(" ")); + console.log(helpText); + break; + case 2 : + let config$2 = readConfig(); + validateConfig(config$2); + let schemaName; + if (argsList !== 0 && argsList.hd === "watch") { + let match$7 = argsList.tl; + schemaName = match$7 !== 0 && match$7.tl === 0 ? match$7.hd : undefined; + } else { + schemaName = undefined; + } + let schemas$2 = selectSchemas(config$2, schemaName); + GeneratedArtifacts.sync(config$2, schemas$2, Process.cwd()); + let showSchemaName = !config$2.legacy || schemas$2.length > 1; + let timeStarts = {}; + schemas$2.map(schema => Utils.setupWatcher((schema, res) => { + let timeStart = timeStarts[schema.name]; + if (timeStart === undefined) { + return; + } + if (typeof res !== "object") { + return; + } + let match = res._0; + if (typeof match === "object") { + switch (match.status) { + case "Success" : + printBuildTime(schema, Nodeperf_hooks.performance.now() - timeStart, showSchemaName); + return printAuthorizationBaselineWarning(schema.authorization); + case "Error" : + if (showSchemaName) { + console.error(`[` + schema.name + `] Schema generation failed.`); + } + return ErrorPrinter.printErrors(match.errors); + } + } + console.error(`[` + schema.name + `] Unexpected generator response.`); + }, schema => { + timeStarts[schema.name] = Nodeperf_hooks.performance.now(); + if (showSchemaName) { + console.log(`[` + schema.name + `] Rebuilding.`); + } else { + console.log("Rebuilding."); + } + }, schema)); + console.log("Watching for changes..."); + break; + } +} catch (raw_error) { + let error = Primitive_exceptions.internalToException(raw_error); + if (error.RE_EXN_ID === Stdlib_Exn.$$Error) { + console.error(error._1); + Process.exit(1); + } else { + console.error("Error!"); + Process.exit(1); + } +} + +let Console; + +let JsExn; + +exports.Console = Console; +exports.JsExn = JsExn; +exports.args = args; +exports.argsList = argsList; +exports.printBuildTime = printBuildTime; +exports.printAuthorizationBaselineWarning = printAuthorizationBaselineWarning; +exports.helpText = helpText; +exports.toolsHelpText = toolsHelpText; +exports.parseFindDefinitionArgs = parseFindDefinitionArgs; +exports.readConfig = readConfig; +exports.validateConfig = validateConfig; +exports.selectSchemas = selectSchemas; +exports.printFindDefinition = printFindDefinition; +exports.buildSchemas = buildSchemas; +exports.generateAuthorizationBaseline = generateAuthorizationBaseline; +/* args Not a pure module */ diff --git a/cli/ErrorPrinter.js b/cli/ErrorPrinter.js new file mode 100644 index 00000000..f4c416cb --- /dev/null +++ b/cli/ErrorPrinter.js @@ -0,0 +1,58 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +'use strict'; + +let Chalk = require("chalk").default; +let Nodefs = require("node:fs"); +let Nodeos = require("node:os"); + +let Chalk$1 = {}; + +function prettyPrintDiagnostic(lines, diagnostic) { + let fileLocText = Chalk.blackBright((diagnostic.range.start.line + 1 | 0).toString() + `:` + (diagnostic.range.start.character + 1 | 0).toString() + `-` + (diagnostic.range.end.line + 1 | 0).toString() + `:` + (diagnostic.range.end.character + 1 | 0).toString()); + console.log(Chalk.red("Error in file:") + ` ` + Chalk.blueBright(diagnostic.file) + `:` + fileLocText); + console.log("\n"); + lines.forEach((line, index) => { + if (!(index > (diagnostic.range.start.line - 5 | 0) && index < (diagnostic.range.end.line + 5 | 0))) { + return; + } + let highlightOnThisLine = index >= diagnostic.range.start.line && index <= diagnostic.range.end.line; + if (highlightOnThisLine) { + let highlightStartOffset = index === diagnostic.range.start.line ? diagnostic.range.start.character : 0; + let highlightEndOffset = index === diagnostic.range.end.line ? diagnostic.range.end.character : line.length; + let lineText = line.slice(0, highlightStartOffset) + Chalk.bold.red(line.slice(highlightStartOffset, highlightEndOffset)) + line.slice(highlightEndOffset); + console.log(` ` + Chalk.bold.red((index + 1 | 0).toString()) + ` ` + Chalk.blackBright(`┆`) + ` ` + lineText); + return; + } + console.log(` ` + (index + 1 | 0).toString() + ` ` + Chalk.blackBright(`┆`) + ` ` + line); + }); + console.log("\n " + diagnostic.message); +} + +function printErrors(errors) { + let fileContentCache = {}; + errors.forEach(error => { + let content = fileContentCache[error.file]; + let fileContentLines; + if (content !== undefined) { + fileContentLines = content; + } else { + let contents; + try { + contents = Nodefs.readFileSync(error.file).toString("utf8").split(Nodeos.EOL); + } catch (exn) { + contents = []; + } + fileContentCache[error.file] = contents; + fileContentLines = contents; + } + prettyPrintDiagnostic(fileContentLines, error); + }); +} + +let Console; + +exports.Chalk = Chalk$1; +exports.Console = Console; +exports.prettyPrintDiagnostic = prettyPrintDiagnostic; +exports.printErrors = printErrors; +/* chalk Not a pure module */ diff --git a/cli/GeneratedArtifacts.js b/cli/GeneratedArtifacts.js new file mode 100644 index 00000000..6c8c64be --- /dev/null +++ b/cli/GeneratedArtifacts.js @@ -0,0 +1,227 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +'use strict'; + +let Utils = require("./Utils.js"); +let Nodefs = require("node:fs"); +let Process = require("process"); +let Nodepath = require("node:path"); +let Stdlib_JSON = require("@rescript/runtime/lib/js/Stdlib_JSON.js"); +let Stdlib_Array = require("@rescript/runtime/lib/js/Stdlib_Array.js"); +let Stdlib_Option = require("@rescript/runtime/lib/js/Stdlib_Option.js"); + +let generatedSchemaHeader = "/* @generated by ResGraph named schema */"; + +let generatedInterfaceHeader = "/* @generated */"; + +let generatedSdlHeader = "# @generated by ResGraph named schema"; + +function decodeOwnership(raw) { + return Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(raw), dict => { + let match = Stdlib_Option.flatMap(dict["name"], Stdlib_JSON.Decode.string); + let match$1 = Stdlib_Option.flatMap(dict["compilerRoot"], Stdlib_JSON.Decode.string); + let match$2 = Stdlib_Option.flatMap(dict["outputFolder"], Stdlib_JSON.Decode.string); + let match$3 = Stdlib_Option.flatMap(dict["moduleName"], Stdlib_JSON.Decode.string); + let match$4 = Stdlib_Option.flatMap(dict["dumpSchemaSdl"], Stdlib_JSON.Decode.bool); + if (match !== undefined && match$1 !== undefined && match$2 !== undefined && match$3 !== undefined && match$4 !== undefined) { + return { + name: match, + compilerRoot: match$1, + outputFolder: match$2, + moduleName: match$3, + dumpSchemaSdl: match$4 + }; + } + }); +} + +function decodeManifest(raw) { + return Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(raw), dict => { + let match = dict["version"]; + let match$1 = Stdlib_Option.flatMap(dict["schemas"], Stdlib_JSON.Decode.array); + if (match === undefined) { + return; + } + if (match$1 === undefined) { + return; + } + if (JSON.stringify(match) !== "1") { + return; + } + let schemas = match$1.map(decodeOwnership); + if (schemas.every(Stdlib_Option.isSome)) { + return { + version: 1, + schemas: Stdlib_Array.keepSome(schemas) + }; + } + }); +} + +function manifestPath(configDir) { + return Nodepath.resolve(configDir, "lib", "resgraph", ".configured-schemas.json"); +} + +function readManifest(path) { + if (!Nodefs.existsSync(path)) { + return; + } + try { + return decodeManifest(JSON.parse(Nodefs.readFileSync(path).toString("utf8"))); + } catch (exn) { + return; + } +} + +function fileStartsWith(path, prefix) { + if (!Nodefs.existsSync(path)) { + return false; + } + try { + return Nodefs.readFileSync(path).toString("utf8").startsWith(prefix); + } catch (exn) { + return false; + } +} + +function removeFile(path) { + if (!Nodefs.existsSync(path)) { + return; + } + try { + Nodefs.unlinkSync(path); + return; + } catch (exn) { + return; + } +} + +function cleanOwnership(ownership) { + let schemaSource = Nodepath.resolve(ownership.outputFolder, ownership.moduleName + ".res"); + let schemaInterface = schemaSource + "i"; + if (fileStartsWith(schemaSource, generatedSchemaHeader)) { + removeFile(schemaSource); + } + if (fileStartsWith(schemaInterface, generatedSchemaHeader)) { + removeFile(schemaInterface); + } + if (Nodefs.existsSync(ownership.outputFolder)) { + let interfacePrefix = ownership.moduleName + "__Interface_"; + try { + Nodefs.readdirSync(ownership.outputFolder).forEach(fileName => { + let path = Nodepath.resolve(ownership.outputFolder, fileName); + if (fileName.startsWith(interfacePrefix) && fileName.endsWith(".res") && fileStartsWith(path, generatedInterfaceHeader)) { + return removeFile(path); + } + }); + } catch (exn) { + + } + } + let schemaSdl = Nodepath.resolve(ownership.outputFolder, "schema.graphql"); + if (fileStartsWith(schemaSdl, generatedSdlHeader)) { + return removeFile(schemaSdl); + } +} + +function sameOwnership(left, right) { + if (left.name === right.name && left.compilerRoot === right.compilerRoot && left.outputFolder === right.outputFolder) { + return left.moduleName === right.moduleName; + } else { + return false; + } +} + +function writeManifest(path, schemas) { + let temporaryPath = path + "." + Process.pid.toString() + ".tmp"; + try { + let directory = Nodepath.dirname(path); + if (!Nodefs.existsSync(directory)) { + Nodefs.mkdirSync(directory, { + recursive: true + }); + } + let payload = { + version: 1, + schemas: schemas + }; + Nodefs.writeFileSync(temporaryPath, Buffer.from(Stdlib_Option.getOr(JSON.stringify(payload), ""))); + Nodefs.renameSync(temporaryPath, path); + return; + } catch (exn) { + return removeFile(temporaryPath); + } +} + +function sameSchemaSlot(left, right) { + if (left.compilerRoot === right.compilerRoot) { + return left.outputFolder === right.outputFolder; + } else { + return false; + } +} + +function sameCompilerModule(left, right) { + if (Utils.portableFilesystemIdentity(left.compilerRoot) === Utils.portableFilesystemIdentity(right.compilerRoot)) { + return left.moduleName.toLowerCase() === right.moduleName.toLowerCase(); + } else { + return false; + } +} + +function sync(config, selectedSchemas, configDir) { + let path = manifestPath(configDir); + let selectedNames = selectedSchemas.map(schema => schema.name); + let current = config.legacy ? [] : config.schemas.map(schema => ({ + name: schema.name, + compilerRoot: Stdlib_Option.getOr(Utils.findCompilerRoot(schema.projectRoot), schema.projectRoot), + outputFolder: schema.outputFolder, + moduleName: schema.moduleName, + dumpSchemaSdl: schema.dumpSchemaSdl + })); + let previous = Stdlib_Option.getOr(Stdlib_Option.map(readManifest(path), manifest => manifest.schemas), []); + let previousForCurrent = currentOwnership => Stdlib_Option.orElse(previous.find(previousOwnership => previousOwnership.name === currentOwnership.name), previous.find(previousOwnership => sameSchemaSlot(previousOwnership, currentOwnership))); + let ownershipClaimedBySelectedSchema = previousOwnership => current.some(currentOwnership => { + if (currentOwnership.name !== previousOwnership.name && selectedNames.includes(currentOwnership.name)) { + return sameCompilerModule(previousOwnership, currentOwnership); + } else { + return false; + } + }); + previous.forEach(previousOwnership => { + let successor = Stdlib_Option.orElse(current.find(currentOwnership => currentOwnership.name === previousOwnership.name), current.find(currentOwnership => sameSchemaSlot(previousOwnership, currentOwnership))); + if (successor !== undefined && !(!sameOwnership(previousOwnership, successor) && selectedNames.includes(successor.name) || ownershipClaimedBySelectedSchema(previousOwnership))) { + return; + } else { + return cleanOwnership(previousOwnership); + } + }); + let next = current.map(currentOwnership => { + let previousOwnership = previousForCurrent(currentOwnership); + if (previousOwnership !== undefined && !sameOwnership(previousOwnership, currentOwnership) && !selectedNames.includes(currentOwnership.name) && !ownershipClaimedBySelectedSchema(previousOwnership)) { + return previousOwnership; + } else { + return currentOwnership; + } + }); + writeManifest(path, next); +} + +let manifestVersion = 1; + +exports.manifestVersion = manifestVersion; +exports.generatedSchemaHeader = generatedSchemaHeader; +exports.generatedInterfaceHeader = generatedInterfaceHeader; +exports.generatedSdlHeader = generatedSdlHeader; +exports.decodeOwnership = decodeOwnership; +exports.decodeManifest = decodeManifest; +exports.manifestPath = manifestPath; +exports.readManifest = readManifest; +exports.fileStartsWith = fileStartsWith; +exports.removeFile = removeFile; +exports.cleanOwnership = cleanOwnership; +exports.sameOwnership = sameOwnership; +exports.writeManifest = writeManifest; +exports.sameSchemaSlot = sameSchemaSlot; +exports.sameCompilerModule = sameCompilerModule; +exports.sync = sync; +/* Utils Not a pure module */ diff --git a/cli/InitProject.js b/cli/InitProject.js new file mode 100644 index 00000000..4a25cef0 --- /dev/null +++ b/cli/InitProject.js @@ -0,0 +1,349 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +'use strict'; + +let Utils = require("./Utils.js"); +let Nodefs = require("node:fs"); +let Nodepath = require("node:path"); +let Stdlib_Exn = require("@rescript/runtime/lib/js/Stdlib_Exn.js"); +let Stdlib_Array = require("@rescript/runtime/lib/js/Stdlib_Array.js"); +let Stdlib_Option = require("@rescript/runtime/lib/js/Stdlib_Option.js"); +let GeneratedArtifacts = require("./GeneratedArtifacts.js"); +let Primitive_exceptions = require("@rescript/runtime/lib/js/Primitive_exceptions.js"); + +function readFile(relativePath, dir) { + let path = Nodepath.resolve(dir, relativePath); + if (!Nodefs.existsSync(path)) { + return { + TAG: "Error", + _0: "FileDoesNotExist" + }; + } + try { + return { + TAG: "Ok", + _0: Nodefs.readFileSync(path).toString("utf8") + }; + } catch (raw_exn) { + let exn = Primitive_exceptions.internalToException(raw_exn); + if (exn.RE_EXN_ID === Stdlib_Exn.$$Error) { + return { + TAG: "Error", + _0: { + TAG: "FileCouldNotBeRead", + _0: undefined + } + }; + } + throw exn; + } +} + +let validSchemaName = (value => /^[A-Za-z0-9_-]+$/.test(value)); + +let validModuleName = (value => /^[A-Z][A-Za-z0-9_]*$/.test(value)); + +let validContextType = (value => /^[A-Z][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)+$/.test(value)); + +function pathIsWithin(path, root) { + let path$1 = Utils.canonicalPath(path); + let root$1 = Utils.canonicalPath(root); + if (path$1 === root$1) { + return true; + } else { + return path$1.startsWith(root$1 + Nodepath.sep); + } +} + +function compilerRoot(projectRoot) { + return Utils.portableFilesystemIdentity(Stdlib_Option.getOr(Utils.findCompilerRoot(projectRoot), projectRoot)); +} + +function authorizationArtifactPaths(schema) { + let authorization = schema.authorization; + if (authorization !== undefined) { + return Stdlib_Array.keepSome([ + authorization.manifestPath, + authorization.baselinePath + ]); + } else { + return []; + } +} + +function artifactFilesystemIdentity(path) { + return Utils.portableFilesystemIdentity(Nodepath.join(Utils.canonicalPath(Nodepath.dirname(path)), Nodepath.basename(path))); +} + +function pathIsGeneratedBySchema(path, schema) { + let pathIdentity = artifactFilesystemIdentity(path); + let outputFolderIdentity = Utils.portableFilesystemIdentity(schema.outputFolder); + let fileName = Nodepath.basename(path); + let interfacePrefix = (schema.moduleName + "__Interface_").toLowerCase(); + let isInterface = Utils.portableFilesystemIdentity(Nodepath.dirname(path)) === outputFolderIdentity && fileName.toLowerCase().startsWith(interfacePrefix) && fileName.toLowerCase().endsWith(".res"); + let compilerRoot = Stdlib_Option.getOr(Utils.findCompilerRoot(schema.projectRoot), schema.projectRoot); + let generatedPaths = [ + Nodepath.resolve(schema.outputFolder, schema.moduleName + ".res"), + Nodepath.resolve(schema.outputFolder, schema.moduleName + ".resi"), + Nodepath.resolve(schema.outputFolder, "schema.graphql"), + Nodepath.resolve(compilerRoot, "lib", "resgraph", schema.name + ".state.marshal"), + Nodepath.resolve(compilerRoot, "lib", "resgraph", schema.name + ".incremental-cache") + ]; + if (isInterface) { + return true; + } else { + return generatedPaths.some(generatedPath => artifactFilesystemIdentity(generatedPath) === pathIdentity); + } +} + +function validateConfig(config, issues, configDir) { + let ownershipManifestIdentity = artifactFilesystemIdentity(GeneratedArtifacts.manifestPath(configDir)); + if (Stdlib_Option.isNone(Utils.findSchema(config, config.defaultSchema))) { + issues.push({ + TAG: "DefaultSchemaDoesNotExist", + schemaName: config.defaultSchema + }); + } + config.schemas.forEach(schema => { + if (!validSchemaName(schema.name)) { + issues.push({ + TAG: "InvalidSchemaName", + schemaName: schema.name + }); + } + if (!validModuleName(schema.moduleName)) { + issues.push({ + TAG: "InvalidModuleName", + schemaName: schema.name, + moduleName: schema.moduleName + }); + } + if (!validContextType(schema.contextType)) { + issues.push({ + TAG: "InvalidContextType", + schemaName: schema.name, + contextType: schema.contextType + }); + } + if (!Nodefs.existsSync(schema.outputFolder)) { + issues.push({ + TAG: "OutputFolderDoesNotExist", + schemaName: schema.name, + path: schema.outputFolder + }); + } + if (!Nodefs.existsSync(schema.projectRoot)) { + issues.push({ + TAG: "ProjectRootDoesNotExist", + schemaName: schema.name, + path: schema.projectRoot + }); + } + schema.includePaths.forEach(path => { + if (!Nodefs.existsSync(path)) { + issues.push({ + TAG: "IncludePathDoesNotExist", + schemaName: schema.name, + path: path + }); + } + if (!pathIsWithin(path, schema.projectRoot)) { + issues.push({ + TAG: "IncludePathOutsideProject", + schemaName: schema.name, + path: path, + projectRoot: schema.projectRoot + }); + return; + } + }); + schema.excludePaths.forEach(path => { + if (!Nodefs.existsSync(path)) { + issues.push({ + TAG: "ExcludePathDoesNotExist", + schemaName: schema.name, + path: path + }); + return; + } + }); + authorizationArtifactPaths(schema).forEach(path => { + if (artifactFilesystemIdentity(path) === ownershipManifestIdentity) { + issues.push({ + TAG: "AuthorizationPathCollidesWithOwnershipManifest", + schemaName: schema.name, + path: path + }); + return; + } + }); + }); + config.schemas.forEach((schema, index) => { + config.schemas.forEach((otherSchema, otherIndex) => { + if (otherIndex > index && Utils.portableFilesystemIdentity(schema.outputFolder) === Utils.portableFilesystemIdentity(otherSchema.outputFolder)) { + issues.push({ + TAG: "DuplicateOutputFolder", + firstSchema: schema.name, + secondSchema: otherSchema.name, + path: schema.outputFolder + }); + } + if (otherIndex > index) { + let otherAuthorizationPaths = authorizationArtifactPaths(otherSchema); + let path = authorizationArtifactPaths(schema).find(path => otherAuthorizationPaths.some(otherPath => artifactFilesystemIdentity(path) === artifactFilesystemIdentity(otherPath))); + if (path !== undefined) { + issues.push({ + TAG: "DuplicateAuthorizationArtifactPath", + firstSchema: schema.name, + secondSchema: otherSchema.name, + path: path + }); + } + authorizationArtifactPaths(schema).forEach(path => { + if (pathIsGeneratedBySchema(path, otherSchema)) { + issues.push({ + TAG: "AuthorizationPathCollidesWithGeneratedArtifact", + authorizationSchema: schema.name, + generatedSchema: otherSchema.name, + path: path + }); + return; + } + }); + authorizationArtifactPaths(otherSchema).forEach(path => { + if (pathIsGeneratedBySchema(path, schema)) { + issues.push({ + TAG: "AuthorizationPathCollidesWithGeneratedArtifact", + authorizationSchema: otherSchema.name, + generatedSchema: schema.name, + path: path + }); + return; + } + }); + } + if (otherIndex > index && compilerRoot(schema.projectRoot) === compilerRoot(otherSchema.projectRoot) && schema.moduleName.toLowerCase() === otherSchema.moduleName.toLowerCase()) { + issues.push({ + TAG: "DuplicateModuleName", + firstSchema: schema.name, + secondSchema: otherSchema.name, + moduleName: schema.moduleName + }); + } + if (otherIndex > index && compilerRoot(schema.projectRoot) === compilerRoot(otherSchema.projectRoot) && schema.name.toLowerCase() === otherSchema.name.toLowerCase()) { + issues.push({ + TAG: "DuplicateSchemaStateName", + firstSchema: schema.name, + secondSchema: otherSchema.name + }); + return; + } + }); + }); +} + +function validateProject(dir) { + let issues = []; + let configFileContents = readFile("./resgraph.json", dir); + if (configFileContents.TAG === "Ok") { + let config; + try { + config = Utils.parseConfig(JSON.parse(configFileContents._0), dir); + } catch (exn) { + config = undefined; + } + if (config !== undefined) { + validateConfig(config, issues, dir); + } else { + issues.push("ConfigFileIssue"); + } + } else { + issues.push("MissingConfigFile"); + } + return issues; +} + +function printProjectIssues(issues) { + console.error("⛔ One or more issues was encountered with this ResGraph project.\n"); + issues.forEach(issue => { + if (typeof issue !== "object") { + if (issue === "MissingConfigFile") { + console.error(`- 🚫 The file "resgraph.json" does not exist in this project root. Please create it and add + configure the content below to your liking: +{ + "src": "./src", + "outputFolder": "./src/__generated__" +}`); + return; + } + console.error(`- 🚫 "resgraph.json" could not be parsed. Please check its schema configuration.`); + return; + } else { + switch (issue.TAG) { + case "OutputFolderDoesNotExist" : + console.error(`- 🚫 Schema "` + issue.schemaName + `" outputFolder "` + issue.path + `" does not exist or cannot be accessed.`); + return; + case "ProjectRootDoesNotExist" : + console.error(`- 🚫 Schema "` + issue.schemaName + `" projectRoot "` + issue.path + `" does not exist or cannot be accessed.`); + return; + case "IncludePathDoesNotExist" : + console.error(`- 🚫 Schema "` + issue.schemaName + `" include path "` + issue.path + `" does not exist or cannot be accessed.`); + return; + case "ExcludePathDoesNotExist" : + console.error(`- 🚫 Schema "` + issue.schemaName + `" exclude path "` + issue.path + `" does not exist or cannot be accessed.`); + return; + case "DuplicateOutputFolder" : + console.error(`- 🚫 Schemas "` + issue.firstSchema + `" and "` + issue.secondSchema + `" use the same outputFolder "` + issue.path + `".`); + return; + case "DuplicateAuthorizationArtifactPath" : + console.error(`- 🚫 Schemas "` + issue.firstSchema + `" and "` + issue.secondSchema + `" use the same authorization manifest or baseline path "` + issue.path + `".`); + return; + case "AuthorizationPathCollidesWithGeneratedArtifact" : + console.error(`- 🚫 Schemas "` + issue.authorizationSchema + `" and "` + issue.generatedSchema + `" configure authorization and generated artifacts at the same path "` + issue.path + `".`); + return; + case "AuthorizationPathCollidesWithOwnershipManifest" : + console.error(`- 🚫 Schema "` + issue.schemaName + `" configures an authorization artifact at the reserved schema ownership manifest path "` + issue.path + `".`); + return; + case "DuplicateModuleName" : + console.error(`- 🚫 Schemas "` + issue.firstSchema + `" and "` + issue.secondSchema + `" use moduleName "` + issue.moduleName + `" in the same ReScript package.`); + return; + case "DuplicateSchemaStateName" : + console.error(`- 🚫 Schemas "` + issue.firstSchema + `" and "` + issue.secondSchema + `" have names that collide on case-insensitive filesystems in the same ReScript package.`); + return; + case "DefaultSchemaDoesNotExist" : + console.error(`- 🚫 defaultSchema "` + issue.schemaName + `" does not name a configured schema.`); + return; + case "InvalidSchemaName" : + console.error(`- 🚫 Schema name "` + issue.schemaName + `" may contain only letters, numbers, underscores, and hyphens.`); + return; + case "InvalidModuleName" : + console.error(`- 🚫 Schema "` + issue.schemaName + `" moduleName "` + issue.moduleName + `" is not a valid ReScript module name.`); + return; + case "InvalidContextType" : + console.error(`- 🚫 Schema "` + issue.schemaName + `" contextType "` + issue.contextType + `" is not a valid qualified ReScript type.`); + return; + case "IncludePathOutsideProject" : + console.error(`- 🚫 Schema "` + issue.schemaName + `" include path "` + issue.path + `" is outside projectRoot "` + issue.projectRoot + `".`); + return; + } + } + }); +} + +let Console; + +let JsExn; + +exports.Console = Console; +exports.JsExn = JsExn; +exports.readFile = readFile; +exports.validSchemaName = validSchemaName; +exports.validModuleName = validModuleName; +exports.validContextType = validContextType; +exports.pathIsWithin = pathIsWithin; +exports.compilerRoot = compilerRoot; +exports.authorizationArtifactPaths = authorizationArtifactPaths; +exports.artifactFilesystemIdentity = artifactFilesystemIdentity; +exports.pathIsGeneratedBySchema = pathIsGeneratedBySchema; +exports.validateConfig = validateConfig; +exports.validateProject = validateProject; +exports.printProjectIssues = printProjectIssues; +/* Utils Not a pure module */ diff --git a/cli/Lsp.js b/cli/Lsp.js new file mode 100644 index 00000000..c42ac755 --- /dev/null +++ b/cli/Lsp.js @@ -0,0 +1,474 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +'use strict'; + +let Url = require("url"); +let Utils = require("./Utils.js"); +let Stdlib = require("@rescript/runtime/lib/js/Stdlib.js"); +let Nodefs = require("node:fs"); +let Nodepath = require("node:path"); +let InitProject = require("./InitProject.js"); +let Stdlib_Dict = require("@rescript/runtime/lib/js/Stdlib_Dict.js"); +let Stdlib_Option = require("@rescript/runtime/lib/js/Stdlib_Option.js"); +let VscodeJsonrpc = require("vscode-jsonrpc"); +let Primitive_option = require("@rescript/runtime/lib/js/Primitive_option.js"); +let GeneratedArtifacts = require("./GeneratedArtifacts.js"); +let LspCompleteGraphQL = require("./LspCompleteGraphQL.js"); +let MessagesJs = require("vscode-jsonrpc/lib/messages.js"); + +let initialized = { + contents: false +}; + +let shutdownRequestAlreadyReceived = { + contents: false +}; + +function log(prim) { + console.error(prim); +} + +let jsonrpcVersion = "2.0"; + +function decodeLspMessage(msg) { + let match = msg.method; + if (match === "textDocument/definition") { + return { + TAG: "Definition", + _0: msg.params + }; + } else if (match === "textDocument/completion") { + return { + TAG: "Completion", + _0: msg.params + }; + } else if (match === "textDocument/didClose") { + return { + TAG: "DidCloseTextDocumentNotification", + _0: msg.params + }; + } else if (match === "textDocument/documentLink") { + return { + TAG: "DocumentLinks", + _0: msg.params + }; + } else if (match === "textDocument/didChange") { + return { + TAG: "DidChangeTextDocumentNotification", + _0: msg.params + }; + } else if (match === "textDocument/didOpen") { + return { + TAG: "DidOpenTextDocumentNotification", + _0: msg.params + }; + } else if (match === "textDocument/hover") { + return { + TAG: "Hover", + _0: msg.params + }; + } else if (match === "textDocument/codeLens") { + return { + TAG: "CodeLens", + _0: msg.params + }; + } else if (match === "textDocument/codeAction") { + return { + TAG: "CodeAction", + _0: msg.params + }; + } else { + return "UnmappedMessage"; + } +} + +let LspMessage = { + decodeLspMessage: decodeLspMessage +}; + +function asMessage(notification) { + return { + jsonrpc: jsonrpcVersion, + method: "textDocument/publishDiagnostics", + params: notification._0 + }; +} + +let Notification = { + asMessage: asMessage +}; + +function codeToInt(code) { + if (code === "ServerNotInitialized") { + return -32002; + } else { + return -32600; + } +} + +function make(code, message) { + return { + code: codeToInt(code), + message: message + }; +} + +let $$Error = { + make: make +}; + +function make$1(textDocumentSyncOpt, hoverProviderOpt, completionProvider, codeLensProviderOpt, documentLinkProviderOpt, codeActionProviderOpt, definitionProviderOpt, param) { + let hoverProvider = hoverProviderOpt !== undefined ? hoverProviderOpt : false; + let codeLensProvider = codeLensProviderOpt !== undefined ? codeLensProviderOpt : false; + let documentLinkProvider = documentLinkProviderOpt !== undefined ? documentLinkProviderOpt : false; + let codeActionProvider = codeActionProviderOpt !== undefined ? codeActionProviderOpt : false; + let definitionProvider = definitionProviderOpt !== undefined ? definitionProviderOpt : false; + return { + capabilities: { + textDocumentSync: 1, + hoverProvider: hoverProvider, + completionProvider: completionProvider, + codeLensProvider: codeLensProvider, + documentLinkProvider: documentLinkProvider, + codeActionProvider: codeActionProvider, + definitionProvider: definitionProvider + } + }; +} + +let InitializeResult = { + make: make$1 +}; + +function $$null() { + return null; +} + +let Result = { + $$null: $$null +}; + +function make$2(id, error, result, param) { + return { + jsonrpc: jsonrpcVersion, + id: id, + error: error, + result: result + }; +} + +let Response = { + make: make$2 +}; + +let Message = { + jsonrpcVersion: jsonrpcVersion, + LspMessage: LspMessage, + Notification: Notification, + $$Error: $$Error, + InitializeResult: InitializeResult, + Result: Result, + Response: Response +}; + +function defaultSendFn(param) { + +} + +let sendFn = { + contents: defaultSendFn +}; + +function send(msg) { + sendFn.contents(msg); +} + +let StreamMessageWriter = {}; + +let StreamMessageReader = {}; + +let Rpc = { + StreamMessageWriter: StreamMessageWriter, + StreamMessageReader: StreamMessageReader +}; + +function start(mode, configFilePath) { + let msg = Utils.readConfigFromDir(configFilePath); + let config; + if (msg.TAG === "Ok") { + config = msg._0; + } else { + let msg$1 = msg._0; + console.error(msg$1); + config = Stdlib.panic(msg$1); + } + let configIssues = []; + InitProject.validateConfig(config, configIssues, configFilePath); + if (configIssues.length !== 0) { + InitProject.printProjectIssues(configIssues); + Stdlib.panic("Invalid ResGraph configuration."); + } + GeneratedArtifacts.sync(config, config.schemas, configFilePath); + let currentResults = {}; + let resFilesCache = {}; + let filesWithDiagnostics = { + contents: [] + }; + let publishDiagnostics = () => { + let filesWithDiagnosticsAtLastPublish = filesWithDiagnostics.contents.slice(); + let errorsByFile = {}; + config.schemas.forEach(schema => { + let match = currentResults[schema.name]; + if (match === undefined) { + return; + } + if (typeof match !== "object") { + return; + } + if (match.status !== "Error") { + return; + } + match.errors.forEach(error => { + let errorsForFile = Stdlib_Option.getOr(errorsByFile[error.file], []); + errorsByFile[error.file] = errorsForFile.concat([error]); + }); + }); + let currentFilesWithDiagnostics = Object.entries(errorsByFile).map(param => { + let file = param[0]; + sendFn.contents(asMessage({ + TAG: "PublishDiagnostics", + _0: { + uri: file, + diagnostics: param[1].map(error => ({ + range: error.range, + message: error.message, + source: "ResGraph" + })) + } + })); + return file; + }); + filesWithDiagnostics.contents = currentFilesWithDiagnostics; + filesWithDiagnosticsAtLastPublish.forEach(fileName => { + if (!currentFilesWithDiagnostics.includes(fileName)) { + return sendFn.contents(asMessage({ + TAG: "PublishDiagnostics", + _0: { + uri: fileName, + diagnostics: [] + } + })); + } + }); + }; + let watchers = config.schemas.map(schema => Utils.setupWatcher((schema, result) => { + if (typeof result !== "object") { + return; + } + currentResults[schema.name] = result._0; + publishDiagnostics(); + }, _schema => {}, schema)); + let openedFile = (uri, text) => { + console.error(`opened ` + uri); + let match = Nodepath.extname(uri); + if (match !== ".res") { + return; + } + resFilesCache[uri] = text; + }; + let updateOpenedFile = (uri, text) => { + if (Nodepath.extname(uri) === ".res" && Stdlib_Option.isSome(resFilesCache[uri])) { + resFilesCache[uri] = text; + return; + } + }; + let onMessage = msg => { + if (MessagesJs.isNotificationMessage(msg)) { + let match = initialized.contents; + let match$1 = msg.method; + if (match) { + if (match$1 === "exit") { + if (shutdownRequestAlreadyReceived.contents === true) { + process.exit(0); + } else { + process.exit(1); + } + } else { + let params = decodeLspMessage(msg); + if (typeof params === "object") { + switch (params.TAG) { + case "DidOpenTextDocumentNotification" : + let params$1 = params._0; + openedFile(params$1.textDocument.uri, params$1.textDocument.text); + break; + case "DidChangeTextDocumentNotification" : + let params$2 = params._0; + let match$2 = params$2.contentChanges.slice().pop(); + if (match$2 !== undefined) { + updateOpenedFile(params$2.textDocument.uri, match$2.text); + } + break; + case "DidCloseTextDocumentNotification" : + Stdlib_Dict.$$delete(resFilesCache, params._0.textDocument.uri); + break; + } + } + } + return; + } + console.error("Could not handle notification message."); + return; + } + if (!MessagesJs.isRequestMessage(msg)) { + return; + } + let sendNullresponse = () => sendFn.contents(make$2(msg.id, undefined, null, undefined)); + let match$3 = initialized.contents; + let match$4 = msg.method; + if (!match$3) { + if (match$4 !== "initialize") { + return sendFn.contents(make$2(msg.id, { + code: -32002, + message: `Server not initialized.` + }, undefined, undefined)); + } else if (match$4 === "initialize") { + initialized.contents = true; + return sendFn.contents(make$2(msg.id, undefined, Primitive_option.some(make$1("Full", true, { + triggerCharacters: [ + "@", + "~" + ] + }, undefined, undefined, undefined, true, undefined)), undefined)); + } else { + return sendFn.contents(make$2(msg.id, { + code: -32600, + message: `Unrecognized editor request.` + }, undefined, undefined)); + } + } + if (match$4 === "shutdown") { + if (shutdownRequestAlreadyReceived.contents === true) { + return sendFn.contents(make$2(msg.id, { + code: -32600, + message: `Language server already received the shutdown request.` + }, undefined, undefined)); + } else { + shutdownRequestAlreadyReceived.contents = true; + watchers.forEach(watcher => { + watcher.close(); + }); + return sendFn.contents(make$2(msg.id, undefined, null, undefined)); + } + } + if (match$4 === "initialize") { + return sendNullresponse(); + } + let params$3 = decodeLspMessage(msg); + if (typeof params$3 === "object") { + switch (params$3.TAG) { + case "Hover" : + let params$4 = params$3._0; + let filePath = Url.fileURLToPath(params$4.textDocument.uri); + let match$5 = Nodepath.extname(params$4.textDocument.uri); + let exit = 0; + switch (match$5) { + case ".graphql" : + let stateName = Stdlib_Option.flatMap(Utils.schemaForGraphqlFile(config, filePath), schema => schema.stateName); + let hover = LspCompleteGraphQL.hoverAtPos(filePath, params$4.position, stateName); + let result = hover !== undefined ? hover : null; + return sendFn.contents(make$2(msg.id, undefined, Primitive_option.some(result), undefined)); + case ".res" : + case ".resi" : + exit = 2; + break; + default: + return sendNullresponse(); + } + if (exit === 2) { + let match$6 = Utils.callPrivateCli({ + TAG: "Hover", + filePath: filePath, + position: params$4.position + }); + let result$1; + result$1 = typeof match$6 !== "object" || match$6.status !== "Hover" ? null : match$6.item; + return sendFn.contents(make$2(msg.id, undefined, Primitive_option.some(result$1), undefined)); + } + break; + case "Completion" : + let params$5 = params$3._0; + let match$7 = Nodepath.extname(params$5.textDocument.uri); + if (match$7 === ".graphql") { + return sendFn.contents(make$2(msg.id, undefined, null, undefined)); + } + let code = resFilesCache[params$5.textDocument.uri]; + if (code === undefined) { + return sendFn.contents(make$2(msg.id, undefined, null, undefined)); + } + let filePath$1 = Url.fileURLToPath(params$5.textDocument.uri); + let tmpname = Utils.createFileInTempDir(undefined); + Nodefs.writeFileSync(tmpname, Buffer.from(code), { + encoding: "utf-8" + }); + let stateName$1 = Stdlib_Option.flatMap(Utils.schemaForFile(config, filePath$1), schema => schema.stateName); + let match$8 = Utils.callPrivateCli({ + TAG: "Completion", + filePath: filePath$1, + position: params$5.position, + tmpname: tmpname, + stateName: stateName$1 + }); + let result$2; + result$2 = typeof match$8 !== "object" || match$8.status !== "Completion" ? null : match$8.items; + return sendFn.contents(make$2(msg.id, undefined, Primitive_option.some(result$2), undefined)); + case "Definition" : + let params$6 = params$3._0; + let filePath$2 = Url.fileURLToPath(params$6.textDocument.uri); + let match$9 = Nodepath.extname(params$6.textDocument.uri); + if (match$9 !== ".graphql") { + return sendNullresponse(); + } + let stateName$2 = Stdlib_Option.flatMap(Utils.schemaForGraphqlFile(config, filePath$2), schema => schema.stateName); + let definition = LspCompleteGraphQL.definitionAtPos(filePath$2, params$6.position, stateName$2); + let result$3 = definition !== undefined ? definition : null; + return sendFn.contents(make$2(msg.id, undefined, Primitive_option.some(result$3), undefined)); + } + } + sendFn.contents(make$2(msg.id, { + code: -32600, + message: `Unrecognized editor request.` + }, undefined, undefined)); + }; + if (mode === "NodeRpc") { + sendFn.contents = prim => { + process.send(prim); + }; + process.on("message", onMessage); + console.error(`Starting LSP in Node RPC.`); + return; + } + let writer = new VscodeJsonrpc.StreamMessageWriter(process.stdout); + let reader = new VscodeJsonrpc.StreamMessageReader(process.stdin); + sendFn.contents = msg => { + writer.write(msg); + }; + reader.listen(onMessage); + console.error(`Starting LSP in stdio mode.`); +} + +process.on("unhandledRejection", (err, _p) => { + console.error(err); +}); + +let Console; + +exports.initialized = initialized; +exports.shutdownRequestAlreadyReceived = shutdownRequestAlreadyReceived; +exports.Console = Console; +exports.log = log; +exports.Message = Message; +exports.defaultSendFn = defaultSendFn; +exports.sendFn = sendFn; +exports.send = send; +exports.Rpc = Rpc; +exports.start = start; +/* Not a pure module */ diff --git a/cli/LspCompleteGraphQL.js b/cli/LspCompleteGraphQL.js new file mode 100644 index 00000000..048d875f --- /dev/null +++ b/cli/LspCompleteGraphQL.js @@ -0,0 +1,157 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +'use strict'; + +let Utils = require("./Utils.js"); +let Nodefs = require("node:fs"); +let Stdlib_Exn = require("@rescript/runtime/lib/js/Stdlib_Exn.js"); +let Primitive_exceptions = require("@rescript/runtime/lib/js/Primitive_exceptions.js"); +let GraphqlLanguageService = require("graphql-language-service"); + +function findOwner(state) { + let match = state.prevState; + if (match == null) { + return; + } + let name = match.name; + if (name == null) { + return; + } + let kind = match.kind; + if (kind == null) { + return; + } else { + return [ + kind, + name + ]; + } +} + +function findLookupValue(token) { + let state = token.state; + let name = state.name; + if (name == null) { + return; + } + let match = state.kind; + if (match == null) { + return; + } + switch (match) { + case "FieldDef" : + let match$1 = findOwner(state); + if (match$1 !== undefined) { + return { + TAG: "Field", + ownerTypeName: match$1[1], + fieldName: name + }; + } else { + return; + } + case "NamedType" : + case "ScalarDef" : + case "ObjectTypeDef" : + case "InterfaceDef" : + case "UnionDef" : + case "EnumDef" : + case "InputDef" : + return { + TAG: "Typename", + _0: name + }; + default: + return; + } +} + +function hoverAtPos(path, pos, stateName) { + try { + let fileContents = Nodefs.readFileSync(path).toString("utf8"); + let match = findLookupValue(GraphqlLanguageService.getTokenAtPosition(fileContents, pos)); + if (match === undefined) { + return; + } + if (match.TAG === "Typename") { + let match$1 = Utils.callPrivateCli({ + TAG: "HoverGraphQL", + filePath: path, + hoverHint: match._0, + stateName: stateName + }); + if (typeof match$1 !== "object" || match$1.status !== "Hover") { + return; + } else { + return match$1.item; + } + } + let match$2 = Utils.callPrivateCli({ + TAG: "HoverGraphQL", + filePath: path, + hoverHint: match.ownerTypeName + `.` + match.fieldName, + stateName: stateName + }); + if (typeof match$2 !== "object" || match$2.status !== "Hover") { + return; + } else { + return match$2.item; + } + } catch (raw_e) { + let e = Primitive_exceptions.internalToException(raw_e); + if (e.RE_EXN_ID === Stdlib_Exn.$$Error) { + console.error(e._1); + return; + } + throw e; + } +} + +function definitionAtPos(path, pos, stateName) { + try { + let fileContents = Nodefs.readFileSync(path).toString("utf8"); + let match = findLookupValue(GraphqlLanguageService.getTokenAtPosition(fileContents, pos)); + if (match === undefined) { + return; + } + if (match.TAG === "Typename") { + let match$1 = Utils.callPrivateCli({ + TAG: "Definition", + filePath: path, + definitionHint: match._0, + stateName: stateName + }); + if (typeof match$1 !== "object" || match$1.status !== "Definition") { + return; + } else { + return match$1.item; + } + } + let match$2 = Utils.callPrivateCli({ + TAG: "Definition", + filePath: path, + definitionHint: match.ownerTypeName + `.` + match.fieldName, + stateName: stateName + }); + if (typeof match$2 !== "object" || match$2.status !== "Definition") { + return; + } else { + return match$2.item; + } + } catch (raw_e) { + let e = Primitive_exceptions.internalToException(raw_e); + if (e.RE_EXN_ID === Stdlib_Exn.$$Error) { + console.error(e._1); + return; + } + throw e; + } +} + +let Console; + +exports.Console = Console; +exports.findOwner = findOwner; +exports.findLookupValue = findLookupValue; +exports.hoverAtPos = hoverAtPos; +exports.definitionAtPos = definitionAtPos; +/* Utils Not a pure module */ diff --git a/cli/LspProtocol.js b/cli/LspProtocol.js new file mode 100644 index 00000000..f69b809e --- /dev/null +++ b/cli/LspProtocol.js @@ -0,0 +1,208 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +'use strict'; + + +function makeDiagnostic(range, message) { + return { + range: range, + message: message, + source: "RescriptRelayRouter" + }; +} + +function makeHover(message, loc) { + return { + contents: { + kind: "markdown", + value: message + }, + range: loc + }; +} + +function completionItemToInt(item) { + switch (item) { + case "Text" : + return 1; + case "Method" : + return 2; + case "Function" : + return 3; + case "Constructor" : + return 4; + case "Field" : + return 5; + case "Variable" : + return 6; + case "Class" : + return 7; + case "Interface" : + return 8; + case "Module" : + return 9; + case "Property" : + return 10; + case "Unit" : + return 11; + case "Value" : + return 12; + case "Enum" : + return 13; + case "Keyword" : + return 14; + case "Snippet" : + return 15; + case "Color" : + return 16; + case "File" : + return 17; + case "Reference" : + return 18; + case "Folder" : + return 19; + case "EnumMember" : + return 20; + case "Constant" : + return 21; + case "Struct" : + return 22; + case "Event" : + return 23; + case "Operator" : + return 24; + case "TypeParameter" : + return 25; + } +} + +function makeCompletionItem(label, kind) { + return { + label: label, + kind: kind + }; +} + +function codeActionKindToString(kind) { + switch (kind) { + case "Empty" : + return ""; + case "QuickFix" : + return "quickfix"; + case "Refactor" : + return "refactor"; + case "RefactorExtract" : + return "refactor.extract"; + case "RefactorInline" : + return "refactor.inline"; + case "RefactorRewrite" : + return "refactor.rewrite"; + case "Source" : + return "source"; + case "SourceOrganizeImports" : + return "source.organizeImports"; + case "SourceFixAll" : + return "source.fixAll"; + } +} + +function make(uri, overwrite, ignoreIfExists, param) { + return { + kind: "create", + uri: uri, + options: { + overwrite: overwrite, + ignoreIfExists: ignoreIfExists + } + }; +} + +let CreateFile = { + make: make +}; + +function make$1(textDocumentUri, edits) { + return { + textDocument: { + uri: textDocumentUri + }, + edits: edits + }; +} + +let TextDocumentEdit = { + make: make$1 +}; + +let DocumentChange = { + CreateFile: CreateFile, + TextDocumentEdit: TextDocumentEdit +}; + +function makeOpenFileCommand(title, fileUri) { + return { + title: title, + command: `vscode.open`, + arguments: [fileUri] + }; +} + +function makeOpenFileAtPosCommand(title, fileUri, pos) { + return { + title: title, + command: `vscode-rescript-relay.open-pos-in-doc`, + arguments: [ + fileUri, + pos.line.toString(), + pos.character.toString() + ] + }; +} + +function makeOpenRouteDefinitionsCommand(title, routes) { + return { + title: title, + command: `vscode-rescript-relay.open-route-definitions`, + arguments: routes.map(r => r.sourceFilePath + `;` + r.routeName + `;` + r.loc.line.toString() + `;` + r.loc.character.toString() + `;` + r.routeRendererFilePath) + }; +} + +function makeTextOnlyCommand(title) { + return { + title: title, + command: "", + arguments: undefined + }; +} + +let Command = { + makeOpenFileCommand: makeOpenFileCommand, + makeOpenFileAtPosCommand: makeOpenFileAtPosCommand, + makeOpenRouteDefinitionsCommand: makeOpenRouteDefinitionsCommand, + makeTextOnlyCommand: makeTextOnlyCommand +}; + +function makeCodeLensItem(range, command) { + return { + range: range, + command: command + }; +} + +function makeDocumentLink(range, fileUri, tooltip, param) { + return { + range: range, + target: fileUri, + tooltip: tooltip + }; +} + +exports.makeDiagnostic = makeDiagnostic; +exports.makeHover = makeHover; +exports.completionItemToInt = completionItemToInt; +exports.makeCompletionItem = makeCompletionItem; +exports.codeActionKindToString = codeActionKindToString; +exports.DocumentChange = DocumentChange; +exports.Command = Command; +exports.makeCodeLensItem = makeCodeLensItem; +exports.makeDocumentLink = makeDocumentLink; +/* No side effect */ diff --git a/cli/Utils.js b/cli/Utils.js new file mode 100644 index 00000000..5b792b4c --- /dev/null +++ b/cli/Utils.js @@ -0,0 +1,556 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +'use strict'; + +let Url = require("url"); +let Nodefs = require("node:fs"); +let Nodeos = require("node:os"); +let Process = require("process"); +let Chokidar = require("chokidar").default; +let Nodepath = require("node:path"); +let Belt_Array = require("@rescript/runtime/lib/js/Belt_Array.js"); +let Stdlib_Exn = require("@rescript/runtime/lib/js/Stdlib_Exn.js"); +let Stdlib_JSON = require("@rescript/runtime/lib/js/Stdlib_JSON.js"); +let Stdlib_Lazy = require("@rescript/runtime/lib/js/Stdlib_Lazy.js"); +let Stdlib_Array = require("@rescript/runtime/lib/js/Stdlib_Array.js"); +let Stdlib_Float = require("@rescript/runtime/lib/js/Stdlib_Float.js"); +let Stdlib_Option = require("@rescript/runtime/lib/js/Stdlib_Option.js"); +let Primitive_option = require("@rescript/runtime/lib/js/Primitive_option.js"); +let Nodechild_process = require("node:child_process"); +let Primitive_exceptions = require("@rescript/runtime/lib/js/Primitive_exceptions.js"); + +let isWindows = (process.platform === "win32"); + +function canonicalPath(path) { + let canonical; + try { + canonical = Nodefs.realpathSync(path); + } catch (exn) { + canonical = path; + } + if (isWindows) { + return canonical.toLowerCase(); + } else { + return canonical; + } +} + +function portableFilesystemIdentity(path) { + return canonicalPath(path).toLowerCase(); +} + +function resolveRelative(path, baseDir) { + return Nodepath.resolve(baseDir, path); +} + +function optionStringToArray(value) { + if (value !== undefined) { + return [Primitive_option.valFromOption(value)]; + } else { + return []; + } +} + +function privateCliCallToArgs(call) { + switch (call.TAG) { + case "GenerateSchema" : + let match = call._0; + let stateName = match.stateName; + let authorization = match.authorization; + let outputFolder = match.outputFolder; + let baseArgs = [ + "generate-schema", + match.projectRoot, + outputFolder, + match.dumpSchemaSdl ? "true" : "false" + ]; + let authorizationArgs; + if (authorization !== undefined) { + let match$1 = authorization.mode; + let tmp; + tmp = match$1 === "Required" ? "required" : "baseline"; + authorizationArgs = [ + tmp, + Stdlib_Option.getOr(authorization.onForbidden, "-"), + Stdlib_Option.getOr(authorization.manifestPath, "-"), + Stdlib_Option.getOr(authorization.baselinePath, "-") + ]; + } else { + authorizationArgs = []; + } + let args = baseArgs.concat(authorizationArgs); + if (stateName !== undefined) { + return args.concat([ + "--schema", + stateName, + "--module", + match.moduleName, + "--context", + match.contextType + ]).concat(match.includePaths.flatMap(path => [ + "--include", + path + ])).concat(match.excludePaths.concat([outputFolder]).flatMap(path => [ + "--exclude", + path + ])); + } else { + return args; + } + case "Completion" : + let position = call.position; + return Belt_Array.concatMany([ + [ + "completion", + call.filePath, + position.line.toString(), + position.character.toString(), + call.tmpname + ], + optionStringToArray(call.stateName) + ]); + case "Hover" : + let position$1 = call.position; + return Belt_Array.concatMany([ + [ + "hover", + call.filePath, + position$1.line.toString(), + position$1.character.toString() + ], + optionStringToArray(call.stateName) + ]); + case "HoverGraphQL" : + return Belt_Array.concatMany([ + [ + "hover-graphql", + call.filePath, + call.hoverHint + ], + optionStringToArray(call.stateName) + ]); + case "Definition" : + return Belt_Array.concatMany([ + [ + "definition-graphql", + call.filePath, + call.definitionHint + ], + optionStringToArray(call.stateName) + ]); + case "FindDefinition" : + return Belt_Array.concatMany([ + [ + "find-definition", + call.filePath, + call.definitionHint + ], + optionStringToArray(call.stateName) + ]); + } +} + +let devBinLocation = "../bin/dev/resgraph.exe"; + +let hasDevBin = Stdlib_Lazy.make(() => Nodefs.existsSync(new Url.URL(devBinLocation, import.meta.url).pathname)); + +function callPrivateCli(command) { + let hasDevBin$1 = Stdlib_Lazy.get(hasDevBin); + let binLocation; + if (hasDevBin$1) { + binLocation = devBinLocation; + } else { + let match = Nodeos.platform(); + let match$1 = Nodeos.arch(); + let tmp = match === "darwin" && match$1 === "arm64" ? "darwinarm64" : match; + binLocation = "../bin/" + tmp + "/resgraph.exe"; + } + return JSON.parse(Nodechild_process.execFileSync(new Url.URL(binLocation, import.meta.url).pathname, privateCliCallToArgs(command), { + maxBuffer: Infinity + }).toString()); +} + +function formatFindDefinitionText(item) { + let match = item.range; + let end_ = match.end; + let start = match.start; + return `path: ` + item.path + `\nkind: ` + item.kind + `\nfile: ` + item.file + `\nrange: ` + start.line.toString() + `:` + start.column.toString() + `-` + end_.line.toString() + `:` + end_.column.toString(); +} + +function stringifyFindDefinitionJson(item) { + let payload_path = item.path; + let payload_kind = item.kind; + let payload_file = item.file; + let payload_range = item.range; + let payload = { + path: payload_path, + kind: payload_kind, + file: payload_file, + range: payload_range + }; + return JSON.stringify(payload); +} + +function stringifyFindDefinitionError(error) { + let payload = { + error: error + }; + return JSON.stringify(payload); +} + +function getLastBuiltFromCompilerLog(compilerLogPath) { + let compilerLogContents = Nodefs.readFileSync(compilerLogPath).toString().split(Nodeos.EOL); + let statusLine = Stdlib_Option.getOr(compilerLogContents[compilerLogContents.length - 2 | 0], ""); + if (statusLine.startsWith("#Done(")) { + return Stdlib_Float.fromString(statusLine.split("#Done(")[1].split(")")[0]); + } +} + +function runIfCompilerDone(fn, compilerLogPath, lastCompletedBuild) { + try { + let buildMarker = getLastBuiltFromCompilerLog(compilerLogPath); + if (buildMarker === undefined) { + return; + } + let lastBuildMarker = lastCompletedBuild.contents; + if (lastBuildMarker !== undefined && lastBuildMarker === buildMarker) { + return; + } else { + lastCompletedBuild.contents = buildMarker; + return fn(); + } + } catch (exn) { + return; + } +} + +function hasProjectConfig(root) { + if (Nodefs.existsSync(Nodepath.join(root, "rescript.json"))) { + return true; + } else { + return Nodefs.existsSync(Nodepath.join(root, "bsconfig.json")); + } +} + +function findCompilerRoot(_path) { + while (true) { + let path = _path; + if (hasProjectConfig(path)) { + return path; + } + let parent = Nodepath.dirname(path); + if (parent === path) { + return; + } + _path = parent; + continue; + }; +} + +function setupWatcher(onResult, onStartRebuild, config) { + let compilerRoot = Stdlib_Option.getOr(findCompilerRoot(config.projectRoot), config.projectRoot); + let compilerLogPath = Nodepath.resolve(compilerRoot, "./lib/bs/.compiler.log"); + let lastCompletedBuild = { + contents: undefined + }; + let generateSchema = () => { + onStartRebuild(config); + try { + let res = callPrivateCli({ + TAG: "GenerateSchema", + _0: config + }); + return onResult(config, { + TAG: "GeneratorResult", + _0: res + }); + } catch (raw_error) { + let error = Primitive_exceptions.internalToException(raw_error); + if (error.RE_EXN_ID === Stdlib_Exn.$$Error) { + console.error(`[` + config.name + `] Generator process failed.`); + console.error(error._1); + return onResult(config, "GeneratorProcessFailure"); + } else { + console.error(`[` + config.name + `] Generator process failed.`); + return onResult(config, "GeneratorProcessFailure"); + } + } + }; + let tmp; + try { + tmp = getLastBuiltFromCompilerLog(compilerLogPath); + } catch (exn) { + tmp = undefined; + } + lastCompletedBuild.contents = tmp; + let compilerWatcher = Chokidar.watch(compilerLogPath).on("change", compilerLogPath => runIfCompilerDone(generateSchema, compilerLogPath, lastCompletedBuild)).on("unlink", compilerLogPath => runIfCompilerDone(generateSchema, compilerLogPath, lastCompletedBuild)); + generateSchema(); + runIfCompilerDone(generateSchema, compilerLogPath, lastCompletedBuild); + return compilerWatcher; +} + +let tempFilePrefix = "resgraph_support_file_" + Process.pid.toString() + "_"; + +let tempFileId = { + contents: 0 +}; + +function createFileInTempDir(extensionOpt) { + let extension = extensionOpt !== undefined ? extensionOpt : ""; + let tempFileName = tempFilePrefix + tempFileId.contents.toString() + extension; + tempFileId.contents = tempFileId.contents + 1 | 0; + return Nodepath.join(Nodeos.tmpdir(), tempFileName); +} + +function parseOptionalString(dict, key) { + let value = dict[key]; + if (value !== undefined) { + return Stdlib_Option.map(Stdlib_JSON.Decode.string(value), value => value); + } else { + return Primitive_option.some(undefined); + } +} + +function parseAuthorizationConfig(dict, baseDir) { + let value = dict["authorization"]; + if (value !== undefined) { + return Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(value), authorization => { + let match = Stdlib_Option.flatMap(authorization["mode"], Stdlib_JSON.Decode.string); + let match$1 = parseOptionalString(authorization, "onForbidden"); + let match$2 = parseOptionalString(authorization, "manifestPath"); + let match$3 = parseOptionalString(authorization, "baselinePath"); + if (match === "required" && match$1 !== undefined && match$2 !== undefined && match$3 !== undefined) { + return Primitive_option.some({ + mode: "Required", + onForbidden: Primitive_option.valFromOption(match$1), + manifestPath: Stdlib_Option.map(Primitive_option.valFromOption(match$2), none => Nodepath.resolve(baseDir, none)), + baselinePath: Stdlib_Option.map(Primitive_option.valFromOption(match$3), none => Nodepath.resolve(baseDir, none)) + }); + } + }); + } else { + return Primitive_option.some(undefined); + } +} + +function decodeStringArray(dict, key, $$default) { + let value = dict[key]; + if (value !== undefined) { + return Stdlib_Option.flatMap(Stdlib_JSON.Decode.array(value), values => { + let decoded = values.map(Stdlib_JSON.Decode.string); + if (decoded.every(Stdlib_Option.isSome)) { + return Stdlib_Array.keepSome(decoded); + } + }); + } else { + return $$default; + } +} + +function capitalizeFirst(value) { + let first = value[0]; + if (first !== undefined) { + return first.toUpperCase() + value.slice(1); + } else { + return value; + } +} + +function moduleNameFromSchemaName(name) { + return name.split("-").flatMap(part => part.split("_")).map(capitalizeFirst).join("") + "Schema"; +} + +function decodeSchema(name, baseDir, projectRoot, dict, stateName) { + let value = dict["dumpSchemaSdl"]; + let dumpSchemaSdl = value !== undefined ? Stdlib_JSON.Decode.bool(value) : false; + let value$1 = dict["contextType"]; + let contextType = value$1 !== undefined ? Stdlib_JSON.Decode.string(value$1) : "ResGraphContext.context"; + let value$2 = dict["moduleName"]; + let moduleName = value$2 !== undefined ? Stdlib_JSON.Decode.string(value$2) : moduleNameFromSchemaName(name); + let match = Stdlib_Option.flatMap(dict["outputFolder"], Stdlib_JSON.Decode.string); + let match$1 = decodeStringArray(dict, "include", []); + let match$2 = decodeStringArray(dict, "exclude", []); + let match$3 = parseAuthorizationConfig(dict, baseDir); + if (match !== undefined && match$1 !== undefined && match$2 !== undefined && moduleName !== undefined && contextType !== undefined && dumpSchemaSdl !== undefined && match$3 !== undefined) { + return { + name: name, + projectRoot: Nodepath.resolve(baseDir, projectRoot), + includePaths: match$1.map(none => Nodepath.resolve(baseDir, none)), + excludePaths: match$2.map(none => Nodepath.resolve(baseDir, none)), + outputFolder: Nodepath.resolve(baseDir, match), + moduleName: moduleName, + contextType: contextType, + dumpSchemaSdl: dumpSchemaSdl, + authorization: Primitive_option.valFromOption(match$3), + stateName: stateName + }; + } +} + +function parseConfig(rawConfig, baseDirOpt) { + let baseDir = baseDirOpt !== undefined ? baseDirOpt : Process.cwd(); + let dict = Stdlib_JSON.Decode.object(rawConfig); + if (dict === undefined) { + return; + } + let rawSchemas = dict["schemas"]; + if (rawSchemas !== undefined) { + let schemaDict = Stdlib_JSON.Decode.object(rawSchemas); + if (schemaDict === undefined) { + return; + } + let schemas = Object.entries(schemaDict).map(param => { + let name = param[0]; + return Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(param[1]), schemaDict => { + let value = schemaDict["projectRoot"]; + let projectRoot = value !== undefined ? Stdlib_JSON.Decode.string(value) : "."; + return Stdlib_Option.flatMap(projectRoot, projectRoot => decodeSchema(name, baseDir, projectRoot, schemaDict, name)); + }); + }); + if (schemas.length === 0 || !schemas.every(Stdlib_Option.isSome)) { + return; + } + let schemas$1 = Stdlib_Array.keepSome(schemas); + let value = dict["defaultSchema"]; + let defaultSchema = value !== undefined ? Stdlib_JSON.Decode.string(value) : Stdlib_Option.map(schemas$1[0], schema => schema.name); + return Stdlib_Option.flatMap(defaultSchema, defaultSchema => Stdlib_Option.map(schemas$1.find(schema => schema.name === defaultSchema), schema => { + let authorization = schema.authorization; + return { + schemas: schemas$1, + defaultSchema: defaultSchema, + legacy: false, + src: schema.projectRoot, + outputFolder: schema.outputFolder, + dumpSchemaSdl: schema.dumpSchemaSdl, + authorization: authorization + }; + })); + } + let src = Stdlib_Option.flatMap(dict["src"], Stdlib_JSON.Decode.string); + if (src !== undefined) { + return Stdlib_Option.map(decodeSchema("default", baseDir, src, dict, undefined), schema => { + let newrecord = {...schema}; + newrecord.moduleName = "ResGraphSchema"; + let authorization = newrecord.authorization; + return { + schemas: [newrecord], + defaultSchema: "default", + legacy: true, + src: newrecord.projectRoot, + outputFolder: newrecord.outputFolder, + dumpSchemaSdl: newrecord.dumpSchemaSdl, + authorization: authorization + }; + }); + } +} + +function readConfigFromDir(dir) { + let readConfigResult = parseConfig(JSON.parse(Nodefs.readFileSync(Nodepath.resolve(dir, "./resgraph.json")).toString("utf8")), dir); + if (readConfigResult !== undefined) { + return { + TAG: "Ok", + _0: readConfigResult + }; + } else { + return { + TAG: "Error", + _0: "Could not parse config, something is wrong" + }; + } +} + +function readConfigFromCwd() { + return readConfigFromDir(Process.cwd()); +} + +function findSchema(config, name) { + return config.schemas.find(schema => schema.name === name); +} + +function defaultSchema(config) { + return findSchema(config, config.defaultSchema); +} + +function pathMatches(path, root) { + if (path === root) { + return true; + } else { + return path.startsWith(root + Nodepath.sep); + } +} + +function schemaForFile(config, filePath) { + let filePath$1 = canonicalPath(filePath); + let matching = config.schemas.filter(schema => { + let projectRoot = canonicalPath(schema.projectRoot); + let includePaths = schema.includePaths.map(canonicalPath); + let excludePaths = schema.excludePaths.map(canonicalPath); + let included = pathMatches(filePath$1, projectRoot) && (includePaths.length === 0 || includePaths.some(root => pathMatches(filePath$1, root))); + let excluded = excludePaths.some(root => pathMatches(filePath$1, root)); + if (included) { + return !excluded; + } else { + return false; + } + }); + if (matching.length !== 1) { + return findSchema(config, config.defaultSchema); + } else { + return matching[0]; + } +} + +function schemaForGraphqlFile(config, filePath) { + let filePath$1 = canonicalPath(filePath); + let mostSpecificSchema = { + contents: undefined + }; + let mostSpecificLength = { + contents: -1 + }; + config.schemas.forEach(schema => { + let outputFolder = canonicalPath(schema.outputFolder); + if (pathMatches(filePath$1, outputFolder) && outputFolder.length > mostSpecificLength.contents) { + mostSpecificSchema.contents = schema; + mostSpecificLength.contents = outputFolder.length; + return; + } + }); + return Stdlib_Option.orElse(mostSpecificSchema.contents, findSchema(config, config.defaultSchema)); +} + +exports.isWindows = isWindows; +exports.canonicalPath = canonicalPath; +exports.portableFilesystemIdentity = portableFilesystemIdentity; +exports.resolveRelative = resolveRelative; +exports.optionStringToArray = optionStringToArray; +exports.privateCliCallToArgs = privateCliCallToArgs; +exports.devBinLocation = devBinLocation; +exports.hasDevBin = hasDevBin; +exports.callPrivateCli = callPrivateCli; +exports.formatFindDefinitionText = formatFindDefinitionText; +exports.stringifyFindDefinitionJson = stringifyFindDefinitionJson; +exports.stringifyFindDefinitionError = stringifyFindDefinitionError; +exports.getLastBuiltFromCompilerLog = getLastBuiltFromCompilerLog; +exports.runIfCompilerDone = runIfCompilerDone; +exports.hasProjectConfig = hasProjectConfig; +exports.findCompilerRoot = findCompilerRoot; +exports.setupWatcher = setupWatcher; +exports.tempFilePrefix = tempFilePrefix; +exports.tempFileId = tempFileId; +exports.createFileInTempDir = createFileInTempDir; +exports.parseOptionalString = parseOptionalString; +exports.parseAuthorizationConfig = parseAuthorizationConfig; +exports.decodeStringArray = decodeStringArray; +exports.capitalizeFirst = capitalizeFirst; +exports.moduleNameFromSchemaName = moduleNameFromSchemaName; +exports.decodeSchema = decodeSchema; +exports.parseConfig = parseConfig; +exports.readConfigFromDir = readConfigFromDir; +exports.readConfigFromCwd = readConfigFromCwd; +exports.findSchema = findSchema; +exports.defaultSchema = defaultSchema; +exports.pathMatches = pathMatches; +exports.schemaForFile = schemaForFile; +exports.schemaForGraphqlFile = schemaForGraphqlFile; +/* isWindows Not a pure module */ diff --git a/package-lock.json b/package-lock.json index ee50a631..95c499b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "resgraph", - "version": "1.3.2", + "version": "1.3.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "resgraph", - "version": "1.3.2", + "version": "1.3.3", "license": "ISC", "dependencies": { "@glennsl/rescript-fetch": "^0.2.2", @@ -22,14 +22,14 @@ "chalk": "^5.2.0", "dataloader": "^2.2.2", "esbuild": "^0.23.1", - "rescript": "^12.0.0" + "rescript": "^12.3.0" }, "engines": { "node": ">=20.11.0" }, "peerDependencies": { "@glennsl/rescript-fetch": ">=0.2.0", - "rescript": ">=12.0.0" + "rescript": ">=12.3.0" } }, "node_modules/@esbuild/aix-ppc64": { @@ -422,13 +422,14 @@ "integrity": "sha512-DcbcbZ/7qctHf5nMerfaZfvHCtJOggy22Wb8vRqzE+OZipFu1VDNwcrWq5w89vAi8rsFD3L2EfzxM6JjNX37Tg==" }, "node_modules/@rescript/darwin-arm64": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@rescript/darwin-arm64/-/darwin-arm64-12.0.0.tgz", - "integrity": "sha512-N2zNj/Uh0zrg8BJGWxdQjYTDf0d2ZV8D+Vmct8xORaUcEZvAkvZb+xA66UxP2QGhWmATcHmBth7oJ48xpWz/Vg==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@rescript/darwin-arm64/-/darwin-arm64-12.3.0.tgz", + "integrity": "sha512-tw0E+XcpPoFj3KizsT5qS/5O/dILi0NBZAz5I8ONK+F43qcqDJDDO+Ise45rGoNWhjwt9WOerYnOd0eUe3U9GA==", "cpu": [ "arm64" ], "dev": true, + "license": "(LGPL-3.0-or-later AND MIT)", "optional": true, "os": [ "darwin" @@ -438,13 +439,14 @@ } }, "node_modules/@rescript/darwin-x64": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@rescript/darwin-x64/-/darwin-x64-12.0.0.tgz", - "integrity": "sha512-Qx/ao3Fl2lx3rF8mbRsyRK5HGAx8pmzyCdpSIAaUun5wLBnsJbNqgvqUH7VeI2TwaczHvcIljrYmnB7T5LJdFQ==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@rescript/darwin-x64/-/darwin-x64-12.3.0.tgz", + "integrity": "sha512-dW2R31PpBvPjSy50mSd9zKOT3dFw/gKtsUZoML81fPuXPJh0h5rwu9z3S40UUUzOa68uKNxXcVLrG1RAgs20Bw==", "cpu": [ "x64" ], "dev": true, + "license": "(LGPL-3.0-or-later AND MIT)", "optional": true, "os": [ "darwin" @@ -454,13 +456,14 @@ } }, "node_modules/@rescript/linux-arm64": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@rescript/linux-arm64/-/linux-arm64-12.0.0.tgz", - "integrity": "sha512-H65csj/0uqqjYy235m0h5CFxbrsas6RoVfjy5Xjf91Vyg/r5R/QIGQvhMtFvl4QojYsa1wXzXGcujwXh+S15xQ==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@rescript/linux-arm64/-/linux-arm64-12.3.0.tgz", + "integrity": "sha512-4GImq346kefHtaMhruFxVwWmr2shWEGQzAGdgZzn5zNWFN32hOXnztHjSFUmj5gmK9fFbidx/oB3BwdbRp5QFQ==", "cpu": [ "arm64" ], "dev": true, + "license": "(LGPL-3.0-or-later AND MIT)", "optional": true, "os": [ "linux" @@ -470,13 +473,14 @@ } }, "node_modules/@rescript/linux-x64": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@rescript/linux-x64/-/linux-x64-12.0.0.tgz", - "integrity": "sha512-WizQwyfMadxF096foZYU3I4zxcnNIc9XP7TP4fqdfe0v6OvQ5yHMI/uIfOsJ1GfNALce2UxvgS0pn67uxc55uA==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@rescript/linux-x64/-/linux-x64-12.3.0.tgz", + "integrity": "sha512-0bPcKI0M38EpoFbhLz2w8ZAY3oZ0GWChwcDn43AN2EAWGLiCORhLP6biqRDg37jK/Bh8Y0MhYzjNWIeHoEDINQ==", "cpu": [ "x64" ], "dev": true, + "license": "(LGPL-3.0-or-later AND MIT)", "optional": true, "os": [ "linux" @@ -486,19 +490,21 @@ } }, "node_modules/@rescript/runtime": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@rescript/runtime/-/runtime-12.0.0.tgz", - "integrity": "sha512-STRbYHT5rnW61vWn2+Bdtj620XaprnoBSbPbkvOYZ+Zs8nfqtpBjhNw+JNpGQLXf73crk6MhxFGPxL2o5vb8TQ==", - "dev": true + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@rescript/runtime/-/runtime-12.3.0.tgz", + "integrity": "sha512-8SRM2f6+LaWach8NJjYQVCCmu6KYmJCb+437L7YpaUpJBDyUOC1x0FF75shZ/Gid9guKPF3zMHrTNHLuyQkkiQ==", + "dev": true, + "license": "MIT" }, "node_modules/@rescript/win32-x64": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@rescript/win32-x64/-/win32-x64-12.0.0.tgz", - "integrity": "sha512-kkqj4kQGbni5YglWoPm76M6hUuzKJHDEiLce5EZxW2Q4FrkS9dNRgm02c2Ck1cz/uKCS3m+ZoKQGIMMXTvo5Jw==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@rescript/win32-x64/-/win32-x64-12.3.0.tgz", + "integrity": "sha512-TM1aLfn8IJFi0/ztVwHqQOcmJg4jWFCmXjrIS9Tif+6Gx8NdX8ttommUscUghe+Dn2pI382FvqPPNWRUR3AI8g==", "cpu": [ "x64" ], "dev": true, + "license": "(LGPL-3.0-or-later AND MIT)", "optional": true, "os": [ "win32" @@ -754,11 +760,11 @@ } }, "node_modules/rescript": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-12.0.0.tgz", - "integrity": "sha512-DGcZI2L5W0c6FuEnspLE0MIe1UtTt1VsW/vQfzBFCEBxSsQtoA6YRHUB8Puwnb30PHqZiFK1ADhn6UgA8LWK0A==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/rescript/-/rescript-12.3.0.tgz", + "integrity": "sha512-xdnpEoDNoeYjZ8HHATwKJEfWti1Nqwx266edfUC8378a5AO1VvZElj2ZZ/SIJNFgUnohH9yDThB+CHEQfijxKw==", "dev": true, - "license": "SEE LICENSE IN LICENSE", + "license": "(LGPL-3.0-or-later AND MIT)", "workspaces": [ "packages/playground", "packages/@rescript/*", @@ -767,10 +773,11 @@ "tests/docstring_tests", "tests/gentype_tests/**", "tests/tools_tests", + "tests/commonjs_tests", "scripts/res" ], "dependencies": { - "@rescript/runtime": "12.0.0" + "@rescript/runtime": "12.3.0" }, "bin": { "bsc": "cli/bsc.js", @@ -783,11 +790,11 @@ "node": ">=20.11.0" }, "optionalDependencies": { - "@rescript/darwin-arm64": "12.0.0", - "@rescript/darwin-x64": "12.0.0", - "@rescript/linux-arm64": "12.0.0", - "@rescript/linux-x64": "12.0.0", - "@rescript/win32-x64": "12.0.0" + "@rescript/darwin-arm64": "12.3.0", + "@rescript/darwin-x64": "12.3.0", + "@rescript/linux-arm64": "12.3.0", + "@rescript/linux-x64": "12.3.0", + "@rescript/win32-x64": "12.3.0" } }, "node_modules/rescript-nodejs": { @@ -995,43 +1002,43 @@ "integrity": "sha512-DcbcbZ/7qctHf5nMerfaZfvHCtJOggy22Wb8vRqzE+OZipFu1VDNwcrWq5w89vAi8rsFD3L2EfzxM6JjNX37Tg==" }, "@rescript/darwin-arm64": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@rescript/darwin-arm64/-/darwin-arm64-12.0.0.tgz", - "integrity": "sha512-N2zNj/Uh0zrg8BJGWxdQjYTDf0d2ZV8D+Vmct8xORaUcEZvAkvZb+xA66UxP2QGhWmATcHmBth7oJ48xpWz/Vg==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@rescript/darwin-arm64/-/darwin-arm64-12.3.0.tgz", + "integrity": "sha512-tw0E+XcpPoFj3KizsT5qS/5O/dILi0NBZAz5I8ONK+F43qcqDJDDO+Ise45rGoNWhjwt9WOerYnOd0eUe3U9GA==", "dev": true, "optional": true }, "@rescript/darwin-x64": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@rescript/darwin-x64/-/darwin-x64-12.0.0.tgz", - "integrity": "sha512-Qx/ao3Fl2lx3rF8mbRsyRK5HGAx8pmzyCdpSIAaUun5wLBnsJbNqgvqUH7VeI2TwaczHvcIljrYmnB7T5LJdFQ==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@rescript/darwin-x64/-/darwin-x64-12.3.0.tgz", + "integrity": "sha512-dW2R31PpBvPjSy50mSd9zKOT3dFw/gKtsUZoML81fPuXPJh0h5rwu9z3S40UUUzOa68uKNxXcVLrG1RAgs20Bw==", "dev": true, "optional": true }, "@rescript/linux-arm64": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@rescript/linux-arm64/-/linux-arm64-12.0.0.tgz", - "integrity": "sha512-H65csj/0uqqjYy235m0h5CFxbrsas6RoVfjy5Xjf91Vyg/r5R/QIGQvhMtFvl4QojYsa1wXzXGcujwXh+S15xQ==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@rescript/linux-arm64/-/linux-arm64-12.3.0.tgz", + "integrity": "sha512-4GImq346kefHtaMhruFxVwWmr2shWEGQzAGdgZzn5zNWFN32hOXnztHjSFUmj5gmK9fFbidx/oB3BwdbRp5QFQ==", "dev": true, "optional": true }, "@rescript/linux-x64": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@rescript/linux-x64/-/linux-x64-12.0.0.tgz", - "integrity": "sha512-WizQwyfMadxF096foZYU3I4zxcnNIc9XP7TP4fqdfe0v6OvQ5yHMI/uIfOsJ1GfNALce2UxvgS0pn67uxc55uA==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@rescript/linux-x64/-/linux-x64-12.3.0.tgz", + "integrity": "sha512-0bPcKI0M38EpoFbhLz2w8ZAY3oZ0GWChwcDn43AN2EAWGLiCORhLP6biqRDg37jK/Bh8Y0MhYzjNWIeHoEDINQ==", "dev": true, "optional": true }, "@rescript/runtime": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@rescript/runtime/-/runtime-12.0.0.tgz", - "integrity": "sha512-STRbYHT5rnW61vWn2+Bdtj620XaprnoBSbPbkvOYZ+Zs8nfqtpBjhNw+JNpGQLXf73crk6MhxFGPxL2o5vb8TQ==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@rescript/runtime/-/runtime-12.3.0.tgz", + "integrity": "sha512-8SRM2f6+LaWach8NJjYQVCCmu6KYmJCb+437L7YpaUpJBDyUOC1x0FF75shZ/Gid9guKPF3zMHrTNHLuyQkkiQ==", "dev": true }, "@rescript/win32-x64": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@rescript/win32-x64/-/win32-x64-12.0.0.tgz", - "integrity": "sha512-kkqj4kQGbni5YglWoPm76M6hUuzKJHDEiLce5EZxW2Q4FrkS9dNRgm02c2Ck1cz/uKCS3m+ZoKQGIMMXTvo5Jw==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@rescript/win32-x64/-/win32-x64-12.3.0.tgz", + "integrity": "sha512-TM1aLfn8IJFi0/ztVwHqQOcmJg4jWFCmXjrIS9Tif+6Gx8NdX8ttommUscUghe+Dn2pI382FvqPPNWRUR3AI8g==", "dev": true, "optional": true }, @@ -1203,17 +1210,17 @@ } }, "rescript": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-12.0.0.tgz", - "integrity": "sha512-DGcZI2L5W0c6FuEnspLE0MIe1UtTt1VsW/vQfzBFCEBxSsQtoA6YRHUB8Puwnb30PHqZiFK1ADhn6UgA8LWK0A==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/rescript/-/rescript-12.3.0.tgz", + "integrity": "sha512-xdnpEoDNoeYjZ8HHATwKJEfWti1Nqwx266edfUC8378a5AO1VvZElj2ZZ/SIJNFgUnohH9yDThB+CHEQfijxKw==", "dev": true, "requires": { - "@rescript/darwin-arm64": "12.0.0", - "@rescript/darwin-x64": "12.0.0", - "@rescript/linux-arm64": "12.0.0", - "@rescript/linux-x64": "12.0.0", - "@rescript/runtime": "12.0.0", - "@rescript/win32-x64": "12.0.0" + "@rescript/darwin-arm64": "12.3.0", + "@rescript/darwin-x64": "12.3.0", + "@rescript/linux-arm64": "12.3.0", + "@rescript/linux-x64": "12.3.0", + "@rescript/runtime": "12.3.0", + "@rescript/win32-x64": "12.3.0" } }, "rescript-nodejs": { diff --git a/package.json b/package.json index 55eda145..66b9ff35 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,14 @@ { "name": "resgraph", - "version": "1.3.2", + "version": "1.3.3", "description": "Build GraphQL servers in ReScript.", "main": "index.js", "scripts": { "prepublish": "npm run build", "build": "rescript clean && npm run res:build && esbuild --external:vscode-jsonrpc --external:chokidar --platform=node --format=esm --bundle cli/Cli.mjs --outfile=dist/Cli.mjs --minify --tree-shaking", "res:build": "rescript", - "res:watch": "rescript -w" + "res:watch": "rescript -w", + "test:package": "node tests/package-runtime-outputs.mjs" }, "bin": { "resgraph": "dist/Cli.mjs" @@ -27,21 +28,21 @@ "node": ">=20.11.0" }, "peerDependencies": { - "rescript": ">=12.0.0", - "@glennsl/rescript-fetch": ">=0.2.0" + "@glennsl/rescript-fetch": ">=0.2.0", + "rescript": ">=12.3.0" }, "dependencies": { + "@glennsl/rescript-fetch": "^0.2.2", "chokidar": "^3.5.3", "graphql-language-service": "^5.1.6", - "vscode-jsonrpc": "^5.0.1", "rescript-nodejs": "^16.1.0", - "@glennsl/rescript-fetch": "^0.2.2" + "vscode-jsonrpc": "^5.0.1" }, "devDependencies": { - "dataloader": "^2.2.2", - "rescript": "^12.0.0", "chalk": "^5.2.0", - "esbuild": "^0.23.1" + "dataloader": "^2.2.2", + "esbuild": "^0.23.1", + "rescript": "^12.3.0" }, "files": [ "bin/*", diff --git a/rescript.json b/rescript.json index 861aa412..32feb49a 100644 --- a/rescript.json +++ b/rescript.json @@ -16,10 +16,15 @@ "package-specs": [ { "module": "esmodule", - "in-source": true + "in-source": true, + "suffix": ".mjs" + }, + { + "module": "commonjs", + "in-source": true, + "suffix": ".js" } ], - "suffix": ".mjs", "dependencies": ["rescript-nodejs", "@glennsl/rescript-fetch"], "compiler-flags": ["-open NodeJs"] } diff --git a/src/res/DataLoader.js b/src/res/DataLoader.js new file mode 100644 index 00000000..e0d7c2d8 --- /dev/null +++ b/src/res/DataLoader.js @@ -0,0 +1,80 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +'use strict'; + +let Dataloader = require("dataloader").default; +let Stdlib_Lazy = require("@rescript/runtime/lib/js/Stdlib_Lazy.js"); +let Primitive_option = require("@rescript/runtime/lib/js/Primitive_option.js"); +let StableStringifyMjs = require("./stableStringify.mjs"); + +let Plain = {}; + +function stableStringifyValue(prim) { + return StableStringifyMjs.stableStringify(prim); +} + +function mapOptions(options) { + let baseOpts_cacheKeyFn = stableStringifyValue; + let baseOpts = { + cacheKeyFn: baseOpts_cacheKeyFn + }; + if (options === undefined) { + return baseOpts; + } + let newrecord = {...baseOpts}; + return newrecord.name = options.name, newrecord.batchScheduleFn = options.batchScheduleFn, newrecord; +} + +function makeSingle(loadFn, options) { + return Stdlib_Lazy.make(() => new Dataloader(keys => Promise.all(keys.map(loadFn)), Primitive_option.toUndefined(mapOptions(options)))); +} + +function makeBatched(loadFn, options) { + return Stdlib_Lazy.make(() => new Dataloader(loadFn, Primitive_option.toUndefined(mapOptions(options)))); +} + +function load(lazyLoader, key) { + let loader = Stdlib_Lazy.get(lazyLoader); + return loader.load(key); +} + +function loadMany(lazyLoader, keys) { + let loader = Stdlib_Lazy.get(lazyLoader); + return loader.loadMany(keys); +} + +function clear(lazyLoader, key) { + let loader = Stdlib_Lazy.get(lazyLoader); + loader.clear(key); +} + +function clearAll(lazyLoader) { + let loader = Stdlib_Lazy.get(lazyLoader); + loader.clearAll(); +} + +function prime(lazyLoader, value) { + let loader = Stdlib_Lazy.get(lazyLoader); + loader.prime(value); +} + +function primeWithPromise(lazyLoader, value) { + let loader = Stdlib_Lazy.get(lazyLoader); + loader.prime(value); +} + +function name(lazyLoader) { + let loader = Stdlib_Lazy.get(lazyLoader); + return Primitive_option.fromNullable(loader.name); +} + +exports.Plain = Plain; +exports.makeSingle = makeSingle; +exports.makeBatched = makeBatched; +exports.load = load; +exports.loadMany = loadMany; +exports.clear = clear; +exports.clearAll = clearAll; +exports.prime = prime; +exports.primeWithPromise = primeWithPromise; +exports.name = name; +/* dataloader Not a pure module */ diff --git a/src/res/GraphQLYoga.js b/src/res/GraphQLYoga.js new file mode 100644 index 00000000..36130222 --- /dev/null +++ b/src/res/GraphQLYoga.js @@ -0,0 +1,50 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +'use strict'; + + +function raise(err) { + throw err; +} + +let GraphQLError = { + raise: raise +}; + +let Rule = {}; + +let ExtendedValidation = { + Rule: Rule +}; + +let Plugin = { + ExtendedValidation: ExtendedValidation +}; + +let Envelope = { + Plugin: Plugin +}; + +let Server = {}; + +let NodeHttpServer = {}; + +let Request; + +let Headers; + +let Body; + +let FormData; + +let Blob; + +exports.Request = Request; +exports.Headers = Headers; +exports.Body = Body; +exports.FormData = FormData; +exports.Blob = Blob; +exports.GraphQLError = GraphQLError; +exports.Envelope = Envelope; +exports.Server = Server; +exports.NodeHttpServer = NodeHttpServer; +/* No side effect */ diff --git a/src/res/ResGraph.js b/src/res/ResGraph.js new file mode 100644 index 00000000..eee27daa --- /dev/null +++ b/src/res/ResGraph.js @@ -0,0 +1,140 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +'use strict'; + +let Graphql = require("graphql"); +let Nodecrypto = require("node:crypto"); +let Primitive_option = require("@rescript/runtime/lib/js/Primitive_option.js"); + +function makeError(message, code) { + return { + message: message, + code: code + }; +} + +function raiseError(error) { + throw new Graphql.GraphQLError(error.message, { + extensions: { + code: error.code + } + }); +} + +function raiseForbidden(_reason) { + return raiseError({ + message: "Forbidden", + code: "FORBIDDEN" + }); +} + +let Authorization = { + makeError: makeError, + raiseError: raiseError, + raiseForbidden: raiseForbidden +}; + +function hashQuery(query) { + return Nodecrypto.createHash("sha256").update(query).digest("hex"); +} + +function makeQueryDocumentCache() { + return {}; +} + +function setCachedQuery(cache, query, document) { + cache[hashQuery(query)] = document; +} + +function getCachedQuery(cache, query) { + return cache[hashQuery(query)]; +} + +function parseQueryCached(cache, query) { + let document = cache[hashQuery(query)]; + if (document !== undefined) { + return Primitive_option.valFromOption(document); + } + let document$1 = Graphql.parse(query); + cache[hashQuery(query)] = document$1; + return document$1; +} + +function variablesFromJson(json) { + if (json === null || typeof json !== "object" || Array.isArray(json)) { + return; + } else { + return json; + } +} + +function variablesToJson(variables) { + return variables; +} + +function executeParsed(schema, document, contextValue, variableValues, operationName, rootValue) { + return Graphql.execute({ + schema: schema, + document: document, + contextValue: contextValue, + variableValues: variableValues, + operationName: operationName, + rootValue: rootValue + }); +} + +function executeParsedToJson(schema, document, contextValue, variablesJson, operationName, rootValue) { + let variableValues = variablesJson !== undefined ? variablesFromJson(variablesJson) : undefined; + return executeParsed(schema, document, contextValue, variableValues, operationName, rootValue).then(prim => prim); +} + +function execute(schema, query, contextValue, cache, variableValues, operationName, rootValue) { + let document = cache !== undefined ? parseQueryCached(cache, query) : Graphql.parse(query); + return executeParsed(schema, document, contextValue, variableValues, operationName, rootValue); +} + +function executeToJson(schema, query, contextValue, cache, variablesJson, operationName, rootValue) { + let variableValues = variablesJson !== undefined ? variablesFromJson(variablesJson) : undefined; + return execute(schema, query, contextValue, cache, variableValues, operationName, rootValue).then(prim => prim); +} + +function Execute_parseQuery(prim) { + return Graphql.parse(prim); +} + +function Execute_executionResultToJson(prim) { + return prim; +} + +let Execute = { + parseQuery: Execute_parseQuery, + makeQueryDocumentCache: makeQueryDocumentCache, + setCachedQuery: setCachedQuery, + getCachedQuery: getCachedQuery, + parseQueryCached: parseQueryCached, + variablesFromJson: variablesFromJson, + variablesToJson: variablesToJson, + executionResultToJson: Execute_executionResultToJson, + executeParsed: executeParsed, + executeParsedToJson: executeParsedToJson, + execute: execute, + executeToJson: executeToJson +}; + +let ResolveInfo = {}; + +let GraphQLLiteralValue; + +let GraphQLJSON; + +let Connections; + +let Utils; + +exports.GraphQLLiteralValue = GraphQLLiteralValue; +exports.GraphQLJSON = GraphQLJSON; +exports.Connections = Connections; +exports.Utils = Utils; +exports.Authorization = Authorization; +exports.Execute = Execute; +exports.ResolveInfo = ResolveInfo; +/* graphql Not a pure module */ diff --git a/src/res/ResGraph__Connections.js b/src/res/ResGraph__Connections.js new file mode 100644 index 00000000..7a444cea --- /dev/null +++ b/src/res/ResGraph__Connections.js @@ -0,0 +1,11 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +'use strict'; + +let GraphqlRelayConnectionsMjs = require("./graphqlRelayConnections.mjs"); + +function connectionFromArray(prim0, prim1) { + return GraphqlRelayConnectionsMjs.connectionFromArray(prim0, prim1); +} + +exports.connectionFromArray = connectionFromArray; +/* ./graphqlRelayConnections.mjs Not a pure module */ diff --git a/src/res/ResGraph__GraphQLJs.js b/src/res/ResGraph__GraphQLJs.js new file mode 100644 index 00000000..3f28645e --- /dev/null +++ b/src/res/ResGraph__GraphQLJs.js @@ -0,0 +1,38 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +'use strict'; + + +let GraphQLLiteralValue = {}; + +let AstNode = {}; + +let Scalars = {}; + +let GraphQLInterfaceType = {}; + +let GraphQLScalar = {}; + +let GraphQLObjectType = {}; + +let GraphQLInputObjectType = {}; + +let GraphQLListType = {}; + +let GraphQLUnionType = {}; + +let GraphQLEnumType = {}; + +let GraphQLSchemaType = {}; + +exports.GraphQLLiteralValue = GraphQLLiteralValue; +exports.AstNode = AstNode; +exports.Scalars = Scalars; +exports.GraphQLInterfaceType = GraphQLInterfaceType; +exports.GraphQLScalar = GraphQLScalar; +exports.GraphQLObjectType = GraphQLObjectType; +exports.GraphQLInputObjectType = GraphQLInputObjectType; +exports.GraphQLListType = GraphQLListType; +exports.GraphQLUnionType = GraphQLUnionType; +exports.GraphQLEnumType = GraphQLEnumType; +exports.GraphQLSchemaType = GraphQLSchemaType; +/* No side effect */ diff --git a/src/res/ResGraph__Utils.js b/src/res/ResGraph__Utils.js new file mode 100644 index 00000000..07a97f8a --- /dev/null +++ b/src/res/ResGraph__Utils.js @@ -0,0 +1,20 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +'use strict'; + +let GraphqlRelayConnectionsMjs = require("./graphqlRelayConnections.mjs"); + +function encode(prim) { + return GraphqlRelayConnectionsMjs.encodeBase64(prim); +} + +function decode(prim) { + return GraphqlRelayConnectionsMjs.decodeBase64(prim); +} + +let Base64 = { + encode: encode, + decode: decode +}; + +exports.Base64 = Base64; +/* ./graphqlRelayConnections.mjs Not a pure module */ diff --git a/tests/package-runtime-outputs.mjs b/tests/package-runtime-outputs.mjs new file mode 100644 index 00000000..a8a0a572 --- /dev/null +++ b/tests/package-runtime-outputs.mjs @@ -0,0 +1,36 @@ +import assert from "node:assert/strict"; +import {execFileSync} from "node:child_process"; +import {createRequire} from "node:module"; +import path from "node:path"; +import {fileURLToPath, pathToFileURL} from "node:url"; + +const testDirectory = path.dirname(fileURLToPath(import.meta.url)); +const packageRoot = path.resolve(testDirectory, ".."); +const commonJsPath = path.join(packageRoot, "src/res/ResGraph.js"); +const esModulePath = path.join(packageRoot, "src/res/ResGraph.mjs"); +const require = createRequire(import.meta.url); + +const commonJsRuntime = require(commonJsPath); +const esModuleRuntime = await import(pathToFileURL(esModulePath)); + +assert.ok(Object.keys(commonJsRuntime).length > 0, "CommonJS runtime is empty"); +assert.ok(Object.keys(esModuleRuntime).length > 0, "ESM runtime is empty"); + +const packOutput = execFileSync( + "npm", + ["pack", "--dry-run", "--json", "--ignore-scripts"], + {cwd: packageRoot, encoding: "utf8"}, +); +const [{files}] = JSON.parse(packOutput); +const packedFiles = new Set(files.map(({path: filePath}) => filePath)); + +assert.ok( + packedFiles.has("src/res/ResGraph.js"), + "npm package is missing CommonJS runtime", +); +assert.ok( + packedFiles.has("src/res/ResGraph.mjs"), + "npm package is missing ESM runtime", +); + +console.log("Verified ESM and CommonJS runtime package outputs.");