From 233bb579988d50da6d15ab32ab66eaece8a409b4 Mon Sep 17 00:00:00 2001 From: Kunal Kundu Date: Thu, 9 Feb 2023 23:11:34 +0530 Subject: [PATCH] feat: support comments in vscode settings (#5436) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: support comments in vscode settings * refactor: destructure imports * test: add test to check if JSON with comments is handled * fix: remove unnecessary log statement * test: remove .only modifier Co-authored-by: Eduardo Bouças * refactor: use utf8 encoding in readFile instead of using toString on contents * refactor: import comment-json using a namespace --------- Co-authored-by: Eduardo Bouças Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- npm-shrinkwrap.json | 63 +++++++++++++++++++ package.json | 1 + src/recipes/vscode/settings.mjs | 13 ++-- .../integration/640.command.recipes.test.cjs | 45 ++++++++++++- tests/integration/utils/handle-questions.cjs | 2 + 5 files changed, 117 insertions(+), 7 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 8172e755f36..44f3ef2ff0b 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -30,6 +30,7 @@ "ci-info": "^3.0.0", "clean-deep": "^3.0.2", "commander": "^9.2.0", + "comment-json": "^4.2.3", "concordance": "^5.0.0", "configstore": "^5.0.0", "content-type": "^1.0.4", @@ -6558,6 +6559,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==" + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -8393,6 +8399,26 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/comment-json": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.3.tgz", + "integrity": "sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==", + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/comment-json/node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/common-path-prefix": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", @@ -13443,6 +13469,14 @@ "node": ">=0.10.0" } }, + "node_modules/has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/has-symbol-support-x": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", @@ -27964,6 +27998,11 @@ "is-string": "^1.0.7" } }, + "array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==" + }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -29333,6 +29372,25 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==" }, + "comment-json": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.3.tgz", + "integrity": "sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==", + "requires": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + } + } + }, "common-path-prefix": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", @@ -33249,6 +33307,11 @@ } } }, + "has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==" + }, "has-symbol-support-x": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", diff --git a/package.json b/package.json index bb9aca1a404..bf874860593 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "ci-info": "^3.0.0", "clean-deep": "^3.0.2", "commander": "^9.2.0", + "comment-json": "^4.2.3", "concordance": "^5.0.0", "configstore": "^5.0.0", "content-type": "^1.0.4", diff --git a/src/recipes/vscode/settings.mjs b/src/recipes/vscode/settings.mjs index 509e4a3759a..90bd3a74fcd 100644 --- a/src/recipes/vscode/settings.mjs +++ b/src/recipes/vscode/settings.mjs @@ -1,17 +1,18 @@ import { mkdir, readFile, stat, writeFile } from 'fs/promises' import { dirname, relative } from 'path' +// eslint-disable-next-line import/no-namespace +import * as JSONC from 'comment-json' import unixify from 'unixify' export const applySettings = (existingSettings, { denoBinary, edgeFunctionsPath, repositoryRoot }) => { const relativeEdgeFunctionsPath = unixify(relative(repositoryRoot, edgeFunctionsPath)) - const settings = { - ...existingSettings, + const settings = JSONC.assign(existingSettings, { 'deno.enable': true, 'deno.enablePaths': existingSettings['deno.enablePaths'] || [], 'deno.unstable': true, 'deno.importMap': '.netlify/edge-functions-import-map.json', - } + }) // If the Edge Functions path isn't already in `deno.enabledPaths`, let's add // it. @@ -38,11 +39,11 @@ export const getSettings = async (settingsPath) => { throw new Error(`${settingsPath} is not a valid file.`) } - const file = await readFile(settingsPath) + const file = await readFile(settingsPath, 'utf8') return { fileExists: true, - settings: JSON.parse(file), + settings: JSONC.parse(file), } } catch (error) { if (error.code !== 'ENOENT') { @@ -57,7 +58,7 @@ export const getSettings = async (settingsPath) => { } export const writeSettings = async ({ settings, settingsPath }) => { - const serializedSettings = JSON.stringify(settings, null, 2) + const serializedSettings = JSONC.stringify(settings, null, 2) await mkdir(dirname(settingsPath), { recursive: true }) await writeFile(settingsPath, serializedSettings) diff --git a/tests/integration/640.command.recipes.test.cjs b/tests/integration/640.command.recipes.test.cjs index 668de5decf6..729453e7e5b 100644 --- a/tests/integration/640.command.recipes.test.cjs +++ b/tests/integration/640.command.recipes.test.cjs @@ -2,11 +2,12 @@ const { readFile } = require('fs/promises') const { resolve } = require('path') const test = require('ava') +const { parse } = require('comment-json') const execa = require('execa') const callCli = require('./utils/call-cli.cjs') const cliPath = require('./utils/cli-path.cjs') -const { CONFIRM, answerWithValue, handleQuestions } = require('./utils/handle-questions.cjs') +const { CONFIRM, NO, answerWithValue, handleQuestions } = require('./utils/handle-questions.cjs') const { withSiteBuilder } = require('./utils/site-builder.cjs') const { normalize } = require('./utils/snapshots.cjs') @@ -96,3 +97,45 @@ test('Does not generate a new VS Code settings file if the user does not confirm t.is(error.code, 'ENOENT') }) }) + +test('Handles JSON with comments', async (t) => { + await withSiteBuilder('repo', async (builder) => { + const comment = '// sets value for someSetting' + await builder + .withContentFile({ + path: '.vscode/settings.json', + content: `{ + "deno.enablePaths":["/some/path"], + "someSetting":"value" ${comment} + }`, + }) + .buildAsync() + + const childProcess = execa(cliPath, ['recipes', 'vscode'], { + cwd: builder.directory, + }) + const settingsPath = resolve(builder.directory, '.vscode', 'settings.json') + + handleQuestions(childProcess, [ + { + question: `There is a VS Code settings file at ${settingsPath}. Can we update it?`, + answer: answerWithValue(CONFIRM), + }, + { + question: 'The Deno VS Code extension is recommended. Would you like to install it now?', + answer: answerWithValue(NO), + }, + ]) + + await childProcess + + const settingsText = await readFile(`${builder.directory}/.vscode/settings.json`, { encoding: 'utf8' }) + t.true(settingsText.includes(comment)) + + const settings = parse(settingsText, null, true) + t.is(settings.someSetting, 'value') + t.is(settings['deno.enable'], true) + t.is(settings['deno.importMap'], '.netlify/edge-functions-import-map.json') + t.deepEqual([...settings['deno.enablePaths']], ['/some/path', 'netlify/edge-functions']) + }) +}) diff --git a/tests/integration/utils/handle-questions.cjs b/tests/integration/utils/handle-questions.cjs index 51a68aad466..7ce20747ae5 100644 --- a/tests/integration/utils/handle-questions.cjs +++ b/tests/integration/utils/handle-questions.cjs @@ -27,10 +27,12 @@ const answerWithValue = (value) => `${value}${CONFIRM}` const CONFIRM = '\n' const DOWN = '\u001B[B' +const NO = 'n' module.exports = { handleQuestions, answerWithValue, CONFIRM, DOWN, + NO, }