@notis_ai/cli 0.2.14 → 0.2.16
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/README.md +83 -21
- package/dist/agent-hooks/notis-agent-hook.mjs +20168 -0
- package/{skills → dist/base-skills}/notis-apps/SKILL.md +167 -72
- package/{skills → dist/base-skills}/notis-cli/SKILL.md +86 -28
- package/dist/base-skills/notis-query/SKILL.md +705 -0
- package/dist/skill-sync/index.js +1528 -0
- package/dist/skill-sync/index.js.map +7 -0
- package/package.json +5 -2
- package/skills/notis-apps/cli.md +39 -18
- package/skills/notis-cli/AGENT_INSTRUCTIONS.md +39 -0
- package/skills/notis-onboarding/BRIEF.md +16 -0
- package/src/agent-hook-entry.js +5 -0
- package/src/cli.js +23 -14
- package/src/command-specs/agents.js +392 -0
- package/src/command-specs/apps.js +1021 -424
- package/src/command-specs/auth.js +63 -10
- package/src/command-specs/index.js +6 -0
- package/src/command-specs/onboarding.js +69 -5
- package/src/command-specs/skills.js +56 -0
- package/src/runtime/agent-memory-state.js +126 -0
- package/src/runtime/agent-setup.js +383 -0
- package/src/runtime/app-dev-consumers.js +154 -0
- package/src/runtime/app-dev-host-lock.js +80 -0
- package/src/runtime/app-dev-process-identity.js +111 -0
- package/src/runtime/app-dev-roots.js +284 -0
- package/src/runtime/app-dev-server.js +447 -29
- package/src/runtime/app-dev-sessions.js +105 -145
- package/src/runtime/app-platform.js +1314 -166
- package/src/runtime/app-registry-scaffolds.js +367 -0
- package/src/runtime/base-skills.d.ts +20 -0
- package/src/runtime/base-skills.js +167 -0
- package/src/runtime/login-listener.js +15 -0
- package/src/runtime/oauth.js +1506 -141
- package/src/runtime/output.js +7 -0
- package/src/runtime/ports.js +16 -0
- package/src/runtime/profiles.js +19 -16
- package/src/runtime/skill-sync/cloud-client.ts +96 -0
- package/src/runtime/skill-sync/index.ts +644 -0
- package/src/runtime/skill-sync/local-scanner.ts +1046 -0
- package/src/runtime/skill-sync/symlink-manager.ts +383 -0
- package/src/runtime/skill-sync/sync-plan.ts +22 -0
- package/src/runtime/skill-sync/types.ts +103 -0
- package/src/runtime/skill-sync/write-cloud-skill.ts +50 -0
- package/src/runtime/store-screenshot.js +6 -1
- package/src/runtime/sync-skills.d.ts +37 -0
- package/src/runtime/sync-skills.js +231 -0
- package/template/app/layout.tsx +5 -1
- package/template/notis.config.ts +1 -0
- package/template/packages/sdk/package.json +2 -1
- package/template/packages/sdk/src/components/DocumentEditor.tsx +13 -3
- package/template/packages/sdk/src/components/MarkdownEditor.tsx +121 -0
- package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +43 -119
- package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +5 -1
- package/template/packages/sdk/src/components/MultiSelectDragOverlay.tsx +1 -1
- package/template/packages/sdk/src/components/NotisSelectionBoundary.tsx +55 -0
- package/template/packages/sdk/src/components/ShortcutHints.tsx +56 -0
- package/template/packages/sdk/src/config.ts +25 -0
- package/template/packages/sdk/src/documents.ts +7 -1
- package/template/packages/sdk/src/hooks/useActiveResource.ts +19 -0
- package/template/packages/sdk/src/hooks/useCollectionInteractions.ts +726 -0
- package/template/packages/sdk/src/hooks/useHandover.ts +3 -0
- package/template/packages/sdk/src/hooks/useMultiSelect.ts +44 -482
- package/template/packages/sdk/src/hooks/useNotis.ts +3 -0
- package/template/packages/sdk/src/hooks/useNotisNavigation.ts +7 -4
- package/template/packages/sdk/src/hooks/useTool.ts +5 -4
- package/template/packages/sdk/src/index.ts +47 -2
- package/template/packages/sdk/src/interactions/actions.ts +46 -0
- package/template/packages/sdk/src/interactions/shortcuts.tsx +634 -0
- package/template/packages/sdk/src/interactions.ts +41 -0
- package/template/packages/sdk/src/provider.tsx +2 -1
- package/template/packages/sdk/src/runtime.ts +92 -4
- package/dist/scaffolds/notis-database/CHANGELOG.md +0 -5
- package/dist/scaffolds/notis-database/app/globals.css +0 -44
- package/dist/scaffolds/notis-database/app/layout.tsx +0 -6
- package/dist/scaffolds/notis-database/app/page.tsx +0 -1088
- package/dist/scaffolds/notis-database/components/ui/badge.tsx +0 -28
- package/dist/scaffolds/notis-database/components/ui/button.tsx +0 -53
- package/dist/scaffolds/notis-database/components/ui/card.tsx +0 -56
- package/dist/scaffolds/notis-database/components/ui/table.tsx +0 -120
- package/dist/scaffolds/notis-database/components.json +0 -20
- package/dist/scaffolds/notis-database/index.html +0 -12
- package/dist/scaffolds/notis-database/lib/types.ts +0 -132
- package/dist/scaffolds/notis-database/lib/utils.ts +0 -6
- package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-fixtures.json +0 -1839
- package/dist/scaffolds/notis-database/notis.config.ts +0 -73
- package/dist/scaffolds/notis-database/package-lock.json +0 -3935
- package/dist/scaffolds/notis-database/package.json +0 -32
- package/dist/scaffolds/notis-database/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-database/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-database/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-database/packages/sdk/src/config.ts +0 -234
- package/dist/scaffolds/notis-database/packages/sdk/src/documents.ts +0 -250
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useHandover.ts +0 -75
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-database/packages/sdk/src/index.ts +0 -100
- package/dist/scaffolds/notis-database/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-database/packages/sdk/src/runtime.ts +0 -351
- package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-database/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-database/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-database/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-database/postcss.config.mjs +0 -8
- package/dist/scaffolds/notis-database/src/dev-main.tsx +0 -23
- package/dist/scaffolds/notis-database/src/mock-runtime.ts +0 -561
- package/dist/scaffolds/notis-database/tailwind.config.ts +0 -59
- package/dist/scaffolds/notis-database/tsconfig.json +0 -23
- package/dist/scaffolds/notis-database/vite.config.ts +0 -22
- package/dist/scaffolds/notis-journal/CHANGELOG.md +0 -29
- package/dist/scaffolds/notis-journal/app/globals.css +0 -37
- package/dist/scaffolds/notis-journal/app/insights/page.tsx +0 -513
- package/dist/scaffolds/notis-journal/app/journal-core.tsx +0 -362
- package/dist/scaffolds/notis-journal/app/journal-ui.tsx +0 -337
- package/dist/scaffolds/notis-journal/app/layout.tsx +0 -6
- package/dist/scaffolds/notis-journal/app/page.tsx +0 -486
- package/dist/scaffolds/notis-journal/components/ui/badge.tsx +0 -28
- package/dist/scaffolds/notis-journal/components/ui/button.tsx +0 -53
- package/dist/scaffolds/notis-journal/components/ui/card.tsx +0 -56
- package/dist/scaffolds/notis-journal/components.json +0 -20
- package/dist/scaffolds/notis-journal/index.html +0 -12
- package/dist/scaffolds/notis-journal/lib/utils.ts +0 -6
- package/dist/scaffolds/notis-journal/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-6.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-fixtures.json +0 -132
- package/dist/scaffolds/notis-journal/notis.config.ts +0 -93
- package/dist/scaffolds/notis-journal/package-lock.json +0 -4615
- package/dist/scaffolds/notis-journal/package.json +0 -34
- package/dist/scaffolds/notis-journal/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +0 -234
- package/dist/scaffolds/notis-journal/packages/sdk/src/documents.ts +0 -250
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useHandover.ts +0 -75
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-journal/packages/sdk/src/index.ts +0 -100
- package/dist/scaffolds/notis-journal/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-journal/packages/sdk/src/runtime.ts +0 -351
- package/dist/scaffolds/notis-journal/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-journal/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-journal/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-journal/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-journal/postcss.config.mjs +0 -8
- package/dist/scaffolds/notis-journal/skills/journal-onboarding/SKILL.md +0 -120
- package/dist/scaffolds/notis-journal/src/dev-main.tsx +0 -58
- package/dist/scaffolds/notis-journal/src/mock-runtime.ts +0 -199
- package/dist/scaffolds/notis-journal/tailwind.config.ts +0 -58
- package/dist/scaffolds/notis-journal/tsconfig.json +0 -23
- package/dist/scaffolds/notis-journal/vite.config.ts +0 -10
- package/dist/scaffolds/notis-notes/CHANGELOG.md +0 -5
- package/dist/scaffolds/notis-notes/app/globals.css +0 -3
- package/dist/scaffolds/notis-notes/app/layout.tsx +0 -6
- package/dist/scaffolds/notis-notes/app/page.tsx +0 -1943
- package/dist/scaffolds/notis-notes/app/phosphor-icons.ts +0 -596
- package/dist/scaffolds/notis-notes/components/ui/badge.tsx +0 -28
- package/dist/scaffolds/notis-notes/components/ui/button.tsx +0 -53
- package/dist/scaffolds/notis-notes/components/ui/card.tsx +0 -56
- package/dist/scaffolds/notis-notes/components.json +0 -20
- package/dist/scaffolds/notis-notes/lib/utils.ts +0 -6
- package/dist/scaffolds/notis-notes/lib/visible-properties.ts +0 -144
- package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-fixtures.json +0 -752
- package/dist/scaffolds/notis-notes/notis.config.ts +0 -80
- package/dist/scaffolds/notis-notes/package-lock.json +0 -4636
- package/dist/scaffolds/notis-notes/package.json +0 -35
- package/dist/scaffolds/notis-notes/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-notes/packages/sdk/src/config.ts +0 -234
- package/dist/scaffolds/notis-notes/packages/sdk/src/documents.ts +0 -250
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useHandover.ts +0 -75
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-notes/packages/sdk/src/index.ts +0 -100
- package/dist/scaffolds/notis-notes/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-notes/packages/sdk/src/runtime.ts +0 -351
- package/dist/scaffolds/notis-notes/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-notes/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-notes/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-notes/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-notes/postcss.config.mjs +0 -8
- package/dist/scaffolds/notis-notes/tailwind.config.ts +0 -58
- package/dist/scaffolds/notis-notes/tsconfig.json +0 -23
- package/dist/scaffolds/notis-notes/vite.config.ts +0 -10
- package/dist/scaffolds/notis-random/CHANGELOG.md +0 -15
- package/dist/scaffolds/notis-random/README.md +0 -33
- package/dist/scaffolds/notis-random/app/globals.css +0 -11
- package/dist/scaffolds/notis-random/app/history/page.tsx +0 -67
- package/dist/scaffolds/notis-random/app/layout.tsx +0 -7
- package/dist/scaffolds/notis-random/app/page.tsx +0 -289
- package/dist/scaffolds/notis-random/components/ui/button.tsx +0 -50
- package/dist/scaffolds/notis-random/components/ui/card.tsx +0 -16
- package/dist/scaffolds/notis-random/components/ui/input.tsx +0 -23
- package/dist/scaffolds/notis-random/components.json +0 -20
- package/dist/scaffolds/notis-random/index.html +0 -12
- package/dist/scaffolds/notis-random/lib/notis-tools.ts +0 -128
- package/dist/scaffolds/notis-random/lib/rng.ts +0 -202
- package/dist/scaffolds/notis-random/lib/roll-record.ts +0 -189
- package/dist/scaffolds/notis-random/lib/utils.ts +0 -25
- package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-fixtures.json +0 -753
- package/dist/scaffolds/notis-random/notis.config.ts +0 -86
- package/dist/scaffolds/notis-random/package-lock.json +0 -4513
- package/dist/scaffolds/notis-random/package.json +0 -36
- package/dist/scaffolds/notis-random/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-random/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-random/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-random/packages/sdk/src/config.ts +0 -234
- package/dist/scaffolds/notis-random/packages/sdk/src/documents.ts +0 -250
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSubscription.ts +0 -76
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useHandover.ts +0 -75
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-random/packages/sdk/src/index.ts +0 -100
- package/dist/scaffolds/notis-random/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-random/packages/sdk/src/runtime.ts +0 -351
- package/dist/scaffolds/notis-random/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-random/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-random/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-random/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-random/postcss.config.mjs +0 -6
- package/dist/scaffolds/notis-random/src/dev-main.tsx +0 -70
- package/dist/scaffolds/notis-random/src/mock-runtime.ts +0 -129
- package/dist/scaffolds/notis-random/tailwind.config.ts +0 -50
- package/dist/scaffolds/notis-random/tsconfig.json +0 -23
- package/dist/scaffolds/notis-random/vite.config.ts +0 -11
- package/dist/scaffolds.json +0 -49
|
@@ -12,9 +12,20 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { createServer } from 'node:http';
|
|
15
|
-
import { spawn } from 'node:child_process';
|
|
16
|
-
import {
|
|
15
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
16
|
+
import {
|
|
17
|
+
appendFileSync,
|
|
18
|
+
existsSync,
|
|
19
|
+
mkdirSync,
|
|
20
|
+
readFileSync,
|
|
21
|
+
renameSync,
|
|
22
|
+
rmSync,
|
|
23
|
+
statSync,
|
|
24
|
+
watch as fsWatch,
|
|
25
|
+
} from 'node:fs';
|
|
26
|
+
import { freemem, loadavg, totalmem } from 'node:os';
|
|
17
27
|
import { dirname, join, resolve } from 'node:path';
|
|
28
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
18
29
|
import { fileURLToPath } from 'node:url';
|
|
19
30
|
|
|
20
31
|
import {
|
|
@@ -28,7 +39,11 @@ import {
|
|
|
28
39
|
readLinkedState,
|
|
29
40
|
writeLinkedState,
|
|
30
41
|
} from './app-platform.js';
|
|
31
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
linkAppDevSessionTarget,
|
|
44
|
+
readAppDevSessions,
|
|
45
|
+
} from './app-dev-sessions.js';
|
|
46
|
+
import { captureDesktopWatcherOwnership } from './app-dev-process-identity.js';
|
|
32
47
|
|
|
33
48
|
const CONTENT_TYPES = {
|
|
34
49
|
'.js': 'application/javascript; charset=utf-8',
|
|
@@ -41,6 +56,115 @@ const CLI_ROOT = resolve(RUNTIME_DIR, '../..');
|
|
|
41
56
|
const REPO_ROOT = resolve(RUNTIME_DIR, '../../../..');
|
|
42
57
|
const HARNESS_TEMPLATE_PATH = join(CLI_ROOT, 'template', '.harness', 'index.html.tmpl');
|
|
43
58
|
const FALLBACK_REACT_VERSION = '19.0.0';
|
|
59
|
+
const BUILD_PROCESS_STOP_GRACE_MS = 1_000;
|
|
60
|
+
const DEV_DIAGNOSTIC_INTERVAL_MS = 30_000;
|
|
61
|
+
const DEV_DIAGNOSTIC_MAX_BYTES = 20 * 1024 * 1024;
|
|
62
|
+
|
|
63
|
+
function processGroupIsRunning(pid, signalProcess = process.kill) {
|
|
64
|
+
try {
|
|
65
|
+
signalProcess(-pid, 0);
|
|
66
|
+
return true;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
return error?.code !== 'ESRCH';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function readProcessGroupRssBytes(groupPids) {
|
|
73
|
+
if (process.platform === 'win32' || groupPids.size === 0) return new Map();
|
|
74
|
+
try {
|
|
75
|
+
const output = execFileSync('ps', ['-axo', 'pgid=,rss='], {
|
|
76
|
+
encoding: 'utf8',
|
|
77
|
+
maxBuffer: 1024 * 1024,
|
|
78
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
79
|
+
});
|
|
80
|
+
const rssByGroup = new Map();
|
|
81
|
+
for (const line of output.split('\n')) {
|
|
82
|
+
const [rawGroupPid, rawRssKiB] = line.trim().split(/\s+/, 2);
|
|
83
|
+
const groupPid = Number.parseInt(rawGroupPid, 10);
|
|
84
|
+
if (!groupPids.has(groupPid)) continue;
|
|
85
|
+
const rssKiB = Number.parseInt(rawRssKiB, 10);
|
|
86
|
+
if (!Number.isFinite(rssKiB)) continue;
|
|
87
|
+
rssByGroup.set(groupPid, (rssByGroup.get(groupPid) || 0) + (rssKiB * 1024));
|
|
88
|
+
}
|
|
89
|
+
return rssByGroup;
|
|
90
|
+
} catch {
|
|
91
|
+
return new Map();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Stop the npm wrapper and every Vite/esbuild descendant it launched.
|
|
97
|
+
*
|
|
98
|
+
* Killing only the npm PID leaves its watch process alive after a Desktop host
|
|
99
|
+
* restart. Each watcher therefore owns a separate POSIX process group. Windows
|
|
100
|
+
* uses taskkill's tree mode for the equivalent cleanup.
|
|
101
|
+
*/
|
|
102
|
+
export async function terminateBuildProcessTree(child, {
|
|
103
|
+
platform = process.platform,
|
|
104
|
+
signalProcess = process.kill,
|
|
105
|
+
spawnProcess = spawn,
|
|
106
|
+
graceMs = BUILD_PROCESS_STOP_GRACE_MS,
|
|
107
|
+
} = {}) {
|
|
108
|
+
const pid = child?.pid;
|
|
109
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return;
|
|
110
|
+
|
|
111
|
+
if (platform === 'win32') {
|
|
112
|
+
await new Promise((resolvePromise) => {
|
|
113
|
+
let settled = false;
|
|
114
|
+
const finish = () => {
|
|
115
|
+
if (settled) return;
|
|
116
|
+
settled = true;
|
|
117
|
+
resolvePromise();
|
|
118
|
+
};
|
|
119
|
+
try {
|
|
120
|
+
const killer = spawnProcess('taskkill.exe', ['/pid', String(pid), '/t', '/f'], {
|
|
121
|
+
stdio: 'ignore',
|
|
122
|
+
windowsHide: true,
|
|
123
|
+
});
|
|
124
|
+
killer.once('error', () => {
|
|
125
|
+
try {
|
|
126
|
+
child.kill('SIGTERM');
|
|
127
|
+
} catch {
|
|
128
|
+
// The wrapper already exited.
|
|
129
|
+
}
|
|
130
|
+
finish();
|
|
131
|
+
});
|
|
132
|
+
killer.once('exit', finish);
|
|
133
|
+
setTimeout(finish, graceMs).unref?.();
|
|
134
|
+
} catch {
|
|
135
|
+
try {
|
|
136
|
+
child.kill('SIGTERM');
|
|
137
|
+
} catch {
|
|
138
|
+
// The wrapper already exited.
|
|
139
|
+
}
|
|
140
|
+
finish();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
signalProcess(-pid, 'SIGTERM');
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if (error?.code === 'ESRCH') return;
|
|
150
|
+
try {
|
|
151
|
+
child.kill('SIGTERM');
|
|
152
|
+
} catch {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const deadline = Date.now() + graceMs;
|
|
158
|
+
while (processGroupIsRunning(pid, signalProcess) && Date.now() < deadline) {
|
|
159
|
+
await delay(25);
|
|
160
|
+
}
|
|
161
|
+
if (!processGroupIsRunning(pid, signalProcess)) return;
|
|
162
|
+
try {
|
|
163
|
+
signalProcess(-pid, 'SIGKILL');
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (error?.code !== 'ESRCH') throw error;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
44
168
|
|
|
45
169
|
function extFor(pathname) {
|
|
46
170
|
const idx = pathname.lastIndexOf('.');
|
|
@@ -243,10 +367,11 @@ function buildHarnessDescriptor({ state, manifest, appConfig, route, scenario =
|
|
|
243
367
|
icon: route.icon || null,
|
|
244
368
|
parentSlug: route.parentSlug || null,
|
|
245
369
|
default: Boolean(route.default),
|
|
370
|
+
resourceDeepLinks: route.resourceDeepLinks === true,
|
|
246
371
|
collection: route.collection || null,
|
|
247
372
|
},
|
|
248
373
|
databases,
|
|
249
|
-
context: { collectionItem: null, screenshotScenario: scenario },
|
|
374
|
+
context: { collectionItem: null, resourceId: null, screenshotScenario: scenario },
|
|
250
375
|
tools,
|
|
251
376
|
};
|
|
252
377
|
}
|
|
@@ -297,7 +422,7 @@ function renderHarnessHtml({ state, manifest, appConfig, route, harnessOptions,
|
|
|
297
422
|
/**
|
|
298
423
|
* Start the dev server for one or more apps.
|
|
299
424
|
*
|
|
300
|
-
* @param {{apps: Array<{slug: string, projectDir: string, appId?: string, targetAppId?: string, userId?: string}>, port: number, watch?: boolean, sessionsFilePath?: string, harness?: { mode?: string, apiBase?: string, jwt?: string }, log?: (m: string) => void, logError?: (m: string) => void}} options
|
|
425
|
+
* @param {{apps: Array<{slug: string, projectDir: string, appId?: string, targetAppId?: string, userId?: string, profileKey?: string, sessionId?: string, mountNonce?: string}>, port: number, watch?: boolean, sessionsFilePath?: string, harness?: { mode?: string, apiBase?: string, jwt?: string }, diagnosticsFile?: string | null, desktopOwnerId?: string | null, desktopOwnerScope?: string | null, terminateBuildProcess?: typeof terminateBuildProcessTree, log?: (m: string) => void, logError?: (m: string) => void}} options
|
|
301
426
|
*/
|
|
302
427
|
export async function startAppDevServer({
|
|
303
428
|
apps,
|
|
@@ -305,6 +430,10 @@ export async function startAppDevServer({
|
|
|
305
430
|
watch = true,
|
|
306
431
|
sessionsFilePath,
|
|
307
432
|
harness = {},
|
|
433
|
+
diagnosticsFile = process.env.NOTIS_DEV_DIAGNOSTICS_FILE || null,
|
|
434
|
+
desktopOwnerId = process.env.NOTIS_APPS_DEV_DESKTOP_OWNER_ID || null,
|
|
435
|
+
desktopOwnerScope = process.env.NOTIS_APPS_DEV_DESKTOP_OWNER_SCOPE || null,
|
|
436
|
+
terminateBuildProcess = terminateBuildProcessTree,
|
|
308
437
|
log = (msg) => process.stdout.write(`${msg}\n`),
|
|
309
438
|
logError = (msg) => process.stderr.write(`${msg}\n`),
|
|
310
439
|
}) {
|
|
@@ -313,22 +442,97 @@ export async function startAppDevServer({
|
|
|
313
442
|
}
|
|
314
443
|
|
|
315
444
|
const appState = new Map();
|
|
316
|
-
|
|
317
|
-
|
|
445
|
+
let diagnosticsTimer = null;
|
|
446
|
+
let diagnosticWriteFailed = false;
|
|
447
|
+
let serverClosing = false;
|
|
448
|
+
// Sidebar reconciliation needs one stream for the shared host, not one
|
|
449
|
+
// long-lived HTTP/1.1 connection per discovered app. Keeping a stream per
|
|
450
|
+
// app exhausts Chromium's per-origin connection pool and can indefinitely
|
|
451
|
+
// queue the active view's bundle fetch during hot reload.
|
|
452
|
+
const hostSseClients = new Set();
|
|
453
|
+
const createAppState = (app) => {
|
|
454
|
+
let resolveBundleReady;
|
|
455
|
+
const bundleReadyPromise = new Promise((resolvePromise) => {
|
|
456
|
+
resolveBundleReady = resolvePromise;
|
|
457
|
+
});
|
|
458
|
+
return {
|
|
318
459
|
slug: app.slug,
|
|
319
460
|
projectDir: app.projectDir,
|
|
320
461
|
appId: app.appId || null,
|
|
321
462
|
targetAppId: app.targetAppId || null,
|
|
322
463
|
userId: app.userId || null,
|
|
464
|
+
profileKey: app.profileKey || null,
|
|
465
|
+
sessionId: app.sessionId || null,
|
|
466
|
+
mountNonce: app.mountNonce || null,
|
|
323
467
|
canonicalBundleDir: getBundleDir(app.projectDir),
|
|
324
468
|
bundleDir: null,
|
|
325
469
|
jsPath: null,
|
|
326
470
|
sseClients: new Set(),
|
|
327
471
|
watcher: null,
|
|
472
|
+
sourceWatchers: [],
|
|
473
|
+
prepareTimer: null,
|
|
474
|
+
reloadTimer: null,
|
|
328
475
|
buildProcess: null,
|
|
476
|
+
watcherOwnership: null,
|
|
329
477
|
lastMtimeMs: 0,
|
|
330
478
|
watchPollTimer: null,
|
|
331
|
-
|
|
479
|
+
bundleReady: false,
|
|
480
|
+
bundleReadyPromise,
|
|
481
|
+
resolveBundleReady,
|
|
482
|
+
buildProcessStopPromise: null,
|
|
483
|
+
};
|
|
484
|
+
};
|
|
485
|
+
for (const app of apps) {
|
|
486
|
+
appState.set(app.slug, createAppState(app));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function writeDevDiagnostic(event) {
|
|
490
|
+
if (!diagnosticsFile) return;
|
|
491
|
+
const memory = process.memoryUsage();
|
|
492
|
+
const watcherPids = new Set(
|
|
493
|
+
[...appState.values()]
|
|
494
|
+
.map((state) => state.buildProcess?.pid)
|
|
495
|
+
.filter((pid) => Number.isSafeInteger(pid) && pid > 0),
|
|
496
|
+
);
|
|
497
|
+
const watcherGroupRss = readProcessGroupRssBytes(watcherPids);
|
|
498
|
+
const record = {
|
|
499
|
+
at: new Date().toISOString(),
|
|
500
|
+
event,
|
|
501
|
+
host_pid: process.pid,
|
|
502
|
+
parent_pid: process.ppid,
|
|
503
|
+
rss_bytes: memory.rss,
|
|
504
|
+
heap_used_bytes: memory.heapUsed,
|
|
505
|
+
heap_total_bytes: memory.heapTotal,
|
|
506
|
+
external_bytes: memory.external,
|
|
507
|
+
system_free_bytes: freemem(),
|
|
508
|
+
system_total_bytes: totalmem(),
|
|
509
|
+
load_average_1m: loadavg()[0],
|
|
510
|
+
watcher_groups_rss_bytes: [...watcherGroupRss.values()].reduce((total, rss) => total + rss, 0),
|
|
511
|
+
apps: [...appState.values()].map((state) => ({
|
|
512
|
+
slug: state.slug,
|
|
513
|
+
project_dir: state.projectDir,
|
|
514
|
+
watcher_pid: state.buildProcess?.pid || null,
|
|
515
|
+
watcher_exit_code: state.buildProcess?.exitCode ?? null,
|
|
516
|
+
watcher_signal: state.buildProcess?.signalCode ?? null,
|
|
517
|
+
watcher_group_rss_bytes: watcherGroupRss.get(state.buildProcess?.pid) ?? null,
|
|
518
|
+
bundle_ready: state.bundleReady,
|
|
519
|
+
})),
|
|
520
|
+
};
|
|
521
|
+
try {
|
|
522
|
+
mkdirSync(dirname(diagnosticsFile), { recursive: true, mode: 0o700 });
|
|
523
|
+
if (existsSync(diagnosticsFile) && statSync(diagnosticsFile).size >= DEV_DIAGNOSTIC_MAX_BYTES) {
|
|
524
|
+
const previous = `${diagnosticsFile}.previous`;
|
|
525
|
+
rmSync(previous, { force: true });
|
|
526
|
+
renameSync(diagnosticsFile, previous);
|
|
527
|
+
}
|
|
528
|
+
appendFileSync(diagnosticsFile, `${JSON.stringify(record)}\n`, { mode: 0o600 });
|
|
529
|
+
diagnosticWriteFailed = false;
|
|
530
|
+
} catch (error) {
|
|
531
|
+
if (!diagnosticWriteFailed) {
|
|
532
|
+
diagnosticWriteFailed = true;
|
|
533
|
+
logError(`[notis apps dev] persistent diagnostics failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
332
536
|
}
|
|
333
537
|
|
|
334
538
|
function broadcastReload(slug) {
|
|
@@ -342,6 +546,14 @@ export async function startAppDevServer({
|
|
|
342
546
|
state.sseClients.delete(res);
|
|
343
547
|
}
|
|
344
548
|
}
|
|
549
|
+
const hostPayload = `event: reload\ndata: ${JSON.stringify({ slug, at: Date.now() })}\n\n`;
|
|
550
|
+
for (const res of hostSseClients) {
|
|
551
|
+
try {
|
|
552
|
+
res.write(hostPayload);
|
|
553
|
+
} catch {
|
|
554
|
+
hostSseClients.delete(res);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
345
557
|
}
|
|
346
558
|
|
|
347
559
|
function matchAppRoute(pathname) {
|
|
@@ -374,6 +586,10 @@ export async function startAppDevServer({
|
|
|
374
586
|
state.jsPath = join(bundleDir, 'app.js');
|
|
375
587
|
try {
|
|
376
588
|
state.lastMtimeMs = statSync(state.jsPath).mtimeMs;
|
|
589
|
+
if (!state.bundleReady) {
|
|
590
|
+
state.bundleReady = true;
|
|
591
|
+
state.resolveBundleReady();
|
|
592
|
+
}
|
|
377
593
|
} catch {
|
|
378
594
|
state.lastMtimeMs = 0;
|
|
379
595
|
}
|
|
@@ -451,6 +667,9 @@ export async function startAppDevServer({
|
|
|
451
667
|
try {
|
|
452
668
|
const body = await readRequestJson(req);
|
|
453
669
|
const appId = typeof body.app_id === 'string' ? body.app_id.trim() : '';
|
|
670
|
+
const version = Number.isInteger(body.version) && body.version > 0
|
|
671
|
+
? body.version
|
|
672
|
+
: null;
|
|
454
673
|
if (!appId) {
|
|
455
674
|
res.writeHead(400, {
|
|
456
675
|
...headers,
|
|
@@ -459,20 +678,69 @@ export async function startAppDevServer({
|
|
|
459
678
|
res.end(JSON.stringify({ error: 'app_id is required' }));
|
|
460
679
|
return;
|
|
461
680
|
}
|
|
681
|
+
if (version === null) {
|
|
682
|
+
res.writeHead(400, {
|
|
683
|
+
...headers,
|
|
684
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
685
|
+
});
|
|
686
|
+
res.end(JSON.stringify({ error: 'version must be a positive integer' }));
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const proofSession = readAppDevSessions(sessionsFilePath).sessions.find((session) => (
|
|
690
|
+
typeof body.session_id === 'string'
|
|
691
|
+
&& typeof body.mount_nonce === 'string'
|
|
692
|
+
&& session.sessionId === body.session_id
|
|
693
|
+
&& session.mountNonce === body.mount_nonce
|
|
694
|
+
&& session.devSlug === state.slug
|
|
695
|
+
&& session.projectDir === state.projectDir
|
|
696
|
+
));
|
|
697
|
+
if (!proofSession) {
|
|
698
|
+
res.writeHead(403, {
|
|
699
|
+
...headers,
|
|
700
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
701
|
+
});
|
|
702
|
+
res.end(JSON.stringify({ error: 'invalid app development session proof' }));
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
const expectedAppId = proofSession.targetAppId || proofSession.appId;
|
|
706
|
+
if (!expectedAppId || appId !== expectedAppId) {
|
|
707
|
+
res.writeHead(409, {
|
|
708
|
+
...headers,
|
|
709
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
710
|
+
});
|
|
711
|
+
res.end(JSON.stringify({ error: 'app_id does not match this development session' }));
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
462
714
|
|
|
463
|
-
const currentState = readLinkedState(
|
|
715
|
+
const currentState = readLinkedState(
|
|
716
|
+
state.projectDir,
|
|
717
|
+
proofSession.profileKey || state.profileKey,
|
|
718
|
+
) || {};
|
|
464
719
|
const now = new Date().toISOString();
|
|
465
|
-
|
|
720
|
+
const promotedInPlace = proofSession.appId === appId;
|
|
721
|
+
const nextState = {
|
|
466
722
|
...currentState,
|
|
467
723
|
app_id: appId,
|
|
468
|
-
dev_app_id: state.appId || currentState.dev_app_id,
|
|
469
724
|
linked_at: currentState.linked_at || now,
|
|
470
|
-
|
|
725
|
+
version,
|
|
726
|
+
deployed_at: now,
|
|
471
727
|
updated_at: now,
|
|
472
|
-
}
|
|
473
|
-
|
|
728
|
+
};
|
|
729
|
+
if (promotedInPlace) {
|
|
730
|
+
delete nextState.dev_app_id;
|
|
731
|
+
delete nextState.dev_linked_at;
|
|
732
|
+
} else {
|
|
733
|
+
nextState.dev_app_id = proofSession.appId || currentState.dev_app_id;
|
|
734
|
+
nextState.dev_linked_at = currentState.dev_linked_at || now;
|
|
735
|
+
}
|
|
736
|
+
writeLinkedState(
|
|
737
|
+
state.projectDir,
|
|
738
|
+
nextState,
|
|
739
|
+
proofSession.profileKey || state.profileKey,
|
|
740
|
+
);
|
|
474
741
|
linkAppDevSessionTarget({
|
|
475
|
-
|
|
742
|
+
sessionId: proofSession.sessionId,
|
|
743
|
+
appId: proofSession.appId,
|
|
476
744
|
devSlug: state.slug,
|
|
477
745
|
targetAppId: appId,
|
|
478
746
|
lastHeartbeatAt: now,
|
|
@@ -480,8 +748,9 @@ export async function startAppDevServer({
|
|
|
480
748
|
const response = {
|
|
481
749
|
ok: true,
|
|
482
750
|
app_id: appId,
|
|
483
|
-
dev_app_id:
|
|
751
|
+
dev_app_id: promotedInPlace ? null : proofSession.appId || null,
|
|
484
752
|
target_app_id: appId,
|
|
753
|
+
version,
|
|
485
754
|
};
|
|
486
755
|
res.writeHead(200, {
|
|
487
756
|
...headers,
|
|
@@ -525,6 +794,18 @@ export async function startAppDevServer({
|
|
|
525
794
|
return;
|
|
526
795
|
}
|
|
527
796
|
|
|
797
|
+
if (url.pathname === '/events') {
|
|
798
|
+
res.writeHead(200, {
|
|
799
|
+
...headers,
|
|
800
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
801
|
+
Connection: 'keep-alive',
|
|
802
|
+
});
|
|
803
|
+
res.write(': connected\n\n');
|
|
804
|
+
hostSseClients.add(res);
|
|
805
|
+
req.on('close', () => hostSseClients.delete(res));
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
|
|
528
809
|
const routed = matchAppRoute(url.pathname);
|
|
529
810
|
if (!routed) {
|
|
530
811
|
res.writeHead(404, headers);
|
|
@@ -644,15 +925,23 @@ export async function startAppDevServer({
|
|
|
644
925
|
try {
|
|
645
926
|
state.watcher = fsWatch(state.bundleDir, { persistent: false }, (_event, filename) => {
|
|
646
927
|
if (!filename || filename !== 'app.js') return;
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
928
|
+
// Vite may emit the filesystem notification while its output file is
|
|
929
|
+
// still being replaced. Debounce the notification so consumers never
|
|
930
|
+
// fetch the previous or partially-written bundle under the new build
|
|
931
|
+
// revision.
|
|
932
|
+
if (state.reloadTimer) clearTimeout(state.reloadTimer);
|
|
933
|
+
state.reloadTimer = setTimeout(() => {
|
|
934
|
+
state.reloadTimer = null;
|
|
935
|
+
try {
|
|
936
|
+
const stat = statSync(state.jsPath);
|
|
937
|
+
if (stat.mtimeMs === state.lastMtimeMs) return;
|
|
938
|
+
state.lastMtimeMs = stat.mtimeMs;
|
|
939
|
+
log(`[notis apps dev] ${state.slug}: bundle updated — reloading portal`);
|
|
940
|
+
broadcastReload(state.slug);
|
|
941
|
+
} catch {
|
|
942
|
+
// Bundle file may be missing mid-write; the next change retries.
|
|
943
|
+
}
|
|
944
|
+
}, 100);
|
|
656
945
|
});
|
|
657
946
|
} catch (error) {
|
|
658
947
|
logError(`[notis apps dev] ${state.slug}: watch failed: ${error.message}`);
|
|
@@ -669,21 +958,88 @@ export async function startAppDevServer({
|
|
|
669
958
|
state.watchPollTimer = setTimeout(() => pollForBundleAndWatch(state), 300);
|
|
670
959
|
}
|
|
671
960
|
|
|
961
|
+
function watchManifestInputs(state) {
|
|
962
|
+
const schedulePrepare = () => {
|
|
963
|
+
if (state.prepareTimer) clearTimeout(state.prepareTimer);
|
|
964
|
+
state.prepareTimer = setTimeout(() => {
|
|
965
|
+
state.prepareTimer = null;
|
|
966
|
+
void prepareArtifactBuild(state.projectDir).then(() => {
|
|
967
|
+
// Name, icon, and route metadata can change without changing the
|
|
968
|
+
// compiled JS bytes. Notify consumers immediately; a generated-entry
|
|
969
|
+
// change will produce the normal second reload after Vite rebuilds.
|
|
970
|
+
broadcastReload(state.slug);
|
|
971
|
+
}).catch((error) => {
|
|
972
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
973
|
+
logError(`[notis apps dev] ${state.slug}: ${message}`);
|
|
974
|
+
});
|
|
975
|
+
}, 75);
|
|
976
|
+
};
|
|
977
|
+
try {
|
|
978
|
+
state.sourceWatchers.push(fsWatch(
|
|
979
|
+
state.projectDir,
|
|
980
|
+
{ persistent: false },
|
|
981
|
+
(_event, filename) => {
|
|
982
|
+
if (filename && /^notis\.config\.(?:ts|js|mjs)$/.test(String(filename))) {
|
|
983
|
+
schedulePrepare();
|
|
984
|
+
}
|
|
985
|
+
},
|
|
986
|
+
));
|
|
987
|
+
} catch (error) {
|
|
988
|
+
logError(`[notis apps dev] ${state.slug}: config watch failed: ${error.message}`);
|
|
989
|
+
}
|
|
990
|
+
const appDir = join(state.projectDir, 'app');
|
|
991
|
+
if (existsSync(appDir)) {
|
|
992
|
+
try {
|
|
993
|
+
state.sourceWatchers.push(fsWatch(
|
|
994
|
+
appDir,
|
|
995
|
+
{ persistent: false, recursive: process.platform === 'darwin' || process.platform === 'win32' },
|
|
996
|
+
schedulePrepare,
|
|
997
|
+
));
|
|
998
|
+
} catch (error) {
|
|
999
|
+
logError(`[notis apps dev] ${state.slug}: route watch failed: ${error.message}`);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
672
1004
|
for (const state of appState.values()) {
|
|
673
1005
|
if (watch) {
|
|
674
1006
|
await prepareArtifactBuild(state.projectDir);
|
|
1007
|
+
watchManifestInputs(state);
|
|
675
1008
|
pollForBundleAndWatch(state);
|
|
676
1009
|
|
|
677
|
-
|
|
1010
|
+
const buildProcess = spawn('npm', ['run', 'build', '--', '--watch'], {
|
|
678
1011
|
cwd: state.projectDir,
|
|
1012
|
+
detached: process.platform !== 'win32',
|
|
679
1013
|
stdio: 'inherit',
|
|
680
1014
|
env: { ...process.env, NOTIS_DEV: '1' },
|
|
681
1015
|
});
|
|
1016
|
+
state.buildProcess = buildProcess;
|
|
1017
|
+
for (let attempt = 0; attempt < 5 && !state.watcherOwnership; attempt += 1) {
|
|
1018
|
+
state.watcherOwnership = captureDesktopWatcherOwnership({
|
|
1019
|
+
pid: buildProcess.pid,
|
|
1020
|
+
projectDir: state.projectDir,
|
|
1021
|
+
desktopOwnerId,
|
|
1022
|
+
desktopOwnerScope,
|
|
1023
|
+
});
|
|
1024
|
+
if (!state.watcherOwnership && attempt < 4) await delay(10);
|
|
1025
|
+
}
|
|
682
1026
|
|
|
683
|
-
|
|
1027
|
+
buildProcess.on('exit', (code) => {
|
|
684
1028
|
if (code !== 0 && code !== null) {
|
|
685
1029
|
logError(`[notis apps dev] ${state.slug}: vite build --watch exited with code ${code}`);
|
|
686
1030
|
}
|
|
1031
|
+
if (!serverClosing) {
|
|
1032
|
+
if (state.buildProcess === buildProcess) state.buildProcess = null;
|
|
1033
|
+
const stopPromise = terminateBuildProcess(buildProcess).catch((error) => {
|
|
1034
|
+
logError(`[notis apps dev] ${state.slug}: watcher cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1035
|
+
});
|
|
1036
|
+
state.buildProcessStopPromise = stopPromise;
|
|
1037
|
+
void stopPromise.finally(() => {
|
|
1038
|
+
if (state.buildProcessStopPromise === stopPromise) {
|
|
1039
|
+
state.buildProcessStopPromise = null;
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
687
1043
|
});
|
|
688
1044
|
} else {
|
|
689
1045
|
updateBundleDir(state, resolveBundleDir(state));
|
|
@@ -692,10 +1048,57 @@ export async function startAppDevServer({
|
|
|
692
1048
|
log(`[notis apps dev] ${state.slug}: serving bundle at http://127.0.0.1:${port}/a/${state.slug}/bundle/app.js`);
|
|
693
1049
|
}
|
|
694
1050
|
|
|
1051
|
+
writeDevDiagnostic('started');
|
|
1052
|
+
if (diagnosticsFile) {
|
|
1053
|
+
diagnosticsTimer = setInterval(() => writeDevDiagnostic('sample'), DEV_DIAGNOSTIC_INTERVAL_MS);
|
|
1054
|
+
diagnosticsTimer.unref?.();
|
|
1055
|
+
}
|
|
1056
|
+
|
|
695
1057
|
return {
|
|
696
1058
|
port,
|
|
1059
|
+
updateApp(slug, updates = {}) {
|
|
1060
|
+
const state = appState.get(slug);
|
|
1061
|
+
if (!state) throw new Error(`unknown app: ${slug}`);
|
|
1062
|
+
for (const key of ['appId', 'targetAppId', 'userId', 'profileKey', 'sessionId', 'mountNonce']) {
|
|
1063
|
+
if (Object.prototype.hasOwnProperty.call(updates, key)) {
|
|
1064
|
+
state[key] = updates[key] || null;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
},
|
|
1068
|
+
isBundleReady(slug) {
|
|
1069
|
+
const state = appState.get(slug);
|
|
1070
|
+
if (!state) throw new Error(`unknown app: ${slug}`);
|
|
1071
|
+
return state.bundleReady;
|
|
1072
|
+
},
|
|
1073
|
+
waitForBundle(slug) {
|
|
1074
|
+
const state = appState.get(slug);
|
|
1075
|
+
if (!state) return Promise.reject(new Error(`unknown app: ${slug}`));
|
|
1076
|
+
return state.bundleReadyPromise;
|
|
1077
|
+
},
|
|
1078
|
+
getWatcherOwnership(slug) {
|
|
1079
|
+
const state = appState.get(slug);
|
|
1080
|
+
if (!state) throw new Error(`unknown app: ${slug}`);
|
|
1081
|
+
return state.watcherOwnership ? { ...state.watcherOwnership } : null;
|
|
1082
|
+
},
|
|
697
1083
|
async close() {
|
|
1084
|
+
serverClosing = true;
|
|
1085
|
+
if (diagnosticsTimer) {
|
|
1086
|
+
clearInterval(diagnosticsTimer);
|
|
1087
|
+
diagnosticsTimer = null;
|
|
1088
|
+
}
|
|
1089
|
+
writeDevDiagnostic('stopping');
|
|
1090
|
+
const buildProcessStops = [];
|
|
698
1091
|
for (const state of appState.values()) {
|
|
1092
|
+
if (state.prepareTimer) clearTimeout(state.prepareTimer);
|
|
1093
|
+
if (state.reloadTimer) clearTimeout(state.reloadTimer);
|
|
1094
|
+
for (const watcher of state.sourceWatchers) {
|
|
1095
|
+
try {
|
|
1096
|
+
watcher.close();
|
|
1097
|
+
} catch {
|
|
1098
|
+
// ignore
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
state.sourceWatchers = [];
|
|
699
1102
|
if (state.watchPollTimer) clearTimeout(state.watchPollTimer);
|
|
700
1103
|
if (state.watcher) {
|
|
701
1104
|
try {
|
|
@@ -712,11 +1115,26 @@ export async function startAppDevServer({
|
|
|
712
1115
|
}
|
|
713
1116
|
}
|
|
714
1117
|
state.sseClients.clear();
|
|
715
|
-
if (state.
|
|
716
|
-
state.
|
|
1118
|
+
if (state.buildProcessStopPromise) {
|
|
1119
|
+
buildProcessStops.push(state.buildProcessStopPromise);
|
|
1120
|
+
}
|
|
1121
|
+
if (state.buildProcess) {
|
|
1122
|
+
const buildProcess = state.buildProcess;
|
|
1123
|
+
state.buildProcess = null;
|
|
1124
|
+
buildProcessStops.push(terminateBuildProcess(buildProcess));
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
await Promise.allSettled(buildProcessStops);
|
|
1128
|
+
for (const res of hostSseClients) {
|
|
1129
|
+
try {
|
|
1130
|
+
res.end();
|
|
1131
|
+
} catch {
|
|
1132
|
+
// ignore
|
|
717
1133
|
}
|
|
718
1134
|
}
|
|
1135
|
+
hostSseClients.clear();
|
|
719
1136
|
await new Promise((resolvePromise) => server.close(() => resolvePromise()));
|
|
1137
|
+
writeDevDiagnostic('stopped');
|
|
720
1138
|
},
|
|
721
1139
|
};
|
|
722
1140
|
}
|