@cbnventures/nova 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/eslint.config.d.ts +32 -1
- package/build/eslint.config.d.ts.map +1 -1
- package/build/eslint.config.js +9 -1
- package/build/eslint.config.js.map +1 -1
- package/build/package.json +63 -59
- package/build/src/api/node-releases.d.ts +7 -0
- package/build/src/api/node-releases.d.ts.map +1 -0
- package/build/src/api/node-releases.js +67 -0
- package/build/src/api/node-releases.js.map +1 -0
- package/build/src/api/spdx-licenses.d.ts +7 -0
- package/build/src/api/spdx-licenses.d.ts.map +1 -0
- package/build/src/api/spdx-licenses.js +43 -0
- package/build/src/api/spdx-licenses.js.map +1 -0
- package/build/src/cli/index.js +60 -18
- package/build/src/cli/index.js.map +1 -1
- package/build/src/cli/recipe/pin-versions.d.ts +7 -0
- package/build/src/cli/recipe/pin-versions.d.ts.map +1 -0
- package/build/src/cli/recipe/pin-versions.js +145 -0
- package/build/src/cli/recipe/pin-versions.js.map +1 -0
- package/build/src/cli/recipe/sync-lts-engines.d.ts +6 -0
- package/build/src/cli/recipe/sync-lts-engines.d.ts.map +1 -0
- package/build/src/cli/recipe/sync-lts-engines.js +118 -0
- package/build/src/cli/recipe/sync-lts-engines.js.map +1 -0
- package/build/src/cli/recipe/sync-packages.d.ts +18 -0
- package/build/src/cli/recipe/sync-packages.d.ts.map +1 -0
- package/build/src/cli/recipe/sync-packages.js +1212 -0
- package/build/src/cli/recipe/sync-packages.js.map +1 -0
- package/build/src/cli/utility/changelog.d.ts +11 -0
- package/build/src/cli/utility/changelog.d.ts.map +1 -0
- package/build/src/cli/utility/changelog.js +670 -0
- package/build/src/cli/utility/changelog.js.map +1 -0
- package/build/src/cli/utility/initialize.d.ts +9 -5
- package/build/src/cli/utility/initialize.d.ts.map +1 -1
- package/build/src/cli/utility/initialize.js +478 -210
- package/build/src/cli/utility/initialize.js.map +1 -1
- package/build/src/cli/utility/type-check.d.ts +9 -0
- package/build/src/cli/utility/type-check.d.ts.map +1 -0
- package/build/src/cli/utility/type-check.js +59 -0
- package/build/src/cli/utility/type-check.js.map +1 -0
- package/build/src/cli/utility/version.d.ts +2 -2
- package/build/src/cli/utility/version.d.ts.map +1 -1
- package/build/src/cli/utility/version.js +107 -68
- package/build/src/cli/utility/version.js.map +1 -1
- package/build/src/lib/item.d.ts +23 -5
- package/build/src/lib/item.d.ts.map +1 -1
- package/build/src/lib/item.js +329 -4
- package/build/src/lib/item.js.map +1 -1
- package/build/src/lib/nova-config.d.ts +4 -4
- package/build/src/lib/nova-config.d.ts.map +1 -1
- package/build/src/lib/nova-config.js +76 -88
- package/build/src/lib/nova-config.js.map +1 -1
- package/build/src/lib/regex.d.ts +9 -1
- package/build/src/lib/regex.d.ts.map +1 -1
- package/build/src/lib/regex.js +9 -1
- package/build/src/lib/regex.js.map +1 -1
- package/build/src/lib/schema.d.ts +18 -0
- package/build/src/lib/schema.d.ts.map +1 -0
- package/build/src/lib/schema.js +13 -0
- package/build/src/lib/schema.js.map +1 -0
- package/build/src/lib/utility.d.ts +9 -1
- package/build/src/lib/utility.d.ts.map +1 -1
- package/build/src/lib/utility.js +219 -40
- package/build/src/lib/utility.js.map +1 -1
- package/build/src/presets/eslint/dx-code-style.d.mts.map +1 -1
- package/build/src/presets/eslint/dx-code-style.mjs +0 -20
- package/build/src/presets/eslint/dx-code-style.mjs.map +1 -1
- package/build/src/presets/eslint/lang-mdx.d.mts.map +1 -1
- package/build/src/presets/eslint/lang-mdx.mjs +0 -21
- package/build/src/presets/eslint/lang-mdx.mjs.map +1 -1
- package/build/src/presets/tsconfig/dx-strict.json +2 -1
- package/build/src/rules/eslint/index.d.ts +4 -0
- package/build/src/rules/eslint/index.d.ts.map +1 -1
- package/build/src/rules/eslint/index.js +4 -0
- package/build/src/rules/eslint/index.js.map +1 -1
- package/build/src/rules/eslint/no-logger-dev.d.ts +3 -1
- package/build/src/rules/eslint/no-logger-dev.d.ts.map +1 -1
- package/build/src/rules/eslint/no-logger-dev.js +4 -4
- package/build/src/rules/eslint/no-logger-dev.js.map +1 -1
- package/build/src/rules/eslint/no-raw-text-in-code.d.ts +6 -0
- package/build/src/rules/eslint/no-raw-text-in-code.d.ts.map +1 -0
- package/build/src/rules/eslint/no-raw-text-in-code.js +34 -0
- package/build/src/rules/eslint/no-raw-text-in-code.js.map +1 -0
- package/build/src/rules/eslint/no-regex-literal-flags.d.ts +6 -0
- package/build/src/rules/eslint/no-regex-literal-flags.d.ts.map +1 -0
- package/build/src/rules/eslint/no-regex-literal-flags.js +30 -0
- package/build/src/rules/eslint/no-regex-literal-flags.js.map +1 -0
- package/build/src/rules/eslint/no-regex-literals.d.ts +9 -0
- package/build/src/rules/eslint/no-regex-literals.d.ts.map +1 -0
- package/build/src/rules/eslint/no-regex-literals.js +55 -0
- package/build/src/rules/eslint/no-regex-literals.js.map +1 -0
- package/build/src/rules/eslint/switch-case-blocks.d.ts +6 -0
- package/build/src/rules/eslint/switch-case-blocks.d.ts.map +1 -0
- package/build/src/rules/eslint/switch-case-blocks.js +36 -0
- package/build/src/rules/eslint/switch-case-blocks.js.map +1 -0
- package/build/src/tests/api/node-releases.test.d.ts +2 -0
- package/build/src/tests/api/node-releases.test.d.ts.map +1 -0
- package/build/src/tests/api/node-releases.test.js +193 -0
- package/build/src/tests/api/node-releases.test.js.map +1 -0
- package/build/src/tests/api/spdx-licenses.test.d.ts +2 -0
- package/build/src/tests/api/spdx-licenses.test.d.ts.map +1 -0
- package/build/src/tests/api/spdx-licenses.test.js +91 -0
- package/build/src/tests/api/spdx-licenses.test.js.map +1 -0
- package/build/src/tests/cli/recipe/pin-versions.test.d.ts +2 -0
- package/build/src/tests/cli/recipe/pin-versions.test.d.ts.map +1 -0
- package/build/src/tests/cli/recipe/pin-versions.test.js +197 -0
- package/build/src/tests/cli/recipe/pin-versions.test.js.map +1 -0
- package/build/src/tests/cli/recipe/sync-lts-engines.test.d.ts +2 -0
- package/build/src/tests/cli/recipe/sync-lts-engines.test.d.ts.map +1 -0
- package/build/src/tests/cli/recipe/sync-lts-engines.test.js +131 -0
- package/build/src/tests/cli/recipe/sync-lts-engines.test.js.map +1 -0
- package/build/src/tests/lib/item.test.d.ts +2 -0
- package/build/src/tests/lib/item.test.d.ts.map +1 -0
- package/build/src/tests/lib/item.test.js +142 -0
- package/build/src/tests/lib/item.test.js.map +1 -0
- package/build/src/tests/lib/nova-config.test.d.ts +2 -0
- package/build/src/tests/lib/nova-config.test.d.ts.map +1 -0
- package/build/src/tests/lib/nova-config.test.js +489 -0
- package/build/src/tests/lib/nova-config.test.js.map +1 -0
- package/build/src/tests/lib/regex.test.d.ts +2 -0
- package/build/src/tests/lib/regex.test.d.ts.map +1 -0
- package/build/src/tests/lib/regex.test.js +342 -0
- package/build/src/tests/lib/regex.test.js.map +1 -0
- package/build/src/tests/lib/schema.test.d.ts +2 -0
- package/build/src/tests/lib/schema.test.d.ts.map +1 -0
- package/build/src/tests/lib/schema.test.js +260 -0
- package/build/src/tests/lib/schema.test.js.map +1 -0
- package/build/src/tests/lib/utility.test.js +704 -44
- package/build/src/tests/lib/utility.test.js.map +1 -1
- package/build/src/tests/rules/eslint/no-logger-dev.test.d.ts +2 -0
- package/build/src/tests/rules/eslint/no-logger-dev.test.d.ts.map +1 -0
- package/build/src/tests/rules/eslint/no-logger-dev.test.js +55 -0
- package/build/src/tests/rules/eslint/no-logger-dev.test.js.map +1 -0
- package/build/src/tests/rules/eslint/no-raw-text-in-code.test.d.ts +2 -0
- package/build/src/tests/rules/eslint/no-raw-text-in-code.test.d.ts.map +1 -0
- package/build/src/tests/rules/eslint/no-raw-text-in-code.test.js +47 -0
- package/build/src/tests/rules/eslint/no-raw-text-in-code.test.js.map +1 -0
- package/build/src/tests/rules/eslint/no-regex-literal-flags.test.d.ts +2 -0
- package/build/src/tests/rules/eslint/no-regex-literal-flags.test.d.ts.map +1 -0
- package/build/src/tests/rules/eslint/no-regex-literal-flags.test.js +47 -0
- package/build/src/tests/rules/eslint/no-regex-literal-flags.test.js.map +1 -0
- package/build/src/tests/rules/eslint/no-regex-literals.test.d.ts +2 -0
- package/build/src/tests/rules/eslint/no-regex-literals.test.d.ts.map +1 -0
- package/build/src/tests/rules/eslint/no-regex-literals.test.js +49 -0
- package/build/src/tests/rules/eslint/no-regex-literals.test.js.map +1 -0
- package/build/src/tests/rules/eslint/switch-case-blocks.test.d.ts +2 -0
- package/build/src/tests/rules/eslint/switch-case-blocks.test.d.ts.map +1 -0
- package/build/src/tests/rules/eslint/switch-case-blocks.test.js +43 -0
- package/build/src/tests/rules/eslint/switch-case-blocks.test.js.map +1 -0
- package/build/src/tests/toolkit/cli-header.test.d.ts +2 -0
- package/build/src/tests/toolkit/cli-header.test.d.ts.map +1 -0
- package/build/src/tests/toolkit/cli-header.test.js +143 -0
- package/build/src/tests/toolkit/cli-header.test.js.map +1 -0
- package/build/src/tests/toolkit/logger.test.d.ts +2 -0
- package/build/src/tests/toolkit/logger.test.d.ts.map +1 -0
- package/build/src/tests/toolkit/logger.test.js +96 -0
- package/build/src/tests/toolkit/logger.test.js.map +1 -0
- package/build/src/tests/toolkit/markdown-table.test.d.ts +2 -0
- package/build/src/tests/toolkit/markdown-table.test.d.ts.map +1 -0
- package/build/src/tests/toolkit/markdown-table.test.js +138 -0
- package/build/src/tests/toolkit/markdown-table.test.js.map +1 -0
- package/build/src/toolkit/cli-header.d.ts +1 -0
- package/build/src/toolkit/cli-header.d.ts.map +1 -1
- package/build/src/toolkit/cli-header.js +24 -13
- package/build/src/toolkit/cli-header.js.map +1 -1
- package/build/src/toolkit/index.d.ts +1 -1
- package/build/src/toolkit/index.d.ts.map +1 -1
- package/build/src/toolkit/index.js +1 -1
- package/build/src/toolkit/index.js.map +1 -1
- package/build/src/toolkit/logger.d.ts.map +1 -1
- package/build/src/toolkit/logger.js +25 -10
- package/build/src/toolkit/logger.js.map +1 -1
- package/build/src/toolkit/markdown-table.d.ts.map +1 -1
- package/build/src/toolkit/markdown-table.js +3 -3
- package/build/src/toolkit/markdown-table.js.map +1 -1
- package/package.json +63 -59
- package/build/src/cli/recipe/sync-metadata.d.ts +0 -5
- package/build/src/cli/recipe/sync-metadata.d.ts.map +0 -1
- package/build/src/cli/recipe/sync-metadata.js +0 -7
- package/build/src/cli/recipe/sync-metadata.js.map +0 -1
- package/build/src/cli/recipe/sync-versions.d.ts +0 -5
- package/build/src/cli/recipe/sync-versions.d.ts.map +0 -1
- package/build/src/cli/recipe/sync-versions.js +0 -7
- package/build/src/cli/recipe/sync-versions.js.map +0 -1
|
@@ -1,47 +1,43 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import { discoverPathsWithFile } from '../../lib/utility.js';
|
|
1
|
+
import { deepStrictEqual, fail, match, notStrictEqual, ok, strictEqual, } from 'node:assert/strict';
|
|
2
|
+
import { mkdir, mkdtemp, readFile, readdir, realpath, rm, stat, writeFile, } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { dirname, join, relative, sep, } from 'node:path';
|
|
5
|
+
import { test } from 'node:test';
|
|
6
|
+
import { currentTimestamp, detectShell, discoverPathsWithFile, executeShell, isCommandExists, isExecuteShellError, isFileIdentical, isPlainObject, isProjectRoot, loadWorkspaceManifests, parseLinuxOsReleaseText, parseWindowsRegistryText, pathExists, renameFileWithDate, saveWorkspaceManifest, } from '../../lib/utility.js';
|
|
7
7
|
import { Logger } from '../../toolkit/index.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
originalCwd = process.cwd();
|
|
13
|
-
sandboxRoot = await fs.mkdtemp(path.join(os.tmpdir(), `nova-${context.name}-`));
|
|
14
|
-
});
|
|
15
|
-
after(async () => {
|
|
8
|
+
test('discoverPathsWithFile', async (context) => {
|
|
9
|
+
const originalCwd = process.cwd();
|
|
10
|
+
const sandboxRoot = await mkdtemp(join(tmpdir(), `nova-${context.name}-`));
|
|
11
|
+
context.after(async () => {
|
|
16
12
|
process.chdir(originalCwd);
|
|
17
|
-
await
|
|
13
|
+
await rm(sandboxRoot, {
|
|
18
14
|
recursive: true,
|
|
19
15
|
force: true,
|
|
20
16
|
});
|
|
21
17
|
});
|
|
22
|
-
|
|
23
|
-
const projectRoot =
|
|
24
|
-
const appRoot =
|
|
25
|
-
const packageRoot =
|
|
26
|
-
const nodeRoot =
|
|
27
|
-
const dotHiddenRoot =
|
|
18
|
+
await context.test('finds every package.json when traversing forward', async () => {
|
|
19
|
+
const projectRoot = join(sandboxRoot, 'forward');
|
|
20
|
+
const appRoot = join(projectRoot, 'apps', 'some-app');
|
|
21
|
+
const packageRoot = join(projectRoot, 'packages', 'some-package');
|
|
22
|
+
const nodeRoot = join(projectRoot, 'node_modules', 'ignore-me');
|
|
23
|
+
const dotHiddenRoot = join(projectRoot, '.hidden', 'ignore-me');
|
|
28
24
|
await Promise.all([
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
mkdir(appRoot, { recursive: true }),
|
|
26
|
+
mkdir(packageRoot, { recursive: true }),
|
|
27
|
+
mkdir(nodeRoot, { recursive: true }),
|
|
28
|
+
mkdir(dotHiddenRoot, { recursive: true }),
|
|
33
29
|
]);
|
|
34
30
|
await Promise.all([
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
writeFile(join(projectRoot, 'package.json'), '{}\n'),
|
|
32
|
+
writeFile(join(appRoot, 'package.json'), '{}\n'),
|
|
33
|
+
writeFile(join(packageRoot, 'package.json'), '{}\n'),
|
|
34
|
+
writeFile(join(nodeRoot, 'package.json'), '{}\n'),
|
|
35
|
+
writeFile(join(dotHiddenRoot, 'package.json'), '{}\n'),
|
|
40
36
|
]);
|
|
41
|
-
const realProjectRoot = await
|
|
37
|
+
const realProjectRoot = await realpath(projectRoot);
|
|
42
38
|
process.chdir(realProjectRoot);
|
|
43
39
|
const absolutePaths = await discoverPathsWithFile('package.json', 'forward');
|
|
44
|
-
const relativePaths = absolutePaths.map((absolutePath) =>
|
|
40
|
+
const relativePaths = absolutePaths.map((absolutePath) => relative(realProjectRoot, absolutePath).split(sep).join('/'));
|
|
45
41
|
Logger.customize({
|
|
46
42
|
name: 'discoverPathsWithFile',
|
|
47
43
|
type: 'test',
|
|
@@ -52,22 +48,22 @@ describe('discoverPathsWithFile', (context) => {
|
|
|
52
48
|
type: 'test',
|
|
53
49
|
purpose: 'forward-relativePaths',
|
|
54
50
|
}).debug(relativePaths);
|
|
55
|
-
|
|
51
|
+
deepStrictEqual(relativePaths, ['', 'apps/some-app', 'packages/some-package']);
|
|
56
52
|
});
|
|
57
|
-
|
|
58
|
-
const projectRoot =
|
|
59
|
-
const appRoot =
|
|
60
|
-
const appStuffRoot =
|
|
61
|
-
await
|
|
53
|
+
await context.test('climbs to parent package.json files when traversing backward', async () => {
|
|
54
|
+
const projectRoot = join(sandboxRoot, 'backward');
|
|
55
|
+
const appRoot = join(projectRoot, 'apps', 'some-app');
|
|
56
|
+
const appStuffRoot = join(appRoot, 'stuff');
|
|
57
|
+
await mkdir(appStuffRoot, { recursive: true });
|
|
62
58
|
await Promise.all([
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
writeFile(join(projectRoot, 'package.json'), '{}\n'),
|
|
60
|
+
writeFile(join(appRoot, 'package.json'), '{}\n'),
|
|
65
61
|
]);
|
|
66
|
-
const realProjectRoot = await
|
|
67
|
-
const realAppStuffRoot = await
|
|
62
|
+
const realProjectRoot = await realpath(projectRoot);
|
|
63
|
+
const realAppStuffRoot = await realpath(appStuffRoot);
|
|
68
64
|
process.chdir(realAppStuffRoot);
|
|
69
65
|
const absolutePaths = await discoverPathsWithFile('package.json', 'backward');
|
|
70
|
-
const relativePaths = absolutePaths.map((absolutePath) =>
|
|
66
|
+
const relativePaths = absolutePaths.map((absolutePath) => relative(realProjectRoot, absolutePath).split(sep).join('/'));
|
|
71
67
|
Logger.customize({
|
|
72
68
|
name: 'discoverPathsWithFile',
|
|
73
69
|
type: 'test',
|
|
@@ -78,7 +74,671 @@ describe('discoverPathsWithFile', (context) => {
|
|
|
78
74
|
type: 'test',
|
|
79
75
|
purpose: 'backward-relativePaths',
|
|
80
76
|
}).debug(relativePaths);
|
|
81
|
-
|
|
77
|
+
deepStrictEqual(relativePaths, ['apps/some-app', '']);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
test('isPlainObject', async (context) => {
|
|
81
|
+
await context.test('returns true for empty object literal', () => {
|
|
82
|
+
strictEqual(isPlainObject({}), true);
|
|
83
|
+
});
|
|
84
|
+
await context.test('returns true for object with properties', () => {
|
|
85
|
+
const obj = {
|
|
86
|
+
a: 1,
|
|
87
|
+
b: 2,
|
|
88
|
+
};
|
|
89
|
+
strictEqual(isPlainObject(obj), true);
|
|
90
|
+
});
|
|
91
|
+
await context.test('returns true for Object.create(null)', () => {
|
|
92
|
+
strictEqual(isPlainObject(Object.create(null)), true);
|
|
93
|
+
});
|
|
94
|
+
await context.test('returns false for null', () => {
|
|
95
|
+
strictEqual(isPlainObject(null), false);
|
|
96
|
+
});
|
|
97
|
+
await context.test('returns false for undefined', () => {
|
|
98
|
+
strictEqual(isPlainObject(undefined), false);
|
|
99
|
+
});
|
|
100
|
+
await context.test('returns false for string', () => {
|
|
101
|
+
strictEqual(isPlainObject('hello'), false);
|
|
102
|
+
});
|
|
103
|
+
await context.test('returns false for number', () => {
|
|
104
|
+
strictEqual(isPlainObject(42), false);
|
|
105
|
+
});
|
|
106
|
+
await context.test('returns false for boolean', () => {
|
|
107
|
+
strictEqual(isPlainObject(true), false);
|
|
108
|
+
});
|
|
109
|
+
await context.test('returns false for array', () => {
|
|
110
|
+
strictEqual(isPlainObject([1, 2, 3]), false);
|
|
111
|
+
});
|
|
112
|
+
await context.test('returns false for Date instance', () => {
|
|
113
|
+
strictEqual(isPlainObject(new Date()), false);
|
|
114
|
+
});
|
|
115
|
+
await context.test('returns false for RegExp instance', () => {
|
|
116
|
+
strictEqual(isPlainObject(new RegExp('test')), false);
|
|
117
|
+
});
|
|
118
|
+
await context.test('returns false for Map instance', () => {
|
|
119
|
+
strictEqual(isPlainObject(new Map()), false);
|
|
120
|
+
});
|
|
121
|
+
await context.test('returns false for Set instance', () => {
|
|
122
|
+
strictEqual(isPlainObject(new Set()), false);
|
|
123
|
+
});
|
|
124
|
+
await context.test('returns false for class instance', () => {
|
|
125
|
+
class Foo {
|
|
126
|
+
}
|
|
127
|
+
strictEqual(isPlainObject(new Foo()), false);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
test('isExecuteShellError', async (context) => {
|
|
131
|
+
await context.test('returns true for object with cmd property', () => {
|
|
132
|
+
strictEqual(isExecuteShellError({ cmd: 'echo hello' }), true);
|
|
133
|
+
});
|
|
134
|
+
await context.test('returns true for object with killed property', () => {
|
|
135
|
+
strictEqual(isExecuteShellError({ killed: false }), true);
|
|
136
|
+
});
|
|
137
|
+
await context.test('returns true for object with code property', () => {
|
|
138
|
+
strictEqual(isExecuteShellError({ code: 1 }), true);
|
|
139
|
+
});
|
|
140
|
+
await context.test('returns true for object with signal property', () => {
|
|
141
|
+
strictEqual(isExecuteShellError({ signal: 'SIGTERM' }), true);
|
|
142
|
+
});
|
|
143
|
+
await context.test('returns true for object with stdout property', () => {
|
|
144
|
+
strictEqual(isExecuteShellError({ stdout: 'output' }), true);
|
|
145
|
+
});
|
|
146
|
+
await context.test('returns true for object with stderr property', () => {
|
|
147
|
+
strictEqual(isExecuteShellError({ stderr: 'error' }), true);
|
|
148
|
+
});
|
|
149
|
+
await context.test('returns true for object with multiple exec properties', () => {
|
|
150
|
+
const error = {
|
|
151
|
+
cmd: 'test',
|
|
152
|
+
killed: false,
|
|
153
|
+
code: 1,
|
|
154
|
+
signal: 'SIGTERM',
|
|
155
|
+
stdout: '',
|
|
156
|
+
stderr: 'failed',
|
|
157
|
+
};
|
|
158
|
+
strictEqual(isExecuteShellError(error), true);
|
|
159
|
+
});
|
|
160
|
+
await context.test('returns false for null', () => {
|
|
161
|
+
strictEqual(isExecuteShellError(null), false);
|
|
162
|
+
});
|
|
163
|
+
await context.test('returns false for undefined', () => {
|
|
164
|
+
strictEqual(isExecuteShellError(undefined), false);
|
|
165
|
+
});
|
|
166
|
+
await context.test('returns false for string', () => {
|
|
167
|
+
strictEqual(isExecuteShellError('error'), false);
|
|
168
|
+
});
|
|
169
|
+
await context.test('returns false for number', () => {
|
|
170
|
+
strictEqual(isExecuteShellError(42), false);
|
|
171
|
+
});
|
|
172
|
+
await context.test('returns false for empty object', () => {
|
|
173
|
+
strictEqual(isExecuteShellError({}), false);
|
|
174
|
+
});
|
|
175
|
+
await context.test('returns false for object with wrong property types', () => {
|
|
176
|
+
strictEqual(isExecuteShellError({ cmd: 123 }), false);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
test('currentTimestamp', async (context) => {
|
|
180
|
+
await context.test('returns a bracketed timestamp string', () => {
|
|
181
|
+
const result = currentTimestamp();
|
|
182
|
+
ok(result.startsWith('['));
|
|
183
|
+
ok(result.endsWith(']'));
|
|
184
|
+
});
|
|
185
|
+
await context.test('matches expected timestamp format', () => {
|
|
186
|
+
const result = currentTimestamp();
|
|
187
|
+
const pattern = new RegExp('^\\[\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3} [+-]\\d{2}\\d{2}]$');
|
|
188
|
+
match(result, pattern);
|
|
189
|
+
});
|
|
190
|
+
await context.test('produces different milliseconds on consecutive calls', () => {
|
|
191
|
+
const results = new Set();
|
|
192
|
+
for (let i = 0; i < 10; i += 1) {
|
|
193
|
+
results.add(currentTimestamp());
|
|
194
|
+
}
|
|
195
|
+
ok(results.size >= 1);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
test('detectShell', async (context) => {
|
|
199
|
+
await context.test('returns a non-empty string', () => {
|
|
200
|
+
const result = detectShell();
|
|
201
|
+
strictEqual(typeof result, 'string');
|
|
202
|
+
ok(result.length > 0);
|
|
203
|
+
});
|
|
204
|
+
await context.test('returns a known shell path', () => {
|
|
205
|
+
const result = detectShell();
|
|
206
|
+
const knownShells = [
|
|
207
|
+
'cmd.exe',
|
|
208
|
+
'/bin/zsh',
|
|
209
|
+
'/bin/bash',
|
|
210
|
+
'/bin/ksh',
|
|
211
|
+
'/bin/sh',
|
|
212
|
+
];
|
|
213
|
+
ok(knownShells.includes(result), `Unexpected shell: "${result}"`);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
test('pathExists', async (context) => {
|
|
217
|
+
await context.test('returns true for existing file', async () => {
|
|
218
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'nova-pathExists-'));
|
|
219
|
+
const tempFile = join(tempDir, 'test.txt');
|
|
220
|
+
await writeFile(tempFile, 'test');
|
|
221
|
+
const result = await pathExists(tempFile);
|
|
222
|
+
strictEqual(result, true);
|
|
223
|
+
await rm(tempDir, {
|
|
224
|
+
recursive: true,
|
|
225
|
+
force: true,
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
await context.test('returns true for existing directory', async () => {
|
|
229
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'nova-pathExists-'));
|
|
230
|
+
const result = await pathExists(tempDir);
|
|
231
|
+
strictEqual(result, true);
|
|
232
|
+
await rm(tempDir, {
|
|
233
|
+
recursive: true,
|
|
234
|
+
force: true,
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
await context.test('returns false for non-existent path', async () => {
|
|
238
|
+
const result = await pathExists(join(tmpdir(), 'nova-does-not-exist-xyz'));
|
|
239
|
+
strictEqual(result, false);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
test('executeShell', async (context) => {
|
|
243
|
+
await context.test('runs a simple echo command', async () => {
|
|
244
|
+
const result = await executeShell('echo hello');
|
|
245
|
+
strictEqual(result.code, 0);
|
|
246
|
+
ok(result.textOut.includes('hello'));
|
|
247
|
+
});
|
|
248
|
+
await context.test('returns non-zero code for failing command', async () => {
|
|
249
|
+
const result = await executeShell('nova-nonexistent-command-xyz-12345');
|
|
250
|
+
notStrictEqual(result.code, 0);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
test('isCommandExists', async (context) => {
|
|
254
|
+
await context.test('returns true for an existing command', async () => {
|
|
255
|
+
const result = await isCommandExists('node');
|
|
256
|
+
strictEqual(result, true);
|
|
257
|
+
});
|
|
258
|
+
await context.test('returns false for a non-existent command', async () => {
|
|
259
|
+
const result = await isCommandExists('nova-nonexistent-command-xyz-12345');
|
|
260
|
+
strictEqual(result, false);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
test('isFileIdentical', async (context) => {
|
|
264
|
+
const sandboxRoot = await mkdtemp(join(tmpdir(), `nova-${context.name}-`));
|
|
265
|
+
context.after(async () => {
|
|
266
|
+
await rm(sandboxRoot, {
|
|
267
|
+
recursive: true,
|
|
268
|
+
force: true,
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
await context.test('returns true when string content matches file', async () => {
|
|
272
|
+
const filePath = join(sandboxRoot, 'string-match.txt');
|
|
273
|
+
await writeFile(filePath, 'hello world');
|
|
274
|
+
const result = await isFileIdentical(filePath, 'hello world');
|
|
275
|
+
strictEqual(result, true);
|
|
276
|
+
});
|
|
277
|
+
await context.test('returns false when string content differs from file', async () => {
|
|
278
|
+
const filePath = join(sandboxRoot, 'string-differ.txt');
|
|
279
|
+
await writeFile(filePath, 'hello world');
|
|
280
|
+
const result = await isFileIdentical(filePath, 'goodbye world');
|
|
281
|
+
strictEqual(result, false);
|
|
282
|
+
});
|
|
283
|
+
await context.test('returns true when object content matches JSON file', async () => {
|
|
284
|
+
const filePath = join(sandboxRoot, 'object-match.json');
|
|
285
|
+
const contents = {
|
|
286
|
+
name: 'nova',
|
|
287
|
+
version: '1.0.0',
|
|
288
|
+
};
|
|
289
|
+
const contentsJson = JSON.stringify(contents, null, 2);
|
|
290
|
+
await writeFile(filePath, `${contentsJson}\n`);
|
|
291
|
+
const result = await isFileIdentical(filePath, contents);
|
|
292
|
+
strictEqual(result, true);
|
|
293
|
+
});
|
|
294
|
+
await context.test('returns false when object content differs from JSON file', async () => {
|
|
295
|
+
const filePath = join(sandboxRoot, 'object-differ.json');
|
|
296
|
+
const existingContents = {
|
|
297
|
+
name: 'nova',
|
|
298
|
+
version: '1.0.0',
|
|
299
|
+
};
|
|
300
|
+
const proposedContents = {
|
|
301
|
+
name: 'nova',
|
|
302
|
+
version: '2.0.0',
|
|
303
|
+
};
|
|
304
|
+
const existingJson = JSON.stringify(existingContents, null, 2);
|
|
305
|
+
await writeFile(filePath, `${existingJson}\n`);
|
|
306
|
+
const result = await isFileIdentical(filePath, proposedContents);
|
|
307
|
+
strictEqual(result, false);
|
|
308
|
+
});
|
|
309
|
+
await context.test('returns false when file does not exist', async () => {
|
|
310
|
+
const filePath = join(sandboxRoot, 'does-not-exist.txt');
|
|
311
|
+
const result = await isFileIdentical(filePath, 'content');
|
|
312
|
+
strictEqual(result, false);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
test('renameFileWithDate', async (context) => {
|
|
316
|
+
const sandboxRoot = await mkdtemp(join(tmpdir(), `nova-${context.name}-`));
|
|
317
|
+
context.after(async () => {
|
|
318
|
+
await rm(sandboxRoot, {
|
|
319
|
+
recursive: true,
|
|
320
|
+
force: true,
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
await context.test('renames a file with a date-stamped name', async () => {
|
|
324
|
+
const filePath = join(sandboxRoot, 'rename-test.txt');
|
|
325
|
+
await writeFile(filePath, 'test content');
|
|
326
|
+
const result = await renameFileWithDate(filePath, 'backup', 'txt');
|
|
327
|
+
strictEqual(result, true);
|
|
328
|
+
const originalExists = await pathExists(filePath);
|
|
329
|
+
strictEqual(originalExists, false);
|
|
330
|
+
const files = await readdir(sandboxRoot);
|
|
331
|
+
const renamedFile = files.find((file) => file.startsWith('backup.'));
|
|
332
|
+
ok(renamedFile !== undefined, 'Renamed file should exist');
|
|
333
|
+
ok(new RegExp('^backup\\.\\d{4}-\\d{2}-\\d{2}_\\d{4}\\.txt$').test(renamedFile));
|
|
334
|
+
});
|
|
335
|
+
await context.test('returns false when source file does not exist', async () => {
|
|
336
|
+
const filePath = join(sandboxRoot, 'does-not-exist.txt');
|
|
337
|
+
const result = await renameFileWithDate(filePath, 'backup', 'txt');
|
|
338
|
+
strictEqual(result, false);
|
|
339
|
+
});
|
|
340
|
+
await context.test('increments counter when target file already exists', async () => {
|
|
341
|
+
const subDir = join(sandboxRoot, 'counter-test');
|
|
342
|
+
await mkdir(subDir);
|
|
343
|
+
const filePath = join(subDir, 'original.txt');
|
|
344
|
+
await writeFile(filePath, 'content');
|
|
345
|
+
const now = new Date();
|
|
346
|
+
const timestamp = [
|
|
347
|
+
now.getUTCFullYear(),
|
|
348
|
+
(now.getUTCMonth() + 1).toString().padStart(2, '0'),
|
|
349
|
+
now.getUTCDate().toString().padStart(2, '0'),
|
|
350
|
+
].join('-');
|
|
351
|
+
const existingName = `backup.${timestamp}_0001.txt`;
|
|
352
|
+
await writeFile(join(subDir, existingName), 'existing');
|
|
353
|
+
const result = await renameFileWithDate(filePath, 'backup', 'txt');
|
|
354
|
+
strictEqual(result, true);
|
|
355
|
+
const files = await readdir(subDir);
|
|
356
|
+
const secondFile = files.find((file) => file.includes('_0002'));
|
|
357
|
+
ok(secondFile !== undefined, 'File with incremented counter should exist');
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
test('isProjectRoot', async (context) => {
|
|
361
|
+
const originalCwd = process.cwd();
|
|
362
|
+
const sandboxRoot = await mkdtemp(join(tmpdir(), `nova-${context.name}-`));
|
|
363
|
+
context.after(async () => {
|
|
364
|
+
process.chdir(originalCwd);
|
|
365
|
+
await rm(sandboxRoot, {
|
|
366
|
+
recursive: true,
|
|
367
|
+
force: true,
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
await context.test('returns true when cwd is project root with single package.json', async () => {
|
|
371
|
+
const projectRoot = join(sandboxRoot, 'single');
|
|
372
|
+
await mkdir(projectRoot, { recursive: true });
|
|
373
|
+
await writeFile(join(projectRoot, 'package.json'), '{}\n');
|
|
374
|
+
const realProjectRoot = await realpath(projectRoot);
|
|
375
|
+
process.chdir(realProjectRoot);
|
|
376
|
+
const result = await isProjectRoot(realProjectRoot);
|
|
377
|
+
strictEqual(result, true);
|
|
378
|
+
});
|
|
379
|
+
await context.test('returns false when cwd has no package.json above', async () => {
|
|
380
|
+
const emptyDir = join(sandboxRoot, 'empty');
|
|
381
|
+
await mkdir(emptyDir, { recursive: true });
|
|
382
|
+
const realEmptyDir = await realpath(emptyDir);
|
|
383
|
+
process.chdir(realEmptyDir);
|
|
384
|
+
const result = await isProjectRoot(realEmptyDir);
|
|
385
|
+
strictEqual(result, false);
|
|
386
|
+
});
|
|
387
|
+
await context.test('returns false when multiple package.json files found above', async () => {
|
|
388
|
+
const projectRoot = join(sandboxRoot, 'multi');
|
|
389
|
+
const appRoot = join(projectRoot, 'apps', 'my-app');
|
|
390
|
+
await mkdir(appRoot, { recursive: true });
|
|
391
|
+
await Promise.all([
|
|
392
|
+
writeFile(join(projectRoot, 'package.json'), '{}\n'),
|
|
393
|
+
writeFile(join(appRoot, 'package.json'), '{}\n'),
|
|
394
|
+
]);
|
|
395
|
+
const realAppRoot = await realpath(appRoot);
|
|
396
|
+
process.chdir(realAppRoot);
|
|
397
|
+
const result = await isProjectRoot(realAppRoot);
|
|
398
|
+
strictEqual(result, false);
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
test('loadWorkspaceManifests', async (context) => {
|
|
402
|
+
const sandboxRoot = await mkdtemp(join(tmpdir(), `nova-${context.name}-`));
|
|
403
|
+
context.after(async () => {
|
|
404
|
+
await rm(sandboxRoot, {
|
|
405
|
+
recursive: true,
|
|
406
|
+
force: true,
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
await context.test('loads package.json for configured workspaces', async () => {
|
|
410
|
+
const projectRoot = join(sandboxRoot, 'valid');
|
|
411
|
+
const pkgDir = join(projectRoot, 'packages', 'core');
|
|
412
|
+
await mkdir(pkgDir, { recursive: true });
|
|
413
|
+
const rootPackageJson = JSON.stringify({ name: 'root' }, null, 2);
|
|
414
|
+
const corePackageJson = JSON.stringify({ name: '@test/core' }, null, 2);
|
|
415
|
+
await Promise.all([
|
|
416
|
+
writeFile(join(projectRoot, 'package.json'), rootPackageJson),
|
|
417
|
+
writeFile(join(pkgDir, 'package.json'), corePackageJson),
|
|
418
|
+
]);
|
|
419
|
+
const result = await loadWorkspaceManifests({
|
|
420
|
+
projectRoot,
|
|
421
|
+
workspaces: [
|
|
422
|
+
['./', {
|
|
423
|
+
name: 'root',
|
|
424
|
+
role: 'project',
|
|
425
|
+
policy: 'freezable',
|
|
426
|
+
}],
|
|
427
|
+
['./packages/core', {
|
|
428
|
+
name: '@test/core',
|
|
429
|
+
role: 'package',
|
|
430
|
+
policy: 'distributable',
|
|
431
|
+
}],
|
|
432
|
+
],
|
|
433
|
+
});
|
|
434
|
+
strictEqual(result.length, 2);
|
|
435
|
+
const firstWorkspace = result[0];
|
|
436
|
+
const secondWorkspace = result[1];
|
|
437
|
+
ok(firstWorkspace !== undefined);
|
|
438
|
+
ok(secondWorkspace !== undefined);
|
|
439
|
+
strictEqual(firstWorkspace.manifest.name, 'root');
|
|
440
|
+
strictEqual(firstWorkspace.fileContents['name'], 'root');
|
|
441
|
+
strictEqual(secondWorkspace.manifest.name, '@test/core');
|
|
442
|
+
strictEqual(secondWorkspace.fileContents['name'], '@test/core');
|
|
443
|
+
});
|
|
444
|
+
await context.test('skips workspace with missing package.json', async () => {
|
|
445
|
+
const projectRoot = join(sandboxRoot, 'missing');
|
|
446
|
+
await mkdir(projectRoot, { recursive: true });
|
|
447
|
+
const rootPackageJson = JSON.stringify({ name: 'root' }, null, 2);
|
|
448
|
+
await writeFile(join(projectRoot, 'package.json'), rootPackageJson);
|
|
449
|
+
const result = await loadWorkspaceManifests({
|
|
450
|
+
projectRoot,
|
|
451
|
+
workspaces: [
|
|
452
|
+
['./', {
|
|
453
|
+
name: 'root',
|
|
454
|
+
role: 'project',
|
|
455
|
+
policy: 'freezable',
|
|
456
|
+
}],
|
|
457
|
+
['./packages/missing', {
|
|
458
|
+
name: '@test/missing',
|
|
459
|
+
role: 'package',
|
|
460
|
+
policy: 'distributable',
|
|
461
|
+
}],
|
|
462
|
+
],
|
|
463
|
+
});
|
|
464
|
+
strictEqual(result.length, 1);
|
|
465
|
+
const onlyWorkspace = result[0];
|
|
466
|
+
ok(onlyWorkspace !== undefined);
|
|
467
|
+
strictEqual(onlyWorkspace.manifest.name, 'root');
|
|
468
|
+
});
|
|
469
|
+
await context.test('returns empty array when no workspaces provided', async () => {
|
|
470
|
+
const result = await loadWorkspaceManifests({
|
|
471
|
+
projectRoot: sandboxRoot,
|
|
472
|
+
workspaces: [],
|
|
473
|
+
});
|
|
474
|
+
strictEqual(result.length, 0);
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
test('saveWorkspaceManifest', async (context) => {
|
|
478
|
+
const sandboxRoot = await mkdtemp(join(tmpdir(), `nova-${context.name}-`));
|
|
479
|
+
context.after(async () => {
|
|
480
|
+
await rm(sandboxRoot, {
|
|
481
|
+
recursive: true,
|
|
482
|
+
force: true,
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
await context.test('writes changed file contents', async () => {
|
|
486
|
+
const filePath = join(sandboxRoot, 'write-test', 'package.json');
|
|
487
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
488
|
+
const original = {
|
|
489
|
+
name: 'test',
|
|
490
|
+
version: '1.0.0',
|
|
491
|
+
};
|
|
492
|
+
const originalJson = JSON.stringify(original, null, 2);
|
|
493
|
+
await writeFile(filePath, `${originalJson}\n`, 'utf-8');
|
|
494
|
+
const modified = {
|
|
495
|
+
name: 'test',
|
|
496
|
+
version: '2.0.0',
|
|
497
|
+
};
|
|
498
|
+
await saveWorkspaceManifest({
|
|
499
|
+
manifest: {
|
|
500
|
+
name: 'test',
|
|
501
|
+
role: 'package',
|
|
502
|
+
policy: 'distributable',
|
|
503
|
+
},
|
|
504
|
+
filePath,
|
|
505
|
+
fileContents: modified,
|
|
506
|
+
}, true);
|
|
507
|
+
const written = JSON.parse(await readFile(filePath, 'utf-8'));
|
|
508
|
+
strictEqual(written.version, '2.0.0');
|
|
509
|
+
});
|
|
510
|
+
await context.test('skips writing when file contents are identical', async () => {
|
|
511
|
+
const filePath = join(sandboxRoot, 'skip-test', 'package.json');
|
|
512
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
513
|
+
const contents = {
|
|
514
|
+
name: 'test',
|
|
515
|
+
version: '1.0.0',
|
|
516
|
+
};
|
|
517
|
+
const contentsJson = JSON.stringify(contents, null, 2);
|
|
518
|
+
await writeFile(filePath, `${contentsJson}\n`, 'utf-8');
|
|
519
|
+
const statBefore = await stat(filePath);
|
|
520
|
+
await saveWorkspaceManifest({
|
|
521
|
+
manifest: {
|
|
522
|
+
name: 'test',
|
|
523
|
+
role: 'package',
|
|
524
|
+
policy: 'distributable',
|
|
525
|
+
},
|
|
526
|
+
filePath,
|
|
527
|
+
fileContents: contents,
|
|
528
|
+
}, true);
|
|
529
|
+
const statAfter = await stat(filePath);
|
|
530
|
+
strictEqual(statBefore.mtimeMs, statAfter.mtimeMs);
|
|
531
|
+
});
|
|
532
|
+
await context.test('creates backup when replaceFile is false', async () => {
|
|
533
|
+
const subDir = join(sandboxRoot, 'backup-test');
|
|
534
|
+
await mkdir(subDir, { recursive: true });
|
|
535
|
+
const filePath = join(subDir, 'package.json');
|
|
536
|
+
const original = {
|
|
537
|
+
name: 'test',
|
|
538
|
+
version: '1.0.0',
|
|
539
|
+
};
|
|
540
|
+
const originalJson = JSON.stringify(original, null, 2);
|
|
541
|
+
await writeFile(filePath, `${originalJson}\n`, 'utf-8');
|
|
542
|
+
const modified = {
|
|
543
|
+
name: 'test',
|
|
544
|
+
version: '2.0.0',
|
|
545
|
+
};
|
|
546
|
+
await saveWorkspaceManifest({
|
|
547
|
+
manifest: {
|
|
548
|
+
name: 'test',
|
|
549
|
+
role: 'package',
|
|
550
|
+
policy: 'distributable',
|
|
551
|
+
},
|
|
552
|
+
filePath,
|
|
553
|
+
fileContents: modified,
|
|
554
|
+
}, false);
|
|
555
|
+
const files = await readdir(subDir);
|
|
556
|
+
const backupFile = files.find((file) => file.startsWith('package.') && file !== 'package.json');
|
|
557
|
+
ok(backupFile !== undefined, 'Backup file should exist');
|
|
558
|
+
const written = JSON.parse(await readFile(filePath, 'utf-8'));
|
|
559
|
+
strictEqual(written.version, '2.0.0');
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
test('parseLinuxOsReleaseText', async (context) => {
|
|
563
|
+
await context.test('parses Ubuntu os-release text', () => {
|
|
564
|
+
const text = [
|
|
565
|
+
'NAME="Ubuntu"',
|
|
566
|
+
'VERSION="22.04.3 LTS (Jammy Jellyfish)"',
|
|
567
|
+
'ID=ubuntu',
|
|
568
|
+
'ID_LIKE=debian',
|
|
569
|
+
'PRETTY_NAME="Ubuntu 22.04.3 LTS"',
|
|
570
|
+
'VERSION_ID="22.04"',
|
|
571
|
+
].join('\n');
|
|
572
|
+
const result = parseLinuxOsReleaseText(text);
|
|
573
|
+
strictEqual(result['NAME'], 'Ubuntu');
|
|
574
|
+
strictEqual(result['VERSION'], '22.04.3 LTS (Jammy Jellyfish)');
|
|
575
|
+
strictEqual(result['ID'], 'ubuntu');
|
|
576
|
+
strictEqual(result['ID_LIKE'], 'debian');
|
|
577
|
+
strictEqual(result['VERSION_ID'], '22.04');
|
|
578
|
+
});
|
|
579
|
+
await context.test('parses Alpine os-release text', () => {
|
|
580
|
+
const text = [
|
|
581
|
+
'NAME="Alpine Linux"',
|
|
582
|
+
'ID=alpine',
|
|
583
|
+
'VERSION_ID=3.19.0',
|
|
584
|
+
'PRETTY_NAME="Alpine Linux v3.19"',
|
|
585
|
+
].join('\n');
|
|
586
|
+
const result = parseLinuxOsReleaseText(text);
|
|
587
|
+
strictEqual(result['NAME'], 'Alpine Linux');
|
|
588
|
+
strictEqual(result['ID'], 'alpine');
|
|
589
|
+
strictEqual(result['VERSION_ID'], '3.19.0');
|
|
590
|
+
});
|
|
591
|
+
await context.test('parses Debian os-release text', () => {
|
|
592
|
+
const text = [
|
|
593
|
+
'PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"',
|
|
594
|
+
'NAME="Debian GNU/Linux"',
|
|
595
|
+
'VERSION_ID="12"',
|
|
596
|
+
'ID=debian',
|
|
597
|
+
].join('\n');
|
|
598
|
+
const result = parseLinuxOsReleaseText(text);
|
|
599
|
+
strictEqual(result['NAME'], 'Debian GNU/Linux');
|
|
600
|
+
strictEqual(result['ID'], 'debian');
|
|
601
|
+
strictEqual(result['VERSION_ID'], '12');
|
|
602
|
+
});
|
|
603
|
+
await context.test('skips comment lines', () => {
|
|
604
|
+
const text = [
|
|
605
|
+
'# This is a comment',
|
|
606
|
+
'NAME="Test"',
|
|
607
|
+
'# Another comment',
|
|
608
|
+
'ID=test',
|
|
609
|
+
].join('\n');
|
|
610
|
+
const result = parseLinuxOsReleaseText(text);
|
|
611
|
+
strictEqual(Object.keys(result).length, 2);
|
|
612
|
+
strictEqual(result['NAME'], 'Test');
|
|
613
|
+
strictEqual(result['ID'], 'test');
|
|
614
|
+
});
|
|
615
|
+
await context.test('skips empty lines', () => {
|
|
616
|
+
const text = [
|
|
617
|
+
'',
|
|
618
|
+
'NAME="Test"',
|
|
619
|
+
'',
|
|
620
|
+
'',
|
|
621
|
+
'ID=test',
|
|
622
|
+
'',
|
|
623
|
+
].join('\n');
|
|
624
|
+
const result = parseLinuxOsReleaseText(text);
|
|
625
|
+
strictEqual(Object.keys(result).length, 2);
|
|
626
|
+
});
|
|
627
|
+
await context.test('strips double-quoted values', () => {
|
|
628
|
+
const text = 'NAME="Ubuntu"';
|
|
629
|
+
const result = parseLinuxOsReleaseText(text);
|
|
630
|
+
strictEqual(result['NAME'], 'Ubuntu');
|
|
631
|
+
});
|
|
632
|
+
await context.test('preserves unquoted values', () => {
|
|
633
|
+
const text = 'ID=ubuntu';
|
|
634
|
+
const result = parseLinuxOsReleaseText(text);
|
|
635
|
+
strictEqual(result['ID'], 'ubuntu');
|
|
636
|
+
});
|
|
637
|
+
await context.test('handles values containing equals sign', () => {
|
|
638
|
+
const text = 'BUG_REPORT_URL="https://example.com?a=1&b=2"';
|
|
639
|
+
const result = parseLinuxOsReleaseText(text);
|
|
640
|
+
strictEqual(result['BUG_REPORT_URL'], 'https://example.com?a=1&b=2');
|
|
641
|
+
});
|
|
642
|
+
await context.test('returns empty object for empty string', () => {
|
|
643
|
+
const result = parseLinuxOsReleaseText('');
|
|
644
|
+
deepStrictEqual(result, {});
|
|
645
|
+
});
|
|
646
|
+
await context.test('handles CRLF line endings', () => {
|
|
647
|
+
const text = 'NAME="Test"\r\nID=test\r\nVERSION_ID="1.0"';
|
|
648
|
+
const result = parseLinuxOsReleaseText(text);
|
|
649
|
+
strictEqual(Object.keys(result).length, 3);
|
|
650
|
+
strictEqual(result['NAME'], 'Test');
|
|
651
|
+
strictEqual(result['ID'], 'test');
|
|
652
|
+
strictEqual(result['VERSION_ID'], '1.0');
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
test('parseWindowsRegistryText', async (context) => {
|
|
656
|
+
await context.test('parses REG_SZ values', () => {
|
|
657
|
+
const text = ' ProductName REG_SZ Windows 11 Pro';
|
|
658
|
+
const result = parseWindowsRegistryText(text);
|
|
659
|
+
const productName = result['ProductName'];
|
|
660
|
+
if (productName === undefined) {
|
|
661
|
+
fail('Expected ProductName to be defined');
|
|
662
|
+
}
|
|
663
|
+
strictEqual(productName.type, 'REG_SZ');
|
|
664
|
+
strictEqual(productName.data, 'Windows 11 Pro');
|
|
665
|
+
});
|
|
666
|
+
await context.test('parses REG_DWORD values', () => {
|
|
667
|
+
const text = ' CurrentMajorVersionNumber REG_DWORD 0xa';
|
|
668
|
+
const result = parseWindowsRegistryText(text);
|
|
669
|
+
const currentMajorVersionNumber = result['CurrentMajorVersionNumber'];
|
|
670
|
+
if (currentMajorVersionNumber === undefined) {
|
|
671
|
+
fail('Expected CurrentMajorVersionNumber to be defined');
|
|
672
|
+
}
|
|
673
|
+
strictEqual(currentMajorVersionNumber.type, 'REG_DWORD');
|
|
674
|
+
strictEqual(currentMajorVersionNumber.data, '0xa');
|
|
675
|
+
});
|
|
676
|
+
await context.test('parses mixed registry types', () => {
|
|
677
|
+
const text = [
|
|
678
|
+
'HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion',
|
|
679
|
+
' ProductName REG_SZ Windows 11 Pro',
|
|
680
|
+
' CurrentBuild REG_SZ 22631',
|
|
681
|
+
' CurrentMajorVersionNumber REG_DWORD 0xa',
|
|
682
|
+
].join('\n');
|
|
683
|
+
const result = parseWindowsRegistryText(text);
|
|
684
|
+
strictEqual(Object.keys(result).length, 3);
|
|
685
|
+
const productName = result['ProductName'];
|
|
686
|
+
const currentBuild = result['CurrentBuild'];
|
|
687
|
+
const currentMajorVersionNumber = result['CurrentMajorVersionNumber'];
|
|
688
|
+
if (productName === undefined) {
|
|
689
|
+
fail('Expected ProductName to be defined');
|
|
690
|
+
}
|
|
691
|
+
if (currentBuild === undefined) {
|
|
692
|
+
fail('Expected CurrentBuild to be defined');
|
|
693
|
+
}
|
|
694
|
+
if (currentMajorVersionNumber === undefined) {
|
|
695
|
+
fail('Expected CurrentMajorVersionNumber to be defined');
|
|
696
|
+
}
|
|
697
|
+
strictEqual(productName.type, 'REG_SZ');
|
|
698
|
+
strictEqual(currentBuild.type, 'REG_SZ');
|
|
699
|
+
strictEqual(currentMajorVersionNumber.type, 'REG_DWORD');
|
|
700
|
+
});
|
|
701
|
+
await context.test('returns empty object for empty string', () => {
|
|
702
|
+
const result = parseWindowsRegistryText('');
|
|
703
|
+
deepStrictEqual(result, {});
|
|
704
|
+
});
|
|
705
|
+
await context.test('skips non-matching lines', () => {
|
|
706
|
+
const text = [
|
|
707
|
+
'HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion',
|
|
708
|
+
'',
|
|
709
|
+
' ProductName REG_SZ Windows 11 Pro',
|
|
710
|
+
].join('\n');
|
|
711
|
+
const result = parseWindowsRegistryText(text);
|
|
712
|
+
strictEqual(Object.keys(result).length, 1);
|
|
713
|
+
const productName = result['ProductName'];
|
|
714
|
+
if (productName === undefined) {
|
|
715
|
+
fail('Expected ProductName to be defined');
|
|
716
|
+
}
|
|
717
|
+
strictEqual(productName.data, 'Windows 11 Pro');
|
|
718
|
+
});
|
|
719
|
+
await context.test('trims trailing whitespace from data', () => {
|
|
720
|
+
const text = ' ProductName REG_SZ Windows 11 Pro ';
|
|
721
|
+
const result = parseWindowsRegistryText(text);
|
|
722
|
+
const productName = result['ProductName'];
|
|
723
|
+
if (productName === undefined) {
|
|
724
|
+
fail('Expected ProductName to be defined');
|
|
725
|
+
}
|
|
726
|
+
strictEqual(productName.data, 'Windows 11 Pro');
|
|
727
|
+
});
|
|
728
|
+
await context.test('handles LF line endings', () => {
|
|
729
|
+
const text = ' ProductName REG_SZ Windows 11\n CurrentBuild REG_SZ 22631';
|
|
730
|
+
const result = parseWindowsRegistryText(text);
|
|
731
|
+
strictEqual(Object.keys(result).length, 2);
|
|
732
|
+
const productName = result['ProductName'];
|
|
733
|
+
const currentBuild = result['CurrentBuild'];
|
|
734
|
+
if (productName === undefined) {
|
|
735
|
+
fail('Expected ProductName to be defined');
|
|
736
|
+
}
|
|
737
|
+
if (currentBuild === undefined) {
|
|
738
|
+
fail('Expected CurrentBuild to be defined');
|
|
739
|
+
}
|
|
740
|
+
strictEqual(productName.data, 'Windows 11');
|
|
741
|
+
strictEqual(currentBuild.data, '22631');
|
|
82
742
|
});
|
|
83
743
|
});
|
|
84
744
|
//# sourceMappingURL=utility.test.js.map
|