start-command 0.27.0 → 0.27.2
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/CHANGELOG.md +15 -0
- package/README.md +7 -3
- package/bun.lock +70 -94
- package/package.json +12 -8
- package/src/bin/cli.js +3 -12
- package/src/lib/docker-utils.js +1 -1
- package/src/lib/output-blocks.js +6 -4
- package/src/lib/usage.js +4 -3
- package/test/create-github-release.mjs +118 -0
- package/test/fixtures/issue-126-bin/pgrep +4 -0
- package/test/fixtures/issue-126-bin/screen +6 -0
- package/test/isolation-cleanup.js +120 -109
- package/test/output-blocks.js +25 -0
- package/test/publish-to-crates.mjs +194 -0
- package/test/status-query.js +40 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import {
|
|
4
|
+
mkdirSync,
|
|
5
|
+
mkdtempSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from 'node:fs';
|
|
10
|
+
import { createServer } from 'node:http';
|
|
11
|
+
import { tmpdir } from 'node:os';
|
|
12
|
+
import { dirname, join, resolve } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
const testDir = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const repoRoot = resolve(testDir, '../..');
|
|
17
|
+
const scriptPath = resolve(repoRoot, 'scripts/publish-to-crates.mjs');
|
|
18
|
+
|
|
19
|
+
function createCargoPackage(tempDir) {
|
|
20
|
+
const packageDir = join(tempDir, 'crate');
|
|
21
|
+
const cargoTomlPath = join(packageDir, 'Cargo.toml');
|
|
22
|
+
mkdirSync(packageDir);
|
|
23
|
+
writeFileSync(
|
|
24
|
+
cargoTomlPath,
|
|
25
|
+
'[package]\nname = "example-crate"\nversion = "1.2.3"\nedition = "2021"\n'
|
|
26
|
+
);
|
|
27
|
+
return packageDir;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function createFakeCargoBin(tempDir) {
|
|
31
|
+
const fakeCargoJs = join(tempDir, 'fake-cargo.cjs');
|
|
32
|
+
writeFileSync(
|
|
33
|
+
fakeCargoJs,
|
|
34
|
+
`
|
|
35
|
+
const fs = require('node:fs');
|
|
36
|
+
fs.writeFileSync(process.env.FAKE_CARGO_ARGS_PATH, JSON.stringify(process.argv.slice(2)));
|
|
37
|
+
process.exit(Number(process.env.FAKE_CARGO_EXIT || 0));
|
|
38
|
+
`
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
return fakeCargoJs;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function withCratesServer(status, callback) {
|
|
45
|
+
const requests = [];
|
|
46
|
+
const server = createServer((request, response) => {
|
|
47
|
+
requests.push(request.url);
|
|
48
|
+
response.writeHead(status, { 'content-type': 'application/json' });
|
|
49
|
+
response.end(JSON.stringify({ ok: status === 200 }));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await new Promise((resolveListen) =>
|
|
53
|
+
server.listen(0, '127.0.0.1', resolveListen)
|
|
54
|
+
);
|
|
55
|
+
const { port } = server.address();
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
return await callback(`http://127.0.0.1:${port}/api/v1`, requests);
|
|
59
|
+
} finally {
|
|
60
|
+
await new Promise((resolveClose) => server.close(resolveClose));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function runScript({
|
|
65
|
+
cargoToken = '',
|
|
66
|
+
cratesIoBaseUrl,
|
|
67
|
+
fakeCargo = false,
|
|
68
|
+
packageDir,
|
|
69
|
+
tempDir,
|
|
70
|
+
}) {
|
|
71
|
+
const outputPath = join(tempDir, 'github-output.txt');
|
|
72
|
+
const cargoArgsPath = join(tempDir, 'cargo-args.json');
|
|
73
|
+
const fakeCargoJs = fakeCargo ? createFakeCargoBin(tempDir) : '';
|
|
74
|
+
|
|
75
|
+
const env = {
|
|
76
|
+
...process.env,
|
|
77
|
+
CARGO_REGISTRY_TOKEN: cargoToken,
|
|
78
|
+
CRATES_IO_BASE_URL: cratesIoBaseUrl,
|
|
79
|
+
CRATES_PUBLISH_RETRY_DELAY_MS: '1',
|
|
80
|
+
FAKE_CARGO_ARGS_PATH: cargoArgsPath,
|
|
81
|
+
GITHUB_OUTPUT: outputPath,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (fakeCargoJs) {
|
|
85
|
+
env.START_CARGO_COMMAND = process.execPath;
|
|
86
|
+
env.START_CARGO_COMMAND_ARGS = JSON.stringify([fakeCargoJs]);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return new Promise((resolveRun, rejectRun) => {
|
|
90
|
+
const child = spawn('node', [scriptPath, '--working-dir', packageDir], {
|
|
91
|
+
cwd: repoRoot,
|
|
92
|
+
env,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
let stdout = '';
|
|
96
|
+
let stderr = '';
|
|
97
|
+
child.stdout.setEncoding('utf8');
|
|
98
|
+
child.stderr.setEncoding('utf8');
|
|
99
|
+
child.stdout.on('data', (chunk) => {
|
|
100
|
+
stdout += chunk;
|
|
101
|
+
});
|
|
102
|
+
child.stderr.on('data', (chunk) => {
|
|
103
|
+
stderr += chunk;
|
|
104
|
+
});
|
|
105
|
+
child.on('error', rejectRun);
|
|
106
|
+
child.on('close', (status, signal) => {
|
|
107
|
+
resolveRun({
|
|
108
|
+
cargoArgsPath,
|
|
109
|
+
outputPath,
|
|
110
|
+
result: {
|
|
111
|
+
signal,
|
|
112
|
+
status,
|
|
113
|
+
stderr,
|
|
114
|
+
stdout,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
describe('publish-to-crates script', () => {
|
|
122
|
+
it('reports success without a token when the version is already published', async () => {
|
|
123
|
+
await withCratesServer(200, async (cratesIoBaseUrl, requests) => {
|
|
124
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'publish-crates-test-'));
|
|
125
|
+
const packageDir = createCargoPackage(tempDir);
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const { outputPath, result } = await runScript({
|
|
129
|
+
cratesIoBaseUrl,
|
|
130
|
+
packageDir,
|
|
131
|
+
tempDir,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(result.status).toBe(0);
|
|
135
|
+
expect(result.stdout).toContain('already_published=true');
|
|
136
|
+
expect(readFileSync(outputPath, 'utf8')).toContain('published=true');
|
|
137
|
+
expect(requests).toEqual(['/api/v1/crates/example-crate/1.2.3']);
|
|
138
|
+
} finally {
|
|
139
|
+
rmSync(tempDir, { force: true, recursive: true });
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('fails clearly when a missing version needs a crates token', async () => {
|
|
145
|
+
await withCratesServer(404, async (cratesIoBaseUrl) => {
|
|
146
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'publish-crates-test-'));
|
|
147
|
+
const packageDir = createCargoPackage(tempDir);
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const { result } = await runScript({
|
|
151
|
+
cratesIoBaseUrl,
|
|
152
|
+
packageDir,
|
|
153
|
+
tempDir,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(result.status).not.toBe(0);
|
|
157
|
+
expect(result.stderr).toContain('Missing crates.io token');
|
|
158
|
+
} finally {
|
|
159
|
+
rmSync(tempDir, { force: true, recursive: true });
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('publishes a missing version with cargo publish', async () => {
|
|
165
|
+
await withCratesServer(404, async (cratesIoBaseUrl) => {
|
|
166
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'publish-crates-test-'));
|
|
167
|
+
const packageDir = createCargoPackage(tempDir);
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const { cargoArgsPath, outputPath, result } = await runScript({
|
|
171
|
+
cargoToken: 'test-token',
|
|
172
|
+
cratesIoBaseUrl,
|
|
173
|
+
fakeCargo: true,
|
|
174
|
+
packageDir,
|
|
175
|
+
tempDir,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(result.status).toBe(0);
|
|
179
|
+
expect(result.stdout).toContain('publish_result=published');
|
|
180
|
+
expect(readFileSync(outputPath, 'utf8')).toContain('published=true');
|
|
181
|
+
expect(JSON.parse(readFileSync(cargoArgsPath, 'utf8'))).toEqual([
|
|
182
|
+
'publish',
|
|
183
|
+
'--allow-dirty',
|
|
184
|
+
'--manifest-path',
|
|
185
|
+
join(packageDir, 'Cargo.toml'),
|
|
186
|
+
'--token',
|
|
187
|
+
'test-token',
|
|
188
|
+
]);
|
|
189
|
+
} finally {
|
|
190
|
+
rmSync(tempDir, { force: true, recursive: true });
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|
package/test/status-query.js
CHANGED
|
@@ -328,6 +328,46 @@ describe('--status query functionality', () => {
|
|
|
328
328
|
);
|
|
329
329
|
});
|
|
330
330
|
|
|
331
|
+
it('should indent nested process ID arrays in links-notation', () => {
|
|
332
|
+
if (process.platform === 'win32') {
|
|
333
|
+
console.log(
|
|
334
|
+
' Skipping: POSIX screen/pgrep process tree fixture is not available on Windows'
|
|
335
|
+
);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const executingRecord = new ExecutionRecord({
|
|
340
|
+
command: 'sleep 100',
|
|
341
|
+
pid: 667105,
|
|
342
|
+
logPath: '/tmp/executing.log',
|
|
343
|
+
options: {
|
|
344
|
+
isolated: 'screen',
|
|
345
|
+
isolationMode: 'detached',
|
|
346
|
+
sessionName: 'issue-126-screen',
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
store.save(executingRecord);
|
|
350
|
+
|
|
351
|
+
const result = runCli(['--status', executingRecord.uuid], {
|
|
352
|
+
PATH: `${path.join(__dirname, 'fixtures', 'issue-126-bin')}${path.delimiter}${process.env.PATH}`,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
expect(result.exitCode).toBe(0);
|
|
356
|
+
expect(result.stdout).toContain(
|
|
357
|
+
[
|
|
358
|
+
' processIds',
|
|
359
|
+
' wrapperPid 667105',
|
|
360
|
+
' screenPid 667120',
|
|
361
|
+
' commandPids',
|
|
362
|
+
' (',
|
|
363
|
+
' 667121',
|
|
364
|
+
' 667122',
|
|
365
|
+
' )',
|
|
366
|
+
].join('\n')
|
|
367
|
+
);
|
|
368
|
+
expect(result.stdout).not.toContain('\n(\n');
|
|
369
|
+
});
|
|
370
|
+
|
|
331
371
|
it('should include Current Time in text format for executing commands', () => {
|
|
332
372
|
const executingRecord = new ExecutionRecord({
|
|
333
373
|
command: 'sleep 100',
|