Skip to content

Commit e3776cf

Browse files
authored
feat(cli,cloud): publish static websites through project settings (#455)
1 parent 74d610d commit e3776cf

11 files changed

Lines changed: 963 additions & 7 deletions

File tree

‎examples/local-runner/cloud-app/README.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,9 @@ a notebook or start a run. Use the published flow below for functional testing.
7070
# 2. Copy the snapshot reader into this directory
7171
cp packages/local-runner/dist/snapshot-reader.iife.js examples/local-runner/cloud-app/snapshot-reader.js
7272

73-
# 3. Publish with a personal API token
74-
DEEPNOTE_TOKEN=... deepnote publish examples/local-runner/cloud-app --project-id <your-project-id>
73+
# 3. Publish with a personal API token and enable the app's Deepnote API access
74+
DEEPNOTE_TOKEN=... deepnote publish examples/local-runner/cloud-app --project-id <your-project-id> --api-access enabled
7575
```
7676

77-
Once published, the app runs on `static-<projectId>.outputs.deepnoteworkspace.com` and acquires a token automatically via postMessage — no server, no manual token configuration.
77+
The command enables website sharing and prints the canonical URL. At that URL, the app acquires a
78+
static-app viewer token automatically via `postMessage` — no server or manual token configuration.

‎packages/cli/README.md‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ deepnote convert notebook.ipynb
4848

4949
# Schedule recurring runs in Deepnote Cloud
5050
deepnote schedule report.deepnote --daily --at 09:00
51+
52+
# Publish a static website to an existing Deepnote project
53+
deepnote publish ./dist --project-id <uuid>
5154
```
5255

5356
## Commands
@@ -516,6 +519,43 @@ deepnote open my-project.deepnote
516519
deepnote open my-project.deepnote -o json
517520
```
518521

522+
### `publish <dir>`
523+
524+
Publish a local static website to an existing Deepnote project. Matching remote files are replaced,
525+
then static website sharing is enabled only after every upload succeeds. By default, existing remote
526+
files that are absent locally and the project's API-access setting are both left unchanged.
527+
528+
```bash
529+
deepnote publish ./dist --project-id <uuid>
530+
```
531+
532+
**Options:**
533+
534+
| Option | Description | Default |
535+
| -------------------------------- | ----------------------------------------------------------------- | -------------------------- |
536+
| `--project-id <uuid>` | Project to publish to (required) | |
537+
| `--path <prefix>` | Target directory at or below `_deepnote_static` | `_deepnote_static` |
538+
| `--api-access enabled\|disabled` | Explicitly enable or disable API access for the published website | unchanged |
539+
| `--prune` | Delete remote files below `--path` that are absent locally | `false` |
540+
| `--token <token>` | Deepnote API token | `DEEPNOTE_TOKEN` |
541+
| `--url <url>` | Deepnote API base URL | `https://api.deepnote.com` |
542+
543+
The command prints the canonical website URL returned by the server. Use `--api-access enabled`
544+
only when the website needs to load notebooks or start runs through the Deepnote API.
545+
546+
**Examples:**
547+
548+
```bash
549+
# Publish an app that needs a static-app viewer token
550+
deepnote publish ./dist --project-id <uuid> --api-access enabled
551+
552+
# Remove files left behind by an older build
553+
deepnote publish ./dist --project-id <uuid> --prune
554+
555+
# Publish a versioned subdirectory
556+
deepnote publish ./dist --project-id <uuid> --path _deepnote_static/v2
557+
```
558+
519559
### `schedule <path>`
520560

521561
Create or update a recurring notebook run in Deepnote Cloud. This does not run the notebook

‎packages/cli/src/cli.ts‎

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { createIntegrationsAddAction } from './commands/integrations/add-integra
1616
import { createIntegrationsEditAction } from './commands/integrations/edit-integration'
1717
import { createLintAction } from './commands/lint'
1818
import { createOpenAction } from './commands/open'
19+
import { createPublishAction } from './commands/publish'
1920
import { createRunAction } from './commands/run'
2021
import { createScheduleAction } from './commands/schedule'
2122
import { createSplitAction } from './commands/split'
@@ -57,8 +58,8 @@ export function createProgram(): Command {
5758
})
5859
.exitOverride(err => {
5960
// Map Commander errors to appropriate exit codes
60-
// InvalidArgumentError (e.g., invalid --type value) should exit with InvalidUsage (2)
61-
if (err.code === 'commander.invalidArgument') {
61+
// Invalid values and missing required options are both invalid usage (2).
62+
if (err.code === 'commander.invalidArgument' || err.code === 'commander.missingMandatoryOptionValue') {
6263
process.exit(ExitCode.InvalidUsage)
6364
}
6465
// For other Commander errors, use the default exit code
@@ -563,6 +564,56 @@ ${c.bold('Exit Codes:')}
563564
})
564565
.action(createSyncAction(program))
565566

567+
// Publish command - publish a local app directory to Deepnote
568+
program
569+
.command('publish')
570+
.description('Publish a local app directory to a Deepnote project')
571+
.argument('<dir>', 'Directory containing the app files to publish')
572+
.requiredOption('--project-id <uuid>', 'Deepnote project ID to publish to')
573+
.option('--url <url>', 'API base URL', DEFAULT_API_URL)
574+
.option('--token <token>', `Bearer token for the Deepnote API (or use ${DEEPNOTE_TOKEN_ENV} env var)`)
575+
.option('--path <prefix>', 'Target directory under _deepnote_static', '_deepnote_static')
576+
.addOption(
577+
new Option('--api-access <state>', 'Allow the published app to call Deepnote APIs').choices([
578+
'enabled',
579+
'disabled',
580+
])
581+
)
582+
.option('--prune', 'Delete remote files below --path that are absent locally')
583+
.addHelpText('after', () => {
584+
const c = getChalk()
585+
return `
586+
${c.bold('Description:')}
587+
Replaces matching files in ${c.dim('_deepnote_static/')} and enables static website sharing
588+
after every upload succeeds. API access is left unchanged unless explicitly set.
589+
590+
${c.bold('Examples:')}
591+
${c.dim('# Publish a build directory to a project')}
592+
$ deepnote publish ./dist --project-id 0f1e2d3c-4b5a-6789-abcd-ef0123456789
593+
594+
${c.dim('# Publish with an explicit token')}
595+
$ deepnote publish ./build --project-id <uuid> --token <token>
596+
597+
${c.dim('# Publish to a custom path prefix')}
598+
$ deepnote publish ./out --project-id <uuid> --path _deepnote_static/v2
599+
600+
${c.dim('# Let the published app call Deepnote APIs')}
601+
$ deepnote publish ./dist --project-id <uuid> --api-access enabled
602+
603+
${c.dim('# Remove remote files that are no longer in the local build')}
604+
$ deepnote publish ./dist --project-id <uuid> --prune
605+
606+
${c.dim('# Quiet mode (no progress output)')}
607+
$ deepnote publish ./dist --project-id <uuid> -q
608+
609+
${c.bold('Exit Codes:')}
610+
${c.dim('0')} Files uploaded and website sharing enabled
611+
${c.dim('1')} Upload, pruning, or settings update failed
612+
${c.dim('2')} Invalid usage (bad path, directory not found, missing token)
613+
`
614+
})
615+
.action(createPublishAction(program))
616+
566617
// Convert command - convert between notebook formats
567618
program
568619
.command('convert')

0 commit comments

Comments
 (0)