@notis_ai/cli 0.2.14 → 0.2.15
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 +82 -20
- package/dist/agent-hooks/notis-agent-hook.mjs +19750 -0
- package/{skills → dist/base-skills}/notis-apps/SKILL.md +84 -53
- package/{skills → dist/base-skills}/notis-cli/SKILL.md +9 -9
- 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 +38 -17
- 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 +871 -387
- 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-roots.js +284 -0
- package/src/runtime/app-dev-server.js +212 -22
- package/src/runtime/app-dev-sessions.js +105 -145
- package/src/runtime/app-platform.js +1291 -165
- 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 +0 -5
- 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 +215 -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 +37 -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 +19 -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 +709 -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/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 +628 -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 +89 -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
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
lstatSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
realpathSync,
|
|
9
|
+
renameSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
statSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from 'node:fs';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
import { dirname, join, parse as parsePath, resolve } from 'node:path';
|
|
16
|
+
|
|
17
|
+
import { usageError } from './errors.js';
|
|
18
|
+
import { CONFIG_DIR } from './profiles.js';
|
|
19
|
+
|
|
20
|
+
export const APP_DEV_ROOTS_VERSION = 1;
|
|
21
|
+
export const DEFAULT_APP_DEV_ROOT = join(homedir(), '.notis', 'apps');
|
|
22
|
+
export const DEFAULT_APP_DEV_ROOTS_FILE = join(CONFIG_DIR, 'app-dev-roots.json');
|
|
23
|
+
const CONFIG_FILENAMES = ['notis.config.ts', 'notis.config.js', 'notis.config.mjs'];
|
|
24
|
+
const LOCK_TIMEOUT_MS = 5_000;
|
|
25
|
+
const lockWait = new Int32Array(new SharedArrayBuffer(4));
|
|
26
|
+
|
|
27
|
+
function rootsFile(filePath) {
|
|
28
|
+
if (filePath) return resolve(filePath);
|
|
29
|
+
const envPath = process.env.NOTIS_APP_DEV_ROOTS_FILE;
|
|
30
|
+
return typeof envPath === 'string' && envPath.trim()
|
|
31
|
+
? resolve(envPath.trim())
|
|
32
|
+
: DEFAULT_APP_DEV_ROOTS_FILE;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function legacyProjectsFile(filePath) {
|
|
36
|
+
const sessionsFile = filePath
|
|
37
|
+
|| join(CONFIG_DIR, 'app-dev-sessions.json');
|
|
38
|
+
const parsed = parsePath(resolve(sessionsFile));
|
|
39
|
+
return join(parsed.dir, `${parsed.name}-projects${parsed.ext || '.json'}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readLockOwner(lockPath) {
|
|
43
|
+
try {
|
|
44
|
+
return readFileSync(join(lockPath, 'owner'), 'utf8').trim();
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function withLock(filePath, callback) {
|
|
51
|
+
mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
|
|
52
|
+
const lockPath = `${filePath}.lock`;
|
|
53
|
+
const owner = `${process.pid}.${randomUUID()}`;
|
|
54
|
+
const startedAt = Date.now();
|
|
55
|
+
while (true) {
|
|
56
|
+
try {
|
|
57
|
+
mkdirSync(lockPath, { mode: 0o700 });
|
|
58
|
+
writeFileSync(join(lockPath, 'owner'), owner, { mode: 0o600 });
|
|
59
|
+
break;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
62
|
+
try {
|
|
63
|
+
const stat = lstatSync(lockPath);
|
|
64
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
65
|
+
throw new Error(`Refusing unsafe app development roots lock: ${lockPath}`);
|
|
66
|
+
}
|
|
67
|
+
const existingOwner = readLockOwner(lockPath);
|
|
68
|
+
const pid = Number.parseInt(String(existingOwner || '').split('.')[0] || '', 10);
|
|
69
|
+
if (Number.isInteger(pid) && pid > 0) {
|
|
70
|
+
try {
|
|
71
|
+
process.kill(pid, 0);
|
|
72
|
+
} catch (ownerError) {
|
|
73
|
+
if (ownerError?.code === 'ESRCH') {
|
|
74
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} catch (statError) {
|
|
80
|
+
if (statError?.code === 'ENOENT') continue;
|
|
81
|
+
throw statError;
|
|
82
|
+
}
|
|
83
|
+
if (Date.now() - startedAt >= LOCK_TIMEOUT_MS) {
|
|
84
|
+
throw new Error(`Timed out waiting for app development roots lock: ${filePath}`);
|
|
85
|
+
}
|
|
86
|
+
Atomics.wait(lockWait, 0, 0, 10);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
return callback();
|
|
91
|
+
} finally {
|
|
92
|
+
if (readLockOwner(lockPath) === owner) {
|
|
93
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function canonicalExistingDirectory(inputPath) {
|
|
99
|
+
const absolute = resolve(String(inputPath || ''));
|
|
100
|
+
let stat;
|
|
101
|
+
try {
|
|
102
|
+
stat = statSync(absolute);
|
|
103
|
+
} catch {
|
|
104
|
+
throw usageError(`App development root does not exist: ${absolute}`);
|
|
105
|
+
}
|
|
106
|
+
if (!stat.isDirectory()) {
|
|
107
|
+
throw usageError(`App development root is not a directory: ${absolute}`);
|
|
108
|
+
}
|
|
109
|
+
return realpathSync(absolute);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function normalizeRegistry(raw) {
|
|
113
|
+
const roots = Array.isArray(raw?.roots) ? raw.roots : [];
|
|
114
|
+
const seen = new Set();
|
|
115
|
+
return roots
|
|
116
|
+
.map((entry) => typeof entry === 'string' ? { path: entry } : entry)
|
|
117
|
+
.filter((entry) => entry && typeof entry.path === 'string' && entry.path.trim())
|
|
118
|
+
.map((entry) => ({
|
|
119
|
+
path: resolve(entry.path),
|
|
120
|
+
registeredAt: typeof entry.registeredAt === 'string'
|
|
121
|
+
? entry.registeredAt
|
|
122
|
+
: new Date(0).toISOString(),
|
|
123
|
+
}))
|
|
124
|
+
.filter((entry) => {
|
|
125
|
+
if (seen.has(entry.path)) return false;
|
|
126
|
+
seen.add(entry.path);
|
|
127
|
+
return true;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function readRaw(filePath) {
|
|
132
|
+
if (!existsSync(filePath)) return { version: APP_DEV_ROOTS_VERSION, roots: [] };
|
|
133
|
+
try {
|
|
134
|
+
return {
|
|
135
|
+
version: APP_DEV_ROOTS_VERSION,
|
|
136
|
+
roots: normalizeRegistry(JSON.parse(readFileSync(filePath, 'utf8'))),
|
|
137
|
+
};
|
|
138
|
+
} catch {
|
|
139
|
+
return { version: APP_DEV_ROOTS_VERSION, roots: [] };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function writeRaw(registry, filePath) {
|
|
144
|
+
mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
|
|
145
|
+
const normalized = {
|
|
146
|
+
version: APP_DEV_ROOTS_VERSION,
|
|
147
|
+
roots: normalizeRegistry(registry),
|
|
148
|
+
};
|
|
149
|
+
const temporary = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
150
|
+
writeFileSync(temporary, JSON.stringify(normalized, null, 2), { mode: 0o600 });
|
|
151
|
+
renameSync(temporary, filePath);
|
|
152
|
+
return normalized;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function migrateLegacyProjectsUnlocked(registry, options) {
|
|
156
|
+
const path = legacyProjectsFile(options.legacySessionsFilePath);
|
|
157
|
+
if (!existsSync(path)) return registry;
|
|
158
|
+
let projects = [];
|
|
159
|
+
try {
|
|
160
|
+
const raw = JSON.parse(readFileSync(path, 'utf8'));
|
|
161
|
+
projects = Array.isArray(raw?.projects) ? raw.projects : [];
|
|
162
|
+
} catch {
|
|
163
|
+
return registry;
|
|
164
|
+
}
|
|
165
|
+
const byPath = new Map(registry.roots.map((entry) => [entry.path, entry]));
|
|
166
|
+
for (const project of projects) {
|
|
167
|
+
if (typeof project?.projectDir !== 'string' || !project.projectDir.trim()) continue;
|
|
168
|
+
try {
|
|
169
|
+
const path = canonicalExistingDirectory(project.projectDir);
|
|
170
|
+
if (path === realpathIfExists(DEFAULT_APP_DEV_ROOT)) continue;
|
|
171
|
+
if (!byPath.has(path)) {
|
|
172
|
+
byPath.set(path, {
|
|
173
|
+
path,
|
|
174
|
+
registeredAt: typeof project.lastMountedAt === 'string'
|
|
175
|
+
? project.lastMountedAt
|
|
176
|
+
: new Date().toISOString(),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
// Deleted or inaccessible legacy projects are intentionally not migrated.
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
rmSync(path, { force: true });
|
|
184
|
+
return { version: APP_DEV_ROOTS_VERSION, roots: [...byPath.values()] };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function realpathIfExists(path) {
|
|
188
|
+
try {
|
|
189
|
+
return realpathSync(path);
|
|
190
|
+
} catch {
|
|
191
|
+
return resolve(path);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function readAppDevRoots(options = {}) {
|
|
196
|
+
const filePath = rootsFile(options.filePath);
|
|
197
|
+
let registry = readRaw(filePath);
|
|
198
|
+
if (options.migrateLegacy !== false) {
|
|
199
|
+
registry = withLock(filePath, () => {
|
|
200
|
+
const before = readRaw(filePath);
|
|
201
|
+
const after = migrateLegacyProjectsUnlocked(before, options);
|
|
202
|
+
const changed = JSON.stringify(before.roots) !== JSON.stringify(after.roots);
|
|
203
|
+
return changed ? writeRaw(after, filePath) : before;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const implicit = realpathIfExists(options.defaultRoot || DEFAULT_APP_DEV_ROOT);
|
|
207
|
+
const roots = [
|
|
208
|
+
{ path: implicit, registeredAt: null, implicit: true },
|
|
209
|
+
...registry.roots
|
|
210
|
+
.filter((entry) => entry.path !== implicit)
|
|
211
|
+
.map((entry) => ({ ...entry, implicit: false })),
|
|
212
|
+
];
|
|
213
|
+
return { version: APP_DEV_ROOTS_VERSION, roots };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function registerAppDevRoot(inputPath, options = {}) {
|
|
217
|
+
const path = canonicalExistingDirectory(inputPath);
|
|
218
|
+
const implicit = realpathIfExists(options.defaultRoot || DEFAULT_APP_DEV_ROOT);
|
|
219
|
+
if (path === implicit) return readAppDevRoots(options);
|
|
220
|
+
const filePath = rootsFile(options.filePath);
|
|
221
|
+
withLock(filePath, () => {
|
|
222
|
+
const registry = readRaw(filePath);
|
|
223
|
+
if (!registry.roots.some((entry) => entry.path === path)) {
|
|
224
|
+
registry.roots.push({ path, registeredAt: new Date().toISOString() });
|
|
225
|
+
writeRaw(registry, filePath);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
return readAppDevRoots({ ...options, migrateLegacy: false });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function removeAppDevRoot(inputPath, options = {}) {
|
|
232
|
+
const path = realpathIfExists(resolve(String(inputPath || '')));
|
|
233
|
+
const implicit = realpathIfExists(options.defaultRoot || DEFAULT_APP_DEV_ROOT);
|
|
234
|
+
if (path === implicit) {
|
|
235
|
+
throw usageError(`The default app development root cannot be removed: ${implicit}`);
|
|
236
|
+
}
|
|
237
|
+
const filePath = rootsFile(options.filePath);
|
|
238
|
+
let removed = false;
|
|
239
|
+
withLock(filePath, () => {
|
|
240
|
+
const registry = readRaw(filePath);
|
|
241
|
+
const next = registry.roots.filter((entry) => entry.path !== path);
|
|
242
|
+
removed = next.length !== registry.roots.length;
|
|
243
|
+
if (removed) writeRaw({ ...registry, roots: next }, filePath);
|
|
244
|
+
});
|
|
245
|
+
return { removed, path, registry: readAppDevRoots({ ...options, migrateLegacy: false }) };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function hasConfig(path) {
|
|
249
|
+
return CONFIG_FILENAMES.some((name) => existsSync(join(path, name)));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function childAppDirs(parent) {
|
|
253
|
+
if (!existsSync(parent)) return [];
|
|
254
|
+
let entries = [];
|
|
255
|
+
try {
|
|
256
|
+
entries = readdirSync(parent, { withFileTypes: true });
|
|
257
|
+
} catch {
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
return entries
|
|
261
|
+
.filter((entry) => entry.isDirectory())
|
|
262
|
+
.filter((entry) => !entry.name.startsWith('.') && !entry.name.startsWith('_'))
|
|
263
|
+
.filter((entry) => entry.name !== 'node_modules')
|
|
264
|
+
.map((entry) => join(parent, entry.name))
|
|
265
|
+
.filter(hasConfig);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function discoverAppProjectsInRoot(inputRoot) {
|
|
269
|
+
const root = realpathIfExists(resolve(inputRoot));
|
|
270
|
+
const candidates = [];
|
|
271
|
+
if (hasConfig(root)) candidates.push(root);
|
|
272
|
+
candidates.push(...childAppDirs(root));
|
|
273
|
+
candidates.push(...childAppDirs(join(root, 'apps')));
|
|
274
|
+
return [...new Set(candidates.map(realpathIfExists))].sort();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function discoverRegisteredAppProjects(options = {}) {
|
|
278
|
+
const roots = readAppDevRoots(options).roots;
|
|
279
|
+
return [...new Set(roots.flatMap((entry) => discoverAppProjectsInRoot(entry.path)))].sort();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function getAppDevRootsFile(filePath) {
|
|
283
|
+
return rootsFile(filePath);
|
|
284
|
+
}
|
|
@@ -28,7 +28,10 @@ import {
|
|
|
28
28
|
readLinkedState,
|
|
29
29
|
writeLinkedState,
|
|
30
30
|
} from './app-platform.js';
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
linkAppDevSessionTarget,
|
|
33
|
+
readAppDevSessions,
|
|
34
|
+
} from './app-dev-sessions.js';
|
|
32
35
|
|
|
33
36
|
const CONTENT_TYPES = {
|
|
34
37
|
'.js': 'application/javascript; charset=utf-8',
|
|
@@ -297,7 +300,7 @@ function renderHarnessHtml({ state, manifest, appConfig, route, harnessOptions,
|
|
|
297
300
|
/**
|
|
298
301
|
* Start the dev server for one or more apps.
|
|
299
302
|
*
|
|
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
|
|
303
|
+
* @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 }, log?: (m: string) => void, logError?: (m: string) => void}} options
|
|
301
304
|
*/
|
|
302
305
|
export async function startAppDevServer({
|
|
303
306
|
apps,
|
|
@@ -313,22 +316,43 @@ export async function startAppDevServer({
|
|
|
313
316
|
}
|
|
314
317
|
|
|
315
318
|
const appState = new Map();
|
|
316
|
-
for
|
|
317
|
-
|
|
319
|
+
// Sidebar reconciliation needs one stream for the shared host, not one
|
|
320
|
+
// long-lived HTTP/1.1 connection per discovered app. Keeping a stream per
|
|
321
|
+
// app exhausts Chromium's per-origin connection pool and can indefinitely
|
|
322
|
+
// queue the active view's bundle fetch during hot reload.
|
|
323
|
+
const hostSseClients = new Set();
|
|
324
|
+
const createAppState = (app) => {
|
|
325
|
+
let resolveBundleReady;
|
|
326
|
+
const bundleReadyPromise = new Promise((resolvePromise) => {
|
|
327
|
+
resolveBundleReady = resolvePromise;
|
|
328
|
+
});
|
|
329
|
+
return {
|
|
318
330
|
slug: app.slug,
|
|
319
331
|
projectDir: app.projectDir,
|
|
320
332
|
appId: app.appId || null,
|
|
321
333
|
targetAppId: app.targetAppId || null,
|
|
322
334
|
userId: app.userId || null,
|
|
335
|
+
profileKey: app.profileKey || null,
|
|
336
|
+
sessionId: app.sessionId || null,
|
|
337
|
+
mountNonce: app.mountNonce || null,
|
|
323
338
|
canonicalBundleDir: getBundleDir(app.projectDir),
|
|
324
339
|
bundleDir: null,
|
|
325
340
|
jsPath: null,
|
|
326
341
|
sseClients: new Set(),
|
|
327
342
|
watcher: null,
|
|
343
|
+
sourceWatchers: [],
|
|
344
|
+
prepareTimer: null,
|
|
345
|
+
reloadTimer: null,
|
|
328
346
|
buildProcess: null,
|
|
329
347
|
lastMtimeMs: 0,
|
|
330
348
|
watchPollTimer: null,
|
|
331
|
-
|
|
349
|
+
bundleReady: false,
|
|
350
|
+
bundleReadyPromise,
|
|
351
|
+
resolveBundleReady,
|
|
352
|
+
};
|
|
353
|
+
};
|
|
354
|
+
for (const app of apps) {
|
|
355
|
+
appState.set(app.slug, createAppState(app));
|
|
332
356
|
}
|
|
333
357
|
|
|
334
358
|
function broadcastReload(slug) {
|
|
@@ -342,6 +366,14 @@ export async function startAppDevServer({
|
|
|
342
366
|
state.sseClients.delete(res);
|
|
343
367
|
}
|
|
344
368
|
}
|
|
369
|
+
const hostPayload = `event: reload\ndata: ${JSON.stringify({ slug, at: Date.now() })}\n\n`;
|
|
370
|
+
for (const res of hostSseClients) {
|
|
371
|
+
try {
|
|
372
|
+
res.write(hostPayload);
|
|
373
|
+
} catch {
|
|
374
|
+
hostSseClients.delete(res);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
345
377
|
}
|
|
346
378
|
|
|
347
379
|
function matchAppRoute(pathname) {
|
|
@@ -374,6 +406,10 @@ export async function startAppDevServer({
|
|
|
374
406
|
state.jsPath = join(bundleDir, 'app.js');
|
|
375
407
|
try {
|
|
376
408
|
state.lastMtimeMs = statSync(state.jsPath).mtimeMs;
|
|
409
|
+
if (!state.bundleReady) {
|
|
410
|
+
state.bundleReady = true;
|
|
411
|
+
state.resolveBundleReady();
|
|
412
|
+
}
|
|
377
413
|
} catch {
|
|
378
414
|
state.lastMtimeMs = 0;
|
|
379
415
|
}
|
|
@@ -451,6 +487,9 @@ export async function startAppDevServer({
|
|
|
451
487
|
try {
|
|
452
488
|
const body = await readRequestJson(req);
|
|
453
489
|
const appId = typeof body.app_id === 'string' ? body.app_id.trim() : '';
|
|
490
|
+
const version = Number.isInteger(body.version) && body.version > 0
|
|
491
|
+
? body.version
|
|
492
|
+
: null;
|
|
454
493
|
if (!appId) {
|
|
455
494
|
res.writeHead(400, {
|
|
456
495
|
...headers,
|
|
@@ -459,20 +498,69 @@ export async function startAppDevServer({
|
|
|
459
498
|
res.end(JSON.stringify({ error: 'app_id is required' }));
|
|
460
499
|
return;
|
|
461
500
|
}
|
|
501
|
+
if (version === null) {
|
|
502
|
+
res.writeHead(400, {
|
|
503
|
+
...headers,
|
|
504
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
505
|
+
});
|
|
506
|
+
res.end(JSON.stringify({ error: 'version must be a positive integer' }));
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const proofSession = readAppDevSessions(sessionsFilePath).sessions.find((session) => (
|
|
510
|
+
typeof body.session_id === 'string'
|
|
511
|
+
&& typeof body.mount_nonce === 'string'
|
|
512
|
+
&& session.sessionId === body.session_id
|
|
513
|
+
&& session.mountNonce === body.mount_nonce
|
|
514
|
+
&& session.devSlug === state.slug
|
|
515
|
+
&& session.projectDir === state.projectDir
|
|
516
|
+
));
|
|
517
|
+
if (!proofSession) {
|
|
518
|
+
res.writeHead(403, {
|
|
519
|
+
...headers,
|
|
520
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
521
|
+
});
|
|
522
|
+
res.end(JSON.stringify({ error: 'invalid app development session proof' }));
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const expectedAppId = proofSession.targetAppId || proofSession.appId;
|
|
526
|
+
if (!expectedAppId || appId !== expectedAppId) {
|
|
527
|
+
res.writeHead(409, {
|
|
528
|
+
...headers,
|
|
529
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
530
|
+
});
|
|
531
|
+
res.end(JSON.stringify({ error: 'app_id does not match this development session' }));
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
462
534
|
|
|
463
|
-
const currentState = readLinkedState(
|
|
535
|
+
const currentState = readLinkedState(
|
|
536
|
+
state.projectDir,
|
|
537
|
+
proofSession.profileKey || state.profileKey,
|
|
538
|
+
) || {};
|
|
464
539
|
const now = new Date().toISOString();
|
|
465
|
-
|
|
540
|
+
const promotedInPlace = proofSession.appId === appId;
|
|
541
|
+
const nextState = {
|
|
466
542
|
...currentState,
|
|
467
543
|
app_id: appId,
|
|
468
|
-
dev_app_id: state.appId || currentState.dev_app_id,
|
|
469
544
|
linked_at: currentState.linked_at || now,
|
|
470
|
-
|
|
545
|
+
version,
|
|
546
|
+
deployed_at: now,
|
|
471
547
|
updated_at: now,
|
|
472
|
-
}
|
|
473
|
-
|
|
548
|
+
};
|
|
549
|
+
if (promotedInPlace) {
|
|
550
|
+
delete nextState.dev_app_id;
|
|
551
|
+
delete nextState.dev_linked_at;
|
|
552
|
+
} else {
|
|
553
|
+
nextState.dev_app_id = proofSession.appId || currentState.dev_app_id;
|
|
554
|
+
nextState.dev_linked_at = currentState.dev_linked_at || now;
|
|
555
|
+
}
|
|
556
|
+
writeLinkedState(
|
|
557
|
+
state.projectDir,
|
|
558
|
+
nextState,
|
|
559
|
+
proofSession.profileKey || state.profileKey,
|
|
560
|
+
);
|
|
474
561
|
linkAppDevSessionTarget({
|
|
475
|
-
|
|
562
|
+
sessionId: proofSession.sessionId,
|
|
563
|
+
appId: proofSession.appId,
|
|
476
564
|
devSlug: state.slug,
|
|
477
565
|
targetAppId: appId,
|
|
478
566
|
lastHeartbeatAt: now,
|
|
@@ -480,8 +568,9 @@ export async function startAppDevServer({
|
|
|
480
568
|
const response = {
|
|
481
569
|
ok: true,
|
|
482
570
|
app_id: appId,
|
|
483
|
-
dev_app_id:
|
|
571
|
+
dev_app_id: promotedInPlace ? null : proofSession.appId || null,
|
|
484
572
|
target_app_id: appId,
|
|
573
|
+
version,
|
|
485
574
|
};
|
|
486
575
|
res.writeHead(200, {
|
|
487
576
|
...headers,
|
|
@@ -525,6 +614,18 @@ export async function startAppDevServer({
|
|
|
525
614
|
return;
|
|
526
615
|
}
|
|
527
616
|
|
|
617
|
+
if (url.pathname === '/events') {
|
|
618
|
+
res.writeHead(200, {
|
|
619
|
+
...headers,
|
|
620
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
621
|
+
Connection: 'keep-alive',
|
|
622
|
+
});
|
|
623
|
+
res.write(': connected\n\n');
|
|
624
|
+
hostSseClients.add(res);
|
|
625
|
+
req.on('close', () => hostSseClients.delete(res));
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
|
|
528
629
|
const routed = matchAppRoute(url.pathname);
|
|
529
630
|
if (!routed) {
|
|
530
631
|
res.writeHead(404, headers);
|
|
@@ -644,15 +745,23 @@ export async function startAppDevServer({
|
|
|
644
745
|
try {
|
|
645
746
|
state.watcher = fsWatch(state.bundleDir, { persistent: false }, (_event, filename) => {
|
|
646
747
|
if (!filename || filename !== 'app.js') return;
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
748
|
+
// Vite may emit the filesystem notification while its output file is
|
|
749
|
+
// still being replaced. Debounce the notification so consumers never
|
|
750
|
+
// fetch the previous or partially-written bundle under the new build
|
|
751
|
+
// revision.
|
|
752
|
+
if (state.reloadTimer) clearTimeout(state.reloadTimer);
|
|
753
|
+
state.reloadTimer = setTimeout(() => {
|
|
754
|
+
state.reloadTimer = null;
|
|
755
|
+
try {
|
|
756
|
+
const stat = statSync(state.jsPath);
|
|
757
|
+
if (stat.mtimeMs === state.lastMtimeMs) return;
|
|
758
|
+
state.lastMtimeMs = stat.mtimeMs;
|
|
759
|
+
log(`[notis apps dev] ${state.slug}: bundle updated — reloading portal`);
|
|
760
|
+
broadcastReload(state.slug);
|
|
761
|
+
} catch {
|
|
762
|
+
// Bundle file may be missing mid-write; the next change retries.
|
|
763
|
+
}
|
|
764
|
+
}, 100);
|
|
656
765
|
});
|
|
657
766
|
} catch (error) {
|
|
658
767
|
logError(`[notis apps dev] ${state.slug}: watch failed: ${error.message}`);
|
|
@@ -669,9 +778,53 @@ export async function startAppDevServer({
|
|
|
669
778
|
state.watchPollTimer = setTimeout(() => pollForBundleAndWatch(state), 300);
|
|
670
779
|
}
|
|
671
780
|
|
|
781
|
+
function watchManifestInputs(state) {
|
|
782
|
+
const schedulePrepare = () => {
|
|
783
|
+
if (state.prepareTimer) clearTimeout(state.prepareTimer);
|
|
784
|
+
state.prepareTimer = setTimeout(() => {
|
|
785
|
+
state.prepareTimer = null;
|
|
786
|
+
void prepareArtifactBuild(state.projectDir).then(() => {
|
|
787
|
+
// Name, icon, and route metadata can change without changing the
|
|
788
|
+
// compiled JS bytes. Notify consumers immediately; a generated-entry
|
|
789
|
+
// change will produce the normal second reload after Vite rebuilds.
|
|
790
|
+
broadcastReload(state.slug);
|
|
791
|
+
}).catch((error) => {
|
|
792
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
793
|
+
logError(`[notis apps dev] ${state.slug}: ${message}`);
|
|
794
|
+
});
|
|
795
|
+
}, 75);
|
|
796
|
+
};
|
|
797
|
+
try {
|
|
798
|
+
state.sourceWatchers.push(fsWatch(
|
|
799
|
+
state.projectDir,
|
|
800
|
+
{ persistent: false },
|
|
801
|
+
(_event, filename) => {
|
|
802
|
+
if (filename && /^notis\.config\.(?:ts|js|mjs)$/.test(String(filename))) {
|
|
803
|
+
schedulePrepare();
|
|
804
|
+
}
|
|
805
|
+
},
|
|
806
|
+
));
|
|
807
|
+
} catch (error) {
|
|
808
|
+
logError(`[notis apps dev] ${state.slug}: config watch failed: ${error.message}`);
|
|
809
|
+
}
|
|
810
|
+
const appDir = join(state.projectDir, 'app');
|
|
811
|
+
if (existsSync(appDir)) {
|
|
812
|
+
try {
|
|
813
|
+
state.sourceWatchers.push(fsWatch(
|
|
814
|
+
appDir,
|
|
815
|
+
{ persistent: false, recursive: process.platform === 'darwin' || process.platform === 'win32' },
|
|
816
|
+
schedulePrepare,
|
|
817
|
+
));
|
|
818
|
+
} catch (error) {
|
|
819
|
+
logError(`[notis apps dev] ${state.slug}: route watch failed: ${error.message}`);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
672
824
|
for (const state of appState.values()) {
|
|
673
825
|
if (watch) {
|
|
674
826
|
await prepareArtifactBuild(state.projectDir);
|
|
827
|
+
watchManifestInputs(state);
|
|
675
828
|
pollForBundleAndWatch(state);
|
|
676
829
|
|
|
677
830
|
state.buildProcess = spawn('npm', ['run', 'build', '--', '--watch'], {
|
|
@@ -694,8 +847,37 @@ export async function startAppDevServer({
|
|
|
694
847
|
|
|
695
848
|
return {
|
|
696
849
|
port,
|
|
850
|
+
updateApp(slug, updates = {}) {
|
|
851
|
+
const state = appState.get(slug);
|
|
852
|
+
if (!state) throw new Error(`unknown app: ${slug}`);
|
|
853
|
+
for (const key of ['appId', 'targetAppId', 'userId', 'profileKey', 'sessionId', 'mountNonce']) {
|
|
854
|
+
if (Object.prototype.hasOwnProperty.call(updates, key)) {
|
|
855
|
+
state[key] = updates[key] || null;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
},
|
|
859
|
+
isBundleReady(slug) {
|
|
860
|
+
const state = appState.get(slug);
|
|
861
|
+
if (!state) throw new Error(`unknown app: ${slug}`);
|
|
862
|
+
return state.bundleReady;
|
|
863
|
+
},
|
|
864
|
+
waitForBundle(slug) {
|
|
865
|
+
const state = appState.get(slug);
|
|
866
|
+
if (!state) return Promise.reject(new Error(`unknown app: ${slug}`));
|
|
867
|
+
return state.bundleReadyPromise;
|
|
868
|
+
},
|
|
697
869
|
async close() {
|
|
698
870
|
for (const state of appState.values()) {
|
|
871
|
+
if (state.prepareTimer) clearTimeout(state.prepareTimer);
|
|
872
|
+
if (state.reloadTimer) clearTimeout(state.reloadTimer);
|
|
873
|
+
for (const watcher of state.sourceWatchers) {
|
|
874
|
+
try {
|
|
875
|
+
watcher.close();
|
|
876
|
+
} catch {
|
|
877
|
+
// ignore
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
state.sourceWatchers = [];
|
|
699
881
|
if (state.watchPollTimer) clearTimeout(state.watchPollTimer);
|
|
700
882
|
if (state.watcher) {
|
|
701
883
|
try {
|
|
@@ -716,6 +898,14 @@ export async function startAppDevServer({
|
|
|
716
898
|
state.buildProcess.kill('SIGTERM');
|
|
717
899
|
}
|
|
718
900
|
}
|
|
901
|
+
for (const res of hostSseClients) {
|
|
902
|
+
try {
|
|
903
|
+
res.end();
|
|
904
|
+
} catch {
|
|
905
|
+
// ignore
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
hostSseClients.clear();
|
|
719
909
|
await new Promise((resolvePromise) => server.close(() => resolvePromise()));
|
|
720
910
|
},
|
|
721
911
|
};
|