@phpsandbox/sdk 0.1.1 → 0.1.4
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 +59 -22
- package/dist/browser/phpsandbox-sdk.esm.js +562 -265
- package/dist/browser/phpsandbox-sdk.esm.min.js +24 -24
- package/dist/browser/phpsandbox-sdk.iife.js +562 -265
- package/dist/browser/phpsandbox-sdk.iife.min.js +24 -24
- package/dist/composer.d.ts +36 -23
- package/dist/composer.js +61 -11
- package/dist/container.d.ts +2 -6
- package/dist/container.js +0 -28
- package/dist/filesystem.d.ts +74 -18
- package/dist/filesystem.js +219 -62
- package/dist/index.d.ts +18 -9
- package/dist/index.js +21 -8
- package/dist/log.d.ts +11 -5
- package/dist/log.js +10 -7
- package/dist/metrics.d.ts +10 -0
- package/dist/metrics.js +13 -0
- package/dist/ports.d.ts +10 -0
- package/dist/ports.js +13 -0
- package/dist/runtime/rest.js +1 -4
- package/dist/runtime-telemetry.d.ts +15 -0
- package/dist/runtime-telemetry.js +111 -0
- package/dist/schemas/publications.d.ts +10 -10
- package/dist/services.d.ts +2 -9
- package/dist/services.js +3 -10
- package/dist/shell.d.ts +9 -0
- package/dist/shell.js +4 -4
- package/dist/terminal.d.ts +17 -11
- package/dist/terminal.js +27 -44
- package/docs/api-summary.md +28 -23
- package/docs/cdn-usage.md +2 -2
- package/docs/getting-started.md +13 -13
- package/examples/basic.ts +1 -1
- package/examples/git-merge-smoke.ts +1 -4
- package/examples/laravel-load-test.ts +67 -80
- package/examples/notebook-readonly-smoke.ts +30 -33
- package/package.json +1 -1
package/docs/getting-started.md
CHANGED
|
@@ -48,18 +48,18 @@ API notebook creates and forks are ephemeral by default. Use `persistent: true`
|
|
|
48
48
|
## 3. Write and Read Files
|
|
49
49
|
|
|
50
50
|
```ts
|
|
51
|
-
await notebook.
|
|
51
|
+
await notebook.files.write('hello.php', '<?php echo "Hello";');
|
|
52
52
|
|
|
53
|
-
const data = await notebook.
|
|
53
|
+
const data = await notebook.files.read('hello.php');
|
|
54
54
|
const text = new TextDecoder().decode(data);
|
|
55
55
|
|
|
56
56
|
console.log(text);
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
The same `write()` method accepts text, bytes, readable streams, and async iterables:
|
|
60
60
|
|
|
61
61
|
```ts
|
|
62
|
-
await notebook.
|
|
62
|
+
await notebook.files.write('binary.dat', new Uint8Array([1, 2, 3]), {
|
|
63
63
|
create: true,
|
|
64
64
|
overwrite: true,
|
|
65
65
|
unlock: false,
|
|
@@ -72,7 +72,7 @@ await notebook.file.writeFile('binary.dat', new Uint8Array([1, 2, 3]), {
|
|
|
72
72
|
### Terminal (stream output)
|
|
73
73
|
|
|
74
74
|
```ts
|
|
75
|
-
const task = await notebook.
|
|
75
|
+
const task = await notebook.terminals.create({ command: 'php', args: ['hello.php'] });
|
|
76
76
|
const reader = task.output.getReader();
|
|
77
77
|
|
|
78
78
|
try {
|
|
@@ -85,14 +85,14 @@ try {
|
|
|
85
85
|
reader.releaseLock();
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
const exitCode = await task.
|
|
88
|
+
const exitCode = await task.wait();
|
|
89
89
|
console.log(`\nExit code: ${exitCode}`);
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
### Shell (single command result)
|
|
93
93
|
|
|
94
94
|
```ts
|
|
95
|
-
const result = await notebook.
|
|
95
|
+
const result = await notebook.exec('php -v', { tty: false });
|
|
96
96
|
result.throw();
|
|
97
97
|
console.log(result.stdout);
|
|
98
98
|
```
|
|
@@ -100,14 +100,14 @@ console.log(result.stdout);
|
|
|
100
100
|
## 5. Search and Watch Files
|
|
101
101
|
|
|
102
102
|
```ts
|
|
103
|
-
const matches = await notebook.
|
|
103
|
+
const matches = await notebook.files.find('*.php', {
|
|
104
104
|
includes: ['app/**'],
|
|
105
105
|
excludes: ['vendor/**'],
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
console.log(matches.map((m) => m.path));
|
|
109
109
|
|
|
110
|
-
const watcher = notebook.
|
|
110
|
+
const watcher = notebook.files.watch(
|
|
111
111
|
'/app',
|
|
112
112
|
{
|
|
113
113
|
recursive: true,
|
|
@@ -125,8 +125,8 @@ watcher.dispose();
|
|
|
125
125
|
## 6. Use Composer and Git
|
|
126
126
|
|
|
127
127
|
```ts
|
|
128
|
-
await notebook.composer.install(
|
|
129
|
-
await notebook.composer.require({ packages: ['monolog/monolog'] });
|
|
128
|
+
(await notebook.composer.install().wait()).throw();
|
|
129
|
+
(await notebook.composer.require({ packages: ['monolog/monolog'] }).wait()).throw();
|
|
130
130
|
|
|
131
131
|
await notebook.git.checkpoint('Jane Doe <jane@example.com>', 'Initial checkpoint');
|
|
132
132
|
```
|
|
@@ -142,8 +142,8 @@ notebook.dispose();
|
|
|
142
142
|
## Common Pitfalls
|
|
143
143
|
|
|
144
144
|
- `get()` notebooks must call `await notebook.ready()` before tool calls.
|
|
145
|
-
- `
|
|
146
|
-
-
|
|
145
|
+
- `files.watch()` requires an `excludes` array.
|
|
146
|
+
- Use `notebook.exec()` for one-shot commands; for streaming shell handles, await `process.wait()` and call `result.throw()` to fail on non-zero exit codes.
|
|
147
147
|
|
|
148
148
|
## Next Reading
|
|
149
149
|
|
package/examples/basic.ts
CHANGED
|
@@ -16,7 +16,7 @@ console.log('\n');
|
|
|
16
16
|
|
|
17
17
|
console.log('====== Read routes/web.php in the notebook ======');
|
|
18
18
|
const decoder = new TextDecoder();
|
|
19
|
-
const content = await notebook.
|
|
19
|
+
const content = await notebook.files.read('routes/web.php');
|
|
20
20
|
console.log(decoder.decode(content));
|
|
21
21
|
|
|
22
22
|
// Always close the notebook when done to clean up the connection.
|
|
@@ -54,10 +54,7 @@ async function main(): Promise<void> {
|
|
|
54
54
|
|
|
55
55
|
await fork.ready();
|
|
56
56
|
const editContent = buildEditContent(base.data.id, forkNotebookId, options);
|
|
57
|
-
|
|
58
|
-
if (!written) {
|
|
59
|
-
throw new Error(`fs.write returned false for ${options.editPath}.`);
|
|
60
|
-
}
|
|
57
|
+
await fork.files.write(options.editPath, editContent);
|
|
61
58
|
console.log(`Edited fork file: ${options.editPath}`);
|
|
62
59
|
|
|
63
60
|
const forkCheckpoint = await fork.git.checkpoint(options.author, `Smoke edit in fork ${forkNotebookId}`, options.ref, false);
|
|
@@ -389,11 +389,10 @@ async function runScenario(
|
|
|
389
389
|
let notebook: NotebookInstance | null = null;
|
|
390
390
|
let previewUrl: string | undefined;
|
|
391
391
|
let logsCaptured = 0;
|
|
392
|
-
let
|
|
392
|
+
let logReader: ReadableStreamDefaultReader<import('../src/log.js').RuntimeLogEntry> | null = null;
|
|
393
393
|
let initTrace: InitTrace | undefined;
|
|
394
394
|
let readyResult: NotebookInitSuccessResult | undefined;
|
|
395
395
|
let initTraceDisposables: Disposable[] = [];
|
|
396
|
-
let telemetryEnabled = false;
|
|
397
396
|
let redisRunning = false;
|
|
398
397
|
|
|
399
398
|
const routeSlug = id.toLowerCase();
|
|
@@ -560,12 +559,19 @@ async function runScenario(
|
|
|
560
559
|
}
|
|
561
560
|
|
|
562
561
|
if (options.mode !== 'init') {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
562
|
+
logReader = notebook.logs.follow().getReader();
|
|
563
|
+
void (async () => {
|
|
564
|
+
while (logReader) {
|
|
565
|
+
const entry = await logReader.read();
|
|
566
|
+
if (entry.done) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
if (entry.value.message.trim().length > 0) {
|
|
570
|
+
logsCaptured += 1;
|
|
571
|
+
ui.updateScenarioLogs(id, logsCaptured);
|
|
572
|
+
}
|
|
567
573
|
}
|
|
568
|
-
});
|
|
574
|
+
})().catch(() => {});
|
|
569
575
|
|
|
570
576
|
await runCheckedStep('notebook.ping', () => notebook!.ping(), { continueOnError: false });
|
|
571
577
|
|
|
@@ -599,8 +605,8 @@ async function runScenario(
|
|
|
599
605
|
});
|
|
600
606
|
|
|
601
607
|
await runCheckedStep('container.state', async () => {
|
|
602
|
-
const [state, ports] = await Promise.all([notebook!.container.state(), notebook!.
|
|
603
|
-
const portInfo = assertPortInfoList(ports, '
|
|
608
|
+
const [state, ports] = await Promise.all([notebook!.container.state(), notebook!.ports.list()]);
|
|
609
|
+
const portInfo = assertPortInfoList(ports, 'ports.list');
|
|
604
610
|
return `${state.state} (${describePorts(portInfo)})`;
|
|
605
611
|
});
|
|
606
612
|
|
|
@@ -609,11 +615,11 @@ async function runScenario(
|
|
|
609
615
|
'container.restart',
|
|
610
616
|
async () => {
|
|
611
617
|
await notebook!.container.start();
|
|
612
|
-
const [state, ports] = await Promise.all([notebook!.container.state(), notebook!.
|
|
618
|
+
const [state, ports] = await Promise.all([notebook!.container.state(), notebook!.ports.list()]);
|
|
613
619
|
if (state.state !== 'running') {
|
|
614
620
|
throw new Error(`Expected running container after restart, got ${state.state}`);
|
|
615
621
|
}
|
|
616
|
-
return `${state.state} (${describePorts(assertPortInfoList(ports, '
|
|
622
|
+
return `${state.state} (${describePorts(assertPortInfoList(ports, 'ports.list after restart'))})`;
|
|
617
623
|
},
|
|
618
624
|
{ timeoutMs: options.stepTimeoutMs * 3, continueOnError: false }
|
|
619
625
|
);
|
|
@@ -626,29 +632,20 @@ async function runScenario(
|
|
|
626
632
|
async () =>
|
|
627
633
|
retryWithBackoff(
|
|
628
634
|
async () => {
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
'
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
options.telemetryTimeoutMs,
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
telemetryEnabled = true;
|
|
644
|
-
|
|
645
|
-
return await firstFulfilled([
|
|
646
|
-
statsPromise.then(
|
|
647
|
-
(stats) =>
|
|
648
|
-
`cpu=${stats.cpu.usage}/${stats.cpu.limit} memory=${stats.memory.usage}/${stats.memory.limit}`
|
|
649
|
-
),
|
|
650
|
-
portsPromise.then((ports) => describePorts(assertPortInfoList(ports, 'container.ports'))),
|
|
651
|
-
]);
|
|
635
|
+
const statsReader = notebook!.metrics.watch().getReader();
|
|
636
|
+
const portsReader = notebook!.ports.watch().getReader();
|
|
637
|
+
try {
|
|
638
|
+
const statsPromise = withTimeout(statsReader.read(), options.telemetryTimeoutMs, 'metrics.watch')
|
|
639
|
+
.then(({ value }) => {
|
|
640
|
+
if (!value) throw new Error('metrics.watch ended before a sample was received.');
|
|
641
|
+
return `cpu=${value.cpu.usage}/${value.cpu.limit} memory=${value.memory.usage}/${value.memory.limit}`;
|
|
642
|
+
});
|
|
643
|
+
const portsPromise = withTimeout(portsReader.read(), options.telemetryTimeoutMs, 'ports.watch')
|
|
644
|
+
.then(({ value }) => describePorts(assertPortInfoList(value ?? [], 'ports.watch')));
|
|
645
|
+
return await firstFulfilled([statsPromise, portsPromise]);
|
|
646
|
+
} finally {
|
|
647
|
+
await Promise.all([statsReader.cancel(), portsReader.cancel()]);
|
|
648
|
+
}
|
|
652
649
|
},
|
|
653
650
|
TELEMETRY_RETRY_DELAYS_MS,
|
|
654
651
|
shouldRetryTelemetryError
|
|
@@ -660,7 +657,7 @@ async function runScenario(
|
|
|
660
657
|
'filesystem.watch-and-write',
|
|
661
658
|
async () => {
|
|
662
659
|
const changeSeen = createDeferred<FileChange>();
|
|
663
|
-
const watch = await notebook!.
|
|
660
|
+
const watch = await notebook!.files.watch(
|
|
664
661
|
'routes',
|
|
665
662
|
{ recursive: true, excludes: ['vendor/**', 'node_modules/**'] },
|
|
666
663
|
(change) => {
|
|
@@ -671,12 +668,9 @@ async function runScenario(
|
|
|
671
668
|
);
|
|
672
669
|
|
|
673
670
|
try {
|
|
674
|
-
const current = textDecoder.decode(await notebook!.
|
|
671
|
+
const current = textDecoder.decode(await notebook!.files.read(routePath));
|
|
675
672
|
const next = appendRouteDefinition(current, id, routeUrlPath);
|
|
676
|
-
|
|
677
|
-
if (!written) {
|
|
678
|
-
throw new Error(`fs.write returned false for ${routePath}.`);
|
|
679
|
-
}
|
|
673
|
+
await notebook!.files.write(routePath, next);
|
|
680
674
|
|
|
681
675
|
const change = await withTimeout(
|
|
682
676
|
changeSeen.promise,
|
|
@@ -692,14 +686,13 @@ async function runScenario(
|
|
|
692
686
|
);
|
|
693
687
|
|
|
694
688
|
await runCheckedStep('filesystem.inspect', async () => {
|
|
695
|
-
const [raw,
|
|
696
|
-
notebook!.
|
|
697
|
-
notebook!.
|
|
698
|
-
notebook!.
|
|
699
|
-
notebook!.
|
|
700
|
-
notebook!.
|
|
701
|
-
notebook!.
|
|
702
|
-
notebook!.file.tail(routePath, 20),
|
|
689
|
+
const [raw, stat, directory, tree, found, tail] = await Promise.all([
|
|
690
|
+
notebook!.files.read(routePath),
|
|
691
|
+
notebook!.files.stat(routePath),
|
|
692
|
+
notebook!.files.list('routes'),
|
|
693
|
+
notebook!.files.tree('routes'),
|
|
694
|
+
notebook!.files.find(routeFileName, { includes: ['routes/**'], maxResults: 10 }),
|
|
695
|
+
notebook!.files.tail(routePath, { lines: 20 }),
|
|
703
696
|
]);
|
|
704
697
|
|
|
705
698
|
const text = textDecoder.decode(raw);
|
|
@@ -707,7 +700,7 @@ async function runScenario(
|
|
|
707
700
|
throw new Error(`Expected ${routeUrlPath} in ${routePath}.`);
|
|
708
701
|
}
|
|
709
702
|
|
|
710
|
-
const [, matches] = await notebook!.
|
|
703
|
+
const [, matches] = await notebook!.files.search(
|
|
711
704
|
{ pattern: routeUrlPath },
|
|
712
705
|
{ includes: [routePath], excludes: [], maxResults: 10 }
|
|
713
706
|
);
|
|
@@ -716,12 +709,12 @@ async function runScenario(
|
|
|
716
709
|
throw new Error(`file.search() did not find ${routeUrlPath}.`);
|
|
717
710
|
}
|
|
718
711
|
|
|
719
|
-
const exists = await notebook!.
|
|
712
|
+
const exists = await notebook!.files.exists(routePath);
|
|
720
713
|
if (!exists) {
|
|
721
714
|
throw new Error(`${routePath} does not exist after write.`);
|
|
722
715
|
}
|
|
723
716
|
|
|
724
|
-
return `size=${stat.size}
|
|
717
|
+
return `size=${stat.size} dir=${directory.length} tree-lines=${tree.split('\n').length} find=${found.length} tail=${tail.length}`;
|
|
725
718
|
});
|
|
726
719
|
|
|
727
720
|
if (options.includePreview) {
|
|
@@ -732,7 +725,10 @@ async function runScenario(
|
|
|
732
725
|
|
|
733
726
|
if (options.includeDownload) {
|
|
734
727
|
await runCheckedStep('filesystem.download', async () => {
|
|
735
|
-
const archive = await notebook!.
|
|
728
|
+
const archive = await notebook!.files.download({
|
|
729
|
+
exclude: ['vendor/**', 'node_modules/**'],
|
|
730
|
+
include: [routePath, 'composer.json'],
|
|
731
|
+
});
|
|
736
732
|
if (archive.size === 0) {
|
|
737
733
|
throw new Error('file.download() returned an empty blob.');
|
|
738
734
|
}
|
|
@@ -784,12 +780,12 @@ async function runScenario(
|
|
|
784
780
|
);
|
|
785
781
|
|
|
786
782
|
await runCheckedStep(
|
|
787
|
-
'
|
|
783
|
+
'terminals.create',
|
|
788
784
|
async () => {
|
|
789
|
-
const process = await notebook!.
|
|
790
|
-
'php',
|
|
791
|
-
['artisan', 'route:list', `--path=${routeUrlPath}`]
|
|
792
|
-
);
|
|
785
|
+
const process = await notebook!.terminals.create({
|
|
786
|
+
command: 'php',
|
|
787
|
+
args: ['artisan', 'route:list', `--path=${routeUrlPath}`],
|
|
788
|
+
});
|
|
793
789
|
|
|
794
790
|
try {
|
|
795
791
|
const outputPromise = readStreamToString(process.output, options.streamTimeoutMs, options.maxStreamChars);
|
|
@@ -797,7 +793,7 @@ async function runScenario(
|
|
|
797
793
|
const [output, exitCode] = await Promise.all([outputPromise, exitPromise]);
|
|
798
794
|
|
|
799
795
|
if (exitCode !== 0) {
|
|
800
|
-
throw new Error(`
|
|
796
|
+
throw new Error(`terminals.create() exited with ${exitCode}: ${trimDetail(output)}`);
|
|
801
797
|
}
|
|
802
798
|
|
|
803
799
|
if (!output.includes(routeUrlPath)) {
|
|
@@ -820,7 +816,7 @@ async function runScenario(
|
|
|
820
816
|
redisRunning = started.running || started.state === 'running' || started.state === 'starting';
|
|
821
817
|
|
|
822
818
|
const stream = notebook!.services.logs('redis', { follow: true, tail: 25 });
|
|
823
|
-
const reader = stream.
|
|
819
|
+
const reader = stream.getReader();
|
|
824
820
|
|
|
825
821
|
try {
|
|
826
822
|
const [snapshot, chunk] = await Promise.all([
|
|
@@ -837,7 +833,6 @@ async function runScenario(
|
|
|
837
833
|
} finally {
|
|
838
834
|
await reader.cancel().catch(() => undefined);
|
|
839
835
|
reader.releaseLock();
|
|
840
|
-
await stream.stop().catch(() => false);
|
|
841
836
|
await notebook!.services.stop('redis').catch(() => started);
|
|
842
837
|
redisRunning = false;
|
|
843
838
|
}
|
|
@@ -847,25 +842,20 @@ async function runScenario(
|
|
|
847
842
|
|
|
848
843
|
await runCheckedStep('composer.metadata', async () => {
|
|
849
844
|
let composerLogChunks = 0;
|
|
850
|
-
const
|
|
851
|
-
|
|
845
|
+
const dump = notebook!.composer.dumpAutoload();
|
|
846
|
+
for await (const chunk of dump.output) {
|
|
847
|
+
if (chunk.data.byteLength > 0) {
|
|
852
848
|
composerLogChunks += 1;
|
|
853
849
|
}
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
try {
|
|
857
|
-
const [packages, dump] = await Promise.all([notebook!.composer.packages(), notebook!.composer.dumpAutoload()]);
|
|
858
|
-
if (!dump.success) {
|
|
859
|
-
throw new Error(dump.message || 'composer.dumpAutoload() failed.');
|
|
860
|
-
}
|
|
861
|
-
if (packages.length === 0) {
|
|
862
|
-
throw new Error('composer.packages() returned no packages.');
|
|
863
|
-
}
|
|
850
|
+
}
|
|
864
851
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
852
|
+
(await dump.wait()).throw();
|
|
853
|
+
const packages = await notebook!.composer.packages();
|
|
854
|
+
if (packages.length === 0) {
|
|
855
|
+
throw new Error('composer.packages() returned no packages.');
|
|
868
856
|
}
|
|
857
|
+
|
|
858
|
+
return `packages=${packages.length} composer-log-events=${composerLogChunks}`;
|
|
869
859
|
}, { timeoutMs: Math.max(options.stepTimeoutMs * 2, 120_000) });
|
|
870
860
|
|
|
871
861
|
await runCheckedStep('git.checkpoint', async () => {
|
|
@@ -960,11 +950,8 @@ async function runScenario(
|
|
|
960
950
|
await notebook.services.stop('redis').catch(() => undefined);
|
|
961
951
|
}
|
|
962
952
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
logListener?.dispose();
|
|
953
|
+
await logReader?.cancel().catch(() => undefined);
|
|
954
|
+
logReader = null;
|
|
968
955
|
for (const disposable of initTraceDisposables) {
|
|
969
956
|
disposable.dispose();
|
|
970
957
|
}
|
|
@@ -155,12 +155,12 @@ async function main(): Promise<void> {
|
|
|
155
155
|
}, false);
|
|
156
156
|
|
|
157
157
|
await recordStep(steps, 'container.opened-ports', options.stepTimeoutMs, async () => {
|
|
158
|
-
const ports = await notebook!.
|
|
159
|
-
return describePorts(assertPortInfoList(ports, '
|
|
158
|
+
const ports = await notebook!.ports.list();
|
|
159
|
+
return describePorts(assertPortInfoList(ports, 'ports.list'));
|
|
160
160
|
}, false);
|
|
161
161
|
|
|
162
162
|
const rootSnapshot = await recordStep(steps, 'filesystem.root', options.stepTimeoutMs, async () => {
|
|
163
|
-
const rootEntries = await notebook!.
|
|
163
|
+
const rootEntries = await notebook!.files.list('/');
|
|
164
164
|
|
|
165
165
|
const preferred = ['composer.json', 'index.php', 'artisan'];
|
|
166
166
|
const rootNames = new Set(rootEntries.map(([name]) => name));
|
|
@@ -174,7 +174,7 @@ async function main(): Promise<void> {
|
|
|
174
174
|
const treePath = rootSnapshot.candidate === 'artisan' ? 'routes' : undefined;
|
|
175
175
|
if (treePath) {
|
|
176
176
|
await recordStep(steps, 'filesystem.tree', options.stepTimeoutMs, async () => {
|
|
177
|
-
const tree = await notebook!.
|
|
177
|
+
const tree = await notebook!.files.tree(treePath);
|
|
178
178
|
return `path=${treePath} bytes=${tree.length}`;
|
|
179
179
|
}, false);
|
|
180
180
|
} else {
|
|
@@ -183,8 +183,8 @@ async function main(): Promise<void> {
|
|
|
183
183
|
|
|
184
184
|
const findSnapshot = await recordStep(steps, 'filesystem.find', options.stepTimeoutMs, async () => {
|
|
185
185
|
const [composerFiles, indexFiles] = await Promise.all([
|
|
186
|
-
notebook!.
|
|
187
|
-
notebook!.
|
|
186
|
+
notebook!.files.find('composer.json', { maxResults: 10 }),
|
|
187
|
+
notebook!.files.find('index.php', { maxResults: 10 }),
|
|
188
188
|
]);
|
|
189
189
|
const candidate = [...composerFiles, ...indexFiles].find((file) => typeof file.path === 'string')?.path;
|
|
190
190
|
return {
|
|
@@ -197,15 +197,13 @@ async function main(): Promise<void> {
|
|
|
197
197
|
|
|
198
198
|
if (candidate) {
|
|
199
199
|
await recordStep(steps, 'filesystem.read-candidate', options.stepTimeoutMs, async () => {
|
|
200
|
-
const [
|
|
201
|
-
notebook!.
|
|
202
|
-
notebook!.
|
|
203
|
-
notebook!.
|
|
204
|
-
notebook!.file.tail(candidate, 10),
|
|
200
|
+
const [stat, content, tail] = await Promise.all([
|
|
201
|
+
notebook!.files.stat(candidate),
|
|
202
|
+
notebook!.files.read(candidate),
|
|
203
|
+
notebook!.files.tail(candidate, { lines: 10 }),
|
|
205
204
|
]);
|
|
206
205
|
|
|
207
|
-
|
|
208
|
-
return `path=${candidate} size=${stat.size} binary=${info.binary} sha256=${sha256(bytes)} tailBytes=${tail.length}`;
|
|
206
|
+
return `path=${candidate} size=${stat.size} sha256=${sha256(content)} tailBytes=${tail.length}`;
|
|
209
207
|
}, false);
|
|
210
208
|
} else {
|
|
211
209
|
recordSkippedStep(steps, 'filesystem.read-candidate', 'No readable candidate file found.');
|
|
@@ -214,7 +212,10 @@ async function main(): Promise<void> {
|
|
|
214
212
|
if (options.includeDownload) {
|
|
215
213
|
if (candidate) {
|
|
216
214
|
await recordStep(steps, 'filesystem.download', options.stepTimeoutMs, async () => {
|
|
217
|
-
const archive = await notebook!.
|
|
215
|
+
const archive = await notebook!.files.download({
|
|
216
|
+
exclude: ['vendor/**', 'node_modules/**'],
|
|
217
|
+
include: [candidate],
|
|
218
|
+
});
|
|
218
219
|
if (archive.size === 0) {
|
|
219
220
|
throw new Error('file.download() returned an empty blob.');
|
|
220
221
|
}
|
|
@@ -228,7 +229,7 @@ async function main(): Promise<void> {
|
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
await recordStep(steps, 'filesystem.search', options.stepTimeoutMs, async () => {
|
|
231
|
-
const [hasMore, matches] = await notebook!.
|
|
232
|
+
const [hasMore, matches] = await notebook!.files.search({
|
|
232
233
|
pattern: 'php',
|
|
233
234
|
isRegExp: false,
|
|
234
235
|
isCaseSensitive: false,
|
|
@@ -260,7 +261,7 @@ async function main(): Promise<void> {
|
|
|
260
261
|
await runExecutionChecks(notebook, steps, options);
|
|
261
262
|
} else {
|
|
262
263
|
recordSkippedStep(steps, 'shell.run', 'Enable with --include-exec.');
|
|
263
|
-
recordSkippedStep(steps, '
|
|
264
|
+
recordSkippedStep(steps, 'terminals.create', 'Enable with --include-exec.');
|
|
264
265
|
recordSkippedStep(steps, 'terminal.interactive', 'Enable with --include-exec.');
|
|
265
266
|
recordSkippedStep(steps, 'terminal.osc', 'Enable with --include-exec.');
|
|
266
267
|
recordSkippedStep(steps, 'repl.eval', 'Enable with --include-exec.');
|
|
@@ -332,26 +333,25 @@ async function runExecutionChecks(notebook: NotebookInstance, steps: StepArtifac
|
|
|
332
333
|
return `exit=${result.exitCode}`;
|
|
333
334
|
}, false);
|
|
334
335
|
|
|
335
|
-
await recordStep(steps, '
|
|
336
|
-
const process = await notebook.
|
|
336
|
+
await recordStep(steps, 'terminals.create', options.stepTimeoutMs, async () => {
|
|
337
|
+
const process = await notebook.terminals.create({ command: 'php', args: ['-r', 'echo "sdk-smoke";'] });
|
|
337
338
|
const [exitCode, output] = await Promise.all([
|
|
338
339
|
withTimeout(process.wait(), options.stepTimeoutMs, 'waiting for terminal process exit'),
|
|
339
340
|
readStreamToString(process.output, options.streamTimeoutMs, options.maxStreamChars),
|
|
340
341
|
]);
|
|
341
342
|
|
|
342
343
|
if (exitCode !== 0 || !output.includes('sdk-smoke')) {
|
|
343
|
-
throw new Error(`
|
|
344
|
+
throw new Error(`terminals.create failed: exit=${exitCode} output=${trimDetail(output)}`);
|
|
344
345
|
}
|
|
345
346
|
|
|
346
347
|
return `exit=${exitCode}`;
|
|
347
348
|
}, false);
|
|
348
349
|
|
|
349
350
|
await recordStep(steps, 'terminal.interactive', options.stepTimeoutMs, async () => {
|
|
350
|
-
const process = await notebook.
|
|
351
|
-
'bash',
|
|
352
|
-
['-lc', 'IFS= read -r line; printf "sdk-input:%s\\n" "$line"'],
|
|
353
|
-
|
|
354
|
-
);
|
|
351
|
+
const process = await notebook.terminals.create({
|
|
352
|
+
command: 'bash',
|
|
353
|
+
args: ['-lc', 'IFS= read -r line; printf "sdk-input:%s\\n" "$line"'],
|
|
354
|
+
});
|
|
355
355
|
|
|
356
356
|
try {
|
|
357
357
|
const writer = process.input.getWriter();
|
|
@@ -374,15 +374,12 @@ async function runExecutionChecks(notebook: NotebookInstance, steps: StepArtifac
|
|
|
374
374
|
}, false);
|
|
375
375
|
|
|
376
376
|
await recordStep(steps, 'terminal.osc', options.stepTimeoutMs, async () => {
|
|
377
|
-
const process = await notebook.
|
|
378
|
-
'bash',
|
|
379
|
-
['-lc', 'printf "sdk-prompt$"'],
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
terminal: { cols: 80, rows: 15 },
|
|
384
|
-
}
|
|
385
|
-
);
|
|
377
|
+
const process = await notebook.terminals.create({
|
|
378
|
+
command: 'bash',
|
|
379
|
+
args: ['-lc', 'printf "sdk-prompt$"'],
|
|
380
|
+
osc: true,
|
|
381
|
+
size: [80, 15],
|
|
382
|
+
});
|
|
386
383
|
|
|
387
384
|
try {
|
|
388
385
|
const [exitCode, output] = await Promise.all([
|
package/package.json
CHANGED