@fgv/ts-res-cli 5.0.0-3 → 5.0.0-30
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/README.md +50 -2
- package/eslint.config.js +16 -0
- package/lib/cli.d.ts +4 -0
- package/lib/cli.js +68 -2
- package/lib/compiler.d.ts +5 -0
- package/lib/compiler.js +34 -0
- package/lib/options.d.ts +1 -1
- package/package.json +18 -17
- package/.eslintrc.js +0 -9
- package/.rush/temp/chunked-rush-logs/ts-res-cli.build.chunks.jsonl +0 -6
- package/.rush/temp/operation/build/log-chunks.jsonl +0 -6
- package/.rush/temp/operation/build/state.json +0 -3
- package/.rush/temp/shrinkwrap-deps.json +0 -649
- package/config/jest.config.json +0 -9
- package/config/rig.json +0 -16
- package/lib/cli.d.ts.map +0 -1
- package/lib/cli.js.map +0 -1
- package/lib/compiler.d.ts.map +0 -1
- package/lib/compiler.js.map +0 -1
- package/lib/defaultConfiguration.d.ts.map +0 -1
- package/lib/defaultConfiguration.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/options.d.ts.map +0 -1
- package/lib/options.js.map +0 -1
- package/src/cli.ts +0 -607
- package/src/compiler.ts +0 -504
- package/src/defaultConfiguration.ts +0 -78
- package/src/index.ts +0 -25
- package/src/options.ts +0 -101
- package/test/integration/cli-configuration.test.ts +0 -419
- package/test/integration/cli-context-filter-working.test.ts +0 -341
- package/test/integration/cli-integration.test.ts +0 -533
- package/test/unit/cli.test.ts +0 -413
- package/test/unit/compiler.test.ts +0 -1084
- package/tsconfig.json +0 -8
|
@@ -1,419 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2025 Erik Fortune
|
|
3
|
-
*
|
|
4
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
-
* in the Software without restriction, including without limitation the rights
|
|
7
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
-
* furnished to do so, subject to the following conditions:
|
|
10
|
-
*
|
|
11
|
-
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
-
* copies or substantial portions of the Software.
|
|
13
|
-
*
|
|
14
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
-
* SOFTWARE.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import '@fgv/ts-utils-jest';
|
|
24
|
-
import { promises as fs } from 'fs';
|
|
25
|
-
import * as path from 'path';
|
|
26
|
-
import { spawn } from 'child_process';
|
|
27
|
-
import * as TsRes from '@fgv/ts-res';
|
|
28
|
-
|
|
29
|
-
describe('CLI Configuration Tests', () => {
|
|
30
|
-
let tempDir: string;
|
|
31
|
-
let cliBin: string;
|
|
32
|
-
let customConfigTestDataDir: string;
|
|
33
|
-
|
|
34
|
-
beforeAll(async () => {
|
|
35
|
-
cliBin = path.resolve(__dirname, '../../bin/ts-res-compile.js');
|
|
36
|
-
customConfigTestDataDir = path.resolve(__dirname, '../../../../data/test/ts-res/custom-config');
|
|
37
|
-
|
|
38
|
-
// Verify test data directory exists
|
|
39
|
-
try {
|
|
40
|
-
await fs.access(customConfigTestDataDir);
|
|
41
|
-
} catch (error) {
|
|
42
|
-
throw new Error(`Custom config test data directory not found at: ${customConfigTestDataDir}`);
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
beforeEach(async () => {
|
|
47
|
-
tempDir = await fs.mkdtemp(path.join(__dirname, '../../temp/test-temp-config-'));
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
afterEach(async () => {
|
|
51
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Helper function to run CLI commands
|
|
56
|
-
*/
|
|
57
|
-
async function runCli(
|
|
58
|
-
args: string[],
|
|
59
|
-
debug = false
|
|
60
|
-
): Promise<{ exitCode: number; stdout: string; stderr: string }> {
|
|
61
|
-
return new Promise((resolve) => {
|
|
62
|
-
if (debug) {
|
|
63
|
-
console.log('Running CLI command:', 'node', [cliBin, ...args]);
|
|
64
|
-
}
|
|
65
|
-
const child = spawn('node', [cliBin, ...args], {
|
|
66
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
let stdout = '';
|
|
70
|
-
let stderr = '';
|
|
71
|
-
|
|
72
|
-
child.stdout.on('data', (data) => {
|
|
73
|
-
stdout += data.toString();
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
child.stderr.on('data', (data) => {
|
|
77
|
-
stderr += data.toString();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
child.on('close', (exitCode) => {
|
|
81
|
-
resolve({ exitCode: exitCode || 0, stdout, stderr });
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
describe('Configuration compatibility tests', () => {
|
|
87
|
-
test('fails to compile custom-config resources with default configuration', async () => {
|
|
88
|
-
const resourcesDir = path.join(customConfigTestDataDir, 'src/resources');
|
|
89
|
-
const outputFile = path.join(tempDir, 'default-config-output.json');
|
|
90
|
-
|
|
91
|
-
const result = await runCli([
|
|
92
|
-
'compile',
|
|
93
|
-
'--input',
|
|
94
|
-
resourcesDir,
|
|
95
|
-
'--output',
|
|
96
|
-
outputFile,
|
|
97
|
-
'--format',
|
|
98
|
-
'source'
|
|
99
|
-
]);
|
|
100
|
-
|
|
101
|
-
expect(result.exitCode).toBe(1);
|
|
102
|
-
expect(result.stderr).toMatch(/Error|Failed|Invalid/i);
|
|
103
|
-
|
|
104
|
-
// The failure should be due to unknown qualifiers like 'territory', 'role', etc.
|
|
105
|
-
// that exist in the custom-config resources but not in the default configuration
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test('succeeds in compiling custom-config resources with correct configuration', async () => {
|
|
109
|
-
const resourcesDir = path.join(customConfigTestDataDir, 'resources');
|
|
110
|
-
const configFile = path.join(customConfigTestDataDir, 'resources-config.json');
|
|
111
|
-
const outputFile = path.join(tempDir, 'custom-config-output.json');
|
|
112
|
-
|
|
113
|
-
const result = await runCli([
|
|
114
|
-
'compile',
|
|
115
|
-
'--input',
|
|
116
|
-
resourcesDir,
|
|
117
|
-
'--output',
|
|
118
|
-
outputFile,
|
|
119
|
-
'--config',
|
|
120
|
-
configFile,
|
|
121
|
-
'--format',
|
|
122
|
-
'source'
|
|
123
|
-
]);
|
|
124
|
-
|
|
125
|
-
if (process.env.DEBUG_CLI_TESTS === 'true') {
|
|
126
|
-
console.log('CLI stdout:', result.stdout);
|
|
127
|
-
console.log('CLI stderr:', result.stderr);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
expect(result.exitCode).toBe(0);
|
|
131
|
-
expect(result.stdout).toMatch(/Successfully compiled resources/);
|
|
132
|
-
|
|
133
|
-
// Verify output file exists and has expected structure
|
|
134
|
-
const outputContent = await fs.readFile(outputFile, 'utf-8');
|
|
135
|
-
const output = JSON.parse(outputContent);
|
|
136
|
-
|
|
137
|
-
expect(output).toHaveProperty('resources');
|
|
138
|
-
|
|
139
|
-
expect(TsRes.ResourceJson.Convert.resourceCollectionDecl.convert(output.resources)).toSucceedAndSatisfy(
|
|
140
|
-
(resources) => {
|
|
141
|
-
expect(resources?.resources?.length).toBeGreaterThan(0);
|
|
142
|
-
|
|
143
|
-
// Verify we can compile resources with custom qualifiers
|
|
144
|
-
const hasCustomQualifiers = resources?.resources?.some((resource) =>
|
|
145
|
-
resource.candidates?.some((candidate) =>
|
|
146
|
-
candidate.conditions?.some(
|
|
147
|
-
(condition) =>
|
|
148
|
-
condition.qualifierName === 'territory' ||
|
|
149
|
-
condition.qualifierName === 'role' ||
|
|
150
|
-
condition.qualifierName === 'platform'
|
|
151
|
-
)
|
|
152
|
-
)
|
|
153
|
-
);
|
|
154
|
-
expect(hasCustomQualifiers).toBe(true);
|
|
155
|
-
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test('test consistent output by building same input twice', async () => {
|
|
162
|
-
const resourcesDir = path.join(customConfigTestDataDir, 'resources');
|
|
163
|
-
const configFile = path.join(customConfigTestDataDir, 'resources-config.json');
|
|
164
|
-
const firstOutputFile = path.join(tempDir, 'first-build.json');
|
|
165
|
-
const secondOutputFile = path.join(tempDir, 'second-build.json');
|
|
166
|
-
|
|
167
|
-
// First compilation
|
|
168
|
-
const firstResult = await runCli([
|
|
169
|
-
'compile',
|
|
170
|
-
'--input',
|
|
171
|
-
resourcesDir,
|
|
172
|
-
'--output',
|
|
173
|
-
firstOutputFile,
|
|
174
|
-
'--config',
|
|
175
|
-
configFile,
|
|
176
|
-
'--format',
|
|
177
|
-
'source'
|
|
178
|
-
]);
|
|
179
|
-
|
|
180
|
-
expect(firstResult.exitCode).toBe(0);
|
|
181
|
-
|
|
182
|
-
// Second compilation of the same input
|
|
183
|
-
const secondResult = await runCli([
|
|
184
|
-
'compile',
|
|
185
|
-
'--input',
|
|
186
|
-
resourcesDir,
|
|
187
|
-
'--output',
|
|
188
|
-
secondOutputFile,
|
|
189
|
-
'--config',
|
|
190
|
-
configFile,
|
|
191
|
-
'--format',
|
|
192
|
-
'source'
|
|
193
|
-
]);
|
|
194
|
-
|
|
195
|
-
expect(secondResult.exitCode).toBe(0);
|
|
196
|
-
expect(secondResult.stdout).toMatch(/Successfully compiled resources/);
|
|
197
|
-
|
|
198
|
-
// Compare the outputs - they should be identical for consistency
|
|
199
|
-
const firstOutput = await fs.readFile(firstOutputFile, 'utf-8');
|
|
200
|
-
const secondOutput = await fs.readFile(secondOutputFile, 'utf-8');
|
|
201
|
-
|
|
202
|
-
const firstParsed = JSON.parse(firstOutput);
|
|
203
|
-
const secondParsed = JSON.parse(secondOutput);
|
|
204
|
-
|
|
205
|
-
expect(secondParsed).toEqual(firstParsed);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test('validates configuration file format', async () => {
|
|
209
|
-
const resourcesDir = path.join(customConfigTestDataDir, 'src/resources');
|
|
210
|
-
const invalidConfigFile = path.join(tempDir, 'invalid-config.json');
|
|
211
|
-
await fs.writeFile(
|
|
212
|
-
invalidConfigFile,
|
|
213
|
-
JSON.stringify({
|
|
214
|
-
// Missing required fields
|
|
215
|
-
name: 'Invalid Config'
|
|
216
|
-
})
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
const outputFile = path.join(tempDir, 'invalid-config-output.json');
|
|
220
|
-
|
|
221
|
-
const result = await runCli([
|
|
222
|
-
'compile',
|
|
223
|
-
'--input',
|
|
224
|
-
resourcesDir,
|
|
225
|
-
'--output',
|
|
226
|
-
outputFile,
|
|
227
|
-
'--config',
|
|
228
|
-
invalidConfigFile
|
|
229
|
-
]);
|
|
230
|
-
|
|
231
|
-
expect(result.exitCode).toBe(1);
|
|
232
|
-
expect(result.stderr).toMatch(/Error|Invalid|Failed/i);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
test('handles missing configuration file gracefully', async () => {
|
|
236
|
-
const resourcesDir = path.join(customConfigTestDataDir, 'src/resources');
|
|
237
|
-
const missingConfigFile = path.join(tempDir, 'missing-config.json');
|
|
238
|
-
const outputFile = path.join(tempDir, 'missing-config-output.json');
|
|
239
|
-
|
|
240
|
-
const result = await runCli([
|
|
241
|
-
'compile',
|
|
242
|
-
'--input',
|
|
243
|
-
resourcesDir,
|
|
244
|
-
'--output',
|
|
245
|
-
outputFile,
|
|
246
|
-
'--config',
|
|
247
|
-
missingConfigFile
|
|
248
|
-
]);
|
|
249
|
-
|
|
250
|
-
expect(result.exitCode).toBe(1);
|
|
251
|
-
expect(result.stderr).toMatch(/Error|File not found|ENOENT/i);
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
describe('Configuration with context filtering', () => {
|
|
256
|
-
test('compiles with territory context using custom configuration', async () => {
|
|
257
|
-
const resourcesDir = path.join(customConfigTestDataDir, 'resources');
|
|
258
|
-
const configFile = path.join(customConfigTestDataDir, 'resources-config.json');
|
|
259
|
-
const outputFile = path.join(tempDir, 'territory-filtered.json');
|
|
260
|
-
|
|
261
|
-
const result = await runCli([
|
|
262
|
-
'compile',
|
|
263
|
-
'--input',
|
|
264
|
-
resourcesDir,
|
|
265
|
-
'--output',
|
|
266
|
-
outputFile,
|
|
267
|
-
'--config',
|
|
268
|
-
configFile,
|
|
269
|
-
'--context',
|
|
270
|
-
'{"territory": "CA"}',
|
|
271
|
-
'--format',
|
|
272
|
-
'source'
|
|
273
|
-
]);
|
|
274
|
-
|
|
275
|
-
expect(result.exitCode).toBe(0);
|
|
276
|
-
|
|
277
|
-
const outputContent = await fs.readFile(outputFile, 'utf-8');
|
|
278
|
-
const output = JSON.parse(outputContent);
|
|
279
|
-
|
|
280
|
-
expect(output).toHaveProperty('resources');
|
|
281
|
-
|
|
282
|
-
// Should include resources that match Canadian territory
|
|
283
|
-
expect(TsRes.ResourceJson.Convert.resourceCollectionDecl.convert(output.resources)).toSucceedAndSatisfy(
|
|
284
|
-
(resources) => {
|
|
285
|
-
const hasCanadianTerritory = resources?.resources?.some((resource) =>
|
|
286
|
-
resource.candidates?.some((candidate) =>
|
|
287
|
-
candidate.conditions?.some(
|
|
288
|
-
(condition) => condition.qualifierName === 'territory' && condition.value === 'CA'
|
|
289
|
-
)
|
|
290
|
-
)
|
|
291
|
-
);
|
|
292
|
-
expect(hasCanadianTerritory).toBe(true);
|
|
293
|
-
return true;
|
|
294
|
-
}
|
|
295
|
-
);
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
test('compiles with role context using custom configuration', async () => {
|
|
299
|
-
const resourcesDir = path.join(customConfigTestDataDir, 'resources');
|
|
300
|
-
const configFile = path.join(customConfigTestDataDir, 'resources-config.json');
|
|
301
|
-
const outputFile = path.join(tempDir, 'role-filtered.json');
|
|
302
|
-
|
|
303
|
-
const result = await runCli([
|
|
304
|
-
'compile',
|
|
305
|
-
'--input',
|
|
306
|
-
resourcesDir,
|
|
307
|
-
'--output',
|
|
308
|
-
outputFile,
|
|
309
|
-
'--config',
|
|
310
|
-
configFile,
|
|
311
|
-
'--context',
|
|
312
|
-
'{"role": "admin"}',
|
|
313
|
-
'--format',
|
|
314
|
-
'source'
|
|
315
|
-
]);
|
|
316
|
-
|
|
317
|
-
expect(result.exitCode).toBe(0);
|
|
318
|
-
|
|
319
|
-
const outputContent = await fs.readFile(outputFile, 'utf-8');
|
|
320
|
-
const output = JSON.parse(outputContent);
|
|
321
|
-
|
|
322
|
-
expect(output).toHaveProperty('resources');
|
|
323
|
-
|
|
324
|
-
// Should include resources that match admin role
|
|
325
|
-
expect(TsRes.ResourceJson.Convert.resourceCollectionDecl.convert(output.resources)).toSucceedAndSatisfy(
|
|
326
|
-
(resources) => {
|
|
327
|
-
const hasAdminRole = resources?.resources?.some((resource) =>
|
|
328
|
-
resource.candidates?.some((candidate) =>
|
|
329
|
-
candidate.conditions?.some(
|
|
330
|
-
(condition) => condition.qualifierName === 'role' && condition.value === 'admin'
|
|
331
|
-
)
|
|
332
|
-
)
|
|
333
|
-
);
|
|
334
|
-
expect(hasAdminRole).toBe(true);
|
|
335
|
-
return true;
|
|
336
|
-
}
|
|
337
|
-
);
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
test.skip('compiles with complex multi-qualifier context', async () => {
|
|
341
|
-
const resourcesDir = path.join(customConfigTestDataDir, 'resources');
|
|
342
|
-
const configFile = path.join(customConfigTestDataDir, 'resources-config.json');
|
|
343
|
-
const outputFile = path.join(tempDir, 'complex-filtered.json');
|
|
344
|
-
|
|
345
|
-
const result = await runCli([
|
|
346
|
-
'compile',
|
|
347
|
-
'--input',
|
|
348
|
-
resourcesDir,
|
|
349
|
-
'--output',
|
|
350
|
-
outputFile,
|
|
351
|
-
'--config',
|
|
352
|
-
configFile,
|
|
353
|
-
'--context',
|
|
354
|
-
'{"language": "fr-CA", "territory": "CA", "role": "user"}',
|
|
355
|
-
'--format',
|
|
356
|
-
'source'
|
|
357
|
-
]);
|
|
358
|
-
|
|
359
|
-
expect(result.exitCode).toBe(0);
|
|
360
|
-
|
|
361
|
-
const outputContent = await fs.readFile(outputFile, 'utf-8');
|
|
362
|
-
const output = JSON.parse(outputContent);
|
|
363
|
-
|
|
364
|
-
expect(output).toHaveProperty('resources');
|
|
365
|
-
|
|
366
|
-
// Should include resources that match the complex context
|
|
367
|
-
expect(TsRes.ResourceJson.Convert.resourceCollectionDecl.convert(output.resources)).toSucceedAndSatisfy(
|
|
368
|
-
(resources) => {
|
|
369
|
-
const hasMatchingConditions = resources?.resources?.some((resource) =>
|
|
370
|
-
resource.candidates?.some((candidate) =>
|
|
371
|
-
candidate.conditions?.some(
|
|
372
|
-
(condition) =>
|
|
373
|
-
(condition.qualifierName === 'language' && condition.value === 'fr-CA') ||
|
|
374
|
-
(condition.qualifierName === 'territory' && condition.value === 'CA') ||
|
|
375
|
-
(condition.qualifierName === 'role' && condition.value === 'user')
|
|
376
|
-
)
|
|
377
|
-
)
|
|
378
|
-
);
|
|
379
|
-
expect(hasMatchingConditions).toBe(true);
|
|
380
|
-
return true;
|
|
381
|
-
}
|
|
382
|
-
);
|
|
383
|
-
});
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
describe('Configuration validation and info commands', () => {
|
|
387
|
-
test('validates resources with custom configuration', async () => {
|
|
388
|
-
const resourcesDir = path.join(customConfigTestDataDir, 'resources');
|
|
389
|
-
const configFile = path.join(customConfigTestDataDir, 'resources-config.json');
|
|
390
|
-
|
|
391
|
-
const result = await runCli(['validate', '--input', resourcesDir, '--config', configFile]);
|
|
392
|
-
|
|
393
|
-
expect(result.exitCode).toBe(0);
|
|
394
|
-
expect(result.stdout).toMatch(/Resources validated successfully/);
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
test('shows info with custom configuration', async () => {
|
|
398
|
-
const resourcesDir = path.join(customConfigTestDataDir, 'resources');
|
|
399
|
-
const configFile = path.join(customConfigTestDataDir, 'resources-config.json');
|
|
400
|
-
|
|
401
|
-
const result = await runCli(['info', '--input', resourcesDir, '--config', configFile]);
|
|
402
|
-
|
|
403
|
-
expect(result.exitCode).toBe(0);
|
|
404
|
-
|
|
405
|
-
const info = JSON.parse(result.stdout);
|
|
406
|
-
expect(info).toHaveProperty('totalResources');
|
|
407
|
-
expect(info).toHaveProperty('totalCandidates');
|
|
408
|
-
expect(info).toHaveProperty('resourceTypes');
|
|
409
|
-
expect(info).toHaveProperty('qualifiers');
|
|
410
|
-
|
|
411
|
-
// Should include custom qualifiers
|
|
412
|
-
expect(info.qualifiers).toContain('territory');
|
|
413
|
-
expect(info.qualifiers).toContain('role');
|
|
414
|
-
expect(info.qualifiers).toContain('platform');
|
|
415
|
-
expect(info.qualifiers).toContain('density');
|
|
416
|
-
expect(info.qualifiers).toContain('region');
|
|
417
|
-
});
|
|
418
|
-
});
|
|
419
|
-
});
|