amorphous-cli 0.3.9 → 0.3.11
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/lib/agent-contract.test.mjs +2 -0
- package/lib/config.mjs +39 -9
- package/lib/config.test.mjs +79 -0
- package/package.json +1 -1
- package/skill/amorphous/SKILL.md +3 -3
|
@@ -54,6 +54,8 @@ test('setup copies the skill, and an edited contract is left alone', () => {
|
|
|
54
54
|
assert.match(skill, /View members can open the live abstract and load its guides/)
|
|
55
55
|
assert.match(skill, /Use members can use the live abstract and submit source recommendations/)
|
|
56
56
|
assert.match(skill, /Only owners and editors can pull and edit source/)
|
|
57
|
+
assert.match(skill, /Only push, preview, publish, invite, or notify when the person asks for that action/i)
|
|
58
|
+
assert.match(skill, /Use `publish --live` only when they explicitly ask to make the version live/i)
|
|
57
59
|
assert.match(skill, /visibility anyone/i)
|
|
58
60
|
assert.match(skill, /anyone can add shared records/i)
|
|
59
61
|
assert.match(skill, /Do not run context again during this task/)
|
package/lib/config.mjs
CHANGED
|
@@ -1,16 +1,46 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from 'node:fs'
|
|
2
2
|
import { homedir } from 'node:os'
|
|
3
|
-
import { join } from 'node:path'
|
|
3
|
+
import { dirname, join, relative, resolve, sep } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
const CLI_PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
7
|
+
|
|
8
|
+
function isWithin(root, path) {
|
|
9
|
+
const fromRoot = relative(resolve(root), resolve(path))
|
|
10
|
+
return fromRoot === '' || (fromRoot !== '..' && !fromRoot.startsWith(`..${sep}`))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function localRepositoryRoot(packageRoot) {
|
|
14
|
+
const root = resolve(packageRoot, '..')
|
|
15
|
+
try {
|
|
16
|
+
const appPackage = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
|
|
17
|
+
const cliPackage = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'))
|
|
18
|
+
if (appPackage.name === 'amorphous' && cliPackage.name === 'amorphous-cli' && resolve(root, 'cli') === resolve(packageRoot)) {
|
|
19
|
+
return root
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
/* The published CLI has no enclosing Amorphous app repository. */
|
|
23
|
+
}
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function loadDotEnv(cwd = process.cwd(), options = {}) {
|
|
28
|
+
const packageRoot = options.packageRoot || CLI_PACKAGE_ROOT
|
|
29
|
+
const explicitRoot = options.amorphousRoot ?? process.env.AMORPHOUS_ROOT
|
|
6
30
|
const roots = []
|
|
7
|
-
if (
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
31
|
+
if (explicitRoot) {
|
|
32
|
+
roots.push(explicitRoot)
|
|
33
|
+
} else {
|
|
34
|
+
const repoRoot = localRepositoryRoot(packageRoot)
|
|
35
|
+
if (!repoRoot || !isWithin(repoRoot, cwd)) return
|
|
36
|
+
let dir = resolve(cwd)
|
|
37
|
+
for (let i = 0; i < 8; i++) {
|
|
38
|
+
roots.push(dir)
|
|
39
|
+
if (dir === repoRoot) break
|
|
40
|
+
const parent = dirname(dir)
|
|
41
|
+
if (parent === dir || !isWithin(repoRoot, parent)) break
|
|
42
|
+
dir = parent
|
|
43
|
+
}
|
|
14
44
|
}
|
|
15
45
|
for (const base of roots) {
|
|
16
46
|
const path = join(base, '.env')
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import test from 'node:test'
|
|
6
|
+
import { loadDotEnv } from './config.mjs'
|
|
7
|
+
|
|
8
|
+
function withEnv(values, run) {
|
|
9
|
+
const previous = Object.fromEntries(Object.keys(values).map((name) => [name, process.env[name]]))
|
|
10
|
+
for (const [name, value] of Object.entries(values)) {
|
|
11
|
+
if (value === undefined) delete process.env[name]
|
|
12
|
+
else process.env[name] = value
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
return run()
|
|
16
|
+
} finally {
|
|
17
|
+
for (const [name, value] of Object.entries(previous)) {
|
|
18
|
+
if (value === undefined) delete process.env[name]
|
|
19
|
+
else process.env[name] = value
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
test('published CLI ignores an unrelated project .env by default', () => {
|
|
25
|
+
const root = mkdtempSync(join(tmpdir(), 'amorphous-config-'))
|
|
26
|
+
const project = join(root, 'project')
|
|
27
|
+
const packageRoot = join(root, 'npm-cache', 'amorphous-cli')
|
|
28
|
+
mkdirSync(project, { recursive: true })
|
|
29
|
+
mkdirSync(packageRoot, { recursive: true })
|
|
30
|
+
writeFileSync(join(project, '.env'), 'APP_ORIGIN=https://amorphous.test\n')
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
withEnv({ APP_ORIGIN: undefined, AMORPHOUS_ROOT: undefined }, () => {
|
|
34
|
+
loadDotEnv(project, { packageRoot })
|
|
35
|
+
assert.equal(process.env.APP_ORIGIN, undefined)
|
|
36
|
+
})
|
|
37
|
+
} finally {
|
|
38
|
+
rmSync(root, { recursive: true, force: true })
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('repository source CLI loads the repository .env for local development', () => {
|
|
43
|
+
const root = mkdtempSync(join(tmpdir(), 'amorphous-config-'))
|
|
44
|
+
const cliRoot = join(root, 'cli')
|
|
45
|
+
mkdirSync(cliRoot, { recursive: true })
|
|
46
|
+
writeFileSync(join(root, 'package.json'), JSON.stringify({ name: 'amorphous' }))
|
|
47
|
+
writeFileSync(join(cliRoot, 'package.json'), JSON.stringify({ name: 'amorphous-cli' }))
|
|
48
|
+
writeFileSync(join(root, '.env'), 'APP_ORIGIN=https://amorphous.test\n')
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
withEnv({ APP_ORIGIN: undefined, AMORPHOUS_ROOT: undefined }, () => {
|
|
52
|
+
loadDotEnv(root, { packageRoot: cliRoot })
|
|
53
|
+
assert.equal(process.env.APP_ORIGIN, 'https://amorphous.test')
|
|
54
|
+
})
|
|
55
|
+
} finally {
|
|
56
|
+
rmSync(root, { recursive: true, force: true })
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('AMORPHOUS_ROOT explicitly opts a published CLI into a local .env', () => {
|
|
61
|
+
const root = mkdtempSync(join(tmpdir(), 'amorphous-config-'))
|
|
62
|
+
const project = join(root, 'project')
|
|
63
|
+
const localRoot = join(root, 'local-app')
|
|
64
|
+
const packageRoot = join(root, 'npm-cache', 'amorphous-cli')
|
|
65
|
+
mkdirSync(project, { recursive: true })
|
|
66
|
+
mkdirSync(localRoot, { recursive: true })
|
|
67
|
+
mkdirSync(packageRoot, { recursive: true })
|
|
68
|
+
writeFileSync(join(localRoot, '.env'), 'APP_ORIGIN=https://amorphous.test\n')
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
withEnv({ APP_ORIGIN: undefined, AMORPHOUS_ROOT: undefined }, () => {
|
|
72
|
+
loadDotEnv(project, { packageRoot, amorphousRoot: localRoot })
|
|
73
|
+
assert.equal(process.env.APP_ORIGIN, 'https://amorphous.test')
|
|
74
|
+
})
|
|
75
|
+
} finally {
|
|
76
|
+
rmSync(root, { recursive: true, force: true })
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
package/package.json
CHANGED
package/skill/amorphous/SKILL.md
CHANGED
|
@@ -36,11 +36,11 @@ A private record belongs to the signed-in person who wrote it. The abstract does
|
|
|
36
36
|
|
|
37
37
|
After `setup`, invite them to describe what they want made. Build it for them.
|
|
38
38
|
Prefer `amorphous-cli serve` while you edit. Open the URL and leave it running. It reloads when the files change, and it is the easiest way for them to look at the work.
|
|
39
|
-
|
|
39
|
+
Only push, preview, publish, invite, or notify when the person asks for that action. Use `publish --live` only when they explicitly ask to make the version live.
|
|
40
40
|
|
|
41
41
|
## Start
|
|
42
42
|
|
|
43
|
-
Run CLI commands as `npx --yes amorphous-cli@latest <command>`; this uses the latest CLI without a global install. The command examples below abbreviate that prefix as `amorphous-cli`. If a command prints `Run: npx --yes amorphous-cli@latest setup`, run that line, then repeat the command. If you are not signed in, run `npx --yes amorphous-cli@latest login`. Show the person the link it prints and ask them to finish in the browser. Do not ask for an email or a code.
|
|
43
|
+
Run CLI commands as `npx --yes amorphous-cli@latest <command>`; this uses the latest CLI without a global install. The published CLI ignores `.env` files in the current project and uses the hosted app. To explicitly use local settings, set `AMORPHOUS_ROOT` to the local Amorphous repo root. The command examples below abbreviate that prefix as `amorphous-cli`. If a command prints `Run: npx --yes amorphous-cli@latest setup`, run that line, then repeat the command. If you are not signed in, run `npx --yes amorphous-cli@latest login`. Show the person the link it prints and ask them to finish in the browser. Do not ask for an email or a code.
|
|
44
44
|
|
|
45
45
|
Then choose the path that matches the request:
|
|
46
46
|
|
|
@@ -61,7 +61,7 @@ When they ask:
|
|
|
61
61
|
- `amorphous-cli push` sends the source. `amorphous-cli push --data` also sends the records in this folder.
|
|
62
62
|
- `amorphous-cli guide save abstract` explicitly saves changes to the shared guide (owner/editor only). `amorphous-cli guide save private` explicitly saves this signed-in person's private guide. A stale revision is refused; reload context and resolve the conflict before saving. Ordinary `push`, `recommend`, and `publish` never save or include either guide.
|
|
63
63
|
- `amorphous-cli preview` and open that URL.
|
|
64
|
-
- `amorphous-cli publish
|
|
64
|
+
- `amorphous-cli publish` creates a version. Add `--live` only when they explicitly ask to make that version live.
|
|
65
65
|
- Collaborators who should propose source for the owner to review can use `amorphous-cli recommend --note "..."`. It does not change the live abstract; the owner reviews and chooses Publish live or Decline.
|
|
66
66
|
- `amorphous-cli invite <email> use` or `edit` or `view`. It emails them a link and prints that link. They open it and sign in with that email.
|
|
67
67
|
- `amorphous-cli notify` emails members that the published version changed. It does not publish. Do that only when they ask.
|