Skip to content

Commit bee12f3

Browse files
boutellmyovchev
andauthored
PRO-6669: Fix native windows (#5155)
* windows paths * fix windows * print stuff * log more stuff * more logging * path-normalizing glob functionality * run quiet * convert more slashes * You can't reliably just pass filenames to async import because C:/ is assumed to be a URL. Use pathToFileUrl * case * SASS eats URLs. Not file paths. C:/ messes that up. * doofus * already quoted * be more judicious * wtf * handle empty nodes properly * unreleased * please eslint * version * need a better fake module.import to go with our new calls to it * accept url objects gracefully * clean never-before-used db id * more correct emulation of import, syntax cleanup * oops * eslint * try a newer symlink type that might be less of a permissions problem on Windows * canonical convert * consistent convert * run quiet * normalize on LF, not CR/LF, in Windows nunjucks output * uploadfs workaround * fix attachments options for test purposes * fix watch rebuild (#5159) * detect the test path under windows (#5161) * eslint --------- Co-authored-by: Miro Yovchev <2827783+myovchev@users.noreply.github.com>
1 parent b7799f6 commit bee12f3

16 files changed

Lines changed: 121 additions & 38 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
### Fixes
2222

2323
* Specify the content type when calling back to Astro with JSON to render an area. This is required starting in Astro 4.9.0 and up, otherwise the request is blocked by CSRF protection.
24+
* Improved support for Node.js on "plain vanilla" Windows, e.g. without WSL. We suggest working with NVM for Windows and Git Bash.
2425
* Fixes `AposBreadcrumbSwitch` tooltip prop that is supposed to be an object, not a string. Object returned from the shared method `getOperationTooltip`.
26+
* Empty text nodes are output properly without a warning.
2527
* Uses `modalData.locale` in `AposI18nLocalize` component. Fixes watcher on `relatedDocTypes` not being properly triggered (uses data and methods for more control instead).
2628
* Layout area fix when no columns are present.
2729

‎index.js‎

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ const process = require('process');
1212
const npmResolve = require('resolve');
1313
const glob = require('./lib/glob.js');
1414
const moogRequire = require('./lib/moog-require');
15+
const importFresh = require('./lib/import-fresh');
16+
const { pathToFileURL } = require('node:url');
1517
let defaults = require('./defaults.js');
1618

17-
const importFresh = moduleName => import(`${moduleName}?${Date.now()}`);
18-
1919
// ## Top-level options
2020
//
2121
// `cluster`
@@ -363,7 +363,7 @@ async function apostrophe(options, telemetry, rootSpan) {
363363
const reallyLocalPath = self.rootDir + localPath;
364364

365365
if (fs.existsSync(reallyLocalPath)) {
366-
local = await self.root.import(reallyLocalPath);
366+
local = await self.root.import(pathToFileURL(reallyLocalPath));
367367
}
368368

369369
// Otherwise making a second apos instance
@@ -395,7 +395,7 @@ async function apostrophe(options, telemetry, rootSpan) {
395395
const configs = glob(self.localModules + '/**/modules.js', { follow: true });
396396
for (const config of configs) {
397397
try {
398-
_.merge(self.options.modules, await self.root.import(config));
398+
_.merge(self.options.modules, await self.root.import(pathToFileURL(config)));
399399
} catch (e) {
400400
console.error(stripIndent`
401401
When nestedModuleSubdirs is active, any modules.js file beneath:
@@ -513,7 +513,7 @@ async function apostrophe(options, telemetry, rootSpan) {
513513
checkTestModule();
514514
// Allow tests to be in test/ or in tests/
515515
const testDir = path.dirname(m.filename);
516-
const moduleDir = testDir.replace(/\/tests?$/, '');
516+
const moduleDir = testDir.replace(/\/tests?$/, '').replace(/\\tests?$/, '');
517517
if (testDir === moduleDir) {
518518
throw new Error('Test file must be in test/ or tests/ subdirectory of module');
519519
}
@@ -527,7 +527,7 @@ async function apostrophe(options, telemetry, rootSpan) {
527527

528528
if (!fs.existsSync(testDir + '/node_modules')) {
529529
fs.mkdirSync(testDir + '/node_modules' + pkgNamespace, { recursive: true });
530-
fs.symlinkSync(moduleDir, testDir + '/node_modules/' + pkgName, 'dir');
530+
fs.symlinkSync(moduleDir, testDir + '/node_modules/' + pkgName, 'junction');
531531
}
532532
// Makes sure we encounter mocha along the way
533533
// and throws an exception if we don't
@@ -721,7 +721,7 @@ async function apostrophe(options, telemetry, rootSpan) {
721721
if (fs.existsSync(path.resolve(self.localModules, name, 'modules.js'))) {
722722
return;
723723
}
724-
const submodule = await self.root.import(path.resolve(self.localModules, name, 'index.js'));
724+
const submodule = await self.root.import(pathToFileURL(path.resolve(self.localModules, name, 'index.js')));
725725
if (
726726
submodule &&
727727
submodule.options &&
@@ -901,8 +901,19 @@ function getRoot(options) {
901901
if (root?.filename && root?.require) {
902902
return {
903903
filename: root.filename,
904-
import: async (id) => root.require(id),
905-
require: (id) => root.require(id)
904+
async import(id) {
905+
// Must accept URL objects
906+
id = id.toString();
907+
// To accurately simulate ES import, we need to
908+
// accept file:// URLs like it can
909+
if (id.startsWith('file:')) {
910+
id = url.fileURLToPath(id);
911+
}
912+
return root.require(id);
913+
},
914+
require(id) {
915+
return root.require(id);
916+
}
906917
};
907918
}
908919

@@ -944,8 +955,19 @@ function getRoot(options) {
944955
const legacyRoot = getLegacyRoot();
945956
return {
946957
filename: legacyRoot.filename,
947-
import: async (id) => legacyRoot.require(id),
948-
require: (id) => legacyRoot.require(id)
958+
async import(id) {
959+
// Must accept URL objects
960+
id = id.toString();
961+
// To accuratesly simulate ES import, we need to
962+
// accept file:// URLs like it can
963+
if (id.startsWith('file:')) {
964+
id = url.fileURLToPath(id);
965+
}
966+
return legacyRoot.require(id);
967+
},
968+
require(id) {
969+
return legacyRoot.require(id);
970+
}
949971
};
950972
};
951973

‎lib/glob.js‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
const { globSync } = require('glob');
22

33
// synchronous glob 10 but with the sorting semantics of glob 8,
4-
// to ease backwards compatibility in Apostrophe startup logic
4+
// to ease backwards compatibility in Apostrophe startup logic.
5+
// Also replaces \ with / for consistency across platforms
56

67
module.exports = (pattern, options) => {
7-
const result = globSync(pattern, options);
8+
pattern = pattern.replaceAll('\\', '//');
9+
const result = globSync(pattern, options).map(path => path.replaceAll('\\', '/'));
810
if (!options.nosort) {
911
result.sort((a, b) => a.localeCompare(b, 'en'));
1012
}

‎lib/import-fresh.js‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module.exports = importFresh;
2+
3+
function importFresh(name) {
4+
// Don't bomb on plain Windows
5+
if (name.match(/^[A-Za-z]:/)) {
6+
name = `file:///${name.replaceAll('\\', '/')}`;
7+
}
8+
return import(`${name}?${Date.now()}`);
9+
}

‎lib/moog-require.js‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ const path = require('path');
55
const glob = require('./glob.js');
66
const resolveFrom = require('resolve-from');
77
const regExpQuote = require('regexp-quote');
8-
9-
const importFresh = moduleName => import(`${moduleName}?${Date.now()}`);
8+
const importFresh = require('./import-fresh');
109

1110
module.exports = async function(options) {
1211
const self = require('./moog')(options);

‎lib/moog.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ module.exports = function(options) {
189189
}
190190
// You can have access to options within a function, if you choose to
191191
// provide one
192-
const properties = ((typeof step[cascade]) === 'function') ? step[cascade](that, options) : step[cascade];
192+
const properties = ((typeof step[cascade]) === 'function') ? await step[cascade](that, options) : step[cascade];
193193
if (properties) {
194194
const valid = [ 'add', 'remove', 'order', 'group' ];
195195
if (properties.add) {

‎modules/@apostrophecms/asset/index.js‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const { stripIndent } = require('common-tags');
66
const { createId } = require('@paralleldrive/cuid2');
77
const chokidar = require('chokidar');
88
const _ = require('lodash');
9-
const { glob } = require('glob');
9+
const { glob } = require('./lib/path');
1010
const globalIcons = require('./lib/globalIcons');
1111
const {
1212
checkModulesWebpackConfig,
@@ -939,7 +939,8 @@ module.exports = {
939939
const pulledChanges = [];
940940
let change = changes.pop();
941941
while (change) {
942-
pulledChanges.push(change);
942+
// Fix windows paths
943+
pulledChanges.push(change.replace(/\\/g, '/'));
943944
change = changes.pop();
944945
}
945946
// No changes - should never happen.

‎modules/@apostrophecms/asset/lib/build/external-module-api.js‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
const fs = require('fs-extra');
22
const path = require('node:path');
3-
const { glob } = require('glob');
3+
const { glob } = require('../../lib/path');
44
const { stripIndent } = require('common-tags');
5+
const { pathToFileURL } = require('node:url');
56

67
// High and Low level public API for external modules.
78
module.exports = (self) => {
@@ -653,16 +654,17 @@ function invoke() {
653654
}
654655
}
655656
}
656-
const jsFilename = JSON.stringify(component);
657+
// We know realPath is a file path at this point
658+
const importUrl = JSON.stringify(pathToFileURL(realPath));
657659
const name = self.getComponentNameByPath(
658660
component,
659661
{ enumerate: options.enumerateImports === true ? i : false }
660662
);
661663
const jsName = JSON.stringify(name);
662664
const importName = `${name}${options.importSuffix || ''}`;
663665
const importCode = options.importName === false
664-
? `import ${jsFilename};\n`
665-
: `import ${importName} from ${jsFilename};\n`;
666+
? `import ${importUrl};\n`
667+
: `import ${importName} from ${importUrl};\n`;
666668

667669
output.importCode += `${importCode}`;
668670

@@ -720,6 +722,7 @@ function invoke() {
720722
let importName = importFrom;
721723
if (!importIndex.includes(importFrom)) {
722724
if (importFrom.substring(0, 1) === '~') {
725+
// An npm module name
723726
importName = self.apos.util.slugify(importFrom).replaceAll('-', '');
724727
output.importCode += `import ${importName}Icon from '${importFrom.substring(1)}';\n`;
725728
} else {

‎modules/@apostrophecms/asset/lib/build/internals.js‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const fs = require('fs-extra');
22
const path = require('node:path');
33
const util = require('node:util');
4-
const { glob } = require('glob');
4+
const { glob } = require('../../lib/path');
55
const { getBuildExtensions, fillExtraBundles } = require('./utils');
66

77
// Internal build interface.
@@ -127,11 +127,12 @@ module.exports = (self) => {
127127
// Get the component name from a file path. The `enumerate` option allows
128128
// to append a number to the component name.
129129
getComponentNameByPath(componentPath, { enumerate } = {}) {
130-
return path
130+
const result = path
131131
.basename(componentPath)
132132
.replace(/-/g, '_')
133133
.replace(/\s+/g, '')
134134
.replace(/\.\w+/, '') + (typeof enumerate === 'number' ? `_${enumerate}` : '');
135+
return result;
135136
},
136137

137138
// Return the reported by the external module during build dev server URL.

‎modules/@apostrophecms/asset/lib/build/task.js‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const fs = require('fs-extra');
55
const { stripIndent } = require('common-tags');
66
const webpackModule = require('webpack');
77
const { mergeWithCustomize: webpackMerge } = require('webpack-merge');
8+
const { pathToFileURL } = require('node:url');
89
const {
910
getBundlesNames,
1011
writeBundlesImportFiles,
@@ -675,12 +676,13 @@ module.exports = (self) => ({
675676
`);
676677
}
677678
}
678-
const jsFilename = JSON.stringify(component);
679+
// We know component is a file path at this point
680+
const importUrl = JSON.stringify(pathToFileURL(component));
679681
const name = getComponentName(component, options, i);
680682
const jsName = JSON.stringify(name);
681683
const importName = `${name}${options.importSuffix || ''}`;
682684
const importCode = `
683-
import ${importName} from ${jsFilename};
685+
import ${importName} from ${importUrl};
684686
`;
685687

686688
output.paths.push(component);

0 commit comments

Comments
 (0)