Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 382d51453a | |||
| cc094d8801 | |||
| 0b7cd9c635 | |||
| 5cba50b56e | |||
| 41ed17317c | |||
| db3b75a65d | |||
| d5027174dd | |||
| 5da1660c3e | |||
| 337fe2c64c |
+4
-4
@@ -9,11 +9,11 @@
|
|||||||
"schemaVersion": 2,
|
"schemaVersion": 2,
|
||||||
"projectType": "npm",
|
"projectType": "npm",
|
||||||
"module": {
|
"module": {
|
||||||
"githost": "gitlab.com",
|
"githost": "code.foss.global",
|
||||||
"gitscope": "gitzone/private",
|
"gitscope": "git.zone",
|
||||||
"gitrepo": "gitzone",
|
"gitrepo": "cli",
|
||||||
"description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.",
|
"description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.",
|
||||||
"npmPackagename": "@gitzone_private/gitzone",
|
"npmPackagename": "@git.zone/cli",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"CLI",
|
"CLI",
|
||||||
|
|||||||
+34
-1
@@ -1,8 +1,41 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## Pending
|
## 2026-06-03 - 2.19.5
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- improve changelog release handling and TypeScript compatibility (cli)
|
||||||
|
- Avoid mutating changelog files when only reading pending entries
|
||||||
|
- Remove the consumed Pending section when moving entries to a versioned release
|
||||||
|
- Use async project metadata creation and safer unknown error handling for updated dependencies
|
||||||
|
- Add changelog helper tests for missing, consumed, and recreated Pending sections
|
||||||
|
- update project metadata for code.foss.global repository (metadata)
|
||||||
|
- Updated repository, issue tracker, and homepage URLs to code.foss.global/git.zone/cli
|
||||||
|
- Aligned smartconfig module metadata with the @git.zone/cli package and git.zone scope
|
||||||
|
|
||||||
|
## 2026-05-24 - 2.19.4
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- refresh dependency resolution for global installs to pick up the fixed `@push.rocks/smartgit` release
|
||||||
|
- Refreshes the lockfile so `@git.zone/tsdoc` resolves `@push.rocks/smartgit@3.3.3`.
|
||||||
|
|
||||||
|
## 2026-05-24 - 2.19.3
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- handle empty commit analysis as a clean no-op
|
||||||
|
- Uses `@git.zone/tsdoc`'s explicit `NoChangesError` signal.
|
||||||
|
- Makes `gitzone commit` and `gitzone commit recommend --json` return cleanly when no uncommitted changes exist.
|
||||||
|
|
||||||
|
## 2026-05-23 - 2.19.2
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- repair `gitzone tools update` for pnpm 11 global installs
|
||||||
|
- Detects and migrates legacy pnpm global roots such as `PNPM_HOME/global/5`.
|
||||||
|
- Runs global pnpm maintenance with project package-manager switching disabled.
|
||||||
|
- Refreshes pnpm v10 and v11 command shims before deleting stale managed global roots.
|
||||||
|
|
||||||
## 2026-05-14 - 2.19.1
|
## 2026-05-14 - 2.19.1
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@git.zone/cli",
|
"name": "@git.zone/cli",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "2.19.1",
|
"version": "2.19.5",
|
||||||
"description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.",
|
"description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
"typings": "dist_ts/index.d.ts",
|
"typings": "dist_ts/index.d.ts",
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitlab.com/gitzone/private/gitzone.git"
|
"url": "https://code.foss.global/git.zone/cli.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"CLI",
|
"CLI",
|
||||||
@@ -53,21 +53,21 @@
|
|||||||
"author": "Task Venture Capital GmbH",
|
"author": "Task Venture Capital GmbH",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://gitlab.com/gitzone/private/gitzone/issues"
|
"url": "https://code.foss.global/git.zone/cli/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://gitlab.com/gitzone/private/gitzone#readme",
|
"homepage": "https://code.foss.global/git.zone/cli#README",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^4.3.0",
|
"@git.zone/tsbuild": "^4.4.2",
|
||||||
"@git.zone/tsrun": "^2.0.1",
|
"@git.zone/tsrun": "^2.0.1",
|
||||||
"@git.zone/tstest": "^3.3.2",
|
"@git.zone/tstest": "^3.6.6",
|
||||||
"@types/node": "^25.4.0"
|
"@types/node": "^25.9.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@git.zone/tsdoc": "^2.0.0",
|
"@git.zone/tsdoc": "^2.0.6",
|
||||||
"@git.zone/tspublish": "^1.11.2",
|
"@git.zone/tspublish": "^1.11.2",
|
||||||
"@push.rocks/commitinfo": "^1.0.12",
|
"@push.rocks/commitinfo": "^1.0.13",
|
||||||
"@push.rocks/early": "^4.0.4",
|
"@push.rocks/early": "^4.0.4",
|
||||||
"@push.rocks/projectinfo": "^5.0.2",
|
"@push.rocks/projectinfo": "^5.1.0",
|
||||||
"@push.rocks/smartcli": "^4.0.20",
|
"@push.rocks/smartcli": "^4.0.20",
|
||||||
"@push.rocks/smartconfig": "^6.0.1",
|
"@push.rocks/smartconfig": "^6.0.1",
|
||||||
"@push.rocks/smartdelay": "^3.0.5",
|
"@push.rocks/smartdelay": "^3.0.5",
|
||||||
@@ -86,10 +86,10 @@
|
|||||||
"@push.rocks/smartopen": "^2.0.0",
|
"@push.rocks/smartopen": "^2.0.0",
|
||||||
"@push.rocks/smartpath": "^6.0.0",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
"@push.rocks/smartpromise": "^4.2.3",
|
"@push.rocks/smartpromise": "^4.2.3",
|
||||||
"@push.rocks/smartscaf": "^4.0.21",
|
"@push.rocks/smartscaf": "^4.0.22",
|
||||||
"@push.rocks/smartshell": "^3.5.0",
|
"@push.rocks/smartshell": "^3.5.0",
|
||||||
"@push.rocks/smartunique": "^3.0.9",
|
"@push.rocks/smartunique": "^3.0.9",
|
||||||
"@push.rocks/smartupdate": "^2.0.6",
|
"@push.rocks/smartupdate": "^2.0.7",
|
||||||
"prettier": "^3.8.1"
|
"prettier": "^3.8.1"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
Generated
+2173
-2801
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,79 @@
|
|||||||
|
import { rm, writeFile, readFile, mkdtemp } from 'node:fs/promises';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
|
||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
|
import {
|
||||||
|
appendPendingChangelogEntry,
|
||||||
|
movePendingToVersion,
|
||||||
|
readPendingChangelog,
|
||||||
|
} from './ts/helpers.changelog.js';
|
||||||
|
|
||||||
|
const withChangelogFile = async (
|
||||||
|
initialContent: string,
|
||||||
|
testFunction: (filePath: string) => Promise<void>,
|
||||||
|
) => {
|
||||||
|
const testDir = await mkdtemp(join(tmpdir(), 'gitzone-changelog-'));
|
||||||
|
const changelogPath = join(testDir, 'changelog.md');
|
||||||
|
try {
|
||||||
|
await writeFile(changelogPath, initialContent);
|
||||||
|
await testFunction(changelogPath);
|
||||||
|
} finally {
|
||||||
|
await rm(testDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tap.test('readPendingChangelog should not create a missing Pending section', async () => {
|
||||||
|
await withChangelogFile('# Changelog\n\n## 2026-01-01 - 1.0.0\n\n### Fixes\n\n- existing\n', async (filePath) => {
|
||||||
|
const pending = await readPendingChangelog(filePath, 'Pending');
|
||||||
|
const changelogContent = await readFile(filePath, 'utf8');
|
||||||
|
|
||||||
|
expect(pending.isEmpty).toBeTrue();
|
||||||
|
expect(pending.block).toEqual('');
|
||||||
|
expect(changelogContent).not.toInclude('## Pending');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('movePendingToVersion should fail when Pending is missing', async () => {
|
||||||
|
await withChangelogFile('# Changelog\n\n## 2026-01-01 - 1.0.0\n\n### Fixes\n\n- existing\n', async (filePath) => {
|
||||||
|
let errorMessage = '';
|
||||||
|
try {
|
||||||
|
await movePendingToVersion(filePath, 'Pending', '## {{date}} - {{version}}', '1.0.1', '2026-01-02');
|
||||||
|
} catch (error) {
|
||||||
|
errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(errorMessage).toEqual('No pending changelog entries. Nothing to release.');
|
||||||
|
|
||||||
|
const changelogContent = await readFile(filePath, 'utf8');
|
||||||
|
expect(changelogContent).not.toInclude('## Pending');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('movePendingToVersion should remove the consumed Pending section', async () => {
|
||||||
|
await withChangelogFile('# Changelog\n\n## Pending\n\n### Fixes\n\n- pending fix\n\n## 2026-01-01 - 1.0.0\n\n### Fixes\n\n- existing\n', async (filePath) => {
|
||||||
|
await movePendingToVersion(filePath, 'Pending', '## {{date}} - {{version}}', '1.0.1', '2026-01-02');
|
||||||
|
|
||||||
|
const changelogContent = await readFile(filePath, 'utf8');
|
||||||
|
expect(changelogContent).not.toInclude('## Pending');
|
||||||
|
expect(changelogContent).toInclude('## 2026-01-02 - 1.0.1\n\n### Fixes\n\n- pending fix');
|
||||||
|
expect(changelogContent).toInclude('## 2026-01-01 - 1.0.0\n\n### Fixes\n\n- existing');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('appendPendingChangelogEntry should recreate Pending when needed', async () => {
|
||||||
|
await withChangelogFile('# Changelog\n\n## 2026-01-01 - 1.0.0\n\n### Fixes\n\n- existing\n', async (filePath) => {
|
||||||
|
await appendPendingChangelogEntry(filePath, 'Pending', {
|
||||||
|
type: 'fix',
|
||||||
|
scope: 'changelog',
|
||||||
|
message: 'record pending changes',
|
||||||
|
});
|
||||||
|
|
||||||
|
const changelogContent = await readFile(filePath, 'utf8');
|
||||||
|
expect(changelogContent).toInclude('## Pending\n\n### Fixes\n\n- record pending changes (changelog)');
|
||||||
|
expect(changelogContent).toInclude('## 2026-01-01 - 1.0.0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@git.zone/cli',
|
name: '@git.zone/cli',
|
||||||
version: '2.19.1',
|
version: '2.19.5',
|
||||||
description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.'
|
description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export class GitzoneConfig {
|
|||||||
return gitzoneConfig;
|
return gitzoneConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
public data: IGitzoneConfigData;
|
public data!: IGitzoneConfigData;
|
||||||
|
|
||||||
public async readConfigFromCwd() {
|
public async readConfigFromCwd() {
|
||||||
const smartconfigInstance = new plugins.smartconfig.Smartconfig(paths.cwd);
|
const smartconfigInstance = new plugins.smartconfig.Smartconfig(paths.cwd);
|
||||||
|
|||||||
+1
-1
@@ -105,7 +105,7 @@ export let run = async () => {
|
|||||||
const rawCliMode = await getRawCliMode();
|
const rawCliMode = await getRawCliMode();
|
||||||
|
|
||||||
// get packageInfo
|
// get packageInfo
|
||||||
const projectInfo = new plugins.projectinfo.ProjectInfo(paths.packageDir);
|
const projectInfo = await plugins.projectinfo.ProjectInfo.create(paths.packageDir);
|
||||||
const projectInfoVersion = (projectInfo.npm as any)?.version;
|
const projectInfoVersion = (projectInfo.npm as any)?.version;
|
||||||
const packageVersion =
|
const packageVersion =
|
||||||
typeof projectInfoVersion === "string" && projectInfoVersion.length > 0
|
typeof projectInfoVersion === "string" && projectInfoVersion.length > 0
|
||||||
|
|||||||
+22
-8
@@ -19,6 +19,12 @@ export interface IPendingChangelog {
|
|||||||
isEmpty: boolean;
|
isEmpty: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IChangelogSection {
|
||||||
|
start: number;
|
||||||
|
bodyStart: number;
|
||||||
|
end: number;
|
||||||
|
}
|
||||||
|
|
||||||
const bucketForCommitType = (commitType: string): TChangelogBucket => {
|
const bucketForCommitType = (commitType: string): TChangelogBucket => {
|
||||||
switch (commitType) {
|
switch (commitType) {
|
||||||
case "BREAKING CHANGE":
|
case "BREAKING CHANGE":
|
||||||
@@ -48,7 +54,7 @@ const writeChangelog = async (filePath: string, content: string): Promise<void>
|
|||||||
const findPendingSection = (
|
const findPendingSection = (
|
||||||
content: string,
|
content: string,
|
||||||
sectionName: string,
|
sectionName: string,
|
||||||
): { start: number; bodyStart: number; end: number } | null => {
|
): IChangelogSection | null => {
|
||||||
const headingRegex = new RegExp(`^##\\s+${sectionName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*$`, "m");
|
const headingRegex = new RegExp(`^##\\s+${sectionName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*$`, "m");
|
||||||
const match = headingRegex.exec(content);
|
const match = headingRegex.exec(content);
|
||||||
if (!match || match.index === undefined) {
|
if (!match || match.index === undefined) {
|
||||||
@@ -123,9 +129,11 @@ export const readPendingChangelog = async (
|
|||||||
filePath: string,
|
filePath: string,
|
||||||
sectionName = "Pending",
|
sectionName = "Pending",
|
||||||
): Promise<IPendingChangelog> => {
|
): Promise<IPendingChangelog> => {
|
||||||
const content = await ensurePendingSection(filePath, sectionName);
|
const content = await readChangelog(filePath);
|
||||||
const pendingSection = findPendingSection(content, sectionName)!;
|
const pendingSection = findPendingSection(content, sectionName);
|
||||||
const block = content.slice(pendingSection.bodyStart, pendingSection.end).trim();
|
const block = pendingSection
|
||||||
|
? content.slice(pendingSection.bodyStart, pendingSection.end).trim()
|
||||||
|
: "";
|
||||||
return {
|
return {
|
||||||
block,
|
block,
|
||||||
isEmpty: block.length === 0,
|
isEmpty: block.length === 0,
|
||||||
@@ -149,8 +157,11 @@ export const movePendingToVersion = async (
|
|||||||
version: string,
|
version: string,
|
||||||
dateString: string,
|
dateString: string,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
let content = await ensurePendingSection(filePath, sectionName);
|
let content = await readChangelog(filePath);
|
||||||
const pendingSection = findPendingSection(content, sectionName)!;
|
const pendingSection = findPendingSection(content, sectionName);
|
||||||
|
if (!pendingSection) {
|
||||||
|
throw new Error("No pending changelog entries. Nothing to release.");
|
||||||
|
}
|
||||||
const pendingBlock = content.slice(pendingSection.bodyStart, pendingSection.end).trim();
|
const pendingBlock = content.slice(pendingSection.bodyStart, pendingSection.end).trim();
|
||||||
if (!pendingBlock) {
|
if (!pendingBlock) {
|
||||||
throw new Error("No pending changelog entries. Nothing to release.");
|
throw new Error("No pending changelog entries. Nothing to release.");
|
||||||
@@ -159,7 +170,10 @@ export const movePendingToVersion = async (
|
|||||||
const renderedHeading = versionHeading
|
const renderedHeading = versionHeading
|
||||||
.replaceAll("{{version}}", version)
|
.replaceAll("{{version}}", version)
|
||||||
.replaceAll("{{date}}", dateString);
|
.replaceAll("{{date}}", dateString);
|
||||||
const nextContent = content.slice(pendingSection.end).replace(/^\n+/, "");
|
const beforePending = content.slice(0, pendingSection.start).trimEnd();
|
||||||
content = `${content.slice(0, pendingSection.bodyStart)}\n\n${renderedHeading}\n\n${pendingBlock}\n\n${nextContent}`;
|
const afterPending = content.slice(pendingSection.end).replace(/^\n+/, "").trimEnd();
|
||||||
|
content = [beforePending, renderedHeading, pendingBlock, afterPending]
|
||||||
|
.filter((block) => block.length > 0)
|
||||||
|
.join("\n\n");
|
||||||
await writeChangelog(filePath, content);
|
await writeChangelog(filePath, content);
|
||||||
};
|
};
|
||||||
|
|||||||
+32
-4
@@ -9,6 +9,13 @@ import { getCliMode, printJson, runWithSuppressedOutput } from "../helpers.climo
|
|||||||
import { appendPendingChangelogEntry } from "../helpers.changelog.js";
|
import { appendPendingChangelogEntry } from "../helpers.changelog.js";
|
||||||
import { resolveCommitWorkflow, type IResolvedCommitWorkflow } from "../helpers.workflow.js";
|
import { resolveCommitWorkflow, type IResolvedCommitWorkflow } from "../helpers.workflow.js";
|
||||||
|
|
||||||
|
const isNoChangesError = (error: unknown): boolean => {
|
||||||
|
return (
|
||||||
|
error instanceof plugins.tsdoc.NoChangesError ||
|
||||||
|
(error instanceof Error && error.name === "NoChangesError")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const run = async (argvArg: any) => {
|
export const run = async (argvArg: any) => {
|
||||||
const mode = await getCliMode(argvArg);
|
const mode = await getCliMode(argvArg);
|
||||||
const subcommand = argvArg._?.[1];
|
const subcommand = argvArg._?.[1];
|
||||||
@@ -65,7 +72,15 @@ export const run = async (argvArg: any) => {
|
|||||||
await runCommandStep(smartshellInstance, "Running build", workflow.buildCommand);
|
await runCommandStep(smartshellInstance, "Running build", workflow.buildCommand);
|
||||||
break;
|
break;
|
||||||
case "analyze":
|
case "analyze":
|
||||||
nextCommitObject = await runAnalyzeStep();
|
try {
|
||||||
|
nextCommitObject = await runAnalyzeStep();
|
||||||
|
} catch (error) {
|
||||||
|
if (isNoChangesError(error)) {
|
||||||
|
logger.log("info", "No uncommitted changes found. Nothing to commit.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
answerBucket = await buildAnswerBucket(nextCommitObject, workflow, mode, argvArg);
|
answerBucket = await buildAnswerBucket(nextCommitObject, workflow, mode, argvArg);
|
||||||
break;
|
break;
|
||||||
case "changelog":
|
case "changelog":
|
||||||
@@ -284,9 +299,22 @@ async function handleRecommend(mode: ICliMode): Promise<void> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const recommendation = mode.json
|
let recommendation: any;
|
||||||
? await runWithSuppressedOutput(recommendationBuilder)
|
try {
|
||||||
: await recommendationBuilder();
|
recommendation = mode.json
|
||||||
|
? await runWithSuppressedOutput(recommendationBuilder)
|
||||||
|
: await recommendationBuilder();
|
||||||
|
} catch (error) {
|
||||||
|
if (isNoChangesError(error)) {
|
||||||
|
if (mode.json) {
|
||||||
|
printJson({ ok: true, noChanges: true });
|
||||||
|
} else {
|
||||||
|
logger.log("info", "No uncommitted changes found.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
if (mode.json) {
|
if (mode.json) {
|
||||||
printJson(recommendation);
|
printJson(recommendation);
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ export async function detectCurrentBranch(): Promise<string> {
|
|||||||
logger.log('info', `Detected current branch: ${branchName}`);
|
logger.log('info', `Detected current branch: ${branchName}`);
|
||||||
return branchName;
|
return branchName;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log('warn', `Failed to detect branch: ${error.message}, falling back to "master"`);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
logger.log('warn', `Failed to detect branch: ${errorMessage}, falling back to "master"`);
|
||||||
return 'master';
|
return 'master';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,6 +226,7 @@ export async function bumpProjectVersion(
|
|||||||
|
|
||||||
return newVersion;
|
return newVersion;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Failed to bump project version: ${error.message}`);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
throw new Error(`Failed to bump project version: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,9 +42,10 @@ export class DiffReporter {
|
|||||||
change.content,
|
change.content,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
logger.log(
|
logger.log(
|
||||||
'error',
|
'error',
|
||||||
`Failed to generate diff for ${change.path}: ${error.message}`,
|
`Failed to generate diff for ${change.path}: ${errorMessage}`,
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,8 @@ export class CopyFormatter extends BaseFormatter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logVerbose(`Failed to process pattern ${pattern.from}: ${error.message}`);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
logVerbose(`Failed to process pattern ${pattern.from}: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,8 @@ export class PackageJsonFormatter extends BaseFormatter {
|
|||||||
packageJson.pnpm = packageJson.pnpm || {};
|
packageJson.pnpm = packageJson.pnpm || {};
|
||||||
packageJson.pnpm.overrides = overrides;
|
packageJson.pnpm.overrides = overrides;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logVerbose(`Could not read overrides.json: ${error.message}`);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
logVerbose(`Could not read overrides.json: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newContent = JSON.stringify(packageJson, null, 2);
|
const newContent = JSON.stringify(packageJson, null, 2);
|
||||||
|
|||||||
@@ -117,7 +117,8 @@ export class TemplatesFormatter extends BaseFormatter {
|
|||||||
try {
|
try {
|
||||||
renderedFiles = await this.renderTemplate(templateName);
|
renderedFiles = await this.renderTemplate(templateName);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logVerbose(`Failed to render template ${templateName}: ${error.message}`);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
logVerbose(`Failed to render template ${templateName}: ${errorMessage}`);
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ export class TsconfigFormatter extends BaseFormatter {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logVerbose(`Could not get tspublish modules: ${error.message}`);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
logVerbose(`Could not get tspublish modules: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
tsconfigObject.compilerOptions.paths = { ...existingPaths, ...tspublishPaths };
|
tsconfigObject.compilerOptions.paths = { ...existingPaths, ...tspublishPaths };
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export class Meta {
|
|||||||
/**
|
/**
|
||||||
* the meta repo data
|
* the meta repo data
|
||||||
*/
|
*/
|
||||||
public metaRepoData: interfaces.IMetaRepoData;
|
public metaRepoData!: interfaces.IMetaRepoData;
|
||||||
public smartshellInstance = new plugins.smartshell.Smartshell({
|
public smartshellInstance = new plugins.smartshell.Smartshell({
|
||||||
executor: 'bash',
|
executor: 'bash',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as plugins from './mod.plugins.js';
|
import * as plugins from './mod.plugins.js';
|
||||||
import * as paths from '../paths.js';
|
import * as paths from '../paths.js';
|
||||||
|
|
||||||
export let run = (argvArg) => {
|
export let run = async (argvArg) => {
|
||||||
let projectInfo = new plugins.projectinfo.ProjectInfo(paths.cwd);
|
let projectInfo = await plugins.projectinfo.ProjectInfo.create(paths.cwd);
|
||||||
if (argvArg._[1] === 'ci') {
|
if (argvArg._[1] === 'ci') {
|
||||||
plugins.smartopen.openUrl(
|
plugins.smartopen.openUrl(
|
||||||
`https://gitlab.com/${projectInfo.git.gituser}/${projectInfo.git.gitrepo}/settings/ci_cd`,
|
`https://gitlab.com/${projectInfo.git.gituser}/${projectInfo.git.gitrepo}/settings/ci_cd`,
|
||||||
|
|||||||
@@ -148,7 +148,8 @@ export class DockerContainer {
|
|||||||
const result = await this.smartshell.exec(command);
|
const result = await this.smartshell.exec(command);
|
||||||
return result.exitCode === 0;
|
return result.exitCode === 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log('error', `Failed to run container: ${error.message}`);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
logger.log('error', `Failed to run container: ${errorMessage}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,7 +178,8 @@ export class DockerContainer {
|
|||||||
const result = await this.smartshell.exec(`docker logs ${tailFlag} ${containerName}`);
|
const result = await this.smartshell.exec(`docker logs ${tailFlag} ${containerName}`);
|
||||||
return result.stdout;
|
return result.stdout;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return `Error getting logs: ${error.message}`;
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
return `Error getting logs: ${errorMessage}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export interface IServiceConfig {
|
|||||||
|
|
||||||
export class ServiceConfiguration {
|
export class ServiceConfiguration {
|
||||||
private configPath: string;
|
private configPath: string;
|
||||||
private config: IServiceConfig;
|
private config!: IServiceConfig;
|
||||||
private docker: DockerContainer;
|
private docker: DockerContainer;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|||||||
@@ -61,13 +61,15 @@ export class ServiceManager {
|
|||||||
default: ['mongodb', 'minio', 'elasticsearch']
|
default: ['mongodb', 'minio', 'elasticsearch']
|
||||||
});
|
});
|
||||||
|
|
||||||
this.enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch'];
|
const enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch'];
|
||||||
|
this.enabledServices = enabledServices;
|
||||||
|
|
||||||
// Save to .smartconfig.json
|
// Save to .smartconfig.json
|
||||||
await this.saveServiceConfiguration(this.enabledServices);
|
await this.saveServiceConfiguration(enabledServices);
|
||||||
} else {
|
} else {
|
||||||
this.enabledServices = gitzoneConfig.services;
|
const enabledServices = gitzoneConfig.services as string[];
|
||||||
logger.log('info', `🔧 Enabled services: ${this.enabledServices.join(', ')}`);
|
this.enabledServices = enabledServices;
|
||||||
|
logger.log('info', `🔧 Enabled services: ${enabledServices.join(', ')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -902,10 +904,11 @@ export class ServiceManager {
|
|||||||
default: currentServices
|
default: currentServices
|
||||||
});
|
});
|
||||||
|
|
||||||
this.enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch'];
|
const enabledServices = response.value || ['mongodb', 'minio', 'elasticsearch'];
|
||||||
|
this.enabledServices = enabledServices;
|
||||||
|
|
||||||
// Save to .smartconfig.json
|
// Save to .smartconfig.json
|
||||||
await this.saveServiceConfiguration(this.enabledServices);
|
await this.saveServiceConfiguration(enabledServices);
|
||||||
|
|
||||||
logger.log('ok', '✅ Service configuration updated');
|
logger.log('ok', '✅ Service configuration updated');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const isTemplate = async (templateNameArg: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getTemplate = async (templateNameArg: string) => {
|
export const getTemplate = async (templateNameArg: string) => {
|
||||||
if (isTemplate(templateNameArg)) {
|
if (await isTemplate(templateNameArg)) {
|
||||||
const localScafTemplate = new plugins.smartscaf.ScafTemplate(
|
const localScafTemplate = new plugins.smartscaf.ScafTemplate(
|
||||||
getTemplatePath(templateNameArg),
|
getTemplatePath(templateNameArg),
|
||||||
);
|
);
|
||||||
@@ -50,6 +50,10 @@ export const run = async (argvArg: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const localScafTemplate = await getTemplate(chosenTemplate);
|
const localScafTemplate = await getTemplate(chosenTemplate);
|
||||||
|
if (!localScafTemplate) {
|
||||||
|
logger.log('error', `Template ${chosenTemplate} not available`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
await localScafTemplate.askCliForMissingVariables();
|
await localScafTemplate.askCliForMissingVariables();
|
||||||
await localScafTemplate.writeToDisk(paths.cwd);
|
await localScafTemplate.writeToDisk(paths.cwd);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import * as plugins from "./mod.plugins.js";
|
|||||||
export interface IInstalledPackage {
|
export interface IInstalledPackage {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
|
globalDir?: string;
|
||||||
|
packagePath?: string;
|
||||||
|
legacy?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPackageUpdateInfo {
|
export interface IPackageUpdateInfo {
|
||||||
@@ -10,6 +13,8 @@ export interface IPackageUpdateInfo {
|
|||||||
currentVersion: string;
|
currentVersion: string;
|
||||||
latestVersion: string;
|
latestVersion: string;
|
||||||
needsUpdate: boolean;
|
needsUpdate: boolean;
|
||||||
|
needsMigration?: boolean;
|
||||||
|
globalDir?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPackageManagerInfo {
|
export interface IPackageManagerInfo {
|
||||||
@@ -19,18 +24,38 @@ export interface IPackageManagerInfo {
|
|||||||
needsUpdate: boolean;
|
needsUpdate: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ILegacyGlobalRootInfo {
|
||||||
|
globalDir: string;
|
||||||
|
packages: IInstalledPackage[];
|
||||||
|
unmanagedPackageNames: string[];
|
||||||
|
safeToDelete: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ILegacyCleanupResult {
|
||||||
|
globalDir: string;
|
||||||
|
deleted: boolean;
|
||||||
|
reason?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IShimSyncResult {
|
||||||
|
name: string;
|
||||||
|
action: "updated" | "removed" | "skipped";
|
||||||
|
reason?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPnpmListProject {
|
||||||
|
path?: string;
|
||||||
|
dependencies?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
export class PackageManagerUtil {
|
export class PackageManagerUtil {
|
||||||
private shell = new plugins.smartshell.Smartshell({
|
private shell = new plugins.smartshell.Smartshell({
|
||||||
executor: "bash",
|
executor: "bash",
|
||||||
});
|
});
|
||||||
|
private pnpmCommand: string | null | undefined;
|
||||||
|
|
||||||
public async detectPnpm(): Promise<boolean> {
|
public async detectPnpm(): Promise<boolean> {
|
||||||
try {
|
return Boolean(await this.getPnpmCommand());
|
||||||
const result = await this.shell.execSilent("pnpm --version 2>/dev/null");
|
|
||||||
return result.exitCode === 0 && Boolean(result.stdout.trim());
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getPnpmVersionInfo(): Promise<IPackageManagerInfo> {
|
public async getPnpmVersionInfo(): Promise<IPackageManagerInfo> {
|
||||||
@@ -45,53 +70,300 @@ export class PackageManagerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const currentVersion = await this.getCurrentPnpmVersion();
|
const currentVersion = await this.getCurrentPnpmVersion();
|
||||||
const latestVersion = await this.getLatestVersion("pnpm", ["https://registry.npmjs.org"]);
|
const latestVersion = await this.getLatestVersion("pnpm", [
|
||||||
|
"https://registry.npmjs.org",
|
||||||
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
available: true,
|
available: true,
|
||||||
currentVersion,
|
currentVersion,
|
||||||
latestVersion,
|
latestVersion,
|
||||||
needsUpdate: latestVersion ? this.isNewerVersion(currentVersion, latestVersion) : false,
|
needsUpdate: latestVersion
|
||||||
|
? this.isNewerVersion(currentVersion, latestVersion)
|
||||||
|
: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getInstalledPackages(): Promise<IInstalledPackage[]> {
|
public async getInstalledPackages(): Promise<IInstalledPackage[]> {
|
||||||
const packages: IInstalledPackage[] = [];
|
const packageMap = new Map<string, IInstalledPackage>();
|
||||||
|
const currentPackages = await this.getCurrentInstalledPackages();
|
||||||
|
|
||||||
try {
|
for (const packageInfo of currentPackages) {
|
||||||
const result = await this.shell.execSilent("pnpm list -g --depth=0 --json 2>/dev/null || true");
|
packageMap.set(packageInfo.name, packageInfo);
|
||||||
const output = result.stdout.trim();
|
|
||||||
if (!output) {
|
|
||||||
return packages;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = JSON.parse(output);
|
|
||||||
const dataArray = Array.isArray(data) ? data : [data];
|
|
||||||
for (const item of dataArray) {
|
|
||||||
const dependencies = item.dependencies || {};
|
|
||||||
for (const [name, info] of Object.entries(dependencies)) {
|
|
||||||
if (!name.startsWith("@git.zone/")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
packages.push({
|
|
||||||
name,
|
|
||||||
version: (info as any).version || "unknown",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
return packages;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return packages;
|
const legacyRoots = await this.getLegacyGlobalRoots();
|
||||||
|
for (const legacyRoot of legacyRoots) {
|
||||||
|
for (const packageInfo of legacyRoot.packages) {
|
||||||
|
if (!packageMap.has(packageInfo.name)) {
|
||||||
|
packageMap.set(packageInfo.name, packageInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(packageMap.values()).sort((packageA, packageB) =>
|
||||||
|
packageA.name.localeCompare(packageB.name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getLegacyGlobalRoots(): Promise<ILegacyGlobalRootInfo[]> {
|
||||||
|
const currentGlobalDir = await this.getCurrentGlobalDir();
|
||||||
|
const baseDirs = new Set<string>();
|
||||||
|
const pnpmHome = process.env.PNPM_HOME;
|
||||||
|
|
||||||
|
if (pnpmHome) {
|
||||||
|
baseDirs.add(plugins.path.join(pnpmHome, "global"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentGlobalDir) {
|
||||||
|
baseDirs.add(plugins.path.dirname(currentGlobalDir));
|
||||||
|
}
|
||||||
|
|
||||||
|
const roots: ILegacyGlobalRootInfo[] = [];
|
||||||
|
for (const baseDir of baseDirs) {
|
||||||
|
try {
|
||||||
|
const entries = await plugins.fs.readdir(baseDir, {
|
||||||
|
withFileTypes: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const globalDir = normalizePath(
|
||||||
|
plugins.path.join(baseDir, entry.name),
|
||||||
|
);
|
||||||
|
if (currentGlobalDir && pathsAreEqual(globalDir, currentGlobalDir)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootInfo = await this.inspectGlobalRoot(globalDir, false);
|
||||||
|
if (rootInfo.packages.length > 0) {
|
||||||
|
roots.push(rootInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return roots;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async cleanupLegacyGlobalRoots(): Promise<ILegacyCleanupResult[]> {
|
||||||
|
const legacyRoots = await this.getLegacyGlobalRoots();
|
||||||
|
if (legacyRoots.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPackageNames = new Set(
|
||||||
|
(await this.getCurrentInstalledPackages()).map(
|
||||||
|
(packageInfo) => packageInfo.name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const cleanupResults: ILegacyCleanupResult[] = [];
|
||||||
|
|
||||||
|
for (const legacyRoot of legacyRoots) {
|
||||||
|
const missingPackageNames = legacyRoot.packages
|
||||||
|
.map((packageInfo) => packageInfo.name)
|
||||||
|
.filter((packageName) => !currentPackageNames.has(packageName));
|
||||||
|
|
||||||
|
if (missingPackageNames.length > 0) {
|
||||||
|
cleanupResults.push({
|
||||||
|
globalDir: legacyRoot.globalDir,
|
||||||
|
deleted: false,
|
||||||
|
reason: `kept because ${missingPackageNames.join(", ")} are not installed in the current pnpm global root`,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!legacyRoot.safeToDelete) {
|
||||||
|
cleanupResults.push({
|
||||||
|
globalDir: legacyRoot.globalDir,
|
||||||
|
deleted: false,
|
||||||
|
reason:
|
||||||
|
legacyRoot.unmanagedPackageNames.length > 0
|
||||||
|
? `kept because it also contains ${legacyRoot.unmanagedPackageNames.join(", ")}`
|
||||||
|
: "kept because it is not a managed @git.zone-only global root",
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blockingShims = await this.getShimReferences(legacyRoot.globalDir);
|
||||||
|
if (blockingShims === null) {
|
||||||
|
cleanupResults.push({
|
||||||
|
globalDir: legacyRoot.globalDir,
|
||||||
|
deleted: false,
|
||||||
|
reason:
|
||||||
|
"kept because PNPM_HOME is not set, so command shims could not be verified",
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockingShims.length > 0) {
|
||||||
|
cleanupResults.push({
|
||||||
|
globalDir: legacyRoot.globalDir,
|
||||||
|
deleted: false,
|
||||||
|
reason: `kept because command shims still reference it: ${blockingShims.join(", ")}`,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await plugins.fs.rm(legacyRoot.globalDir, {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
|
cleanupResults.push({
|
||||||
|
globalDir: legacyRoot.globalDir,
|
||||||
|
deleted: true,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
cleanupResults.push({
|
||||||
|
globalDir: legacyRoot.globalDir,
|
||||||
|
deleted: false,
|
||||||
|
reason: `delete failed: ${(error as Error).message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanupResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async syncCurrentGlobalShims(): Promise<IShimSyncResult[]> {
|
||||||
|
const pnpmShimDirs = await this.getPnpmShimDirs();
|
||||||
|
if (!pnpmShimDirs) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: "PNPM_HOME",
|
||||||
|
action: "skipped",
|
||||||
|
reason: "PNPM_HOME is not set",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const results: IShimSyncResult[] = [];
|
||||||
|
const currentBinNames = new Set<string>();
|
||||||
|
const currentPackages = await this.getCurrentInstalledPackages();
|
||||||
|
|
||||||
|
for (const packageInfo of currentPackages) {
|
||||||
|
if (!packageInfo.packagePath) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const packageJson = await readJson(
|
||||||
|
plugins.path.join(packageInfo.packagePath, "package.json"),
|
||||||
|
);
|
||||||
|
const binNames = getPackageBinNames(packageInfo.name, packageJson);
|
||||||
|
const nodeModulesDir = getNodeModulesDir(
|
||||||
|
packageInfo.packagePath,
|
||||||
|
packageInfo.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const binName of binNames) {
|
||||||
|
currentBinNames.add(binName);
|
||||||
|
const sourceShim = plugins.path.join(nodeModulesDir, ".bin", binName);
|
||||||
|
|
||||||
|
for (const pnpmShimDir of pnpmShimDirs) {
|
||||||
|
const destinationShim = plugins.path.join(pnpmShimDir, binName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sourceContent = await plugins.fs.readFile(sourceShim, "utf8");
|
||||||
|
const sourceStat = await plugins.fs.stat(sourceShim);
|
||||||
|
await plugins.fs.writeFile(
|
||||||
|
destinationShim,
|
||||||
|
rewriteShimForPnpmHome(sourceContent),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
await plugins.fs.chmod(destinationShim, sourceStat.mode);
|
||||||
|
results.push({
|
||||||
|
name: formatShimName(pnpmShimDir, binName),
|
||||||
|
action: "updated",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
results.push({
|
||||||
|
name: formatShimName(pnpmShimDir, binName),
|
||||||
|
action: "skipped",
|
||||||
|
reason: (error as Error).message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const legacyRoots = await this.getLegacyGlobalRoots();
|
||||||
|
const legacyGlobalDirs = legacyRoots.map(
|
||||||
|
(legacyRoot) => legacyRoot.globalDir,
|
||||||
|
);
|
||||||
|
if (legacyGlobalDirs.length === 0) {
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const pnpmShimDir of pnpmShimDirs) {
|
||||||
|
try {
|
||||||
|
const entries = await plugins.fs.readdir(pnpmShimDir, {
|
||||||
|
withFileTypes: true,
|
||||||
|
});
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isFile() || currentBinNames.has(entry.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = plugins.path.join(pnpmShimDir, entry.name);
|
||||||
|
let content = "";
|
||||||
|
try {
|
||||||
|
content = await plugins.fs.readFile(filePath, "utf8");
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!legacyGlobalDirs.some((legacyGlobalDir) =>
|
||||||
|
content.includes(legacyGlobalDir),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await plugins.fs.rm(filePath, { force: true });
|
||||||
|
results.push({
|
||||||
|
name: formatShimName(pnpmShimDir, entry.name),
|
||||||
|
action: "removed",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
results.push({
|
||||||
|
name: formatShimName(pnpmShimDir, entry.name),
|
||||||
|
action: "skipped",
|
||||||
|
reason: (error as Error).message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
results.push({
|
||||||
|
name: pnpmShimDir,
|
||||||
|
action: "skipped",
|
||||||
|
reason: (error as Error).message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getLatestVersion(
|
public async getLatestVersion(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
registries = ["https://verdaccio.lossless.digital", "https://registry.npmjs.org"],
|
registries = [
|
||||||
|
"https://verdaccio.lossless.digital",
|
||||||
|
"https://registry.npmjs.org",
|
||||||
|
],
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
for (const registry of registries) {
|
for (const registry of registries) {
|
||||||
const latest = await this.getLatestVersionFromRegistry(registry, packageName);
|
const latest = await this.getLatestVersionFromRegistry(
|
||||||
|
registry,
|
||||||
|
packageName,
|
||||||
|
);
|
||||||
if (latest) {
|
if (latest) {
|
||||||
return latest;
|
return latest;
|
||||||
}
|
}
|
||||||
@@ -99,23 +371,60 @@ export class PackageManagerUtil {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async installLatest(packageName: string): Promise<boolean> {
|
public async installLatest(
|
||||||
const packageSpecifier = `${packageName}@latest`;
|
packageName: string,
|
||||||
|
version = "latest",
|
||||||
|
): Promise<boolean> {
|
||||||
|
const pnpmCommand = await this.getPnpmCommand();
|
||||||
|
if (!pnpmCommand) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const packageSpecifier = `${packageName}@${version}`;
|
||||||
console.log(` Installing ${packageSpecifier} via pnpm...`);
|
console.log(` Installing ${packageSpecifier} via pnpm...`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await this.shell.exec(`pnpm add -g ${shellQuote(packageSpecifier)}`);
|
const result = await this.shell.exec(
|
||||||
|
`${pnpmCommand} add -g ${shellQuote(packageSpecifier)}`,
|
||||||
|
);
|
||||||
return result.exitCode === 0;
|
return result.exitCode === 0;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async updatePnpm(targetVersion: string): Promise<boolean> {
|
||||||
|
const pnpmCommand = await this.getPnpmCommand();
|
||||||
|
if (!pnpmCommand) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const neutralDir = process.env.PNPM_HOME || "/tmp";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await this.shell.exec(
|
||||||
|
`${pnpmCommand} --dir ${shellQuote(neutralDir)} self-update ${shellQuote(targetVersion)}`,
|
||||||
|
);
|
||||||
|
this.pnpmCommand = undefined;
|
||||||
|
const currentVersion = await this.getCurrentPnpmVersion();
|
||||||
|
return (
|
||||||
|
result.exitCode === 0 &&
|
||||||
|
!this.isNewerVersion(currentVersion, targetVersion)
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public isNewerVersion(current: string, latest: string): boolean {
|
public isNewerVersion(current: string, latest: string): boolean {
|
||||||
const currentParts = normalizeSemver(current);
|
const currentParts = normalizeSemver(current);
|
||||||
const latestParts = normalizeSemver(latest);
|
const latestParts = normalizeSemver(latest);
|
||||||
|
|
||||||
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
|
for (
|
||||||
|
let i = 0;
|
||||||
|
i < Math.max(currentParts.length, latestParts.length);
|
||||||
|
i++
|
||||||
|
) {
|
||||||
const currentPart = currentParts[i] || 0;
|
const currentPart = currentParts[i] || 0;
|
||||||
const latestPart = latestParts[i] || 0;
|
const latestPart = latestParts[i] || 0;
|
||||||
if (latestPart > currentPart) return true;
|
if (latestPart > currentPart) return true;
|
||||||
@@ -127,7 +436,10 @@ export class PackageManagerUtil {
|
|||||||
|
|
||||||
private async getCurrentPnpmVersion(): Promise<string> {
|
private async getCurrentPnpmVersion(): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const result = await this.shell.execSilent("pnpm --version 2>/dev/null");
|
const result = await this.execPnpmSilent("--version 2>/dev/null");
|
||||||
|
if (!result) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
const versionMatch = result.stdout.trim().match(/(\d+\.\d+\.\d+)/);
|
const versionMatch = result.stdout.trim().match(/(\d+\.\d+\.\d+)/);
|
||||||
return versionMatch?.[1] || "unknown";
|
return versionMatch?.[1] || "unknown";
|
||||||
} catch {
|
} catch {
|
||||||
@@ -135,6 +447,265 @@ export class PackageManagerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getPnpmCommand(): Promise<string | null> {
|
||||||
|
if (this.pnpmCommand !== undefined) {
|
||||||
|
return this.pnpmCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidates = [
|
||||||
|
"pnpm --pm-on-fail=ignore",
|
||||||
|
"pnpm --config.manage-package-manager-versions=false",
|
||||||
|
"pnpm",
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
try {
|
||||||
|
const result = await this.shell.execSilent(
|
||||||
|
`${candidate} --version 2>/dev/null`,
|
||||||
|
);
|
||||||
|
if (result.exitCode === 0 && Boolean(result.stdout.trim())) {
|
||||||
|
this.pnpmCommand = candidate;
|
||||||
|
return this.pnpmCommand;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Try the next supported pnpm invocation form.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pnpmCommand = null;
|
||||||
|
return this.pnpmCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async execPnpmSilent(commandArgs: string): Promise<any | null> {
|
||||||
|
const pnpmCommand = await this.getPnpmCommand();
|
||||||
|
if (!pnpmCommand) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.shell.execSilent(`${pnpmCommand} ${commandArgs}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getPnpmListProjects(): Promise<IPnpmListProject[]> {
|
||||||
|
try {
|
||||||
|
const result = await this.execPnpmSilent(
|
||||||
|
"list -g --depth=0 --json 2>/dev/null || true",
|
||||||
|
);
|
||||||
|
const output = result?.stdout.trim();
|
||||||
|
if (!output) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = JSON.parse(output);
|
||||||
|
return Array.isArray(data) ? data : [data];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getCurrentGlobalDir(): Promise<string | null> {
|
||||||
|
const listProjects = await this.getPnpmListProjects();
|
||||||
|
for (const listProject of listProjects) {
|
||||||
|
if (typeof listProject.path === "string" && listProject.path.length > 0) {
|
||||||
|
return normalizeGlobalDir(listProject.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await this.execPnpmSilent("root -g 2>/dev/null");
|
||||||
|
const output = result?.stdout.trim();
|
||||||
|
return output ? normalizeGlobalDir(output) : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getCurrentInstalledPackages(): Promise<IInstalledPackage[]> {
|
||||||
|
const listProjects = await this.getPnpmListProjects();
|
||||||
|
const currentGlobalDir = await this.getCurrentGlobalDir();
|
||||||
|
const packageMap = new Map<string, IInstalledPackage>();
|
||||||
|
|
||||||
|
for (const listProject of listProjects) {
|
||||||
|
const globalDir = listProject.path
|
||||||
|
? normalizeGlobalDir(listProject.path)
|
||||||
|
: currentGlobalDir || undefined;
|
||||||
|
const dependencies = listProject.dependencies || {};
|
||||||
|
for (const [name, info] of Object.entries(dependencies)) {
|
||||||
|
if (!name.startsWith("@git.zone/")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
packageMap.set(name, {
|
||||||
|
name,
|
||||||
|
version: getDependencyVersion(info),
|
||||||
|
globalDir,
|
||||||
|
packagePath: getDependencyPackagePath(info),
|
||||||
|
legacy: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packageMap.size === 0 && currentGlobalDir) {
|
||||||
|
const currentRootInfo = await this.inspectGlobalRoot(
|
||||||
|
currentGlobalDir,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
for (const packageInfo of currentRootInfo.packages) {
|
||||||
|
packageMap.set(packageInfo.name, packageInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(packageMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async inspectGlobalRoot(
|
||||||
|
globalDir: string,
|
||||||
|
current: boolean,
|
||||||
|
): Promise<ILegacyGlobalRootInfo> {
|
||||||
|
const normalizedGlobalDir = normalizeGlobalDir(globalDir);
|
||||||
|
const rootPackageJson = await readJson(
|
||||||
|
plugins.path.join(normalizedGlobalDir, "package.json"),
|
||||||
|
);
|
||||||
|
const dependencies = getDependencyMap(rootPackageJson?.dependencies);
|
||||||
|
const packageMap = new Map<string, IInstalledPackage>();
|
||||||
|
|
||||||
|
for (const [name, spec] of Object.entries(dependencies)) {
|
||||||
|
if (!name.startsWith("@git.zone/")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
packageMap.set(name, {
|
||||||
|
name,
|
||||||
|
version:
|
||||||
|
(await this.getInstalledPackageVersion(normalizedGlobalDir, name)) ||
|
||||||
|
normalizeDependencySpec(spec),
|
||||||
|
globalDir: normalizedGlobalDir,
|
||||||
|
packagePath: getPackagePath(normalizedGlobalDir, name),
|
||||||
|
legacy: !current,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const gitZoneScopeDir = plugins.path.join(
|
||||||
|
normalizedGlobalDir,
|
||||||
|
"node_modules",
|
||||||
|
"@git.zone",
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
const entries = await plugins.fs.readdir(gitZoneScopeDir, {
|
||||||
|
withFileTypes: true,
|
||||||
|
});
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const name = `@git.zone/${entry.name}`;
|
||||||
|
packageMap.set(name, {
|
||||||
|
name,
|
||||||
|
version:
|
||||||
|
(await this.getInstalledPackageVersion(
|
||||||
|
normalizedGlobalDir,
|
||||||
|
name,
|
||||||
|
)) || "unknown",
|
||||||
|
globalDir: normalizedGlobalDir,
|
||||||
|
packagePath: getPackagePath(normalizedGlobalDir, name),
|
||||||
|
legacy: !current,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// A pnpm global root may be empty or may not have node_modules materialized yet.
|
||||||
|
}
|
||||||
|
|
||||||
|
const dependencyNames = Object.keys(dependencies);
|
||||||
|
const unmanagedPackageNames = dependencyNames.filter(
|
||||||
|
(packageName) => !packageName.startsWith("@git.zone/"),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
globalDir: normalizedGlobalDir,
|
||||||
|
packages: Array.from(packageMap.values()).sort((packageA, packageB) =>
|
||||||
|
packageA.name.localeCompare(packageB.name),
|
||||||
|
),
|
||||||
|
unmanagedPackageNames,
|
||||||
|
safeToDelete:
|
||||||
|
!current &&
|
||||||
|
dependencyNames.length > 0 &&
|
||||||
|
unmanagedPackageNames.length === 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getInstalledPackageVersion(
|
||||||
|
globalDir: string,
|
||||||
|
packageName: string,
|
||||||
|
): Promise<string | null> {
|
||||||
|
const packageJson = await readJson(
|
||||||
|
plugins.path.join(
|
||||||
|
globalDir,
|
||||||
|
"node_modules",
|
||||||
|
...packageName.split("/"),
|
||||||
|
"package.json",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return typeof packageJson?.version === "string"
|
||||||
|
? packageJson.version
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getShimReferences(
|
||||||
|
legacyGlobalDir: string,
|
||||||
|
): Promise<string[] | null> {
|
||||||
|
const pnpmShimDirs = await this.getPnpmShimDirs();
|
||||||
|
if (!pnpmShimDirs) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const references: string[] = [];
|
||||||
|
for (const pnpmShimDir of pnpmShimDirs) {
|
||||||
|
try {
|
||||||
|
const entries = await plugins.fs.readdir(pnpmShimDir, {
|
||||||
|
withFileTypes: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isFile()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = plugins.path.join(pnpmShimDir, entry.name);
|
||||||
|
try {
|
||||||
|
const content = await plugins.fs.readFile(filePath, "utf8");
|
||||||
|
if (content.includes(legacyGlobalDir)) {
|
||||||
|
references.push(formatShimName(pnpmShimDir, entry.name));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore unreadable or non-text files in PNPM_HOME.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return references;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getPnpmShimDirs(): Promise<string[] | null> {
|
||||||
|
const pnpmHome = process.env.PNPM_HOME;
|
||||||
|
if (!pnpmHome) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidateDirs = [plugins.path.join(pnpmHome, "bin"), pnpmHome];
|
||||||
|
const shimDirs: string[] = [];
|
||||||
|
for (const candidateDir of candidateDirs) {
|
||||||
|
try {
|
||||||
|
const stat = await plugins.fs.stat(candidateDir);
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
shimDirs.push(normalizePath(candidateDir));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore missing pnpm shim directories.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return shimDirs.length > 0 ? Array.from(new Set(shimDirs)) : null;
|
||||||
|
}
|
||||||
|
|
||||||
private async getLatestVersionFromRegistry(
|
private async getLatestVersionFromRegistry(
|
||||||
registry: string,
|
registry: string,
|
||||||
packageName: string,
|
packageName: string,
|
||||||
@@ -164,6 +735,125 @@ export class PackageManagerUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function readJson(filePath: string): Promise<any | null> {
|
||||||
|
try {
|
||||||
|
const content = await plugins.fs.readFile(filePath, "utf8");
|
||||||
|
return JSON.parse(content);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDependencyMap(value: unknown): Record<string, string> {
|
||||||
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return value as Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDependencyVersion(info: any): string {
|
||||||
|
if (info && typeof info === "object" && typeof info.version === "string") {
|
||||||
|
return info.version;
|
||||||
|
}
|
||||||
|
if (typeof info === "string") {
|
||||||
|
return normalizeDependencySpec(info);
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDependencyPackagePath(info: any): string | undefined {
|
||||||
|
return info && typeof info === "object" && typeof info.path === "string"
|
||||||
|
? normalizePath(info.path)
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPackagePath(globalDir: string, packageName: string): string {
|
||||||
|
return plugins.path.join(
|
||||||
|
globalDir,
|
||||||
|
"node_modules",
|
||||||
|
...packageName.split("/"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPackageBinNames(packageName: string, packageJson: any): string[] {
|
||||||
|
const bin = packageJson?.bin;
|
||||||
|
if (typeof bin === "string") {
|
||||||
|
return [getDefaultBinName(packageName)];
|
||||||
|
}
|
||||||
|
if (!bin || typeof bin !== "object" || Array.isArray(bin)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return Object.keys(bin)
|
||||||
|
.filter((binName) => binName.length > 0)
|
||||||
|
.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultBinName(packageName: string): string {
|
||||||
|
const packageNameParts = packageName.split("/");
|
||||||
|
return packageNameParts[packageNameParts.length - 1] || packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeModulesDir(packagePath: string, packageName: string): string {
|
||||||
|
return packageName.startsWith("@")
|
||||||
|
? plugins.path.dirname(plugins.path.dirname(packagePath))
|
||||||
|
: plugins.path.dirname(packagePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rewriteShimForPnpmHome(content: string): string {
|
||||||
|
const targetMatch = content.match(/^# cmd-shim-target=(.+)$/m);
|
||||||
|
if (!targetMatch?.[1]) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
const absoluteTarget = `"${escapeDoubleQuotedShell(targetMatch[1])}"`;
|
||||||
|
return content.replace(
|
||||||
|
/"\$basedir\/(?:\.\.\/)+store\/[^"\n]+"/g,
|
||||||
|
absoluteTarget,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeDoubleQuotedShell(value: string): string {
|
||||||
|
return value.replace(/["\\$`]/g, "\\$&");
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatShimName(pnpmShimDir: string, binName: string): string {
|
||||||
|
const pnpmHome = process.env.PNPM_HOME;
|
||||||
|
if (!pnpmHome) {
|
||||||
|
return binName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const relativeName = plugins.path.relative(
|
||||||
|
pnpmHome,
|
||||||
|
plugins.path.join(pnpmShimDir, binName),
|
||||||
|
);
|
||||||
|
return relativeName && !relativeName.startsWith("..")
|
||||||
|
? relativeName
|
||||||
|
: binName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDependencySpec(spec: unknown): string {
|
||||||
|
if (typeof spec !== "string" || spec.length === 0) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
const versionMatch = spec.match(/\d+\.\d+\.\d+(?:[-+][\w.-]+)?/);
|
||||||
|
return versionMatch?.[0] || spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeGlobalDir(globalDir: string): string {
|
||||||
|
const normalizedPath = normalizePath(globalDir);
|
||||||
|
return plugins.path.basename(normalizedPath) === "node_modules"
|
||||||
|
? plugins.path.dirname(normalizedPath)
|
||||||
|
: normalizedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePath(filePath: string): string {
|
||||||
|
return plugins.path.resolve(filePath).replace(/\/+$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function pathsAreEqual(pathA: string, pathB: string): boolean {
|
||||||
|
return normalizePath(pathA) === normalizePath(pathB);
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeSemver(version: string): number[] {
|
function normalizeSemver(version: string): number[] {
|
||||||
return version
|
return version
|
||||||
.replace(/^[^\d]*/, "")
|
.replace(/^[^\d]*/, "")
|
||||||
|
|||||||
+226
-46
@@ -51,7 +51,9 @@ async function runUpdate(argvArg: any, mode: ICliMode): Promise<void> {
|
|||||||
|
|
||||||
const pnpmInfo = await pmUtil.getPnpmVersionInfo();
|
const pnpmInfo = await pmUtil.getPnpmVersionInfo();
|
||||||
if (!pnpmInfo.available) {
|
if (!pnpmInfo.available) {
|
||||||
console.log("pnpm is required for gitzone tools update, but it was not found.");
|
console.log(
|
||||||
|
"pnpm is required for gitzone tools update, but it was not found.",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,18 +61,25 @@ async function runUpdate(argvArg: any, mode: ICliMode): Promise<void> {
|
|||||||
console.log(" Name Current Latest Status");
|
console.log(" Name Current Latest Status");
|
||||||
console.log(" ----------------------------------------------");
|
console.log(" ----------------------------------------------");
|
||||||
const latestPnpm = (pnpmInfo.latestVersion || "unknown").padEnd(12);
|
const latestPnpm = (pnpmInfo.latestVersion || "unknown").padEnd(12);
|
||||||
const pnpmStatus = pnpmInfo.latestVersion === null
|
const pnpmStatus =
|
||||||
? "? Version unknown"
|
pnpmInfo.latestVersion === null
|
||||||
: pnpmInfo.needsUpdate
|
? "? Version unknown"
|
||||||
? "Update available"
|
: pnpmInfo.needsUpdate
|
||||||
: "Up to date";
|
? "Update available"
|
||||||
console.log(` ${"pnpm".padEnd(9)}${pnpmInfo.currentVersion.padEnd(12)}${latestPnpm}${pnpmStatus}`);
|
: "Up to date";
|
||||||
|
console.log(
|
||||||
|
` ${"pnpm".padEnd(9)}${pnpmInfo.currentVersion.padEnd(12)}${latestPnpm}${pnpmStatus}`,
|
||||||
|
);
|
||||||
console.log("");
|
console.log("");
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
console.log("Using pnpm as the supported global package manager.\n");
|
console.log("Using pnpm as the supported global package manager.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pnpmNeedsUpdate = Boolean(
|
||||||
|
pnpmInfo.latestVersion && pnpmInfo.needsUpdate,
|
||||||
|
);
|
||||||
|
|
||||||
const selfUpdated = await handleSelfUpdate(pmUtil, mode);
|
const selfUpdated = await handleSelfUpdate(pmUtil, mode);
|
||||||
if (selfUpdated) {
|
if (selfUpdated) {
|
||||||
return;
|
return;
|
||||||
@@ -78,39 +87,79 @@ async function runUpdate(argvArg: any, mode: ICliMode): Promise<void> {
|
|||||||
|
|
||||||
const installedPackages = await pmUtil.getInstalledPackages();
|
const installedPackages = await pmUtil.getInstalledPackages();
|
||||||
const packageInfos = await getPackageUpdateInfos(pmUtil, installedPackages);
|
const packageInfos = await getPackageUpdateInfos(pmUtil, installedPackages);
|
||||||
|
const legacyRoots = await pmUtil.getLegacyGlobalRoots();
|
||||||
|
const legacyCleanupNeeded = legacyRoots.length > 0;
|
||||||
|
|
||||||
if (packageInfos.length === 0) {
|
if (packageInfos.length === 0) {
|
||||||
console.log("No managed @git.zone packages found installed globally.");
|
console.log("No managed @git.zone packages found installed globally.");
|
||||||
return;
|
} else {
|
||||||
}
|
console.log("Installed @git.zone packages:\n");
|
||||||
|
|
||||||
console.log("Installed @git.zone packages:\n");
|
|
||||||
console.log(" Package Current Latest Status");
|
|
||||||
console.log(" ------------------------------------------------------------");
|
|
||||||
for (const packageInfo of packageInfos) {
|
|
||||||
const status = packageInfo.latestVersion === "unknown"
|
|
||||||
? "? Version unknown"
|
|
||||||
: packageInfo.needsUpdate
|
|
||||||
? "Update available"
|
|
||||||
: "Up to date";
|
|
||||||
console.log(
|
console.log(
|
||||||
` ${packageInfo.name.padEnd(28)}${packageInfo.currentVersion.padEnd(12)}${packageInfo.latestVersion.padEnd(12)}${status}`,
|
" Package Current Latest Status",
|
||||||
);
|
);
|
||||||
|
console.log(
|
||||||
|
" ------------------------------------------------------------",
|
||||||
|
);
|
||||||
|
for (const packageInfo of packageInfos) {
|
||||||
|
console.log(
|
||||||
|
` ${packageInfo.name.padEnd(28)}${packageInfo.currentVersion.padEnd(12)}${packageInfo.latestVersion.padEnd(12)}${getPackageStatus(packageInfo)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.log("");
|
||||||
|
|
||||||
|
await printMissingPackages(pmUtil, installedPackages);
|
||||||
}
|
}
|
||||||
console.log("");
|
|
||||||
|
|
||||||
await printMissingPackages(pmUtil, installedPackages);
|
const packagesToUpdate = packageInfos.filter(
|
||||||
|
(packageInfo) => packageInfo.needsUpdate || packageInfo.needsMigration,
|
||||||
|
);
|
||||||
|
const packagesToMigrate = packageInfos.filter(
|
||||||
|
(packageInfo) => packageInfo.needsMigration,
|
||||||
|
);
|
||||||
|
|
||||||
const packagesToUpdate = packageInfos.filter((packageInfo) => packageInfo.needsUpdate);
|
if (packagesToMigrate.length > 0) {
|
||||||
if (packagesToUpdate.length === 0) {
|
console.log(
|
||||||
|
`Detected ${packagesToMigrate.length} package(s) in legacy pnpm global roots.`,
|
||||||
|
);
|
||||||
|
if (verbose) {
|
||||||
|
for (const packageInfo of packagesToMigrate) {
|
||||||
|
console.log(
|
||||||
|
` ${packageInfo.name} -> ${packageInfo.globalDir || "unknown"}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("");
|
||||||
|
} else if (legacyCleanupNeeded) {
|
||||||
|
console.log(
|
||||||
|
`Detected ${legacyRoots.length} legacy pnpm global root(s) for cleanup.`,
|
||||||
|
);
|
||||||
|
if (verbose) {
|
||||||
|
for (const legacyRoot of legacyRoots) {
|
||||||
|
console.log(` ${legacyRoot.globalDir}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
packagesToUpdate.length === 0 &&
|
||||||
|
!pnpmNeedsUpdate &&
|
||||||
|
!legacyCleanupNeeded
|
||||||
|
) {
|
||||||
console.log("All managed packages are up to date.");
|
console.log("All managed packages are up to date.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Found ${packagesToUpdate.length} package(s) with available updates.\n`);
|
const actionCount =
|
||||||
|
packagesToUpdate.length +
|
||||||
|
(pnpmNeedsUpdate ? 1 : 0) +
|
||||||
|
(legacyCleanupNeeded ? 1 : 0);
|
||||||
|
console.log(`Found ${actionCount} update action(s).\n`);
|
||||||
|
|
||||||
if (!mode.yes && !mode.interactive) {
|
if (!mode.yes && !mode.interactive) {
|
||||||
console.log("Run gitzone tools update -y to update without prompts.");
|
console.log(
|
||||||
|
"Run gitzone tools update -y to update, migrate, and cleanup without prompts.",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +169,7 @@ async function runUpdate(argvArg: any, mode: ICliMode): Promise<void> {
|
|||||||
const answer = await interactInstance.askQuestion({
|
const answer = await interactInstance.askQuestion({
|
||||||
type: "confirm",
|
type: "confirm",
|
||||||
name: "confirmUpdate",
|
name: "confirmUpdate",
|
||||||
message: "Do you want to update these packages?",
|
message: "Do you want to update, migrate, and cleanup these tools?",
|
||||||
default: true,
|
default: true,
|
||||||
});
|
});
|
||||||
shouldUpdate = answer.value === true;
|
shouldUpdate = answer.value === true;
|
||||||
@@ -131,7 +180,35 @@ async function runUpdate(argvArg: any, mode: ICliMode): Promise<void> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await installPackages(pmUtil, packagesToUpdate.map((packageInfo) => packageInfo.name), "updated");
|
if (pnpmNeedsUpdate && pnpmInfo.latestVersion) {
|
||||||
|
console.log(`Updating pnpm to ${pnpmInfo.latestVersion}...`);
|
||||||
|
const success = await pmUtil.updatePnpm(pnpmInfo.latestVersion);
|
||||||
|
console.log(
|
||||||
|
success
|
||||||
|
? "pnpm updated successfully.\n"
|
||||||
|
: "pnpm update failed. Continuing with package updates.\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const installResult =
|
||||||
|
packagesToUpdate.length > 0
|
||||||
|
? await installPackages(
|
||||||
|
pmUtil,
|
||||||
|
packagesToUpdate.map((packageInfo) => ({
|
||||||
|
name: packageInfo.name,
|
||||||
|
version:
|
||||||
|
packageInfo.latestVersion !== "unknown"
|
||||||
|
? packageInfo.latestVersion
|
||||||
|
: undefined,
|
||||||
|
})),
|
||||||
|
"updated",
|
||||||
|
)
|
||||||
|
: { successCount: 0, failCount: 0 };
|
||||||
|
|
||||||
|
if (packagesToUpdate.length > 0 || legacyCleanupNeeded) {
|
||||||
|
await syncCurrentGlobalShims(pmUtil);
|
||||||
|
await cleanupLegacyInstalls(pmUtil);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runInstall(argvArg: any, mode: ICliMode): Promise<void> {
|
async function runInstall(argvArg: any, mode: ICliMode): Promise<void> {
|
||||||
@@ -142,7 +219,9 @@ async function runInstall(argvArg: any, mode: ICliMode): Promise<void> {
|
|||||||
|
|
||||||
const pnpmAvailable = await pmUtil.detectPnpm();
|
const pnpmAvailable = await pmUtil.detectPnpm();
|
||||||
if (!pnpmAvailable) {
|
if (!pnpmAvailable) {
|
||||||
console.log("pnpm is required for gitzone tools install, but it was not found.");
|
console.log(
|
||||||
|
"pnpm is required for gitzone tools install, but it was not found.",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,8 +230,12 @@ async function runInstall(argvArg: any, mode: ICliMode): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const installedPackages = await pmUtil.getInstalledPackages();
|
const installedPackages = await pmUtil.getInstalledPackages();
|
||||||
const installedNames = new Set(installedPackages.map((packageInfo) => packageInfo.name));
|
const installedNames = new Set(
|
||||||
const missingPackages = GITZONE_PACKAGES.filter((packageName) => !installedNames.has(packageName));
|
installedPackages.map((packageInfo) => packageInfo.name),
|
||||||
|
);
|
||||||
|
const missingPackages = GITZONE_PACKAGES.filter(
|
||||||
|
(packageName) => !installedNames.has(packageName),
|
||||||
|
);
|
||||||
|
|
||||||
if (missingPackages.length === 0) {
|
if (missingPackages.length === 0) {
|
||||||
console.log("All managed @git.zone packages are already installed.");
|
console.log("All managed @git.zone packages are already installed.");
|
||||||
@@ -163,7 +246,9 @@ async function runInstall(argvArg: any, mode: ICliMode): Promise<void> {
|
|||||||
|
|
||||||
if (!mode.yes && !mode.interactive) {
|
if (!mode.yes && !mode.interactive) {
|
||||||
await printPackageListWithLatest(pmUtil, missingPackages);
|
await printPackageListWithLatest(pmUtil, missingPackages);
|
||||||
console.log("Run gitzone tools install -y to install all missing packages without prompts.");
|
console.log(
|
||||||
|
"Run gitzone tools install -y to install all missing packages without prompts.",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +295,9 @@ async function handleSelfUpdate(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(` @git.zone/cli ${currentVersion} -> ${latestVersion} Update available\n`);
|
console.log(
|
||||||
|
` @git.zone/cli ${currentVersion} -> ${latestVersion} Update available\n`,
|
||||||
|
);
|
||||||
|
|
||||||
if (!mode.yes && !mode.interactive) {
|
if (!mode.yes && !mode.interactive) {
|
||||||
console.log("Run gitzone tools update -y to update gitzone first.");
|
console.log("Run gitzone tools update -y to update gitzone first.");
|
||||||
@@ -236,11 +323,15 @@ async function handleSelfUpdate(
|
|||||||
|
|
||||||
const success = await pmUtil.installLatest("@git.zone/cli");
|
const success = await pmUtil.installLatest("@git.zone/cli");
|
||||||
if (!success) {
|
if (!success) {
|
||||||
console.log("\ngitzone self-update failed. Continuing with the current version.\n");
|
console.log(
|
||||||
|
"\ngitzone self-update failed. Continuing with the current version.\n",
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("\ngitzone has been updated. Re-run gitzone tools update to check remaining packages.");
|
console.log(
|
||||||
|
"\ngitzone has been updated. Re-run gitzone tools update to check remaining packages.",
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,17 +352,39 @@ async function getPackageUpdateInfos(
|
|||||||
needsUpdate: latestVersion
|
needsUpdate: latestVersion
|
||||||
? pmUtil.isNewerVersion(installedPackage.version, latestVersion)
|
? pmUtil.isNewerVersion(installedPackage.version, latestVersion)
|
||||||
: false,
|
: false,
|
||||||
|
needsMigration: installedPackage.legacy === true,
|
||||||
|
globalDir: installedPackage.globalDir,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return packageInfos;
|
return packageInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPackageStatus(packageInfo: IPackageUpdateInfo): string {
|
||||||
|
if (packageInfo.latestVersion === "unknown") {
|
||||||
|
return "? Version unknown";
|
||||||
|
}
|
||||||
|
if (packageInfo.needsUpdate && packageInfo.needsMigration) {
|
||||||
|
return "Update + migrate";
|
||||||
|
}
|
||||||
|
if (packageInfo.needsUpdate) {
|
||||||
|
return "Update available";
|
||||||
|
}
|
||||||
|
if (packageInfo.needsMigration) {
|
||||||
|
return "Migrate global root";
|
||||||
|
}
|
||||||
|
return "Up to date";
|
||||||
|
}
|
||||||
|
|
||||||
async function printMissingPackages(
|
async function printMissingPackages(
|
||||||
pmUtil: PackageManagerUtil,
|
pmUtil: PackageManagerUtil,
|
||||||
installedPackages: IInstalledPackage[],
|
installedPackages: IInstalledPackage[],
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const installedNames = new Set(installedPackages.map((packageInfo) => packageInfo.name));
|
const installedNames = new Set(
|
||||||
const missingPackages = GITZONE_PACKAGES.filter((packageName) => !installedNames.has(packageName));
|
installedPackages.map((packageInfo) => packageInfo.name),
|
||||||
|
);
|
||||||
|
const missingPackages = GITZONE_PACKAGES.filter(
|
||||||
|
(packageName) => !installedNames.has(packageName),
|
||||||
|
);
|
||||||
if (missingPackages.length === 0) {
|
if (missingPackages.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -296,14 +409,18 @@ async function printPackageListWithLatest(
|
|||||||
|
|
||||||
async function installPackages(
|
async function installPackages(
|
||||||
pmUtil: PackageManagerUtil,
|
pmUtil: PackageManagerUtil,
|
||||||
packageNames: string[],
|
packageSpecs: Array<string | { name: string; version?: string }>,
|
||||||
action: "installed" | "updated",
|
action: "installed" | "updated",
|
||||||
): Promise<void> {
|
): Promise<{ successCount: number; failCount: number }> {
|
||||||
let successCount = 0;
|
let successCount = 0;
|
||||||
let failCount = 0;
|
let failCount = 0;
|
||||||
|
|
||||||
for (const packageName of packageNames) {
|
for (const packageSpec of packageSpecs) {
|
||||||
const success = await pmUtil.installLatest(packageName);
|
const packageName =
|
||||||
|
typeof packageSpec === "string" ? packageSpec : packageSpec.name;
|
||||||
|
const packageVersion =
|
||||||
|
typeof packageSpec === "string" ? undefined : packageSpec.version;
|
||||||
|
const success = await pmUtil.installLatest(packageName, packageVersion);
|
||||||
if (success) {
|
if (success) {
|
||||||
console.log(` ${packageName} ${action} successfully`);
|
console.log(` ${packageName} ${action} successfully`);
|
||||||
successCount++;
|
successCount++;
|
||||||
@@ -319,6 +436,56 @@ async function installPackages(
|
|||||||
} else {
|
} else {
|
||||||
console.log(`${successCount} package(s) ${action}, ${failCount} failed.`);
|
console.log(`${successCount} package(s) ${action}, ${failCount} failed.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { successCount, failCount };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cleanupLegacyInstalls(
|
||||||
|
pmUtil: PackageManagerUtil,
|
||||||
|
): Promise<void> {
|
||||||
|
const cleanupResults = await pmUtil.cleanupLegacyGlobalRoots();
|
||||||
|
if (cleanupResults.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Legacy pnpm global roots:\n");
|
||||||
|
for (const cleanupResult of cleanupResults) {
|
||||||
|
if (cleanupResult.deleted) {
|
||||||
|
console.log(` ${cleanupResult.globalDir} deleted`);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
` ${cleanupResult.globalDir} kept (${cleanupResult.reason || "unknown reason"})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncCurrentGlobalShims(
|
||||||
|
pmUtil: PackageManagerUtil,
|
||||||
|
): Promise<void> {
|
||||||
|
const shimResults = await pmUtil.syncCurrentGlobalShims();
|
||||||
|
const changedResults = shimResults.filter(
|
||||||
|
(shimResult) => shimResult.action !== "skipped",
|
||||||
|
);
|
||||||
|
const skippedResults = shimResults.filter(
|
||||||
|
(shimResult) => shimResult.action === "skipped",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (changedResults.length === 0 && skippedResults.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Command shims:\n");
|
||||||
|
for (const shimResult of changedResults) {
|
||||||
|
console.log(` ${shimResult.name} ${shimResult.action}`);
|
||||||
|
}
|
||||||
|
for (const shimResult of skippedResults) {
|
||||||
|
console.log(
|
||||||
|
` ${shimResult.name} skipped (${shimResult.reason || "unknown reason"})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.log("");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showHelp(mode?: ICliMode): void {
|
export function showHelp(mode?: ICliMode): void {
|
||||||
@@ -327,12 +494,21 @@ export function showHelp(mode?: ICliMode): void {
|
|||||||
name: "gitzone tools",
|
name: "gitzone tools",
|
||||||
usage: "gitzone tools <command> [options]",
|
usage: "gitzone tools <command> [options]",
|
||||||
commands: [
|
commands: [
|
||||||
{ name: "update", description: "Check and update globally installed @git.zone packages" },
|
{
|
||||||
{ name: "install", description: "Install missing managed @git.zone packages" },
|
name: "update",
|
||||||
|
description: "Check and update globally installed @git.zone packages",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "install",
|
||||||
|
description: "Install missing managed @git.zone packages",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
flags: [
|
flags: [
|
||||||
{ flag: "-y, --yes", description: "Run without confirmation prompts" },
|
{ flag: "-y, --yes", description: "Run without confirmation prompts" },
|
||||||
{ flag: "-v, --verbose", description: "Show package manager diagnostics" },
|
{
|
||||||
|
flag: "-v, --verbose",
|
||||||
|
description: "Show package manager diagnostics",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
packageManager: "pnpm",
|
packageManager: "pnpm",
|
||||||
managedPackages: GITZONE_PACKAGES,
|
managedPackages: GITZONE_PACKAGES,
|
||||||
@@ -344,8 +520,12 @@ export function showHelp(mode?: ICliMode): void {
|
|||||||
console.log("Usage: gitzone tools <command> [options]");
|
console.log("Usage: gitzone tools <command> [options]");
|
||||||
console.log("");
|
console.log("");
|
||||||
console.log("Commands:");
|
console.log("Commands:");
|
||||||
console.log(" update Check and update globally installed @git.zone packages");
|
console.log(
|
||||||
console.log(" install Install missing managed @git.zone packages");
|
" update Check and update globally installed @git.zone packages",
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
" install Install missing managed @git.zone packages",
|
||||||
|
);
|
||||||
console.log("");
|
console.log("");
|
||||||
console.log("Options:");
|
console.log("Options:");
|
||||||
console.log(" -y, --yes Run without confirmation prompts");
|
console.log(" -y, --yes Run without confirmation prompts");
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as smartlog from '@push.rocks/smartlog';
|
|||||||
import * as smartlogDestinationLocal from '@push.rocks/smartlog-destination-local';
|
import * as smartlogDestinationLocal from '@push.rocks/smartlog-destination-local';
|
||||||
import * as smartconfig from '@push.rocks/smartconfig';
|
import * as smartconfig from '@push.rocks/smartconfig';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import * as fs from 'node:fs/promises';
|
||||||
import * as projectinfo from '@push.rocks/projectinfo';
|
import * as projectinfo from '@push.rocks/projectinfo';
|
||||||
import * as smartcli from '@push.rocks/smartcli';
|
import * as smartcli from '@push.rocks/smartcli';
|
||||||
import * as smartpath from '@push.rocks/smartpath';
|
import * as smartpath from '@push.rocks/smartpath';
|
||||||
@@ -22,6 +23,7 @@ export {
|
|||||||
smartlogDestinationLocal,
|
smartlogDestinationLocal,
|
||||||
smartconfig,
|
smartconfig,
|
||||||
path,
|
path,
|
||||||
|
fs,
|
||||||
projectinfo,
|
projectinfo,
|
||||||
smartcli,
|
smartcli,
|
||||||
smartpath,
|
smartpath,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {}
|
"paths": {}
|
||||||
},
|
},
|
||||||
"exclude": ["dist_*/**/*.d.ts"]
|
"exclude": ["dist_*/**/*.d.ts"]
|
||||||
|
|||||||
Reference in New Issue
Block a user