Skip to content

Commit a158331

Browse files
authoredMay 24, 2024··
fix: remove dependency on copy-template-dir (#6659)
1 parent 85a74da commit a158331

File tree

15 files changed

+190
-2167
lines changed

15 files changed

+190
-2167
lines changed
 

‎package-lock.json

+3-2,160
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@
9999
"configstore": "6.0.0",
100100
"content-type": "1.0.5",
101101
"cookie": "0.6.0",
102-
"copy-template-dir": "1.4.0",
103102
"cron-parser": "4.9.0",
104103
"debug": "4.3.4",
105104
"decache": "4.6.2",
@@ -144,6 +143,8 @@
144143
"lodash": "4.17.21",
145144
"log-symbols": "6.0.0",
146145
"log-update": "6.0.0",
146+
"maxstache": "^1.0.7",
147+
"maxstache-stream": "^1.0.4",
147148
"multiparty": "4.2.3",
148149
"netlify": "13.1.16",
149150
"netlify-headers-parser": "7.1.4",
@@ -163,6 +164,7 @@
163164
"pump": "3.0.0",
164165
"raw-body": "2.5.2",
165166
"read-package-up": "11.0.0",
167+
"readdirp": "^3.6.0",
166168
"semver": "7.6.2",
167169
"source-map-support": "0.5.21",
168170
"strip-ansi-control-characters": "2.0.0",

‎src/commands/functions/functions-create.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@ import { createRequire } from 'module'
55
import path, { dirname, join, relative } from 'path'
66
import process from 'process'
77
import { fileURLToPath, pathToFileURL } from 'url'
8-
import { promisify } from 'util'
98

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

27-
const copyTemplateDir = promisify(copyTemplateDirOriginal)
28-
2925
const require = createRequire(import.meta.url)
3026

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

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// License for copy-template-dir.
2+
// Original repository: https://github.com/yoshuawuyts/copy-template-dir
3+
4+
// The MIT License (MIT)
5+
6+
// Copyright (c) 2015 Yoshua Wuyts
7+
8+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
9+
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
10+
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11+
// persons to whom the Software is furnished to do so, subject to the following conditions:
12+
13+
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
14+
// Software.
15+
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
17+
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18+
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
21+
import assert from 'assert'
22+
import fs from 'fs'
23+
import path from 'path'
24+
import { pipeline } from 'stream'
25+
import { promisify } from 'util'
26+
27+
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'maxstache... Remove this comment to see the full error message
28+
import maxstache from 'maxstache'
29+
// @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
30+
import maxstacheStream from 'maxstache-stream'
31+
import readdirp, { EntryInfo, ReaddirpStream } from 'readdirp'
32+
33+
const noop = (): void => undefined
34+
35+
// Remove a leading underscore
36+
function removeUnderscore(filepath: string): string {
37+
const parts = filepath.split(path.sep)
38+
const filename = parts.pop()?.replace(/^_/, '') || ''
39+
return [...parts, filename].join(path.sep)
40+
}
41+
42+
// Write a file to a directory
43+
async function writeFile(outDir: string, vars: Record<string, string>, file: EntryInfo): Promise<void> {
44+
const fileName = file.path
45+
const inFile = file.fullPath
46+
const parentDir = path.dirname(file.path)
47+
const outFile = path.join(outDir, maxstache(removeUnderscore(fileName), vars))
48+
49+
await fs.promises.mkdir(path.join(outDir, maxstache(parentDir, vars)), { recursive: true })
50+
51+
const rs = fs.createReadStream(inFile)
52+
const ts = maxstacheStream(vars)
53+
const ws = fs.createWriteStream(outFile)
54+
55+
await promisify(pipeline)(rs, ts, ws)
56+
}
57+
58+
// High throughput template dir writes
59+
export async function copyTemplateDir(srcDir: string, outDir: string, vars: any): Promise<string[]> {
60+
if (!vars) vars = noop
61+
62+
assert.strictEqual(typeof srcDir, 'string')
63+
assert.strictEqual(typeof outDir, 'string')
64+
assert.strictEqual(typeof vars, 'object')
65+
66+
await fs.promises.mkdir(outDir, { recursive: true })
67+
68+
const rs: ReaddirpStream = readdirp(srcDir)
69+
const streams: Promise<void>[] = []
70+
const createdFiles: string[] = []
71+
72+
rs.on('data', (file: EntryInfo) => {
73+
createdFiles.push(path.join(outDir, maxstache(removeUnderscore(file.path), vars)))
74+
streams.push(writeFile(outDir, vars, file))
75+
})
76+
77+
await new Promise<void>((resolve, reject) => {
78+
rs.on('end', async () => {
79+
try {
80+
await Promise.all(streams)
81+
resolve()
82+
} catch (error) {
83+
reject(error)
84+
}
85+
})
86+
rs.on('error', (error) => {
87+
reject(error)
88+
})
89+
})
90+
91+
return createdFiles
92+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import fs from 'fs'
2+
import path from 'path'
3+
import { fileURLToPath } from 'url'
4+
5+
import readdirp from 'readdirp'
6+
import { describe, expect, test } from 'vitest'
7+
8+
import { copyTemplateDir } from '../../../../dist/utils/copy-template-dir/copy-template-dir.js'
9+
10+
// eslint-disable-next-line no-underscore-dangle
11+
const __filename = fileURLToPath(import.meta.url)
12+
// eslint-disable-next-line no-underscore-dangle
13+
const __dirname = path.dirname(__filename)
14+
15+
describe('copyTemplateDir', () => {
16+
test('should assert input values', async () => {
17+
await expect(copyTemplateDir()).rejects.toThrow(/string/)
18+
await expect(copyTemplateDir('foo')).rejects.toThrow(/string/)
19+
await expect(copyTemplateDir('foo', 'bar', 'err')).rejects.toThrow(/object/)
20+
})
21+
22+
test('should write a bunch of files', async () => {
23+
const checkCreatedFileNames = (names) => {
24+
expect(names).toContain('.a')
25+
expect(names).toContain('c')
26+
expect(names).toContain('1.txt')
27+
expect(names).toContain('2.txt')
28+
expect(names).toContain('3.txt')
29+
expect(names).toContain('.txt')
30+
expect(names).toContain(`foo${path.sep}.b`)
31+
expect(names).toContain(`foo${path.sep}d`)
32+
expect(names).toContain(`foo${path.sep}4.txt`)
33+
}
34+
35+
const inDir = path.join(__dirname, 'fixtures')
36+
const outDir = path.join(__dirname, '../tmp')
37+
38+
const createdFiles = await copyTemplateDir(inDir, outDir, {})
39+
40+
expect(Array.isArray(createdFiles)).toBe(true)
41+
expect(createdFiles.length).toBe(10)
42+
43+
// Checks the direct output of the function, to ensure names are correct
44+
checkCreatedFileNames(createdFiles.map((filePath) => path.relative(outDir, filePath)))
45+
46+
// Checks that the files were created in the file system
47+
const files = await readdirp.promise(outDir)
48+
checkCreatedFileNames(files.map((file) => file.path))
49+
50+
// Cleanup
51+
fs.rmdirSync(outDir, { recursive: true })
52+
})
53+
54+
test('should inject context variables strings', async () => {
55+
const inDir = path.join(__dirname, 'fixtures')
56+
const outDir = path.join(__dirname, '../tmp')
57+
58+
await copyTemplateDir(inDir, outDir, { foo: 'bar' })
59+
60+
const fileContent = fs.readFileSync(path.join(outDir, '1.txt'), 'utf-8').trim()
61+
expect(fileContent).toBe('hello bar sama')
62+
63+
// Cleanup
64+
fs.rmdirSync(outDir, { recursive: true })
65+
})
66+
67+
test('should inject context variables strings into filenames', async () => {
68+
const inDir = path.join(__dirname, 'fixtures')
69+
const outDir = path.join(__dirname, '../tmp')
70+
71+
await copyTemplateDir(inDir, outDir, { foo: 'bar' })
72+
73+
expect(fs.existsSync(path.join(outDir, 'bar.txt'))).toBe(true)
74+
75+
// Cleanup
76+
fs.rmdirSync(outDir, { recursive: true })
77+
})
78+
79+
test('should inject context variables strings into directory names', async () => {
80+
const inDir = path.join(__dirname, 'fixtures')
81+
const outDir = path.join(__dirname, '../tmp')
82+
83+
await copyTemplateDir(inDir, outDir, { foo: 'bar' })
84+
85+
expect(fs.existsSync(path.join(outDir, 'bar'))).toBe(true)
86+
87+
// Cleanup
88+
fs.rmdirSync(outDir, { recursive: true })
89+
})
90+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hello {{foo}} sama

‎tests/unit/utils/copy-template-dir/fixtures/2.txt

Whitespace-only changes.

‎tests/unit/utils/copy-template-dir/fixtures/3.txt

Whitespace-only changes.

‎tests/unit/utils/copy-template-dir/fixtures/_.a

Whitespace-only changes.

‎tests/unit/utils/copy-template-dir/fixtures/_c

Whitespace-only changes.

‎tests/unit/utils/copy-template-dir/fixtures/foo/4.txt

Whitespace-only changes.

‎tests/unit/utils/copy-template-dir/fixtures/foo/_.b

Whitespace-only changes.

‎tests/unit/utils/copy-template-dir/fixtures/foo/_d

Whitespace-only changes.

‎tests/unit/utils/copy-template-dir/fixtures/{{foo}}.txt

Whitespace-only changes.

‎tests/unit/utils/copy-template-dir/fixtures/{{foo}}/5.txt

Whitespace-only changes.

2 commit comments

Comments
 (2)

github-actions[bot] commented on May 24, 2024

@github-actions[bot]

📊 Benchmark results

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

github-actions[bot] commented on May 24, 2024

@github-actions[bot]

📊 Benchmark results

  • Dependency count: 1,231
  • Package size: 295 MB
  • Number of ts-expect-error directives: 997
Please sign in to comment.