@vellumai/assistant 0.5.1 → 0.5.2
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/ARCHITECTURE.md +54 -54
- package/docs/architecture/integrations.md +62 -67
- package/docs/credential-execution-service.md +3 -3
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +111 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
- package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
- package/src/__tests__/app-dir-path-guard.test.ts +78 -0
- package/src/__tests__/app-executors.test.ts +1 -291
- package/src/__tests__/app-git-history.test.ts +4 -4
- package/src/__tests__/app-routes-csp.test.ts +1 -0
- package/src/__tests__/app-store-dir-names.test.ts +426 -0
- package/src/__tests__/attachments-store.test.ts +169 -21
- package/src/__tests__/attachments.test.ts +115 -1
- package/src/__tests__/btw-routes.test.ts +1 -0
- package/src/__tests__/canonical-guardian-store.test.ts +38 -0
- package/src/__tests__/channel-reply-delivery.test.ts +55 -0
- package/src/__tests__/checker.test.ts +54 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/config-schema-cmd.test.ts +68 -21
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +149 -5
- package/src/__tests__/conversation-agent-loop.test.ts +290 -2
- package/src/__tests__/conversation-attachments.test.ts +17 -19
- package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
- package/src/__tests__/conversation-disk-view.test.ts +810 -0
- package/src/__tests__/conversation-error.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +551 -0
- package/src/__tests__/conversation-fork-route.test.ts +386 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
- package/src/__tests__/conversation-media-retry.test.ts +8 -2
- package/src/__tests__/conversation-queue.test.ts +36 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
- package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
- package/src/__tests__/conversation-skill-tools.test.ts +4 -9
- package/src/__tests__/conversation-slash-commands.test.ts +149 -0
- package/src/__tests__/conversation-store.test.ts +24 -21
- package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
- package/src/__tests__/conversation-title-service.test.ts +137 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-vault-unit.test.ts +5 -10
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
- package/src/__tests__/diagnostics-export.test.ts +70 -1
- package/src/__tests__/first-greeting.test.ts +80 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
- package/src/__tests__/history-repair.test.ts +32 -10
- package/src/__tests__/http-conversation-lineage.test.ts +251 -0
- package/src/__tests__/image-source-path-reinject.test.ts +136 -0
- package/src/__tests__/llm-context-normalization.test.ts +1116 -0
- package/src/__tests__/llm-context-route-provider.test.ts +217 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
- package/src/__tests__/media-generate-image.test.ts +47 -94
- package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
- package/src/__tests__/memory-recall-quality.test.ts +5 -5
- package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
- package/src/__tests__/migration-export-http.test.ts +3 -1
- package/src/__tests__/migration-import-commit-http.test.ts +18 -4
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
- package/src/__tests__/mime-builder.test.ts +3 -2
- package/src/__tests__/non-member-access-request.test.ts +12 -1
- package/src/__tests__/notification-decision-identity.test.ts +52 -0
- package/src/__tests__/oauth-apps-routes.test.ts +103 -0
- package/src/__tests__/oauth-store.test.ts +115 -0
- package/src/__tests__/provider-error-scenarios.test.ts +1 -3
- package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
- package/src/__tests__/recording-handler.test.ts +17 -0
- package/src/__tests__/registry.test.ts +3 -8
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
- package/src/__tests__/schema-transforms.test.ts +165 -5
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -2
- package/src/__tests__/starter-task-flow.test.ts +1 -0
- package/src/__tests__/suggestion-routes.test.ts +443 -0
- package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
- package/src/__tests__/swarm-recursion.test.ts +1 -0
- package/src/__tests__/swarm-tool.test.ts +1 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
- package/src/__tests__/top-level-renderer.test.ts +22 -0
- package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
- package/src/__tests__/web-fetch.test.ts +6 -2
- package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
- package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
- package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
- package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
- package/src/agent/attachments.ts +27 -1
- package/src/agent/loop.ts +29 -1
- package/src/avatar/traits-png-sync.ts +80 -25
- package/src/bundler/app-bundler.ts +4 -4
- package/src/calls/call-domain.ts +1 -0
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/auth.ts +92 -0
- package/src/cli/commands/avatar.ts +7 -6
- package/src/cli/commands/config.ts +2 -0
- package/src/cli/commands/oauth/providers.ts +29 -0
- package/src/cli/program.ts +12 -0
- package/src/cli.ts +15 -48
- package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
- package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
- package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
- package/src/config/bundled-tool-registry.ts +2 -14
- package/src/config/feature-flag-registry.json +8 -0
- package/src/config/loader.ts +64 -0
- package/src/config/raw-config-utils.ts +30 -0
- package/src/config/schema-utils.ts +28 -7
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/elevenlabs.ts +18 -0
- package/src/config/schemas/memory-lifecycle.ts +4 -2
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/services.ts +8 -6
- package/src/contacts/contact-store.ts +13 -6
- package/src/contacts/contacts-write.ts +0 -1
- package/src/context/window-manager.ts +13 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +48 -7
- package/src/daemon/conversation-agent-loop.ts +56 -19
- package/src/daemon/conversation-attachments.ts +18 -36
- package/src/daemon/conversation-error.ts +2 -1
- package/src/daemon/conversation-history.ts +18 -4
- package/src/daemon/conversation-lifecycle.ts +39 -15
- package/src/daemon/conversation-messaging.ts +70 -26
- package/src/daemon/conversation-process.ts +58 -34
- package/src/daemon/conversation-runtime-assembly.ts +21 -38
- package/src/daemon/conversation-slash.ts +121 -256
- package/src/daemon/conversation-surfaces.ts +143 -20
- package/src/daemon/conversation-tool-setup.ts +0 -6
- package/src/daemon/conversation-workspace.ts +21 -1
- package/src/daemon/conversation.ts +51 -29
- package/src/daemon/first-greeting.ts +35 -0
- package/src/daemon/handlers/config-embeddings.ts +148 -0
- package/src/daemon/handlers/config-model.ts +71 -26
- package/src/daemon/handlers/conversations.ts +0 -23
- package/src/daemon/handlers/recording.ts +26 -21
- package/src/daemon/host-cu-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +106 -64
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +19 -0
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/shared.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/message-types/upgrades.ts +23 -0
- package/src/daemon/server.ts +83 -12
- package/src/daemon/shutdown-handlers.ts +8 -5
- package/src/daemon/startup-error.ts +9 -0
- package/src/daemon/tool-side-effects.ts +11 -28
- package/src/events/tool-permission-telemetry-listener.ts +1 -3
- package/src/instrument.ts +0 -4
- package/src/media/app-icon-generator.ts +2 -2
- package/src/memory/app-git-service.ts +28 -16
- package/src/memory/app-store.ts +230 -41
- package/src/memory/attachments-store.ts +558 -130
- package/src/memory/conversation-attention-store.ts +70 -0
- package/src/memory/conversation-crud.ts +442 -3
- package/src/memory/conversation-directories.ts +125 -0
- package/src/memory/conversation-disk-view.ts +390 -0
- package/src/memory/conversation-key-store.ts +17 -5
- package/src/memory/conversation-queries.ts +5 -1
- package/src/memory/conversation-title-service.ts +21 -49
- package/src/memory/db-init.ts +28 -0
- package/src/memory/embedding-backend.ts +42 -53
- package/src/memory/embedding-gemini.test.ts +4 -4
- package/src/memory/embedding-local.ts +1 -3
- package/src/memory/embedding-ollama.ts +1 -3
- package/src/memory/embedding-openai.ts +1 -3
- package/src/memory/indexer.ts +9 -7
- package/src/memory/items-extractor.ts +42 -13
- package/src/memory/job-handlers/conversation-starters.ts +6 -1
- package/src/memory/job-handlers/embedding.test.ts +1 -4
- package/src/memory/llm-request-log-store.ts +100 -1
- package/src/memory/migrations/102-alter-table-columns.ts +5 -0
- package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
- package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
- package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
- package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
- package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
- package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
- package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
- package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
- package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/retriever.test.ts +601 -2
- package/src/memory/retriever.ts +85 -9
- package/src/memory/schema/conversations.ts +6 -0
- package/src/memory/schema/infrastructure.ts +13 -7
- package/src/memory/schema/oauth.ts +6 -0
- package/src/messaging/providers/gmail/mime-builder.ts +3 -1
- package/src/notifications/copy-composer.ts +26 -0
- package/src/notifications/decision-engine.ts +14 -1
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +36 -0
- package/src/oauth/byo-connection.test.ts +1 -45
- package/src/oauth/byo-connection.ts +2 -8
- package/src/oauth/connect-orchestrator.ts +15 -11
- package/src/oauth/connection-resolver.test.ts +191 -0
- package/src/oauth/connection-resolver.ts +66 -38
- package/src/oauth/connection.ts +0 -1
- package/src/oauth/oauth-store.ts +97 -47
- package/src/oauth/platform-connection.test.ts +0 -1
- package/src/oauth/platform-connection.ts +11 -3
- package/src/oauth/seed-providers.ts +78 -3
- package/src/oauth/token-persistence.ts +16 -10
- package/src/permissions/checker.ts +71 -8
- package/src/prompts/templates/BOOTSTRAP.md +2 -0
- package/src/providers/anthropic/client.ts +8 -1
- package/src/providers/failover.ts +4 -1
- package/src/providers/gemini/client.ts +50 -0
- package/src/providers/model-catalog.ts +92 -0
- package/src/providers/model-intents.ts +29 -20
- package/src/providers/openai/client.ts +49 -0
- package/src/providers/types.ts +2 -0
- package/src/runtime/access-request-helper.ts +16 -7
- package/src/runtime/auth/credential-service.ts +3 -1
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/btw-sidechain.ts +101 -0
- package/src/runtime/channel-reply-delivery.ts +17 -1
- package/src/runtime/http-router.ts +3 -1
- package/src/runtime/http-server.ts +196 -141
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/migrations/vbundle-builder.ts +5 -1
- package/src/runtime/routes/access-request-decision.ts +41 -0
- package/src/runtime/routes/app-management-routes.ts +6 -3
- package/src/runtime/routes/app-routes.ts +7 -3
- package/src/runtime/routes/approval-routes.ts +1 -0
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
- package/src/runtime/routes/attachment-routes.ts +45 -15
- package/src/runtime/routes/btw-routes.ts +21 -61
- package/src/runtime/routes/conversation-management-routes.ts +68 -0
- package/src/runtime/routes/conversation-query-routes.ts +180 -10
- package/src/runtime/routes/conversation-routes.ts +222 -28
- package/src/runtime/routes/conversation-starter-routes.ts +9 -11
- package/src/runtime/routes/diagnostics-routes.ts +1 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
- package/src/runtime/routes/llm-context-normalization.ts +1199 -0
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/memory-item-routes.test.ts +34 -0
- package/src/runtime/routes/memory-item-routes.ts +4 -0
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/oauth-apps.ts +291 -0
- package/src/runtime/routes/secret-routes.ts +28 -1
- package/src/runtime/routes/settings-routes.ts +14 -0
- package/src/runtime/routes/trace-event-routes.ts +4 -1
- package/src/schedule/schedule-store.ts +9 -21
- package/src/security/secure-keys.ts +21 -0
- package/src/signals/bash.ts +1 -1
- package/src/swarm/backend-claude-code.ts +3 -6
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +3 -1
- package/src/tools/AGENTS.md +6 -10
- package/src/tools/apps/executors.ts +17 -232
- package/src/tools/claude-code/claude-code.ts +2 -3
- package/src/tools/credentials/vault.ts +7 -12
- package/src/tools/host-filesystem/read.ts +13 -10
- package/src/tools/network/__tests__/web-search.test.ts +4 -2
- package/src/tools/schedule/list.ts +2 -7
- package/src/tools/schema-transforms.ts +5 -0
- package/src/tools/shared/filesystem/format-diff.ts +2 -7
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/tool-manifest.ts +0 -6
- package/src/tools/ui-surface/definitions.ts +2 -2
- package/src/util/device-id.ts +28 -5
- package/src/util/platform.ts +6 -0
- package/src/util/pricing.ts +1 -0
- package/src/util/retry.ts +1 -3
- package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
- package/src/workspace/migrations/003-seed-device-id.ts +3 -4
- package/src/workspace/migrations/006-services-config.ts +5 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
- package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/top-level-renderer.ts +12 -0
- package/src/__tests__/asset-materialize-tool.test.ts +0 -523
- package/src/__tests__/asset-search-tool.test.ts +0 -536
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
- package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
- package/src/__tests__/media-visibility-policy.test.ts +0 -190
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
- package/src/daemon/media-visibility-policy.ts +0 -59
- package/src/tools/assets/materialize.ts +0 -248
- package/src/tools/assets/search.ts +0 -400
package/src/memory/app-store.ts
CHANGED
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Directory layout:
|
|
5
5
|
* ~/.vellum/apps/
|
|
6
|
-
* <
|
|
7
|
-
* <
|
|
6
|
+
* <dirName>.json # app definition
|
|
7
|
+
* <dirName>/
|
|
8
8
|
* records/
|
|
9
9
|
* <record-id>.json # individual record
|
|
10
|
+
*
|
|
11
|
+
* `dirName` is a human-readable slug derived from the app name at creation
|
|
12
|
+
* time and frozen thereafter (renaming an app does NOT rename its directory).
|
|
13
|
+
* Pre-migration apps (no `dirName` in JSON) fall back to using `id` (UUID).
|
|
10
14
|
*/
|
|
11
15
|
|
|
12
16
|
import { randomUUID } from "node:crypto";
|
|
@@ -43,12 +47,14 @@ export interface AppDefinition {
|
|
|
43
47
|
schemaJson: string;
|
|
44
48
|
htmlDefinition: string;
|
|
45
49
|
version?: string;
|
|
46
|
-
/** Additional pages keyed by filename (e.g. "settings.html"
|
|
50
|
+
/** Additional pages keyed by filename (e.g. "settings.html" -> HTML content). */
|
|
47
51
|
pages?: Record<string, string>;
|
|
48
52
|
createdAt: number;
|
|
49
53
|
updatedAt: number;
|
|
50
54
|
/** App format version. undefined or 1 = legacy single-HTML, 2 = multi-file TSX. */
|
|
51
55
|
formatVersion?: number;
|
|
56
|
+
/** Filesystem directory/file stem. Frozen at creation -- never changes on rename. */
|
|
57
|
+
dirName?: string;
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
/**
|
|
@@ -78,6 +84,29 @@ function validateId(id: string): void {
|
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Validate a dirName read from persisted JSON.
|
|
89
|
+
* Superset of validateId rules plus git pathspec metacharacters,
|
|
90
|
+
* since dirName is used directly in git pathspecs by app-git-service.
|
|
91
|
+
*/
|
|
92
|
+
export function validateDirName(dirName: string): void {
|
|
93
|
+
if (
|
|
94
|
+
!dirName ||
|
|
95
|
+
dirName.includes("/") ||
|
|
96
|
+
dirName.includes("\\") ||
|
|
97
|
+
dirName.includes("..") ||
|
|
98
|
+
dirName !== dirName.trim()
|
|
99
|
+
) {
|
|
100
|
+
throw new Error(`Invalid dirName: ${dirName}`);
|
|
101
|
+
}
|
|
102
|
+
// Reject git pathspec metacharacters
|
|
103
|
+
if (/[*?[\]:()]/.test(dirName)) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Invalid dirName: contains git pathspec characters: ${dirName}`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
81
110
|
/**
|
|
82
111
|
* Validate a page filename to prevent path traversal and ensure it is a safe
|
|
83
112
|
* relative filename (e.g. "settings.html").
|
|
@@ -103,6 +132,121 @@ export function getAppsDir(): string {
|
|
|
103
132
|
return dir;
|
|
104
133
|
}
|
|
105
134
|
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Slug generation
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Convert a name to a filesystem-safe slug.
|
|
141
|
+
* - Lowercase
|
|
142
|
+
* - Replace non-alphanumeric (except hyphens) with hyphens
|
|
143
|
+
* - Collapse consecutive hyphens, trim leading/trailing
|
|
144
|
+
* - Truncate to 60 chars (re-trim trailing hyphen)
|
|
145
|
+
* - Fall back to "app-<random>" if result is empty (e.g. emoji-only names)
|
|
146
|
+
*/
|
|
147
|
+
export function slugify(name: string): string {
|
|
148
|
+
let slug = name
|
|
149
|
+
.toLowerCase()
|
|
150
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
151
|
+
.replace(/-{2,}/g, "-")
|
|
152
|
+
.replace(/^-+|-+$/g, "");
|
|
153
|
+
|
|
154
|
+
if (slug.length > 60) {
|
|
155
|
+
slug = slug.slice(0, 60).replace(/-+$/, "");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!slug) {
|
|
159
|
+
slug = `app-${randomUUID().slice(0, 8)}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return slug;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Generate a unique directory name from an app name.
|
|
167
|
+
* Appends -2, -3, etc. if the base slug collides with existing names.
|
|
168
|
+
*/
|
|
169
|
+
export function generateAppDirName(
|
|
170
|
+
name: string,
|
|
171
|
+
existingNames: Set<string>,
|
|
172
|
+
): string {
|
|
173
|
+
const base = slugify(name);
|
|
174
|
+
if (!existingNames.has(base)) return base;
|
|
175
|
+
let counter = 2;
|
|
176
|
+
while (existingNames.has(`${base}-${counter}`)) {
|
|
177
|
+
counter++;
|
|
178
|
+
}
|
|
179
|
+
return `${base}-${counter}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// App directory resolution (id -> dirName)
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
/** Cache of id -> dirName mappings to avoid repeated filesystem scans. */
|
|
187
|
+
const idToDirNameCache = new Map<string, string>();
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Resolve an app's directory name and path from its ID.
|
|
191
|
+
* Scans JSON files if not cached. Falls back to `id` for pre-migration apps.
|
|
192
|
+
*/
|
|
193
|
+
export function resolveAppDir(id: string): {
|
|
194
|
+
dirName: string;
|
|
195
|
+
appDir: string;
|
|
196
|
+
} {
|
|
197
|
+
validateId(id);
|
|
198
|
+
|
|
199
|
+
// Check cache first
|
|
200
|
+
const cached = idToDirNameCache.get(id);
|
|
201
|
+
if (cached) {
|
|
202
|
+
return { dirName: cached, appDir: join(getAppsDir(), cached) };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const dir = getAppsDir();
|
|
206
|
+
const entries = readdirSync(dir);
|
|
207
|
+
|
|
208
|
+
for (const entry of entries) {
|
|
209
|
+
if (!entry.endsWith(".json")) continue;
|
|
210
|
+
const filePath = join(dir, entry);
|
|
211
|
+
try {
|
|
212
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
213
|
+
const parsed = JSON.parse(raw) as { id?: string; dirName?: string };
|
|
214
|
+
if (parsed.id === id) {
|
|
215
|
+
const dirName = parsed.dirName || id;
|
|
216
|
+
if (parsed.dirName) {
|
|
217
|
+
validateDirName(dirName);
|
|
218
|
+
}
|
|
219
|
+
idToDirNameCache.set(id, dirName);
|
|
220
|
+
return { dirName, appDir: join(dir, dirName) };
|
|
221
|
+
}
|
|
222
|
+
} catch {
|
|
223
|
+
// skip malformed files
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// If no JSON file found, fall back to id (backward compat)
|
|
228
|
+
idToDirNameCache.set(id, id);
|
|
229
|
+
return { dirName: id, appDir: join(dir, id) };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Convenience wrapper: returns the app directory path for the given app ID. */
|
|
233
|
+
export function getAppDirPath(appId: string): string {
|
|
234
|
+
return resolveAppDir(appId).appDir;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Invalidate the id->dirName cache for a specific app or all apps. */
|
|
238
|
+
function invalidateDirNameCache(appId?: string): void {
|
|
239
|
+
if (appId) {
|
|
240
|
+
idToDirNameCache.delete(appId);
|
|
241
|
+
} else {
|
|
242
|
+
idToDirNameCache.clear();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
// File path validation
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
|
|
106
250
|
/**
|
|
107
251
|
* Validate a relative file path within an app directory.
|
|
108
252
|
* Prevents path traversal and access to protected directories.
|
|
@@ -123,7 +267,7 @@ function validateFilePath(appId: string, path: string): string {
|
|
|
123
267
|
if (normalized === "records" || normalized.startsWith("records/")) {
|
|
124
268
|
throw new Error(`Invalid file path: 'records/' directory is protected`);
|
|
125
269
|
}
|
|
126
|
-
const appDir =
|
|
270
|
+
const appDir = getAppDirPath(appId);
|
|
127
271
|
const resolved = resolve(appDir, path);
|
|
128
272
|
// Ensure the resolved path is still within the app directory
|
|
129
273
|
if (!resolved.startsWith(appDir + "/") && resolved !== appDir) {
|
|
@@ -132,7 +276,7 @@ function validateFilePath(appId: string, path: string): string {
|
|
|
132
276
|
// Follow symlinks to the real path so a symlink inside the app directory
|
|
133
277
|
// cannot escape the boundary. For non-existent paths, walk up to the
|
|
134
278
|
// nearest existing ancestor and resolve it, then re-append trailing
|
|
135
|
-
// components
|
|
279
|
+
// components -- catches symlinked parent directories on new file writes.
|
|
136
280
|
let realResolved: string;
|
|
137
281
|
if (existsSync(resolved)) {
|
|
138
282
|
realResolved = realpathSync(resolved);
|
|
@@ -163,9 +307,13 @@ function validateFilePath(appId: string, path: string): string {
|
|
|
163
307
|
return resolved;
|
|
164
308
|
}
|
|
165
309
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
// Pages helpers
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
|
|
314
|
+
/** Persist pages as individual files under the app's pages/ subdirectory. */
|
|
315
|
+
function savePages(appDirPath: string, pages: Record<string, string>): void {
|
|
316
|
+
const pagesDir = join(appDirPath, "pages");
|
|
169
317
|
mkdirSync(pagesDir, { recursive: true });
|
|
170
318
|
for (const [filename, content] of Object.entries(pages)) {
|
|
171
319
|
validatePageFilename(filename);
|
|
@@ -179,8 +327,8 @@ function savePages(appId: string, pages: Record<string, string>): void {
|
|
|
179
327
|
}
|
|
180
328
|
|
|
181
329
|
/** Load pages from disk. Returns undefined if no pages directory exists. */
|
|
182
|
-
function loadPages(
|
|
183
|
-
const pagesDir = join(
|
|
330
|
+
function loadPages(appDirPath: string): Record<string, string> | undefined {
|
|
331
|
+
const pagesDir = join(appDirPath, "pages");
|
|
184
332
|
if (!existsSync(pagesDir)) return undefined;
|
|
185
333
|
const entries = readdirSync(pagesDir);
|
|
186
334
|
if (entries.length === 0) return undefined;
|
|
@@ -191,6 +339,33 @@ function loadPages(appId: string): Record<string, string> | undefined {
|
|
|
191
339
|
return pages;
|
|
192
340
|
}
|
|
193
341
|
|
|
342
|
+
// ---------------------------------------------------------------------------
|
|
343
|
+
// Existing dirName collector (for dedup)
|
|
344
|
+
// ---------------------------------------------------------------------------
|
|
345
|
+
|
|
346
|
+
/** Scan all JSON files and build a set of existing dirName values. */
|
|
347
|
+
function collectExistingDirNames(): Set<string> {
|
|
348
|
+
const dir = getAppsDir();
|
|
349
|
+
const entries = readdirSync(dir);
|
|
350
|
+
const names = new Set<string>();
|
|
351
|
+
for (const entry of entries) {
|
|
352
|
+
if (!entry.endsWith(".json")) continue;
|
|
353
|
+
try {
|
|
354
|
+
const raw = readFileSync(join(dir, entry), "utf-8");
|
|
355
|
+
const parsed = JSON.parse(raw) as { id?: string; dirName?: string };
|
|
356
|
+
// Use dirName if present, otherwise fall back to id (pre-migration)
|
|
357
|
+
names.add(parsed.dirName ?? parsed.id ?? entry.replace(/\.json$/, ""));
|
|
358
|
+
} catch {
|
|
359
|
+
// skip malformed
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return names;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ---------------------------------------------------------------------------
|
|
366
|
+
// CRUD operations
|
|
367
|
+
// ---------------------------------------------------------------------------
|
|
368
|
+
|
|
194
369
|
export function createApp(params: {
|
|
195
370
|
name: string;
|
|
196
371
|
description?: string;
|
|
@@ -204,6 +379,11 @@ export function createApp(params: {
|
|
|
204
379
|
}): AppDefinition {
|
|
205
380
|
const dir = getAppsDir();
|
|
206
381
|
const now = Date.now();
|
|
382
|
+
|
|
383
|
+
// Generate a unique dirName from the app name
|
|
384
|
+
const existingNames = collectExistingDirNames();
|
|
385
|
+
const dirName = generateAppDirName(params.name, existingNames);
|
|
386
|
+
|
|
207
387
|
const app: AppDefinition = {
|
|
208
388
|
id: randomUUID(),
|
|
209
389
|
name: params.name,
|
|
@@ -216,10 +396,11 @@ export function createApp(params: {
|
|
|
216
396
|
createdAt: now,
|
|
217
397
|
updatedAt: now,
|
|
218
398
|
formatVersion: params.formatVersion,
|
|
399
|
+
dirName,
|
|
219
400
|
};
|
|
220
401
|
|
|
221
|
-
// Write htmlDefinition to {
|
|
222
|
-
const appDir = join(dir,
|
|
402
|
+
// Write htmlDefinition to {dirName}/index.html on disk
|
|
403
|
+
const appDir = join(dir, dirName);
|
|
223
404
|
mkdirSync(appDir, { recursive: true });
|
|
224
405
|
if (typeof params.htmlDefinition !== "string") {
|
|
225
406
|
throw new Error(
|
|
@@ -230,21 +411,24 @@ export function createApp(params: {
|
|
|
230
411
|
|
|
231
412
|
// Write preview to companion file to keep the JSON small
|
|
232
413
|
if (params.preview) {
|
|
233
|
-
writeFileSync(join(dir, `${
|
|
414
|
+
writeFileSync(join(dir, `${dirName}.preview`), params.preview, "utf-8");
|
|
234
415
|
}
|
|
235
416
|
|
|
236
|
-
// Strip htmlDefinition, pages, and preview from the JSON file
|
|
417
|
+
// Strip htmlDefinition, pages, and preview from the JSON file -- only store metadata
|
|
237
418
|
const {
|
|
238
419
|
htmlDefinition: _html,
|
|
239
420
|
pages: _pages,
|
|
240
421
|
preview: _preview,
|
|
241
422
|
...jsonDef
|
|
242
423
|
} = app;
|
|
243
|
-
writeFileSync(join(dir, `${
|
|
424
|
+
writeFileSync(join(dir, `${dirName}.json`), JSON.stringify(jsonDef, null, 2));
|
|
425
|
+
|
|
426
|
+
// Update cache
|
|
427
|
+
idToDirNameCache.set(app.id, dirName);
|
|
244
428
|
|
|
245
429
|
// Persist additional pages as separate files
|
|
246
430
|
if (params.pages && Object.keys(params.pages).length > 0) {
|
|
247
|
-
savePages(
|
|
431
|
+
savePages(appDir, params.pages);
|
|
248
432
|
app.pages = params.pages;
|
|
249
433
|
}
|
|
250
434
|
|
|
@@ -253,26 +437,27 @@ export function createApp(params: {
|
|
|
253
437
|
|
|
254
438
|
export function getApp(id: string): AppDefinition | null {
|
|
255
439
|
validateId(id);
|
|
440
|
+
const { dirName, appDir } = resolveAppDir(id);
|
|
256
441
|
const dir = getAppsDir();
|
|
257
|
-
const filePath = join(dir, `${
|
|
442
|
+
const filePath = join(dir, `${dirName}.json`);
|
|
258
443
|
if (!existsSync(filePath)) return null;
|
|
259
444
|
const raw = readFileSync(filePath, "utf-8");
|
|
260
445
|
const app = JSON.parse(raw) as AppDefinition;
|
|
261
446
|
|
|
262
|
-
// Read htmlDefinition from {
|
|
263
|
-
const indexPath = join(
|
|
447
|
+
// Read htmlDefinition from {dirName}/index.html on disk
|
|
448
|
+
const indexPath = join(appDir, "index.html");
|
|
264
449
|
app.htmlDefinition = existsSync(indexPath)
|
|
265
450
|
? readFileSync(indexPath, "utf-8")
|
|
266
451
|
: (app.htmlDefinition ?? "");
|
|
267
452
|
|
|
268
453
|
// Load preview from companion file
|
|
269
|
-
const previewPath = join(dir, `${
|
|
454
|
+
const previewPath = join(dir, `${dirName}.preview`);
|
|
270
455
|
if (existsSync(previewPath)) {
|
|
271
456
|
app.preview = readFileSync(previewPath, "utf-8");
|
|
272
457
|
}
|
|
273
458
|
|
|
274
459
|
// Load pages from disk
|
|
275
|
-
const pages = loadPages(
|
|
460
|
+
const pages = loadPages(appDir);
|
|
276
461
|
if (pages) {
|
|
277
462
|
app.pages = pages;
|
|
278
463
|
}
|
|
@@ -286,8 +471,9 @@ export function getApp(id: string): AppDefinition | null {
|
|
|
286
471
|
*/
|
|
287
472
|
export function getAppPreview(id: string): string | null {
|
|
288
473
|
validateId(id);
|
|
474
|
+
const { dirName } = resolveAppDir(id);
|
|
289
475
|
const dir = getAppsDir();
|
|
290
|
-
const previewPath = join(dir, `${
|
|
476
|
+
const previewPath = join(dir, `${dirName}.preview`);
|
|
291
477
|
if (existsSync(previewPath)) {
|
|
292
478
|
return readFileSync(previewPath, "utf-8");
|
|
293
479
|
}
|
|
@@ -334,6 +520,8 @@ export function updateApp(
|
|
|
334
520
|
const existing = getApp(id);
|
|
335
521
|
if (!existing) throw new Error(`App not found: ${id}`);
|
|
336
522
|
|
|
523
|
+
const { dirName, appDir } = resolveAppDir(id);
|
|
524
|
+
|
|
337
525
|
// Extract pages, htmlDefinition, and preview before spreading into the JSON-persisted definition
|
|
338
526
|
const {
|
|
339
527
|
pages,
|
|
@@ -348,9 +536,8 @@ export function updateApp(
|
|
|
348
536
|
updatedAt: Date.now(),
|
|
349
537
|
};
|
|
350
538
|
|
|
351
|
-
// Write htmlDefinition to {
|
|
539
|
+
// Write htmlDefinition to {dirName}/index.html if provided in updates
|
|
352
540
|
const dir = getAppsDir();
|
|
353
|
-
const appDir = join(dir, id);
|
|
354
541
|
if (htmlUpdate !== undefined) {
|
|
355
542
|
updated.htmlDefinition = htmlUpdate;
|
|
356
543
|
mkdirSync(appDir, { recursive: true });
|
|
@@ -360,29 +547,29 @@ export function updateApp(
|
|
|
360
547
|
// Write preview to companion file
|
|
361
548
|
if (previewUpdate !== undefined) {
|
|
362
549
|
updated.preview = previewUpdate;
|
|
363
|
-
writeFileSync(join(dir, `${
|
|
550
|
+
writeFileSync(join(dir, `${dirName}.preview`), previewUpdate, "utf-8");
|
|
364
551
|
}
|
|
365
552
|
|
|
366
|
-
// Don't persist htmlDefinition, pages, or preview in the JSON file
|
|
553
|
+
// Don't persist htmlDefinition, pages, or preview in the JSON file -- they live as separate files
|
|
367
554
|
const {
|
|
368
555
|
pages: _existingPages,
|
|
369
556
|
htmlDefinition: _html,
|
|
370
557
|
preview: _preview,
|
|
371
558
|
...jsonDef
|
|
372
559
|
} = updated;
|
|
373
|
-
writeFileSync(join(dir, `${
|
|
560
|
+
writeFileSync(join(dir, `${dirName}.json`), JSON.stringify(jsonDef, null, 2));
|
|
374
561
|
|
|
375
562
|
// Clear existing pages directory before writing new pages to prevent stale files
|
|
376
563
|
if (pages && Object.keys(pages).length > 0) {
|
|
377
|
-
const pagesDir = join(
|
|
564
|
+
const pagesDir = join(appDir, "pages");
|
|
378
565
|
if (existsSync(pagesDir)) {
|
|
379
566
|
rmSync(pagesDir, { recursive: true, force: true });
|
|
380
567
|
}
|
|
381
|
-
savePages(
|
|
568
|
+
savePages(appDir, pages);
|
|
382
569
|
}
|
|
383
570
|
|
|
384
571
|
// Re-attach pages to the returned object
|
|
385
|
-
const loadedPages = loadPages(
|
|
572
|
+
const loadedPages = loadPages(appDir);
|
|
386
573
|
if (loadedPages) {
|
|
387
574
|
updated.pages = loadedPages;
|
|
388
575
|
}
|
|
@@ -392,17 +579,18 @@ export function updateApp(
|
|
|
392
579
|
|
|
393
580
|
export function deleteApp(id: string): void {
|
|
394
581
|
validateId(id);
|
|
582
|
+
const { dirName, appDir } = resolveAppDir(id);
|
|
395
583
|
const dir = getAppsDir();
|
|
396
|
-
const filePath = join(dir, `${
|
|
584
|
+
const filePath = join(dir, `${dirName}.json`);
|
|
397
585
|
if (existsSync(filePath)) {
|
|
398
586
|
unlinkSync(filePath);
|
|
399
587
|
}
|
|
400
|
-
const previewPath = join(dir, `${
|
|
588
|
+
const previewPath = join(dir, `${dirName}.preview`);
|
|
401
589
|
if (existsSync(previewPath)) {
|
|
402
590
|
unlinkSync(previewPath);
|
|
403
591
|
}
|
|
404
|
-
const appDir = join(dir, id);
|
|
405
592
|
rmSync(appDir, { recursive: true, force: true });
|
|
593
|
+
invalidateDirNameCache(id);
|
|
406
594
|
}
|
|
407
595
|
|
|
408
596
|
export function createAppRecord(
|
|
@@ -412,7 +600,7 @@ export function createAppRecord(
|
|
|
412
600
|
validateId(appId);
|
|
413
601
|
const app = getApp(appId);
|
|
414
602
|
if (!app) throw new Error(`App not found: ${appId}`);
|
|
415
|
-
const recordsDir = join(
|
|
603
|
+
const recordsDir = join(getAppDirPath(appId), "records");
|
|
416
604
|
mkdirSync(recordsDir, { recursive: true });
|
|
417
605
|
const now = Date.now();
|
|
418
606
|
const record: AppRecord = {
|
|
@@ -435,7 +623,7 @@ export function getAppRecord(
|
|
|
435
623
|
): AppRecord | null {
|
|
436
624
|
validateId(appId);
|
|
437
625
|
validateId(recordId);
|
|
438
|
-
const filePath = join(
|
|
626
|
+
const filePath = join(getAppDirPath(appId), "records", `${recordId}.json`);
|
|
439
627
|
if (!existsSync(filePath)) return null;
|
|
440
628
|
const raw = readFileSync(filePath, "utf-8");
|
|
441
629
|
return JSON.parse(raw) as AppRecord;
|
|
@@ -443,7 +631,7 @@ export function getAppRecord(
|
|
|
443
631
|
|
|
444
632
|
export function queryAppRecords(appId: string): AppRecord[] {
|
|
445
633
|
validateId(appId);
|
|
446
|
-
const recordsDir = join(
|
|
634
|
+
const recordsDir = join(getAppDirPath(appId), "records");
|
|
447
635
|
if (!existsSync(recordsDir)) return [];
|
|
448
636
|
const entries = readdirSync(recordsDir);
|
|
449
637
|
const records: AppRecord[] = [];
|
|
@@ -474,7 +662,7 @@ export function updateAppRecord(
|
|
|
474
662
|
updatedAt: Date.now(),
|
|
475
663
|
};
|
|
476
664
|
writeFileSync(
|
|
477
|
-
join(
|
|
665
|
+
join(getAppDirPath(appId), "records", `${recordId}.json`),
|
|
478
666
|
JSON.stringify(updated, null, 2),
|
|
479
667
|
);
|
|
480
668
|
return updated;
|
|
@@ -483,7 +671,7 @@ export function updateAppRecord(
|
|
|
483
671
|
export function deleteAppRecord(appId: string, recordId: string): void {
|
|
484
672
|
validateId(appId);
|
|
485
673
|
validateId(recordId);
|
|
486
|
-
const filePath = join(
|
|
674
|
+
const filePath = join(getAppDirPath(appId), "records", `${recordId}.json`);
|
|
487
675
|
if (existsSync(filePath)) {
|
|
488
676
|
unlinkSync(filePath);
|
|
489
677
|
}
|
|
@@ -494,12 +682,13 @@ export function deleteAppRecord(appId: string, recordId: string): void {
|
|
|
494
682
|
// ---------------------------------------------------------------------------
|
|
495
683
|
|
|
496
684
|
/**
|
|
497
|
-
* Recursively list all files under
|
|
498
|
-
* and `app.json`. Returns relative paths like `index.html`,
|
|
685
|
+
* Recursively list all files under the app's directory, excluding `records/`
|
|
686
|
+
* subdirectory and `app.json`. Returns relative paths like `index.html`,
|
|
687
|
+
* `styles.css`, `js/app.js`.
|
|
499
688
|
*/
|
|
500
689
|
export function listAppFiles(appId: string): string[] {
|
|
501
690
|
validateId(appId);
|
|
502
|
-
const appDir =
|
|
691
|
+
const appDir = getAppDirPath(appId);
|
|
503
692
|
if (!existsSync(appDir)) return [];
|
|
504
693
|
|
|
505
694
|
const results: string[] = [];
|