Skip to content

Commit

Permalink
fix: remove dependency on copy-template-dir (#6659)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lunnatica committed May 24, 2024
1 parent 85a74da commit a158331
Show file tree
Hide file tree
Showing 15 changed files with 190 additions and 2,167 deletions.
2,163 changes: 3 additions & 2,160 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@
"configstore": "6.0.0",
"content-type": "1.0.5",
"cookie": "0.6.0",
"copy-template-dir": "1.4.0",
"cron-parser": "4.9.0",
"debug": "4.3.4",
"decache": "4.6.2",
Expand Down Expand Up @@ -144,6 +143,8 @@
"lodash": "4.17.21",
"log-symbols": "6.0.0",
"log-update": "6.0.0",
"maxstache": "^1.0.7",
"maxstache-stream": "^1.0.4",
"multiparty": "4.2.3",
"netlify": "13.1.16",
"netlify-headers-parser": "7.1.4",
Expand All @@ -163,6 +164,7 @@
"pump": "3.0.0",
"raw-body": "2.5.2",
"read-package-up": "11.0.0",
"readdirp": "^3.6.0",
"semver": "7.6.2",
"source-map-support": "0.5.21",
"strip-ansi-control-characters": "2.0.0",
Expand Down
7 changes: 1 addition & 6 deletions src/commands/functions/functions-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@ import { createRequire } from 'module'
import path, { dirname, join, relative } from 'path'
import process from 'process'
import { fileURLToPath, pathToFileURL } from 'url'
import { promisify } from 'util'

import { OptionValues } from 'commander'
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'copy... Remove this comment to see the full error message
import copyTemplateDirOriginal from 'copy-template-dir'
import { findUp } from 'find-up'
import fuzzy from 'fuzzy'
import inquirer from 'inquirer'
Expand All @@ -19,13 +16,12 @@ import ora from 'ora'
import { fileExistsAsync } from '../../lib/fs.js'
import { getAddons, getCurrentAddon, getSiteData } from '../../utils/addons/prepare.js'
import { NETLIFYDEVERR, NETLIFYDEVLOG, NETLIFYDEVWARN, chalk, error, log } from '../../utils/command-helpers.js'
import { copyTemplateDir } from '../../utils/copy-template-dir/copy-template-dir.js'
import { getDotEnvVariables, injectEnvVariables } from '../../utils/dev.js'
import execa from '../../utils/execa.js'
import { readRepoURL, validateRepoURL } from '../../utils/read-repo-url.js'
import BaseCommand from '../base-command.js'

const copyTemplateDir = promisify(copyTemplateDirOriginal)

const require = createRequire(import.meta.url)

const templatesDir = path.resolve(dirname(fileURLToPath(import.meta.url)), '../../../functions-templates')
Expand Down Expand Up @@ -523,7 +519,6 @@ const scaffoldFromTemplate = async function (command, options, argumentName, fun
// be removed before the command finishes.
const omittedFromOutput = new Set(['.netlify-function-template.mjs', 'package.json', 'package-lock.json'])
const createdFiles = await copyTemplateDir(pathToTemplate, functionPath, vars)
// @ts-expect-error TS(7006) FIXME: Parameter 'filePath' implicitly has an 'any' type.
createdFiles.forEach((filePath) => {
const filename = path.basename(filePath)

Expand Down
92 changes: 92 additions & 0 deletions src/utils/copy-template-dir/copy-template-dir.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// License for copy-template-dir.
// Original repository: https://github.com/yoshuawuyts/copy-template-dir

// The MIT License (MIT)

// Copyright (c) 2015 Yoshua Wuyts

// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
// Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import assert from 'assert'
import fs from 'fs'
import path from 'path'
import { pipeline } from 'stream'
import { promisify } from 'util'

// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'maxstache... Remove this comment to see the full error message
import maxstache from 'maxstache'
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'maxstache-stream... Remove this comment to see the full error message
import maxstacheStream from 'maxstache-stream'
import readdirp, { EntryInfo, ReaddirpStream } from 'readdirp'

const noop = (): void => undefined

// Remove a leading underscore
function removeUnderscore(filepath: string): string {
const parts = filepath.split(path.sep)
const filename = parts.pop()?.replace(/^_/, '') || ''
return [...parts, filename].join(path.sep)
}

// Write a file to a directory
async function writeFile(outDir: string, vars: Record<string, string>, file: EntryInfo): Promise<void> {
const fileName = file.path
const inFile = file.fullPath
const parentDir = path.dirname(file.path)
const outFile = path.join(outDir, maxstache(removeUnderscore(fileName), vars))

await fs.promises.mkdir(path.join(outDir, maxstache(parentDir, vars)), { recursive: true })

const rs = fs.createReadStream(inFile)
const ts = maxstacheStream(vars)
const ws = fs.createWriteStream(outFile)

await promisify(pipeline)(rs, ts, ws)
}

// High throughput template dir writes
export async function copyTemplateDir(srcDir: string, outDir: string, vars: any): Promise<string[]> {
if (!vars) vars = noop

assert.strictEqual(typeof srcDir, 'string')
assert.strictEqual(typeof outDir, 'string')
assert.strictEqual(typeof vars, 'object')

await fs.promises.mkdir(outDir, { recursive: true })

const rs: ReaddirpStream = readdirp(srcDir)
const streams: Promise<void>[] = []
const createdFiles: string[] = []

rs.on('data', (file: EntryInfo) => {
createdFiles.push(path.join(outDir, maxstache(removeUnderscore(file.path), vars)))
streams.push(writeFile(outDir, vars, file))
})

await new Promise<void>((resolve, reject) => {
rs.on('end', async () => {
try {
await Promise.all(streams)
resolve()
} catch (error) {
reject(error)
}
})
rs.on('error', (error) => {
reject(error)
})
})

return createdFiles
}
90 changes: 90 additions & 0 deletions tests/unit/utils/copy-template-dir/copy-template-dir.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'

import readdirp from 'readdirp'
import { describe, expect, test } from 'vitest'

import { copyTemplateDir } from '../../../../dist/utils/copy-template-dir/copy-template-dir.js'

// eslint-disable-next-line no-underscore-dangle
const __filename = fileURLToPath(import.meta.url)
// eslint-disable-next-line no-underscore-dangle
const __dirname = path.dirname(__filename)

describe('copyTemplateDir', () => {
test('should assert input values', async () => {
await expect(copyTemplateDir()).rejects.toThrow(/string/)
await expect(copyTemplateDir('foo')).rejects.toThrow(/string/)
await expect(copyTemplateDir('foo', 'bar', 'err')).rejects.toThrow(/object/)
})

test('should write a bunch of files', async () => {
const checkCreatedFileNames = (names) => {
expect(names).toContain('.a')
expect(names).toContain('c')
expect(names).toContain('1.txt')
expect(names).toContain('2.txt')
expect(names).toContain('3.txt')
expect(names).toContain('.txt')
expect(names).toContain(`foo${path.sep}.b`)
expect(names).toContain(`foo${path.sep}d`)
expect(names).toContain(`foo${path.sep}4.txt`)
}

const inDir = path.join(__dirname, 'fixtures')
const outDir = path.join(__dirname, '../tmp')

const createdFiles = await copyTemplateDir(inDir, outDir, {})

expect(Array.isArray(createdFiles)).toBe(true)
expect(createdFiles.length).toBe(10)

// Checks the direct output of the function, to ensure names are correct
checkCreatedFileNames(createdFiles.map((filePath) => path.relative(outDir, filePath)))

// Checks that the files were created in the file system
const files = await readdirp.promise(outDir)
checkCreatedFileNames(files.map((file) => file.path))

// Cleanup
fs.rmdirSync(outDir, { recursive: true })
})

test('should inject context variables strings', async () => {
const inDir = path.join(__dirname, 'fixtures')
const outDir = path.join(__dirname, '../tmp')

await copyTemplateDir(inDir, outDir, { foo: 'bar' })

const fileContent = fs.readFileSync(path.join(outDir, '1.txt'), 'utf-8').trim()
expect(fileContent).toBe('hello bar sama')

// Cleanup
fs.rmdirSync(outDir, { recursive: true })
})

test('should inject context variables strings into filenames', async () => {
const inDir = path.join(__dirname, 'fixtures')
const outDir = path.join(__dirname, '../tmp')

await copyTemplateDir(inDir, outDir, { foo: 'bar' })

expect(fs.existsSync(path.join(outDir, 'bar.txt'))).toBe(true)

// Cleanup
fs.rmdirSync(outDir, { recursive: true })
})

test('should inject context variables strings into directory names', async () => {
const inDir = path.join(__dirname, 'fixtures')
const outDir = path.join(__dirname, '../tmp')

await copyTemplateDir(inDir, outDir, { foo: 'bar' })

expect(fs.existsSync(path.join(outDir, 'bar'))).toBe(true)

// Cleanup
fs.rmdirSync(outDir, { recursive: true })
})
})
1 change: 1 addition & 0 deletions tests/unit/utils/copy-template-dir/fixtures/1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello {{foo}} sama
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.

2 comments on commit a158331

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📊 Benchmark results

  • Dependency count: 1,231
  • Package size: 295 MB
  • Number of ts-expect-error directives: 997

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📊 Benchmark results

  • Dependency count: 1,231
  • Package size: 295 MB
  • Number of ts-expect-error directives: 997

Please sign in to comment.