@toa.io/operations 1.0.0-alpha.28 → 1.0.0-alpha.280

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +204 -0
  2. package/image/runtime.Dockerfile +4 -0
  3. package/package.json +10 -10
  4. package/readme.md +16 -0
  5. package/src/deployment/.deployment/.describe/components.js +1 -5
  6. package/src/deployment/.deployment/.describe/compositions.js +8 -7
  7. package/src/deployment/.deployment/.describe/dependencies.js +1 -6
  8. package/src/deployment/.deployment/.describe/events.js +63 -0
  9. package/src/deployment/.deployment/.describe/index.js +4 -13
  10. package/src/deployment/.deployment/.describe/mounts.js +20 -0
  11. package/src/deployment/.deployment/.describe/resources.js +33 -0
  12. package/src/deployment/.deployment/.describe/services.js +28 -5
  13. package/src/deployment/.deployment/.describe/services.test.js +61 -0
  14. package/src/deployment/.deployment/.describe/variables.js +6 -9
  15. package/src/deployment/.deployment/declare.js +1 -5
  16. package/src/deployment/.deployment/describe.js +133 -9
  17. package/src/deployment/.deployment/index.js +3 -9
  18. package/src/deployment/.deployment/merge.js +46 -6
  19. package/src/deployment/.deployment/merge.test.js +72 -0
  20. package/src/deployment/chart/templates/compositions.yaml +70 -0
  21. package/src/deployment/chart/templates/mono.yaml +181 -0
  22. package/src/deployment/chart/templates/services.yaml +75 -4
  23. package/src/deployment/chart/values.yaml +11 -1
  24. package/src/deployment/composition.js +2 -5
  25. package/src/deployment/deployment.js +19 -12
  26. package/src/deployment/factory.js +50 -28
  27. package/src/deployment/images/composition.Dockerfile +2 -2
  28. package/src/deployment/images/composition.js +26 -22
  29. package/src/deployment/images/factory.js +15 -10
  30. package/src/deployment/images/format.js +48 -0
  31. package/src/deployment/images/format.test.js +74 -0
  32. package/src/deployment/images/image.fixtures.js +11 -11
  33. package/src/deployment/images/image.js +20 -22
  34. package/src/deployment/images/image.test.js +6 -5
  35. package/src/deployment/images/index.js +1 -5
  36. package/src/deployment/images/mono.Dockerfile +18 -0
  37. package/src/deployment/images/mono.js +72 -0
  38. package/src/deployment/images/runtime-base.test.js +91 -0
  39. package/src/deployment/images/service.Dockerfile +3 -3
  40. package/src/deployment/images/service.js +8 -8
  41. package/src/deployment/index.js +1 -5
  42. package/src/deployment/operator.js +6 -6
  43. package/src/deployment/operator.test.js +8 -7
  44. package/src/deployment/registry.js +66 -22
  45. package/src/deployment/registry.test.js +177 -0
  46. package/src/deployment/service.js +1 -5
  47. package/src/deployment/workspace.js +13 -8
  48. package/src/index.js +1 -3
  49. package/src/process.js +2 -8
  50. package/src/process.test.js +20 -0
  51. package/types/_deployment/composition.d.ts +1 -1
  52. package/types/_deployment/dependency.d.ts +3 -1
  53. package/types/_deployment/deployment.d.ts +3 -3
  54. package/types/_deployment/factory.d.ts +2 -2
  55. package/types/_deployment/images/factory.d.ts +2 -2
  56. package/types/_deployment/images/image.d.ts +4 -2
  57. package/types/_deployment/images/registry.d.ts +2 -2
  58. package/types/_deployment/operator.d.ts +2 -2
  59. package/types/_deployment/registry.d.ts +10 -8
  60. package/types/_deployment/service.d.ts +4 -1
  61. package/types/dependency.ts +73 -0
  62. package/types/index.ts +1 -0
  63. package/types/dependency.d.ts +0 -39
  64. package/types/index.d.ts +0 -1
@@ -5,6 +5,16 @@ metadata:
5
5
  name: extension-{{ required "deployment name is required" .name }}
