From d5c9b34b3b1fae5f37ee61cf882b01e85aca6026 Mon Sep 17 00:00:00 2001 From: Michael Molisani Date: Tue, 29 Oct 2024 10:07:22 -0400 Subject: [PATCH] fix: hidden --node-version flag to bypass version discovery Signed-off-by: Michael Molisani --- packages/create-app/src/app.ts | 9 +- packages/create-app/src/impl.ts | 8 +- packages/create-app/src/node.ts | 6 + packages/create-app/tests/app.spec.ts | 42 +++ ... in registry, picks highest even major.txt | 2 +- ...kipped when --node-version is provided.txt | 277 ++++++++++++++++++ ...M_CONFIG_REGISTRY, URL ends with slash.txt | 2 +- ...gistry direct from NPM_CONFIG_REGISTRY.txt | 2 +- .../registry data has no versions.txt | 2 +- ...able to discover registry from process.txt | 2 +- ..._EXECPATH to get registry config value.txt | 2 +- 11 files changed, 346 insertions(+), 8 deletions(-) create mode 100644 packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/node version logic/version discovery skipped when --node-version is provided.txt diff --git a/packages/create-app/src/app.ts b/packages/create-app/src/app.ts index 6311cd6..5f57c05 100644 --- a/packages/create-app/src/app.ts +++ b/packages/create-app/src/app.ts @@ -1,6 +1,6 @@ // Copyright 2024 Bloomberg Finance L.P. // Distributed under the terms of the Apache 2.0 license. -import { buildApplication, buildCommand } from "@stricli/core"; +import { buildApplication, buildCommand, numberParser } from "@stricli/core"; import packageJson from "../package.json"; const command = buildCommand({ @@ -67,6 +67,13 @@ const command = buildCommand({ parse: String, default: "", }, + nodeVersion: { + kind: "parsed", + brief: "Node.js version to use for engines.node minimum and @types/node, bypasses version discovery logic", + parse: numberParser, + optional: true, + hidden: true, + }, }, aliases: { n: "name", diff --git a/packages/create-app/src/impl.ts b/packages/create-app/src/impl.ts index ce38dbd..3629156 100644 --- a/packages/create-app/src/impl.ts +++ b/packages/create-app/src/impl.ts @@ -115,6 +115,7 @@ export interface CreateProjectFlags extends PackageJsonTemplateValues { readonly template: "single" | "multi"; readonly autoComplete: boolean; readonly command?: string; + readonly nodeVersion?: number; } export default async function (this: LocalContext, flags: CreateProjectFlags, directoryPath: string): Promise { @@ -137,7 +138,12 @@ export default async function (this: LocalContext, flags: CreateProjectFlags, di const packageName = flags.name ?? path.basename(directoryPath); const commandName = flags.command ?? packageName; - const nodeVersions = await calculateAcceptableNodeVersions(this.process); + let nodeVersions: NodeVersions; + if (flags.nodeVersion) { + nodeVersions = { engine: `>=${flags.nodeVersion}`, types: `${flags.nodeVersion}.x` }; + } else { + nodeVersions = await calculateAcceptableNodeVersions(this.process); + } let packageJson = buildPackageJson( { diff --git a/packages/create-app/src/node.ts b/packages/create-app/src/node.ts index d150fef..92b2994 100644 --- a/packages/create-app/src/node.ts +++ b/packages/create-app/src/node.ts @@ -36,6 +36,9 @@ export async function calculateAcceptableNodeVersions(process: NodeJS.Process): process.stderr.write( `No version of @types/node found with major ${majorVersion}, falling back to ${typesVersion}\n`, ); + process.stderr.write( + `Rerun this command with the hidden flag --node-version to manually specify the Node.js major version` + ); } } } @@ -49,6 +52,9 @@ export async function calculateAcceptableNodeVersions(process: NodeJS.Process): process.stderr.write( `Unable to determine version of @types/node for ${process.versions.node}, assuming ${typesVersion}\n`, ); + process.stderr.write( + `Rerun this command with the hidden flag --node-version to manually specify the Node.js major version` + ); } return { diff --git a/packages/create-app/tests/app.spec.ts b/packages/create-app/tests/app.spec.ts index 39287be..4327ff0 100644 --- a/packages/create-app/tests/app.spec.ts +++ b/packages/create-app/tests/app.spec.ts @@ -783,6 +783,48 @@ describe("creates new application", () => { }); describe("node version logic", () => { + it("version discovery skipped when --node-version is provided", async function () { + const stdout = new FakeWritableStream(); + const stderr = new FakeWritableStream(); + const cwd = sinon.stub().returns("/home"); + const vol = Volume.fromJSON({}); + const memfs = createFsFromVolume(vol); + + const execFileSync = sandbox.stub(child_process, "execFileSync"); + execFileSync.returns("REGISTRY"); + + const fetch = sandbox.stub(globalThis, "fetch"); + fetch.resolves( + new Response( + JSON.stringify({}), + ), + ); + + const context: DeepPartial = { + process: { + stdout, + stderr, + cwd, + versions: { + node: `${futureLTSNodeMajorVersion}.0.0`, + }, + }, + fs: memfs as any, + path: nodePath, + }; + + await run(app, ["node-version-test", "--node-version", `${futureLTSNodeMajorVersion + 1}`], context as LocalContext); + + const result = { + stdout: stdout.text, + stderr: stderr.text, + files: vol.toJSON(), + }; + compareToBaseline(this, ApplicationTestResultFormat, result); + expect(execFileSync.callCount).to.equal(0, "execFileSync called unexpectedly"); + expect(fetch.callCount).to.equal(0, "fetch called unexpectedly"); + }); + it("exact version exists for types", async function () { const stdout = new FakeWritableStream(); const stderr = new FakeWritableStream(); diff --git a/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/node version logic/major version does not exist in registry, picks highest even major.txt b/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/node version logic/major version does not exist in registry, picks highest even major.txt index 373b85e..45fc495 100644 --- a/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/node version logic/major version does not exist in registry, picks highest even major.txt +++ b/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/node version logic/major version does not exist in registry, picks highest even major.txt @@ -2,7 +2,7 @@ [STDERR] No version of @types/node found with major 30, falling back to 20.x - +Rerun this command with the hidden flag --node-version to manually specify the Node.js major version [FILES] ::::/home/node-version-test/.gitignore # Logs diff --git a/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/node version logic/version discovery skipped when --node-version is provided.txt b/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/node version logic/version discovery skipped when --node-version is provided.txt new file mode 100644 index 0000000..34218d2 --- /dev/null +++ b/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/node version logic/version discovery skipped when --node-version is provided.txt @@ -0,0 +1,277 @@ +[STDOUT] + +[STDERR] + +[FILES] +::::/home/node-version-test/.gitignore +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Dependency directories +node_modules/ +jspm_packages/ + +*.tsbuildinfo +dist + +::::/home/node-version-test/package.json +{ + "name": "node-version-test", + "author": "", + "description": "Stricli command line application", + "license": "MIT", + "type": "module", + "version": "0.0.0", + "files": [ + "dist" + ], + "bin": { + "node-version-test": "dist/cli.js", + "__node-version-test_bash_complete": "dist/bash-complete.js" + }, + "engines": { + "node": ">=31" + }, + "scripts": { + "prebuild": "tsc -p src/tsconfig.json", + "build": "tsup", + "prepublishOnly": "npm run build", + "postinstall": "node-version-test install" + }, + "tsup": { + "entry": [ + "src/bin/cli.ts", + "src/bin/bash-complete.ts" + ], + "format": [ + "esm" + ], + "tsconfig": "src/tsconfig.json", + "clean": true, + "splitting": true, + "minify": true + }, + "dependencies": { + "@stricli/core": "", + "@stricli/auto-complete": "" + }, + "devDependencies": { + "@types/node": "31.x", + "tsup": "^6.7.0", + "typescript": "5.6.x" + } +} +::::/home/node-version-test/src/app.ts +import { buildApplication, buildRouteMap } from "@stricli/core"; +import { buildInstallCommand, buildUninstallCommand } from "@stricli/auto-complete"; +import { name, version, description } from "../package.json"; +import { subdirCommand } from "./commands/subdir/command"; +import { nestedRoutes } from "./commands/nested/commands"; + +const routes = buildRouteMap({ + routes: { + subdir: subdirCommand, + nested: nestedRoutes, + install: buildInstallCommand("node-version-test", { bash: "__node-version-test_bash_complete" }), + uninstall: buildUninstallCommand("node-version-test", { bash: true }), + }, + docs: { + brief: description, + hideRoute: { + install: true, + uninstall: true, + }, + }, +}); + +export const app = buildApplication(routes, { + name, + versionInfo: { + currentVersion: version, + }, +}); + +::::/home/node-version-test/src/bin/bash-complete.ts +#!/usr/bin/env node +import { proposeCompletions } from "@stricli/core"; +import { buildContext } from "../context"; +import { app } from "../app"; +const inputs = process.argv.slice(3); +if (process.env["COMP_LINE"]?.endsWith(" ")) { + inputs.push(""); +} +await proposeCompletions(app, inputs, buildContext(process)); +try { + for (const { completion } of await proposeCompletions(app, inputs, buildContext(process))) { + process.stdout.write(`${completion}\n`); + } +} catch { + // ignore +} + +::::/home/node-version-test/src/bin/cli.ts +#!/usr/bin/env node +import { run } from "@stricli/core"; +import { buildContext } from "../context"; +import { app } from "../app"; +await run(app, process.argv.slice(2), buildContext(process)); + +::::/home/node-version-test/src/commands/nested/commands.ts +import { buildCommand, buildRouteMap } from "@stricli/core"; + +export const fooCommand = buildCommand({ + loader: async () => { + const { foo } = await import("./impl"); + return foo; + }, + parameters: { + positional: { + kind: "tuple", + parameters: [], + }, + }, + docs: { + brief: "Nested foo command", + }, +}); + +export const barCommand = buildCommand({ + loader: async () => { + const { bar } = await import("./impl"); + return bar; + }, + parameters: { + positional: { + kind: "tuple", + parameters: [], + }, + }, + docs: { + brief: "Nested bar command", + }, +}); + +export const nestedRoutes = buildRouteMap({ + routes: { + foo: fooCommand, + bar: barCommand, + }, + docs: { + brief: "Nested commands", + }, +}); + +::::/home/node-version-test/src/commands/nested/impl.ts +import type { LocalContext } from "../../context"; + +interface FooCommandFlags { + // ... +} + +export async function foo(this: LocalContext, flags: FooCommandFlags): Promise { + // ... +} + +interface BarCommandFlags { + // ... +} + +export async function bar(this: LocalContext, flags: BarCommandFlags): Promise { + // ... +} + +::::/home/node-version-test/src/commands/subdir/command.ts +import { buildCommand } from "@stricli/core"; + +export const subdirCommand = buildCommand({ + loader: async () => import("./impl"), + parameters: { + positional: { + kind: "tuple", + parameters: [], + }, + }, + docs: { + brief: "Command in subdirectory", + }, +}); + +::::/home/node-version-test/src/commands/subdir/impl.ts +import type { LocalContext } from "../../context"; + +interface SubdirCommandFlags { + // ... +} + +export default async function(this: LocalContext, flags: SubdirCommandFlags): Promise { + // ... +} + +::::/home/node-version-test/src/context.ts +import type { CommandContext } from "@stricli/core"; +import type { StricliAutoCompleteContext } from "@stricli/auto-complete"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +export interface LocalContext extends CommandContext, StricliAutoCompleteContext { + readonly process: NodeJS.Process; + // ... +} + +export function buildContext(process: NodeJS.Process): LocalContext { + return { + process, + os, + fs, + path, + }; +} + +::::/home/node-version-test/src/tsconfig.json +{ + "compilerOptions": { + "noEmit": true, + "rootDir": "..", + "types": [ + "node" + ], + "resolveJsonModule": true, + "target": "esnext", + "module": "esnext", + "moduleResolution": "bundler", + "lib": [ + "esnext" + ], + "skipLibCheck": true, + "strict": true, + "isolatedModules": true, + "forceConsistentCasingInFileNames": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + "verbatimModuleSyntax": true + }, + "include": [ + "**/*" + ], + "exclude": [] +} \ No newline at end of file diff --git a/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/reads registry direct from NPM_CONFIG_REGISTRY, URL ends with slash.txt b/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/reads registry direct from NPM_CONFIG_REGISTRY, URL ends with slash.txt index 69f144c..960cff5 100644 --- a/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/reads registry direct from NPM_CONFIG_REGISTRY, URL ends with slash.txt +++ b/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/reads registry direct from NPM_CONFIG_REGISTRY, URL ends with slash.txt @@ -2,7 +2,7 @@ [STDERR] Unable to determine version of @types/node for 30.0.0, assuming 30.x - +Rerun this command with the hidden flag --node-version to manually specify the Node.js major version [FILES] ::::/home/node-version-test/.gitignore # Logs diff --git a/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/reads registry direct from NPM_CONFIG_REGISTRY.txt b/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/reads registry direct from NPM_CONFIG_REGISTRY.txt index 69f144c..960cff5 100644 --- a/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/reads registry direct from NPM_CONFIG_REGISTRY.txt +++ b/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/reads registry direct from NPM_CONFIG_REGISTRY.txt @@ -2,7 +2,7 @@ [STDERR] Unable to determine version of @types/node for 30.0.0, assuming 30.x - +Rerun this command with the hidden flag --node-version to manually specify the Node.js major version [FILES] ::::/home/node-version-test/.gitignore # Logs diff --git a/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/registry data has no versions.txt b/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/registry data has no versions.txt index 69f144c..960cff5 100644 --- a/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/registry data has no versions.txt +++ b/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/registry data has no versions.txt @@ -2,7 +2,7 @@ [STDERR] Unable to determine version of @types/node for 30.0.0, assuming 30.x - +Rerun this command with the hidden flag --node-version to manually specify the Node.js major version [FILES] ::::/home/node-version-test/.gitignore # Logs diff --git a/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/unable to discover registry from process.txt b/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/unable to discover registry from process.txt index 69f144c..960cff5 100644 --- a/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/unable to discover registry from process.txt +++ b/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/unable to discover registry from process.txt @@ -2,7 +2,7 @@ [STDERR] Unable to determine version of @types/node for 30.0.0, assuming 30.x - +Rerun this command with the hidden flag --node-version to manually specify the Node.js major version [FILES] ::::/home/node-version-test/.gitignore # Logs diff --git a/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/uses NPM_EXECPATH to get registry config value.txt b/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/uses NPM_EXECPATH to get registry config value.txt index 69f144c..960cff5 100644 --- a/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/uses NPM_EXECPATH to get registry config value.txt +++ b/packages/create-app/tests/baselines/reference/app/creates new application/checks for @types__node/registry logic/uses NPM_EXECPATH to get registry config value.txt @@ -2,7 +2,7 @@ [STDERR] Unable to determine version of @types/node for 30.0.0, assuming 30.x - +Rerun this command with the hidden flag --node-version to manually specify the Node.js major version [FILES] ::::/home/node-version-test/.gitignore # Logs