reg-cli 0.18.16 → 0.19.0-rc1

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/dist/index.js DELETED
@@ -1,200 +0,0 @@
1
- "use strict";
2
-
3
- var _glob = _interopRequireDefault(require("glob"));
4
- var _makeDir = _interopRequireDefault(require("make-dir"));
5
- var _del = require("del");
6
- var _fs = _interopRequireDefault(require("fs"));
7
- var _path = _interopRequireDefault(require("path"));
8
- var _lodash = require("lodash");
9
- var _log = _interopRequireDefault(require("./log"));
10
- var _report = _interopRequireDefault(require("./report"));
11
- var _bluebird = _interopRequireDefault(require("bluebird"));
12
- var _events = _interopRequireDefault(require("events"));
13
- var _processAdaptor = _interopRequireDefault(require("./process-adaptor"));
14
- var _imageFinder = require("./image-finder");
15
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
16
- // $FlowIgnore
17
- // $FlowIgnore
18
- // $FlowIgnore
19
- // $FlowIgnore
20
- var copyImages = (actualImages, {
21
- expectedDir,
22
- actualDir
23
- }) => {
24
- return Promise.all(actualImages.map(image => new Promise((resolve, reject) => {
25
- try {
26
- _makeDir.default.sync(_path.default.dirname(_path.default.join(expectedDir, image)));
27
- var writeStream = _fs.default.createWriteStream(_path.default.join(expectedDir, image));
28
- _fs.default.createReadStream(_path.default.join(actualDir, image)).pipe(writeStream);
29
- writeStream.on('finish', err => {
30
- if (err) reject(err);
31
- resolve();
32
- });
33
- } catch (err) {
34
- reject(err);
35
- }
36
- })));
37
- };
38
- var compareImages = (emitter, {
39
- expectedImages,
40
- actualImages,
41
- dirs,
42
- matchingThreshold,
43
- thresholdPixel,
44
- thresholdRate,
45
- concurrency,
46
- enableAntialias
47
- }) => {
48
- var images = actualImages.filter(actualImage => expectedImages.includes(actualImage));
49
- concurrency = images.length < 20 ? 1 : concurrency || 4;
50
- var processes = (0, _lodash.range)(concurrency).map(() => new _processAdaptor.default(emitter));
51
- return _bluebird.default.map(images, image => {
52
- var p = processes.find(p => !p.isRunning());
53
- if (p) {
54
- return p.run({
55
- ...dirs,
56
- image,
57
- matchingThreshold,
58
- thresholdRate,
59
- thresholdPixel,
60
- enableAntialias
61
- });
62
- }
63
- }, {
64
- concurrency
65
- }).then(result => {
66
- processes.forEach(p => p.close());
67
- return result;
68
- }).filter(r => !!r);
69
- };
70
- var cleanupExpectedDir = (expectedDir, changedFiles) => {
71
- var paths = changedFiles.map(image => {
72
- var directories = expectedDir.split("\\");
73
- return escapeGlob(_path.default.posix.join(...directories, image));
74
- });
75
- // force: true needed to allow deleting outside working directory
76
- return (0, _del.deleteAsync)(paths, {
77
- force: true
78
- });
79
- };
80
- var escapeGlob = fileName => {
81
- return fileName.replace(/(\*)/g, '[$1]').replace(/(\*)/g, '[$1]').replace(/(\?)/g, '[$1]').replace(/(\[)/g, '[$1]').replace(/(\])/g, '[$1]').replace(/(\{)/g, '[$1]').replace(/(\})/g, '[$1]').replace(/(\))/g, '[$1]').replace(/(\()/g, '[$1]').replace(/(\!)/g, '[$1]');
82
- };
83
- var aggregate = result => {
84
- var passed = result.filter(r => r.passed).map(r => r.image);
85
- var failed = result.filter(r => !r.passed).map(r => r.image);
86
- var diffItems = failed.map(image => image.replace(/\.[^\.]+$/, '.png'));
87
- return {
88
- passed,
89
- failed,
90
- diffItems
91
- };
92
- };
93
- var updateExpected = ({
94
- actualDir,
95
- expectedDir,
96
- diffDir,
97
- deletedImages,
98
- newImages,
99
- diffItems
100
- }) => {
101
- return cleanupExpectedDir(expectedDir, [...deletedImages, ...diffItems]).then(() => copyImages([...newImages, ...diffItems], {
102
- actualDir,
103
- expectedDir,
104
- diffDir
105
- })).then(() => {
106
- _log.default.success(`\nAll images are updated. `);
107
- });
108
- };
109
- module.exports = params => {
110
- var {
111
- actualDir,
112
- expectedDir,
113
- diffDir,
114
- json,
115
- concurrency = 4,
116
- update,
117
- report,
118
- junitReport,
119
- extendedErrors,
120
- urlPrefix,
121
- threshold,
122
- matchingThreshold = 0,
123
- thresholdRate,
124
- thresholdPixel,
125
- enableAntialias,
126
- enableClientAdditionalDetection
127
- } = params;
128
- var dirs = {
129
- actualDir,
130
- expectedDir,
131
- diffDir
132
- };
133
- var emitter = new _events.default();
134
- var {
135
- expectedImages,
136
- actualImages,
137
- deletedImages,
138
- newImages
139
- } = (0, _imageFinder.findImages)(expectedDir, actualDir);
140
- _makeDir.default.sync(expectedDir);
141
- _makeDir.default.sync(diffDir);
142
- setImmediate(() => emitter.emit('start'));
143
- compareImages(emitter, {
144
- expectedImages,
145
- actualImages,
146
- dirs,
147
- matchingThreshold,
148
- thresholdRate: thresholdRate || threshold,
149
- thresholdPixel,
150
- concurrency,
151
- enableAntialias: !!enableAntialias
152
- }).then(result => aggregate(result)).then(({
153
- passed,
154
- failed,
155
- diffItems
156
- }) => {
157
- return (0, _report.default)({
158
- passedItems: passed,
159
- failedItems: failed,
160
- newItems: newImages,
161
- deletedItems: deletedImages,
162
- expectedItems: update ? actualImages : expectedImages,
163
- actualItems: actualImages,
164
- diffItems,
165
- json: json || './reg.json',
166
- actualDir,
167
- expectedDir,
168
- diffDir,
169
- report: report || '',
170
- junitReport: junitReport || '',
171
- extendedErrors: !!extendedErrors,
172
- urlPrefix: urlPrefix || '',
173
- enableClientAdditionalDetection: !!enableClientAdditionalDetection
174
- });
175
- }).then(result => {
176
- deletedImages.forEach(image => emitter.emit('compare', {
177
- type: 'delete',
178
- path: image
179
- }));
180
- newImages.forEach(image => emitter.emit('compare', {
181
- type: 'new',
182
- path: image
183
- }));
184
- if (update) {
185
- return updateExpected({
186
- actualDir,
187
- expectedDir,
188
- diffDir,
189
- deletedImages,
190
- newImages,
191
- diffItems: result.diffItems
192
- }).then(() => {
193
- emitter.emit('update');
194
- return result;
195
- });
196
- }
197
- return result;
198
- }).then(result => emitter.emit('complete', result)).catch(err => emitter.emit('error', err));
199
- return emitter;
200
- };
package/dist/log.js DELETED
@@ -1,21 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
- var chalk = require('chalk');
8
- var _default = exports.default = {
9
- info(text) {
10
- console.log(chalk.cyan(text));
11
- },
12
- warn(text) {
13
- console.log(chalk.gray(text));
14
- },
15
- success(text) {
16
- console.log(chalk.green(text));
17
- },
18
- fail(text) {
19
- console.log(chalk.red(text));
20
- }
21
- };
@@ -1,40 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
- var _child_process = require("child_process");
8
- var _path = _interopRequireDefault(require("path"));
9
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
- // $FlowIgnore
11
- class ProcessAdaptor {
12
- constructor(emitter) {
13
- this._process = (0, _child_process.fork)(_path.default.resolve(__dirname, './diff.js'));
14
- this._isRunning = false;
15
- this._emitter = emitter;
16
- }
17
- isRunning() {
18
- return this._isRunning;
19
- }
20
- run(params) {
21
- return new Promise((resolve, reject) => {
22
- this._isRunning = true;
23
- if (!this._process || !this._process.send) resolve();
24
- this._process.send(params);
25
- this._process.once('message', result => {
26
- this._isRunning = false;
27
- this._emitter.emit('compare', {
28
- type: result.passed ? 'pass' : 'fail',
29
- path: result.image
30
- });
31
- resolve(result);
32
- });
33
- });
34
- }
35
- close() {
36
- if (!this._process || !this._process.kill) return;
37
- this._process.kill();
38
- }
39
- }
40
- exports.default = ProcessAdaptor;
package/dist/report.js DELETED
@@ -1,170 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
- var _mustache = _interopRequireDefault(require("mustache"));
8
- var detectDiff = _interopRequireWildcard(require("x-img-diff-js"));
9
- var _fs = _interopRequireDefault(require("fs"));
10
- var _makeDir = _interopRequireDefault(require("make-dir"));
11
- var _path = _interopRequireDefault(require("path"));
12
- var xmlBuilder = _interopRequireWildcard(require("xmlbuilder2"));
13
- function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
14
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
- // $FlowIgnore
16
- // $FlowIgnore
17
- // $FlowIgnore
18
- var loadFaviconAsDataURL = type => {
19
- var fname = _path.default.resolve(__dirname, `../report/assets/favicon_${type}.png`);
20
- var buffer = _fs.default.readFileSync(fname);
21
- return 'data:image/png;base64,' + buffer.toString('base64');
22
- };
23
- var encodeFilePath = filePath => {
24
- return filePath.split(_path.default.sep).map(p => encodeURIComponent(p)).join(_path.default.sep);
25
- };
26
- var createJSONReport = params => {
27
- return {
28
- failedItems: params.failedItems,
29
- newItems: params.newItems,
30
- deletedItems: params.deletedItems,
31
- passedItems: params.passedItems,
32
- expectedItems: params.expectedItems,
33
- actualItems: params.actualItems,
34
- diffItems: params.diffItems,
35
- actualDir: `${params.urlPrefix}${_path.default.relative(_path.default.dirname(params.json), params.actualDir)}`,
36
- expectedDir: `${params.urlPrefix}${_path.default.relative(_path.default.dirname(params.json), params.expectedDir)}`,
37
- diffDir: `${params.urlPrefix}${_path.default.relative(_path.default.dirname(params.json), params.diffDir)}`
38
- };
39
- };
40
- var createHTMLReport = params => {
41
- var file = _path.default.join(__dirname, '../template/template.html');
42
- var js = _fs.default.readFileSync(_path.default.join(__dirname, '../report/ui/dist/report.js'));
43
- var css = _fs.default.readFileSync(_path.default.join(__dirname, '../report/ui/dist/style.css'));
44
- var template = _fs.default.readFileSync(file);
45
- var json = {
46
- type: params.failedItems.length === 0 ? 'success' : 'danger',
47
- hasNew: params.newItems.length > 0,
48
- newItems: params.newItems.map(item => ({
49
- raw: item,
50
- encoded: encodeFilePath(item)
51
- })),
52
- hasDeleted: params.deletedItems.length > 0,
53
- deletedItems: params.deletedItems.map(item => ({
54
- raw: item,
55
- encoded: encodeFilePath(item)
56
- })),
57
- hasPassed: params.passedItems.length > 0,
58
- passedItems: params.passedItems.map(item => ({
59
- raw: item,
60
- encoded: encodeFilePath(item)
61
- })),
62
- hasFailed: params.failedItems.length > 0,
63
- failedItems: params.failedItems.map(item => ({
64
- raw: item,
65
- encoded: encodeFilePath(item)
66
- })),
67
- actualDir: params.fromJSON ? params.actualDir : `${params.urlPrefix}${_path.default.relative(_path.default.dirname(params.report), params.actualDir)}`,
68
- expectedDir: params.fromJSON ? params.expectedDir : `${params.urlPrefix}${_path.default.relative(_path.default.dirname(params.report), params.expectedDir)}`,
69
- diffDir: params.fromJSON ? params.diffDir : `${params.urlPrefix}${_path.default.relative(_path.default.dirname(params.report), params.diffDir)}`,
70
- ximgdiffConfig: {
71
- enabled: params.enableClientAdditionalDetection,
72
- workerUrl: `${params.urlPrefix}worker.js`
73
- }
74
- };
75
- var faviconType = json.hasFailed || json.hasNew || json.hasDeleted ? 'failure' : 'success';
76
- var view = {
77
- js,
78
- css,
79
- report: JSON.stringify(json),
80
- faviconData: loadFaviconAsDataURL(faviconType)
81
- };
82
- return _mustache.default.render(template.toString(), view);
83
- };
84
- var createJunitReport = params => {
85
- var failedTests = params.failedItems.length + params.newItems.length + params.deletedItems.length;
86
- var numberOfTests = failedTests + params.passedItems.length;
87
- var doc = xmlBuilder.create({
88
- version: '1.0'
89
- });
90
- var testsuitesElement = doc.ele('testsuites', {
91
- name: 'reg-cli tests',
92
- tests: numberOfTests,
93
- failures: failedTests
94
- });
95
- var testsuiteElement = testsuitesElement.ele('testsuite', {
96
- name: 'reg-cli',
97
- tests: numberOfTests,
98
- failures: failedTests
99
- });
100
- params.failedItems.forEach(item => {
101
- addFailedJunitTestElement(testsuiteElement, item, 'failed');
102
- });
103
- params.newItems.forEach(item => {
104
- if (params.extendedErrors) {
105
- addFailedJunitTestElement(testsuiteElement, item, 'newItem');
106
- } else {
107
- addPassedJunitTestElement(testsuiteElement, item);
108
- }
109
- });
110
- params.deletedItems.forEach(item => {
111
- if (params.extendedErrors) {
112
- addFailedJunitTestElement(testsuiteElement, item, 'deletedItem');
113
- } else {
114
- addPassedJunitTestElement(testsuiteElement, item);
115
- }
116
- });
117
- params.passedItems.forEach(item => {
118
- addPassedJunitTestElement(testsuiteElement, item);
119
- });
120
- return doc.end({
121
- prettyPrint: true
122
- });
123
- };
124
- function addPassedJunitTestElement(testsuiteElement, item) {
125
- testsuiteElement.ele('testcase', {
126
- name: item
127
- });
128
- }
129
- function addFailedJunitTestElement(testsuiteElement, item, reason) {
130
- testsuiteElement.ele('testcase', {
131
- name: item
132
- }).ele('failure', {
133
- message: reason
134
- });
135
- }
136
- function createXimdiffWorker(params) {
137
- var file = _path.default.join(__dirname, '../template/worker_pre.js');
138
- var moduleJs = _fs.default.readFileSync(_path.default.join(__dirname, '../report/ui/dist/worker.js'), 'utf8');
139
- var wasmLoaderJs = _fs.default.readFileSync(detectDiff.getBrowserJsPath(), 'utf8');
140
- var template = _fs.default.readFileSync(file);
141
- var ximgdiffWasmUrl = `${params.urlPrefix}detector.wasm`;
142
- return _mustache.default.render(template.toString(), {
143
- ximgdiffWasmUrl
144
- }) + '\n' + moduleJs + '\n' + wasmLoaderJs;
145
- }
146
- var _default = params => {
147
- if (!!params.report) {
148
- var html = createHTMLReport(params);
149
- _makeDir.default.sync(_path.default.dirname(params.report));
150
- _fs.default.writeFileSync(params.report, html);
151
- if (!!params.enableClientAdditionalDetection) {
152
- var workerjs = createXimdiffWorker(params);
153
- _fs.default.writeFileSync(_path.default.resolve(_path.default.dirname(params.report), 'worker.js'), workerjs);
154
- var wasmBuf = _fs.default.readFileSync(detectDiff.getBrowserWasmPath());
155
- _fs.default.writeFileSync(_path.default.resolve(_path.default.dirname(params.report), 'detector.wasm'), wasmBuf);
156
- }
157
- }
158
- if (!!params.junitReport) {
159
- var junitXml = createJunitReport(params);
160
- _makeDir.default.sync(_path.default.dirname(params.junitReport));
161
- _fs.default.writeFileSync(params.junitReport, junitXml);
162
- }
163
- var json = createJSONReport(params);
164
- if (!params.fromJSON) {
165
- _makeDir.default.sync(_path.default.dirname(params.json));
166
- _fs.default.writeFileSync(params.json, JSON.stringify(json));
167
- }
168
- return json;
169
- };
170
- exports.default = _default;
package/dist/tracing.js DELETED
@@ -1,169 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.withSpan = exports.startSpan = exports.startRootSpan = exports.shutdownTracing = exports.isTracingEnabled = exports.initTracing = exports.getTracer = exports.getTraceContext = exports.endRootSpan = void 0;
7
- var _sdkNode = require("@opentelemetry/sdk-node");
8
- var _exporterTraceOtlpHttp = require("@opentelemetry/exporter-trace-otlp-http");
9
- var _api = require("@opentelemetry/api");
10
- var _resources = require("@opentelemetry/resources");
11
- var _semanticConventions = require("@opentelemetry/semantic-conventions");
12
- var sdk = null;
13
- var isInitialized = false;
14
- var currentRootSpan = null;
15
- var currentRootContext = null;
16
- var isTracingEnabled = () => {
17
- return process.env.OTEL_ENABLED === 'true' || process.env.JAEGER_ENABLED === 'true';
18
- };
19
- exports.isTracingEnabled = isTracingEnabled;
20
- var initTracing = () => {
21
- if (!isTracingEnabled() || isInitialized) {
22
- return;
23
- }
24
- var endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318/v1/traces';
25
- var exporter = new _exporterTraceOtlpHttp.OTLPTraceExporter({
26
- url: endpoint
27
- });
28
- sdk = new _sdkNode.NodeSDK({
29
- resource: new _resources.Resource({
30
- [_semanticConventions.SemanticResourceAttributes.SERVICE_NAME]: 'reg-cli-js',
31
- [_semanticConventions.SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0'
32
- }),
33
- traceExporter: exporter
34
- });
35
- sdk.start();
36
- isInitialized = true;
37
- if (process.env.OTEL_DEBUG === 'true') {
38
- console.log('[Tracing-JS] OpenTelemetry initialized');
39
- console.log(`[Tracing-JS] OTLP endpoint: ${endpoint}`);
40
- }
41
- };
42
- exports.initTracing = initTracing;
43
- var shutdownTracing = async () => {
44
- if (sdk && isInitialized) {
45
- if (process.env.OTEL_DEBUG === 'true') {
46
- console.log('[Tracing-JS] Starting SDK shutdown...');
47
- }
48
- await sdk.shutdown();
49
- if (process.env.OTEL_DEBUG === 'true') {
50
- console.log('[Tracing-JS] OpenTelemetry SDK shut down');
51
- }
52
- }
53
- };
54
- exports.shutdownTracing = shutdownTracing;
55
- var getTracer = () => {
56
- return _api.trace.getTracer('reg-cli-js', '1.0.0');
57
- };
58
- exports.getTracer = getTracer;
59
- var startRootSpan = name => {
60
- if (!isTracingEnabled() || !isInitialized) {
61
- return null;
62
- }
63
- var tracer = getTracer();
64
- currentRootSpan = tracer.startSpan(name);
65
- currentRootContext = _api.trace.setSpan(_api.ROOT_CONTEXT, currentRootSpan);
66
- if (process.env.OTEL_DEBUG === 'true') {
67
- var spanContext = currentRootSpan.spanContext();
68
- console.log(`[Tracing-JS] Started root span: ${name}`);
69
- console.log(`[Tracing-JS] Trace ID: ${spanContext.traceId}`);
70
- console.log(`[Tracing-JS] Span ID: ${spanContext.spanId}`);
71
- }
72
- return {
73
- traceId: currentRootSpan.spanContext().traceId,
74
- spanId: currentRootSpan.spanContext().spanId
75
- };
76
- };
77
- exports.startRootSpan = startRootSpan;
78
- var endRootSpan = (success = true) => {
79
- if (currentRootSpan) {
80
- if (success) {
81
- currentRootSpan.setStatus({
82
- code: _api.SpanStatusCode.OK
83
- });
84
- } else {
85
- currentRootSpan.setStatus({
86
- code: _api.SpanStatusCode.ERROR
87
- });
88
- }
89
- currentRootSpan.end();
90
- if (process.env.OTEL_DEBUG === 'true') {
91
- console.log('[Tracing-JS] Ended root span');
92
- }
93
- currentRootSpan = null;
94
- currentRootContext = null;
95
- }
96
- };
97
- exports.endRootSpan = endRootSpan;
98
- var startSpan = (name, attributes = {}) => {
99
- if (!isTracingEnabled() || !isInitialized) {
100
- return {
101
- end: () => {},
102
- setStatus: () => {},
103
- setAttribute: () => {}
104
- };
105
- }
106
- var tracer = getTracer();
107
- var parentContext = currentRootContext || _api.ROOT_CONTEXT;
108
- var span = tracer.startSpan(name, {
109
- attributes
110
- }, parentContext);
111
- var startTime = Date.now();
112
- if (process.env.OTEL_DEBUG === 'true') {
113
- console.log(`[Tracing-JS] Started span: ${name}`);
114
- }
115
-
116
- // Wrap the end method to log when span ends
117
- var originalEnd = span.end.bind(span);
118
- span.end = () => {
119
- originalEnd();
120
- if (process.env.OTEL_DEBUG === 'true') {
121
- var duration = Date.now() - startTime;
122
- console.log(`[Tracing-JS] Ended span: ${name} (${duration}ms)`);
123
- }
124
- };
125
- return span;
126
- };
127
- exports.startSpan = startSpan;
128
- var withSpan = async (name, attributes, fn) => {
129
- if (!isTracingEnabled() || !isInitialized) {
130
- return fn();
131
- }
132
- var tracer = getTracer();
133
- var parentContext = currentRootContext || _api.ROOT_CONTEXT;
134
- return tracer.startActiveSpan(name, {
135
- attributes
136
- }, parentContext, async span => {
137
- try {
138
- var result = await fn();
139
- span.setStatus({
140
- code: _api.SpanStatusCode.OK
141
- });
142
- return result;
143
- } catch (error) {
144
- span.setStatus({
145
- code: _api.SpanStatusCode.ERROR,
146
- message: error.message
147
- });
148
- throw error;
149
- } finally {
150
- span.end();
151
- if (process.env.OTEL_DEBUG === 'true') {
152
- console.log(`[Tracing-JS] Ended span: ${name}`);
153
- }
154
- }
155
- });
156
- };
157
-
158
- // For child processes - context propagation
159
- exports.withSpan = withSpan;
160
- var getTraceContext = () => {
161
- if (currentRootSpan) {
162
- return {
163
- traceId: currentRootSpan.spanContext().traceId,
164
- spanId: currentRootSpan.spanContext().spanId
165
- };
166
- }
167
- return null;
168
- };
169
- exports.getTraceContext = getTraceContext;
Binary file
Binary file
Binary file
Binary file
Binary file