6
6
  spec:
7
7
  replicas: {{ .replicas | default 2 }}
8
+ # A backstop for stuck rollouts only. Must stay above the `helm upgrade --timeout`
9
+ # the deploy runs with, so a slow but progressing rollout is not marked failed.
10
+ progressDeadlineSeconds: 900
11
+ strategy:
12
+ type: RollingUpdate
13
+ rollingUpdate:
14
+ # a replacement must pass readiness before a running replica is taken out,
15
+ # otherwise a single pod serves all traffic while the others start up
16
+ maxUnavailable: 0
17
+ maxSurge: 50%
8
18
  selector:
9
19
  matchLabels:
10
20
  toa.io/service: extension-{{ .name }}
@@ -16,6 +26,25 @@ spec:
16
26
  containers:
17
27
  - name: extension-{{ .name }}
18
28
  image: {{ .image }}
29
+ {{- if .resources }}
30
+ resources:
31
+ {{- if or .resources.cpu .resources.memory }}
32
+ requests:
33
+ {{- if .resources.cpu }}
34
+ cpu: {{ index .resources.cpu 0 }}
35
+ {{- end }}
36
+ {{- if .resources.memory }}
37
+ memory: {{ index .resources.memory 0 }}
38
+ {{- end }}
39
+ limits:
40
+ {{- if .resources.cpu }}
41
+ cpu: {{ index .resources.cpu 1 }}
42
+ {{- end }}
43
+ {{- if .resources.memory }}
44
+ memory: {{ index .resources.memory 1 }}
45
+ {{- end }}
46
+ {{- end }}
47
+ {{- end }}
19
48
  {{- if .variables }}
20
49
  env:
21
50
  {{- range .variables }}
@@ -23,19 +52,46 @@ spec:
23
52
  {{- end }}
24
53
  {{- end }}
25
54
  {{- if .probe }}
26
- readinessProbe:
55
+ startupProbe:
27
56
  httpGet:
28
57
  path: {{ .probe.path }}
29
58
  port: {{ .probe.port }}
30
59
  {{- if .probe.delay }}
31
60
  initialDelaySeconds: {{ .probe.delay }}
32
61
  {{- end }}
62
+ periodSeconds: 2
63
+ timeoutSeconds: 3
64
+ failureThreshold: 150
65
+ readinessProbe:
66
+ httpGet:
67
+ path: {{ .probe.path }}
68
+ port: {{ .probe.port }}
69
+ periodSeconds: 10
70
+ timeoutSeconds: 3
71
+ failureThreshold: 3
33
72
  {{- end }}
73
+ lifecycle:
74
+ preStop:
75
+ exec:
76
+ command: [sleep, "5"]
77
+ terminationGracePeriodSeconds: 45
78
+ topologySpreadConstraints:
79
+ - maxSkew: 1
80
+ topologyKey: kubernetes.io/hostname
81
+ whenUnsatisfiable: ScheduleAnyway
82
+ labelSelector:
83
+ matchLabels:
84
+ toa.io/service: extension-{{ .name }}
85
+ {{- if .port }}
34
86
  ---
35
87
  apiVersion: v1
36
88
  kind: Service
37
89
  metadata:
38
90
  name: extension-{{ .name }}
91
+ {{- if .annotations }}
92
+ annotations:
93
+ {{ toYaml .annotations | indent 4 }}
94
+ {{- end }}
39
95
  spec:
40
96
  type: ClusterIP
41
97
  selector:
@@ -45,9 +101,12 @@ spec:
45
101
  protocol: TCP
46
102
  port: {{ .port }}
47
103
  targetPort: {{ .port }}
48
- ---
104
+ {{- end }}
49
105
  {{- if .ingress }}
50
106
  {{- $service := .name }}
107
+ {{- $port := .port }}
108
+ {{- $path := .ingress.path | default "/" }}
109
+ ---
51
110
  apiVersion: networking.k8s.io/v1
52
111
  kind: Ingress
53
112
  metadata:
@@ -65,13 +124,25 @@ spec:
65
124
  - host: {{ . }}
66
125
  http:
