@opencraw/studio 0.0.26 → 0.0.28

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.
Files changed (47) hide show
  1. package/dist/browser-provisioning/install-chromium.use-case.d.ts +13 -0
  2. package/dist/document-view/workbook-view.mapper.d.ts +8 -0
  3. package/dist/folder-dialog/choose-folder.client.d.ts +25 -0
  4. package/dist/folder-dialog/folder-dialog.algorithm.d.ts +25 -0
  5. package/dist/folder-dialog/index.d.ts +2 -0
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.esm.js +355 -8
  8. package/dist/main.d.ts +2 -0
  9. package/dist/recipe-workspace/index.d.ts +1 -0
  10. package/dist/recipe-workspace/open-path.use-case.d.ts +18 -0
  11. package/dist/src/browser-provisioning/install-chromium.use-case.d.ts +13 -0
  12. package/dist/src/document-view/workbook-view.mapper.d.ts +8 -0
  13. package/dist/src/folder-dialog/choose-folder.client.d.ts +25 -0
  14. package/dist/src/folder-dialog/folder-dialog.algorithm.d.ts +25 -0
  15. package/dist/src/folder-dialog/index.d.ts +2 -0
  16. package/dist/src/index.d.ts +2 -2
  17. package/dist/src/main.d.ts +2 -0
  18. package/dist/src/recipe-workspace/index.d.ts +1 -0
  19. package/dist/src/recipe-workspace/open-path.use-case.d.ts +18 -0
  20. package/dist/src/studio-api/choose-folder-view.contract.d.ts +7 -0
  21. package/dist/src/studio-api/command.contract.d.ts +18 -0
  22. package/dist/src/studio-api/deck-view.contract.d.ts +8 -0
  23. package/dist/src/studio-api/grid-view.contract.d.ts +8 -0
  24. package/dist/src/studio-api/index.d.ts +6 -2
  25. package/dist/src/studio-api/open-path-view.contract.d.ts +23 -0
  26. package/dist/src/studio-server/choose-folder.handler.d.ts +13 -0
  27. package/dist/src/studio-server/open-path.handler.d.ts +11 -0
  28. package/dist/src/studio-server/studio-http.use-case.d.ts +7 -0
  29. package/dist/src/studio-server/workspace.store.d.ts +4 -0
  30. package/dist/studio-api/choose-folder-view.contract.d.ts +7 -0
  31. package/dist/studio-api/command.contract.d.ts +18 -0
  32. package/dist/studio-api/deck-view.contract.d.ts +8 -0
  33. package/dist/studio-api/grid-view.contract.d.ts +8 -0
  34. package/dist/studio-api/index.d.ts +6 -2
  35. package/dist/studio-api/open-path-view.contract.d.ts +23 -0
  36. package/dist/studio-server/choose-folder.handler.d.ts +13 -0
  37. package/dist/studio-server/open-path.handler.d.ts +11 -0
  38. package/dist/studio-server/studio-http.use-case.d.ts +7 -0
  39. package/dist/studio-server/workspace.store.d.ts +4 -0
  40. package/dist/ui/apple-touch-icon.png +0 -0
  41. package/dist/ui/assets/index-CMzh4YyN.js +38 -0
  42. package/dist/ui/assets/{pdf-B1dO9f8r.js → pdf-lT-wVEE5.js} +1 -1
  43. package/dist/ui/favicon.ico +0 -0
  44. package/dist/ui/favicon.svg +30 -0
  45. package/dist/ui/index.html +4 -2
  46. package/package.json +3 -3
  47. package/dist/ui/assets/index-IlNz2Qwr.js +0 -38
@@ -1,2 +1,15 @@
1
+ /**
2
+ * How the install is run: the current executable as a Node, on Playwright's own CLI. Inside the desktop app
3
+ * the executable is Electron, which runs as plain Node only with `ELECTRON_RUN_AS_NODE` (otherwise it would
4
+ * start a second copy of the app); under Node the variable does nothing.
5
+ *
6
+ * @param cliPath - Playwright's `cli.js`.
7
+ * @returns What to hand to `execFile`.
8
+ */
9
+ export declare function installInvocation(cliPath: string): {
10
+ file: string;
11
+ args: string[];
12
+ env: NodeJS.ProcessEnv;
13
+ };
1
14
  export declare function installChromiumOnce(run?: () => Promise<void>): Promise<void>;
2
15
  //# sourceMappingURL=install-chromium.use-case.d.ts.map
@@ -24,6 +24,14 @@ export interface GridSheetView {
24
24
  columnCount: number;
25
25
  rows: GridCellView[][];
26
26
  merges: GridMergeView[];
27
+ /** Column widths in characters by 0-based column, as the spreadsheet stores them; absent keys are columns the file does not size. */
28
+ columnWidths?: Record<string, number>;
29
+ /** Row heights in points by 0-based row, as the spreadsheet stores them. */
30
+ rowHeights?: Record<string, number>;
31
+ /** The width of a column the file does not size, in characters. */
32
+ defaultColumnWidth?: number;
33
+ /** The height of a row the file does not size, in points. */
34
+ defaultRowHeight?: number;
27
35
  }
28
36
  /** What `grid-view` answers with (studio plan §3.4, issue #94's 5c): every sheet's cells, typed, with hidden sheets/rows marked and merged ranges resolved — the CSV/workbook grid's own view of the document, structurally the same idea as `pdf-view.mapper.ts`'s `PdfDocumentView`. */
