anear-js-api 1.8.0 → 2.0.1
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 +48 -0
- package/lib/api/AnearApi.js +18 -0
- package/lib/ci/publishAssets.js +35 -0
- package/lib/ci/registerAppVersion.js +43 -0
- package/lib/state_machines/AnearCoreServiceMachine.js +29 -5
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -115,6 +115,31 @@ ANEARAPP_API_VERSION=v1
|
|
|
115
115
|
ANEARAPP_API_URL=http://api.lvh.me:3001/developer # Optional, for local development
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
+
### Local Dev Quickstart (no Docker)
|
|
119
|
+
|
|
120
|
+
To keep development behavior close to today:
|
|
121
|
+
|
|
122
|
+
1. Keep runtime asset publishing enabled (default):
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
unset ANEARAPP_SKIP_RUNTIME_ASSET_SYNC
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
2. Start your app process normally:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npm install
|
|
132
|
+
node index.js
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
3. If your ANAPI environment is not Rails `development`, set:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
export ANEARAPP_SINGLE_LATEST_VERSION=true
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
This preserves a single mutable `latest` app version for iterative development.
|
|
142
|
+
|
|
118
143
|
### Developer API Authentication
|
|
119
144
|
|
|
120
145
|
`anear-js-api` communicates with the **ANAPI Developer API** (`/developer/v1/*` endpoints) using JWT authentication:
|
|
@@ -263,6 +288,29 @@ The JSAPI automatically handles asset management:
|
|
|
263
288
|
2. **Image Upload**: All images in `assets/images/` are uploaded to CloudFront
|
|
264
289
|
3. **Template Preloading**: All PUG templates are preloaded and cached
|
|
265
290
|
|
|
291
|
+
### CI-Managed Assets Mode
|
|
292
|
+
|
|
293
|
+
For containerized deployments, you can publish assets during CI and skip runtime asset sync:
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
ANEARAPP_SKIP_RUNTIME_ASSET_SYNC=true
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
When this variable is enabled, `AnearCoreServiceMachine` skips image/font/CSS upload states and proceeds directly to template compilation and event lifecycle listening.
|
|
300
|
+
|
|
301
|
+
CI helpers are available under:
|
|
302
|
+
|
|
303
|
+
- `anear-js-api/lib/ci/publishAssets.js`
|
|
304
|
+
- `anear-js-api/lib/ci/registerAppVersion.js`
|
|
305
|
+
|
|
306
|
+
### Versioned Lifecycle Channels
|
|
307
|
+
|
|
308
|
+
ACSM subscribes to versioned lifecycle channels derived from ANAPI app metadata:
|
|
309
|
+
|
|
310
|
+
`anear:<appId>:v:<appVersion>:e`
|
|
311
|
+
|
|
312
|
+
ANAPI must return `latest_app_version` in developer app payloads for startup channel resolution.
|
|
313
|
+
|
|
266
314
|
### Asset Structure
|
|
267
315
|
|
|
268
316
|
```
|
package/lib/api/AnearApi.js
CHANGED
|
@@ -80,6 +80,24 @@ class AnearApi extends ApiService {
|
|
|
80
80
|
return attrs
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
async createAppVersion(appId, attrs) {
|
|
84
|
+
logger.debug(`API: POST app_versions for app ${appId}`)
|
|
85
|
+
const json = await this.post("app_versions", attrs, { app: appId })
|
|
86
|
+
return json.data
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async updateAppVersion(appVersionId, attrs) {
|
|
90
|
+
logger.debug(`API: PUT app_versions/${appVersionId}`)
|
|
91
|
+
const json = await this.put("app_versions", appVersionId, attrs)
|
|
92
|
+
return json.data
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async getLatestAppVersion(appId) {
|
|
96
|
+
logger.debug(`API: GET apps/${appId}/latest_app_version`)
|
|
97
|
+
const json = await this.get(`apps/${appId}/latest_app_version`)
|
|
98
|
+
return json.data
|
|
99
|
+
}
|
|
100
|
+
|
|
83
101
|
async saveAppEventContext(eventId, appmContext) {
|
|
84
102
|
logger.debug(`API: POST developer events/${eventId}/app_event_context`)
|
|
85
103
|
const path = `events/${eventId}/app_event_context`
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
const C = require('../utils/Constants')
|
|
4
|
+
const ImageAssetsUploader = require('../utils/ImageAssetsUploader')
|
|
5
|
+
const FontAssetsUploader = require('../utils/FontAssetsUploader')
|
|
6
|
+
const CssUploader = require('../utils/CssUploader')
|
|
7
|
+
|
|
8
|
+
async function publishAssetsFromCi(appId = process.env.ANEARAPP_APP_ID) {
|
|
9
|
+
if (!appId) {
|
|
10
|
+
throw new Error('ANEARAPP_APP_ID must be set to publish assets from CI')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const imageAssetsUploader = new ImageAssetsUploader(C.ImagesDirPath, appId)
|
|
14
|
+
const imageAssetsUrl = await imageAssetsUploader.uploadAssets()
|
|
15
|
+
|
|
16
|
+
const fontAssetsUploader = new FontAssetsUploader(C.FontsDirPath, appId)
|
|
17
|
+
const fontAssetsUrl = await fontAssetsUploader.uploadAssets()
|
|
18
|
+
|
|
19
|
+
const cssUploader = new CssUploader(
|
|
20
|
+
C.CssDirPath,
|
|
21
|
+
imageAssetsUrl,
|
|
22
|
+
fontAssetsUrl,
|
|
23
|
+
appId
|
|
24
|
+
)
|
|
25
|
+
const cssUrl = await cssUploader.uploadCss()
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
appId,
|
|
29
|
+
imageAssetsUrl,
|
|
30
|
+
fontAssetsUrl,
|
|
31
|
+
cssUrl
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { publishAssetsFromCi }
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
const AnearApi = require('../api/AnearApi')
|
|
4
|
+
|
|
5
|
+
async function registerAppVersionFromCi({
|
|
6
|
+
appId = process.env.ANEARAPP_APP_ID,
|
|
7
|
+
version = process.env.APP_VERSION,
|
|
8
|
+
gitSha = process.env.GITHUB_SHA,
|
|
9
|
+
imageUri = process.env.IMAGE_URI,
|
|
10
|
+
assetManifestHash = process.env.ASSET_MANIFEST_HASH,
|
|
11
|
+
status = 'building'
|
|
12
|
+
} = {}) {
|
|
13
|
+
if (!appId || !version) {
|
|
14
|
+
throw new Error('ANEARAPP_APP_ID and APP_VERSION are required to register an app version')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const data = await AnearApi.createAppVersion(appId, {
|
|
18
|
+
version,
|
|
19
|
+
status,
|
|
20
|
+
git_sha: gitSha,
|
|
21
|
+
image_uri: imageUri,
|
|
22
|
+
asset_manifest_hash: assetManifestHash
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return data
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function markAppVersionStatus(appVersionId, status, extraAttrs = {}) {
|
|
29
|
+
if (!appVersionId || !status) {
|
|
30
|
+
throw new Error('appVersionId and status are required to update app version status')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const data = await AnearApi.updateAppVersion(appVersionId, {
|
|
34
|
+
status,
|
|
35
|
+
...extraAttrs
|
|
36
|
+
})
|
|
37
|
+
return data
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
registerAppVersionFromCi,
|
|
42
|
+
markAppVersionStatus
|
|
43
|
+
}
|
|
@@ -58,8 +58,9 @@ const ImageAssetsUploader = require('../utils/ImageAssetsUploader')
|
|
|
58
58
|
const FontAssetsUploader = require('../utils/FontAssetsUploader')
|
|
59
59
|
const C = require('../utils/Constants')
|
|
60
60
|
|
|
61
|
-
const
|
|
61
|
+
const CreateVersionedEventChannelNameTemplate = (appId, appVersion) => `anear:${appId}:v:${normalizeVersionToken(appVersion)}:e`
|
|
62
62
|
const DefaultTemplatesRootDir = "./views"
|
|
63
|
+
const SkipRuntimeAssetSync = process.env.ANEARAPP_SKIP_RUNTIME_ASSET_SYNC === 'true'
|
|
63
64
|
|
|
64
65
|
// Process-wide signal handling.
|
|
65
66
|
// We install exactly once per process to avoid duplicate handlers in reconnect/restart scenarios.
|
|
@@ -152,9 +153,21 @@ const AnearCoreServiceMachineConfig = appId => ({
|
|
|
152
153
|
actions: ({ context }) => logger.warn(`[ACSM] Realtime connected, but shutdown already requested for app ${context.appId}; not subscribing to CREATE/LOAD commands`)
|
|
153
154
|
}
|
|
154
155
|
],
|
|
155
|
-
ATTACHED: '
|
|
156
|
+
ATTACHED: 'syncAssetsOrSkip'
|
|
156
157
|
}
|
|
157
158
|
},
|
|
159
|
+
syncAssetsOrSkip: {
|
|
160
|
+
always: [
|
|
161
|
+
{
|
|
162
|
+
guard: 'runtimeAssetSyncDisabled',
|
|
163
|
+
actions: () => logger.info('[ACSM] Runtime asset sync disabled; assuming CI has already published assets'),
|
|
164
|
+
target: 'loadAndCompilePugTemplates'
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
target: 'uploadNewImageAssets'
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
},
|
|
158
171
|
uploadNewImageAssets: {
|
|
159
172
|
invoke: {
|
|
160
173
|
src: 'uploadNewImageAssets',
|
|
@@ -298,7 +311,13 @@ const AnearCoreServiceMachineFunctions = {
|
|
|
298
311
|
createEventsCreationChannel: assign(
|
|
299
312
|
{
|
|
300
313
|
newEventCreationChannel: ({ context, self }) => {
|
|
301
|
-
const
|
|
314
|
+
const attrs = context.appData?.data?.attributes || {}
|
|
315
|
+
const appVersion = attrs['latest-app-version']
|
|
316
|
+
if (!appVersion) {
|
|
317
|
+
throw new Error('[ACSM] Missing latest-app-version in app data; cannot subscribe to lifecycle channel')
|
|
318
|
+
}
|
|
319
|
+
const channelName = CreateVersionedEventChannelNameTemplate(context.appId, appVersion)
|
|
320
|
+
logger.info(`[ACSM] Subscribing to versioned lifecycle channel ${channelName}`)
|
|
302
321
|
return RealtimeMessaging.getChannel(channelName, self)
|
|
303
322
|
}
|
|
304
323
|
}
|
|
@@ -314,7 +333,7 @@ const AnearCoreServiceMachineFunctions = {
|
|
|
314
333
|
self,
|
|
315
334
|
'LOAD_EVENT'
|
|
316
335
|
)
|
|
317
|
-
logger.info(`[ACSM] Subscribed to CREATE_EVENT and LOAD_EVENT on
|
|
336
|
+
logger.info(`[ACSM] Subscribed to CREATE_EVENT and LOAD_EVENT on versioned lifecycle channel (pid=${process.pid})`)
|
|
318
337
|
},
|
|
319
338
|
logIgnoredLifecycleCommand: ({ event, context }) => {
|
|
320
339
|
logger.warn(`[ACSM] Ignoring ${event.type} because shutdown has been requested (appId=${context.appId} pid=${process.pid})`)
|
|
@@ -436,10 +455,15 @@ const AnearCoreServiceMachineFunctions = {
|
|
|
436
455
|
},
|
|
437
456
|
guards: {
|
|
438
457
|
noImageAssetFilesFound: ({ event }) => event.output === null,
|
|
439
|
-
acceptLifecycleCommands: ({ context }) => !context.shutdownRequested
|
|
458
|
+
acceptLifecycleCommands: ({ context }) => !context.shutdownRequested,
|
|
459
|
+
runtimeAssetSyncDisabled: () => SkipRuntimeAssetSync
|
|
440
460
|
}
|
|
441
461
|
}
|
|
442
462
|
|
|
463
|
+
function normalizeVersionToken(value) {
|
|
464
|
+
return String(value).replace(/[^A-Za-z0-9_.-]/g, '_')
|
|
465
|
+
}
|
|
466
|
+
|
|
443
467
|
const AnearCoreServiceMachine = (appEventMachineFactory, appParticipantMachineFactory = null) => {
|
|
444
468
|
const appId = process.env.ANEARAPP_APP_ID
|
|
445
469
|
const machineConfig = AnearCoreServiceMachineConfig(appId)
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anear-js-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Javascript Developer API for Anear Apps",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "jest"
|
|
7
|
+
"test": "jest",
|
|
8
|
+
"audit:prod": "npm audit --omit=dev",
|
|
9
|
+
"audit:prod:fix": "npm audit fix --omit=dev",
|
|
10
|
+
"audit:full": "npm audit",
|
|
11
|
+
"audit:full:fix": "npm audit fix"
|
|
8
12
|
},
|
|
9
13
|
"repository": {
|
|
10
14
|
"type": "git",
|