67
126
  paths:
68
- - path: /
127
+ - path: {{ $path }}
128
+ pathType: Prefix
129
+ backend:
130
+ service:
131
+ name: extension-{{ $service }}
132
+ port:
133
+ number: {{ $port }}
134
+ {{- end }}
135
+ {{- if .ingress.default }}
136
+ - http:
137
+ paths:
138
+ - path: {{ $path }}
69
139
  pathType: Prefix
70
140
  backend:
71
141
  service:
72
142
  name: extension-{{ $service }}
73
143
  port:
74
- number: 8000
144
+ number: {{ $port }}
75
145
  {{- end }}
76
146
  {{- end }}
147
+ ---
77
148
  {{- end }}
@@ -3,9 +3,16 @@ compositions:
3
3
  - name: todos
4
4
  image: localhost:5000/composition-todos:0.0.0
5
5
  replicas: 2
6
+ mounts:
7
+ - name: mount-name
8
+ path: /storage
9
+ claim: storage-pvc
6
10
  components:
7
11
  - todos-tasks
8
12
  - todos-stats
13
+ resources:
14
+ cpu: [100m, 1]
15
+ memory: [100Mi, 1Gi]
9
16
  variables:
10
17
  - name: TOA_CONFIGURATION_TODOS_TASKS
11
18
  value: foo
@@ -18,7 +25,6 @@ compositions:
18
25
  replicas: 3
19
26
  components:
20
27
  - users-users
21
- # TODO: create component services only if sync binding is being used
22
28
  components:
23
29
  - todos-tasks
24
30
  - todos-stats
@@ -28,7 +34,11 @@ services:
28
34
  image: localhost:5000/resources-gateway:0.0.0
29
35
  port: 8000
30
36
  replicas: 2
37
+ resources:
38
+ cpu: [100m, 1]
39
+ memory: [100Mi, 1Gi]
31
40
  ingress:
41
+ default: true
32
42
  hosts: [dummies.toa.io]
33
43
  class: alb
