Skip to content

Commit

Permalink
fix: use shell to spawn npm on Windows (#595)
Browse files Browse the repository at this point in the history
Co-authored-by: Dominic Griesel <dominic.griesel@nabucasa.com>
  • Loading branch information
mcm1957 and AlCalzone committed Apr 17, 2024
1 parent 252f5d6 commit 012c318
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 16 deletions.
9 changes: 7 additions & 2 deletions build/lib/executeCommand.js
Expand Up @@ -30,10 +30,15 @@ function executeCommand(command, argsOrOptions, options) {
spawnOptions.cwd = options.cwd;
// Fix npm / node executable paths on Windows
if (isWindows) {
if (command === "npm")
if (command === "npm") {
command += ".cmd";
else if (command === "node")
// Needed since Node.js v18.20.2 and v20.12.2
// https://github.com/nodejs/node/releases/tag/v18.20.2
spawnOptions.shell = true;
}
else if (command === "node") {
command += ".exe";
}
}
if (options.logCommandExecution == null)
options.logCommandExecution = false;
Expand Down
4 changes: 2 additions & 2 deletions build/tests/integration/lib/dbConnection.d.ts
Expand Up @@ -3,8 +3,8 @@
/// <reference types="node" />
/// <reference types="node" />
import EventEmitter from "events";
export declare type ObjectsDB = Record<string, ioBroker.Object>;
export declare type StatesDB = Record<string, ioBroker.State>;
export type ObjectsDB = Record<string, ioBroker.Object>;
export type StatesDB = Record<string, ioBroker.State>;
export interface DBConnection {
on(event: "objectChange", handler: ioBroker.ObjectChangeHandler): this;
on(event: "stateChange", handler: ioBroker.StateChangeHandler): this;
Expand Down
38 changes: 35 additions & 3 deletions build/tests/packageFiles/index.js
Expand Up @@ -93,13 +93,13 @@ function validatePackageFiles(adapterDir) {
skipIfInvalid.call(this, "package.json");
});
const packageContent = require(packageJsonPath);
const iopackContent = require(ioPackageJsonPath);
const requiredProperties = [
"name",
"version",
"description",
"author",
"license",
"main",
"repository",
"repository.type",
];
Expand All @@ -112,6 +112,11 @@ function validatePackageFiles(adapterDir) {
(0, chai_1.expect)(name).to.match(/^[a-z]/, `The adapter name must start with a letter!`);
(0, chai_1.expect)(name).to.match(/[a-z0-9]$/, `The adapter name must end with a letter or number!`);
});
if (!iopackContent.common.onlyWWW) {
it(`property main is defined for non onlyWWW adapters`, () => {
(0, chai_1.expect)(packageContent.main).to.not.be.undefined;
});
}
it(`The repository type is "git"`, () => {
(0, chai_1.expect)(packageContent.repository.type).to.equal("git");
});
Expand Down Expand Up @@ -142,7 +147,6 @@ function validatePackageFiles(adapterDir) {
"common.desc",
"common.icon",
"common.extIcon",
"common.license",
"common.type",
"common.authors",
"native",
Expand Down Expand Up @@ -174,6 +178,29 @@ function validatePackageFiles(adapterDir) {
(0, chai_1.expect)((0, typeguards_1.isObject)(news)).to.be.true;
(0, chai_1.expect)(Object.keys(news).length).to.be.at.most(20);
});
if (iopackContent.common.licenseInformation) {
it(`if common.licenseInformation exists, it is an object with required properties`, () => {
(0, chai_1.expect)(iopackContent.common.licenseInformation).to.be.an("object");
(0, chai_1.expect)(iopackContent.common.licenseInformation.type).to.be.oneOf(["free", "commercial", "paid", "limited"]);
if (iopackContent.common.licenseInformation.type !== "free") {
(0, chai_1.expect)(iopackContent.common.licenseInformation.link, "License link is missing").to.not.be.undefined;
}
});
it(`common.license should not exist together with common.licenseInformation`, () => {
(0, chai_1.expect)(iopackContent.common.license, "common.license must be removed").to.be.undefined;
});
}
else {
it(`common.license must exist without common.licenseInformation`, () => {
(0, chai_1.expect)(iopackContent.common.license, "common.licenseInformation (preferred) or common.license (deprecated) must exist").to.not.be.undefined;
});
}
if (iopackContent.common.tier != undefined) {
it(`common.tier must be 1, 2 or 3`, () => {
(0, chai_1.expect)(iopackContent.common.tier).to.be.at.least(1);
(0, chai_1.expect)(iopackContent.common.tier).to.be.at.most(3);
});
}
// If the adapter has a configuration page, check that a supported admin UI is used
const hasNoConfigPage = iopackContent.common.noConfig === true ||
iopackContent.common.noConfig === "true" ||
Expand All @@ -200,7 +227,12 @@ function validatePackageFiles(adapterDir) {
(0, chai_1.expect)(iopackContent.common.version).to.equal(packageContent.version);
});
it("The license matches", () => {
(0, chai_1.expect)(iopackContent.common.license).to.equal(packageContent.license);
if (iopackContent.common.licenseInformation) {
(0, chai_1.expect)(iopackContent.common.licenseInformation.license).to.equal(packageContent.license);
}
else {
(0, chai_1.expect)(iopackContent.common.license).to.equal(packageContent.license);
}
});
});
});
Expand Down
2 changes: 1 addition & 1 deletion build/tests/unit/mocks/mockAdapter.d.ts
Expand Up @@ -2,7 +2,7 @@
import type { MockDatabase } from "./mockDatabase";
import { MockLogger } from "./mockLogger";
import { Mock } from "./tools";
export declare type MockAdapter = Mock<ioBroker.Adapter> & {
export type MockAdapter = Mock<ioBroker.Adapter> & {
readyHandler: ioBroker.ReadyHandler | undefined;
objectChangeHandler: ioBroker.ObjectChangeHandler | undefined;
stateChangeHandler: ioBroker.StateChangeHandler | undefined;
Expand Down
2 changes: 1 addition & 1 deletion build/tests/unit/mocks/mockLogger.d.ts
@@ -1,6 +1,6 @@
/// <reference types="iobroker" />
import { Mock } from "./tools";
export declare type MockLogger = Mock<ioBroker.Logger> & {
export type MockLogger = Mock<ioBroker.Logger> & {
resetMock(): void;
resetMockHistory(): void;
resetMockBehavior(): void;
Expand Down
8 changes: 4 additions & 4 deletions build/tests/unit/mocks/tools.d.ts
@@ -1,13 +1,13 @@
/// <reference types="sinon" />
import type { Equals, Overwrite } from "alcalzone-shared/types";
export declare type IsAny<T> = Equals<T extends never ? false : true, boolean>;
export declare type MockableMethods<T, All = Required<T>, NoAny = {
export type IsAny<T> = Equals<T extends never ? false : true, boolean>;
export type MockableMethods<T, All = Required<T>, NoAny = {
[K in keyof All]: IsAny<All[K]> extends true ? never : All[K] extends (...args: any[]) => void ? K : never;
}> = NoAny[keyof NoAny];
export declare type Mock<T extends {}> = Overwrite<T, {
export type Mock<T extends {}> = Overwrite<T, {
[K in MockableMethods<T>]: sinon.SinonStub;
}>;
export declare function doResetHistory(parent: Record<string, any>): void;
export declare function doResetBehavior(parent: Record<string, any>, implementedMethods: Record<string, any>): void;
export declare function stubAndPromisifyImplementedMethods<T extends string>(parent: Record<T, any>, implementedMethods: Partial<Record<T, any>>, allowUserOverrides?: T[]): void;
export declare type ImplementedMethodDictionary<T> = Partial<Record<MockableMethods<T>, "none" | "normal" | "no error">>;
export type ImplementedMethodDictionary<T> = Partial<Record<MockableMethods<T>, "none" | "normal" | "no error">>;
12 changes: 9 additions & 3 deletions src/lib/executeCommand.ts
@@ -1,5 +1,5 @@
import { isArray, isObject } from "alcalzone-shared/typeguards";
import { spawn, SpawnOptions } from "child_process";
import { SpawnOptions, spawn } from "child_process";

const isWindows = /^win/.test(process.platform);

Expand Down Expand Up @@ -70,8 +70,14 @@ export function executeCommand(

// Fix npm / node executable paths on Windows
if (isWindows) {
if (command === "npm") command += ".cmd";
else if (command === "node") command += ".exe";
if (command === "npm") {
command += ".cmd";
// Needed since Node.js v18.20.2 and v20.12.2
// https://github.com/nodejs/node/releases/tag/v18.20.2
spawnOptions.shell = true;
} else if (command === "node") {
command += ".exe";
}
}

if (options.logCommandExecution == null)
Expand Down

0 comments on commit 012c318

Please sign in to comment.