29
37
  export interface WorkbookDocumentView {
@@ -0,0 +1,25 @@
1
+ import type { DialogCommand } from './folder-dialog.algorithm';
2
+ /** What a dialog program did: its output, or how it ended. `missing` means the program is not installed. */
3
+ export type DialogRun = {
4
+ output: string;
5
+ } | {
6
+ cancelled: true;
7
+ } | {
8
+ missing: true;
9
+ };
10
+ /** Runs one dialog program and waits for the person to answer; real by default, replaced in tests. */
11
+ export type RunDialog = (command: DialogCommand) => Promise<DialogRun>;
12
+ /** Runs the program with Node's own `execFile`: no shell, so a path or title is never parsed as a command. */
13
+ export declare const runDialogProgram: RunDialog;
14
+ /**
15
+ * Opens the operating system's own folder dialog on the machine the server
16
+ * runs on, which is where the recipe folder is: a browser tab cannot do it,
17
+ * the server can. Waits until the person picks a folder or closes the dialog.
18
+ *
19
+ * @param platform - `process.platform`; passed in so a test names the platform it means.
20
+ * @param run - How a dialog program is run.
21
+ * @returns The folder picked, or `undefined` when the dialog was closed without a choice.
22
+ * @throws Error when the platform has no dialog program installed (a server with no display, a container).
23
+ */
24
+ export declare function chooseFolderWithDialog(platform?: NodeJS.Platform, run?: RunDialog): Promise<string | undefined>;
25
+ //# sourceMappingURL=choose-folder.client.d.ts.map
@@ -0,0 +1,25 @@
1
+ /** One way to open a folder dialog: a program and its arguments. */
2
+ export interface DialogCommand {
3
+ file: string;
4
+ args: string[];
5
+ }
6
+ /**
7
+ * The programs that can open a folder dialog on a platform, in the order to
8
+ * try them: the first one installed is used. Windows and macOS have one each
9
+ * in every install; Linux has whichever of `zenity` (GNOME and most others)
10
+ * and `kdialog` (KDE) the desktop ships.
11
+ *
12
+ * @param platform - `process.platform`.
13
+ * @returns The candidates; empty for a platform with no known dialog.
14
+ */
15
+ export declare function folderDialogCommands(platform: NodeJS.Platform): DialogCommand[];
16
+ /**
17
+ * The folder a dialog printed: its output without the line break and the
18
+ * trailing separator (`osascript` ends a folder with `/`), a root kept as it
19
+ * is. Nothing printed means the person cancelled.
20
+ *
21
+ * @param output - What the dialog program wrote to its standard output.
22
+ * @returns The folder path, or `undefined` for a cancelled dialog.
23
+ */
24
+ export declare function folderFromDialogOutput(output: string): string | undefined;
25
+ //# sourceMappingURL=folder-dialog.algorithm.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { chooseFolderWithDialog } from './choose-folder.client';
2
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.d.ts CHANGED
@@ -9,6 +9,6 @@ export { runSample, loadRecipePair } from './sample-run';
9
9
  export type { RecipePair, SampleRunCallbacks, SampleRunHandle, SampleRunRecord, SampleRunRejected, SampleRunResult, SampleStepSummary, SampleStoppedBy } from './sample-run';
10
10
  export { fetchStartPage } from './page-snapshot';
11
11
  export type { WhyFacts, WhyStepFact } from './explain-why';
12
- export { sampleBudgetSchema, openWorkspaceCommandSchema, importDocumentCommandSchema, runSampleCommandSchema, stopRunCommandSchema, saveRecipeCommandSchema, fetchStartPageCommandSchema, saveOutlineCommandSchema, takeSnapshotCommandSchema, verifySelectorCommandSchema, inferSelectorCommandSchema, missingWhyTargetSchema, rejectedWhyTargetSchema, whyTargetSchema, explainWhyCommandSchema, studioCommandSchema, importDocumentViewSchema, stoppedBySchema, fieldTraceSchema, traceLineEventSchema, recordEventSchema, recordRejectedEventSchema, runFinishedEventSchema, workspaceChangedEventSchema, studioEventSchema, loadedHooksSchema, recipeIssueSchema, recipeListingSchema, workspaceViewSchema, sampleRunRecordSchema, sampleRunViewSchema, startPageViewSchema, snapshotViewSchema, verifySelectorViewSchema, fieldPickSchema, inferSelectorViewSchema, sentencePartSchema, outlineCardSchema, outlineBracketSchema, outlineNodeSchema, outlineViewSchema, whyViewSchema, inspectPageCommandSchema, responsesSeenCommandSchema, documentTreeCommandSchema, pdfViewCommandSchema, tablePreviewOptionsSchema, tablePreviewCommandSchema, regionPreviewCommandSchema, regionPreviewMatchSchema, regionPreviewViewSchema, gridViewCommandSchema, gridPreviewOptionsSchema, gridPreviewCommandSchema, deckViewCommandSchema, deckPreviewOptionsSchema, deckPreviewCommandSchema, domTreeNodeViewSchema, pageDataFindingSchema, inspectViewSchema, observedResponseSchema, responsesSeenViewSchema, documentTreeNodeViewSchema, documentTreeViewSchema, pdfCellViewSchema, pdfRowViewSchema, pdfPageViewSchema, pdfDocumentViewSchema, tablePreviewTableSchema, tablePreviewBandSchema, tablePreviewMatchSchema, tablePreviewViewSchema, gridCellViewSchema, gridMergeViewSchema, gridSheetViewSchema, workbookDocumentViewSchema, gridTablePreviewMatchSchema, gridTablePreviewViewSchema, deckShapeViewSchema, deckChartSeriesViewSchema, deckChartViewSchema, deckSlideViewSchema, deckDocumentViewSchema, deckTablePreviewMatchSchema, deckTablePreviewViewSchema, startRecordingCommandSchema, stopRecordingCommandSchema, recordingCardEventSchema, recordingNoteEventSchema, recordingStoppedEventSchema, } from './studio-api';
13
- export type { SampleBudget, OpenWorkspaceCommand, ImportDocumentCommand, RunSampleCommand, StopRunCommand, SaveRecipeCommand, FetchStartPageCommand, SaveOutlineCommand, TakeSnapshotCommand, VerifySelectorCommand, InferSelectorCommand, MissingWhyTarget, RejectedWhyTarget, WhyTarget, ExplainWhyCommand, InspectPageCommand, ResponsesSeenCommand, DocumentTreeCommand, PdfViewCommand, TablePreviewOptions, TablePreviewCommand, RegionPreviewCommand, GridViewCommand, GridPreviewOptions, GridPreviewCommand, DeckViewCommand, DeckPreviewOptions, DeckPreviewCommand, StudioCommand, ImportDocumentView, RegionPreviewMatchView, RegionPreviewView, StoppedBy, FieldTraceView, TraceLineEvent, RecordEvent, RecordRejectedEvent, RunFinishedEvent, WorkspaceChangedEvent, StudioEvent, LoadedHooks, RecipeIssue, RecipeListing, WorkspaceView, SampleRunView, StartPageView, SnapshotView, VerifySelectorView, FieldPick, InferSelectorView, SentencePart, OutlineCard, OutlineBracket, OutlineNode, OutlineView, WhyView, DomTreeNodeView, PageDataKind, PageDataFindingView, InspectView, ObservedResponseView, ResponsesSeenView, DocumentTreeNodeView, DocumentTreeView, PdfCellView, PdfRowView, PdfPageView, PdfDocumentView, TablePreviewTableView, TablePreviewBandView, TablePreviewMatchView, TablePreviewView, GridCellView, GridMergeView, GridSheetView, WorkbookDocumentView, GridTablePreviewMatchView, GridTablePreviewView, DeckShapeView, DeckChartSeriesView, DeckChartView, DeckSlideView, DeckDocumentView, DeckTablePreviewMatchView, DeckTablePreviewView, StartRecordingCommand, StopRecordingCommand, RecordingCardEvent, RecordingNoteEvent, RecordingStoppedEvent, } from './studio-api';
12
+ export { sampleBudgetSchema, openWorkspaceCommandSchema, chooseFolderCommandSchema, openPathCommandSchema, importDocumentCommandSchema, runSampleCommandSchema, stopRunCommandSchema, saveRecipeCommandSchema, fetchStartPageCommandSchema, saveOutlineCommandSchema, takeSnapshotCommandSchema, verifySelectorCommandSchema, inferSelectorCommandSchema, missingWhyTargetSchema, rejectedWhyTargetSchema, whyTargetSchema, explainWhyCommandSchema, studioCommandSchema, chooseFolderViewSchema, openPathViewSchema, importDocumentViewSchema, stoppedBySchema, fieldTraceSchema, traceLineEventSchema, recordEventSchema, recordRejectedEventSchema, runFinishedEventSchema, workspaceChangedEventSchema, studioEventSchema, loadedHooksSchema, recipeIssueSchema, recipeListingSchema, workspaceViewSchema, sampleRunRecordSchema, sampleRunViewSchema, startPageViewSchema, snapshotViewSchema, verifySelectorViewSchema, fieldPickSchema, inferSelectorViewSchema, sentencePartSchema, outlineCardSchema, outlineBracketSchema, outlineNodeSchema, outlineViewSchema, whyViewSchema, inspectPageCommandSchema, responsesSeenCommandSchema, documentTreeCommandSchema, pdfViewCommandSchema, tablePreviewOptionsSchema, tablePreviewCommandSchema, regionPreviewCommandSchema, regionPreviewMatchSchema, regionPreviewViewSchema, gridViewCommandSchema, gridPreviewOptionsSchema, gridPreviewCommandSchema, deckViewCommandSchema, deckPreviewOptionsSchema, deckPreviewCommandSchema, domTreeNodeViewSchema, pageDataFindingSchema, inspectViewSchema, observedResponseSchema, responsesSeenViewSchema, documentTreeNodeViewSchema, documentTreeViewSchema, pdfCellViewSchema, pdfRowViewSchema, pdfPageViewSchema, pdfDocumentViewSchema, tablePreviewTableSchema, tablePreviewBandSchema, tablePreviewMatchSchema, tablePreviewViewSchema, gridCellViewSchema, gridMergeViewSchema, gridSheetViewSchema, workbookDocumentViewSchema, gridTablePreviewMatchSchema, gridTablePreviewViewSchema, deckShapeViewSchema, deckChartSeriesViewSchema, deckChartViewSchema, deckSlideViewSchema, deckDocumentViewSchema, deckTablePreviewMatchSchema, deckTablePreviewViewSchema, startRecordingCommandSchema, stopRecordingCommandSchema, recordingCardEventSchema, recordingNoteEventSchema, recordingStoppedEventSchema, } from './studio-api';
13
+ export type { SampleBudget, OpenWorkspaceCommand, ChooseFolderCommand, OpenPathCommand, ImportDocumentCommand, RunSampleCommand, StopRunCommand, SaveRecipeCommand, FetchStartPageCommand, SaveOutlineCommand, TakeSnapshotCommand, VerifySelectorCommand, InferSelectorCommand, MissingWhyTarget, RejectedWhyTarget, WhyTarget, ExplainWhyCommand, InspectPageCommand, ResponsesSeenCommand, DocumentTreeCommand, PdfViewCommand, TablePreviewOptions, TablePreviewCommand, RegionPreviewCommand, GridViewCommand, GridPreviewOptions, GridPreviewCommand, DeckViewCommand, DeckPreviewOptions, DeckPreviewCommand, StudioCommand, ChooseFolderView, OpenPathView, ImportDocumentView, RegionPreviewMatchView, RegionPreviewView, StoppedBy, FieldTraceView, TraceLineEvent, RecordEvent, RecordRejectedEvent, RunFinishedEvent, WorkspaceChangedEvent, StudioEvent, LoadedHooks, RecipeIssue, RecipeListing, WorkspaceView, SampleRunView, StartPageView, SnapshotView, VerifySelectorView, FieldPick, InferSelectorView, SentencePart, OutlineCard, OutlineBracket, OutlineNode, OutlineView, WhyView, DomTreeNodeView, PageDataKind, PageDataFindingView, InspectView, ObservedResponseView, ResponsesSeenView, DocumentTreeNodeView, DocumentTreeView, PdfCellView, PdfRowView, PdfPageView, PdfDocumentView, TablePreviewTableView, TablePreviewBandView, TablePreviewMatchView, TablePreviewView, GridCellView, GridMergeView, GridSheetView, WorkbookDocumentView, GridTablePreviewMatchView, GridTablePreviewView, DeckShapeView, DeckChartSeriesView, DeckChartView, DeckSlideView, DeckDocumentView, DeckTablePreviewMatchView, DeckTablePreviewView, StartRecordingCommand, StopRecordingCommand, RecordingCardEvent, RecordingNoteEvent, RecordingStoppedEvent, } from './studio-api';
14
14
  //# sourceMappingURL=index.d.ts.map
package/dist/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import { join, dirname, basename, extname, normalize, sep, resolve } from 'node:path';
1
+ import { join, dirname, basename, extname, isAbsolute, relative, resolve, normalize, sep } from 'node:path';
2
2
  import { createServer } from 'node:http';
3
3
  import { promisify, stripVTControlCharacters } from 'node:util';
4
4
  import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -8,7 +8,7 @@ import { randomBytes, createHash } from 'node:crypto';
8
8
  import { execFile } from 'node:child_process';
9
9
  import { createRequire } from 'node:module';
10
10
  import { load } from 'cheerio';
11
- import { readFile, writeFile, rm, rename, mkdir } from 'node:fs/promises';
11
+ import { readFile, writeFile, rm, rename, mkdir, stat } from 'node:fs/promises';
12
12
  import { describeJson } from '@opencraw/probe';
13
13
  import { tmpdir } from 'node:os';
14
14
  import { existsSync, createReadStream } from 'node:fs';
@@ -52,6 +52,16 @@ const openWorkspaceCommandSchema = z.object({
52
52
  type: z.literal('open-workspace'),
53
53
  folder: z.string().min(1)
54
54
  });
55
+ /** Says what a path is (folder, recipe or document) and where to open it: how a link or an IDE entry opens a path (issue #235). `workspace` is the folder a document should belong to, when the caller has one. */
56
+ const openPathCommandSchema = z.object({
57
+ type: z.literal('open-path'),
58
+ path: z.string().min(1),
59
+ workspace: z.string().min(1).optional()
60
+ });
61
+ /** Opens the operating system's folder dialog on the server's machine: the answer is the folder picked, if any. */
62
+ const chooseFolderCommandSchema = z.object({
63
+ type: z.literal('choose-folder')
64
+ });
55
65
  /** Runs one input recipe under a budget, streaming `trace-line` and `record` events, ending in `run-finished`. */
56
66
  const runSampleCommandSchema = z.object({
57
67
  type: z.literal('run-sample'),
@@ -344,7 +354,7 @@ const importDocumentCommandSchema = z.object({
344
354
  name: z.string().min(1),
345
355
  bytes: z.string()
346
356
  });
347
- const studioCommandSchema = z.discriminatedUnion('type', [openWorkspaceCommandSchema, importDocumentCommandSchema, runSampleCommandSchema, stopRunCommandSchema, saveRecipeCommandSchema, fetchStartPageCommandSchema, saveOutlineCommandSchema, takeSnapshotCommandSchema, verifySelectorCommandSchema, inferSelectorCommandSchema, explainWhyCommandSchema, inspectPageCommandSchema, responsesSeenCommandSchema, documentTreeCommandSchema, pdfViewCommandSchema, tablePreviewCommandSchema, regionPreviewCommandSchema, gridViewCommandSchema, gridPreviewCommandSchema, deckViewCommandSchema, deckPreviewCommandSchema, startRecordingCommandSchema, stopRecordingCommandSchema]);
357
+ const studioCommandSchema = z.discriminatedUnion('type', [openWorkspaceCommandSchema, chooseFolderCommandSchema, openPathCommandSchema, importDocumentCommandSchema, runSampleCommandSchema, stopRunCommandSchema, saveRecipeCommandSchema, fetchStartPageCommandSchema, saveOutlineCommandSchema, takeSnapshotCommandSchema, verifySelectorCommandSchema, inferSelectorCommandSchema, explainWhyCommandSchema, inspectPageCommandSchema, responsesSeenCommandSchema, documentTreeCommandSchema, pdfViewCommandSchema, tablePreviewCommandSchema, regionPreviewCommandSchema, gridViewCommandSchema, gridPreviewCommandSchema, deckViewCommandSchema, deckPreviewCommandSchema, startRecordingCommandSchema, stopRecordingCommandSchema]);
348
358
 
349
359
  /**
350
360
  * What `take-snapshot` answers with: the rewritten document ready for the
@@ -525,6 +535,32 @@ const workspaceViewSchema = z.object({
525
535
  hooks: loadedHooksSchema.optional()
526
536
  });
527
537
 
538
+ /** `choose-folder`'s answer: the folder the person picked in the operating system's dialog; absent when the dialog was closed without a choice. */
539
+ const chooseFolderViewSchema = z.object({
540
+ folder: z.string().min(1).optional()
541
+ });
542
+
543
+ /**
544
+ * `open-path`'s answer: what the path turned out to be, and the folder to
545
+ * open as the workspace for it. A `recipe` names the input recipe to select
546
+ * when there is one; a `document` is where the file is read from (in place
547
+ * when it is already inside the folder, else the copy made there) and its
548
+ * `file:` URL, ready for a recipe's `start.url`.
549
+ */
550
+ const openPathViewSchema = z.discriminatedUnion('kind', [z.object({
551
+ kind: z.literal('folder'),
552
+ folder: z.string().min(1)
553
+ }), z.object({
554
+ kind: z.literal('recipe'),
555
+ folder: z.string().min(1),
556
+ recipeId: z.string().min(1).optional()
557
+ }), z.object({
558
+ kind: z.literal('document'),
559
+ folder: z.string().min(1),
560
+ path: z.string().min(1),
561
+ url: z.string().startsWith('file:')
562
+ })]);
563
+
528
564
  /** `import-document`'s answer: where the copy landed, and the `file:` URL a recipe's `start.url` points at it with. */
529
565
  const importDocumentViewSchema = z.object({
530
566
  path: z.string().min(1),
@@ -708,7 +744,15 @@ const gridSheetViewSchema = z.object({
708
744
  hiddenRows: z.array(z.number().int()),
709
745
  columnCount: z.number().int(),
710
746
  rows: z.array(z.array(gridCellViewSchema)),
711
- merges: z.array(gridMergeViewSchema)
747
+ merges: z.array(gridMergeViewSchema),
748
+ /** Column widths in characters by 0-based column, as the spreadsheet stores them (absent for a CSV, and for a column the file does not size). */
749
+ columnWidths: z.record(z.string(), z.number()).optional(),
750
+ /** Row heights in points by 0-based row, as the spreadsheet stores them. */
751
+ rowHeights: z.record(z.string(), z.number()).optional(),
752
+ /** The width of a column the file does not size, in characters. */
753
+ defaultColumnWidth: z.number().optional(),
754
+ /** The height of a row the file does not size, in points. */
755
+ defaultRowHeight: z.number().optional()
712
756
  });
713
757
  const csvFormatViewSchema = z.object({
714
758
  encoding: z.string(),
@@ -860,14 +904,174 @@ function cookieToken(header) {
860
904
  return undefined;
861
905
  }
862
906
 
907
+ const TITLE = 'Choose the recipe folder';
908
+ /** PowerShell's own Windows Forms dialog: the only folder picker every Windows install has. UTF-8 out, so a path with accents survives the pipe. */
909
+ const WINDOWS_SCRIPT = ['[Console]::OutputEncoding = [System.Text.Encoding]::UTF8', 'Add-Type -AssemblyName System.Windows.Forms', '$dialog = New-Object System.Windows.Forms.FolderBrowserDialog', `$dialog.Description = '${TITLE}'`, '$dialog.ShowNewFolderButton = $true', "if ($dialog.ShowDialog() -eq 'OK') { [Console]::Out.Write($dialog.SelectedPath) }"].join('; ');
910
+ /**
911
+ * The programs that can open a folder dialog on a platform, in the order to
912
+ * try them: the first one installed is used. Windows and macOS have one each
913
+ * in every install; Linux has whichever of `zenity` (GNOME and most others)
914
+ * and `kdialog` (KDE) the desktop ships.
915
+ *
916
+ * @param platform - `process.platform`.
917
+ * @returns The candidates; empty for a platform with no known dialog.
918
+ */
919
+ function folderDialogCommands(platform) {
920
+ switch (platform) {
921
+ case 'win32':
922
+ {
923
+ return [{
924
+ file: 'powershell.exe',
925
+ args: ['-NoProfile', '-STA', '-NonInteractive', '-Command', WINDOWS_SCRIPT]
926
+ }];
927
+ }
928
+ case 'darwin':
929
+ {
930
+ return [{
931
+ file: 'osascript',
932
+ args: ['-e', `POSIX path of (choose folder with prompt "${TITLE}")`]
933
+ }];
934
+ }
935
+ case 'linux':
936
+ case 'freebsd':
937
+ case 'openbsd':
938
+ {
939
+ return [{
940
+ file: 'zenity',
941
+ args: ['--file-selection', '--directory', `--title=${TITLE}`]
942
+ }, {
943
+ file: 'kdialog',
944
+ args: ['--getexistingdirectory', '.', '--title', TITLE]
945
+ }];
946
+ }
947
+ default:
948
+ {
949
+ return [];
950
+ }
951
+ }
952
+ }
953
+ /**
954
+ * The folder a dialog printed: its output without the line break and the
955
+ * trailing separator (`osascript` ends a folder with `/`), a root kept as it
956
+ * is. Nothing printed means the person cancelled.
957
+ *
958
+ * @param output - What the dialog program wrote to its standard output.
959
+ * @returns The folder path, or `undefined` for a cancelled dialog.
960
+ */
961
+ function folderFromDialogOutput(output) {
962
+ const line = output.trim();
963
+ if (line === '') return undefined;
964
+ const withoutSeparator = line.replace(/[/\\]+$/u, '');
965
+ return withoutSeparator === '' || /^[A-Za-z]:$/u.test(withoutSeparator) ? line : withoutSeparator;
966
+ }
967
+
968
+ /** Runs the program with Node's own `execFile`: no shell, so a path or title is never parsed as a command. */
969
+ const runDialogProgram = async command => await new Promise(resolve => {
970
+ execFile(command.file, command.args, {
971
+ windowsHide: false,
972
+ encoding: 'utf8'
973
+ }, (error, stdout) => {
974
+ if (error === null) {
975
+ resolve({
976
+ output: stdout
977
+ });
978
+ return;
979
+ }
980
+ if ('code' in error && error.code === 'ENOENT') {
981
+ resolve({
982
+ missing: true
983
+ });
984
+ return;
985
+ }
986
+ // `zenity` and `osascript` exit non-zero when the dialog is closed without a choice.
987
+ resolve(stdout.trim() === '' ? {
988
+ cancelled: true
989
+ } : {
990
+ output: stdout
991
+ });
992
+ });
993
+ });
994
+ /**
995
+ * Opens the operating system's own folder dialog on the machine the server
996
+ * runs on, which is where the recipe folder is: a browser tab cannot do it,
997
+ * the server can. Waits until the person picks a folder or closes the dialog.
998
+ *
999
+ * @param platform - `process.platform`; passed in so a test names the platform it means.
1000
+ * @param run - How a dialog program is run.
1001
+ * @returns The folder picked, or `undefined` when the dialog was closed without a choice.
1002
+ * @throws Error when the platform has no dialog program installed (a server with no display, a container).
1003
+ */
1004
+ async function chooseFolderWithDialog(platform = process.platform, run = runDialogProgram) {
1005
+ const candidates = folderDialogCommands(platform);
1006
+ for (const command of candidates) {
1007
+ const result = await run(command);
1008
+ if ('missing' in result) continue;
1009
+ if ('cancelled' in result) return undefined;
1010
+ return folderFromDialogOutput(result.output);
1011
+ }
1012
+ const tried = candidates.map(command => command.file).join(' or ');
1013
+ throw new Error(`there is no folder dialog on this machine${tried === '' ? '' : ` (${tried} is not installed)`}: type the folder instead`);
1014
+ }
1015
+
1016
+ /** Asks the dialog, and clears `openDialog` once it has answered or failed, so the next request opens a new one. */
1017
+ async function askOnce(state, ask) {
1018
+ try {
1019
+ return await ask();
1020
+ } finally {
1021
+ state.openDialog = undefined;
1022
+ }
1023
+ }
1024
+ /**
1025
+ * Handles `choose-folder`: opens the folder dialog of the machine the server
1026
+ * runs on and answers the folder picked. Only answers: the UI follows with
1027
+ * `open-workspace`, the same as when the folder is typed. A dialog still
1028
+ * open is not opened a second time; the second request waits for the first.
1029
+ *
1030
+ * @param state - The server state holding the dialog to use.
1031
+ * @returns The folder, or none when the dialog was closed.
1032
+ */
1033
+ async function handleChooseFolder(state) {
1034
+ if (state.chooseFolder === undefined) throw new Error('this server has no folder dialog: type the folder instead');
1035
+ state.openDialog ??= askOnce(state, state.chooseFolder);
1036
+ const folder = await state.openDialog;
1037
+ return folder === undefined ? {} : {
1038
+ folder
1039
+ };
1040
+ }
1041
+
863
1042
  const execFileAsync = promisify(execFile);
864
1043
  /** `playwright`'s own `exports` map doesn't whitelist a `./cli` subpath, so its CLI is resolved off `playwright/package.json`'s real location — the same technique `@opencraw/captcha-tesseract`'s `bundledLangPath()` uses for a bundled data path. */
865
1044
  function playwrightCliPath() {
866
1045
  const require = createRequire(import.meta.url);
867
1046
  return join(dirname(require.resolve('playwright/package.json')), 'cli.js');
868
1047
  }
1048
+ /**
1049
+ * How the install is run: the current executable as a Node, on Playwright's own CLI. Inside the desktop app
1050
+ * the executable is Electron, which runs as plain Node only with `ELECTRON_RUN_AS_NODE` (otherwise it would
1051
+ * start a second copy of the app); under Node the variable does nothing.
1052
+ *
1053
+ * @param cliPath - Playwright's `cli.js`.
1054
+ * @returns What to hand to `execFile`.
1055
+ */
1056
+ function installInvocation(cliPath) {
1057
+ return {
1058
+ file: process.execPath,
1059
+ args: [cliPath, 'install', 'chromium'],
1060
+ env: {
1061
+ ...process.env,
1062
+ ELECTRON_RUN_AS_NODE: '1'
1063
+ }
1064
+ };
1065
+ }
869
1066
  async function runPlaywrightInstall() {
870
- await execFileAsync(process.execPath, [playwrightCliPath(), 'install', 'chromium']);
1067
+ const {
1068
+ file,
1069
+ args,
1070
+ env
1071
+ } = installInvocation(playwrightCliPath());
1072
+ await execFileAsync(file, args, {
1073
+ env
1074
+ });
871
1075
  }
872
1076
  let installing;
873
1077
  /**
@@ -1364,6 +1568,22 @@ function sheetView(sheet) {
1364
1568
  ref: reference,
1365
1569
  ...range
1366
1570
  }];
1571
+ }),
1572
+ ...(sheet.columnWidths !== undefined && {
1573
+ columnWidths: {
1574
+ ...sheet.columnWidths
1575
+ }
1576
+ }),
1577
+ ...(sheet.rowHeights !== undefined && {
1578
+ rowHeights: {
1579
+ ...sheet.rowHeights
1580
+ }
1581
+ }),
1582
+ ...(sheet.defaultColumnWidth !== undefined && {
1583
+ defaultColumnWidth: sheet.defaultColumnWidth
1584
+ }),
1585
+ ...(sheet.defaultRowHeight !== undefined && {
1586
+ defaultRowHeight: sheet.defaultRowHeight
1367
1587
  })
1368
1588
  };
1369
1589
  }
@@ -3026,6 +3246,102 @@ function isAlreadyThere(error) {
3026
3246
  return typeof error === 'object' && error !== null && 'code' in error && error.code === 'EEXIST';
3027
3247
  }
3028
3248
 
3249
+ /** A recipe is a small JSON file; anything larger is a document that happens to be JSON. */
3250
+ const MAX_RECIPE_BYTES = 5 * 1024 * 1024;
3251
+ /**
3252
+ * Says what a path is, for a link or an IDE menu entry that only knows a
3253
+ * path: a folder opens as the workspace; a `.json` file whose `kind` is
3254
+ * `input` or `output` is a recipe, opened through its own folder; any other
3255
+ * file is a document to start a recipe from.
3256
+ *
3257
+ * A document is read in place when it is already inside the workspace folder
3258
+ * (`workspace`, else the file's own folder), and copied into the folder
3259
+ * otherwise, with the same no-overwrite numbering `import-document` uses.
3260
+ *
3261
+ * @param path - An absolute path to a folder or a file.
3262
+ * @param workspace - The folder a document should belong to, when the caller has one (an IDE's workspace folder).
3263
+ * @returns What it is and where to open it.
3264
+ * @throws Error when the path is relative or does not exist.
3265
+ */
3266
+ async function openPath(path, workspace) {
3267
+ if (!isAbsolute(path)) throw new Error(`"${path}" is not an absolute path`);
3268
+ const info = await statOrNothing(path);
3269
+ if (info === undefined) throw new Error(`"${path}" does not exist`);
3270
+ if (info.isDirectory()) return {
3271
+ kind: 'folder',
3272
+ folder: path
3273
+ };
3274
+ const recipe = info.size <= MAX_RECIPE_BYTES ? await recipeAt(path) : undefined;
3275
+ if (recipe !== undefined) return {
3276
+ kind: 'recipe',
3277
+ folder: dirname(path),
3278
+ recipeId: recipe.id
3279
+ };
3280
+ const folder = workspace ?? dirname(path);
3281
+ if (isInside(folder, path)) return {
3282
+ kind: 'document',
3283
+ folder,
3284
+ path,
3285
+ url: pathToFileURL(path).href
3286
+ };
3287
+ const copy = await importDocument(folder, basename(path), new Uint8Array(await readFile(path)));
3288
+ return {
3289
+ kind: 'document',
3290
+ folder,
3291
+ path: copy.path,
3292
+ url: copy.url
3293
+ };
3294
+ }
3295
+ /** The recipe a `.json` file holds, with the id of the input recipe to select (an output recipe selects the first input naming it); `undefined` when the file is not a recipe. */
3296
+ async function recipeAt(path) {
3297
+ if (extname(path).toLowerCase() !== '.json') return undefined;
3298
+ let content;
3299
+ try {
3300
+ content = JSON.parse(await readFile(path, 'utf8'));
3301
+ } catch {
3302
+ return undefined;
3303
+ }
3304
+ const {
3305
+ kind,
3306
+ id
3307
+ } = typeof content === 'object' && content !== null ? content : {};
3308
+ if (kind === 'input') return {
3309
+ id: typeof id === 'string' ? id : undefined
3310
+ };
3311
+ if (kind !== 'output') return undefined;
3312
+ const listing = await openWorkspace(dirname(path));
3313
+ for (const candidate of listing.recipes) {
3314
+ const named = candidate.kind === 'input' ? inputNaming(candidate.text, id) : undefined;
3315
+ if (named !== undefined) return {
3316
+ id: named
3317
+ };
3318
+ }
3319
+ return {};
3320
+ }
3321
+ /** The id of the input recipe whose text names `outputId` as its output, read from the JSON itself so an input that does not validate still counts. */
3322
+ function inputNaming(text, outputId) {
3323
+ try {
3324
+ const {
3325
+ id,
3326
+ output
3327
+ } = JSON.parse(text);
3328
+ return output === outputId && typeof id === 'string' ? id : undefined;
3329
+ } catch {
3330
+ return undefined;
3331
+ }
3332
+ }
3333
+ async function statOrNothing(path) {
3334
+ try {
3335
+ return await stat(path);
3336
+ } catch {
3337
+ return undefined;
3338
+ }
3339
+ }
3340
+ function isInside(folder, path) {
3341
+ const from = relative(resolve(folder), resolve(path));
3342
+ return from !== '' && !from.startsWith('..') && !isAbsolute(from);
3343
+ }
3344
+
3029
3345
  /**
3030
3346
  * Handles `import-document` (issue #120): decodes the base64 bytes and copies
3031
3347
  * the file into the folder (see `recipe-workspace/import-document.use-case.ts`
@@ -3860,6 +4176,18 @@ function handleInspectPage(state, command) {
3860
4176
  };
3861
4177
  }
3862
4178
 
4179
+ /**
4180
+ * Handles `open-path`: says what the path is (folder, recipe or document) and
4181
+ * where to open it. Opens nothing itself: the UI follows with `open-workspace`
4182
+ * on the answer's folder, the same as when the folder is typed.
4183
+ *
4184
+ * @param command - The path, and the workspace it should belong to if the caller has one.
4185
+ * @returns What the path is.
4186
+ */
4187
+ async function handleOpenPath(command) {
4188
+ return await openPath(command.path, command.workspace);
4189
+ }
4190
+
3863
4191
  /**
3864
4192
  * Which of what Studio was started with calls outside the process, for the bar that warns the person and
3865
4193
  * for the stubs a sample run may use.
@@ -5163,6 +5491,7 @@ async function startStudioServer(options = {}) {
5163
5491
  if (options.initialFolder !== undefined) state.folder = options.initialFolder;
5164
5492
  state.browser = options.browser;
5165
5493
  state.plugins = options.plugins;
5494
+ state.chooseFolder = options.chooseFolder ?? (() => chooseFolderWithDialog());
5166
5495
  if (options.recorderProfileDir !== undefined) state.recorderProfileDir = options.recorderProfileDir;
5167
5496
  const server = createServer((request, response) => {
5168
5497
  void handleRequest(request, response, token, state, uiRoot);
@@ -5177,9 +5506,15 @@ async function startStudioServer(options = {}) {
5177
5506
  const address = server.address();
5178
5507
  const port = typeof address === 'object' && address !== null ? address.port : 0;
5179
5508
  const folderParam = options.initialFolder === undefined ? '' : `&folder=${encodeURIComponent(options.initialFolder)}`;
5509
+ const base = `http://127.0.0.1:${port}/?token=${token}`;
5180
5510
  return {
5181
- url: `http://127.0.0.1:${port}/?token=${token}${folderParam}`,
5511
+ url: `${base}${folderParam}`,
5182
5512
  token,
5513
+ link: target => {
5514
+ const workspace = target?.workspace ?? options.initialFolder;
5515
+ const open = target?.open === undefined ? '' : `&open=${encodeURIComponent(target.open)}`;
5516
+ return `${base}${workspace === undefined ? '' : `&folder=${encodeURIComponent(workspace)}`}${open}`;
5517
+ },
5183
5518
  close: () => stop(server, state)
5184
5519
  };
5185
5520
  }
@@ -5275,6 +5610,14 @@ function dispatch(command, state) {
5275
5610
  {
5276
5611
  return handleOpenWorkspace(state, command);
5277
5612
  }
5613
+ case 'choose-folder':
5614
+ {
5615
+ return handleChooseFolder(state);
5616
+ }
5617
+ case 'open-path':
5618
+ {
5619
+ return handleOpenPath(command);
5620
+ }
5278
5621
  case 'import-document':
5279
5622
  {
5280
5623
  return handleImportDocument(command);
@@ -5416,7 +5759,11 @@ async function main(argv, options = {}) {
5416
5759
  initialFolder: folder,
5417
5760
  plugins: options.plugins
5418
5761
  });
5419
- process.stdout.write(`OpenCraw Studio: ${server.url}\n`);
5762
+ const address = options.open === undefined ? server.url : server.link({
5763
+ open: resolve(options.open),
5764
+ workspace: folder
5765
+ });
5766
+ process.stdout.write(`OpenCraw Studio: ${address}\n`);
5420
5767
  process.stdout.write(`Workspace: ${folder}\n`);
5421
5768
  if (options.plugins !== undefined) process.stdout.write(`Hooks: ${options.plugins.source} (runs as your code)\n`);
5422
5769
  const shutdown = () => {
@@ -5426,5 +5773,5 @@ async function main(argv, options = {}) {
5426
5773
  process.on('SIGTERM', shutdown);
5427
5774
  }
5428
5775
 
5429
- export { deckChartSeriesViewSchema, deckChartViewSchema, deckDocumentViewSchema, deckPreviewCommandSchema, deckPreviewOptionsSchema, deckShapeViewSchema, deckSlideViewSchema, deckTablePreviewMatchSchema, deckTablePreviewViewSchema, deckViewCommandSchema, documentTreeCommandSchema, documentTreeNodeViewSchema, documentTreeViewSchema, domTreeNodeViewSchema, explainWhyCommandSchema, fetchStartPage, fetchStartPageCommandSchema, fieldPickSchema, fieldTraceSchema, gridCellViewSchema, gridMergeViewSchema, gridPreviewCommandSchema, gridPreviewOptionsSchema, gridSheetViewSchema, gridTablePreviewMatchSchema, gridTablePreviewViewSchema, gridViewCommandSchema, importDocument, importDocumentCommandSchema, importDocumentViewSchema, inferSelectorCommandSchema, inferSelectorViewSchema, inspectPageCommandSchema, inspectViewSchema, loadRecipePair, loadedHooksSchema, main, missingWhyTargetSchema, observedResponseSchema, openWorkspace, openWorkspaceCommandSchema, outlineBracketSchema, outlineCardSchema, outlineNodeSchema, outlineViewSchema, pageDataFindingSchema, pdfCellViewSchema, pdfDocumentViewSchema, pdfPageViewSchema, pdfRowViewSchema, pdfViewCommandSchema, recipeIssueSchema, recipeListingSchema, recordEventSchema, recordRejectedEventSchema, recordingCardEventSchema, recordingNoteEventSchema, recordingStoppedEventSchema, regionPreviewCommandSchema, regionPreviewMatchSchema, regionPreviewViewSchema, rejectedWhyTargetSchema, responsesSeenCommandSchema, responsesSeenViewSchema, runFinishedEventSchema, runSample, runSampleCommandSchema, sampleBudgetSchema, sampleRunRecordSchema, sampleRunViewSchema, saveOutlineCommandSchema, saveRecipe, saveRecipeCommandSchema, sentencePartSchema, snapshotViewSchema, startPageViewSchema, startRecordingCommandSchema, startStudioServer, stopRecordingCommandSchema, stopRunCommandSchema, stoppedBySchema, studioCommandSchema, studioEventSchema, tablePreviewBandSchema, tablePreviewCommandSchema, tablePreviewMatchSchema, tablePreviewOptionsSchema, tablePreviewTableSchema, tablePreviewViewSchema, takeSnapshotCommandSchema, traceLineEventSchema, verifySelectorCommandSchema, verifySelectorViewSchema, whyTargetSchema, whyViewSchema, workbookDocumentViewSchema, workspaceChangedEventSchema, workspaceViewSchema };
5776
+ export { chooseFolderCommandSchema, chooseFolderViewSchema, deckChartSeriesViewSchema, deckChartViewSchema, deckDocumentViewSchema, deckPreviewCommandSchema, deckPreviewOptionsSchema, deckShapeViewSchema, deckSlideViewSchema, deckTablePreviewMatchSchema, deckTablePreviewViewSchema, deckViewCommandSchema, documentTreeCommandSchema, documentTreeNodeViewSchema, documentTreeViewSchema, domTreeNodeViewSchema, explainWhyCommandSchema, fetchStartPage, fetchStartPageCommandSchema, fieldPickSchema, fieldTraceSchema, gridCellViewSchema, gridMergeViewSchema, gridPreviewCommandSchema, gridPreviewOptionsSchema, gridSheetViewSchema, gridTablePreviewMatchSchema, gridTablePreviewViewSchema, gridViewCommandSchema, importDocument, importDocumentCommandSchema, importDocumentViewSchema, inferSelectorCommandSchema, inferSelectorViewSchema, inspectPageCommandSchema, inspectViewSchema, loadRecipePair, loadedHooksSchema, main, missingWhyTargetSchema, observedResponseSchema, openPathCommandSchema, openPathViewSchema, openWorkspace, openWorkspaceCommandSchema, outlineBracketSchema, outlineCardSchema, outlineNodeSchema, outlineViewSchema, pageDataFindingSchema, pdfCellViewSchema, pdfDocumentViewSchema, pdfPageViewSchema, pdfRowViewSchema, pdfViewCommandSchema, recipeIssueSchema, recipeListingSchema, recordEventSchema, recordRejectedEventSchema, recordingCardEventSchema, recordingNoteEventSchema, recordingStoppedEventSchema, regionPreviewCommandSchema, regionPreviewMatchSchema, regionPreviewViewSchema, rejectedWhyTargetSchema, responsesSeenCommandSchema, responsesSeenViewSchema, runFinishedEventSchema, runSample, runSampleCommandSchema, sampleBudgetSchema, sampleRunRecordSchema, sampleRunViewSchema, saveOutlineCommandSchema, saveRecipe, saveRecipeCommandSchema, sentencePartSchema, snapshotViewSchema, startPageViewSchema, startRecordingCommandSchema, startStudioServer, stopRecordingCommandSchema, stopRunCommandSchema, stoppedBySchema, studioCommandSchema, studioEventSchema, tablePreviewBandSchema, tablePreviewCommandSchema, tablePreviewMatchSchema, tablePreviewOptionsSchema, tablePreviewTableSchema, tablePreviewViewSchema, takeSnapshotCommandSchema, traceLineEventSchema, verifySelectorCommandSchema, verifySelectorViewSchema, whyTargetSchema, whyViewSchema, workbookDocumentViewSchema, workspaceChangedEventSchema, workspaceViewSchema };
5430
5777
  //# sourceMappingURL=index.esm.js.map
package/dist/main.d.ts CHANGED
@@ -3,6 +3,8 @@ import type { TrustedPlugins } from './trusted-plugins';
3
3
  export interface StudioMainOptions {
4
4
  /** Hooks and plugins loaded from a file the person named at launch (`opencraw studio --hooks <file>`, issue #150). `main` never loads code itself. */
5
5
  plugins?: TrustedPlugins;
6
+ /** A folder, recipe file or document the printed link opens straight away (`opencraw studio --open <path>`, issue #235); resolved against the current directory. */
7
+ open?: string;
6
8
  }
7
9
  /**
8
10
  * Runs `opencraw-studio`: starts the server, opens `folder` (if given) as
@@ -1,5 +1,6 @@
1
1
  export { openWorkspace } from './open-workspace.use-case';
2
2
  export { saveRecipe } from './save-recipe.use-case';
3
+ export { openPath } from './open-path.use-case';
3
4
  export { importDocument } from './import-document.use-case';
4
5
  export type { ImportedDocument } from './import-document.use-case';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,18 @@
1
+ import type { OpenPathView } from '../studio-api';
2
+ /**
3
+ * Says what a path is, for a link or an IDE menu entry that only knows a
4
+ * path: a folder opens as the workspace; a `.json` file whose `kind` is
5
+ * `input` or `output` is a recipe, opened through its own folder; any other
6
+ * file is a document to start a recipe from.
7
+ *
8
+ * A document is read in place when it is already inside the workspace folder
9
+ * (`workspace`, else the file's own folder), and copied into the folder
10
+ * otherwise, with the same no-overwrite numbering `import-document` uses.
11
+ *
12
+ * @param path - An absolute path to a folder or a file.
13
+ * @param workspace - The folder a document should belong to, when the caller has one (an IDE's workspace folder).
14
+ * @returns What it is and where to open it.
15
+ * @throws Error when the path is relative or does not exist.
16
+ */
17
+ export declare function openPath(path: string, workspace?: string): Promise<OpenPathView>;
18
+ //# sourceMappingURL=open-path.use-case.d.ts.map
@@ -1,2 +1,15 @@
1
+ /**
2
+ * How the install is run: the current executable as a Node, on Playwright's own CLI. Inside the desktop app
3
+ * the executable is Electron, which runs as plain Node only with `ELECTRON_RUN_AS_NODE` (otherwise it would
4
+ * start a second copy of the app); under Node the variable does nothing.
5
+ *
6
+ * @param cliPath - Playwright's `cli.js`.
7
+ * @returns What to hand to `execFile`.
8
+ */
9
+ export declare function installInvocation(cliPath: string): {
10
+ file: string;
11
+ args: string[];
12
+ env: NodeJS.ProcessEnv;
13
+ };
1
14
  export declare function installChromiumOnce(run?: () => Promise<void>): Promise<void>;
2
15
  //# sourceMappingURL=install-chromium.use-case.d.ts.map
@@ -24,6 +24,14 @@ export interface GridSheetView {
24
24
  columnCount: number;
25
25
  rows: GridCellView[][];
26
26
  merges: GridMergeView[];
27
+ /** Column widths in characters by 0-based column, as the spreadsheet stores them; absent keys are columns the file does not size. */
28
+ columnWidths?: Record<string, number>;
29
+ /** Row heights in points by 0-based row, as the spreadsheet stores them. */
30
+ rowHeights?: Record<string, number>;
31
+ /** The width of a column the file does not size, in characters. */
32
+ defaultColumnWidth?: number;
33
+ /** The height of a row the file does not size, in points. */
34
+ defaultRowHeight?: number;
27
35
  }
28
36
  /** What `grid-view` answers with (studio plan §3.4, issue #94's 5c): every sheet's cells, typed, with hidden sheets/rows marked and merged ranges resolved — the CSV/workbook grid's own view of the document, structurally the same idea as `pdf-view.mapper.ts`'s `PdfDocumentView`. */
29
37
  export interface WorkbookDocumentView {