Skip to content

Commit b529009

Browse files
authored
features: hashing, tsx, fetchSync, cheerio, makeSynchronous (#27)
1 parent 7facff4 commit b529009

31 files changed

+7006
-4375
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ jobs:
1010
runs-on: ubuntu-latest
1111
steps:
1212
- uses: actions/checkout@v4
13+
- run: npm install -g [email protected] # todo: delete if https://github.com/nodejs/corepack/issues/612 is resolved
1314
- run: corepack enable
1415
- run: pnpm install
1516
- run: pnpm run build

.github/workflows/pkg.pr.new.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: pkg.pr.new
2+
on:
3+
push: {}
4+
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v4
10+
- run: npm install -g [email protected] # todo: delete if https://github.com/nodejs/corepack/issues/612 is resolved
11+
- run: corepack enable
12+
- run: pnpm install
13+
- run: pnpm build
14+
- run: pnpm pkg-pr-new publish

.vscode/settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
"editor.codeActionsOnSave": {
33
"source.fixAll.eslint": "always"
44
},
5+
"editor.inlineSuggest.suppressSuggestions": true,
6+
"editor.quickSuggestionsDelay": 3000,
7+
"editor.quickSuggestions": { "other": false, "comments": false, "strings": false },
8+
"editor.wordBasedSuggestions": "off",
9+
"editor.acceptSuggestionOnCommitCharacter": false,
10+
11+
512
"eslint.validate": [
613
"javascript",
714
"typescript",

README.md

Lines changed: 149 additions & 36 deletions
Large diffs are not rendered by default.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
-- Kill "QuickTime Player" if running
2+
say "Closing quicktime!"
3+
tell application "QuickTime Player" to if it is running then quit
4+
delay 2
5+
6+
-- Click “QuickTime Player” in the Dock.
7+
say "Opening quicktime!"
8+
set timeoutSeconds to 2.000000
9+
set uiScript to "click UI Element \"QuickTime Player\" of list 1 of application process \"Dock\""
10+
my doWithTimeout( uiScript, timeoutSeconds )
11+
delay 2
12+
13+
-- Click the “Cancel” button.
14+
say "Cancelling quicktime!"
15+
set timeoutSeconds to 2.000000
16+
set uiScript to "click UI Element \"Cancel\" of window \"Open\" of application process \"QuickTime Player\""
17+
my doWithTimeout( uiScript, timeoutSeconds )
18+
19+
20+
-- Click the “File” menu.
21+
set timeoutSeconds to 2.000000
22+
set uiScript to "click menu bar item \"File\" of menu bar 1 of application process \"QuickTime Player\""
23+
my doWithTimeout( uiScript, timeoutSeconds )
24+
25+
-- New Screen Recording
26+
set timeoutSeconds to 2.000000
27+
set uiScript to "click menu item \"New Screen Recording\" of menu 1 of menu bar item \"File\" of menu bar 1 of application process \"QuickTime Player\""
28+
my doWithTimeout( uiScript, timeoutSeconds )
29+
30+
tell application "System Events"
31+
delay 0.5
32+
keystroke return
33+
end tell
34+
35+
36+
on doWithTimeout(uiScript, timeoutSeconds)
37+
set endDate to (current date) + timeoutSeconds
38+
repeat
39+
try
40+
run script "tell application \"System Events\"
41+
" & uiScript & "
42+
end tell"
43+
exit repeat
44+
on error errorMessage
45+
if ((current date) > endDate) then
46+
error "Can not " & uiScript
47+
end if
48+
end try
49+
end repeat
50+
end doWithTimeout
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-- Click the “Stop Recording” menu bar item.
2+
delay 3.451958
3+
set timeoutSeconds to 2.000000
4+
set uiScript to "click menu bar item 1 of menu bar 1 of application process \"screencaptureui\""
5+
my doWithTimeout( uiScript, timeoutSeconds )
6+
7+
on doWithTimeout(uiScript, timeoutSeconds)
8+
set endDate to (current date) + timeoutSeconds
9+
repeat
10+
try
11+
run script "tell application \"System Events\"
12+
" & uiScript & "
13+
end tell"
14+
exit repeat
15+
on error errorMessage
16+
if ((current date) > endDate) then
17+
error "Can not " & uiScript
18+
end if
19+
end try
20+
end repeat
21+
end doWithTimeout

e2e/base.ts

Lines changed: 110 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
/* eslint-disable no-unused-vars */
2+
/* eslint-disable @typescript-eslint/no-explicit-any */
3+
/* eslint-disable unicorn/prefer-module */
14
// eslint-disable-next-line unicorn/filename-case
25
/* eslint-disable no-console */
3-
/* eslint-disable import/no-extraneous-dependencies */
46
/**
57
* Copyright (c) Microsoft Corporation.
68
*
@@ -20,11 +22,11 @@ import {test as base, type Page, _electron} from '@playwright/test'
2022
import {downloadAndUnzipVSCode} from '@vscode/test-electron/out/download'
2123

2224
export {expect} from '@playwright/test'
23-
import {spawnSync} from 'child_process'
2425
import dedent from 'dedent'
25-
import * as fs from 'fs'
26-
import * as os from 'os'
27-
import * as path from 'path'
26+
import {SpawnSyncOptionsWithBufferEncoding, spawnSync} from 'node:child_process'
27+
import * as fs from 'node:fs'
28+
import * as os from 'node:os'
29+
import * as path from 'node:path'
2830

2931
export type TestOptions = {
3032
vscodeVersion: string
@@ -39,13 +41,20 @@ type TestFixtures = TestOptions & {
3941
export const test = base.extend<TestFixtures>({
4042
vscodeVersion: ['insiders', {option: true}],
4143
async workbox({vscodeVersion, createProject, createTempDir}, use) {
44+
const titleSlug = slugify(test.info().title)
4245
const defaultCachePath = await createTempDir()
4346
const vscodePath = await downloadAndUnzipVSCode(vscodeVersion)
44-
await fs.promises.cp(
45-
'/Users/mmkal/.vscode/extensions/dbaeumer.vscode-eslint-2.4.2',
46-
path.join(defaultCachePath, 'extensions', 'dbaeumer.vscode-eslint-2.4.2'),
47-
{recursive: true},
48-
)
47+
const systemVscodeFolder = path.join(os.homedir(), '.vscode')
48+
const systemExtensionsDirectory = path.join(systemVscodeFolder, 'extensions')
49+
const gifskiPath = path.join(os.homedir(), 'Downloads/gifski-1.32.0/mac/gifski')
50+
const systemExtensionNames = await fs.promises.readdir(systemExtensionsDirectory)
51+
52+
const systemEslintExtension = systemExtensionNames.find(child => child.startsWith('dbaeumer.vscode-eslint'))!
53+
const systemEslintExtensionPath = path.join(systemExtensionsDirectory, systemEslintExtension)
54+
55+
await fs.promises.cp(systemEslintExtensionPath, path.join(defaultCachePath, 'extensions', systemEslintExtension), {
56+
recursive: true,
57+
})
4958
const projectPath = await createProject()
5059
const electronApp = await _electron.launch({
5160
executablePath: vscodePath,
@@ -66,21 +75,63 @@ export const test = base.extend<TestFixtures>({
6675
`--user-data-dir=${path.join(defaultCachePath, 'user-data')}`,
6776
projectPath,
6877
],
69-
recordVideo: {dir: 'test-results/videos'},
78+
recordVideo: {
79+
dir: `test-results/videos/${titleSlug}`,
80+
// 942:707 is the default aspect ratio of the electron runner and I don't know how to change it.
81+
size: {width: 942 * 1.5, height: 707 * 1.5},
82+
},
7083
})
7184
const workbox = await electronApp.firstWindow()
7285
await workbox.context().tracing.start({screenshots: true, snapshots: true, title: test.info().title})
86+
87+
const exec = (command: string, options?: SpawnSyncOptionsWithBufferEncoding) =>
88+
spawnSync(command, {cwd: projectPath, stdio: 'inherit', shell: true, ...options})
89+
90+
if (process.env.QUICKTIME_RECORD) {
91+
exec('osascript e2e/applescript/start-recording.applescript', {cwd: process.cwd()})
92+
}
93+
7394
await use(workbox)
95+
96+
let videoPath: string | undefined
97+
if (process.env.QUICKTIME_RECORD) {
98+
const before = new Date()
99+
exec('osascript e2e/applescript/stop-recording.applescript', {cwd: process.cwd()})
100+
const desktop = path.join(os.homedir(), 'Desktop')
101+
await new Promise(r => setTimeout(r, 3000)) // give it a few seconds to save
102+
const after = new Date()
103+
const desktopMovFiles = fs
104+
.readdirSync(desktop)
105+
.filter(f => f.endsWith('.mov'))
106+
.map(f => {
107+
const filepath = path.join(desktop, f)
108+
return {filepath, ctime: fs.statSync(filepath).ctime}
109+
})
110+
.filter(f => Date.now() - f.ctime.getTime() < 1000 * 60 * 60)
111+
.sort((a, b) => b.ctime.getTime() - a.ctime.getTime())
112+
const likelyFiles = desktopMovFiles.filter(f => f.ctime > before && f.ctime < after)
113+
if (likelyFiles.length !== 1) {
114+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
115+
throw new Error(
116+
`Expected one file to be created between ${before.toISOString()} and ${after.toISOString()}, got ${JSON.stringify(desktopMovFiles, null, 2)}`,
117+
)
118+
}
119+
videoPath = likelyFiles[0].filepath
120+
}
121+
74122
const tracePath = test.info().outputPath('trace.zip')
75123
await workbox.context().tracing.stop({path: tracePath})
76124
test.info().attachments.push({name: 'trace', path: tracePath, contentType: 'application/zip'})
77125
await electronApp.close()
78126
const logPath = path.join(defaultCachePath, 'user-data')
79127
const video = workbox.video()
80128
if (video) {
81-
// await workbox.video()?.saveAs(path.join(process.cwd(), 'videos', slugify(test.info().title) + '.webm'))
82-
const exec = (command: string) => spawnSync(command, {cwd: projectPath, stdio: 'inherit', shell: true})
83-
exec(`ffmpeg -y -i ${await video.path()} -pix_fmt rgb24 ${process.cwd()}/gifs/${slugify(test.info().title)}.gif`)
129+
videoPath ||= await video.path()
130+
const targetVideoPath = path.join(process.cwd(), 'test-results', 'videos', titleSlug + path.extname(videoPath))
131+
await fs.promises.cp(videoPath, targetVideoPath)
132+
exec(
133+
`${gifskiPath} --fps 32 ${targetVideoPath} -o ${path.join(process.cwd(), 'gifs', titleSlug + '.gif')} --quality 100`,
134+
)
84135
}
85136

86137
if (fs.existsSync(logPath)) {
@@ -102,57 +153,73 @@ export const test = base.extend<TestFixtures>({
102153
fs.writeFileSync(fullpath, content)
103154
}
104155

105-
// exec(`npm init playwright@latest --yes -- --quiet --browser=chromium --gha --install-deps`)
156+
const editJson = (name: string, edit: (json: unknown) => void) => {
157+
const fullpath = path.join(projectPath, name)
158+
if (!fs.existsSync(fullpath)) {
159+
fs.mkdirSync(path.dirname(fullpath), {recursive: true})
160+
fs.cpSync(path.join(__dirname, '..', name), fullpath)
161+
}
162+
const json = JSON.parse(fs.readFileSync(fullpath, 'utf8')) as {}
163+
edit(json)
164+
fs.writeFileSync(fullpath, JSON.stringify(json, null, 2))
165+
}
166+
106167
exec('npm init -y')
107-
exec(`pnpm install eslint eslint-plugin-codegen eslint-plugin-mmkal typescript ts-node --save-dev`)
168+
exec(`pnpm install eslint@8 react@18 @types/react typescript tsx --save-dev`)
108169

109-
write('tsconfig.json', fs.readFileSync(path.join(__dirname, '..', 'tsconfig.json'), 'utf8'))
170+
editJson('tsconfig.json', (json: any) => {
171+
json.compilerOptions.jsx = 'react'
172+
json.include.push('node_modules/eslint-plugin-codegen')
173+
})
110174
write(
111-
'.eslintrc.js',
112-
// parserOptions: {project: null} // this is more of an eslint-plugin-mmkal thing but typescript-eslint doesn't like the processor I've got
113-
// also, need to not pass fatal messages in the processor to eslint-plugin-markdown
175+
'eslint.config.js',
114176
dedent`
115-
module.exports = {
116-
...require('eslint-plugin-mmkal').getRecommended(),
117-
parserOptions: {project: null},
118-
plugins: ['codegen'],
119-
extends: ['plugin:codegen/recommended'],
120-
rules: {
121-
'mmkal/codegen/codegen': 'off',
122-
'codegen/codegen': 'warn',
123-
},
124-
}
177+
module.exports = [
178+
...require(${JSON.stringify(path.join(__dirname, '..', 'eslint.config.js'))})
179+
.map(({rules, ...cfg}) => ({
180+
...cfg,
181+
}))
182+
.filter(cfg => Object.keys(cfg).length > 0),
183+
{rules: {'codegen/codegen': 'warn'}},
184+
]
125185
`,
126186
)
127187

128188
write('src/barrel/a.ts', 'export const a = 1')
129189
write('src/barrel/b.ts', 'export const b = 1')
130190
write('src/barrel/index.ts', '')
131191
write('src/custom/index.ts', '')
132-
write('README.md', '')
192+
write('src/custom/component.tsx', '')
193+
write('README.md', 'Sample project')
133194

195+
// use local eslint-plugin-codegen by inserting typescript directly into node_modules
196+
editJson('package.json', (json: any) => (json.devDependencies['eslint-plugin-codegen'] = '*'))
134197
write(
135-
'.vscode/settings.json',
136-
fs
137-
.readFileSync(path.join(__dirname, '../.vscode/settings.json'))
138-
.toString()
139-
.replace('{', '{\n "editor.autoClosingBrackets": "never",')
140-
.replace('{', '{\n "editor.autoIndent": "none",')
141-
.replace('{', '{\n "editor.insertSpaces": false,'),
198+
'node_modules/eslint-plugin-codegen/index.ts',
199+
`export * from ${JSON.stringify(path.join(__dirname, '..', 'src'))}`,
142200
)
143201

202+
editJson('.vscode/settings.json', (json: any) => {
203+
json['editor.inlineSuggest.enabled'] = false
204+
json['editor.autoClosingBrackets'] = 'never'
205+
json['editor.autoClosingQuotes'] = 'never'
206+
json['editor.autoIndent'] = 'none'
207+
json['editor.insertSpaces'] = false
208+
json['eslint.experimental.useFlatConfig'] = true
209+
})
210+
144211
return projectPath
145212
})
146213
},
147214
// eslint-disable-next-line no-empty-pattern
148215
async createTempDir({}, use) {
149-
const tempDirs: string[] = []
216+
const temporaryDirectories: string[] = []
150217
await use(async () => {
151-
const tempDir = await fs.promises.realpath(await fs.promises.mkdtemp(path.join(os.tmpdir(), 'pwtest-')))
152-
tempDirs.push(tempDir)
153-
return tempDir
218+
const tempo = await fs.promises.realpath(await fs.promises.mkdtemp(path.join(os.tmpdir(), 'pwtest-')))
219+
temporaryDirectories.push(tempo)
220+
return tempo
154221
})
155-
for (const tempDir of tempDirs) await fs.promises.rm(tempDir, {recursive: true})
222+
for (const tempo of temporaryDirectories) await fs.promises.rm(tempo, {recursive: true})
156223
},
157224
})
158225

0 commit comments

Comments
 (0)