diff --git a/TODO b/TODO index 67bcb8df12..15c5f20399 100644 --- a/TODO +++ b/TODO @@ -14,4 +14,8 @@ Good next steps: 1. Get a demo script of creating some tables w the meta api 2. Get export working 3. Get boilerplate (lql init) working -4. Get testing framework working (this will be HUGE) \ No newline at end of file +4. Get testing framework working (this will be HUGE) + + + +**** MOVE postgraphile-s over \ No newline at end of file diff --git a/packages/cli/__tests__/__snapshots__/cli.test.ts.snap b/packages/cli/__tests__/__snapshots__/cli.test.ts.snap new file mode 100644 index 0000000000..79de39c8cb --- /dev/null +++ b/packages/cli/__tests__/__snapshots__/cli.test.ts.snap @@ -0,0 +1,62 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Inquirerer prompts user and correctly processes delayed input 1`] = ` +{ + "autocompleteField": "firry third option", +} +`; + +exports[`Inquirerer prompts user and correctly processes delayed input 2`] = ` +{ + "autocompleteField": "firry third option", +} +`; + +exports[`Inquirerer prompts user and correctly processes delayed input 3`] = ` +[ + "", + "autocompleteField? +Argument--autocompleteFieldtype[autocomplete] +>Yourinput: +$ +", + ">firstoption +", + "firrysecondoption +", + "firrythirdoption +", + "", + "autocompleteField? +Argument--autocompleteFieldtype[autocomplete] +>Yourinput: +$ +", + "firstoption +", + ">firrysecondoption +", + "firrythirdoption +", + "", + "autocompleteField? +Argument--autocompleteFieldtype[autocomplete] +>Yourinput: +$ +", + "firstoption +", + "firrysecondoption +", + ">firrythirdoption +", +] +`; + +exports[`Inquirerer prompts user and correctly processes delayed input 4`] = ` +[ + "", + "", + "", +] +`; diff --git a/packages/cli/__tests__/__snapshots__/extensions.test.ts.snap b/packages/cli/__tests__/__snapshots__/extensions.test.ts.snap new file mode 100644 index 0000000000..e185844bea --- /dev/null +++ b/packages/cli/__tests__/__snapshots__/extensions.test.ts.snap @@ -0,0 +1,87 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`extension runs \`extension\` command after workspace and module setup: extension-update - files 1`] = ` +[ + "__tests__/first.test.ts", + ".questions.json", + "jest.config.js", + "Makefile", + "my-module.control", + "package.json", + "README.md", + "sqitch.conf", + "sqitch.plan", + "src/index.ts", + "tsconfig.esm.json", + "tsconfig.json", +] +`; + +exports[`extension runs \`extension\` command after workspace and module setup: extension-update - result 1`] = ` +{ + "_": [ + "extension", + ], + "cwd": "", + "extensions": [ + "plpgsql", + "module-c", + ], +} +`; + +exports[`extension runs \`extension\` command after workspace and module setup: initial - control file 1`] = ` +"# my-module extension +comment = 'my-module extension' +default_version = '0.0.1' +module_pathname = '$libdir/my-module' +requires = 'mod-1,mod2' +relocatable = false +superuser = false + " +`; + +exports[`extension runs \`extension\` command after workspace and module setup: initial - module dependencies 1`] = ` +{ + "modules": [], + "native": [ + "mod-1", + "mod2", + ], +} +`; + +exports[`extension runs \`extension\` command after workspace and module setup: initial - required modules 1`] = ` +[ + "mod-1", + "mod2", +] +`; + +exports[`extension runs \`extension\` command after workspace and module setup: updated - control file 1`] = ` +"# my-module extension +comment = 'my-module extension' +default_version = '0.0.1' +module_pathname = '$libdir/my-module' +requires = 'plpgsql,module-c' +relocatable = false +superuser = false + " +`; + +exports[`extension runs \`extension\` command after workspace and module setup: updated - module dependencies 1`] = ` +{ + "modules": [], + "native": [ + "plpgsql", + "module-c", + ], +} +`; + +exports[`extension runs \`extension\` command after workspace and module setup: updated - required modules 1`] = ` +[ + "plpgsql", + "module-c", +] +`; diff --git a/packages/cli/__tests__/__snapshots__/init.test.ts.snap b/packages/cli/__tests__/__snapshots__/init.test.ts.snap new file mode 100644 index 0000000000..adfcad8f86 --- /dev/null +++ b/packages/cli/__tests__/__snapshots__/init.test.ts.snap @@ -0,0 +1,122 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`init initialize module 1`] = ` +"# my-module extension +comment = 'my-module extension' +default_version = '0.0.1' +module_pathname = '$libdir/my-module' +requires = 'citext,plpgsql' +relocatable = false +superuser = false + " +`; + +exports[`init initialize module: module-only - argv 1`] = ` +{ + "MODULENAME": "my-module", + "_": [ + "init", + ], + "cwd": "", + "extensions": [ + "plpgsql", + "citext", + ], + "name": "my-module", +} +`; + +exports[`init initialize module: module-only - files 1`] = ` +[ + ".eslintrc.json", + ".gitignore", + ".prettierrc.json", + ".questions.json", + "bin/install.sh", + "bootstrap-roles.sql", + "docker-compose.yml", + "launchql.json", + "lerna.json", + "LICENSE", + "Makefile", + "package.json", + "packages/my-module/__tests__/first.test.ts", + "packages/my-module/.questions.json", + "packages/my-module/jest.config.js", + "packages/my-module/Makefile", + "packages/my-module/my-module.control", + "packages/my-module/package.json", + "packages/my-module/README.md", + "packages/my-module/sqitch.conf", + "packages/my-module/sqitch.plan", + "packages/my-module/src/index.ts", + "packages/my-module/tsconfig.esm.json", + "packages/my-module/tsconfig.json", + "README.md", + "tsconfig.json", +] +`; + +exports[`init initialize module: module-only - result 1`] = ` +{ + "MODULENAME": "my-module", + "_": [ + "init", + ], + "cwd": "", + "extensions": [ + "plpgsql", + "citext", + ], + "name": "my-module", +} +`; + +exports[`init initialize module: module-only - transformResults 1`] = `[]`; + +exports[`init initialize module: module-only - writeResults 1`] = `[]`; + +exports[`init initializes workspace: workspace - argv 1`] = ` +{ + "_": [ + "init", + ], + "cwd": "", + "name": "my-workspace", + "workspace": true, +} +`; + +exports[`init initializes workspace: workspace - files 1`] = ` +[ + "my-workspace/.eslintrc.json", + "my-workspace/.gitignore", + "my-workspace/.prettierrc.json", + "my-workspace/.questions.json", + "my-workspace/bin/install.sh", + "my-workspace/bootstrap-roles.sql", + "my-workspace/docker-compose.yml", + "my-workspace/launchql.json", + "my-workspace/lerna.json", + "my-workspace/LICENSE", + "my-workspace/Makefile", + "my-workspace/package.json", + "my-workspace/README.md", + "my-workspace/tsconfig.json", +] +`; + +exports[`init initializes workspace: workspace - result 1`] = ` +{ + "_": [ + "init", + ], + "cwd": "", + "name": "my-workspace", + "workspace": true, +} +`; + +exports[`init initializes workspace: workspace - transformResults 1`] = `[]`; + +exports[`init initializes workspace: workspace - writeResults 1`] = `[]`; diff --git a/packages/cli/__tests__/cli.test.ts b/packages/cli/__tests__/cli.test.ts new file mode 100644 index 0000000000..5b5afd8fa8 --- /dev/null +++ b/packages/cli/__tests__/cli.test.ts @@ -0,0 +1,42 @@ +import { Inquirerer, Question } from 'inquirerer'; +import { KEY_SEQUENCES, setupTests, TestEnvironment } from '../test-utils'; + +const beforeEachSetup = setupTests(); + +describe('Inquirerer', () => { + let environment: TestEnvironment; + + beforeEach(() => { + environment = beforeEachSetup(); + }); + + it('prompts user and correctly processes delayed input', async () => { + const { mockInput, mockOutput, writeResults, transformResults, enqueueInputResponse } = environment; + + const prompter = new Inquirerer({ + input: mockInput, + output: mockOutput, + noTty: false + }); + + const questions: Question[] = [{ + name: 'autocompleteField', + type: 'autocomplete', + options: ['first option', 'firry second option', 'firry third option'] + }]; + + const argv = {}; + + enqueueInputResponse({ type: 'read', value: 'fir' }); + enqueueInputResponse({ type: 'key', value: KEY_SEQUENCES.DOWN_ARROW }); + enqueueInputResponse({ type: 'key', value: KEY_SEQUENCES.DOWN_ARROW }); + enqueueInputResponse({ type: 'key', value: KEY_SEQUENCES.ENTER }); + + const result = await prompter.prompt(argv, questions); + + expect(argv).toMatchSnapshot(); + expect(result).toMatchSnapshot(); + expect(writeResults).toMatchSnapshot(); + expect(transformResults).toMatchSnapshot(); + }); +}); diff --git a/packages/cli/__tests__/extensions.test.ts b/packages/cli/__tests__/extensions.test.ts new file mode 100644 index 0000000000..11faf1cde1 --- /dev/null +++ b/packages/cli/__tests__/extensions.test.ts @@ -0,0 +1,100 @@ +import { Inquirerer, InquirererOptions } from 'inquirerer'; +import { setupTests, TestEnvironment } from '../test-utils'; +import * as os from 'os'; +import * as path from 'path'; +import * as fs from 'fs'; +import { sync as glob } from 'glob'; +import { commands } from '../src/commands'; +import { ParsedArgs } from 'minimist'; +import { LaunchQLProject } from '@launchql/migrate'; + +const beforeEachSetup = setupTests(); + +describe('extension', () => { + let environment: TestEnvironment; + let tempDir: string; + + beforeAll(() => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'launchql-init-test-')); + }); + + afterAll(() => { + // Uncomment to inspect output: console.log(tempDir); + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + + beforeEach(() => { + environment = beforeEachSetup(); + }); + + const runCommand = async (argv: ParsedArgs) => { + const prompter = new Inquirerer({ + input: environment.mockInput, + output: environment.mockOutput, + noTty: true + }); + + // @ts-ignore + return commands(argv, prompter, {}); + }; + + it('runs `extension` command after workspace and module setup', async () => { + const workspacePath = path.join(tempDir, 'my-workspace'); + const modulePath = path.join(workspacePath, 'packages', 'my-module'); + + // Step 1: Initialize workspace + await runCommand({ + _: ['init'], + cwd: tempDir, + name: 'my-workspace', + workspace: true + }); + + // Step 2: Initialize module inside workspace + await runCommand({ + _: ['init'], + cwd: workspacePath, + name: 'my-module', + MODULENAME: 'my-module', + extensions: ['mod-1', 'mod2'] + }); + + // Step 2b: Snapshot initial control file and module dependencies + const initialProject = new LaunchQLProject(modulePath); + await initialProject.init(); + + expect(initialProject.getModuleControlFile()).toMatchSnapshot('initial - control file'); + expect(initialProject.getModuleDependencies('my-module')).toMatchSnapshot('initial - module dependencies'); + expect(initialProject.getRequiredModules()).toMatchSnapshot('initial - required modules'); + + // Step 3: Run `extension` command to update module + const extensionResult = await runCommand({ + _: ['extension'], + cwd: modulePath, + extensions: ['plpgsql', 'module-c'] + }); + + // Clean `cwd` for stable snapshot + extensionResult.cwd = ''; + + const allFiles = glob('**/*', { + cwd: modulePath, + dot: true, + nodir: true, + absolute: true + }); + + const relativeFiles = allFiles.map(file => path.relative(modulePath, file)); + + expect(extensionResult).toMatchSnapshot('extension-update - result'); + expect(relativeFiles).toMatchSnapshot('extension-update - files'); + + // Step 4: Re-init project and validate changes + const updatedProject = new LaunchQLProject(modulePath); + await updatedProject.init(); + + expect(updatedProject.getModuleControlFile()).toMatchSnapshot('updated - control file'); + expect(updatedProject.getModuleDependencies('my-module')).toMatchSnapshot('updated - module dependencies'); + expect(updatedProject.getRequiredModules()).toMatchSnapshot('updated - required modules'); + }); +}); diff --git a/packages/cli/__tests__/first.test.ts b/packages/cli/__tests__/first.test.ts deleted file mode 100644 index 2d48e8da80..0000000000 --- a/packages/cli/__tests__/first.test.ts +++ /dev/null @@ -1,3 +0,0 @@ -it('works', () => { - console.log('hello test world!'); -}) \ No newline at end of file diff --git a/packages/cli/__tests__/init.test.ts b/packages/cli/__tests__/init.test.ts new file mode 100644 index 0000000000..8fed01e307 --- /dev/null +++ b/packages/cli/__tests__/init.test.ts @@ -0,0 +1,91 @@ +import { Inquirerer, InquirererOptions } from 'inquirerer'; +import { setupTests, TestEnvironment } from '../test-utils'; +import * as os from 'os'; +import * as path from 'path'; +import * as fs from 'fs'; +import { sync as glob } from 'glob'; +import { commands } from '../src/commands'; +import { ParsedArgs } from 'minimist'; +import { LaunchQLProject } from '@launchql/migrate'; + +const beforeEachSetup = setupTests(); + +describe('init', () => { + let environment: TestEnvironment; + let tempDir: string; + + beforeAll(() => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'launchql-init-test-')); + }); + + afterAll(() => { + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + + beforeEach(() => { + environment = beforeEachSetup(); + }); + + const runInitTest = async (argv: ParsedArgs, label: string) => { + const { mockInput, mockOutput, writeResults, transformResults } = environment; + + const prompter = new Inquirerer({ + input: mockInput, + output: mockOutput, + noTty: true + }); + + // @ts-ignore + const result = await commands(argv, prompter, {}); + + const absoluteFiles = glob('**/*', { + cwd: argv.cwd, + dot: true, + nodir: true, + absolute: true + }); + + const relativeFiles = absoluteFiles.map(file => path.relative(argv.cwd, file)); + argv.cwd = ''; + + expect(argv).toMatchSnapshot(`${label} - argv`); + expect(result).toMatchSnapshot(`${label} - result`); + expect(writeResults).toMatchSnapshot(`${label} - writeResults`); + expect(transformResults).toMatchSnapshot(`${label} - transformResults`); + expect(relativeFiles).toMatchSnapshot(`${label} - files`); + }; + + it('initializes workspace', async () => { + await runInitTest( + { + _: ['init'], + cwd: tempDir, + name: 'my-workspace', + workspace: true + }, + 'workspace' + ); + }); + + it('initialize module', async () => { + const workspaceDir = path.join(tempDir, 'my-workspace'); + const moduleDir = path.join(workspaceDir, 'packages', 'my-module'); + + await runInitTest( + { + _: ['init'], + cwd: workspaceDir, + name: 'my-module', + MODULENAME: 'my-module', + extensions: ['plpgsql', 'citext'] + }, + 'module-only' + ); + + const lql = new LaunchQLProject(moduleDir); + await lql.init(); + + expect(lql.getModuleControlFile()).toMatchSnapshot(); + }); + +}); diff --git a/packages/cli/package.json b/packages/cli/package.json index 0ead2cd831..de21a47a8b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -44,7 +44,7 @@ "@launchql/types": "^2.0.0", "chalk": "^4.1.0", "deepmerge": "^4.3.1", - "inquirerer": "^2.0.2", + "inquirerer": "^2.0.6", "js-yaml": "^4.1.0", "minimist": "^1.2.8", "shelljs": "^0.9.2" diff --git a/packages/cli/src/commands/extension.ts b/packages/cli/src/commands/extension.ts index 8a66d17332..5377991960 100644 --- a/packages/cli/src/commands/extension.ts +++ b/packages/cli/src/commands/extension.ts @@ -23,16 +23,17 @@ export default async ( const questions: Question[] = [ { - name: 'modules', + name: 'extensions', message: 'Which modules does this one depend on?', type: 'checkbox', + allowCustomOptions: true, options: filtered, default: installed } ]; const answers = await prompter.prompt(argv, questions); - const selected = (answers.modules as OptionValue[]) + const selected = (answers.extensions as OptionValue[]) .filter(opt => opt.selected) .map(opt => opt.name); diff --git a/packages/cli/src/commands/init/module.ts b/packages/cli/src/commands/init/module.ts index b17cddce90..f5a827f832 100644 --- a/packages/cli/src/commands/init/module.ts +++ b/packages/cli/src/commands/init/module.ts @@ -1,4 +1,4 @@ -import { Inquirerer, Question } from 'inquirerer'; +import { Inquirerer, OptionValue, Question } from 'inquirerer'; import chalk from 'chalk'; import path from 'path'; import fs, { writeFileSync, mkdirSync } from 'fs'; @@ -7,7 +7,7 @@ import { writeRenderedTemplates, moduleTemplate } from '@launchql/templatizer'; -import { getAvailableExtensions, getExtensionInfo, getWorkspacePath, listModules, makePlan, sluggify, writeExtensionControlFile, writeExtensionMakefile } from '@launchql/migrate'; +import { getAvailableExtensions, getExtensionInfo, getWorkspacePath, listModules, makePlan, sluggify, writeExtensionControlFile, writeExtensionMakefile, writeExtensions } from '@launchql/migrate'; import { exec } from 'shelljs'; function isInsideAllowedDirs(cwd: string, allowedDirs: string[]): boolean { @@ -96,6 +96,7 @@ export default async function runModuleSetup(argv: Partial>, message: 'which extensions?', options: availExtensions, type: 'checkbox', + allowCustomOptions: true, // default: ['plpgsql'], // default: [{ // name: 'plpgsql', @@ -135,19 +136,29 @@ export default async function runModuleSetup(argv: Partial>, writeFileSync(`${targetPath}/sqitch.plan`, plan); - const info = await getExtensionInfo(targetPath); - - await writeExtensionMakefile( - info.Makefile, - modName, - '0.0.1' - ); - await writeExtensionControlFile( - info.controlFile, - modName, - answers.extensions.map((a:any)=>a.name), - '0.0.1' - ); + // const info = await getExtensionInfo(targetPath); + + // console.log({ansers: answers.extensions}) + // console.log(answers, argv); + + const mods = answers.extensions.filter((a: OptionValue)=>a.selected).map((a: OptionValue)=>a.name); + const mods2 = argv.extensions.filter((a: OptionValue)=>a.selected).map((a: OptionValue)=>a.value); + // console.log({mods2, ext: argv.extensions}); + + // console.log({mods}); + writeExtensions(targetPath, mods); + + // await writeExtensionMakefile( + // info.Makefile, + // modName, + // '0.0.1' + // ); + // await writeExtensionControlFile( + // info.controlFile, + // modName, + // answers.extensions.filter((a: OptionValue)=>a.selected).map((a: OptionValue)=>a.name), + // '0.0.1' + // ); process.chdir(cur); return { ...argv, ...answers }; diff --git a/packages/cli/test-utils/index.ts b/packages/cli/test-utils/index.ts new file mode 100644 index 0000000000..fd8fe6a91e --- /dev/null +++ b/packages/cli/test-utils/index.ts @@ -0,0 +1,138 @@ +import readline from 'readline'; +import { Readable, Transform, Writable } from 'stream'; +import stripAnsi from 'strip-ansi'; + +import { CLIOptions } from 'inquirerer'; + +export const KEY_SEQUENCES = { + ENTER: '\u000d', + UP_ARROW: '\u001b[A', + DOWN_ARROW: '\u001b[B', + SPACE: ' ' +}; + +function humanizeKeySequences(data: string): string { + const keyMap: { [key: string]: string } = { + '\u000d': '', + '\u001b[A': '', + '\u001b[B': '', + ' ': '' + }; + + return data.replace(/[\u000d\u001b[A\u001b[B ]/g, (match) => keyMap[match] ?? match); +} +interface InputResponse { + type: 'key' | 'read'; + value: string; +} + +interface MockReadline { + question: (questionText: string, cb: (input: string) => void) => void; + close: () => void; +} + +export interface TestEnvironment { + options: Partial; + mockInput: Readable; + mockOutput: Writable; + writeResults: string[]; + transformResults: string[]; + enqueueInputResponse: (input: InputResponse) => void; +} + +function setupReadlineMock(inputQueue: InputResponse[], currentInputIndex: number): void { + readline.createInterface = jest.fn().mockReturnValue({ + question: (questionText: string, cb: (input: string) => void) => { + const nextInput = inputQueue[currentInputIndex++]; + if (nextInput && nextInput.type === 'read') { + setTimeout(() => cb(nextInput.value), 1); // simulate readline 1ms + } + }, + close: jest.fn(), + } as MockReadline); +} + +export function setupTests(): () => TestEnvironment { + let options: Partial; + let mockWrite: jest.Mock; + let mockInput: Readable; + let mockOutput: Writable; + let transformStream: Transform; + + let writeResults: string[] = []; + let transformResults: string[] = []; + + let inputQueue: InputResponse[] = []; + let currentInputIndex = 0; + let lastScheduledTime = 0; + + + const beforeEachSetup = (): TestEnvironment => { + jest.clearAllMocks(); + mockWrite = jest.fn(); + writeResults = []; + transformResults = []; + + mockInput = new Readable({ read(size) { } }); + (mockInput as any).setRawMode = jest.fn(); + + mockOutput = new Writable({ + write: (chunk, encoding, callback) => { + const str = chunk.toString(); + const humanizedStr = humanizeKeySequences(str); + const cleanStr = stripAnsi(humanizedStr); + writeResults.push(cleanStr); + mockWrite(str); + callback(); + } + }); + + // mock I/O streams so we can keep TTY for testing and CI/CD 🎨 + options = { + noTty: false, + input: mockInput, + output: mockOutput, + minimistOpts: { + alias: { + v: 'version' + } + } + }; + + transformStream = new Transform({ + transform(chunk, encoding, callback) { + const data = chunk.toString(); + const humanizedData = humanizeKeySequences(data); + const cleanData = stripAnsi(humanizedData); + transformResults.push(cleanData); + this.push(chunk); + callback(); + } + }); + + setupReadlineMock(inputQueue, currentInputIndex); + mockInput.pipe(transformStream); + + const enqueueInputResponse = (input: InputResponse) => { + lastScheduledTime += 1; + + if (input.type === 'key') { + setTimeout(() => mockInput.push(input.value), lastScheduledTime); + } else { + inputQueue.push(input); // We assume that handling read inputs remains the same unless you need to delay these as well + } + }; + + + return { + options, + mockInput, + mockOutput, + writeResults, + transformResults, + enqueueInputResponse + }; + }; + + return beforeEachSetup; +} diff --git a/packages/explorer/package.json b/packages/explorer/package.json index 4afe777b07..9964032085 100644 --- a/packages/explorer/package.json +++ b/packages/explorer/package.json @@ -43,11 +43,11 @@ "@launchql/types": "^2.0.0", "express": "^5.1.0", "graphile-build": "^4.14.1", - "graphql-upload": "^17.0.0", + "graphql-upload": "^15.0.2", "postgraphile": "^4.14.1" }, "devDependencies": { - "@types/graphql-upload": "^17.0.0", + "@types/graphql-upload": "^15.0.2", "@types/express": "^5.0.1", "@types/rimraf": "^4.0.5", "nodemon": "^3.1.10", diff --git a/packages/explorer/src/server.ts b/packages/explorer/src/server.ts index ae8cfdb306..f0f43857db 100644 --- a/packages/explorer/src/server.ts +++ b/packages/explorer/src/server.ts @@ -1,6 +1,6 @@ import express, { Request, Response, NextFunction, Express } from 'express'; import { postgraphile } from 'postgraphile'; -import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; +import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.js'; import { middleware as parseDomains } from '@launchql/url-domains'; import { graphileCache, diff --git a/packages/migrate/src/init.ts b/packages/migrate/src/init.ts deleted file mode 100644 index 578509cf12..0000000000 --- a/packages/migrate/src/init.ts +++ /dev/null @@ -1,233 +0,0 @@ -import '@launchql/db-template'; -import { promisify } from 'util'; -import { exec } from 'child_process'; -import { sqitchPath, launchqlPath } from './paths'; -import { - writeExtensionMakefile, - writeExtensionControlFile, - getExtensionInfo -} from './extensions'; -import { dirname, basename } from 'path'; -import * as shell from 'shelljs'; -import { writeFileSync } from 'fs'; -import { makePlan } from './plans'; -import { sluggify } from './utils'; - -// const srcPath = dirname(require.resolve('@launchql/db-template')); - -interface PackageOptions { - name: string; - description: string; - author: string; -} - -interface InitOptions extends PackageOptions { - extensions: string[]; - username: string; - scoped: boolean; -} - -const makePackage = ({ name, description, author }: PackageOptions): Record => ({ - name, - version: '0.0.1', - description, - author, - publishConfig: { - access: 'restricted' - }, - scripts: { - test: 'FAST_TEST=1 launchql-templatedb && jest', - 'test:watch': 'FAST_TEST=1 jest --watch' - }, - devDependencies: { - '@babel/cli': '7.10.4', - '@babel/core': '7.10.4', - '@babel/plugin-proposal-class-properties': '7.10.4', - '@babel/plugin-proposal-export-default-from': '7.10.4', - '@babel/plugin-proposal-object-rest-spread': '7.10.4', - '@babel/plugin-transform-runtime': '7.10.4', - '@babel/preset-env': '7.10.4', - '@babel/runtime': '^7.4.2', - '@launchql/db-testing': 'latest', - '@launchql/graphql-testing': 'latest', - 'babel-eslint': '10.1.0', - 'babel-jest': '26.1.0', - 'babel-plugin-import-graphql': '2.7.0', - 'babel-plugin-macros': '2.8.0', - eslint: '^7.3.1', - 'eslint-config-prettier': '^6.10.0', - 'eslint-plugin-prettier': '^3.1.2', - 'graphql-tag': '2.10.3', - graphql: '^14.0.2', - jest: '26.1.0', - prettier: '2.0.5', - 'regenerator-runtime': '^0.13.2' - }, - dependencies: {} -}); - -export const init = async ({ - name, - description, - author, - extensions, - username, - scoped -}: InitOptions): Promise => { - const workspacePath = await launchqlPath(); - - const cur = process.cwd(); - if (process.env.INIT_PATH) { - process.chdir(process.env.INIT_PATH); - } - - const pkgname = scoped ? `@${username}/${name}` : name; - const sqitchname = scoped ? `${username}-${name}` : name; - - const cmd = ['sqitch', 'init', sqitchname, '--engine', 'pg'].join(' '); - await promisify(exec)(cmd.trim()); - - const pkgPath = await sqitchPath(); - const pkg = makePackage({ name: pkgname, description, author }); - - // TODO — discover what was inside of the sqitch/* in the old template -// shell.cp('-r', `${srcPath}/sqitch/*`, `${pkgPath}/`); -// shell.cp('-r', `${srcPath}/sqitch/.*`, `${pkgPath}/`); - - writeFileSync(`${pkgPath}/package.json`, JSON.stringify(pkg, null, 2)); - shell.mkdir('-p', `${pkgPath}/sql`); - - const extname = sluggify(sqitchname); - const info = await getExtensionInfo(pkgPath); - - await writeExtensionMakefile( - info.Makefile, - extname, - '0.0.1' - ); - - await writeExtensionControlFile( - info.controlFile, - extname, - extensions, - '0.0.1' - ); - - const settings = { name: sqitchname, projects: true }; - const plan = await makePlan(workspacePath, pkgPath, settings); - - writeFileSync(`${pkgPath}/sqitch.plan`, plan); - writeFileSync(`${pkgPath}/.npmignore`, `# NOTE keeping this minimal since we generally want everything - -*.log -npm-debug.log* -node_modules -package-lock.json -yarn.lock -`); - writeFileSync(`${pkgPath}/.gitignore`, `node_modules\n`); - process.chdir(cur); -}; - -export const initSkitch = async (): Promise => { - const dir = process.cwd(); - // shell.cp('-r', `${srcPath}/template/*`, `${dir}/`); - // shell.cp('-r', `${srcPath}/template/.*`, `${dir}/`); - - const name = sluggify(basename(process.cwd())); - const pkg = { - private: true, - name, - scripts: { - build: 'lerna run prepare --parallel', - bootstrap: 'lerna bootstrap --use-workspaces' - }, - devDependencies: { - '@babel/cli': '7.12.1', - '@babel/core': '7.12.3', - '@pyramation/babel-preset-env': '0.1.0', - 'babel-eslint': '10.1.0', - 'babel-jest': '26.6.1', - eslint: '7.12.1', - 'eslint-config-prettier': '^6.10.0', - 'eslint-plugin-prettier': '^3.1.2', - jest: '26.6.1', - lerna: '3.22.1', - prettier: '2.1.2' - }, - workspaces: ['packages/*'] - }; - - writeFileSync(`${dir}/package.json`, JSON.stringify(pkg, null, 2)); - - const lerna = { - lerna: '3.4.2', - useWorkspaces: true, - npmClient: 'yarn', - npmClientArgs: ['--no-lockfile'], - packages: ['packages/*'], - version: 'independent', - registry: 'https://registry.npmjs.org', - command: { - create: { - license: 'SEE LICENSE IN LICENSE', - access: 'restricted' - }, - publish: { - allowBranch: 'master' - } - } - }; - - const ignore = `node_modules -.DS_Store -.eslintcache -*.log -**/node_modules -coverage -packages/**/build -packages/**/main -packages/**/module`; - - writeFileSync(`${dir}/lerna.json`, JSON.stringify(lerna, null, 2)); - writeFileSync(`${dir}/.gitignore`, ignore); - writeFileSync(`${dir}/.npmignore`, ignore); - - writeFileSync( - `${dir}/Makefile`, - ` -up: -\tdocker-compose up -d - -down: -\tdocker-compose down -v - -ssh: -\tdocker exec -it ${name}-postgres /bin/bash - -install: -\tdocker exec ${name}-postgres /sql-bin/install.sh -` - ); - - writeFileSync( - `${dir}/docker-compose.yml`, - `version: "2" -services: - postgres: - container_name: ${name}-postgres - image: pyramation/postgis - environment: - - "POSTGRES_USER=postgres" - - "POSTGRES_PASSWORD=password" - ports: - - "5432:5432" - expose: - - "5432" - volumes: - - ./bin:/sql-bin - - ./packages:/sql-packages - - ./extensions:/sql-extensions -` - ); -}; diff --git a/packages/server/package.json b/packages/server/package.json index 1f1e1fbb4f..9861666288 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -51,7 +51,7 @@ "graphile-meta-schema": "^0.2.5", "graphile-search-plugin": "^0.1.2", "graphile-simple-inflector": "^0.1.1", - "graphql-upload": "^17.0.0", + "graphql-upload": "^15.0.2", "graphql-tag": "2.12.6", "lru-cache": "^11.1.0", "pg": "^8.15.6", @@ -63,7 +63,7 @@ }, "devDependencies": { "@types/cors": "^2.8.17", - "@types/graphql-upload": "^17.0.0", + "@types/graphql-upload": "^15.0.2", "@types/express": "^5.0.1", "@types/pg": "^8.11.10", "@types/request-ip": "^0.0.41", diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index 2319f74d7d..20a938628a 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -5,7 +5,7 @@ import { getRootPgPool } from '@launchql/server-utils'; -import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'; +import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.js'; import { middleware as parseDomains } from '@launchql/url-domains'; import express, { Express, RequestHandler } from 'express'; import { createAuthenticateMiddleware } from './middleware/auth'; diff --git a/yarn.lock b/yarn.lock index 50b42219d8..8dedd9901a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1570,16 +1570,15 @@ dependencies: "@types/node" "*" -"@types/graphql-upload@^17.0.0": - version "17.0.0" - resolved "https://registry.yarnpkg.com/@types/graphql-upload/-/graphql-upload-17.0.0.tgz#078d85fdd63271d1ad4b83ea42bbae3370b14d67" - integrity sha512-2ccZtgR7o43PultV3asCeqHZxFnAbtBA7nZ4slEMSaf3fN9yWI11HtZvRuN3LK0Qcdnzj2YeXzBYwbI8WGqhUw== +"@types/graphql-upload@^15.0.2": + version "15.0.2" + resolved "https://registry.yarnpkg.com/@types/graphql-upload/-/graphql-upload-15.0.2.tgz#f6fa1bee2337c6798fb10438a851ed80e3a6f402" + integrity sha512-dK4GN/JbMmgHbsKZaUWVYwaLMCwIR0QMBcFz+jb4xj/cRLq1yo2VfnoFvnP5yCw7W4IgGgW7JRwEvM4jn0ahlA== dependencies: "@types/express" "*" "@types/koa" "*" - "@types/node" "*" fs-capacitor "^8.0.0" - graphql "^16.9.0" + graphql "0.13.1 - 16" "@types/http-assert@*": version "1.5.6" @@ -4021,7 +4020,7 @@ fresh@^2.0.0: resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== -fs-capacitor@^6.1.0: +fs-capacitor@^6.1.0, fs-capacitor@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-6.2.0.tgz#fa79ac6576629163cb84561995602d8999afb7f5" integrity sha512-nKcE1UduoSKX27NSZlg879LdQc94OtbOsEmKMN2MBNudXREvijRKx2GEBsTMTfws+BrbkJoEuynbGSVRSpauvw== @@ -4498,16 +4497,16 @@ graphql-upload@11.0.0: isobject "^4.0.0" object-path "^0.11.4" -graphql-upload@^17.0.0: - version "17.0.0" - resolved "https://registry.yarnpkg.com/graphql-upload/-/graphql-upload-17.0.0.tgz#a6052889e350db0cab4644da9c52ab4713a52f40" - integrity sha512-AI42S1UR1mdqg+LQ7KqGbrgcf4l9gpPu/R0drM4vSA5C94NfIjYyCeCdpktEledvZoAL8JURLLeB53++WACo1w== +graphql-upload@^15.0.2: + version "15.0.2" + resolved "https://registry.yarnpkg.com/graphql-upload/-/graphql-upload-15.0.2.tgz#851667589439ee617238e2d90253000a2601ac04" + integrity sha512-ufJAkZJBKWRDD/4wJR3VZMy9QWTwqIYIciPtCEF5fCNgWF+V1p7uIgz+bP2YYLiS4OJBhCKR8rnqE/Wg3XPUiw== dependencies: "@types/busboy" "^1.5.0" "@types/node" "*" "@types/object-path" "^0.11.1" busboy "^1.6.0" - fs-capacitor "^8.0.0" + fs-capacitor "^6.2.0" http-errors "^2.0.0" object-path "^0.11.8" @@ -4516,7 +4515,7 @@ graphql-ws@^5.6.2: resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.16.2.tgz#7b0306c1bdb0e97a05e800ccd523f46fb212e37c" integrity sha512-E1uccsZxt/96jH/OwmLPuXMACILs76pKF2i3W861LpKBCYtGIyPQGtWLuBLkND4ox1KHns70e83PS4te50nvPQ== -graphql@15.5.2, "graphql@>=0.9 <0.14 || ^14.0.2 || ^15.4.0", "graphql@^0.6.0 || ^0.7.0 || ^0.8.0-b || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.2 || ^15.0.0", graphql@^16.9.0: +"graphql@0.13.1 - 16", graphql@15.5.2, "graphql@>=0.9 <0.14 || ^14.0.2 || ^15.4.0", "graphql@^0.6.0 || ^0.7.0 || ^0.8.0-b || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.2 || ^15.0.0": version "15.5.2" resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.5.2.tgz#efa19f8f2bf1a48eb7d5c85bf17e144ba8bb0480" integrity sha512-dZjLPWNQqYv0dqV2RNbiFed0LtSp6yd4jchsDGnuhDKa9OQHJYCfovaOEvY91w9gqbYO7Se9LKDTl3xxYva/3w== @@ -4908,10 +4907,10 @@ inquirer@^8.2.4: through "^2.3.6" wrap-ansi "^6.0.1" -inquirerer@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/inquirerer/-/inquirerer-2.0.2.tgz#d7f09cab315641df2cefacd476688da43fea6abb" - integrity sha512-spvm1XnBvYtNcEcsmJvnZ6jnV/7XOxcbHOHnYKWZInSi2KibI4CsQ3mtBOiVjjEfb/Cdyj2kXD81gH5qyPSIsA== +inquirerer@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/inquirerer/-/inquirerer-2.0.6.tgz#53132736b88d73a329997b8dbe4323b70cf2394b" + integrity sha512-IWDvzsINKahw8z+b419Baw7SQBjsT1pczpX7sUBiYhwA+lCtzTx3+nQx+d/HcyiXVjQSnIOqQy4viB6RJMMVtg== dependencies: chalk "^4.1.0" deepmerge "^4.3.1"