diff --git a/.eslintignore b/.eslintignore index 9340ad9b86d..0ca61147f73 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,3 +4,4 @@ test/fixtures build/ docs/ protos/ +packages/ diff --git a/.github/header-checker-lint.yml b/.github/header-checker-lint.yml index 2d3ac20701c..0e6c4bc1889 100644 --- a/.github/header-checker-lint.yml +++ b/.github/header-checker-lint.yml @@ -1,2 +1,15 @@ +allowedCopyrightHolders: + - 'Google LLC' +allowedLicenses: + - 'Apache-2.0' + - 'MIT' + - 'BSD-3' +sourceFileExtensions: + - 'ts' + - 'js' + - 'java' ignoreFiles: - "packages/*/webpack.config.js" + - "packages/*/__snapshots__/**/*.js" + - "packages/typeless-sample-bot/test/fixtures/**/*.ts" + - "packages/typeless-sample-bot/test/fixtures/**/*.js" diff --git a/.github/snippet-bot.yml b/.github/snippet-bot.yml new file mode 100644 index 00000000000..6a58a5d5a4b --- /dev/null +++ b/.github/snippet-bot.yml @@ -0,0 +1,3 @@ +ignoreFiles: + - packages/typeless-sample-bot/test/fixtures/**/*.js + - packages/typeless-sample-bot/test/fixtures/**/*.ts diff --git a/packages/typeless-sample-bot/.eslintignore b/packages/typeless-sample-bot/.eslintignore new file mode 100644 index 00000000000..bcfb46a9fd3 --- /dev/null +++ b/packages/typeless-sample-bot/.eslintignore @@ -0,0 +1,5 @@ +**/node_modules +**/coverage +test/fixtures +build/ +__snapshots__/ diff --git a/packages/typeless-sample-bot/.eslintrc.json b/packages/typeless-sample-bot/.eslintrc.json new file mode 100644 index 00000000000..2ac940d538e --- /dev/null +++ b/packages/typeless-sample-bot/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": "./node_modules/gts", + "rules": { + "no-process-exit": 0 + } +} diff --git a/packages/typeless-sample-bot/.gitignore b/packages/typeless-sample-bot/.gitignore new file mode 100644 index 00000000000..dd87e2d73f9 --- /dev/null +++ b/packages/typeless-sample-bot/.gitignore @@ -0,0 +1,2 @@ +node_modules +build diff --git a/packages/typeless-sample-bot/.prettierrc.cjs b/packages/typeless-sample-bot/.prettierrc.cjs new file mode 100644 index 00000000000..d1b95106f4c --- /dev/null +++ b/packages/typeless-sample-bot/.prettierrc.cjs @@ -0,0 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module.exports = { + ...require('gts/.prettierrc.json') +} diff --git a/packages/typeless-sample-bot/__snapshots__/index.js b/packages/typeless-sample-bot/__snapshots__/index.js new file mode 100644 index 00000000000..41e8b63d1c6 --- /dev/null +++ b/packages/typeless-sample-bot/__snapshots__/index.js @@ -0,0 +1,124 @@ +exports['sample transformation correctly transforms TS 1'] = ` +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * This application demonstrates how to perform basic operations on + * schemas with the Google Cloud Pub/Sub API. + * + * For more information, see the README.md under /pubsub and the documentation + * at https://cloud.google.com/pubsub/docs. + */ + +// sample-metadata: +// title: List schemas on a project +// description: Gets a list of schemas which were previously created in the project. +// usage: node listSchemas.js + +// [START pubsub_list_schemas] + +// Imports the Google Cloud client library +const { PubSub } = require("@google-cloud/pubsub"); + +// Creates a client; cache this for further use +const pubSubClient = new PubSub(); + +async function listSchemas() { + for await (const s of pubSubClient.listSchemas()) { + console.log(s.name); + } + console.log('Listed schemas.'); +} +// [END pubsub_list_schemas] + +function main() { + listSchemas().catch((err) => { + console.error(err.message); + process.exitCode = 1; + }); +} + +main(); +` + +exports['command line option "targets" works with a single sample file 1'] = ` +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * This application demonstrates how to perform basic operations on + * schemas with the Google Cloud Pub/Sub API. + * + * For more information, see the README.md under /pubsub and the documentation + * at https://cloud.google.com/pubsub/docs. + */ + +// sample-metadata: +// title: List schemas on a project +// description: Gets a list of schemas which were previously created in the project. +// usage: node listSchemas.js + +// [START pubsub_list_schemas] + +// Imports the Google Cloud client library +const { PubSub } = require("@google-cloud/pubsub"); + +// Creates a client; cache this for further use +const pubSubClient = new PubSub(); + +async function listSchemas() { + for await (const s of pubSubClient.listSchemas()) { + console.log(s.name); + } + console.log('Listed schemas.'); +} +// [END pubsub_list_schemas] + +function main() { + listSchemas().catch((err) => { + console.error(err.message); + process.exitCode = 1; + }); +} + +main(); +` + +exports['command line option "targets" works with multiple sample files 1'] = [ + "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n/**\n * This application demonstrates how to perform basic operations on\n * schemas with the Google Cloud Pub/Sub API.\n *\n * For more information, see the README.md under /pubsub and the documentation\n * at https://cloud.google.com/pubsub/docs.\n */\n\n// sample-metadata:\n// title: Delete a previously created schema\n// description: Deletes a schema which was previously created in the project.\n// usage: node deleteSchema.js \n\n// [START pubsub_delete_schema]\n/**\n * TODO(developer): Uncomment this variable before running the sample.\n */\n// const schemaNameOrId = 'YOUR_SCHEMA_NAME_OR_ID';\n\n// Imports the Google Cloud client library\nconst { PubSub } = require(\"@google-cloud/pubsub\");\n\n// Creates a client; cache this for further use\nconst pubSubClient = new PubSub();\n\nasync function deleteSchema(schemaNameOrId) {\n const schema = pubSubClient.schema(schemaNameOrId);\n const name = await schema.getName();\n await schema.delete();\n console.log(`Schema ${name} deleted.`);\n}\n// [END pubsub_delete_schema]\n\nfunction main(schemaNameOrId = 'YOUR_SCHEMA_NAME_OR_ID') {\n deleteSchema(schemaNameOrId).catch((err) => {\n console.error(err.message);\n process.exitCode = 1;\n });\n}\n\nmain(...process.argv.slice(2));", + "// Copyright 2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n/**\n * This application demonstrates how to perform basic operations on\n * schemas with the Google Cloud Pub/Sub API.\n *\n * For more information, see the README.md under /pubsub and the documentation\n * at https://cloud.google.com/pubsub/docs.\n */\n\n// sample-metadata:\n// title: Get a previously created schema\n// description: Gets information about a schema which was previously created in the project.\n// usage: node getSchema.js \n\n// [START pubsub_get_schema]\n/**\n * TODO(developer): Uncomment this variable before running the sample.\n */\n// const schemaNameOrId = 'YOUR_SCHEMA_NAME_OR_ID';\n\n// Imports the Google Cloud client library\nconst { PubSub } = require(\"@google-cloud/pubsub\");\n\n// Creates a client; cache this for further use\nconst pubSubClient = new PubSub();\n\nasync function getSchema(schemaNameOrId) {\n const schema = pubSubClient.schema(schemaNameOrId);\n const info = await schema.get();\n const fullName = await schema.getName();\n console.log(`Schema ${fullName} info: ${JSON.stringify(info, null, 4)}.`);\n}\n// [END pubsub_get_schema]\n\nfunction main(schemaNameOrId = 'YOUR_SCHEMA_NAME_OR_ID') {\n getSchema(schemaNameOrId).catch((err) => {\n console.error(err.message);\n process.exitCode = 1;\n });\n}\n\nmain(...process.argv.slice(2));" +] + +exports['command line option "recursive" causes recursion and only matches samples 1'] = [ + "// Copyright 2019-2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n/**\n * This application demonstrates how to perform basic operations on\n * subscriptions with the Google Cloud Pub/Sub API.\n *\n * For more information, see the README.md under /pubsub and the documentation\n * at https://cloud.google.com/pubsub/docs.\n */\n\n// sample-metadata:\n// title: Listen For Avro Records\n// description: Listens for records in Avro encoding from a subscription.\n// usage: node listenForAvroRecords.js [timeout-in-seconds]\n\n// [START pubsub_subscribe_avro_records]\n/**\n * TODO(developer): Uncomment these variables before running the sample.\n */\n// const subscriptionNameOrId = 'YOUR_SUBSCRIPTION_NAME_OR_ID';\n// const timeout = 60;\n\n// Imports the Google Cloud client library\nconst { PubSub, Schema, Encodings } = require(\"@google-cloud/pubsub\");\n\n// Node FS library, to load definitions\nconst fs = require(\"fs\");\n\n// And the Apache Avro library\nconst avro = require(\"avro-js\");\n\n// Creates a client; cache this for further use\nconst pubSubClient = new PubSub();\n\nfunction listenForAvroRecords(subscriptionNameOrId, timeout) {\n // References an existing subscription\n const subscription = pubSubClient.subscription(subscriptionNameOrId);\n\n // Make an encoder using the official avro-js library.\n const definition = fs.\n readFileSync('system-test/fixtures/provinces.avsc').\n toString();\n const type = avro.parse(definition);\n\n // Create an event handler to handle messages\n let messageCount = 0;\n const messageHandler = async (message) => {\n // \"Ack\" (acknowledge receipt of) the message\n message.ack();\n\n // Get the schema metadata from the message.\n const schemaMetadata = Schema.metadataFromMessage(message.attributes);\n\n let result;\n switch (schemaMetadata.encoding) {\n case Encodings.Binary:\n result = type.fromBuffer(message.data);\n break;\n case Encodings.Json:\n result = type.fromString(message.data.toString());\n break;\n default:\n console.log(`Unknown schema encoding: ${schemaMetadata.encoding}`);\n break;}\n\n\n console.log(`Received message ${message.id}:`);\n console.log(`\\tData: ${JSON.stringify(result, null, 4)}`);\n console.log(`\\tAttributes: ${message.attributes}`);\n messageCount += 1;\n };\n\n // Listen for new messages until timeout is hit\n subscription.on('message', messageHandler);\n\n setTimeout(() => {\n subscription.removeListener('message', messageHandler);\n console.log(`${messageCount} message(s) received.`);\n }, timeout * 1000);\n}\n// [END pubsub_subscribe_avro_records]\n\nfunction main(\nsubscriptionNameOrId = 'YOUR_SUBSCRIPTION_NAME_OR_ID',\ntimeout = 60)\n{\n timeout = Number(timeout);\n\n try {\n listenForAvroRecords(subscriptionNameOrId, timeout);\n } catch (err) {\n console.error(err.message);\n process.exitCode = 1;\n }\n}\n\nmain(...process.argv.slice(2));", + "// Copyright 2019-2021 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n/**\n * This sample demonstrates how to perform basic operations on topics with\n * the Google Cloud Pub/Sub API.\n *\n * For more information, see the README.md under /pubsub and the documentation\n * at https://cloud.google.com/pubsub/docs.\n */\n\n// sample-metadata:\n// title: Publish Avro Records to a Topic\n// description: Publishes a record in Avro to a topic with a schema.\n// usage: node publishAvroRecords.js \n\n// [START pubsub_publish_avro_records]\n/**\n * TODO(developer): Uncomment this variable before running the sample.\n */\n// const topicNameOrId = 'YOUR_TOPIC_NAME_OR_ID';\n\n// Imports the Google Cloud client library\nconst { PubSub, Encodings } = require(\"@google-cloud/pubsub\");\n\n// And the Apache Avro library\nconst avro = require(\"avro-js\");\nconst fs = require(\"fs\");\n\n// Creates a client; cache this for further use\nconst pubSubClient = new PubSub();\n\n\n\n\n\n\nasync function publishAvroRecords(topicNameOrId) {\n // Get the topic metadata to learn about its schema encoding.\n const topic = pubSubClient.topic(topicNameOrId);\n const [topicMetadata] = await topic.getMetadata();\n const topicSchemaMetadata = topicMetadata.schemaSettings;\n\n if (!topicSchemaMetadata) {\n console.log(`Topic ${topicNameOrId} doesn't seem to have a schema.`);\n return;\n }\n const schemaEncoding = topicSchemaMetadata.encoding;\n\n // Make an encoder using the official avro-js library.\n const definition = fs.\n readFileSync('system-test/fixtures/provinces.avsc').\n toString();\n const type = avro.parse(definition);\n\n // Encode the message.\n const province = {\n name: 'Ontario',\n post_abbr: 'ON' };\n\n let dataBuffer;\n switch (schemaEncoding) {\n case Encodings.Binary:\n dataBuffer = type.toBuffer(province);\n break;\n case Encodings.Json:\n dataBuffer = Buffer.from(type.toString(province));\n break;\n default:\n console.log(`Unknown schema encoding: ${schemaEncoding}`);\n break;}\n\n if (!dataBuffer) {\n console.log(`Invalid encoding ${schemaEncoding} on the topic.`);\n return;\n }\n\n const messageId = await topic.publish(dataBuffer);\n console.log(`Avro record ${messageId} published.`);\n}\n// [END pubsub_publish_avro_records]\n\nfunction main(topicNameOrId = 'YOUR_TOPIC_NAME_OR_ID') {\n publishAvroRecords(topicNameOrId).catch((err) => {\n console.error(err.message);\n process.exitCode = 1;\n });\n}\n\nmain(...process.argv.slice(2));" +] + +exports['sample transformation does not change JS 1'] = [ + { + "filename": "test.js", + "contents": "// Copyright 2019-2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n/**\n * This sample demonstrates how to perform basic operations on topics with\n * the Google Cloud Pub/Sub API.\n *\n * For more information, see the README.md under /pubsub and the documentation\n * at https://cloud.google.com/pubsub/docs.\n */\n\n'use strict';\n\n// This test fixture sample has been modified to factor out changes that\n// gts fix would reverse anyway.\n\n// sample-metadata:\n// title: Create Topic\n// description: Creates a new topic.\n// usage: node createTopic.js \n\nasync function main(topicNameOrId = 'YOUR_TOPIC_NAME_OR_ID') {\n // [START pubsub_create_topic]\n /**\n * TODO(developer): Uncomment this variable before running the sample.\n */\n // const topicNameOrId = 'YOUR_TOPIC_NAME_OR_ID';\n\n // Imports the Google Cloud client library\n const { PubSub } = require('@google-cloud/pubsub');\n\n // Creates a client; cache this for further use\n const pubSubClient = new PubSub();\n\n async function createTopic() {\n // Creates a new topic\n await pubSubClient.createTopic(topicNameOrId);\n console.log(`Topic ${topicNameOrId} created.`);\n }\n\n createTopic();\n // [END pubsub_create_topic]\n}\n\nmain(...process.argv.slice(2)).catch((e) => {\n console.error(e);\n process.exitCode = -1;\n});" + } +] diff --git a/packages/typeless-sample-bot/package.json b/packages/typeless-sample-bot/package.json new file mode 100644 index 00000000000..0cdd9b11c34 --- /dev/null +++ b/packages/typeless-sample-bot/package.json @@ -0,0 +1,52 @@ +{ + "name": "@google-cloud/typeless-sample-bot", + "description": "Google Cloud GitHub bot that converts TypeScript snippets into JavaScript", + "version": "0.0.1", + "license": "Apache-2.0", + "author": "Google LLC", + "engines": { + "node": ">=16.0.0" + }, + "repository": "googleapis/google-cloud-node", + "main": "node build/src/bin/samples-ts-to-js.js", + "type": "module", + "files": [ + "build/src" + ], + "scripts": { + "compile": "tsc", + "test": "c8 mocha build/test --recursive", + "snapshots-update": "SNAPSHOT_UPDATE=1 npm test", + "pretest": "npm run compile", + "system-test": "echo no system tests yet 🙀", + "lint": "gts check", + "fix": "gts fix" + }, + "dependencies": { + "@babel/core": "^7.18.10", + "@babel/plugin-transform-modules-commonjs": "^7.18.0", + "@babel/preset-env": "^7.18.0", + "@babel/preset-typescript": "^7.18.0", + "@babel/traverse": "^7.18.10", + "chalk": "^5.0.1", + "debug": "^4.3.4", + "recast": "^0.21.1", + "yargs": "^17.4.1" + }, + "devDependencies": { + "@babel/cli": "^7.16.0", + "@babel/types": "^7.17.0", + "@types/babel__core": "^7.1.19", + "@types/babel__traverse": "^7.14.2", + "@types/mocha": "^9.1.1", + "@types/node": "^17.0.23", + "@types/sinon": "^10.0.13", + "@types/yargs": "^17.0.10", + "c8": "^7.11.3", + "gts": "^3.1.0", + "mocha": "^10.0.0", + "sinon": "^14.0.0", + "snap-shot-it": "^7.9.6", + "typescript": "^4.6.3" + } +} diff --git a/packages/typeless-sample-bot/src/bin/samples-ts-to-js.ts b/packages/typeless-sample-bot/src/bin/samples-ts-to-js.ts new file mode 100644 index 00000000000..5cfb23e57d8 --- /dev/null +++ b/packages/typeless-sample-bot/src/bin/samples-ts-to-js.ts @@ -0,0 +1,196 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import yargs from 'yargs'; +import loggers from '../loggers.js'; +import {createLoggers} from '../loggers.js'; +import util from 'node:util'; +import {symbols} from '../symbols.js'; +import { + filterByContents, + findSamples, + transformSamples, + writeSamples, + waitForAllSamples, + fromArray, +} from '../samples.js'; +import url from 'node:url'; + +let returnValue = 0; + +// Converts an array of unknown-type items into a string suitable for +// printing to the console. +export function consolize(args: unknown[]): string { + const strings = args.map(a => { + if (typeof a === 'string') { + return a; + } else if (typeof a === 'number') { + return `${a}`; + } else { + return util.inspect(a, false, 2, true); + } + }); + return strings.join(' '); +} + +async function processArgs(args: string[]) { + const argv = await yargs(args) + .options({ + targets: { + demandOption: true, + type: 'array', + describe: 'one or more items to process', + alias: ['t'], + }, + recursive: { + demandOption: false, + boolean: true, + describe: 'process the target(s) as directories, recursively', + alias: ['r'], + }, + verbose: { + demandOption: false, + default: false, + boolean: true, + describe: + 'set flag to get verbose output about actions (GCP_DEBUG=verbose)', + alias: ['v'], + }, + debug: { + demandOption: false, + default: false, + boolean: true, + describe: 'same as using GCP_DEBUG=*', + alias: ['d'], + }, + art: { + demandOption: false, + boolean: true, + default: true, + describe: 'allow ASCII/ANSI art/colours', + alias: ['c'], + }, + }) + .help().argv; + + if (argv.mode === 'help') { + return undefined; + } + + symbols.art = argv.art; + + return argv; +} + +let loggersSetUp = false; +async function setupLoggers(verbose: boolean) { + // This may be needed on subsequent testing runs. + returnValue = 0; + + // Don't double-create or .on() anything. This can happen + // during testing. + if (loggersSetUp) { + return; + } + + await createLoggers(); + + // Set up our log outputs as needed + if (verbose) { + loggers.verbose.on('log', (args: unknown[]) => + console.debug(symbols.grey(consolize([symbols.bug, ...args]))) + ); + } + loggers.step.on('log', (args: unknown[]) => + console.log(consolize([symbols.step, ...args])) + ); + loggers.error.on('log', (args: unknown[]) => { + console.error(symbols.red(consolize([symbols.failure, ...args]))); + + // Also cause main() to return a failure. + returnValue = 1; + }); + + loggersSetUp = true; +} + +export async function main(args: string[]): Promise { + console.log( + symbols.green('Typeless sample bot, converts TS to JS sample snippets') + ); + + // Process command line args and get loggers configured. + const argv = await processArgs(args); + if (!argv) { + return 0; + } + await setupLoggers(argv.verbose); + + // Find all of the samples we're interested in working on. + let sampleFns: AsyncIterable; + if (argv.recursive) { + sampleFns = findSamples( + argv.targets.map(i => i.toString()), + /(?!node_modules).*\.ts$/ + ); + } else { + sampleFns = fromArray(argv.targets.map(i => i.toString())); + } + + // Filter down to samples that have a snippet tag. + const filtered = filterByContents(sampleFns); + + // Transform those samples using Babel. + const transformed = transformSamples(filtered); + + // Write out all of the output samples. + const written = writeSamples(transformed); + + try { + // Wait for the pipeline to complete. + const count = await waitForAllSamples(written); + + if (!count) { + // Should this be considred a failure? + loggers.error('No samples were selected.'); + } + } catch (e) { + loggers.error('Exception during processing:', e); + } + + if (!returnValue) { + console.log(symbols.success, symbols.green('Generation complete!')); + } else { + console.log( + symbols.failure, + symbols.redBright( + 'Something failed. (Maybe not everything, check the log above.)' + ) + ); + } + + return returnValue; +} + +// Only activate our command line mode if this is the "main" module. +if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { + main(process.argv.slice(2)) + .then(e => process.exit(e)) + .catch((e: Error) => { + console.error( + `Top level exception: ${e.toString()} ${e.stack?.toString()}` + ); + process.exit(1); + }); +} diff --git a/packages/typeless-sample-bot/src/debug-js.d.ts b/packages/typeless-sample-bot/src/debug-js.d.ts new file mode 100644 index 00000000000..a66a34adf85 --- /dev/null +++ b/packages/typeless-sample-bot/src/debug-js.d.ts @@ -0,0 +1,16 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Sadly this debug-js module doesn't seem to have typings. +declare module 'debug'; diff --git a/packages/typeless-sample-bot/src/gcp-debuglog.ts b/packages/typeless-sample-bot/src/gcp-debuglog.ts new file mode 100644 index 00000000000..8a58ec5b8ca --- /dev/null +++ b/packages/typeless-sample-bot/src/gcp-debuglog.ts @@ -0,0 +1,119 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import EventEmitter from 'node:events'; +import * as process from 'node:process'; +import * as util from 'node:util'; + +// Adds typings for event sinks. +export declare interface GcpDebugLogger { + on(event: 'log', listener: (args: unknown[]) => void): this; + on(event: string, listener: Function): this; +} + +// Our logger instance. This actually contains the meat of dealing +// with log lines, including EventEmitter +export class GcpDebugLogger extends EventEmitter { + // The function we'll call with new log lines. + // Should be built in Node util stuff, or the "debug" package, or whatever. + upstream: Function; + + // Self-referential function wrapper that calls invoke() on us. + func: GcpDebugLogFunction; + + constructor(upstream: Function) { + super(); + + this.upstream = upstream; + this.func = Object.assign(this.invoke.bind(this), { + // Also add an instance pointer back to us. + instance: this, + + // And pull over the EventEmitter functionality. + on: (event: string, listener: (args: unknown[]) => void) => + this.on(event, listener), + }) as unknown as GcpDebugLogFunction; + } + + invoke(...args: unknown[]): void { + // Push out any upstream logger first. + if (this.upstream) { + this.upstream(...args); + } + + // Emit sink events. + this.emit('log', args); + } +} + +// This can be used in place of a real logger while waiting for Promises. +export const placeholder = new GcpDebugLogger(() => {}).func; + +// Add typing info for the EventEmitter we're adding to the returned function. +export interface GcpDebugLogFunction extends Function { + instance: GcpDebugLogger; + on(event: 'log', listener: (args: unknown[]) => void): this; +} + +// Keep a copy of all namespaced loggers so users can reliably .on() them. +const loggerCache = new Map(); + +// True once we've imported any GCP logging variables into upstream loggers. +let varsSet = false; + +export default async function makeLogger( + namespace: string +): Promise { + // Reuse loggers so things like sinks are persistent. + if (loggerCache.has(namespace)) { + return loggerCache.get(namespace)!.func; + } + + // Look for the GCP debug variable shared across languages. + // Not sure what the format of this will be yet. + const gcpEnv = (process.env['GCP_DEBUG'] ?? '').split(','); + + // Are we plugging into any other popular frameworks? + const debugPkg = (await import('debug')).default; + let logOutput: Function; + if (debugPkg) { + logOutput = debugPkg(namespace); + + if (!varsSet) { + // Also copy over any GCP global enables. + const existingEnables = process.env['DEBUG'] ?? ''; + debugPkg.enable( + `${existingEnables}${existingEnables ? ',' : ''}${gcpEnv}` + ); + + varsSet = true; + } + } else { + logOutput = util.debuglog(namespace); + + if (!varsSet) { + // Also copy over any GCP global enables. + const existingEnables = process.env['NODE_DEBUG'] ?? ''; + process.env['NODE_DEBUG'] = `${existingEnables}${ + existingEnables ? ',' : '' + }${gcpEnv}`; + + varsSet = true; + } + } + + const logger = new GcpDebugLogger(logOutput); + loggerCache.set(namespace, logger); + return logger.func; +} diff --git a/packages/typeless-sample-bot/src/import-to-require.ts b/packages/typeless-sample-bot/src/import-to-require.ts new file mode 100644 index 00000000000..c325125b79e --- /dev/null +++ b/packages/typeless-sample-bot/src/import-to-require.ts @@ -0,0 +1,124 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// https://lihautan.com/step-by-step-guide-for-writing-a-babel-transformation/ +// https://lihautan.com/babel-ast-explorer/ +// https://github.com/esamattis/babel-plugin-ts-optchain/blob/master/packages/babel-plugin-ts-optchain/src/plugin.ts + +import * as Babel from '@babel/types'; +import {NodePath, Visitor} from '@babel/traverse'; + +type NodePathArray = NodePath[]; +type NodePathSingle = NodePath; + +function getArray(path: NodePathSingle, subPathName: string): NodePathArray { + return path.get(subPathName) as NodePathArray; +} + +function getIdentifier(path: NodePath, subPathName: string): string { + const nodeSingle = path.get(subPathName) as NodePathSingle; + const nodeIdentifier = nodeSingle.node as Babel.Identifier; + return nodeIdentifier.name; +} + +function getStringValue(path: NodePath, subPathName: string): string { + const nodeSingle = path.get(subPathName) as NodePathSingle; + const stringLiteral = nodeSingle.node as Babel.StringLiteral; + return stringLiteral.value; +} + +// ImportDeclaration +function specificImport(path: NodePathSingle) { + const specifiers: NodePathArray = getArray(path, 'specifiers'); + const ids = specifiers.map((s: NodePathSingle) => { + // s: ImportSpecifier + const importedName = getIdentifier(s, 'imported'); + const localName = getIdentifier(s, 'local'); + if (importedName !== localName) { + console.error( + 'Imported name', + importedName, + 'is not the same as local name', + localName + ); + return ''; + } else { + return importedName; + } + }); + const from = getStringValue(path, 'source'); + const properties = ids.map(id => { + return Babel.objectProperty( + /* key */ Babel.identifier(id), + /* value */ Babel.identifier(id), + /* computed */ false, + /* shorthand */ true + ); + }); + + const declarator = Babel.variableDeclarator( + Babel.objectPattern(properties), + Babel.callExpression(Babel.identifier('require'), [ + Babel.stringLiteral(from), + ]) + ); + + const replacement = Babel.inherits( + Babel.variableDeclaration('const', [declarator]), + path.node + ); + path.replaceWith(replacement); +} + +// ImportDeclaration +function wildcardImport(path: NodePathSingle) { + const specifiers: NodePathArray = getArray(path, 'specifiers'); + const ns = getIdentifier(specifiers[0], 'local'); + const from = getStringValue(path, 'source'); + + const declarator = Babel.variableDeclarator( + Babel.identifier(ns), + Babel.callExpression(Babel.identifier('require'), [ + Babel.stringLiteral(from), + ]) + ); + + const replacement = Babel.inherits( + Babel.variableDeclaration('const', [declarator]), + path.node + ); + path.replaceWith(replacement); +} + +interface VisitorPlugin { + visitor: Visitor; +} + +export default function importToRequire(): VisitorPlugin { + return { + visitor: { + ImportDeclaration(path: NodePathSingle) { + const specifiers = path.get('specifiers') as NodePathArray; + if (specifiers && specifiers.length > 0) { + if (specifiers[0].isImportSpecifier()) { + specificImport(path); + } else if (specifiers[0].isImportNamespaceSpecifier()) { + wildcardImport(path); + } + path.skip(); + } + }, + }, + }; +} diff --git a/packages/typeless-sample-bot/src/loggers.ts b/packages/typeless-sample-bot/src/loggers.ts new file mode 100644 index 00000000000..447111784ef --- /dev/null +++ b/packages/typeless-sample-bot/src/loggers.ts @@ -0,0 +1,34 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import {placeholder} from './gcp-debuglog.js'; +import makeLog from './gcp-debuglog.js'; + +// This creates our topical loggers. Since this is ESM, loading modules is +// now an async operation, so we have to put some placeholders in (to get +// typings) until these are actually ready. + +const loggers = { + verbose: placeholder, + step: placeholder, + error: placeholder, +}; + +export async function createLoggers() { + loggers.verbose = await makeLog('verbose'); + loggers.step = await makeLog('step'); + loggers.error = await makeLog('error'); +} + +export default loggers; diff --git a/packages/typeless-sample-bot/src/samples.ts b/packages/typeless-sample-bot/src/samples.ts new file mode 100644 index 00000000000..02fb2ddb252 --- /dev/null +++ b/packages/typeless-sample-bot/src/samples.ts @@ -0,0 +1,130 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// TODO(feywind) - Don't lose spaces at the front of comments +// TODO(feywind) - Also add 'use strict' and a comment about being generated + +import loggers from './loggers.js'; +import {treeWalk} from './tree-walk.js'; +import {readFile, writeFile} from 'fs/promises'; +import babel from '@babel/core'; + +// Converts an async iterable into an array of the same type. +export async function toArray(iterable: AsyncIterable): Promise { + const output: T[] = []; + for await (const i of iterable) { + output.push(i); + } + + return output; +} + +// Converts an array into an async iterable of the same type. +export async function* fromArray(array: T[]): AsyncIterable { + for (const i of array) { + yield i; + } +} + +// Given a set of starting paths, search down for anything that matches the regex. +export async function* findSamples( + rootDirs: string[], + matcher?: RegExp +): AsyncIterable { + for (const rootDir of rootDirs) { + for await (const fn of treeWalk(rootDir)) { + if (matcher && !matcher.test(fn)) { + continue; + } + yield fn; + } + } +} + +// Ironically, this triggers snippet-bot, so there's a nonce in the middle: ()? +const sampleChecker = /\[()?START .*\]/; + +export interface Sample { + filename: string; + contents: string; +} + +// Filter an async iterable by file conents. This also loads the file contents +// and caches them, since we'll be needing them anyway. +export async function* filterByContents( + filenames: AsyncIterable +): AsyncIterable { + for await (const fn of filenames) { + const contents = (await readFile(fn)).toString(); + if (sampleChecker.test(contents)) { + yield { + filename: fn, + contents, + }; + } + } +} + +// Instead of a babelrc, this is used so that we can get more control over +// the transform process. +const babelConfig = { + presets: [['@babel/preset-typescript', {}]], + plugins: [['./build/src/import-to-require']], + parserOpts: {} as babel.ParserOptions, + generatorOpts: { + // Ensures that Babel keeps newlines so that comments end up + // on separate lines as before. + retainLines: true, + } as babel.GeneratorOptions, +}; + +// Transform all of the loaded samples from the async iterator, +// producing output samples that have been transformed, with a new filename. +export async function* transformSamples( + samples: AsyncIterable +): AsyncIterable { + for await (const s of samples) { + const config = Object.assign({}, babelConfig, {filename: s.filename}); + const transformed = await babel.transformAsync(s.contents, config); + yield { + filename: s.filename.replace(/\.ts$/g, '.js'), + contents: transformed?.code || '', + }; + } +} + +// Write out all samples to the file system. +export async function* writeSamples( + samples: AsyncIterable +): AsyncIterable { + for await (const s of samples) { + loggers.verbose('writing new sample', s.filename); + await writeFile(s.filename, s.contents); + yield s; + } +} + +// Terminator function that waits for everything else to complete, +// and returns a count of how many we processed. +export async function waitForAllSamples( + samples: AsyncIterable +): Promise { + let count = 0; + for await (const s of samples) { + loggers.step('Generated', s.filename); + count++; + } + + return count; +} diff --git a/packages/typeless-sample-bot/src/symbols.ts b/packages/typeless-sample-bot/src/symbols.ts new file mode 100644 index 00000000000..f897c6157f1 --- /dev/null +++ b/packages/typeless-sample-bot/src/symbols.ts @@ -0,0 +1,98 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import chalk from 'chalk'; + +/** + * `Symbols` is our switchboard for accessing things that we might + * need to disable for accessibility or other reasons. This takes care + * of getting back reasonable text for symbols, as well as coloration. + * + * This module serves a secondary goal of containing all emoji text + * to the one file, to avoid messy unicode source files. + */ +class Symbols { + // Controlled by the arg parser. + private _art = true; + + get art() { + return this._art; + } + + set art(useArt: boolean) { + this._art = useArt; + } + + // Looks up the symbol set to use, based on `art`. + private get symbolSet(): string[] { + if (this._art) { + return emojiSymbols; + } else { + return plainSymbols; + } + } + + // Wrapper for chalk.green() that turns off if `art` is false. + green(str: string): string { + return this._art ? chalk.green(str) : str; + } + + // Wrapper for chalk.grey() that turns off if `art` is false. + grey(str: string): string { + return this._art ? chalk.grey(str) : str; + } + + // Wrapper for chalk.red() that turns off if `art` is false. + red(str: string): string { + return this._art ? chalk.red(str) : str; + } + + // Wrapper for chalk.redBright() that turns off if `art` is false. + redBright(str: string): string { + return this._art ? chalk.redBright(str) : str; + } + + // Returns an appropriate symbol text for "debug", based on `art`. + get bug(): string { + return this.symbolSet[symbolEnum.bug]; + } + + // Returns an appropriate symbol text for a "failure", based on `art`. + get failure(): string { + return this.symbolSet[symbolEnum.failure]; + } + + // Returns an appropriate symbol text for a "success", based on `art`. + get success(): string { + return this.symbolSet[symbolEnum.success]; + } + + // Returns an appropriate symbol text for a "step", based on `art`. + get step(): string { + return this.symbolSet[symbolEnum.step]; + } +} + +export const symbols = new Symbols(); + +const symbolEnum = { + bug: 0, + failure: 1, + success: 2, + step: 3, +}; + +const emojiSymbols = ['🐞', '❌', '✅', '✨']; + +const plainSymbols = ['[bug]', '[error]', '[success]', '[step]']; diff --git a/packages/typeless-sample-bot/src/tree-walk.ts b/packages/typeless-sample-bot/src/tree-walk.ts new file mode 100644 index 00000000000..05c0c60e7ca --- /dev/null +++ b/packages/typeless-sample-bot/src/tree-walk.ts @@ -0,0 +1,30 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import {resolve} from 'node:path'; +import {readdir} from 'node:fs/promises'; + +// Given a root directory, traverse its tree to find all files. This +// will be done using async iterators, returning results opportunistically. +export async function* treeWalk(rootDir: string): AsyncIterable { + const entries = await readdir(rootDir, {withFileTypes: true}); + for (const entry of entries) { + const resolved = resolve(rootDir, entry.name); + if (entry.isDirectory()) { + yield* treeWalk(resolved); + } else { + yield resolved; + } + } +} diff --git a/packages/typeless-sample-bot/test/fixtures/createTopic.js b/packages/typeless-sample-bot/test/fixtures/createTopic.js new file mode 100644 index 00000000000..6e9ac0b967b --- /dev/null +++ b/packages/typeless-sample-bot/test/fixtures/createTopic.js @@ -0,0 +1,59 @@ +// Copyright 2019-2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * This sample demonstrates how to perform basic operations on topics with + * the Google Cloud Pub/Sub API. + * + * For more information, see the README.md under /pubsub and the documentation + * at https://cloud.google.com/pubsub/docs. + */ + +'use strict'; + +// This test fixture sample has been modified to factor out changes that +// gts fix would reverse anyway. + +// sample-metadata: +// title: Create Topic +// description: Creates a new topic. +// usage: node createTopic.js + +async function main(topicNameOrId = 'YOUR_TOPIC_NAME_OR_ID') { + // [START pubsub_create_topic] + /** + * TODO(developer): Uncomment this variable before running the sample. + */ + // const topicNameOrId = 'YOUR_TOPIC_NAME_OR_ID'; + + // Imports the Google Cloud client library + const {PubSub} = require('@google-cloud/pubsub'); + + // Creates a client; cache this for further use + const pubSubClient = new PubSub(); + + async function createTopic() { + // Creates a new topic + await pubSubClient.createTopic(topicNameOrId); + console.log(`Topic ${topicNameOrId} created.`); + } + + createTopic(); + // [END pubsub_create_topic] +} + +main(...process.argv.slice(2)).catch((e) => { + console.error(e); + process.exitCode = -1; +}); diff --git a/packages/typeless-sample-bot/test/fixtures/deleteSchema.ts b/packages/typeless-sample-bot/test/fixtures/deleteSchema.ts new file mode 100644 index 00000000000..8599d61578a --- /dev/null +++ b/packages/typeless-sample-bot/test/fixtures/deleteSchema.ts @@ -0,0 +1,55 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * This application demonstrates how to perform basic operations on + * schemas with the Google Cloud Pub/Sub API. + * + * For more information, see the README.md under /pubsub and the documentation + * at https://cloud.google.com/pubsub/docs. + */ + +// sample-metadata: +// title: Delete a previously created schema +// description: Deletes a schema which was previously created in the project. +// usage: node deleteSchema.js + +// [START pubsub_delete_schema] +/** + * TODO(developer): Uncomment this variable before running the sample. + */ +// const schemaNameOrId = 'YOUR_SCHEMA_NAME_OR_ID'; + +// Imports the Google Cloud client library +import {PubSub} from '@google-cloud/pubsub'; + +// Creates a client; cache this for further use +const pubSubClient = new PubSub(); + +async function deleteSchema(schemaNameOrId: string) { + const schema = pubSubClient.schema(schemaNameOrId); + const name = await schema.getName(); + await schema.delete(); + console.log(`Schema ${name} deleted.`); +} +// [END pubsub_delete_schema] + +function main(schemaNameOrId = 'YOUR_SCHEMA_NAME_OR_ID') { + deleteSchema(schemaNameOrId).catch(err => { + console.error(err.message); + process.exitCode = 1; + }); +} + +main(...process.argv.slice(2)); diff --git a/packages/typeless-sample-bot/test/fixtures/folder/listenForAvroRecords.ts b/packages/typeless-sample-bot/test/fixtures/folder/listenForAvroRecords.ts new file mode 100644 index 00000000000..f479e04c13b --- /dev/null +++ b/packages/typeless-sample-bot/test/fixtures/folder/listenForAvroRecords.ts @@ -0,0 +1,109 @@ +// Copyright 2019-2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * This application demonstrates how to perform basic operations on + * subscriptions with the Google Cloud Pub/Sub API. + * + * For more information, see the README.md under /pubsub and the documentation + * at https://cloud.google.com/pubsub/docs. + */ + +// sample-metadata: +// title: Listen For Avro Records +// description: Listens for records in Avro encoding from a subscription. +// usage: node listenForAvroRecords.js [timeout-in-seconds] + +// [START pubsub_subscribe_avro_records] +/** + * TODO(developer): Uncomment these variables before running the sample. + */ +// const subscriptionNameOrId = 'YOUR_SUBSCRIPTION_NAME_OR_ID'; +// const timeout = 60; + +// Imports the Google Cloud client library +import {PubSub, Schema, Encodings, Message} from '@google-cloud/pubsub'; + +// Node FS library, to load definitions +import * as fs from 'fs'; + +// And the Apache Avro library +import * as avro from 'avro-js'; + +// Creates a client; cache this for further use +const pubSubClient = new PubSub(); + +function listenForAvroRecords(subscriptionNameOrId: string, timeout: number) { + // References an existing subscription + const subscription = pubSubClient.subscription(subscriptionNameOrId); + + // Make an encoder using the official avro-js library. + const definition = fs + .readFileSync('system-test/fixtures/provinces.avsc') + .toString(); + const type = avro.parse(definition); + + // Create an event handler to handle messages + let messageCount = 0; + const messageHandler = async (message: Message) => { + // "Ack" (acknowledge receipt of) the message + message.ack(); + + // Get the schema metadata from the message. + const schemaMetadata = Schema.metadataFromMessage(message.attributes); + + let result: object | undefined; + switch (schemaMetadata.encoding) { + case Encodings.Binary: + result = type.fromBuffer(message.data); + break; + case Encodings.Json: + result = type.fromString(message.data.toString()); + break; + default: + console.log(`Unknown schema encoding: ${schemaMetadata.encoding}`); + break; + } + + console.log(`Received message ${message.id}:`); + console.log(`\tData: ${JSON.stringify(result, null, 4)}`); + console.log(`\tAttributes: ${message.attributes}`); + messageCount += 1; + }; + + // Listen for new messages until timeout is hit + subscription.on('message', messageHandler); + + setTimeout(() => { + subscription.removeListener('message', messageHandler); + console.log(`${messageCount} message(s) received.`); + }, timeout * 1000); +} +// [END pubsub_subscribe_avro_records] + +function main( + subscriptionNameOrId = 'YOUR_SUBSCRIPTION_NAME_OR_ID', + timeout = 60 +) { + timeout = Number(timeout); + + try { + listenForAvroRecords(subscriptionNameOrId, timeout); + } catch (err) { + console.error(err.message); + process.exitCode = 1; + } +} + +main(...process.argv.slice(2)); diff --git a/packages/typeless-sample-bot/test/fixtures/folder/publishAvroRecords.ts b/packages/typeless-sample-bot/test/fixtures/folder/publishAvroRecords.ts new file mode 100644 index 00000000000..d269f547926 --- /dev/null +++ b/packages/typeless-sample-bot/test/fixtures/folder/publishAvroRecords.ts @@ -0,0 +1,101 @@ +// Copyright 2019-2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * This sample demonstrates how to perform basic operations on topics with + * the Google Cloud Pub/Sub API. + * + * For more information, see the README.md under /pubsub and the documentation + * at https://cloud.google.com/pubsub/docs. + */ + +// sample-metadata: +// title: Publish Avro Records to a Topic +// description: Publishes a record in Avro to a topic with a schema. +// usage: node publishAvroRecords.js + +// [START pubsub_publish_avro_records] +/** + * TODO(developer): Uncomment this variable before running the sample. + */ +// const topicNameOrId = 'YOUR_TOPIC_NAME_OR_ID'; + +// Imports the Google Cloud client library +import {PubSub, Encodings} from '@google-cloud/pubsub'; + +// And the Apache Avro library +import * as avro from 'avro-js'; +import * as fs from 'fs'; + +// Creates a client; cache this for further use +const pubSubClient = new PubSub(); + +interface ProvinceObject { + name: string; + post_abbr: string; +} + +async function publishAvroRecords(topicNameOrId: string) { + // Get the topic metadata to learn about its schema encoding. + const topic = pubSubClient.topic(topicNameOrId); + const [topicMetadata] = await topic.getMetadata(); + const topicSchemaMetadata = topicMetadata.schemaSettings; + + if (!topicSchemaMetadata) { + console.log(`Topic ${topicNameOrId} doesn't seem to have a schema.`); + return; + } + const schemaEncoding = topicSchemaMetadata.encoding; + + // Make an encoder using the official avro-js library. + const definition = fs + .readFileSync('system-test/fixtures/provinces.avsc') + .toString(); + const type = avro.parse(definition); + + // Encode the message. + const province: ProvinceObject = { + name: 'Ontario', + post_abbr: 'ON', + }; + let dataBuffer: Buffer | undefined; + switch (schemaEncoding) { + case Encodings.Binary: + dataBuffer = type.toBuffer(province); + break; + case Encodings.Json: + dataBuffer = Buffer.from(type.toString(province)); + break; + default: + console.log(`Unknown schema encoding: ${schemaEncoding}`); + break; + } + if (!dataBuffer) { + console.log(`Invalid encoding ${schemaEncoding} on the topic.`); + return; + } + + const messageId = await topic.publish(dataBuffer); + console.log(`Avro record ${messageId} published.`); +} +// [END pubsub_publish_avro_records] + +function main(topicNameOrId = 'YOUR_TOPIC_NAME_OR_ID') { + publishAvroRecords(topicNameOrId).catch(err => { + console.error(err.message); + process.exitCode = 1; + }); +} + +main(...process.argv.slice(2)); diff --git a/packages/typeless-sample-bot/test/fixtures/folder/validateSchema.ts b/packages/typeless-sample-bot/test/fixtures/folder/validateSchema.ts new file mode 100644 index 00000000000..fd9282e0b0a --- /dev/null +++ b/packages/typeless-sample-bot/test/fixtures/folder/validateSchema.ts @@ -0,0 +1,82 @@ +// Copyright 2019-2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * This sample demonstrates how to perform basic operations on topics with + * the Google Cloud Pub/Sub API. + * + * For more information, see the README.md under /pubsub and the documentation + * at https://cloud.google.com/pubsub/docs. + */ + +// sample-metadata: +// title: Validate a schema definition +// description: Validates an Avro-based schema definition before creation (or other use). +// usage: node validateSchema.js + +// (No tag, currently - this sample is non-canonical) +/** + * TODO(developer): Uncomment this variable before running the sample. + */ +// const schemaText = 'YOUR_SCHEMA_TEXT'; + +// Imports the Google Cloud client library +import {PubSub, SchemaTypes} from '@google-cloud/pubsub'; + +// Creates a client; cache this for further use +const pubSubClient = new PubSub(); + +async function validateSchema(schemaText: string) { + try { + await pubSubClient.validateSchema({ + type: SchemaTypes.Avro, + definition: schemaText, + }); + console.log('Validated with no error.'); + } catch (e) { + console.log('Received error:', e); + } +} +// (No tag, currently - this sample is non-canonical) + +// Just a sample AVSC definition to try. +const sampleAvsc = ` +{ + "type":"record", + "name":"Province", + "namespace":"utilities", + "doc":"A list of provinces in Canada.", + "fields":[ + { + "name":"name", + "type":"string", + "doc":"The common name of the province." + }, + { + "name":"post_abbr", + "type":"string", + "doc":"The postal code abbreviation of the province." + } + ] +} +`; + +function main(schemaText = sampleAvsc) { + validateSchema(schemaText).catch(err => { + console.error(err.message); + process.exitCode = 1; + }); +} + +main(...process.argv.slice(2)); diff --git a/packages/typeless-sample-bot/test/fixtures/getSchema.ts b/packages/typeless-sample-bot/test/fixtures/getSchema.ts new file mode 100644 index 00000000000..35d08892aef --- /dev/null +++ b/packages/typeless-sample-bot/test/fixtures/getSchema.ts @@ -0,0 +1,55 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * This application demonstrates how to perform basic operations on + * schemas with the Google Cloud Pub/Sub API. + * + * For more information, see the README.md under /pubsub and the documentation + * at https://cloud.google.com/pubsub/docs. + */ + +// sample-metadata: +// title: Get a previously created schema +// description: Gets information about a schema which was previously created in the project. +// usage: node getSchema.js + +// [START pubsub_get_schema] +/** + * TODO(developer): Uncomment this variable before running the sample. + */ +// const schemaNameOrId = 'YOUR_SCHEMA_NAME_OR_ID'; + +// Imports the Google Cloud client library +import {PubSub} from '@google-cloud/pubsub'; + +// Creates a client; cache this for further use +const pubSubClient = new PubSub(); + +async function getSchema(schemaNameOrId: string) { + const schema = pubSubClient.schema(schemaNameOrId); + const info = await schema.get(); + const fullName = await schema.getName(); + console.log(`Schema ${fullName} info: ${JSON.stringify(info, null, 4)}.`); +} +// [END pubsub_get_schema] + +function main(schemaNameOrId = 'YOUR_SCHEMA_NAME_OR_ID') { + getSchema(schemaNameOrId).catch(err => { + console.error(err.message); + process.exitCode = 1; + }); +} + +main(...process.argv.slice(2)); diff --git a/packages/typeless-sample-bot/test/fixtures/listSchemas.ts b/packages/typeless-sample-bot/test/fixtures/listSchemas.ts new file mode 100644 index 00000000000..6fa6bce9636 --- /dev/null +++ b/packages/typeless-sample-bot/test/fixtures/listSchemas.ts @@ -0,0 +1,51 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * This application demonstrates how to perform basic operations on + * schemas with the Google Cloud Pub/Sub API. + * + * For more information, see the README.md under /pubsub and the documentation + * at https://cloud.google.com/pubsub/docs. + */ + +// sample-metadata: +// title: List schemas on a project +// description: Gets a list of schemas which were previously created in the project. +// usage: node listSchemas.js + +// [START pubsub_list_schemas] + +// Imports the Google Cloud client library +import {PubSub} from '@google-cloud/pubsub'; + +// Creates a client; cache this for further use +const pubSubClient = new PubSub(); + +async function listSchemas() { + for await (const s of pubSubClient.listSchemas()) { + console.log(s.name); + } + console.log('Listed schemas.'); +} +// [END pubsub_list_schemas] + +function main() { + listSchemas().catch(err => { + console.error(err.message); + process.exitCode = 1; + }); +} + +main(); diff --git a/packages/typeless-sample-bot/test/index.ts b/packages/typeless-sample-bot/test/index.ts new file mode 100644 index 00000000000..8e4a2091af7 --- /dev/null +++ b/packages/typeless-sample-bot/test/index.ts @@ -0,0 +1,162 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as assert from 'assert'; +import {describe, it} from 'mocha'; +import snapshot from 'snap-shot-it'; +import * as samples from '../src/samples.js'; +import * as main from '../src/bin/samples-ts-to-js.js'; +import {readFile, rm, stat} from 'node:fs/promises'; +import * as url from 'node:url'; +import * as path from 'node:path'; +import * as sinon from 'sinon'; + +// ESM removes __dirname. +const dirName = url.fileURLToPath(new URL('.', import.meta.url)); + +// We're in 'build' here, need to back out. +const fixturePath = path.join(dirName, '..', '..', 'test', 'fixtures'); + +async function loadFixture(name: string): Promise { + const fn = path.join(fixturePath, name); + return (await readFile(fn)).toString(); +} + +describe('sample transformation', () => { + it('does not change JS', async () => { + const fixture = await loadFixture('createTopic.js'); + + const sIn = samples.fromArray([ + { + filename: 'test.js', + contents: fixture, + }, + ]); + const sOut = await samples.toArray(samples.transformSamples(sIn)); + snapshot(sOut); + }); + + it('correctly transforms TS', async () => { + const fixture = await loadFixture('listSchemas.ts'); + + const sIn = samples.fromArray([ + { + filename: 'listSchemas.ts', + contents: fixture, + }, + ]); + + const sOut = await samples.toArray(samples.transformSamples(sIn)); + + snapshot(sOut[0].contents); + }); +}); + +describe('command line option', () => { + const sandbox = sinon.createSandbox(); + + afterEach(() => { + sandbox.restore(); + }); + + it('"no-art" removes emojis and colours', async () => { + const cmdline = ['node', 'index.ts', '--no-art', '-t', 'foo.ts']; + sandbox.stub(console, 'error').callsFake((...params) => { + if (typeof params[0] === 'string') { + assert.strictEqual(params[0].includes('[error]'), true); + } + }); + + const retcode = await main.main(cmdline); + assert.strictEqual(retcode, 1); + }); + + it('"targets" works with a single sample file', async () => { + try { + const cmdline = [ + 'node', + 'index.ts', + '--targets', + path.join(fixturePath, 'listSchemas.ts'), + ]; + const retcode = await main.main(cmdline); + assert.strictEqual(retcode, 0); + const contents = await loadFixture('listSchemas.js'); + snapshot(contents); + } finally { + rm(path.join(fixturePath, 'listSchemas.js')).catch(() => {}); + } + }); + + it('"targets" works with multiple sample files', async () => { + try { + const cmdline = [ + 'node', + 'index.ts', + '--targets', + path.join(fixturePath, 'deleteSchema.ts'), + path.join(fixturePath, 'getSchema.ts'), + ]; + const retcode = await main.main(cmdline); + assert.strictEqual(retcode, 0); + const contents = [ + await loadFixture('deleteSchema.js'), + await loadFixture('getSchema.js'), + ]; + snapshot(contents); + } finally { + rm(path.join(fixturePath, 'deleteSchema.js')).catch(() => {}); + rm(path.join(fixturePath, 'getSchema.js')).catch(() => {}); + } + }); + + it('"recursive" causes recursion and only matches samples', async () => { + const targets = [ + path.join('folder', 'listenForAvroRecords.js'), + path.join('folder', 'publishAvroRecords.js'), + ]; + const antiTarget = path.join(fixturePath, 'folder', 'validateSchema.js'); + try { + const cmdline = [ + 'node', + 'index.ts', + '--recursive', + '--targets', + path.join(fixturePath, 'folder'), + ]; + const retcode = await main.main(cmdline); + assert.strictEqual(retcode, 0); + + const contents = await Promise.all(targets.map(loadFixture)); + snapshot(contents); + + await assert.rejects(stat(antiTarget)); + } finally { + targets.forEach(t => { + rm(path.join(fixturePath, t)).catch(() => {}); + }); + } + }); +}); + +/* + +More test ideas: + +- TS samples should come out with 'use require' and a "generated" comment + This is not in the generator yet. + +- Separate tests for debuglog? + +*/ diff --git a/packages/typeless-sample-bot/tsconfig.json b/packages/typeless-sample-bot/tsconfig.json new file mode 100644 index 00000000000..58c531b461e --- /dev/null +++ b/packages/typeless-sample-bot/tsconfig.json @@ -0,0 +1,111 @@ +{ + "extends": "./node_modules/gts/tsconfig-google.json", + "include": [ + "src/*.ts", + "src/**/*.ts", + "test/*.ts", + "tests/**/*.ts" + ], + "exclude": [ + "test/fixtures/*.ts" + ], + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "lib": ["es2020"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + + /* Modules */ + "module": "ES2022", /* Specify what module code is generated. */ + "rootDir": ".", /* Specify the root folder within your source files. */ + "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + "resolveJsonModule": true, /* Enable importing .json files */ + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + "outDir": "build", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +}