Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion .github/workflows/autoflow-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,45 @@ jobs:
- name: Workspace runtime qualification (Node 24 + workerd)
run: deno task fullstack:workspace-qualification

# #1228 (B2.5): the deployment guide and PACKAGE_SURFACE.md claim the
# generated dist/server artifacts run on Bun. A claim that survives only on
# local evidence is an overclaim, so the same fixture the Node legs serve is
# booted under a pinned Bun here: real server, real HTTP probes.
bun-serve-smoke:
name: dist/server Bun smoke
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
# v7.0.1
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
# #1156 R11: same exact-SHA expression as every required job.
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
- uses: ./.github/actions/setup-deno-workspace
- name: Install Bun 1.4.1 (version + SHA-256 pinned)
run: |
curl -sSfL -o /tmp/bun.zip \
https://github.com/oven-sh/bun/releases/download/bun-v1.4.1/bun-linux-x64.zip
echo "74c1c3bee7cd998500c8f969cd8972355ac6a07207e94a39eece1999b56ffabf /tmp/bun.zip" | sha256sum -c -
unzip -q /tmp/bun.zip -d /tmp/bun
echo "/tmp/bun/bun-linux-x64" >> "$GITHUB_PATH"
- name: Build the request-time fixture
run: deno task fixture:request-time:build
- name: Boot dist/server/serve.mjs under Bun
working-directory: packages/adapter-vite/__fixtures__/request-time
run: |
set -u
OPEN_ELEMENT_PORT=4893 OPEN_ELEMENT_HOST=127.0.0.1 bun dist/server/serve.mjs &
server_pid=$!
trap 'kill $server_pid 2>/dev/null || true' EXIT
for _ in $(seq 1 50); do
curl -sf -o /dev/null http://127.0.0.1:4893/ && break
sleep 0.2
done
curl -sf http://127.0.0.1:4893/ | grep -q 'request-time fixture home'
curl -sf http://127.0.0.1:4893/live | grep -q 'request-time live'

# #1156 (ADR-0146): one deterministic exact-SHA PR full-CI evidence artifact.
# This job runs only for pull requests and only after every required
# full-matrix job succeeded (default needs gating — no `if: always()`), so a
Expand All @@ -196,7 +235,13 @@ jobs:
pr-full-ci-evidence:
name: pr-full-ci-evidence
if: github.event_name == 'pull_request'
needs: [dependency-review, autoflow-ci, node-serve-smoke, workspace-qualification]
needs: [
dependency-review,
autoflow-ci,
node-serve-smoke,
bun-serve-smoke,
workspace-qualification,
]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
Expand Down
23 changes: 23 additions & 0 deletions docs/current/DENO_DESKTOP_TARGET.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,26 @@ Manual native smoke remains required when Deno Desktop canary or OS integration
changes: build the Reader, open the native app, verify directory picker behavior,
and verify the close button or Cmd/Ctrl+W triggers `/api/app/close` and clean
shutdown.

## v0.44 Build Status (Beta.2 ruling, #1228)

The desktop examples predate the v0.44 compiled module grammar. Their SPA
route modules colocate `loader`/`action`/`tagName` exports and helper
statements with the `@element` page class, and their `render()` bodies carry
local statements and early returns — all outside the compiled grammar — so
the full `npm:vite build` fails closed with OEC9008 (reader: 5 route modules,
mastodon: 4) and, behind it, OEC9007. The fail-closed behavior is the grammar
working as designed: no silent fallback, no wrong output.

Beta.2 investigated a bounded repair. Moving the route wiring into sibling
plain modules is mechanical, but the `render()` bodies then fail OEC9007
(single-return JSX) and OEC9006 (undecorated fields), so the repair is a full
re-authoring of nine large renders against the compiled grammar with no
browser E2E safety net — not a bounded fix. The ruling: the desktop examples
are **excluded from qualifying consumer evidence** and the re-authoring is
carried to Beta.3 (B3.8).

`deno task check` and `deno task smoke` stay green and CI-gated
(`examples:check`), so the examples still qualify the SPA runtime, loaders,
actions, and UI interop against workspace source. They are workspace dogfood
fixtures, not packed-artifact consumers.
7 changes: 7 additions & 0 deletions examples/deno-desktop-mastodon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ deno task build

This builds the SPA with Vite and then packages the Deno Desktop app.

> **v0.44 status (#1228):** `deno task build` currently fails closed with
> OEC9008/OEC9007 — the route modules predate the v0.44 compiled module
> grammar (runtime top-level statements and multi-statement `render()`
> bodies). Re-authoring against the grammar is carried to Beta.3 (B3.8); see
> `docs/current/DENO_DESKTOP_TARGET.md`. `deno task check` and
> `deno task smoke` stay green and CI-gated via `examples:check`.

To build and open the desktop window:

```bash
Expand Down
7 changes: 7 additions & 0 deletions examples/deno-desktop-reader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ deno task build # Vite build + deno desktop compile
open deno-desktop-reader.app
```

> **v0.44 status (#1228):** `deno task build` currently fails closed with
> OEC9008/OEC9007 — the route modules predate the v0.44 compiled module
> grammar (runtime top-level statements and multi-statement `render()`
> bodies). Re-authoring against the grammar is carried to Beta.3 (B3.8); see
> `docs/current/DENO_DESKTOP_TARGET.md`. `deno task check` and
> `deno task smoke` stay green and CI-gated via `examples:check`.

## Architecture

- `reader.tsx` — Vite client entry, SPA bootstrap
Expand Down
2 changes: 1 addition & 1 deletion examples/open-element-in-fresh/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
],
"imports": {
"@/": "./",
"@openelement/ui": "npm:@openelement/ui@^0.42.0",
"@openelement/ui": "npm:@openelement/ui@0.44.0-beta.1",
"fresh": "jsr:@fresh/core@^2.3.3",
"fresh/runtime": "jsr:@fresh/core@^2.3.3/runtime",
"preact": "npm:preact@^10.29.1",
Expand Down
12 changes: 6 additions & 6 deletions examples/open-element-in-fresh/deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

99 changes: 99 additions & 0 deletions packages/create/__tests__/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
assertUnifiedProductVersions,
buildTemplates,
resolveVersions,
validateProjectName,
} from '../src/template-builder.ts';

const packageDir = join(import.meta.dirname!, '..');
Expand All @@ -24,6 +25,23 @@ async function runCreate(executable: string, cwd: string, name: string) {
return new TextDecoder().decode(result.stdout);
}

async function runCreateExpectingFailure(executable: string, cwd: string, name: string) {
const result = await new Deno.Command(Deno.execPath(), {
args: ['run', '-A', executable, name],
cwd,
stdout: 'piped',
stderr: 'piped',
}).output();
assert(result.code !== 0, `expected create to fail for "${name}"`);
return new TextDecoder().decode(result.stderr);
}

// A clean, actionable CLI error is a single message line: no runtime stack
// trace vomit (L9). Deno stack frames are indented `at file:///` lines.
function assertCleanError(stderr: string): void {
assertFalse(stderr.includes('\n at '), `stack trace leaked into CLI error:\n${stderr}`);
}

Deno.test('starter exposes only product imports and the standard lifecycle', () => {
const denoJson = JSON.parse(readTemplate('deno.json.tmpl'));
assertEquals(Object.keys(denoJson.imports).sort(), [
Expand Down Expand Up @@ -286,6 +304,87 @@ Deno.test('source CLI generates a complete, token-free starter', async () => {
}
});

Deno.test('L11: project name validation enforces npm-name and traversal rules', () => {
for (const name of ['my-app', 'app', 'my_app', 'app2', '2app', 'my.app', 'a']) {
assertEquals(validateProjectName(name), null, name);
}
const invalid: Array<[string, string]> = [
['MyApp', 'lowercase'],
['my app', 'may only contain'],
['foo/bar', 'may only contain'],
['.hidden', 'may only contain'],
['_private', 'may only contain'],
['-leading', 'may only contain'],
['../escape', '".."'],
['a..b', '".."'],
['x'.repeat(215), '214'],
];
for (const [name, fragment] of invalid) {
const message = validateProjectName(name);
assert(message !== null, `expected "${name}" to be rejected`);
assert(message.includes(fragment), `"${name}": expected "${fragment}" in "${message}"`);
}
});

Deno.test('L9/L11: CLI rejects an invalid project name with a clean actionable error', async () => {
const tmpRoot = Deno.makeTempDirSync({ prefix: 'open-create-invalid-' });
try {
const stderr = await runCreateExpectingFailure(
join(packageDir, 'src', 'cli.ts'),
tmpRoot,
'Bad Name',
);
assert(stderr.includes('Invalid project name'), stderr);
assertCleanError(stderr);
assertFalse(existsSync(join(tmpRoot, 'Bad Name')));
} finally {
Deno.removeSync(tmpRoot, { recursive: true });
}
});

Deno.test('L9: CLI refuses an existing target directory with guidance, not a stack trace', async () => {
const tmpRoot = Deno.makeTempDirSync({ prefix: 'open-create-exists-' });
try {
const executable = join(packageDir, 'src', 'cli.ts');
await runCreate(executable, tmpRoot, 'sample-app');
const stderr = await runCreateExpectingFailure(executable, tmpRoot, 'sample-app');
assert(stderr.includes('already exists'), stderr);
// Actionable: tells the adopter how to resolve the collision.
assert(stderr.includes('Choose a different name'), stderr);
assertCleanError(stderr);
} finally {
Deno.removeSync(tmpRoot, { recursive: true });
}
});

Deno.test({
name: 'L9: CLI reports scaffolding write failures cleanly and actionably',
// chmod-based permission failures are a POSIX mechanism; the CI matrix for
// this suite is Linux/macOS only.
ignore: Deno.build.os === 'windows',
async fn() {
const tmpRoot = Deno.makeTempDirSync({ prefix: 'open-create-readonly-' });
try {
Deno.mkdirSync(join(tmpRoot, 'readonly'));
Deno.chmodSync(join(tmpRoot, 'readonly'), 0o555);
const stderr = await runCreateExpectingFailure(
join(packageDir, 'src', 'cli.ts'),
join(tmpRoot, 'readonly'),
'sample-app',
);
assert(
stderr.includes('Permission denied') || stderr.includes('Failed to'),
stderr,
);
assert(stderr.includes('sample-app'), stderr);
assertCleanError(stderr);
} finally {
Deno.chmodSync(join(tmpRoot, 'readonly'), 0o755);
Deno.removeSync(tmpRoot, { recursive: true });
}
},
});

Deno.test('packed CLI retains every starter template, including dotfiles', async () => {
const tmpRoot = Deno.makeTempDirSync({ prefix: 'open-create-packed-' });
try {
Expand Down
Loading
Loading