Skip to content

Commit 46a2dd1

Browse files
authored
fix: dont use gulp streams for simple copying (#2681)
* fix: dont use gulp streams for simple copying * chore: format
1 parent 882072d commit 46a2dd1

4 files changed

Lines changed: 155 additions & 55 deletions

File tree

package-lock.json

Lines changed: 10 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/copy-blockly.js

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,7 @@
11
#!/usr/bin/env node
22
const fs = require('fs').promises;
33
const path = require('path');
4-
5-
async function copyDir(src, dest) {
6-
await fs.mkdir(dest, {recursive: true});
7-
const entries = await fs.readdir(src, {withFileTypes: true});
8-
for (const entry of entries) {
9-
const srcPath = path.join(src, entry.name);
10-
const destPath = path.join(dest, entry.name);
11-
if (entry.isDirectory()) {
12-
await copyDir(srcPath, destPath);
13-
} else if (entry.isFile()) {
14-
await fs.copyFile(srcPath, destPath);
15-
}
16-
}
17-
}
4+
const {copyDirectoryContents} = require('./copy-helpers');
185

196
(async () => {
207
const destRoot = path.resolve(__dirname, '..', 'examples', 'lib');
@@ -54,7 +41,7 @@ async function copyDir(src, dest) {
5441

5542
// Copy the entire blockly package into destRoot (preserves README, dist, msg, media, etc.)
5643
try {
57-
await copyDir(blocklyDir, blocklyRoot);
44+
await copyDirectoryContents(blocklyDir, blocklyRoot);
5845
console.log('Blockly copied to', blocklyRoot);
5946
} catch (e) {
6047
console.error('Failed to copy Blockly package:', e);
@@ -88,7 +75,7 @@ async function copyDir(src, dest) {
8875
distExists = false;
8976
}
9077
if (distExists) {
91-
await copyDir(srcDist, path.join(dest, 'dist'));
78+
await copyDirectoryContents(srcDist, path.join(dest, 'dist'));
9279
copiedLocal.push(shortName);
9380
console.log(
9481
`Copied local @blockly/${shortName}/dist to ${path.join(

scripts/copy-helpers.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/**
8+
* @fileoverview Shared filesystem copy helpers (binary-safe, no gulp streams).
9+
*/
10+
11+
const fs = require('fs');
12+
const path = require('path');
13+
14+
/**
15+
* True if the path looks like a glob pattern (not a literal file path).
16+
* @param {string} filePath Absolute or normalized path.
17+
* @returns {boolean} Whether the path contains glob metacharacters.
18+
*/
19+
function hasGlobPattern(filePath) {
20+
return /[*?[\]]/.test(filePath);
21+
}
22+
23+
/**
24+
* Expand a glob pattern to matching paths. Prefer Node's `fs.globSync` (Node
25+
* 22+); older Node uses the `glob` package (pulled in by gulp).
26+
* @param {string} pattern Absolute path that may contain glob metacharacters.
27+
* @returns {!Array<string>} Matching paths from the filesystem.
28+
*/
29+
function expandGlob(pattern) {
30+
if (typeof fs.globSync === 'function') {
31+
return fs.globSync(pattern);
32+
}
33+
return require('glob').sync(pattern);
34+
}
35+
36+
/**
37+
* Expand `blocklyDemoConfig.files`-style entries to concrete paths.
38+
* @param {!Array<string>} sources Paths relative to cwd or absolute.
39+
* @returns {!Array<string>} Absolute paths to files (directories skipped).
40+
*/
41+
function expandSourcePaths(sources) {
42+
const out = [];
43+
for (const src of sources) {
44+
const resolved = path.resolve(src);
45+
if (!hasGlobPattern(resolved)) {
46+
out.push(resolved);
47+
continue;
48+
}
49+
const matches = expandGlob(resolved);
50+
for (const m of matches) {
51+
if (fs.existsSync(m) && fs.statSync(m).isFile()) {
52+
out.push(m);
53+
}
54+
}
55+
}
56+
return out;
57+
}
58+
59+
/**
60+
* Copy files under baseDir into destRoot, preserving directory structure
61+
* relative to baseDir. Skips missing sources.
62+
* Uses fs.copyFileSync so binary files are not corrupted.
63+
* Glob patterns in `sources` are expanded.
64+
* @param {!Array<string>} sources Source file paths (may include globs).
65+
* @param {string} baseDir Base directory (e.g. 'examples' or 'plugins').
66+
* @param {string} destRoot Destination root (e.g. 'gh-pages/examples').
67+
*/
68+
function copyFilesWithBase(sources, baseDir, destRoot) {
69+
const baseResolved = path.resolve(baseDir);
70+
const destRootResolved = path.resolve(destRoot);
71+
const sourceFiles = expandSourcePaths(sources);
72+
for (const srcPath of sourceFiles) {
73+
if (!fs.existsSync(srcPath)) {
74+
continue;
75+
}
76+
if (!fs.statSync(srcPath).isFile()) {
77+
continue;
78+
}
79+
const rel = path.relative(baseResolved, path.resolve(srcPath));
80+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
81+
throw new Error(
82+
`Source ${srcPath} is not under base directory ${baseDir}`,
83+
);
84+
}
85+
const destPath = path.join(destRootResolved, rel);
86+
fs.mkdirSync(path.dirname(destPath), {recursive: true});
87+
fs.copyFileSync(srcPath, destPath);
88+
}
89+
}
90+
91+
/**
92+
* Copy a directory tree into dest. If dest already exists as a directory,
93+
* the contents of src are merged/copied into it.
94+
* No-op if src does not exist. Binary-safe.
95+
* @param {string} src Source directory path.
96+
* @param {string} dest Destination directory path.
97+
* @returns {!Promise<void>} Resolves when the copy completes.
98+
*/
99+
async function copyDirectoryContents(src, dest) {
100+
try {
101+
await fs.promises.access(src);
102+
} catch {
103+
return;
104+
}
105+
await fs.promises.mkdir(dest, {recursive: true});
106+
await fs.promises.cp(src, dest, {recursive: true});
107+
}
108+
109+
module.exports = {
110+
copyFilesWithBase,
111+
copyDirectoryContents,
112+
};

scripts/gh-predeploy.js

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const path = require('path');
1515
const showdown = require('showdown');
1616
gulp.header = require('gulp-header');
1717

18+
const {copyFilesWithBase, copyDirectoryContents} = require('./copy-helpers');
19+
1820
const appDirectory = fs.realpathSync(process.cwd());
1921
const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath);
2022

@@ -288,18 +290,16 @@ function createReadmePage(pluginDir, isLocal) {
288290
* @param {string} pluginDir The directory with the plugin source files.
289291
* @param {boolean} isLocal True if building for a local test. False if
290292
* building for gh-pages.
291-
* @returns {*} gulp stream
292293
*/
293294
function preparePlugin(pluginDir, isLocal) {
294295
console.log(`Preparing ${pluginDir} plugin for deployment.`);
295296
createPluginPage(pluginDir, isLocal);
296297
createReadmePage(pluginDir, isLocal);
297-
return gulp
298-
.src(['./plugins/' + pluginDir + '/build/test_bundle.js'], {
299-
base: './plugins/',
300-
allowEmpty: true,
301-
})
302-
.pipe(gulp.dest('./gh-pages/plugins/'));
298+
copyFilesWithBase(
299+
[path.join('plugins', pluginDir, 'build', 'test_bundle.js')],
300+
'plugins',
301+
path.join('gh-pages', 'plugins'),
302+
);
303303
}
304304

305305
/**
@@ -328,8 +328,9 @@ function prepareToDeployPlugins(done) {
328328
const folders = getPluginFolders();
329329
return gulp.parallel(
330330
folders.map(function (folder) {
331-
return function preDeployPlugin() {
332-
return preparePlugin(folder, false);
331+
return function preDeployPlugin(done) {
332+
preparePlugin(folder, false);
333+
done();
333334
};
334335
}),
335336
)(done);
@@ -344,8 +345,9 @@ function prepareLocalPlugins(done) {
344345
const folders = getPluginFolders();
345346
return gulp.parallel(
346347
folders.map(function (folder) {
347-
return function preDeployPlugin() {
348-
return preparePlugin(folder, true);
348+
return function preDeployPlugin(done) {
349+
preparePlugin(folder, true);
350+
done();
349351
};
350352
}),
351353
)(done);
@@ -445,7 +447,6 @@ function createExamplePage(pageRoot, pagePath, demoConfig, isLocal) {
445447
* @param {boolean} isLocal True if building for a local test. False if
446448
* building for gh-pages.
447449
* @param {Function} done Completed callback.
448-
* @returns {Function | undefined} Gulp task.
449450
*/
450451
function prepareExample(exampleDir, isLocal, done) {
451452
const baseDir = 'examples';
@@ -468,12 +469,13 @@ function prepareExample(exampleDir, isLocal, done) {
468469

469470
// Special case: do a straight copy for the devsite demo, with no wrappers.
470471
if (packageJson.name == 'blockly-devsite-demo') {
471-
return gulp
472-
.src(
473-
fileList.map((f) => path.join(baseDir, exampleDir, f)),
474-
{base: baseDir, allowEmpty: true},
475-
)
476-
.pipe(gulp.dest('./gh-pages/examples/'));
472+
copyFilesWithBase(
473+
fileList.map((f) => path.join(baseDir, exampleDir, f)),
474+
baseDir,
475+
path.join('gh-pages', 'examples'),
476+
);
477+
done();
478+
return;
477479
}
478480

479481
// All other examples.
@@ -487,14 +489,14 @@ function prepareExample(exampleDir, isLocal, done) {
487489
// Copy over all other files mentioned in the demoConfig to the
488490
// correct directory.
489491
const assets = fileList.filter((f) => !pageRegex.test(f));
490-
let stream;
491492
if (assets.length) {
492-
stream = gulp.src(
493+
copyFilesWithBase(
493494
assets.map((f) => path.join(baseDir, exampleDir, f)),
494-
{base: baseDir, allowEmpty: true},
495+
baseDir,
496+
path.join('gh-pages', 'examples'),
495497
);
496498
}
497-
return stream.pipe(gulp.dest('./gh-pages/examples/'));
499+
done();
498500
}
499501

500502
/**
@@ -517,20 +519,17 @@ function getExampleFolders() {
517519
*
518520
* This is treated separately from other examples because it doesn't
519521
* get the same page chrome added to it.
520-
* @returns {Function | undefined} Gulp task.
521522
*/
522-
function prepareDeveloperTools() {
523+
async function prepareDeveloperTools() {
523524
const baseDir = 'examples';
524525
const devToolsDir = 'developer-tools';
525526
console.log(`Preparing developer-tools for deployment.`);
526527

527-
// Create target folder, if it doesn't exist.
528-
fs.mkdirSync(path.join('gh-pages', baseDir, devToolsDir), {recursive: true});
529-
530-
// Copy all files from `dist/` subdirectory into the corresponding gh-pages directory
531-
return gulp
532-
.src('./examples/developer-tools/dist/*')
533-
.pipe(gulp.dest('./gh-pages/examples/developer-tools'));
528+
// Copy all files from `dist/` into the gh-pages developer-tools directory
529+
await copyDirectoryContents(
530+
path.join(baseDir, devToolsDir, 'dist'),
531+
path.join('gh-pages', baseDir, devToolsDir),
532+
);
534533
}
535534

536535
/**

0 commit comments

Comments
 (0)