34
44
  annotations:
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
- class Composition {
1
+ export class Composition {
4
2
  name
5
3
  image
6
4
  /** @type {string[]} */
@@ -10,9 +8,8 @@ class Composition {
10
8
  this.name = composition.name
11
9
  this.image = image.reference
12
10
  this.components = composition.components.map(component)
11
+ this.resources = composition.resources
13
12
  }
14
13
  }
15
14
 
16
15
  const component = (component) => component.locator.label
17
-
18
- exports.Composition = Composition
@@ -1,23 +1,21 @@
1
- 'use strict'
1
+ import { join } from 'node:path'
2
+ import { writeFile as write } from 'node:fs/promises'
3
+ import { yaml as jsyaml } from '@toa.io/generic'
4
+ import fs from 'fs-extra'
2
5
 
3
- const { join } = require('node:path')
4
- const { writeFile: write } = require('node:fs/promises')
5
- const { dump } = require('@toa.io/yaml')
6
- const fs = require('fs-extra')
6
+ import { merge, declare, describe } from './.deployment/index.js'
7
7
 
8
- const { merge, declare, describe } = require('./.deployment')
9
-
10
- class Deployment {
8
+ export class Deployment {
11
9
  #chart
12
10
  #values
13
11
  #process
14
12
  #target
15
13
 
16
- constructor (context, compositions, dependencies, process) {
14
+ constructor (context, compositions, dependencies, process, image) {
17
15
  const dependency = merge(dependencies)
18
16
 
19
17
  this.#chart = declare(context, dependency)
20
- this.#values = describe(context, compositions, dependency)
18
+ this.#values = describe(context, compositions, dependency, image)
21
19
  this.#process = process
22
20
  }
23
21
 
@@ -42,6 +40,7 @@ class Deployment {
42
40
 
43
41
  if (options.namespace !== undefined) args.push('-n', options.namespace)
44
42
  if (options.wait === true) args.push('--wait')
43
+ if (options.timeout !== undefined) args.push('--timeout', options.timeout)
45
44
 
46
45
  await this.#process.execute('helm', ['dependency', 'update', this.#target])
47
46
  await this.#process.execute('helm', ['upgrade', this.#chart.name, '-i', ...args, this.#target])
@@ -68,6 +67,9 @@ class Deployment {
68
67
  addVariables(this.#values.compositions, variables, used)
69
68
  addVariables(this.#values.services, variables, used)
70
69
 
70
+ if (this.#values.mono !== undefined)
71
+ addVariables([this.#values.mono], variables, used)
72
+
71
73
  return variables
72
74
  }
73
75
  }
@@ -85,6 +87,11 @@ function addVariables (list, variables, used = new Set()) {
85
87
  }
86
88
  }
87
89
 
88
- const TEMPLATES = join(__dirname, 'chart/templates')
90
+ const TEMPLATES = join(import.meta.dirname, 'chart/templates')
91
+
89
92
 
90
- exports.Deployment = Deployment
93
+
94
+ function dump (object) {
95
+ // js-yaml writes plain objects only, and the values carry locators and images
96
+ return jsyaml.dump(JSON.parse(JSON.stringify(object)), { noRefs: true, lineWidth: -1 })
97
+ }
@@ -1,35 +1,56 @@
1
- 'use strict'
2
-
3
- const { context: load } = require('@toa.io/norm')
4
- const { Process } = require('../process')
5
- const { Operator } = require('./operator')
6
- const { Factory: ImagesFactory } = require('./images')
7
- const { Deployment } = require('./deployment')
8
- const { Registry } = require('./registry')
9
- const { Composition } = require('./composition')
10
- const { Service } = require('./service')
11
-
12
- class Factory {
1
+ import { readFileSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { createRequire } from 'node:module'
4
+ import { pathToFileURL } from 'node:url'
5
+ import { context as load } from '@toa.io/norm'
6
+ import { Process } from '../process.js'
7
+ import { Operator } from './operator.js'
8
+ import { Factory as ImagesFactory } from './images/index.js'
9
+ import { Deployment } from './deployment.js'
10
+
11
+ // a dependency is resolved the way a package is
12
+ const require = createRequire(import.meta.url)
13
+ import { Registry } from './registry.js'
14
+ import { Composition } from './composition.js'
15
+ import { Service } from './service.js'
16
+
17
+ export class Factory {
13
18
  #context
19
+ #mono
14
20
  #compositions
15
21
  #dependencies
16
22
  #registry
17
23
  #process
18
- #extensionComponents = []
24
+ #image
19
25
 
20
- constructor (context) {
26
+ constructor (context, options = {}) {
21
27
  this.#context = context
28
+ this.#mono = options.mono === true
22
29
  this.#process = new Process()
23
30
 
24
31
  const imagesFactory = new ImagesFactory(context.name, context.runtime, context.registry)
25
32
 
26
- this.#registry = new Registry(context.registry, imagesFactory, this.#process)
27
- this.#compositions = context.compositions.map((composition) => this.#composition(composition))
33
+ this.#registry = new Registry(context.name, context.registry, imagesFactory, this.#process)
28
34
  this.#dependencies = this.#getDependencies()
35
+ this.#compositions = []
36
+
37
+ if (this.#mono)
38
+ this.#image = this.#registry.mono({
39
+ components: context.components
40
+ })
41
+ else
42
+ this.#compositions = context.compositions.map((composition) => this.#composition(composition))
29
43
  }
30
44
 
31
- operator () {
32
- const deployment = new Deployment(this.#context, this.#compositions, this.#dependencies, this.#process)
45
+ async operator () {
46
+ const deployment = new Deployment(
47
+ this.#context,
48
+ this.#compositions,
49
+ // the constructor cannot await, so what it started is settled here
50
+ await this.#dependencies,
51
+ this.#process,
52
+ this.#image
53
+ )
33
54
 
34
55
  return new Operator(deployment, this.#registry)
35
56
  }
@@ -44,14 +65,14 @@ class Factory {
44
65
  return new Composition(composition, image)
45
66
  }
46
67
 
47
- #getDependencies () {
68
+ async #getDependencies () {
48
69
  /** @type {toa.deployment.Dependency[]} */
49
70
  const dependencies = []
50
71
 
51
72
  if (this.#context.dependencies === undefined) return dependencies
52
73
 
53
74
  for (const [reference, instances] of Object.entries(this.#context.dependencies)) {
54
- const dependency = this.#getDependency(reference, instances)
75
+ const dependency = await this.#getDependency(reference, instances)
55
76
 
56
77
  if (dependency !== undefined) dependencies.push(dependency)
57
78
  }
@@ -59,9 +80,11 @@ class Factory {
59
80
  return dependencies
60
81
  }
61
82
 
62
- #getDependency (path, instances) {
63
- const module = require(path)
64
- const pkg = require(path + '/package.json')
83
+ async #getDependency (reference, instances) {
84
+ // a dependency may be named the way a package is or written as a directory,
85
+ // and a module is loaded by file
86
+ const module = await import(pathToFileURL(require.resolve(reference)).href)
87
+ const pkg = JSON.parse(readFileSync(require.resolve(join(reference, 'package.json')), 'utf8'))
65
88
 
66
89
  if (module.deployment === undefined) return
67
90
 
@@ -71,7 +94,8 @@ class Factory {
71
94
  const dependency = module.deployment(instances, annotation)
72
95
 
73
96
  /** @type {toa.deployment.Service[]} */
74
- const services = dependency.services?.map((service) => this.#service(path, service))
97
+ const services = dependency.services?.map((service) =>
98
+ this.#mono ? service : this.#service(reference, service))
75
99
 
76
100
  return { ...dependency, services }
77
101
  }
@@ -87,11 +111,9 @@ class Factory {
87
111
  return new Service(service, image)
88
112
  }
89
113
 
90
- static async create (path, environment) {
114
+ static async create (path, environment, options = {}) {
91
115
  const context = await load(path, environment)
92
116
 
93
- return new Factory(context)
117
+ return new Factory(context, options)
94
118
  }
95
119
  }
96
-
97
- exports.Factory = Factory
@@ -5,7 +5,6 @@ FROM {{build.image}}
5
5
  ENV NODE_ENV=production
6
6
  RUN if [ "{{runtime.registry}}" != "" ]; then npm set registry {{runtime.registry}}; fi
7
7
  RUN if [ "{{runtime.proxy}}" != "" ]; then npm set proxy {{runtime.proxy}}; fi
8
- RUN npm i -g @toa.io/runtime@{{runtime.version}} --omit=dev
9
8
 
10
9
  WORKDIR /composition
11
10
  COPY --chown=node:node . /composition
@@ -13,7 +12,8 @@ COPY --chown=node:node . /composition
13
12
  {{build.run}}
14
13
 
15
14
  # run 'npm i' in each component
16
- RUN for entry in *; do if [ -f "$entry/package.json" ]; then (cd $entry && npm i --omit=dev); fi; done
15
+ RUN --mount=type=cache,target=/root/.npm \
16
+ for entry in *; do if grep -qs '"dependencies"' "$entry/package.json"; then (cd $entry && npm i --omit=dev); fi; done
17
17
 
18
18
  USER node
19
19
  CMD toa compose *
@@ -1,13 +1,12 @@
1
- 'use strict'
1
+ import { join } from 'node:path'
2
+ import fs from 'fs-extra'
3
+ import { createHash } from 'node:crypto'
2
4
 
3
- const { join } = require('node:path')
4
- const fs = require('fs-extra')
5
- const { createHash } = require('node:crypto')
5
+ import { Image } from './image.js'
6
+ import { declare } from './format.js'
6
7
 
7
- const { Image } = require('./image')
8
-
9
- class Composition extends Image {
10
- dockerfile = join(__dirname, 'composition.Dockerfile')
8
+ export class Composition extends Image {
9
+ dockerfile = join(import.meta.dirname, 'composition.Dockerfile')
11
10
 
12
11
  #name
13
12
  #image
@@ -37,34 +36,39 @@ class Composition extends Image {
37
36
  }
38
37
 
39
38
  get base () {
40
- if (this.#image !== undefined) {
41
- return this.#image
42
- }
39
+ if (this.#image !== undefined) return this.#image
43
40
 
44
- let image = null
41
+ const images = new Set(this.#components.map((component) => component.build?.image))
45
42
 
46
- for (const component of this.#components) {
47
- const value = component.build?.image
43
+ if (images.size > 1)
44
+ throw new Error(`Composition '${this.#name}' requires different base images for its components. Specify base image for the composition in the context.`)
45
+
46
+ return images.values().next().value
47
+ }
48
48
 
49
- if (image !== null && image !== value) {
50
- throw new Error(`Composition '${this.#name}' requires different base images for its components. Specify base image for the composition in the context.`)
51
- }
49
+ get run () {
50
+ const commands = []
52
51
 
53
- image = value
52
+ for (const component of this.#components) {
53
+ const run = component.build?.run
54
+
55
+ if (run !== undefined)
56
+ commands.push(run)
54
57
  }
55
58
 
56
- return image ?? undefined
59
+ return commands.join('\n')
57
60
  }
58
61
 
59
62
  async prepare (root) {
60
63
  const context = await super.prepare(root)
61
64
 
62
65
  for (const component of this.#components) {
63
- await fs.copy(component.path, join(context, component.locator.label))
66
+ const target = join(context, component.locator.label)
67
+
68
+ await fs.copy(component.path, target)
69
+ await declare(component.path, target, component.locator.label)
64
70
  }
65
71
 
66
72
  return context
67
73
  }
68
74
  }
69
-
70
- exports.Composition = Composition
@@ -1,12 +1,8 @@
1
- 'use strict'
1
+ import { Composition } from './composition.js'
2
+ import { Service } from './service.js'
3
+ import { Mono } from './mono.js'
2
4
 
3
- const { Composition } = require('./composition')
4
- const { Service } = require('./service')
5
-
6
- /**
7
- * @implements {toa.deployment.images.Factory}
8
- */
9
- class Factory {
5
+ export class Factory {
10
6
  /** @type {string} */
11
7
  #scope
12
8
 
@@ -48,6 +44,15 @@ class Factory {
48
44
 
49
45
  return instance
50
46
  }
51
- }
52
47
 
53
- exports.Factory = Factory
48
+ /**
49
+ * @returns {Mono}
50
+ */
51
+ mono (composition) {
52
+ const instance = new Mono(this.#scope, this.#runtime, this.#registry, composition)
53
+
54
+ instance.tag()
55
+
56
+ return instance
57
+ }
58
+ }
@@ -0,0 +1,48 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { writeFile } from 'node:fs/promises'
3
+ import { dirname, join, parse } from 'node:path'
4
+
5
+ /**
6
+ * A component is copied into the image on its own, away from the package that
7
+ * declared what its files are. Node reads a `.js` as CommonJS unless a manifest
8
+ * beside it says otherwise, so the one the component was written under is
9
+ * restated where it lands.
10
+ *
11
+ * A component that ships its own manifest already says so, and is left alone.
12
+ *
13
+ * @param {string} source the component in the workspace
14
+ * @param {string} target where it was copied
15
+ * @param {string} name
16
+ */
17
+ export async function declare (source, target, name) {
18
+ if (existsSync(join(target, MANIFEST))) return
19
+
20
+ await writeFile(join(target, MANIFEST),
21
+ JSON.stringify({ name, private: true, type: format(source) }, null, 2) + '\n')
22
+ }
23
+
24
+ /**
25
+ * The module format a directory's files are read as, which the nearest manifest
26
+ * above it decides.
27
+ *
28
+ * @param {string} directory
29
+ * @returns {'module' | 'commonjs'}
30
+ */
31
+ export function format (directory) {
32
+ const { root } = parse(directory)
33
+
34
+ let current = directory
35
+
36
+ while (current !== root) {
37
+ const manifest = join(current, MANIFEST)
38
+
39
+ if (existsSync(manifest))
40
+ return JSON.parse(readFileSync(manifest, 'utf8')).type === 'module' ? 'module' : 'commonjs'
41
+
42
+ current = dirname(current)
43
+ }
44
+
45
+ return 'commonjs'
46
+ }
47
+
48
+ const MANIFEST = 'package.json'
@@ -0,0 +1,74 @@
1
+ import { it, describe, before, after } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { mkdtemp, mkdir, writeFile, readFile, rm } from 'node:fs/promises'
4
+ import { tmpdir } from 'node:os'
5
+ import { join } from 'node:path'
6
+
7
+ import { declare, format } from './format.js'
8
+
9
+ let root
10
+
11
+ before(async () => {
12
+ root = await mkdtemp(join(tmpdir(), 'format-'))
13
+ })
14
+
15
+ after(async () => {
16
+ await rm(root, { recursive: true, force: true })
17
+ })
18
+
19
+ describe('format', () => {
20
+ it('should read the nearest manifest above', async () => {
21
+ const workspace = join(root, 'modules')
22
+ const component = join(workspace, 'components', 'one')
23
+
24
+ await mkdir(component, { recursive: true })
25
+ await writeFile(join(workspace, 'package.json'), JSON.stringify({ type: 'module' }))
26
+
27
+ assert.strictEqual(format(component), 'module')
28
+ })
29
+
30
+ it('should default to commonjs', async () => {
31
+ const workspace = join(root, 'scripts')
32
+ const component = join(workspace, 'components', 'one')
33
+
34
+ await mkdir(component, { recursive: true })
35
+ await writeFile(join(workspace, 'package.json'), JSON.stringify({ name: 'scripts' }))
36
+
37
+ assert.strictEqual(format(component), 'commonjs')
38
+ })
39
+ })
40
+
41
+ describe('declare', () => {
42
+ it('should state the format the component was written under', async () => {
43
+ const workspace = join(root, 'stated')
44
+ const source = join(workspace, 'components', 'one')
45
+ const target = join(root, 'image', 'one')
46
+
47
+ await mkdir(source, { recursive: true })
48
+ await mkdir(target, { recursive: true })
49
+ await writeFile(join(workspace, 'package.json'), JSON.stringify({ type: 'module' }))
50
+
51
+ await declare(source, target, 'default-one')
52
+
53
+ const manifest = JSON.parse(await readFile(join(target, 'package.json'), 'utf8'))
54
+
55
+ assert.strictEqual(manifest.type, 'module')
56
+ assert.strictEqual(manifest.name, 'default-one')
57
+ assert.strictEqual(manifest.private, true)
58
+ })
59
+
60
+ it('should leave a manifest the component ships alone', async () => {
61
+ const source = join(root, 'own', 'components', 'one')
62
+ const target = join(root, 'image', 'own')
63
+
64
+ await mkdir(source, { recursive: true })
65
+ await mkdir(target, { recursive: true })
66
+
67
+ const own = JSON.stringify({ name: 'own', dependencies: { matchacho: '0.6.0' } })
68
+
69
+ await writeFile(join(target, 'package.json'), own)
70
+ await declare(source, target, 'default-own')
71
+
72
+ assert.strictEqual(await readFile(join(target, 'package.json'), 'utf8'), own)
73
+ })
74
+ })
@@ -1,7 +1,5 @@
1
- 'use strict'
2
-
3
- const { Image } = require('./image')
4
- const { generate } = require('randomstring')
1
+ import { Image } from './image.js'
2
+ import { generate } from 'randomstring'
5
3
 
6
4
  const version = '168b04ff'
7
5
  const name = generate()
@@ -29,10 +27,12 @@ const registry = {
29
27
  base: 'node:alpine'
30
28
  }
31
29
 
32
- exports.scope = generate()
33
- exports.name = name
34
- exports.version = 'ba2409fc'
35
- exports.Class = Class
36
- exports.runtime = runtime
37
- exports.registry = registry
38
- exports.process = process
30
+ export const scope = generate()
31
+
32
+ // distinct from the module's own `version` above
33
+ const published = 'ba2409fc'
34
+
35
+ // the fixture stands in for the global process
36
+ const current = process
37
+
38
+ export { name, Class, runtime, registry, current as process, published as version }