@gefyra/diffyr6-cli 1.0.0 → 1.0.1

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/src/utils/fs.js CHANGED
@@ -1,38 +1,38 @@
1
- import fs from 'fs';
2
- import fsp from 'fs/promises';
3
-
4
- /**
5
- * Checks if a path exists (file or directory)
6
- */
7
- export async function pathExists(targetPath) {
8
- try {
9
- await fsp.access(targetPath, fs.constants.F_OK);
10
- return true;
11
- } catch {
12
- return false;
13
- }
14
- }
15
-
16
- /**
17
- * Checks if a file exists and is actually a file
18
- */
19
- export async function fileExists(targetPath) {
20
- try {
21
- const stat = await fsp.stat(targetPath);
22
- return stat.isFile();
23
- } catch {
24
- return false;
25
- }
26
- }
27
-
28
- /**
29
- * Checks if a directory exists and is actually a directory
30
- */
31
- export async function directoryExists(targetPath) {
32
- try {
33
- const stat = await fsp.stat(targetPath);
34
- return stat.isDirectory();
35
- } catch {
36
- return false;
37
- }
38
- }
1
+ import fs from 'fs';
2
+ import fsp from 'fs/promises';
3
+
4
+ /**
5
+ * Checks if a path exists (file or directory)
6
+ */
7
+ export async function pathExists(targetPath) {
8
+ try {
9
+ await fsp.access(targetPath, fs.constants.F_OK);
10
+ return true;
11
+ } catch {
12
+ return false;
13
+ }
14
+ }
15
+
16
+ /**
17
+ * Checks if a file exists and is actually a file
18
+ */
19
+ export async function fileExists(targetPath) {
20
+ try {
21
+ const stat = await fsp.stat(targetPath);
22
+ return stat.isFile();
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Checks if a directory exists and is actually a directory
30
+ */
31
+ export async function directoryExists(targetPath) {
32
+ try {
33
+ const stat = await fsp.stat(targetPath);
34
+ return stat.isDirectory();
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
package/src/utils/html.js CHANGED
@@ -1,28 +1,28 @@
1
- /**
2
- * Strips HTML tags from a string
3
- */
4
- export function stripHtml(html) {
5
- return html.replace(/<[^>]*>/g, '');
6
- }
7
-
8
- /**
9
- * Normalizes whitespace in a string
10
- */
11
- export function normalizeWhitespace(text) {
12
- return text.replace(/\s+/g, ' ').trim();
13
- }
14
-
15
- /**
16
- * Decodes HTML entities
17
- */
18
- export function decodeEntities(text) {
19
- const entities = {
20
- '&amp;': '&',
21
- '&lt;': '<',
22
- '&gt;': '>',
23
- '&quot;': '"',
24
- '&#39;': "'",
25
- '&apos;': "'",
26
- };
27
- return text.replace(/&[#a-z0-9]+;/gi, (match) => entities[match.toLowerCase()] || match);
28
- }
1
+ /**
2
+ * Strips HTML tags from a string
3
+ */
4
+ export function stripHtml(html) {
5
+ return html.replace(/<[^>]*>/g, '');
6
+ }
7
+
8
+ /**
9
+ * Normalizes whitespace in a string
10
+ */
11
+ export function normalizeWhitespace(text) {
12
+ return text.replace(/\s+/g, ' ').trim();
13
+ }
14
+
15
+ /**
16
+ * Decodes HTML entities
17
+ */
18
+ export function decodeEntities(text) {
19
+ const entities = {
20
+ '&amp;': '&',
21
+ '&lt;': '<',
22
+ '&gt;': '>',
23
+ '&quot;': '"',
24
+ '&#39;': "'",
25
+ '&apos;': "'",
26
+ };
27
+ return text.replace(/&[#a-z0-9]+;/gi, (match) => entities[match.toLowerCase()] || match);
28
+ }
@@ -1,101 +1,101 @@
1
- import { spawn } from 'child_process';
2
-
3
- /**
4
- * Spawns a process and collects or streams output
5
- */
6
- export function spawnProcess(command, args, cwd, options = {}) {
7
- const { rejectOnNonZero = false, stream = false, stdio } = options;
8
- return new Promise((resolve, reject) => {
9
- const spawnOptions = {
10
- cwd,
11
- shell: process.platform === 'win32',
12
- env: process.env,
13
- };
14
- if (stdio) {
15
- spawnOptions.stdio = stdio;
16
- } else if (stream) {
17
- spawnOptions.stdio = 'inherit';
18
- }
19
-
20
- const child = spawn(command, args, spawnOptions);
21
-
22
- let stdout = '';
23
- let stderr = '';
24
-
25
- const collectOutput = !spawnOptions.stdio;
26
- if (collectOutput && child.stdout) {
27
- child.stdout.on('data', (data) => {
28
- stdout += data.toString();
29
- });
30
- }
31
- if (collectOutput && child.stderr) {
32
- child.stderr.on('data', (data) => {
33
- stderr += data.toString();
34
- });
35
- }
36
- child.on('error', (error) => {
37
- reject(
38
- new Error(`${command} could not be started: ${error.message}`)
39
- );
40
- });
41
- child.on('close', (code) => {
42
- const exitCode = code ?? 0;
43
- if (rejectOnNonZero && exitCode !== 0) {
44
- const msg = collectOutput
45
- ? `${command} failed (exit code ${exitCode}). Details:\n${stdout}\n${stderr}`
46
- : `${command} failed (exit code ${exitCode}).`;
47
- reject(new Error(msg));
48
- return;
49
- }
50
- resolve({ stdout, stderr, exitCode });
51
- });
52
- });
53
- }
54
-
55
- /**
56
- * Creates a simple terminal spinner with configurable frames
57
- */
58
- export function createAnimator(label, options = {}) {
59
- const frames =
60
- options.frames ||
61
- [
62
- '🐟 ~~~~~',
63
- ' 🐟 ~~~~~',
64
- ' 🐟 ~~~~~',
65
- ' 🐟 ~~~~~',
66
- ' 🐟 ~~~~~',
67
- ' 🐟 ~~~~~',
68
- ' 🐟 ~~~~~',
69
- ' 🐟 ~~~~~',
70
- ];
71
- const interval = options.interval || 150;
72
- let index = 0;
73
- let timer = null;
74
-
75
- function render() {
76
- const frame = frames[index];
77
- index = (index + 1) % frames.length;
78
- const text = `${frame} ${label}`;
79
- process.stdout.write(`\r${text}`);
80
- }
81
-
82
- return {
83
- start() {
84
- if (timer) {
85
- return;
86
- }
87
- render();
88
- timer = setInterval(render, interval);
89
- },
90
- stop() {
91
- if (!timer) {
92
- return;
93
- }
94
- clearInterval(timer);
95
- timer = null;
96
- process.stdout.write('\r');
97
- const blank = ' '.repeat(process.stdout.columns || 40);
98
- process.stdout.write(`${blank}\r`);
99
- },
100
- };
101
- }
1
+ import { spawn } from 'child_process';
2
+
3
+ /**
4
+ * Spawns a process and collects or streams output
5
+ */
6
+ export function spawnProcess(command, args, cwd, options = {}) {
7
+ const { rejectOnNonZero = false, stream = false, stdio } = options;
8
+ return new Promise((resolve, reject) => {
9
+ const spawnOptions = {
10
+ cwd,
11
+ shell: process.platform === 'win32',
12
+ env: process.env,
13
+ };
14
+ if (stdio) {
15
+ spawnOptions.stdio = stdio;
16
+ } else if (stream) {
17
+ spawnOptions.stdio = 'inherit';
18
+ }
19
+
20
+ const child = spawn(command, args, spawnOptions);
21
+
22
+ let stdout = '';
23
+ let stderr = '';
24
+
25
+ const collectOutput = !spawnOptions.stdio;
26
+ if (collectOutput && child.stdout) {
27
+ child.stdout.on('data', (data) => {
28
+ stdout += data.toString();
29
+ });
30
+ }
31
+ if (collectOutput && child.stderr) {
32
+ child.stderr.on('data', (data) => {
33
+ stderr += data.toString();
34
+ });
35
+ }
36
+ child.on('error', (error) => {
37
+ reject(
38
+ new Error(`${command} could not be started: ${error.message}`)
39
+ );
40
+ });
41
+ child.on('close', (code) => {
42
+ const exitCode = code ?? 0;
43
+ if (rejectOnNonZero && exitCode !== 0) {
44
+ const msg = collectOutput
45
+ ? `${command} failed (exit code ${exitCode}). Details:\n${stdout}\n${stderr}`
46
+ : `${command} failed (exit code ${exitCode}).`;
47
+ reject(new Error(msg));
48
+ return;
49
+ }
50
+ resolve({ stdout, stderr, exitCode });
51
+ });
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Creates a simple terminal spinner with configurable frames
57
+ */
58
+ export function createAnimator(label, options = {}) {
59
+ const frames =
60
+ options.frames ||
61
+ [
62
+ '🐟 ~~~~~',
63
+ ' 🐟 ~~~~~',
64
+ ' 🐟 ~~~~~',
65
+ ' 🐟 ~~~~~',
66
+ ' 🐟 ~~~~~',
67
+ ' 🐟 ~~~~~',
68
+ ' 🐟 ~~~~~',
69
+ ' 🐟 ~~~~~',
70
+ ];
71
+ const interval = options.interval || 150;
72
+ let index = 0;
73
+ let timer = null;
74
+
75
+ function render() {
76
+ const frame = frames[index];
77
+ index = (index + 1) % frames.length;
78
+ const text = `${frame} ${label}`;
79
+ process.stdout.write(`\r${text}`);
80
+ }
81
+
82
+ return {
83
+ start() {
84
+ if (timer) {
85
+ return;
86
+ }
87
+ render();
88
+ timer = setInterval(render, interval);
89
+ },
90
+ stop() {
91
+ if (!timer) {
92
+ return;
93
+ }
94
+ clearInterval(timer);
95
+ timer = null;
96
+ process.stdout.write('\r');
97
+ const blank = ' '.repeat(process.stdout.columns || 40);
98
+ process.stdout.write(`${blank}\r`);
99
+ },
100
+ };
101
+ }
@@ -1,135 +1,135 @@
1
- import fsp from 'fs/promises';
2
- import path from 'path';
3
- import { fileURLToPath } from 'url';
4
-
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = path.dirname(__filename);
7
-
8
- /**
9
- * Identifies R4 profiles that are based on resource types removed in R6
10
- * @param {string} r4Dir - R4 resources directory
11
- * @returns {Promise<Array<{profile: string, resource: string}>>}
12
- */
13
- export async function findRemovedResources(r4Dir) {
14
- // Load list of resources removed in R6
15
- const removedResourceTypes = await loadRemovedResourceTypes();
16
- const removedSet = new Set(removedResourceTypes);
17
-
18
- // Read R4 profiles
19
- const r4Profiles = await readProfileResources(r4Dir);
20
-
21
- // Filter profiles based on removed resource types
22
- const removed = [];
23
- for (const { profile, resource } of r4Profiles) {
24
- if (removedSet.has(resource)) {
25
- removed.push({ profile, resource });
26
- }
27
- }
28
-
29
- return removed;
30
- }
31
-
32
- /**
33
- * Loads the list of resource types that were removed in R6
34
- */
35
- async function loadRemovedResourceTypes() {
36
- const configPath = path.resolve(__dirname, '..', '..', 'config', 'resources-r4-not-in-r6.json');
37
- const content = await fsp.readFile(configPath, 'utf8');
38
- const data = JSON.parse(content);
39
- return data.resources || [];
40
- }
41
-
42
- /**
43
- * Reads all StructureDefinition profiles from a directory
44
- */
45
- async function readProfileResources(baseDir) {
46
- const resourcesDir = path.join(baseDir, 'fsh-generated', 'resources');
47
- const stat = await fsp.stat(resourcesDir).catch(() => null);
48
- if (!stat || !stat.isDirectory()) {
49
- return [];
50
- }
51
-
52
- const files = await collectJsonFiles(resourcesDir);
53
- const results = [];
54
- const seen = new Set();
55
-
56
- for (const filePath of files) {
57
- const raw = await fsp.readFile(filePath, 'utf8').catch(() => '');
58
- if (!raw) {
59
- continue;
60
- }
61
-
62
- let data;
63
- try {
64
- data = JSON.parse(raw);
65
- } catch {
66
- continue;
67
- }
68
-
69
- if (!data || data.resourceType !== 'StructureDefinition') {
70
- continue;
71
- }
72
-
73
- const resource = resolveProfileResourceType(data);
74
- const profile =
75
- data.title ||
76
- data.name ||
77
- data.id ||
78
- extractLastSegment(data.url) ||
79
- path.basename(filePath, '.json');
80
-
81
- if (!resource || !profile) {
82
- continue;
83
- }
84
-
85
- const key = `${profile}::${resource}`.toLowerCase();
86
- if (seen.has(key)) {
87
- continue;
88
- }
89
-
90
- seen.add(key);
91
- results.push({ profile, resource });
92
- }
93
-
94
- return results;
95
- }
96
-
97
- async function collectJsonFiles(dir) {
98
- const results = [];
99
- const entries = await fsp.readdir(dir, { withFileTypes: true }).catch(() => []);
100
- for (const entry of entries) {
101
- const entryPath = path.join(dir, entry.name);
102
- if (entry.isDirectory()) {
103
- const nested = await collectJsonFiles(entryPath);
104
- results.push(...nested);
105
- continue;
106
- }
107
- if (entry.isFile() && entry.name.toLowerCase().endsWith('.json')) {
108
- results.push(entryPath);
109
- }
110
- }
111
- return results;
112
- }
113
-
114
- function resolveProfileResourceType(data) {
115
- if (!data || typeof data !== 'object') {
116
- return '';
117
- }
118
- if (typeof data.type === 'string' && data.type) {
119
- return data.type;
120
- }
121
- if (typeof data.baseDefinition === 'string' && data.baseDefinition) {
122
- return extractLastSegment(data.baseDefinition);
123
- }
124
- return '';
125
- }
126
-
127
- function extractLastSegment(url) {
128
- if (!url) {
129
- return '';
130
- }
131
- const hashIndex = url.lastIndexOf('#');
132
- const slashIndex = url.lastIndexOf('/');
133
- const index = Math.max(hashIndex, slashIndex);
134
- return index >= 0 ? url.slice(index + 1) : url;
135
- }
1
+ import fsp from 'fs/promises';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+
8
+ /**
9
+ * Identifies R4 profiles that are based on resource types removed in R6
10
+ * @param {string} r4Dir - R4 resources directory
11
+ * @returns {Promise<Array<{profile: string, resource: string}>>}
12
+ */
13
+ export async function findRemovedResources(r4Dir) {
14
+ // Load list of resources removed in R6
15
+ const removedResourceTypes = await loadRemovedResourceTypes();
16
+ const removedSet = new Set(removedResourceTypes);
17
+
18
+ // Read R4 profiles
19
+ const r4Profiles = await readProfileResources(r4Dir);
20
+
21
+ // Filter profiles based on removed resource types
22
+ const removed = [];
23
+ for (const { profile, resource } of r4Profiles) {
24
+ if (removedSet.has(resource)) {
25
+ removed.push({ profile, resource });
26
+ }
27
+ }
28
+
29
+ return removed;
30
+ }
31
+
32
+ /**
33
+ * Loads the list of resource types that were removed in R6
34
+ */
35
+ async function loadRemovedResourceTypes() {
36
+ const configPath = path.resolve(__dirname, '..', '..', 'config', 'resources-r4-not-in-r6.json');
37
+ const content = await fsp.readFile(configPath, 'utf8');
38
+ const data = JSON.parse(content);
39
+ return data.resources || [];
40
+ }
41
+
42
+ /**
43
+ * Reads all StructureDefinition profiles from a directory
44
+ */
45
+ async function readProfileResources(baseDir) {
46
+ const resourcesDir = path.join(baseDir, 'fsh-generated', 'resources');
47
+ const stat = await fsp.stat(resourcesDir).catch(() => null);
48
+ if (!stat || !stat.isDirectory()) {
49
+ return [];
50
+ }
51
+
52
+ const files = await collectJsonFiles(resourcesDir);
53
+ const results = [];
54
+ const seen = new Set();
55
+
56
+ for (const filePath of files) {
57
+ const raw = await fsp.readFile(filePath, 'utf8').catch(() => '');
58
+ if (!raw) {
59
+ continue;
60
+ }
61
+
62
+ let data;
63
+ try {
64
+ data = JSON.parse(raw);
65
+ } catch {
66
+ continue;
67
+ }
68
+
69
+ if (!data || data.resourceType !== 'StructureDefinition') {
70
+ continue;
71
+ }
72
+
73
+ const resource = resolveProfileResourceType(data);
74
+ const profile =
75
+ data.title ||
76
+ data.name ||
77
+ data.id ||
78
+ extractLastSegment(data.url) ||
79
+ path.basename(filePath, '.json');
80
+
81
+ if (!resource || !profile) {
82
+ continue;
83
+ }
84
+
85
+ const key = `${profile}::${resource}`.toLowerCase();
86
+ if (seen.has(key)) {
87
+ continue;
88
+ }
89
+
90
+ seen.add(key);
91
+ results.push({ profile, resource });
92
+ }
93
+
94
+ return results;
95
+ }
96
+
97
+ async function collectJsonFiles(dir) {
98
+ const results = [];
99
+ const entries = await fsp.readdir(dir, { withFileTypes: true }).catch(() => []);
100
+ for (const entry of entries) {
101
+ const entryPath = path.join(dir, entry.name);
102
+ if (entry.isDirectory()) {
103
+ const nested = await collectJsonFiles(entryPath);
104
+ results.push(...nested);
105
+ continue;
106
+ }
107
+ if (entry.isFile() && entry.name.toLowerCase().endsWith('.json')) {
108
+ results.push(entryPath);
109
+ }
110
+ }
111
+ return results;
112
+ }
113
+
114
+ function resolveProfileResourceType(data) {
115
+ if (!data || typeof data !== 'object') {
116
+ return '';
117
+ }
118
+ if (typeof data.type === 'string' && data.type) {
119
+ return data.type;
120
+ }
121
+ if (typeof data.baseDefinition === 'string' && data.baseDefinition) {
122
+ return extractLastSegment(data.baseDefinition);
123
+ }
124
+ return '';
125
+ }
126
+
127
+ function extractLastSegment(url) {
128
+ if (!url) {
129
+ return '';
130
+ }
131
+ const hashIndex = url.lastIndexOf('#');
132
+ const slashIndex = url.lastIndexOf('/');
133
+ const index = Math.max(hashIndex, slashIndex);
134
+ return index >= 0 ? url.slice(index + 1) : url;
135
+ }